1 jls.loader.provide('jls.net.http.HttpServer');
  2 
  3 jls.loader.require('jls.lang.Thread');
  4 jls.loader.require('jls.net.Socket');
  5 jls.loader.require('jls.net.ServerSocket');
  6 jls.loader.require('jls.net.SelectorThread');
  7 jls.loader.require('jls.net.BufferSelectionHandler');
  8 jls.loader.require('jls.net.http.HttpContext');
  9 jls.loader.require('jls.net.http.HttpExchange');
 10 jls.loader.require('jls.net.http.HttpRequestHeader');
 11 jls.loader.require('jls.net.FileSelectionHandler');
 12 jls.loader.require('jls.io.File');
 13 
 14 jls.net.http.HttpServer = jls.lang.Class.create(/** @lends jls.net.http.HttpServer.prototype */
 15 {
 16     /**
 17      * Creates a HTTP server.
 18      *
 19      * @constructs
 20 	 * @class A HTTP server.
 21      */
 22     initialize : function() {
 23         this._selectorThread = new jls.net.SelectorThread();
 24         this._contexts = {};
 25         this._defaultContext = new jls.net.http.HttpContext('');
 26         this._defaultContext.setHandler(jls.net.http.HttpServer.defaultHandler);
 27     },
 28     bind : function(addr, backlog) {
 29         jls.logger.info('Start HTTP server on ' + addr.toString());
 30         this._serverSocket = new jls.net.ServerSocket(addr.getPort(), backlog, addr.getAddress());
 31         this._serverSocket.configureBlocking(false);
 32         
 33         var key = this._selectorThread.register(this._serverSocket, jls.net.http.HttpServer._onAccept, jls.net.Selector.OP_ACCEPT);
 34         key.httpServer = this;
 35     },
 36     /**
 37      * Creates a context at the given path.
 38      * 
 39      * @param {String} path The path of the context.
 40      * @param {Function} [handler] The handler of the context.
 41      * @returns {jls.net.http.HttpContext} the created context.
 42      */
 43     createContext : function(path, handler)  {
 44         jls.logger.debug('HttpServer.createContext("' + path + '")');
 45 		var context = new jls.net.http.HttpContext(path);
 46 		if (handler) {
 47 			context.setHandler(handler);
 48 		}
 49         this._contexts[path] = context;
 50 		return context;
 51     },
 52     getHttpContext : function(path)  {
 53         jls.logger.debug('getHttpContext("' + path + '")');
 54         var maxLength = -1;
 55         var context = null;
 56         for (var p in this._contexts) {
 57             jls.logger.trace('context "' + p + '"');
 58             if ((p.length > maxLength) && ((path.startsWith(p)) || (path + '/' == p))) {
 59 				jls.logger.info('Found matching context path "' + p + '" for "' + path + '"');
 60                 maxLength = p.length;
 61                 context = this._contexts[p];
 62             }
 63         }
 64         if (context == null) {
 65             jls.logger.info('No matching context found for "' + path + '"');
 66             return this._defaultContext;
 67         }
 68         return context;
 69     },
 70     start : function() {
 71         this._selectorThread.start();
 72         jls.logger.info('HTTP server started');
 73     },
 74     stop : function() {
 75         this._selectorThread.stop();
 76         jls.logger.info('HTTP server stopped');
 77     }
 78 });
 79 
 80 Object.extend(jls.net.http.HttpServer, /** @lends jls.net.http.HttpServer */
 81 {
 82     /**
 83      * Creates a HTTP server for a specified socket address.
 84      * 
 85      * @param {jls.net.InetSocketAddress} addr The socket address.
 86      * @param {Number} [backlog] The backlog.
 87      * @returns {jls.net.http.HttpServer} the created HTTP server.
 88      */
 89 	create : function(addr, backlog) {
 90 		var httpServer = new jls.net.http.HttpServer();
 91 		if (addr) {
 92 			httpServer.bind(addr, backlog);
 93 		}
 94 		return httpServer;
 95     },
 96     _onAccept : function(key, inFlags, outFlags) {
 97         jls.logger.info('Accept connection');
 98         
 99         var socket = key.socket.accept();
100         socket.configureBlocking(false);
101         
102         var readKey = key.selectorThread.register(socket, jls.net.http.HttpServer._onReadHeader, jls.net.Selector.OP_READ);
103         readKey.header = new jls.net.http.HttpRequestHeader();
104         readKey.httpServer = key.httpServer;
105     },
106     _onClose : function(key) {
107         jls.logger.info('Closing socket');
108         key.selectorThread.lazyunregister(key);
109         key.socket.close();
110     },
111     _onReadHeader : function(key, inFlags, outFlags) {
112         var status = key.header.onRead(key.socket);
113         switch (status) {
114         case jls.net.SelectionHandler.STATUS_IN_PROGRESS:
115             break;
116         case jls.net.SelectionHandler.STATUS_DONE:
117             if (key.header.getUri() == '/admin/stop') {
118                 jls.net.http.ServerMgr.getInstance().shutdown();
119                 jls.net.http.HttpServer._onClose(key);
120                 return;
121             }
122             // The header has been proceed, create the http exchange
123             var context = key.httpServer.getHttpContext(key.header.getUri());
124             var handle = context.getHandler();
125             jls.logger.info('Request path is "' + context.getPath() + '"');
126             jls.logger.info('header: ->' + key.header.toString() + '<-');
127             key.httpExchange = new jls.net.http.HttpExchange(context, key.header, key.socket);
128             // Call handler
129             handle(key.httpExchange);
130             // Dump response
131             /*jls.lang.System.out.print('Response: ->');
132             key.httpExchange.onWrite(jls.lang.System.out);
133             jls.lang.System.out.println('<-');*/
134             // Switch socket handler
135             key.selectorThread.changeHandler(key, jls.net.http.HttpServer._onRead);
136 			// call it
137 			jls.net.http.HttpServer._onRead(key, inFlags, outFlags);
138             break;
139         default:
140             jls.logger.warn('Invalid selection handler status (' + status + ')');
141         case jls.net.SelectionHandler.STATUS_FAILURE:
142             // No more data, closing socket
143             jls.net.http.HttpServer._onClose(key);
144             break;
145         }
146     },
147     _onRead : function(key, inFlags, outFlags) {
148         jls.logger.info('HttpServer._onRead()');
149         var status = key.httpExchange.onRead(key.socket);
150         switch (status) {
151         case jls.net.SelectionHandler.STATUS_IN_PROGRESS:
152             break;
153         case jls.net.SelectionHandler.STATUS_DONE:
154             key.httpExchange._responseHandler.reset();
155             key.selectorThread.changeHandler(key, jls.net.http.HttpServer._onWrite);
156             key.selectorThread.changeMode(key, jls.net.Selector.OP_WRITE);
157             break;
158         default:
159             jls.logger.warn('Invalid selection handler status (' + status + ')');
160         case jls.net.SelectionHandler.STATUS_FAILURE:
161             // No more data, closing socket
162             jls.net.http.HttpServer._onClose(key);
163             break;
164         }
165     },
166     _onWrite : function(key, inFlags, outFlags) {
167         jls.logger.info('HttpServer._onWrite()');
168         var status = key.httpExchange.onWrite(key.socket);
169         switch (status) {
170         case jls.net.SelectionHandler.STATUS_IN_PROGRESS:
171             break;
172         case jls.net.SelectionHandler.STATUS_DONE:
173             key.selectorThread.changeHandler(key, jls.net.http.HttpServer._onReadHeader);
174             key.selectorThread.changeMode(key, jls.net.Selector.OP_READ);
175             key.httpExchange = null;
176             break;
177         default:
178             jls.logger.warn('Invalid selection handler status (' + status + ')');
179         case jls.net.SelectionHandler.STATUS_FAILURE:
180             // No more data, closing socket
181             jls.net.http.HttpServer._onClose(key);
182             break;
183         }
184     }
185 });
186 
187 /*
188  * Provide basic handler implementations.
189  */
190 Object.extend(jls.net.http.HttpServer, /** @lends jls.net.http.HttpServer */
191 {
192     /**
193      * A default handler implementation with the HTTP status: not found.
194      * 
195      * @param {jls.net.http.HttpExchange} httpExchange The socket address.
196      */
197     defaultHandler : function(httpExchange) {
198         jls.logger.info('onRequest(' + httpExchange.getRequestURI() + ')');
199         //httpExchange.setRequestBodySelectionHandler();
200         httpExchange.sendResponseHeaders(jls.net.http.HttpHeader.HTTP_NOT_FOUND, 'Resource "' + httpExchange.getRequestURI() + '" not found.');
201     },
202     /**
203      * A basic body handler implementation.
204      * Parameters:<dl>
205      * <dt>requestHandler</dt><dd>The function to call once the body has been read</dd>
206      * </dl>
207      * 
208      * @param {jls.net.http.HttpExchange} httpExchange The socket address.
209      */
210     bodyHandler : function(httpExchange) {
211 		var requestHandler = httpExchange.getHttpContext().getAttribute('requestHandler');
212         var contentLength = httpExchange.getRequestHeaders().getContentLength();
213 		// TODO use a buffer pool to reuse allocated buffers
214         var buffer = jls.lang.ByteBuffer.allocate(contentLength);
215         var sh = new jls.net.BufferSelectionHandler(buffer);
216         var previousOnRead = sh.onRead;
217         sh.onRead = function(channel) {
218             var status = previousOnRead.call(sh, channel);
219             if (status == jls.net.SelectionHandler.STATUS_DONE) {
220                 sh.buffer().flip();
221                 requestHandler(httpExchange, sh.buffer());
222             }
223             return status;
224         };
225         httpExchange.setRequestBodySelectionHandler(sh);
226     },
227     /**
228      * A basic folder page generation implementation.
229      * 
230      * @param {String} reqPath The folder request path.
231      * @param {Array} filenames The file name list.
232      * @returns {String} The HTML content.
233      */
234     createFolderPage : function(reqPath, filenames) {
235 		if (! reqPath.endsWith('/')) {
236 			reqPath += '/';
237 		}
238 		var content = '<html><head></head><body>';
239 		for (var i = 0; i < filenames.length; i++) {
240 			content += '<a href="' + reqPath + filenames[i] + '">' + filenames[i] + '</a><br>';
241 		}
242 		content += '</body></html>';
243 		return content;
244     },
245     /**
246      * Default content type objects.
247      */
248 	defaultContentTypes : {
249 		'.html' : 'text/html; charset=UTF-8',
250 		'.js' : 'text/plain; charset=UTF-8',
251 		'.txt' : 'text/plain; charset=UTF-8',
252 		'.xml' : 'text/xml; charset=UTF-8'
253 	},
254     /**
255      * A basic file handler implementation.
256      * Parameters:<dl>
257      * <dt>rootFile</dt><dd>The root path to look for files</dd>
258      * <dt>contentTypes</dt><dd>The extention / content type mapping to use</dd>
259      * <dt>createFolderPage</dt><dd>A function to generate a folder page</dd>
260      * </dl>
261      * 
262      * @param {jls.net.http.HttpExchange} httpExchange The socket address.
263      */
264     fileHandler : function(httpExchange) {
265 		var reqPath = httpExchange.getRequestURI();
266 		var ctxPath = httpExchange.getHttpContext().getPath();
267 		var path = reqPath.substring(ctxPath.length);
268         jls.logger.info('fileHandler(URI: ' + reqPath + ', ' + ctxPath + ', ' + path + ')');
269         var rootFile = httpExchange.getHttpContext().getAttribute('rootFile');
270         if (rootFile == null) {
271             throw new jls.lang.Exception('fileHandler: Root file not defined');
272         }
273         var contentTypes = httpExchange.getHttpContext().getAttribute('contentTypes');
274         if (contentTypes == null) {
275             contentTypes = {};
276             httpExchange.getHttpContext().setAttribute('contentTypes', contentTypes);
277         }
278         var res = path.length == 0 ? rootFile : new jls.io.File(rootFile, path);
279         jls.logger.info('Checking "' + res.getPath() + '"');
280 		if (res.exists()) {
281 			jls.logger.info('Sending "' + res.getPath() + '"');
282 			if (res.isFile()) {
283                 for (var ext in contentTypes) {
284                     if (res.getPath().endsWith(ext)) {
285                         httpExchange.getResponseHeaders().setField(jls.net.http.HttpHeader.HEADER_CONTENT_TYPE,
286                             contentTypes[ext]);
287                         break;
288                     }
289                 }
290 				httpExchange.sendResponseHeaders(jls.net.http.HttpHeader.HTTP_OK, new jls.net.FileSelectionHandler(res));
291 			} else {
292 				var createFolderPage = httpExchange.getHttpContext().getAttribute('createFolderPage');
293 				if (createFolderPage != null) {
294 					var content = createFolderPage(reqPath, res.list());
295 					httpExchange.sendResponseHeaders(jls.net.http.HttpHeader.HTTP_OK, content);
296 				} else {
297 					jls.logger.info('Cannot browse folder');
298 					jls.net.http.HttpServer.defaultHandler(httpExchange);
299 				}
300 			}
301 		} else {
302 			jls.logger.info('Not found');
303             jls.net.http.HttpServer.defaultHandler(httpExchange);
304 		}
305     }
306 });
307