├── .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 extends HttpController> 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 extends HttpController> clazz) {
37 | this.clazz = clazz;
38 | this.className = clazz.getName();
39 | }
40 |
41 | public Class extends HttpController> 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 extends HttpController> 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 extends HttpController>) 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 extends HttpController>) 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 extends HttpController> 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 extends HttpController> 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 extends HttpController> 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 extends HttpController> 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 extends HttpController> 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 extends HttpController> 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 extends HttpController> clazz = (Class extends HttpController>) 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 extends HttpController> 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 |
--------------------------------------------------------------------------------