├── .gitignore ├── LICENSE ├── README.md ├── bin ├── server.bat └── server.sh ├── conf ├── container.properties ├── containerServer.xml ├── log4j.properties └── web.xml ├── docs ├── 0.0.1 │ └── Netty+Spring的web控制器框架.docx └── 基于Netty的web控制器框架.docx ├── pom.xml └── src ├── main ├── java │ └── cn │ │ └── lechange │ │ └── happor │ │ ├── ControllerRegistry.java │ │ ├── ControllerScanner.java │ │ ├── HapporChannelInitializer.java │ │ ├── HapporContext.java │ │ ├── HapporWebserver.java │ │ ├── HttpRootController.java │ │ ├── WebserverHandler.java │ │ ├── annotation │ │ ├── Controller.java │ │ ├── DefaultController.java │ │ ├── Filter.java │ │ ├── UriParam.java │ │ └── UriSection.java │ │ ├── container │ │ ├── ContainerPath.java │ │ ├── JarContainerServer.java │ │ ├── JarImporter.java │ │ └── ServerMain.java │ │ ├── context │ │ ├── HapporAutomaticContext.java │ │ ├── HapporManualContext.java │ │ ├── HapporMultipleContext.java │ │ └── HapporSpringContext.java │ │ ├── controller │ │ ├── HttpAsyncHandler.java │ │ ├── HttpController.java │ │ ├── HttpNormalFilter.java │ │ ├── HttpNormalHandler.java │ │ └── HttpTransitHandler.java │ │ ├── springtags │ │ ├── HapporNamespaceHandler.java │ │ ├── HapporServerElement.java │ │ └── TagHapporServerParser.java │ │ └── utils │ │ ├── AsyncHttpClient.java │ │ ├── PackageUtil.java │ │ └── UriParser.java └── resources │ ├── META-INF │ ├── spring.handlers │ └── spring.schemas │ └── conf │ └── springtags.xsd └── test └── java └── cn └── lechange └── happor ├── Test.java ├── TestAnnotation.java ├── TestJarStub.java ├── TestMultiple.java ├── TestWebserverHandler.java ├── TestWithoutSpring.java └── controllers ├── DefaultHandler.java ├── TestAsyncHandler.java ├── TestIncomingFilter.java ├── TestNormalHandler.java └── TestTransitHandler.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ping.X 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # happor 2 | A netty-spring-based web controller framework. 3 | 4 | I consider that Netty bound with SpringMVC could be a very nice web MVC framework instead of Tomcat + Servlet + SpringMVC. 5 | 6 | However, SpringMVC is based on Servlet and, it's hard to dettach Servlet from SpringMVC. In some implements, we may only focus on controller usage, such as RESTful API develop. 7 | 8 | Things can be simplification. By using Spring IoC to make a controller framework based on Netty without Servlet, I setup this project. 9 | 10 | #A hello-world demo 11 | ```Java 12 | @Controller(method="GET", uriPattern="^/test/(\\w+)") 13 | public class Test extends HttpNormalHandler { 14 | 15 | @UriSection(1) 16 | private String name; 17 | 18 | @Override 19 | protected void handle(FullHttpRequest request, FullHttpResponse response) { 20 | // TODO Auto-generated method stub 21 | String words = "hello " + name; 22 | response.content().writeBytes(words.getBytes()); 23 | response.headers().set("Content-Type", "text/plain"); 24 | response.headers().set("Content-Length", response.content().readableBytes()); 25 | } 26 | 27 | @Override 28 | protected void atlast() { 29 | // TODO Auto-generated method stub 30 | 31 | } 32 | 33 | public static void main(String[] args) { 34 | // TODO Auto-generated method stub 35 | HapporAutomaticContext context = new HapporAutomaticContext(); 36 | context.runServer(); 37 | } 38 | 39 | } 40 | ``` 41 | 42 | Run and visit `http://localhost/test/someone`, the browser will show `hello someone`. 43 | -------------------------------------------------------------------------------- /bin/server.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | if "%1"=="" java -jar happor.jar 3 | if "%1"=="reload" java -jar happor.jar reload 4 | -------------------------------------------------------------------------------- /bin/server.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | if [ "$1" == "" ]; then 3 | java -server -jar happor.jar 4 | elif [ "$1" = "reload" ]; then 5 | java -jar happor.jar reload 6 | fi 7 | -------------------------------------------------------------------------------- /conf/container.properties: -------------------------------------------------------------------------------- 1 | # 2 | # container config 3 | # 4 | 5 | container=default,test 6 | 7 | .default.path= 8 | .default.jar=C:\\Users\\Administrator.USER-20140913DL\\Desktop\\ht.jar 9 | 10 | .test.path=aaa 11 | .test.jar=D:\\github\\happortest\\happortest\\target\\happortest-0.0.1-SNAPSHOT.jar 12 | -------------------------------------------------------------------------------- /conf/containerServer.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /conf/log4j.properties: -------------------------------------------------------------------------------- 1 | # LOG LEVEL: DEBUG 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 28 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /docs/0.0.1/Netty+Spring的web控制器框架.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xphh/happor/e9ee6e05cca87dbafda6d024df1b4b03c6cf01cf/docs/0.0.1/Netty+Spring的web控制器框架.docx -------------------------------------------------------------------------------- /docs/基于Netty的web控制器框架.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xphh/happor/e9ee6e05cca87dbafda6d024df1b4b03c6cf01cf/docs/基于Netty的web控制器框架.docx -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | cn.lechange 6 | happor 7 | 0.4.3-Trunk 8 | jar 9 | 10 | happor 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | io.netty 20 | netty-all 21 | 4.0.23.Final 22 | 23 | 24 | org.springframework 25 | spring-core 26 | 3.2.2.RELEASE 27 | 28 | 29 | org.springframework 30 | spring-beans 31 | 3.2.2.RELEASE 32 | 33 | 34 | org.springframework 35 | spring-context 36 | 3.2.2.RELEASE 37 | 38 | 39 | log4j 40 | log4j 41 | 1.2.17 42 | 43 | 44 | 45 | 46 | ${project.artifactId} 47 | 48 | 49 | org.apache.maven.plugins 50 | maven-jar-plugin 51 | 2.5 52 | 53 | 54 | 55 | true 56 | lib 57 | cn.lechange.happor.container.ServerMain 58 | 59 | 60 | ${project.build.directory}/package 61 | 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-dependency-plugin 66 | 67 | 68 | copy 69 | package 70 | 71 | copy-dependencies 72 | 73 | 74 | ${project.build.directory}/package/lib 75 | 76 | 77 | 78 | 79 | 80 | org.apache.maven.plugins 81 | maven-surefire-plugin 82 | 83 | true 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-antrun-plugin 89 | 1.6 90 | 91 | 92 | copy-files 93 | package 94 | 95 | 96 | 99 | 102 | 105 | 108 | 111 | 112 | 113 | 114 | run 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/ControllerRegistry.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor; 2 | 3 | import cn.lechange.happor.controller.HttpController; 4 | 5 | public class ControllerRegistry { 6 | 7 | private String method; 8 | private String uriPattern; 9 | private String className; 10 | private Class clazz; 11 | 12 | public String getMethod() { 13 | return method; 14 | } 15 | 16 | public void setMethod(String method) { 17 | this.method = method; 18 | } 19 | 20 | public String getUriPattern() { 21 | return uriPattern; 22 | } 23 | 24 | public void setUriPattern(String uriPattern) { 25 | this.uriPattern = uriPattern; 26 | } 27 | 28 | public String getClassName() { 29 | return className; 30 | } 31 | 32 | public void setClassName(String className) { 33 | this.className = className; 34 | } 35 | 36 | public void setClazz(Class clazz) { 37 | this.clazz = clazz; 38 | this.className = clazz.getName(); 39 | } 40 | 41 | public Class getClazz() { 42 | return clazz; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/ControllerScanner.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import org.apache.log4j.Logger; 9 | 10 | import cn.lechange.happor.annotation.Controller; 11 | import cn.lechange.happor.annotation.DefaultController; 12 | import cn.lechange.happor.annotation.Filter; 13 | import cn.lechange.happor.controller.HttpController; 14 | import cn.lechange.happor.utils.PackageUtil; 15 | 16 | public class ControllerScanner { 17 | 18 | private static Logger logger = Logger.getLogger(ControllerScanner.class); 19 | 20 | public static void main(String[] args) { 21 | ControllerScanner cs = new ControllerScanner(); 22 | cs.scan(""); 23 | for (ControllerRegistry r : cs.getHandlers()) { 24 | System.out.println(r.getClassName()); 25 | } 26 | for (Map.Entry entry : cs.getFilters().entrySet()) { 27 | System.out.println(entry.getKey() + " => " + entry.getValue().getClassName()); 28 | } 29 | } 30 | 31 | private List handlers = new ArrayList(); 32 | private Map filters = new HashMap(); 33 | private Class defaultClazz; 34 | 35 | public void scan(String packageName) { 36 | scan(Thread.currentThread().getContextClassLoader(), packageName); 37 | } 38 | 39 | @SuppressWarnings("unchecked") 40 | public void scan(ClassLoader classLoader, String packageName) { 41 | List list = PackageUtil.getClassName(classLoader, packageName); 42 | for (String className : list) { 43 | try { 44 | Class clazz = classLoader.loadClass(className); 45 | if (HttpController.class.isAssignableFrom(clazz)) { 46 | if (clazz.isAnnotationPresent(Controller.class)) { 47 | Controller anno = clazz.getAnnotation(Controller.class); 48 | ControllerRegistry registry = new ControllerRegistry(); 49 | registry.setClazz((Class) clazz); 50 | registry.setMethod(anno.method()); 51 | registry.setUriPattern(anno.uriPattern()); 52 | if (clazz.isAnnotationPresent(Filter.class)) { 53 | Filter annoFilter = clazz.getAnnotation(Filter.class); 54 | filters.put(annoFilter.value(), registry); 55 | } else { 56 | handlers.add(registry); 57 | } 58 | } else if (clazz.isAnnotationPresent(DefaultController.class)) { 59 | defaultClazz = (Class) clazz; 60 | } 61 | } 62 | } catch (ClassNotFoundException e) { 63 | logger.error(e); 64 | } 65 | } 66 | if (defaultClazz != null) { 67 | ControllerRegistry registry = new ControllerRegistry(); 68 | registry.setClazz(defaultClazz); 69 | handlers.add(registry); 70 | } 71 | } 72 | 73 | public List getHandlers() { 74 | return handlers; 75 | } 76 | 77 | public Map getFilters() { 78 | return filters; 79 | } 80 | 81 | public ControllerRegistry getFilter(String name) { 82 | return filters.get(name); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/HapporChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.socket.SocketChannel; 7 | import io.netty.handler.codec.http.HttpObjectAggregator; 8 | import io.netty.handler.codec.http.HttpRequestDecoder; 9 | import io.netty.handler.codec.http.HttpResponseEncoder; 10 | import io.netty.handler.timeout.IdleStateHandler; 11 | 12 | public class HapporChannelInitializer extends 13 | ChannelInitializer { 14 | 15 | private HapporWebserver server; 16 | 17 | public HapporChannelInitializer(HapporWebserver server) { 18 | this.server = server; 19 | } 20 | 21 | @Override 22 | protected void initChannel(SocketChannel ch) throws Exception { 23 | int timeout = server.getTimeout(); 24 | // TODO Auto-generated method stub 25 | ch.pipeline().addLast(new HttpRequestDecoder()) 26 | .addLast(new HttpResponseEncoder()) 27 | .addLast(new HttpObjectAggregator(server.getMaxHttpSize())) 28 | .addLast(new IdleStateHandler(timeout, timeout, timeout, TimeUnit.SECONDS)) 29 | .addLast(new HttpRootController(server)); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/HapporContext.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | import org.apache.log4j.Logger; 7 | import cn.lechange.happor.annotation.Controller; 8 | import cn.lechange.happor.controller.HttpController; 9 | 10 | public abstract class HapporContext { 11 | 12 | private static Logger logger = Logger.getLogger(HapporContext.class); 13 | 14 | private HapporWebserver server; 15 | private Map controllers = new LinkedHashMap(); 16 | private WebserverHandler webserverHandler; 17 | 18 | public void printInfo() { 19 | logger.info("HapporContext = " + this); 20 | for (Map.Entry entry : controllers 21 | .entrySet()) { 22 | logger.info(entry.getKey() + "[" + entry.getValue().getMethod() 23 | + " " + entry.getValue().getUriPattern() + "]"); 24 | } 25 | logger.info("webserverHandler = " + webserverHandler); 26 | } 27 | 28 | public void runServer() { 29 | printInfo(); 30 | server.loadContext(this); 31 | server.startup(); 32 | } 33 | 34 | public HapporContext setServer(HapporWebserver server) { 35 | this.server = server; 36 | return this; 37 | } 38 | 39 | public HapporWebserver getServer() { 40 | return server; 41 | } 42 | 43 | public HapporContext setWebserverHandler(WebserverHandler handler) { 44 | webserverHandler = handler; 45 | return this; 46 | } 47 | 48 | public WebserverHandler getWebserverHandler() { 49 | return webserverHandler; 50 | } 51 | 52 | public Map getControllers() { 53 | return controllers; 54 | } 55 | 56 | public HapporContext addController(Class clazz, 57 | String method, String uriPattern) { 58 | String name = "controller#" + controllers.size() + "_" + clazz.getName(); 59 | ControllerRegistry registry = new ControllerRegistry(); 60 | registry.setClazz(clazz); 61 | registry.setMethod(method); 62 | registry.setUriPattern(uriPattern); 63 | controllers.put(name, registry); 64 | return this; 65 | } 66 | 67 | public HapporContext addController(Class clazz) { 68 | if (clazz.isAnnotationPresent(Controller.class)) { 69 | Controller anno = clazz.getAnnotation(Controller.class); 70 | addController(clazz, anno.method(), anno.uriPattern()); 71 | } else { 72 | addController(clazz, null, null); 73 | } 74 | return this; 75 | } 76 | 77 | public HapporContext addController(ControllerRegistry registry) { 78 | String name = "controller#" + controllers.size() + "_" + registry.getClassName(); 79 | controllers.put(name, registry); 80 | return this; 81 | } 82 | 83 | public void clearControllers() { 84 | controllers.clear(); 85 | } 86 | 87 | public abstract HttpController getController(Class clazz); 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/HapporWebserver.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.locks.ReadWriteLock; 5 | import java.util.concurrent.locks.ReentrantReadWriteLock; 6 | 7 | import io.netty.bootstrap.ServerBootstrap; 8 | import io.netty.channel.ChannelFuture; 9 | import io.netty.channel.ChannelOption; 10 | import io.netty.channel.EventLoopGroup; 11 | import io.netty.channel.nio.NioEventLoopGroup; 12 | import io.netty.channel.socket.nio.NioServerSocketChannel; 13 | import io.netty.handler.codec.http.HttpRequest; 14 | import io.netty.handler.logging.LogLevel; 15 | import io.netty.handler.logging.LoggingHandler; 16 | 17 | import org.apache.log4j.BasicConfigurator; 18 | import org.apache.log4j.Level; 19 | import org.apache.log4j.LogManager; 20 | import org.apache.log4j.Logger; 21 | import org.apache.log4j.helpers.NullEnumeration; 22 | 23 | import cn.lechange.happor.utils.AsyncHttpClient; 24 | 25 | public class HapporWebserver { 26 | 27 | private static Logger logger = Logger.getLogger(HapporWebserver.class); 28 | 29 | static { 30 | if (LogManager.getRootLogger().getAllAppenders() instanceof NullEnumeration) { 31 | BasicConfigurator.configure(); 32 | LogManager.getRootLogger().setLevel(Level.INFO); 33 | } 34 | } 35 | 36 | private int port = 80; 37 | private int executeThreads = 0; 38 | private int maxHttpSize = 1000000; 39 | private int timeout = 3; 40 | 41 | public int getPort() { 42 | return port; 43 | } 44 | public void setPort(int port) { 45 | this.port = port; 46 | } 47 | public int getExecuteThreads() { 48 | return executeThreads; 49 | } 50 | public void setExecuteThreads(int executeThreads) { 51 | this.executeThreads = executeThreads; 52 | } 53 | public int getMaxHttpSize() { 54 | return maxHttpSize; 55 | } 56 | public void setMaxHttpSize(int maxHttpSize) { 57 | this.maxHttpSize = maxHttpSize; 58 | } 59 | public int getTimeout() { 60 | return timeout; 61 | } 62 | public void setTimeout(int timeout) { 63 | this.timeout = timeout; 64 | } 65 | 66 | private ReadWriteLock contextLock = new ReentrantReadWriteLock(); 67 | 68 | private Map pathContexts; 69 | private HapporContext context; 70 | 71 | public void loadContext(HapporContext context) { 72 | contextLock.writeLock().lock(); 73 | this.context = context; 74 | contextLock.writeLock().unlock(); 75 | } 76 | 77 | public void setPathContexts(Map pathContexts) { 78 | contextLock.writeLock().lock(); 79 | this.pathContexts = pathContexts; 80 | contextLock.writeLock().unlock(); 81 | } 82 | 83 | public HapporContext getContext(HttpRequest request) { 84 | contextLock.readLock().lock(); 85 | HapporContext retContext = context; 86 | if (pathContexts != null) { 87 | String uri = request.getUri(); 88 | for (Map.Entry entry : pathContexts.entrySet()) { 89 | String path = entry.getKey(); 90 | HapporContext ctx = entry.getValue(); 91 | if (uri.startsWith("/" + path + "/")) { 92 | logger.info("enter path: " + path); 93 | request.setUri(uri.substring(1 + path.length())); 94 | retContext = ctx; 95 | break; 96 | } 97 | } 98 | } 99 | contextLock.readLock().unlock(); 100 | return retContext; 101 | } 102 | 103 | private AsyncHttpClient asyncHttpClient; 104 | 105 | public AsyncHttpClient getAsyncHttpClient() { 106 | if (asyncHttpClient == null) { 107 | asyncHttpClient = new AsyncHttpClient(); 108 | asyncHttpClient.setMaxHttpSize(maxHttpSize); 109 | asyncHttpClient.setTimeout(timeout); 110 | } 111 | return asyncHttpClient; 112 | } 113 | 114 | public void startup() { 115 | logger.info("HttpServer is starting..."); 116 | logger.info("port = " + port); 117 | logger.info("timeout = " + timeout); 118 | logger.info("maxHttpSize = " + maxHttpSize); 119 | logger.info("executeThreads = " + executeThreads); 120 | 121 | EventLoopGroup bossGroup = new NioEventLoopGroup(1); 122 | EventLoopGroup workerGroup = new NioEventLoopGroup(executeThreads); 123 | try { 124 | ServerBootstrap b = new ServerBootstrap(); 125 | b.group(bossGroup, workerGroup) 126 | .channel(NioServerSocketChannel.class) 127 | .handler(new LoggingHandler(LogLevel.INFO)) 128 | .childHandler(new HapporChannelInitializer(this)) 129 | .option(ChannelOption.SO_BACKLOG, 1024) 130 | .option(ChannelOption.SO_REUSEADDR, true) 131 | .childOption(ChannelOption.SO_KEEPALIVE, true) 132 | .childOption(ChannelOption.TCP_NODELAY, true); 133 | 134 | // Bind and start to accept incoming connections. 135 | ChannelFuture f = b.bind(port).sync(); // (7) 136 | logger.info("HttpServer start OK!"); 137 | 138 | if (context != null) { 139 | WebserverHandler handler = context.getWebserverHandler(); 140 | if (handler != null) { 141 | handler.onInit(this); 142 | } 143 | } 144 | 145 | // Wait until the server socket is closed. 146 | // In this example, this does not happen, but you can do that to 147 | // gracefully shut down your server. 148 | f.channel().closeFuture().sync(); 149 | } catch (InterruptedException e) { 150 | // TODO Auto-generated catch block 151 | e.printStackTrace(); 152 | logger.error("HttpServer start @port[" + port + "] FAIL!"); 153 | } finally { 154 | workerGroup.shutdownGracefully(); 155 | bossGroup.shutdownGracefully(); 156 | } 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/HttpRootController.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor; 2 | 3 | import java.util.Map; 4 | import org.apache.log4j.Logger; 5 | 6 | import cn.lechange.happor.controller.HttpController; 7 | import cn.lechange.happor.utils.UriParser; 8 | import io.netty.channel.ChannelFutureListener; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.channel.ChannelInboundHandlerAdapter; 11 | import io.netty.handler.codec.http.DefaultFullHttpResponse; 12 | import io.netty.handler.codec.http.FullHttpRequest; 13 | import io.netty.handler.codec.http.FullHttpResponse; 14 | import io.netty.handler.codec.http.HttpResponseStatus; 15 | import io.netty.handler.timeout.IdleStateEvent; 16 | 17 | public class HttpRootController extends ChannelInboundHandlerAdapter { 18 | 19 | private static Logger logger = Logger.getLogger(HttpRootController.class); 20 | 21 | private HapporWebserver server; 22 | 23 | public HttpRootController(HapporWebserver server) { 24 | this.server = server; 25 | } 26 | 27 | private FullHttpRequest request; 28 | private FullHttpResponse response; 29 | 30 | @Override 31 | public void channelRead(ChannelHandlerContext ctx, Object msg) 32 | throws Exception { 33 | // TODO Auto-generated method stub 34 | if (msg instanceof FullHttpRequest) { 35 | request = (FullHttpRequest) msg; 36 | response = new DefaultFullHttpResponse( 37 | request.getProtocolVersion(), HttpResponseStatus.OK); 38 | 39 | HapporContext happorContext = server.getContext(request); 40 | if (happorContext == null) { 41 | logger.error("cannot find path for URI: " + request.getUri()); 42 | response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR); 43 | ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); 44 | return; 45 | } 46 | 47 | HttpController lastController = null; 48 | 49 | Map controllers = happorContext.getControllers(); 50 | for (Map.Entry entry : controllers.entrySet()) { 51 | ControllerRegistry registry = entry.getValue(); 52 | String method = registry.getMethod(); 53 | String uriPattern = registry.getUriPattern(); 54 | UriParser uriParser = new UriParser(request.getUri()); 55 | 56 | if (isMethodMatch(method) && isUriMatch(uriParser, uriPattern)) { 57 | HttpController controller = happorContext.getController(registry.getClazz()); 58 | controller.setPrev(lastController); 59 | controller.setServer(server); 60 | controller.setUriParser(uriParser); 61 | boolean isEnd = controller.input(ctx, request, response); 62 | if (isEnd) { 63 | break; 64 | } 65 | lastController = controller; 66 | } 67 | } 68 | } 69 | } 70 | 71 | @Override 72 | public void userEventTriggered(ChannelHandlerContext ctx, 73 | Object evt) throws Exception { 74 | if (evt instanceof IdleStateEvent) { 75 | if (request == null) { 76 | logger.warn("connection[" + ctx + "] timeout."); 77 | ctx.channel().close(); 78 | } else { 79 | logger.warn("handle request[" + request.getUri() + "] timeout."); 80 | response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR); 81 | ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); 82 | request.release(); 83 | } 84 | } 85 | } 86 | 87 | private boolean isMethodMatch(String method) { 88 | if (method == null || method.isEmpty()) { 89 | return true; 90 | } else if (request.getMethod().name().equals(method)) { 91 | return true; 92 | } else { 93 | return false; 94 | } 95 | } 96 | 97 | private boolean isUriMatch(UriParser uri, String pattern) { 98 | if (pattern == null || pattern.isEmpty()) { 99 | return true; 100 | } else if (uri.matches(pattern)) { 101 | return true; 102 | } else { 103 | return false; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/WebserverHandler.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor; 2 | 3 | public interface WebserverHandler { 4 | 5 | public void onInit(HapporWebserver server); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/annotation/Controller.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Target(ElementType.TYPE) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Documented 12 | public @interface Controller { 13 | public String method() default ""; 14 | public String uriPattern() default ""; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/annotation/DefaultController.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Target(ElementType.TYPE) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Documented 12 | public @interface DefaultController { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/annotation/Filter.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Target(ElementType.TYPE) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Documented 12 | public @interface Filter { 13 | public String value() default ""; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/annotation/UriParam.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Target(ElementType.FIELD) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Documented 12 | public @interface UriParam { 13 | public String value() default ""; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/annotation/UriSection.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Target(ElementType.FIELD) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Documented 12 | public @interface UriSection { 13 | public int value() default 0; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/container/ContainerPath.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.container; 2 | 3 | public class ContainerPath { 4 | 5 | private String path; 6 | private String jar; 7 | 8 | public String getPath() { 9 | return path; 10 | } 11 | public void setPath(String path) { 12 | this.path = path; 13 | } 14 | public String getJar() { 15 | return jar; 16 | } 17 | public void setJar(String jar) { 18 | this.jar = jar; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/container/JarContainerServer.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.container; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileWriter; 6 | import java.io.IOException; 7 | import java.io.PrintWriter; 8 | import java.text.SimpleDateFormat; 9 | import java.util.ArrayList; 10 | import java.util.Date; 11 | import java.util.List; 12 | import java.util.Properties; 13 | import java.util.Timer; 14 | import java.util.TimerTask; 15 | 16 | import org.apache.log4j.Logger; 17 | import org.apache.log4j.PropertyConfigurator; 18 | import org.springframework.context.support.FileSystemXmlApplicationContext; 19 | 20 | import cn.lechange.happor.HapporContext; 21 | import cn.lechange.happor.HapporWebserver; 22 | import cn.lechange.happor.WebserverHandler; 23 | import cn.lechange.happor.context.HapporMultipleContext; 24 | import cn.lechange.happor.springtags.HapporServerElement; 25 | 26 | public class JarContainerServer { 27 | 28 | private static Logger logger = Logger.getLogger(JarContainerServer.class); 29 | 30 | private HapporWebserver server; 31 | private String containerConfig; 32 | private String log4jConfig; 33 | private List pathList = new ArrayList(); 34 | 35 | public JarContainerServer(String filename) { 36 | FileSystemXmlApplicationContext serverContext = new FileSystemXmlApplicationContext(filename); 37 | HapporServerElement element = serverContext.getBean(HapporServerElement.class); 38 | server = element.getServer(); 39 | containerConfig = element.getConfigs().get("container"); 40 | log4jConfig = element.getConfigs().get("log4j"); 41 | serverContext.close(); 42 | } 43 | 44 | public String getContainerConfig() { 45 | return containerConfig; 46 | } 47 | 48 | private void readContainerConfig(String filename) { 49 | FileInputStream in = null; 50 | try { 51 | in = new FileInputStream(filename); 52 | Properties prop = new Properties(); 53 | prop.load(in); 54 | String container = prop.getProperty("container"); 55 | if (container != null) { 56 | String[] list = container.split(","); 57 | for (String name : list) { 58 | ContainerPath c = new ContainerPath(); 59 | c.setPath(prop.getProperty("." + name + ".path")); 60 | c.setJar(prop.getProperty("." + name + ".jar")); 61 | pathList.add(c); 62 | } 63 | } 64 | } catch (Exception e) { 65 | logger.error(e.getMessage()); 66 | } finally { 67 | if (in != null) { 68 | try { 69 | in.close(); 70 | } catch (IOException e) { 71 | 72 | } 73 | } 74 | } 75 | } 76 | 77 | public static final String LOAD_LOG = "logs/load.log"; 78 | private void logLoad() { 79 | FileWriter writer = null; 80 | PrintWriter out = null; 81 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 82 | try { 83 | writer = new FileWriter(LOAD_LOG, true); 84 | out = new PrintWriter(writer); 85 | out.println("Loaded at " + sdf.format(new Date())); 86 | } catch (IOException e) { 87 | logger.error(e.getMessage()); 88 | } finally { 89 | try { 90 | if (writer != null) { 91 | writer.close(); 92 | } 93 | if (out != null) { 94 | out.close(); 95 | } 96 | } catch (IOException e) { 97 | logger.error(e.getMessage()); 98 | } 99 | } 100 | } 101 | 102 | public void load() { 103 | readContainerConfig(containerConfig); 104 | HapporMultipleContext context = new HapporMultipleContext(); 105 | for (ContainerPath container : pathList) { 106 | String path = container.getPath(); 107 | String jar = container.getJar(); 108 | logger.info("config path[" +path + "] " + jar); 109 | HapporContext ctx = JarImporter.load(jar); 110 | if (ctx == null) { 111 | logger.error("load fail: " + jar); 112 | } else { 113 | if (path.isEmpty()) { 114 | context.setDefault(ctx); 115 | } else { 116 | context.addPath(path, ctx); 117 | } 118 | } 119 | } 120 | context.printInfo(); 121 | context.applyServer(server); 122 | 123 | logLoad(); 124 | 125 | WebserverHandler handler = context.getWebserverHandler(); 126 | if (handler != null) { 127 | handler.onInit(server); 128 | } 129 | } 130 | 131 | public void start() { 132 | if(log4jConfig != null) { 133 | PropertyConfigurator.configure(log4jConfig); 134 | } 135 | if (containerConfig == null) { 136 | logger.error("no container!"); 137 | return; 138 | } 139 | Timer timer = new Timer(); 140 | timer.schedule(checkTask, 1000, 1000); 141 | server.startup(); 142 | } 143 | 144 | private TimerTask checkTask = new TimerTask() { 145 | 146 | private long lastTime = 0; 147 | 148 | @Override 149 | public void run() { 150 | // TODO Auto-generated method stub 151 | File file = new File(containerConfig); 152 | long nowTime = file.lastModified(); 153 | if (lastTime != nowTime) { 154 | logger.warn("container config[" + containerConfig + "] changed!"); 155 | load(); 156 | lastTime = nowTime; 157 | } 158 | } 159 | 160 | }; 161 | 162 | public static void runDebug(String path, Class clazz) { 163 | HapporContext jarContext = JarImporter.loadLocal(clazz); 164 | if (jarContext == null) { 165 | return; 166 | } 167 | HapporMultipleContext context = new HapporMultipleContext(); 168 | if (path.isEmpty()) { 169 | context.setDefault(jarContext); 170 | } else { 171 | context.addPath(path, jarContext); 172 | } 173 | context.runServer(); 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/container/JarImporter.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.container; 2 | 3 | import java.io.File; 4 | import java.net.MalformedURLException; 5 | import java.net.URL; 6 | import java.net.URLClassLoader; 7 | import java.util.List; 8 | 9 | import org.apache.log4j.Logger; 10 | 11 | import cn.lechange.happor.HapporContext; 12 | import cn.lechange.happor.utils.PackageUtil; 13 | 14 | public class JarImporter { 15 | 16 | private static Logger logger = Logger.getLogger(JarImporter.class); 17 | 18 | public static interface JarStub { 19 | public HapporContext init(ClassLoader classLoader, String jarPath); 20 | } 21 | 22 | public static HapporContext loadLocal(Class clazz) { 23 | try { 24 | JarStub stub = (JarStub) clazz.newInstance(); 25 | HapporContext jarContext = stub.init(Thread.currentThread().getContextClassLoader(), ""); 26 | if (jarContext == null) { 27 | logger.error("cannot get jar context!"); 28 | return null; 29 | } 30 | return jarContext; 31 | } catch (Exception e) { 32 | logger.error(e); 33 | return null; 34 | } 35 | } 36 | 37 | public static HapporContext load(String filename) { 38 | HapporContext jarContext = null; 39 | File file = new File(filename); 40 | URL[] urls = new URL[1]; 41 | try { 42 | urls[0] = file.toURI().toURL(); 43 | } catch (MalformedURLException e) { 44 | logger.error(e); 45 | return null; 46 | } 47 | URLClassLoader classLoader = new URLClassLoader(urls); 48 | List list = PackageUtil.getClassName(classLoader, "!"); 49 | for (String name : list) { 50 | try { 51 | Class clazz = classLoader.loadClass(name); 52 | if (JarStub.class.isAssignableFrom(clazz)) { 53 | logger.info("JarStub: " + name); 54 | JarStub stub = (JarStub) clazz.newInstance(); 55 | jarContext = stub.init(classLoader, file.getParent()); 56 | if (jarContext == null) { 57 | logger.error("cannot get jar context!"); 58 | return null; 59 | } 60 | } 61 | } catch (Exception e) { 62 | logger.error(e); 63 | return null; 64 | } 65 | } 66 | return jarContext; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/container/ServerMain.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.container; 2 | 3 | import java.io.File; 4 | 5 | public class ServerMain { 6 | 7 | /** 8 | * @param args 9 | */ 10 | public static void main(String[] args) { 11 | // TODO Auto-generated method stub 12 | JarContainerServer server = new JarContainerServer("conf/containerServer.xml"); 13 | if (args.length == 0) { 14 | server.start(); 15 | } else if (args.length == 1) { 16 | String cmd = args[0]; 17 | if (cmd.equals("reload")) { 18 | System.out.println("Reloading..."); 19 | reload(server.getContainerConfig()); 20 | } else { 21 | System.err.println("no cmd '" + cmd + "'"); 22 | } 23 | } else { 24 | System.err.println("wrong cmd"); 25 | } 26 | } 27 | 28 | public static void reload(String filename) { 29 | File log = new File(JarContainerServer.LOAD_LOG); 30 | long lastTime = log.lastModified(); 31 | 32 | File file = new File(filename); 33 | if (!file.exists()) { 34 | System.err.println("config file[" + filename + "] is not exist!"); 35 | return; 36 | } 37 | file.setLastModified(System.currentTimeMillis()); 38 | for (int i = 0; i < 3; i++) { 39 | try { 40 | Thread.sleep(1000); 41 | } catch (InterruptedException e) { 42 | // TODO Auto-generated catch block 43 | e.printStackTrace(); 44 | } 45 | long time = log.lastModified(); 46 | if (time != lastTime) { 47 | System.out.println("Reloaded."); 48 | return; 49 | } 50 | } 51 | 52 | System.err.println("Reload Fail!"); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/context/HapporAutomaticContext.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.context; 2 | 3 | import org.apache.log4j.Logger; 4 | 5 | import cn.lechange.happor.ControllerRegistry; 6 | import cn.lechange.happor.ControllerScanner; 7 | 8 | public class HapporAutomaticContext extends HapporManualContext { 9 | 10 | private static Logger logger = Logger.getLogger(HapporAutomaticContext.class); 11 | 12 | private ControllerScanner controllerScanner; 13 | 14 | public HapporAutomaticContext() { 15 | this(""); 16 | } 17 | 18 | public HapporAutomaticContext(String packageName) { 19 | this(Thread.currentThread().getContextClassLoader(), packageName); 20 | } 21 | 22 | public HapporAutomaticContext(ClassLoader classLoader, String packageName) { 23 | controllerScanner = new ControllerScanner(); 24 | controllerScanner.scan(classLoader, packageName); 25 | for (ControllerRegistry r : controllerScanner.getHandlers()) { 26 | addController(r); 27 | } 28 | } 29 | 30 | public void addFilters(String[] names) { 31 | clearControllers(); 32 | for (String name : names) { 33 | ControllerRegistry r = controllerScanner.getFilter(name); 34 | if (r == null) { 35 | logger.error("no filter named '" + name + "'"); 36 | } else { 37 | addController(r); 38 | } 39 | } 40 | for (ControllerRegistry r : controllerScanner.getHandlers()) { 41 | addController(r); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/context/HapporManualContext.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.context; 2 | 3 | import org.apache.log4j.Logger; 4 | 5 | import cn.lechange.happor.HapporContext; 6 | import cn.lechange.happor.HapporWebserver; 7 | import cn.lechange.happor.controller.HttpController; 8 | 9 | public class HapporManualContext extends HapporContext { 10 | 11 | private static Logger logger = Logger.getLogger(HapporManualContext.class); 12 | 13 | public HapporManualContext() { 14 | setServer(new HapporWebserver()); 15 | } 16 | 17 | @Override 18 | public HttpController getController(Class clazz) { 19 | // TODO Auto-generated method stub 20 | HttpController controller = null; 21 | try { 22 | controller = clazz.newInstance(); 23 | } catch (InstantiationException e) { 24 | logger.error(e.getMessage()); 25 | } catch (IllegalAccessException e) { 26 | logger.error(e.getMessage()); 27 | } 28 | return controller; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/context/HapporMultipleContext.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.context; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | import org.apache.log4j.Logger; 7 | 8 | import cn.lechange.happor.ControllerRegistry; 9 | import cn.lechange.happor.HapporContext; 10 | import cn.lechange.happor.HapporWebserver; 11 | import cn.lechange.happor.WebserverHandler; 12 | import cn.lechange.happor.controller.HttpController; 13 | 14 | public class HapporMultipleContext extends HapporContext { 15 | 16 | private static Logger logger = Logger.getLogger(HapporMultipleContext.class); 17 | 18 | private HapporWebserver server; 19 | private HapporContext defaultContext; 20 | private Map pathContexts = new LinkedHashMap(); 21 | 22 | public HapporMultipleContext() { 23 | server = new HapporWebserver(); 24 | applyServer(server); 25 | setWebserverHandler(new WebserverHandler() { 26 | 27 | public void onInit(HapporWebserver server) { 28 | // TODO Auto-generated method stub 29 | if (defaultContext != null) { 30 | WebserverHandler handler = defaultContext.getWebserverHandler(); 31 | if (handler != null) { 32 | handler.onInit(server); 33 | } 34 | } 35 | for (Map.Entry entry : pathContexts.entrySet()) { 36 | WebserverHandler handler = entry.getValue().getWebserverHandler(); 37 | if (handler != null) { 38 | handler.onInit(server); 39 | } 40 | } 41 | } 42 | 43 | }); 44 | } 45 | 46 | public void setDefault(HapporContext ctx) { 47 | ctx.setServer(server); 48 | defaultContext = ctx; 49 | } 50 | 51 | public void addPath(String path, HapporContext ctx) { 52 | ctx.setServer(server); 53 | pathContexts.put(path, ctx); 54 | } 55 | 56 | public void delPath(String path) { 57 | pathContexts.remove(path); 58 | } 59 | 60 | public void clearPath() { 61 | pathContexts.clear(); 62 | } 63 | 64 | public void applyServer(HapporWebserver server) { 65 | setServer(server); 66 | server.loadContext(this); 67 | server.setPathContexts(pathContexts); 68 | } 69 | 70 | @Override 71 | public void printInfo() { 72 | logger.info("This is a multiple context: " + this); 73 | if (defaultContext != null) { 74 | logger.info("=> default"); 75 | defaultContext.printInfo(); 76 | } 77 | for (Map.Entry entry : pathContexts.entrySet()) { 78 | logger.info("=> path: " + entry.getKey()); 79 | entry.getValue().printInfo(); 80 | } 81 | } 82 | 83 | @Override 84 | public Map getControllers() { 85 | if (defaultContext == null) { 86 | return null; 87 | } 88 | return defaultContext.getControllers(); 89 | } 90 | 91 | @Override 92 | public HttpController getController(Class clazz) { 93 | if (defaultContext == null) { 94 | return null; 95 | } 96 | return defaultContext.getController(clazz); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/context/HapporSpringContext.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.context; 2 | 3 | import org.apache.log4j.Logger; 4 | import org.springframework.beans.factory.NoSuchBeanDefinitionException; 5 | import org.springframework.beans.factory.config.BeanDefinition; 6 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; 7 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 8 | import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; 9 | import org.springframework.context.ApplicationContext; 10 | import org.springframework.context.support.FileSystemXmlApplicationContext; 11 | 12 | import cn.lechange.happor.ControllerRegistry; 13 | import cn.lechange.happor.ControllerScanner; 14 | import cn.lechange.happor.HapporContext; 15 | import cn.lechange.happor.HapporWebserver; 16 | import cn.lechange.happor.WebserverHandler; 17 | import cn.lechange.happor.controller.HttpController; 18 | import cn.lechange.happor.springtags.HapporServerElement; 19 | 20 | public class HapporSpringContext extends HapporContext { 21 | 22 | private static Logger logger = Logger.getLogger(HapporSpringContext.class); 23 | 24 | private FileSystemXmlApplicationContext ctx; 25 | private ClassLoader classLoader; 26 | 27 | public HapporSpringContext(String filename) { 28 | this(Thread.currentThread().getContextClassLoader(), filename); 29 | } 30 | 31 | public HapporSpringContext(final ClassLoader classLoader, String filename) { 32 | this.classLoader = classLoader; 33 | 34 | ctx = new FileSystemXmlApplicationContext(filename) { 35 | @Override 36 | protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) { 37 | super.initBeanDefinitionReader(reader); 38 | reader.setBeanClassLoader(classLoader); 39 | setClassLoader(classLoader); 40 | } 41 | }; 42 | 43 | registerAllBeans(); 44 | 45 | setServer(ctx.getBean(HapporWebserver.class)); 46 | 47 | try { 48 | setWebserverHandler(ctx.getBean(WebserverHandler.class)); 49 | } catch (NoSuchBeanDefinitionException e) { 50 | logger.warn("has no WebserverHandler"); 51 | } 52 | 53 | 54 | } 55 | 56 | public ApplicationContext getApplicationContext() { 57 | return ctx; 58 | } 59 | 60 | public void close() { 61 | if (ctx != null) { 62 | ctx.close(); 63 | } 64 | } 65 | 66 | @Override 67 | public HttpController getController(Class clazz) { 68 | // TODO Auto-generated method stub 69 | return (HttpController) ctx.getBean(clazz); 70 | } 71 | 72 | @SuppressWarnings("unchecked") 73 | private void registerAllBeans() { 74 | HapporServerElement element = ctx.getBean(HapporServerElement.class); 75 | if (element != null) { 76 | registerWebserver(element.getServer()); 77 | if (element.getHandlerClass() != null) { 78 | try { 79 | Class handlerClass = (Class) classLoader 80 | .loadClass(element.getHandlerClass()); 81 | registerHandler(handlerClass); 82 | } catch (ClassNotFoundException e) { 83 | logger.error(e.getMessage()); 84 | } 85 | } 86 | if (element.getFilters() == null) { 87 | for (ControllerRegistry registry : element.getControllers()) { 88 | addControllerFromRegistry(registry); 89 | } 90 | } else { 91 | ControllerScanner scanner = new ControllerScanner(); 92 | scanner.scan(classLoader, element.getAutoScanPackage()); 93 | for (String name : element.getFilters()) { 94 | ControllerRegistry registry = scanner.getFilter(name); 95 | if (registry == null) { 96 | logger.error("no filter named '" + name + "'"); 97 | } else { 98 | addControllerFromRegistry(registry); 99 | } 100 | } 101 | for (ControllerRegistry registry : scanner.getHandlers()) { 102 | addControllerFromRegistry(registry); 103 | } 104 | } 105 | } 106 | } 107 | 108 | private void addControllerFromRegistry(ControllerRegistry registry) { 109 | String className = registry.getClassName(); 110 | try { 111 | @SuppressWarnings("unchecked") 112 | Class clazz = (Class) classLoader 113 | .loadClass(className); 114 | registry.setClazz(clazz); 115 | registerController(clazz); 116 | addController(registry); 117 | } catch (ClassNotFoundException e) { 118 | logger.error(e.getMessage()); 119 | } 120 | } 121 | 122 | private void registerWebserver(HapporWebserver server) { 123 | BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(HapporWebserver.class); 124 | builder.addPropertyValue("port", server.getPort()); 125 | builder.addPropertyValue("timeout", server.getTimeout()); 126 | builder.addPropertyValue("maxHttpSize", server.getMaxHttpSize()); 127 | builder.addPropertyValue("executeThreads", server.getExecuteThreads()); 128 | BeanDefinition beanDefinition = builder.getBeanDefinition(); 129 | BeanDefinitionRegistry factory = (BeanDefinitionRegistry) ctx.getBeanFactory(); 130 | factory.registerBeanDefinition("server", beanDefinition); 131 | } 132 | 133 | private void registerHandler(Class clazz) { 134 | BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); 135 | BeanDefinition beanDefinition = builder.getBeanDefinition(); 136 | BeanDefinitionRegistry factory = (BeanDefinitionRegistry) ctx.getBeanFactory(); 137 | factory.registerBeanDefinition("handler", beanDefinition); 138 | } 139 | 140 | private void registerController(Class clazz) { 141 | String name = "controller#" + getControllers().size(); 142 | BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); 143 | BeanDefinition beanDefinition = builder.getBeanDefinition(); 144 | beanDefinition.setScope("prototype"); 145 | BeanDefinitionRegistry factory = (BeanDefinitionRegistry) ctx.getBeanFactory(); 146 | factory.registerBeanDefinition(name, beanDefinition); 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/controller/HttpAsyncHandler.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.controller; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | import io.netty.handler.codec.http.FullHttpResponse; 5 | 6 | public abstract class HttpAsyncHandler extends HttpController { 7 | 8 | @Override 9 | protected boolean handleRequest(FullHttpRequest request, 10 | FullHttpResponse response) { 11 | // TODO Auto-generated method stub 12 | handle(request, response); 13 | return true; 14 | } 15 | 16 | @Override 17 | protected void handleResponse(FullHttpResponse response) { 18 | // TODO Auto-generated method stub 19 | 20 | } 21 | 22 | protected abstract void handle(FullHttpRequest request, 23 | FullHttpResponse response); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/controller/HttpController.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.controller; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | import org.apache.log4j.Logger; 6 | 7 | import cn.lechange.happor.HapporWebserver; 8 | import cn.lechange.happor.annotation.UriParam; 9 | import cn.lechange.happor.annotation.UriSection; 10 | import cn.lechange.happor.utils.UriParser; 11 | 12 | import io.netty.channel.ChannelFutureListener; 13 | import io.netty.channel.ChannelHandlerContext; 14 | import io.netty.handler.codec.http.FullHttpRequest; 15 | import io.netty.handler.codec.http.FullHttpResponse; 16 | 17 | public abstract class HttpController { 18 | 19 | private static Logger logger = Logger.getLogger(HttpController.class); 20 | 21 | private UriParser uriParser; 22 | 23 | public UriParser getUriParser() { 24 | return uriParser; 25 | } 26 | 27 | public void setUriParser(UriParser uriParser) { 28 | this.uriParser = uriParser; 29 | } 30 | 31 | private ChannelHandlerContext ctx; 32 | private FullHttpRequest request; 33 | 34 | final public boolean input(ChannelHandlerContext ctx, FullHttpRequest request, 35 | FullHttpResponse response) { 36 | logger.info("HTTP[" + request.getMethod() + " " 37 | + request.getUri() + " " + request.getProtocolVersion() 38 | + "] => " + this + " [ from " + prev + " ]"); 39 | this.ctx = ctx; 40 | this.request = request; 41 | parseFieldAnnotation(); 42 | return handleRequest(request, response); 43 | } 44 | 45 | final public void output(FullHttpResponse response) { 46 | handleResponse(response); 47 | } 48 | 49 | private HttpController prev; 50 | final public void setPrev(HttpController controller) { 51 | prev = controller; 52 | } 53 | 54 | private HapporWebserver server; 55 | public void setServer(HapporWebserver server) { 56 | this.server = server; 57 | } 58 | public HapporWebserver getServer() { 59 | return server; 60 | } 61 | 62 | private void realFinish(FullHttpResponse response) { 63 | if (response != null) { 64 | logger.info("HTTP[" + request.getMethod() + " " + request.getUri() 65 | + " " + request.getProtocolVersion() + "] response " + response.getStatus()); 66 | ctx.channel().writeAndFlush(response) 67 | .addListener(ChannelFutureListener.CLOSE); 68 | } else { 69 | ctx.channel().close(); 70 | } 71 | request.release(); 72 | } 73 | 74 | protected void finish(FullHttpResponse response) { 75 | handleResponse(response); 76 | if (prev == null) { 77 | realFinish(response); 78 | } else { 79 | prev.finish(response); 80 | } 81 | } 82 | 83 | protected abstract boolean handleRequest(FullHttpRequest request, 84 | FullHttpResponse response); 85 | protected abstract void handleResponse(FullHttpResponse response); 86 | 87 | private void parseFieldAnnotation() { 88 | Field[] fields = getClass().getDeclaredFields(); 89 | for (Field field : fields) { 90 | if (field.isAnnotationPresent(UriSection.class)) { 91 | UriSection uriSection = (UriSection) field.getAnnotation(UriSection.class); 92 | setField(field, uriParser.getSection(uriSection.value())); 93 | } else if (field.isAnnotationPresent(UriParam.class)) { 94 | UriParam uriParam = (UriParam) field.getAnnotation(UriParam.class); 95 | setField(field, uriParser.getParam(uriParam.value())); 96 | } 97 | } 98 | } 99 | 100 | private void setField(Field field, String value) { 101 | field.setAccessible(true); 102 | try { 103 | if (field.getType() == int.class) { 104 | field.setInt(this, Integer.valueOf(value)); 105 | } else if (field.getType() == long.class) { 106 | field.setLong(this, Long.valueOf(value)); 107 | } else if (field.getType() == short.class) { 108 | field.setShort(this, Short.valueOf(value)); 109 | } else if (field.getType() == float.class) { 110 | field.setFloat(this, Float.valueOf(value)); 111 | } else if (field.getType() == double.class) { 112 | field.setDouble(this, Double.valueOf(value)); 113 | } else { 114 | field.set(this, value); 115 | } 116 | } catch (IllegalArgumentException e) { 117 | // TODO Auto-generated catch block 118 | logger.error(e.getMessage()); 119 | } catch (IllegalAccessException e) { 120 | // TODO Auto-generated catch block 121 | logger.error(e.getMessage()); 122 | } 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/controller/HttpNormalFilter.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.controller; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | import io.netty.handler.codec.http.FullHttpResponse; 5 | 6 | public abstract class HttpNormalFilter extends HttpController { 7 | 8 | private boolean isEnd = false; 9 | 10 | @Override 11 | protected boolean handleRequest(FullHttpRequest request, 12 | FullHttpResponse response) { 13 | // TODO Auto-generated method stub 14 | incoming(request); 15 | return isEnd; 16 | } 17 | 18 | @Override 19 | protected void handleResponse(FullHttpResponse response) { 20 | // TODO Auto-generated method stub 21 | outgoing(response); 22 | } 23 | 24 | @Override 25 | protected void finish(FullHttpResponse response) { 26 | isEnd = true; 27 | super.finish(response); 28 | } 29 | 30 | protected abstract void incoming(FullHttpRequest request); 31 | protected abstract void outgoing(FullHttpResponse response); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/controller/HttpNormalHandler.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.controller; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | import io.netty.handler.codec.http.FullHttpResponse; 5 | 6 | public abstract class HttpNormalHandler extends HttpController { 7 | 8 | @Override 9 | protected boolean handleRequest(FullHttpRequest request, 10 | FullHttpResponse response) { 11 | // TODO Auto-generated method stub 12 | handle(request, response); 13 | finish(response); 14 | atlast(); 15 | return true; 16 | } 17 | 18 | @Override 19 | protected void handleResponse(FullHttpResponse response) { 20 | // TODO Auto-generated method stub 21 | 22 | } 23 | 24 | protected abstract void handle(FullHttpRequest request, 25 | FullHttpResponse response); 26 | 27 | protected abstract void atlast(); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/controller/HttpTransitHandler.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.controller; 2 | 3 | import cn.lechange.happor.utils.AsyncHttpClient; 4 | 5 | import io.netty.handler.codec.http.DefaultFullHttpResponse; 6 | import io.netty.handler.codec.http.FullHttpRequest; 7 | import io.netty.handler.codec.http.FullHttpResponse; 8 | import io.netty.handler.codec.http.HttpResponseStatus; 9 | 10 | public abstract class HttpTransitHandler extends HttpAsyncHandler { 11 | 12 | private FullHttpRequest transRequest; 13 | private FullHttpResponse transResponse; 14 | 15 | @Override 16 | protected void handle(FullHttpRequest request, FullHttpResponse response) { 17 | // TODO Auto-generated method stub 18 | transRequest = request.retain(); 19 | incoming(transRequest); 20 | } 21 | 22 | protected void transit(String host, int port) { 23 | 24 | getServer().getAsyncHttpClient().sendRequest(host, port, transRequest, 25 | new AsyncHttpClient.Callback() { 26 | 27 | public void onConnectFail() { 28 | // TODO Auto-generated method stub 29 | transResponse = new DefaultFullHttpResponse( 30 | transRequest.getProtocolVersion(), new HttpResponseStatus(591, "Transit Connect Fail")); 31 | finish(transResponse); 32 | } 33 | 34 | public void onTimeout() { 35 | // TODO Auto-generated method stub 36 | transResponse = new DefaultFullHttpResponse( 37 | transRequest.getProtocolVersion(), new HttpResponseStatus(592, "Transit Timeout")); 38 | finish(transResponse); 39 | } 40 | 41 | public void onResponse(FullHttpResponse response) { 42 | // TODO Auto-generated method stub 43 | transResponse = response.retain(); 44 | outgoing(transResponse); 45 | finish(transResponse); 46 | } 47 | 48 | }); 49 | } 50 | 51 | protected abstract void incoming(FullHttpRequest request); 52 | protected abstract void outgoing(FullHttpResponse response); 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/springtags/HapporNamespaceHandler.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.springtags; 2 | 3 | import org.springframework.beans.factory.xml.NamespaceHandlerSupport; 4 | 5 | public class HapporNamespaceHandler extends NamespaceHandlerSupport { 6 | 7 | public void init() { 8 | // TODO Auto-generated method stub 9 | registerBeanDefinitionParser("server", new TagHapporServerParser()); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/springtags/HapporServerElement.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.springtags; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import cn.lechange.happor.ControllerRegistry; 7 | import cn.lechange.happor.HapporWebserver; 8 | 9 | public class HapporServerElement { 10 | 11 | private HapporWebserver server; 12 | private String handlerClass; 13 | private List controllers; 14 | private String autoScanPackage; 15 | private List filters; 16 | private Map configs; 17 | 18 | public HapporWebserver getServer() { 19 | return server; 20 | } 21 | public void setServer(HapporWebserver server) { 22 | this.server = server; 23 | } 24 | public String getHandlerClass() { 25 | return handlerClass; 26 | } 27 | public void setHandlerClass(String handlerClass) { 28 | this.handlerClass = handlerClass; 29 | } 30 | public List getControllers() { 31 | return controllers; 32 | } 33 | public void setControllers(List controllers) { 34 | this.controllers = controllers; 35 | } 36 | public String getAutoScanPackage() { 37 | return autoScanPackage; 38 | } 39 | public void setAutoScanPackage(String autoScanPackage) { 40 | this.autoScanPackage = autoScanPackage; 41 | } 42 | public List getFilters() { 43 | return filters; 44 | } 45 | public void setFilters(List filters) { 46 | this.filters = filters; 47 | } 48 | public Map getConfigs() { 49 | return configs; 50 | } 51 | public void setConfigs(Map configs) { 52 | this.configs = configs; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/springtags/TagHapporServerParser.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.springtags; 2 | 3 | import java.io.FileInputStream; 4 | import java.io.FileNotFoundException; 5 | import java.io.IOException; 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Properties; 11 | 12 | import org.springframework.beans.factory.support.AbstractBeanDefinition; 13 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; 14 | import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; 15 | import org.springframework.beans.factory.xml.ParserContext; 16 | import org.springframework.util.xml.DomUtils; 17 | import org.w3c.dom.Element; 18 | 19 | import cn.lechange.happor.ControllerRegistry; 20 | import cn.lechange.happor.HapporWebserver; 21 | 22 | public class TagHapporServerParser extends AbstractSimpleBeanDefinitionParser { 23 | 24 | @Override 25 | protected String resolveId(Element element, 26 | AbstractBeanDefinition beanDefinition, ParserContext parserContext) { 27 | return "webserver"; 28 | } 29 | 30 | @Override 31 | protected void doParse(Element element, ParserContext parserContext, 32 | BeanDefinitionBuilder builder) { 33 | String port = element.getAttribute("port"); 34 | String timeout = element.getAttribute("timeout"); 35 | String maxHttpSize = element.getAttribute("maxHttpSize"); 36 | String executeThreads = element.getAttribute("executeThreads"); 37 | String propFile = element.getAttribute("propFile"); 38 | 39 | if (!propFile.isEmpty()) { 40 | FileInputStream in = null; 41 | try { 42 | in = new FileInputStream(propFile); 43 | Properties prop = new Properties(); 44 | prop.load(in); 45 | port = prop.getProperty("happor.server.port", port); 46 | timeout = prop.getProperty("happor.server.timeout", timeout); 47 | maxHttpSize = prop.getProperty("happor.server.maxHttpSize", maxHttpSize); 48 | executeThreads = prop.getProperty("happor.server.executeThreads", executeThreads); 49 | } catch (FileNotFoundException e) { 50 | e.printStackTrace(); 51 | System.exit(-1); 52 | } catch (IOException e) { 53 | e.printStackTrace(); 54 | System.exit(-1); 55 | } finally { 56 | if (in != null) { 57 | try { 58 | in.close(); 59 | } catch (IOException e) { 60 | 61 | } 62 | } 63 | } 64 | } 65 | 66 | HapporWebserver server = new HapporWebserver(); 67 | server.setPort(Integer.valueOf(port)); 68 | server.setTimeout(Integer.valueOf(timeout)); 69 | server.setMaxHttpSize(Integer.valueOf(maxHttpSize)); 70 | server.setExecuteThreads(Integer.valueOf(executeThreads)); 71 | builder.addPropertyValue("server", server); 72 | 73 | Element handler = DomUtils.getChildElementByTagName(element, "handler"); 74 | if (handler != null) { 75 | String handlerClass = handler.getAttribute("class"); 76 | builder.addPropertyValue("handlerClass", handlerClass); 77 | } 78 | 79 | List controllers = new ArrayList(); 80 | Element controllersTag = DomUtils.getChildElementByTagName(element, "controllers"); 81 | if (controllersTag != null) { 82 | String packageName = controllersTag.getAttribute("package"); 83 | List list = DomUtils.getChildElementsByTagName(controllersTag, "controller"); 84 | for (Element controller : list) { 85 | String className = packageName + "." + controller.getAttribute("class"); 86 | String method = controller.getAttribute("method"); 87 | String uriPattern = controller.getAttribute("uriptn"); 88 | ControllerRegistry registry = new ControllerRegistry(); 89 | registry.setClassName(className); 90 | registry.setMethod(method); 91 | registry.setUriPattern(uriPattern); 92 | controllers.add(registry); 93 | } 94 | } 95 | builder.addPropertyValue("controllers", controllers); 96 | 97 | Element autoScanTag = DomUtils.getChildElementByTagName(element, "controllers-auto-scan"); 98 | if (autoScanTag != null) { 99 | String packageName = autoScanTag.getAttribute("package"); 100 | builder.addPropertyValue("autoScanPackage", packageName); 101 | List filters = new ArrayList(); 102 | List list = DomUtils.getChildElementsByTagName(autoScanTag, "filter"); 103 | for (Element filter : list) { 104 | String name = filter.getAttribute("name"); 105 | filters.add(name); 106 | } 107 | builder.addPropertyValue("filters", filters); 108 | } 109 | 110 | Map configs = new HashMap(); 111 | List configTagList = DomUtils.getChildElementsByTagName(element, "config"); 112 | for (Element configTag : configTagList) { 113 | String type = configTag.getAttribute("type"); 114 | String file = configTag.getAttribute("file"); 115 | configs.put(type, file); 116 | } 117 | builder.addPropertyValue("configs", configs); 118 | 119 | } 120 | 121 | @Override 122 | protected Class getBeanClass(Element element) { 123 | return HapporServerElement.class; 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/utils/AsyncHttpClient.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.utils; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import org.apache.log4j.Logger; 6 | import org.apache.log4j.PropertyConfigurator; 7 | 8 | import io.netty.bootstrap.Bootstrap; 9 | import io.netty.channel.ChannelFuture; 10 | import io.netty.channel.ChannelFutureListener; 11 | import io.netty.channel.ChannelHandlerContext; 12 | import io.netty.channel.ChannelInboundHandlerAdapter; 13 | import io.netty.channel.ChannelInitializer; 14 | import io.netty.channel.ChannelOption; 15 | import io.netty.channel.EventLoopGroup; 16 | import io.netty.channel.SimpleChannelInboundHandler; 17 | import io.netty.channel.nio.NioEventLoopGroup; 18 | import io.netty.channel.socket.SocketChannel; 19 | import io.netty.channel.socket.nio.NioSocketChannel; 20 | import io.netty.handler.codec.http.DefaultFullHttpRequest; 21 | import io.netty.handler.codec.http.FullHttpRequest; 22 | import io.netty.handler.codec.http.FullHttpResponse; 23 | import io.netty.handler.codec.http.HttpMethod; 24 | import io.netty.handler.codec.http.HttpObjectAggregator; 25 | import io.netty.handler.codec.http.HttpRequestEncoder; 26 | import io.netty.handler.codec.http.HttpResponseDecoder; 27 | import io.netty.handler.codec.http.HttpVersion; 28 | import io.netty.handler.timeout.IdleStateEvent; 29 | import io.netty.handler.timeout.IdleStateHandler; 30 | import io.netty.util.AttributeKey; 31 | 32 | public class AsyncHttpClient { 33 | 34 | private static Logger logger = Logger.getLogger(AsyncHttpClient.class); 35 | 36 | private Bootstrap bootstrap; 37 | 38 | private int timeout = 3; 39 | private int maxHttpSize = 8000; 40 | 41 | public int getTimeout() { 42 | return timeout; 43 | } 44 | 45 | public void setTimeout(int timeout) { 46 | this.timeout = timeout; 47 | } 48 | 49 | public int getMaxHttpSize() { 50 | return maxHttpSize; 51 | } 52 | 53 | public void setMaxHttpSize(int maxHttpSize) { 54 | this.maxHttpSize = maxHttpSize; 55 | } 56 | 57 | public AsyncHttpClient() { 58 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 59 | bootstrap = new Bootstrap(); 60 | bootstrap.group(workerGroup); 61 | bootstrap.channel(NioSocketChannel.class); 62 | bootstrap.option(ChannelOption.SO_KEEPALIVE, true); 63 | bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000); 64 | bootstrap.handler(new ChannelInitializer() { 65 | @Override 66 | public void initChannel(SocketChannel ch) throws Exception { 67 | ChannelInboundHandlerAdapter handler = new SimpleChannelInboundHandler() { 68 | 69 | @Override 70 | protected void channelRead0(ChannelHandlerContext ctx, 71 | FullHttpResponse response) throws Exception { 72 | // TODO Auto-generated method stub 73 | Callback cb = ctx.channel().attr(KEY_CB).get(); 74 | FullHttpRequest request = ctx.channel().attr(KEY_REQ).get(); 75 | logger.info("recv http response[" + request.getUri() + "] " + response.getStatus()); 76 | cb.onResponse(response); 77 | ctx.channel().close(); 78 | } 79 | 80 | @Override 81 | public void userEventTriggered(ChannelHandlerContext ctx, 82 | Object evt) throws Exception { 83 | if (evt instanceof IdleStateEvent) { 84 | Callback cb = ctx.channel().attr(KEY_CB).get(); 85 | FullHttpRequest request = ctx.channel().attr(KEY_REQ).get(); 86 | logger.warn("http request[" + request.getUri() + "] timeout."); 87 | cb.onTimeout(); 88 | ctx.channel().close(); 89 | } 90 | } 91 | }; 92 | 93 | ch.pipeline() 94 | .addLast(new HttpResponseDecoder()) 95 | .addLast(new HttpRequestEncoder()) 96 | .addLast(new HttpObjectAggregator(getMaxHttpSize())) 97 | .addLast(new IdleStateHandler(getTimeout(), getTimeout(), getTimeout(), TimeUnit.SECONDS)) 98 | .addLast(handler); 99 | } 100 | }); 101 | } 102 | 103 | public static interface Callback { 104 | public void onResponse(FullHttpResponse response); 105 | public void onTimeout(); 106 | public void onConnectFail(); 107 | } 108 | 109 | final static AttributeKey KEY_CB = AttributeKey.valueOf("cb"); 110 | final static AttributeKey KEY_REQ = AttributeKey.valueOf("req"); 111 | 112 | public void sendRequest(final String host, final int port, 113 | final FullHttpRequest request, final Callback cb) { 114 | logger.info("send http request[" + request.getUri() + "] to " + host + ":" + port); 115 | 116 | bootstrap.connect(host, port).addListener(new ChannelFutureListener() { 117 | 118 | public void operationComplete(ChannelFuture f) throws Exception { 119 | // TODO Auto-generated method stub 120 | if (f.isSuccess()) { 121 | request.headers().set("Host", host + ":" + port); 122 | f.channel().attr(KEY_CB).set(cb); 123 | f.channel().attr(KEY_REQ).set(request); 124 | f.channel().writeAndFlush(request); 125 | } else { 126 | logger.warn("connect to " + host + ":" + port + " failed."); 127 | cb.onConnectFail(); 128 | } 129 | } 130 | 131 | }); 132 | } 133 | 134 | public static void main(String[] args) { 135 | PropertyConfigurator.configure("conf/log4j.properties"); 136 | 137 | AsyncHttpClient client = new AsyncHttpClient(); 138 | DefaultFullHttpRequest request = new DefaultFullHttpRequest( 139 | HttpVersion.HTTP_1_1, HttpMethod.GET, "/AsyncHttpClient"); 140 | client.sendRequest("127.0.0.1", 8888, request, new Callback() { 141 | 142 | public void onResponse(FullHttpResponse response) { 143 | // TODO Auto-generated method stub 144 | System.out.println("AsyncHttpClient onResponse"); 145 | } 146 | 147 | public void onTimeout() { 148 | // TODO Auto-generated method stub 149 | System.out.println("AsyncHttpClient onTimeout"); 150 | } 151 | 152 | public void onConnectFail() { 153 | // TODO Auto-generated method stub 154 | System.out.println("AsyncHttpClient onConnectFail"); 155 | } 156 | 157 | }); 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/utils/PackageUtil.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.utils; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.URL; 6 | import java.net.URLClassLoader; 7 | import java.util.ArrayList; 8 | import java.util.Enumeration; 9 | import java.util.List; 10 | import java.util.jar.JarEntry; 11 | import java.util.jar.JarFile; 12 | 13 | public class PackageUtil { 14 | 15 | public static void main(String[] args) { 16 | List list = getClassName(""); 17 | for (String className : list) { 18 | System.out.println(className); 19 | } 20 | } 21 | 22 | /** 23 | * 获取某包下(包括该包的所有子包)所有类 24 | * @param packageName 包名 25 | * @return 类的完整名称 26 | */ 27 | public static List getClassName(String packageName) { 28 | return getClassName(Thread.currentThread().getContextClassLoader(), packageName, true); 29 | } 30 | 31 | /** 32 | * 获取某包下(包括该包的所有子包)所有类 33 | * @param ClassLoader ClassLoader 34 | * @param packageName 包名 35 | * @return 类的完整名称 36 | */ 37 | public static List getClassName(ClassLoader loader, String packageName) { 38 | return getClassName(loader, packageName, true); 39 | } 40 | 41 | /** 42 | * 获取某包下所有类 43 | * @param ClassLoader ClassLoader 44 | * @param packageName 包名 45 | * @param childPackage 是否遍历子包 46 | * @return 类的完整名称 47 | */ 48 | public static List getClassName(ClassLoader loader, String packageName, boolean recur) { 49 | List classNames = null; 50 | String packagePath = packageName.replace(".", "/"); 51 | URL url = loader.getResource(packagePath); 52 | if (url != null) { 53 | String type = url.getProtocol(); 54 | if (type.equals("file")) { 55 | String classPath = loader.getResource("").getPath(); 56 | classNames = getClassNameByFile(classPath, url.getPath(), recur); 57 | } else if (type.equals("jar")) { 58 | classNames = getClassNameByJar(url.getPath(), recur); 59 | } 60 | } else { 61 | classNames = getClassNameByJars(((URLClassLoader) loader).getURLs(), packagePath, recur); 62 | } 63 | return classNames; 64 | } 65 | 66 | /** 67 | * 从项目文件获取某包下所有类 68 | * @param filePath 文件路径 69 | * @param className 类名集合 70 | * @param childPackage 是否遍历子包 71 | * @return 类的完整名称 72 | */ 73 | private static List getClassNameByFile(String classPath, String filePath, boolean recur) { 74 | List classNames = new ArrayList(); 75 | File file = new File(filePath); 76 | File[] childFiles = file.listFiles(); 77 | for (File childFile : childFiles) { 78 | if (childFile.isDirectory()) { 79 | if (recur) { 80 | classNames.addAll(getClassNameByFile(classPath, childFile.getPath(), recur)); 81 | } 82 | } else { 83 | String childFilePath = childFile.toURI().getPath(); 84 | if (childFilePath.endsWith(".class")) { 85 | childFilePath = childFilePath.substring(classPath.length(), childFilePath.lastIndexOf(".")); 86 | childFilePath = childFilePath.replace("/", "."); 87 | classNames.add(childFilePath); 88 | } 89 | } 90 | } 91 | 92 | return classNames; 93 | } 94 | 95 | /** 96 | * 从jar获取某包下所有类 97 | * @param jarPath jar文件路径 98 | * @param childPackage 是否遍历子包 99 | * @return 类的完整名称 100 | */ 101 | private static List getClassNameByJar(String jarPath, boolean recur) { 102 | List myClassName = new ArrayList(); 103 | String[] jarInfo = jarPath.split("!"); 104 | String jarFilePath = jarInfo[0].substring(jarInfo[0].indexOf("/")); 105 | String packagePath = jarInfo[1].substring(1); 106 | JarFile jarFile = null; 107 | try { 108 | jarFile = new JarFile(jarFilePath); 109 | Enumeration entrys = jarFile.entries(); 110 | while (entrys.hasMoreElements()) { 111 | JarEntry jarEntry = entrys.nextElement(); 112 | String entryName = jarEntry.getName(); 113 | if (entryName.endsWith(".class")) { 114 | if (recur) { 115 | if (entryName.startsWith(packagePath)) { 116 | entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf(".")); 117 | myClassName.add(entryName); 118 | } 119 | } else { 120 | int index = entryName.lastIndexOf("/"); 121 | String myPackagePath; 122 | if (index != -1) { 123 | myPackagePath = entryName.substring(0, index); 124 | } else { 125 | myPackagePath = entryName; 126 | } 127 | if (myPackagePath.equals(packagePath)) { 128 | entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf(".")); 129 | myClassName.add(entryName); 130 | } 131 | } 132 | } 133 | } 134 | } catch (IOException e) { 135 | e.printStackTrace(); 136 | } finally { 137 | if (jarFile != null) { 138 | try { 139 | jarFile.close(); 140 | } catch (IOException e) { 141 | e.printStackTrace(); 142 | } 143 | } 144 | } 145 | return myClassName; 146 | } 147 | 148 | /** 149 | * 从所有jar中搜索该包,并获取该包下所有类 150 | * @param urls URL集合 151 | * @param packagePath 包路径 152 | * @param childPackage 是否遍历子包 153 | * @return 类的完整名称 154 | */ 155 | private static List getClassNameByJars(URL[] urls, String packagePath, boolean recur) { 156 | List myClassName = new ArrayList(); 157 | if (urls != null) { 158 | for (int i = 0; i < urls.length; i++) { 159 | URL url = urls[i]; 160 | String urlPath = url.getPath(); 161 | // 不必搜索classes文件夹 162 | if (urlPath.endsWith("classes/")) { 163 | continue; 164 | } 165 | String jarPath = urlPath + "!/" + packagePath; 166 | myClassName.addAll(getClassNameByJar(jarPath, recur)); 167 | } 168 | } 169 | return myClassName; 170 | } 171 | } -------------------------------------------------------------------------------- /src/main/java/cn/lechange/happor/utils/UriParser.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.utils; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | public class UriParser { 11 | 12 | private String uri; 13 | private String absoluteUri; 14 | private List sections = new ArrayList(); 15 | private Map params = new HashMap(); 16 | 17 | public UriParser(String uri) { 18 | this.uri = uri; 19 | 20 | int pos = uri.indexOf("?"); 21 | if (pos >= 0) { 22 | absoluteUri = uri.substring(0, pos); 23 | String pStr = uri.substring(pos + 1); 24 | String pairs[] = pStr.split("&"); 25 | for (String pair : pairs) { 26 | String nv[] = pair.split("="); 27 | if (nv.length == 2) { 28 | params.put(nv[0], nv[1]); 29 | } 30 | } 31 | } else { 32 | absoluteUri = uri; 33 | } 34 | sections.add(absoluteUri); 35 | } 36 | 37 | public String getAbsoluteUri() { 38 | return absoluteUri; 39 | } 40 | 41 | public boolean matches(String uriPattern) { 42 | Pattern pattern = Pattern.compile(uriPattern); 43 | Matcher matcher = pattern.matcher(uri); 44 | if (matcher.find()) { 45 | for (int i = 1; i <= matcher.groupCount(); i++) { 46 | sections.add(matcher.group(i)); 47 | } 48 | return true; 49 | } 50 | return false; 51 | } 52 | 53 | public String getSection(int index) { 54 | return sections.get(index); 55 | } 56 | 57 | public String getParam(String name) { 58 | return params.get(name); 59 | } 60 | 61 | /** 62 | * @param args 63 | */ 64 | public static void main(String[] args) { 65 | // TODO Auto-generated method stub 66 | UriParser up = new UriParser("/test/aaa?x=1&yy=abc"); 67 | System.out.println(up.getAbsoluteUri()); 68 | System.out.println(up.getParam("x")); 69 | System.out.println(up.getParam("yy")); 70 | if (up.matches("^/test/(\\w+)")) { 71 | System.out.println(up.getSection(0)); 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.handlers: -------------------------------------------------------------------------------- 1 | http\://happor.lechange.cn/schema/tags=cn.lechange.happor.springtags.HapporNamespaceHandler -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.schemas: -------------------------------------------------------------------------------- 1 | http\://happor.lechange.cn/schema/tags/springtags.xsd=conf/springtags.xsd -------------------------------------------------------------------------------- /src/main/resources/conf/springtags.xsd: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | webserver configuration 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | webserver handler 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | webserver controller list 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | webserver controller 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | auto scan controllers from specific package 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | webserver controller 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | config file 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /src/test/java/cn/lechange/happor/Test.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor; 2 | 3 | import cn.lechange.happor.context.HapporSpringContext; 4 | 5 | public class Test { 6 | 7 | public static void main(String[] args) { 8 | // TODO Auto-generated method stub 9 | HapporContext context = new HapporSpringContext("conf/web.xml"); 10 | context.runServer(); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/cn/lechange/happor/TestAnnotation.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | import io.netty.handler.codec.http.FullHttpResponse; 5 | import cn.lechange.happor.annotation.Controller; 6 | import cn.lechange.happor.annotation.UriSection; 7 | import cn.lechange.happor.context.HapporAutomaticContext; 8 | import cn.lechange.happor.controller.HttpNormalHandler; 9 | 10 | @Controller(method="GET", uriPattern="^/anno/(\\w+)") 11 | public class TestAnnotation extends HttpNormalHandler { 12 | 13 | @UriSection(1) 14 | private String name; 15 | 16 | @Override 17 | protected void handle(FullHttpRequest request, FullHttpResponse response) { 18 | // TODO Auto-generated method stub 19 | String words = "hello " + name; 20 | response.content().writeBytes(words.getBytes()); 21 | response.headers().set("Content-Type", "text/plain"); 22 | response.headers().set("Content-Length", response.content().readableBytes()); 23 | } 24 | 25 | @Override 26 | protected void atlast() { 27 | // TODO Auto-generated method stub 28 | 29 | } 30 | 31 | public static void main(String[] args) { 32 | // TODO Auto-generated method stub 33 | HapporAutomaticContext context = new HapporAutomaticContext(); 34 | context.addFilters(new String[] {"incoming"}); 35 | context.getServer().setPort(9080); 36 | context.runServer(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/cn/lechange/happor/TestJarStub.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor; 2 | 3 | import cn.lechange.happor.container.JarImporter.JarStub; 4 | import cn.lechange.happor.context.HapporSpringContext; 5 | 6 | public class TestJarStub implements JarStub { 7 | 8 | public HapporContext init(ClassLoader classLoader, String jarPath) { 9 | // TODO Auto-generated method stub 10 | HapporContext ctx = new HapporSpringContext(classLoader, jarPath + "/conf/web.xml"); 11 | return ctx; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/cn/lechange/happor/TestMultiple.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor; 2 | 3 | import cn.lechange.happor.context.HapporMultipleContext; 4 | import cn.lechange.happor.context.HapporSpringContext; 5 | 6 | public class TestMultiple { 7 | 8 | public static void main(String[] args) { 9 | // TODO Auto-generated method stub 10 | HapporContext sub1 = new HapporSpringContext("conf/web.xml"); 11 | HapporContext sub2 = new HapporSpringContext("conf/web.xml"); 12 | HapporContext sub3 = new HapporSpringContext("conf/web.xml"); 13 | 14 | HapporMultipleContext context = new HapporMultipleContext(); 15 | context.addPath("aaa", sub1); 16 | context.addPath("bbb", sub2); 17 | context.setDefault(sub3); 18 | 19 | context.getServer().setPort(9080); 20 | context.runServer(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/cn/lechange/happor/TestWebserverHandler.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor; 2 | 3 | public class TestWebserverHandler implements WebserverHandler { 4 | 5 | public void onInit(HapporWebserver server) { 6 | // TODO Auto-generated method stub 7 | System.out.println("TestWebserverHandler [" + server + "] init"); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/cn/lechange/happor/TestWithoutSpring.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor; 2 | 3 | import cn.lechange.happor.context.HapporManualContext; 4 | import cn.lechange.happor.controllers.DefaultHandler; 5 | import cn.lechange.happor.controllers.TestAsyncHandler; 6 | import cn.lechange.happor.controllers.TestIncomingFilter; 7 | import cn.lechange.happor.controllers.TestNormalHandler; 8 | import cn.lechange.happor.controllers.TestTransitHandler; 9 | 10 | public class TestWithoutSpring { 11 | 12 | /** 13 | * @param args 14 | */ 15 | public static void main(String[] args) { 16 | // TODO Auto-generated method stub 17 | HapporContext context = new HapporManualContext(); 18 | 19 | context.setWebserverHandler(new TestWebserverHandler()) 20 | .addController(TestIncomingFilter.class, null, ".*") 21 | .addController(TestNormalHandler.class, "GET", "^/test/(\\w+)") 22 | .addController(TestAsyncHandler.class, "GET", "^/async") 23 | .addController(TestTransitHandler.class, "GET", "^/trans") 24 | .addController(DefaultHandler.class); 25 | 26 | context.getServer().setPort(9080); 27 | context.getServer().setExecuteThreads(16); 28 | context.getServer().setMaxHttpSize(1000000); 29 | context.getServer().setTimeout(30); 30 | 31 | context.runServer(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/cn/lechange/happor/controllers/DefaultHandler.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.controllers; 2 | 3 | import cn.lechange.happor.annotation.DefaultController; 4 | import cn.lechange.happor.controller.HttpNormalHandler; 5 | import io.netty.handler.codec.http.FullHttpRequest; 6 | import io.netty.handler.codec.http.FullHttpResponse; 7 | import io.netty.handler.codec.http.HttpResponseStatus; 8 | 9 | @DefaultController 10 | public class DefaultHandler extends HttpNormalHandler { 11 | 12 | @Override 13 | protected void handle(FullHttpRequest request, FullHttpResponse response) { 14 | // TODO Auto-generated method stub 15 | response.setStatus(HttpResponseStatus.FORBIDDEN); 16 | } 17 | 18 | @Override 19 | protected void atlast() { 20 | // TODO Auto-generated method stub 21 | 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/cn/lechange/happor/controllers/TestAsyncHandler.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.controllers; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | import io.netty.handler.codec.http.FullHttpResponse; 5 | import cn.lechange.happor.annotation.Controller; 6 | import cn.lechange.happor.controller.HttpAsyncHandler; 7 | 8 | @Controller(method="GET", uriPattern="^/async") 9 | public class TestAsyncHandler extends HttpAsyncHandler { 10 | 11 | @Override 12 | protected void handle(FullHttpRequest request, final FullHttpResponse response) { 13 | // TODO Auto-generated method stub 14 | new Thread() { 15 | @Override 16 | public void run() { 17 | try { 18 | Thread.sleep(3000); 19 | } catch (InterruptedException e) { 20 | // TODO Auto-generated catch block 21 | e.printStackTrace(); 22 | } 23 | response.content().writeBytes("test async ok!".getBytes()); 24 | response.headers().set("Content-Type", "text/plain"); 25 | response.headers().set("Content-Length", response.content().readableBytes()); 26 | finish(response); 27 | } 28 | }.start(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/cn/lechange/happor/controllers/TestIncomingFilter.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.controllers; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | import io.netty.handler.codec.http.FullHttpResponse; 5 | import cn.lechange.happor.annotation.Controller; 6 | import cn.lechange.happor.annotation.Filter; 7 | import cn.lechange.happor.controller.HttpNormalFilter; 8 | 9 | @Controller 10 | @Filter("incoming") 11 | public class TestIncomingFilter extends HttpNormalFilter { 12 | 13 | @Override 14 | protected void incoming(FullHttpRequest request) { 15 | // TODO Auto-generated method stub 16 | System.out.println("incoming..."); 17 | request.headers().set("x-filter-incoming", getClass().getSimpleName()); 18 | } 19 | 20 | @Override 21 | protected void outgoing(FullHttpResponse response) { 22 | // TODO Auto-generated method stub 23 | System.out.println("outgoing..."); 24 | response.headers().set("x-filter-outgoing", getClass().getSimpleName()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/cn/lechange/happor/controllers/TestNormalHandler.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.controllers; 2 | 3 | import cn.lechange.happor.annotation.Controller; 4 | import cn.lechange.happor.controller.HttpNormalHandler; 5 | import io.netty.handler.codec.http.FullHttpRequest; 6 | import io.netty.handler.codec.http.FullHttpResponse; 7 | 8 | @Controller(method="GET", uriPattern="^/test/(\\w+)") 9 | public class TestNormalHandler extends HttpNormalHandler { 10 | 11 | @Override 12 | protected void handle(FullHttpRequest request, FullHttpResponse response) { 13 | // TODO Auto-generated method stub 14 | String words = "hello world " + getUriParser().getSection(1); 15 | response.content().writeBytes(words.getBytes()); 16 | response.headers().set("Content-Type", "text/plain"); 17 | response.headers().set("Content-Length", response.content().readableBytes()); 18 | } 19 | 20 | @Override 21 | protected void atlast() { 22 | // TODO Auto-generated method stub 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/cn/lechange/happor/controllers/TestTransitHandler.java: -------------------------------------------------------------------------------- 1 | package cn.lechange.happor.controllers; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | import io.netty.handler.codec.http.FullHttpResponse; 5 | import cn.lechange.happor.annotation.Controller; 6 | import cn.lechange.happor.controller.HttpTransitHandler; 7 | 8 | @Controller(method="GET", uriPattern="^/trans") 9 | public class TestTransitHandler extends HttpTransitHandler { 10 | 11 | @Override 12 | protected void incoming(FullHttpRequest request) { 13 | // TODO Auto-generated method stub 14 | request.setUri("/test/fromtrans"); 15 | request.headers().add("x-incoming", "1"); 16 | transit("127.0.0.1", 9080); 17 | } 18 | 19 | @Override 20 | protected void outgoing(FullHttpResponse response) { 21 | // TODO Auto-generated method stub 22 | response.headers().add("x-outgoing", "1"); 23 | response.content().writeBytes(" - transit back".getBytes()); 24 | response.headers().set("Content-Length", response.content().readableBytes()); 25 | } 26 | 27 | } 28 | --------------------------------------------------------------------------------