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