PageRenderTime 59ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/org/nodex/java/core/http/HttpServer.java

https://github.com/starksm64/node.x
Java | 371 lines | 310 code | 46 blank | 15 comment | 32 complexity | 9ccd896b0611d831798783da1ec306fe MD5 | raw file
  1. /*
  2. * Copyright 2002-2011 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
  5. * this file except in compliance with the License. You may obtain a copy of the
  6. * License at http://www.apache.org/licenses/LICENSE-2.0
  7. *
  8. * Unless required by applicable law or agreed to in writing, software distributed
  9. * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  10. * CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11. * specific language governing permissions and limitations under the License.
  12. */
  13. package org.nodex.java.core.http;
  14. import org.jboss.netty.bootstrap.ServerBootstrap;
  15. import org.jboss.netty.buffer.ChannelBuffer;
  16. import org.jboss.netty.channel.Channel;
  17. import org.jboss.netty.channel.ChannelFactory;
  18. import org.jboss.netty.channel.ChannelHandlerContext;
  19. import org.jboss.netty.channel.ChannelPipeline;
  20. import org.jboss.netty.channel.ChannelPipelineFactory;
  21. import org.jboss.netty.channel.ChannelState;
  22. import org.jboss.netty.channel.ChannelStateEvent;
  23. import org.jboss.netty.channel.Channels;
  24. import org.jboss.netty.channel.ExceptionEvent;
  25. import org.jboss.netty.channel.MessageEvent;
  26. import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
  27. import org.jboss.netty.channel.group.ChannelGroup;
  28. import org.jboss.netty.channel.group.ChannelGroupFuture;
  29. import org.jboss.netty.channel.group.ChannelGroupFutureListener;
  30. import org.jboss.netty.channel.group.DefaultChannelGroup;
  31. import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
  32. import org.jboss.netty.channel.socket.nio.NioSocketChannel;
  33. import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
  34. import org.jboss.netty.handler.codec.http.HttpHeaders;
  35. import org.jboss.netty.handler.codec.http.HttpRequest;
  36. import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
  37. import org.jboss.netty.handler.codec.http.HttpResponse;
  38. import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
  39. import org.jboss.netty.handler.codec.http.HttpResponseStatus;
  40. import org.jboss.netty.handler.codec.http.websocket.WebSocketFrameDecoder;
  41. import org.jboss.netty.handler.codec.http.websocket.WebSocketFrameEncoder;
  42. import org.jboss.netty.handler.ssl.SslHandler;
  43. import org.jboss.netty.handler.stream.ChunkedWriteHandler;
  44. import org.nodex.java.core.EventHandler;
  45. import org.nodex.java.core.Nodex;
  46. import org.nodex.java.core.NodexInternal;
  47. import org.nodex.java.core.SSLBase;
  48. import org.nodex.java.core.ThreadSourceUtils;
  49. import javax.net.ssl.SSLEngine;
  50. import java.net.InetAddress;
  51. import java.net.InetSocketAddress;
  52. import java.net.UnknownHostException;
  53. import java.util.HashMap;
  54. import java.util.Map;
  55. import java.util.concurrent.ConcurrentHashMap;
  56. import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
  57. import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.ORIGIN;
  58. import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY1;
  59. import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY2;
  60. import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_LOCATION;
  61. import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_ORIGIN;
  62. import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_PROTOCOL;
  63. import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
  64. import static org.jboss.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
  65. import static org.jboss.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
  66. import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
  67. public class HttpServer extends SSLBase {
  68. private EventHandler<HttpServerRequest> requestHandler;
  69. private EventHandler<Websocket> wsHandler;
  70. private Map<Channel, ServerConnection> connectionMap = new ConcurrentHashMap();
  71. private Map<String, Object> connectionOptions = new HashMap();
  72. private ChannelGroup serverChannelGroup;
  73. private boolean listening;
  74. private ClientAuth clientAuth = ClientAuth.NONE;
  75. private final Thread th;
  76. private final long contextID;
  77. public HttpServer() {
  78. Long cid = Nodex.instance.getContextID();
  79. if (cid == null) {
  80. throw new IllegalStateException("HTTPServer can only be used from an event loop");
  81. }
  82. this.contextID = cid;
  83. this.th = Thread.currentThread();
  84. //Defaults
  85. connectionOptions.put("child.tcpNoDelay", true);
  86. connectionOptions.put("child.keepAlive", true);
  87. connectionOptions.put("reuseAddress", true); //Not child since applies to the acceptor socket
  88. }
  89. public HttpServer requestHandler(EventHandler<HttpServerRequest> requestHandler) {
  90. checkThread();
  91. this.requestHandler = requestHandler;
  92. return this;
  93. }
  94. public HttpServer websocketHandler(EventHandler<Websocket> wsHandler) {
  95. checkThread();
  96. this.wsHandler = wsHandler;
  97. return this;
  98. }
  99. public HttpServer listen(int port) {
  100. return listen(port, "0.0.0.0");
  101. }
  102. public HttpServer listen(int port, String host) {
  103. checkThread();
  104. if (requestHandler == null && wsHandler == null) {
  105. throw new IllegalStateException("Set request or websocket handler first");
  106. }
  107. if (listening) {
  108. throw new IllegalStateException("Listen already called");
  109. }
  110. listening = true;
  111. serverChannelGroup = new DefaultChannelGroup("nodex-acceptor-channels");
  112. ChannelFactory factory =
  113. new NioServerSocketChannelFactory(
  114. NodexInternal.instance.getAcceptorPool(),
  115. NodexInternal.instance.getWorkerPool());
  116. ServerBootstrap bootstrap = new ServerBootstrap(factory);
  117. bootstrap.setOptions(connectionOptions);
  118. checkSSL();
  119. bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
  120. public ChannelPipeline getPipeline() {
  121. ChannelPipeline pipeline = Channels.pipeline();
  122. if (ssl) {
  123. SSLEngine engine = context.createSSLEngine();
  124. engine.setUseClientMode(false);
  125. switch (clientAuth) {
  126. case REQUEST: {
  127. engine.setWantClientAuth(true);
  128. break;
  129. }
  130. case REQUIRED: {
  131. engine.setNeedClientAuth(true);
  132. break;
  133. }
  134. case NONE: {
  135. engine.setNeedClientAuth(false);
  136. break;
  137. }
  138. }
  139. pipeline.addLast("ssl", new SslHandler(engine));
  140. }
  141. pipeline.addLast("decoder", new HttpRequestDecoder());
  142. pipeline.addLast("encoder", new HttpResponseEncoder());
  143. pipeline.addLast("chunkedWriter", new ChunkedWriteHandler()); // For large file / sendfile support
  144. pipeline.addLast("handler", new ServerHandler());
  145. return pipeline;
  146. }
  147. });
  148. try {
  149. Channel serverChannel = bootstrap.bind(new InetSocketAddress(InetAddress.getByName(host), port));
  150. serverChannelGroup.add(serverChannel);
  151. } catch (UnknownHostException e) {
  152. e.printStackTrace();
  153. }
  154. return this;
  155. }
  156. public HttpServer setSSL(boolean ssl) {
  157. checkThread();
  158. this.ssl = ssl;
  159. return this;
  160. }
  161. public HttpServer setKeyStorePath(String path) {
  162. checkThread();
  163. this.keyStorePath = path;
  164. return this;
  165. }
  166. public HttpServer setKeyStorePassword(String pwd) {
  167. checkThread();
  168. this.keyStorePassword = pwd;
  169. return this;
  170. }
  171. public HttpServer setTrustStorePath(String path) {
  172. checkThread();
  173. this.trustStorePath = path;
  174. return this;
  175. }
  176. public HttpServer setTrustStorePassword(String pwd) {
  177. checkThread();
  178. this.trustStorePassword = pwd;
  179. return this;
  180. }
  181. public HttpServer setClientAuthRequired(boolean required) {
  182. checkThread();
  183. clientAuth = required ? ClientAuth.REQUIRED : ClientAuth.NONE;
  184. return this;
  185. }
  186. public void close() {
  187. checkThread();
  188. close(null);
  189. }
  190. public void close(final EventHandler<Void> doneHandler) {
  191. checkThread();
  192. for (ServerConnection conn : connectionMap.values()) {
  193. conn.internalClose();
  194. }
  195. if (doneHandler != null) {
  196. serverChannelGroup.close().addListener(new ChannelGroupFutureListener() {
  197. public void operationComplete(ChannelGroupFuture channelGroupFuture) throws Exception {
  198. NodexInternal.instance.executeOnContext(contextID, new Runnable() {
  199. public void run() {
  200. doneHandler.onEvent(null);
  201. }
  202. });
  203. }
  204. });
  205. }
  206. }
  207. protected void checkThread() {
  208. // All ops must always be invoked on same thread
  209. if (Thread.currentThread() != th) {
  210. throw new IllegalStateException("Invoked with wrong thread, actual: " + Thread.currentThread() + " expected: " + th);
  211. }
  212. }
  213. public class ServerHandler extends SimpleChannelUpstreamHandler {
  214. private void calcAndWriteWSHandshakeResponse(Channel ch, HttpRequest request, long c) {
  215. String key1 = request.getHeader(SEC_WEBSOCKET_KEY1);
  216. String key2 = request.getHeader(SEC_WEBSOCKET_KEY2);
  217. ChannelBuffer output = WebsocketHandshakeHelper.calcResponse(key1, key2, c);
  218. HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101,
  219. "Web Socket Protocol Handshake"));
  220. res.setContent(output);
  221. res.addHeader(HttpHeaders.Names.CONTENT_LENGTH, res.getContent().readableBytes());
  222. res.addHeader(SEC_WEBSOCKET_ORIGIN, request.getHeader(ORIGIN));
  223. res.addHeader(SEC_WEBSOCKET_LOCATION, getWebSocketLocation(request, request.getUri()));
  224. String protocol = request.getHeader(SEC_WEBSOCKET_PROTOCOL);
  225. if (protocol != null) {
  226. res.addHeader(SEC_WEBSOCKET_PROTOCOL, protocol);
  227. }
  228. res.addHeader(HttpHeaders.Names.UPGRADE, WEBSOCKET);
  229. res.addHeader(CONNECTION, HttpHeaders.Values.UPGRADE);
  230. ChannelPipeline p = ch.getPipeline();
  231. p.replace("decoder", "wsdecoder", new WebSocketFrameDecoder());
  232. ch.write(res);
  233. p.replace("encoder", "wsencoder", new WebSocketFrameEncoder());
  234. }
  235. @Override
  236. public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
  237. NioSocketChannel ch = (NioSocketChannel) e.getChannel();
  238. Object msg = e.getMessage();
  239. ServerConnection conn = connectionMap.get(ch);
  240. if (conn != null) {
  241. if (msg instanceof HttpRequest) {
  242. HttpRequest request = (HttpRequest) msg;
  243. if (HttpHeaders.is100ContinueExpected(request)) {
  244. ch.write(new DefaultHttpResponse(HTTP_1_1, CONTINUE));
  245. }
  246. if (HttpHeaders.Values.UPGRADE.equalsIgnoreCase(request.getHeader(CONNECTION)) &&
  247. WEBSOCKET.equalsIgnoreCase(request.getHeader(HttpHeaders.Names.UPGRADE))) {
  248. // Websocket handshake
  249. Websocket ws = new Websocket(request.getUri(), conn);
  250. boolean containsKey1 = request.containsHeader(SEC_WEBSOCKET_KEY1);
  251. boolean containsKey2 = request.containsHeader(SEC_WEBSOCKET_KEY2);
  252. if (containsKey1 && containsKey2) {
  253. long c = request.getContent().readLong();
  254. calcAndWriteWSHandshakeResponse(ch, request, c);
  255. conn.handleWebsocketConnect(ws);
  256. } else {
  257. ch.write(new DefaultHttpResponse(HTTP_1_1, FORBIDDEN));
  258. }
  259. } else {
  260. conn.handleMessage(msg);
  261. }
  262. } else {
  263. conn.handleMessage(msg);
  264. }
  265. }
  266. }
  267. @Override
  268. public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
  269. throws Exception {
  270. final NioSocketChannel ch = (NioSocketChannel) e.getChannel();
  271. final ServerConnection conn = connectionMap.get(ch);
  272. ch.close();
  273. final Throwable t = e.getCause();
  274. if (conn != null && t instanceof Exception) {
  275. ThreadSourceUtils.runOnCorrectThread(ch, new Runnable() {
  276. public void run() {
  277. conn.handleException((Exception) t);
  278. }
  279. });
  280. } else {
  281. t.printStackTrace();
  282. }
  283. }
  284. @Override
  285. public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
  286. final NioSocketChannel ch = (NioSocketChannel) e.getChannel();
  287. final long contextID = NodexInternal.instance.associateContextWithWorker(ch.getWorker());
  288. ThreadSourceUtils.runOnCorrectThread(ch, new Runnable() {
  289. public void run() {
  290. final ServerConnection conn = new ServerConnection(ch, contextID, Thread.currentThread());
  291. conn.requestHandler(requestHandler);
  292. conn.wsHandler(wsHandler);
  293. connectionMap.put(ch, conn);
  294. }
  295. });
  296. }
  297. @Override
  298. public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) {
  299. final NioSocketChannel ch = (NioSocketChannel) e.getChannel();
  300. final ServerConnection conn = connectionMap.remove(ch);
  301. ThreadSourceUtils.runOnCorrectThread(ch, new Runnable() {
  302. public void run() {
  303. conn.handleClosed();
  304. NodexInternal.instance.destroyContext(conn.getContextID());
  305. }
  306. });
  307. }
  308. @Override
  309. public void channelInterestChanged(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
  310. final NioSocketChannel ch = (NioSocketChannel) e.getChannel();
  311. final ServerConnection conn = connectionMap.get(ch);
  312. ChannelState state = e.getState();
  313. if (state == ChannelState.INTEREST_OPS) {
  314. ThreadSourceUtils.runOnCorrectThread(ch, new Runnable() {
  315. public void run() {
  316. conn.handleInterestedOpsChanged();
  317. }
  318. });
  319. }
  320. }
  321. private String getWebSocketLocation(HttpRequest req, String path) {
  322. return "ws://" + req.getHeader(HttpHeaders.Names.HOST) + path;
  323. }
  324. }
  325. }