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