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 		var context = new jls.net.http.HttpContext(path);
 45 		if (handler) {
 46 			context.setHandler(handler);
 47 		}
 48         this._contexts[path] = context;
 49 		return context;
 50     },
 51     getHttpContext : function(path)  {
 52         for (var p in this._contexts) {
 53             if (path.startsWith('/' + p)) {
 54 				jls.logger.info('Found context path "' + p + '" for "' + path + '"');
 55                 // TODO Find the context with the maximum path matching length
 56                 return this._contexts[p];
 57             }
 58         }
 59 		jls.logger.info('No context found for "' + path + '"');
 60         return this._defaultContext;
 61     },
 62     start : function() {
 63         this._selectorThread.start();
 64         jls.logger.info('HTTP server started');
 65     },
 66     stop : function() {
 67         this._selectorThread.stop();
 68         jls.logger.info('HTTP server stopped');
 69     }
 70 });
 71 
 72 Object.extend(jls.net.http.HttpServer, /** @lends jls.net.http.HttpServer */
 73 {
 74     /**
 75      * Creates a HTTP server for a specified socket address.
 76      * 
 77      * @param {jls.net.InetSocketAddress} addr The socket address.
 78      * @param {Number} [backlog] The backlog.
 79      * @returns {jls.net.http.HttpServer} the created HTTP server.
 80      */
 81 	create : function(addr, backlog) {
 82 		var httpServer = new jls.net.http.HttpServer();
 83 		if (addr) {
 84 			httpServer.bind(addr, backlog);
 85 		}
 86 		return httpServer;
 87     },
 88     defaultHandler : function(httpExchange) {
 89         jls.logger.info('onRequest(' + httpExchange.getRequestURI() + ')');
 90         //httpExchange.setRequestBodySelectionHandler();
 91         httpExchange.sendResponseHeaders(jls.net.http.HttpHeader.HTTP_NOT_FOUND, 'Resource "' + httpExchange.getRequestURI() + '" not found.');
 92     },
 93     /**
 94      * A basic file handler.
 95      * 
 96      * @param {jls.net.http.HttpExchange} httpExchange The socket address.
 97      */
 98     fileHandler : function(httpExchange) {
 99 		var reqPath = httpExchange.getRequestURI();
100 		var ctxPath = httpExchange.getHttpContext().getPath();
101 		var path = reqPath.substring(ctxPath.length < 2 ? ctxPath.length + 1 : ctxPath.length + 2);
102         jls.logger.info('_fileHandler(URI: ' + httpExchange.getRequestURI() + ', ' + ctxPath + ', ' + path + ')');
103 		var rootFile = httpExchange.getHttpContext().getAttribute('rootFile');
104         if (rootFile == null) {
105             throw new jls.lang.Exception('fileHandler: Root file not defined');
106         }
107 		var res = path.length == 0 ? rootFile : new jls.io.File(rootFile, path);
108         jls.logger.info('Checking "' + res.getPath() + '"');
109 		if (res.exists()) {
110 			jls.logger.info('Sending "' + res.getPath() + '"');
111 			if (res.isFile()) {
112 				httpExchange.sendResponseHeaders(jls.net.http.HttpHeader.HTTP_OK, new jls.net.FileSelectionHandler(res));
113 			} else {
114 				if (! reqPath.endsWith('/')) {
115 					reqPath += '/';
116 				}
117 				var content = '';
118 				var filenames = res.list();
119 				for (var i = 0; i < filenames.length; i++) {
120 					content += '<a href="' + reqPath + filenames[i] + '">' + filenames[i] + '</a><br>';
121 				}
122 				httpExchange.sendResponseHeaders(jls.net.http.HttpHeader.HTTP_OK, content);
123 			}
124 		} else {
125 			jls.logger.info('Not found');
126             jls.net.http.HttpServer.defaultHandler(httpExchange);
127 		}
128     },
129     _onAccept : function(key, inFlags, outFlags) {
130         jls.logger.info('Accept connection');
131         
132         var socket = key.socket.accept();
133         socket.configureBlocking(false);
134         
135         var readKey = key.selectorThread.register(socket, jls.net.http.HttpServer._onReadHeader, jls.net.Selector.OP_READ);
136         readKey.header = new jls.net.http.HttpRequestHeader();
137         readKey.httpServer = key.httpServer;
138     },
139     _onClose : function(key) {
140         jls.logger.info('Closing socket');
141         key.selectorThread.lazyunregister(key);
142         key.socket.close();
143     },
144     _onReadHeader : function(key, inFlags, outFlags) {
145         var status = key.header.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.header.getUri() == '/admin/stop') {
151                 jls.net.http.ServerMgr.getInstance().shutdown();
152                 jls.net.http.HttpServer._onClose(key);
153                 return;
154             }
155             // The header has been proceed, create the http exchange
156             var context = key.httpServer.getHttpContext(key.header.getUri());
157             var handle = context.getHandler();
158             jls.logger.info('Request path is "' + context.getPath() + '"');
159             jls.logger.info('header: ->' + key.header.toString() + '<-');
160             key.httpExchange = new jls.net.http.HttpExchange(context, key.header, key.socket);
161             // Call handler
162             handle(key.httpExchange);
163             // Dump response
164             /*jls.lang.System.out.print('Response: ->');
165             key.httpExchange.onWrite(jls.lang.System.out);
166             jls.lang.System.out.println('<-');*/
167             key.httpExchange._responseHandler.reset();
168             // Switch socket handler
169             key.selectorThread.changeHanlder(key, jls.net.http.HttpServer._onRead);
170 			// call it
171 			jls.net.http.HttpServer._onRead(key, inFlags, outFlags);
172             break;
173         default:
174             jls.logger.warn('Invalid selection handler status (' + status + ')');
175         case jls.net.SelectionHandler.STATUS_FAILURE:
176             // No more data, closing socket
177             jls.net.http.HttpServer._onClose(key);
178             break;
179         }
180     },
181     _onRead : function(key, inFlags, outFlags) {
182         jls.logger.info('HttpServer._onRead()');
183         var status = key.httpExchange.onRead(key.socket);
184         switch (status) {
185         case jls.net.SelectionHandler.STATUS_IN_PROGRESS:
186             break;
187         case jls.net.SelectionHandler.STATUS_DONE:
188             key.selectorThread.changeHanlder(key, jls.net.http.HttpServer._onWrite);
189             key.selectorThread.changeMode(key, jls.net.Selector.OP_WRITE);
190             break;
191         default:
192             jls.logger.warn('Invalid selection handler status (' + status + ')');
193         case jls.net.SelectionHandler.STATUS_FAILURE:
194             // No more data, closing socket
195             jls.net.http.HttpServer._onClose(key);
196             break;
197         }
198     },
199     _onWrite : function(key, inFlags, outFlags) {
200         jls.logger.info('HttpServer._onWrite()');
201         var status = key.httpExchange.onWrite(key.socket);
202         switch (status) {
203         case jls.net.SelectionHandler.STATUS_IN_PROGRESS:
204             break;
205         case jls.net.SelectionHandler.STATUS_DONE:
206             key.selectorThread.changeHanlder(key, jls.net.http.HttpServer._onReadHeader);
207             key.selectorThread.changeMode(key, jls.net.Selector.OP_READ);
208             key.httpExchange = null;
209             break;
210         default:
211             jls.logger.warn('Invalid selection handler status (' + status + ')');
212         case jls.net.SelectionHandler.STATUS_FAILURE:
213             // No more data, closing socket
214             jls.net.http.HttpServer._onClose(key);
215             break;
216         }
217     }
218 });
219 
220