var bytes = require('component-bytes.js'); var Runtime = java.lang.Runtime; var InetSocketAddress = java.net.InetSocketAddress; var Executors = java.util.concurrent.Executors; var Bytes = Packages.me.hatter.tools.commons.bytes.Bytes; var IOUtil = Packages.me.hatter.tools.commons.io.IOUtil; var HttpServer = Packages.com.sun.net.httpserver.HttpServer; // { // "status": 200, // "headers": [[key, value],[key2, value2]], // "responseJSON": ..., // "responseBytes": ..., // "responseText": ..., // } var writeResponse = (httpExchange, response) => { response.status = (response.status == null) ? 200 : response.status; var resBs; var resStream = null; if (response.responseJSON != null) { resBs = Bytes.from(JSON.stringify(response.responseJSON)).getBytes(); } else if (response.responseText != null) { resBs = Bytes.from(response.responseText).getBytes(); } else if (response.responseStream != null) { resBs = null; resStream = response.responseStream; } else { resBs = response.responseBytes; } var responseHeaders = httpExchange.getResponseHeaders(); if ((response.headers != null) && (response.headers.length > 0)) { for (var i = 0; i < response.headers.length; i++) { var keyValue = response.headers[i]; responseHeaders.set(keyValue[0], keyValue[1]); } } else { responseHeaders.set('Content-Type', 'text/plain;charset=UTF-8'); } httpExchange.sendResponseHeaders(response.status, (resBs != null) ? resBs.length : 0); var resBody = httpExchange.getResponseBody(); if (resBs != null) { resBody.write(resBs); } else { IOUtil.copy(response.responseStream, resBody); IOUtil.closeQuietly(response.responseStream); } resBody.close(); }; exports.parseQuery = exports.parseQueryParameterMap = (httpExchange) => { var queryParameterMap = {}; var rawQuery = $STR(httpExchange.getRequestURI().getRawQuery()); if (rawQuery != null) { rawQuery.split('&').forEach((kv) => { var indexOfE = kv.indexOf('='); var key = ''; var value = ''; if (indexOfE >= 0) { key = kv.substring(0, indexOfE); value = kv.substring(indexOfE + 1); } else { key = kv; } queryParameterMap[key] = queryParameterMap[key] || []; queryParameterMap[key].push(value); }); } return queryParameterMap; }; var handleDir = (httpExchange, dir, opts) => { var sb = []; if (opts.handleDir) { if (!httpExchange.getRequestURI().getPath().endsWith('/')) { return { status: 302, "headers": [ ["Location", httpExchange.getRequestURI().getPath() + '/'], ["Content-Type", 'text/html;charset=UTF-8'], ], bytes: new java.lang.String('\n' + '\n' + 'Redirecting ...\n' + '\n' + '\n' + '\n' + 'Redirect to new location: ' + httpExchange.getRequestURI().getPath() + '/' + '\n' + '\n' + '').getBytes('UTF-8') }; } var files = dir.getAbsoluteFile().listFiles(); sb.push('

Directory Listing

\n'); sb.push('
\n'); $EACH(files, (f) => { var isDir = f.isDirectory(); if (isDir) { sb.push('[' + f.getName() + ']' + '   ' + new Date(f.lastModified()) + '' + '
\n'); } else { sb.push('' + f.getName() + '' + '   ' + bytes.showBytes(f.length()) + '   ' + new Date(f.lastModified()) + '' + '' + '
\n'); } }); } else { sb.push('Directory listing is forbidden.'); } return { html: sb.join('') }; }; // https://www.freeformatter.com/mime-types-list.html var mimeTypeList = [ [['txt', 'text'], 'text/plain;charset=UTF-8'], [['jpg', 'jpeg'], 'image/jpeg'], [['png'], 'image/png'], [['swf'], 'application/x-shockwave-flash'], [['js'], 'text/javascript;charset=UTF-8'], [['htm', 'html'], 'text/html;charset=UTF-8'], [['css'], 'text/css;charset=UTF-8'], [['geojson'], 'application/vnd.geo+json'], [['svg'], 'image/svg+xml'], [['7z'], 'application/x-7z-compressed'], [['ace'], 'application/x-ace-compressed'], [['acc'], 'application/vnd.americandynamics.acc'], [['pdf'], 'application/pdf'], [['aac'], 'audio/x-aac'], [['apk'], 'application/vnd.android.package-archive'], [['dmg'], 'application/x-apple-diskimage'], [['mpkg'], 'application/vnd.apple.installer+xml'], [['s'], 'text/x-asm'], [['avi'], 'video/x-msvideo'], [['bin'], 'application/octet-stream'], [['bmp'], 'image/bmp'], [['torrent'], 'application/x-bittorrent'], [['sh'], 'application/x-sh'], [['bz'], 'application/x-bzip'], [['bz2'], 'application/x-bzip2'], [['csh'], 'application/x-csh'], [['c'], 'text/x-c'], [['csv'], ' text/csv'], [['deb'], 'application/x-debian-package'], [['dtd'], 'application/xml-dtd'], [['dwg'], 'image/vnd.dwg'], [['es'], 'application/ecmascript'], [['epub'], 'application/epub+zip'], [['eml'], 'message/rfc822'], [['f4v'], 'video/x-f4v'], [['flv'], 'video/x-flv'], [['gif'], 'image/gif'], [['h261'], 'video/h261'], [['h263'], 'video/h263'], [['h264'], 'video/h264'], [['ico'], 'image/x-icon'], [['cer'], 'application/pkix-cert'], [['pki'], 'application/pkixcmp'], [['crl'], 'application/pkix-crl'], [['jad'], 'text/vnd.sun.j2me.app-descriptor'], [['jar'], 'application/java-archive'], [['class'], 'application/java-vm'], [['jnlp'], 'application/x-java-jnlp-file'], [['ser'], 'application/java-serialized-object'], [['java'], 'text/x-java-source,java'], [['json'], 'application/json'], [['m3u', 'm3u8'], 'audio/x-mpegurl'], [['mathml'], 'application/mathml+xml'], [['exe'], 'application/x-msdownload'], [['cab'], 'application/vnd.ms-cab-compressed'], [['eot'], 'application/vnd.ms-fontobject'], [['xls'], 'application/vnd.ms-excel'], [['ppt'], 'application/vnd.ms-powerpoint'], [['doc'], 'application/msword'], [['vsd'], 'application/vnd.visio'], [['chm'], 'application/vnd.ms-htmlhelp'], [['pptx'], 'application/vnd.openxmlformats-officedocument.presentationml.presentation'], [['ppsx'], 'application/vnd.openxmlformats-officedocument.presentationml.slideshow'], [['xlsx'], 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'], [['docx'], 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'], [['dotx'], 'application/vnd.openxmlformats-officedocument.wordprocessingml.template'], [['vsdx'], 'application/vnd.visio2013'], [['mpeg'], 'video/mpeg'], [['mp4a'], 'audio/mp4'], [['mp4'], 'video/mp4'], [['psd'], 'image/vnd.adobe.photoshop'], [['p10'], 'application/pkcs10'], [['p12'], 'application/x-pkcs12'], [['p7m'], 'application/pkcs7-mime'], [['p7s'], 'application/pkcs7-signature'], [['p7r'], 'application/x-pkcs7-certreqresp'], [['p7b'], 'application/x-pkcs7-certificates'], [['p8'], 'application/pkcs8'], [['pgp'], 'application/pgp-encrypted'], [['rar'], 'application/x-rar-compressed'], [['rm'], 'application/vnd.rn-realmedia'], [['rtf'], 'application/rtf'], [['movie'], 'video/x-sgi-movie'], [['tiff'], 'image/tiff'], [['tar'], 'application/x-tar'], [['tex'], 'application/x-tex'], [['ttf'], 'application/x-font-ttf'], [['vcs'], 'text/x-vcalendar'], [['vcf'], 'text/x-vcard'], [['wav'], 'audio/x-wav'], [['woff'], 'application/x-font-woff'], [['webp'], 'image/webp'], [['der'], 'application/x-x509-ca-cert'], [['xml'], 'application/xml'], [['zip'], 'application/zip'] ]; var mimeTypeMap = {}; mimeTypeList.forEach((m) => { m[0].forEach((t) => { mimeTypeMap[t] = m[1]; }); }); var getMimeTypeByExt = (ext) => { return mimeTypeMap[ext] || 'application/octet-stream'; }; var fileCacheSize = 0; var fileCacheCount = 0; var fileCacheMap = {}; var handleFile = (httpExchange, file, opts) => { var fileAbsPath = file.getAbsolutePath(); if (opts.enableFileCache) { println('[INFO] ' + getDateYmd() + ' ' + 'File cache hit: ' + fileAbsPath); var cachedFileContent = fileCacheMap[fileAbsPath]; if (cachedFileContent) { return cachedFileContent; } // cache hit } if (file.exists()) { var path = $STR(httpExchange.getRequestURI().getPath()).toLowerCase(); var lastIndexOfDot = path.lastIndexOf('.'); var ext = ''; if (lastIndexOfDot >= 0) { ext = path.substring(lastIndexOfDot + 1); } var contentType = null; if (opts.getContentType) { contentType = opts.getContentType(httpExchange, file, ext); } contentType = contentType || getMimeTypeByExt(ext); if (opts.enableFileCache && (file.length() < (opts.cacheFileLength || (1024 * 1024 * 5)))) { // default cache file length 5MB if (fileCacheCount < (opts.cacheCount || 1000)) { // default cache size 1000 var fileContent = { headers: [ ['Content-Type', contentType], ['Content-Length', file.length()] ], bytes: $$.rFile(file).rStream().bytesAndClose() }; fileCacheCount++; fileCacheSize += file.length(); println('[INFO] ' + getDateYmd() + ' ' + 'File cached: ' + fileAbsPath + ', size: ' + bytes.showBytes(file.length()) + ', cached count: ' + fileCacheCount + ', cached size: ' + bytes.showBytes(fileCacheSize)); fileCacheMap[fileAbsPath] = fileContent; return fileContent; } } return { headers: [ ['Content-Type', contentType], ['Content-Length', file.length()] ], stream: $$.rFile(file).rStream().stream() }; } else { return null; } }; var getDateYmd = () => { return new java.text.SimpleDateFormat('yyyy-MM-dd HH:mm:ss').format(new java.util.Date()); }; /** * @param {*} httpExchange * @param {*} options * basePath - base path * getContentType (httpExchange, file, ext) - get content type * handleDir - enable dir listing */ exports.handleFile = (httpExchange, options) => { var opts = options || {}; var path = $STR(httpExchange.getRequestURI().getPath()); if (path == '/') { return handleDir(httpExchange, opts.basePath ? $$.file(opts.basePath) : $$.file(''), opts); } var f = path.substring(1); var file = opts.basePath ? $$.file(opts.basePath, f) : $$.file(f); if (file.exists()) { if (file.isDirectory()) { return handleDir(httpExchange, file, opts); } else { return handleFile(httpExchange, file, opts); } } return null; }; /** * * @param {*} port port number * @param {*} handler process handle * return { * text: 'text' * } * return { * html: 'html' * } * ... * @param {*} options * processors - processor count * printErrors - print exception detail */ exports.serveHTTP = (port, handler, options) => { var opts = options || {}; var addr = new InetSocketAddress(port); var httpServer = HttpServer.create(addr, 0); if (opts.processors) { httpServer.setExecutor(Executors.newFixedThreadPool(opts.processors)); } else { httpServer.setExecutor(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2)); } httpServer.createContext("/", (httpExchange) => { try { println('[INFO] ' + getDateYmd() + ' ' + httpExchange.getRequestURI()); var response = handler(httpExchange); if (response != null) { if (response.text != null) { responseResult = { "status": response.status || 200, "headers": [ ["Content-Type", "text/plain;charset=UTF-8"], ], "responseText": response.text }; } else if (response.html != null) { responseResult = { "status": response.status || 200, "headers": [ ["Content-Type", "text/html;charset=UTF-8"], ], "responseText": response.html }; } else if (response.css != null) { responseResult = { "status": response.status || 200, "headers": [ ["Content-Type", "text/css;charset=UTF-8"], ], "responseText": response.css }; } else if (response.js != null) { responseResult = { "status": response.status || 200, "headers": [ ["Content-Type", "text/javascript;charset=UTF-8"], ], "responseText": response.js }; } else if (response.json != null) { responseResult = { "status": response.status || 200, "headers": [ ["Content-Type", "application/json;charset=UTF-8"], ], "responseJSON": response.json }; } else if (response.bytes != null) { responseResult = { "status": response.status || 200, "headers": response.headers, "responseBytes": response.bytes }; } else if (response.stream != null) { responseResult = { "status": response.status || 200, "headers": response.headers, "responseStream": response.stream }; } else { responseResult = { "status": response.status || 200, "headers": [ ["Content-Type", "text/plain;charset=UTF-8"], ], "responseText": $STR(response) }; } writeResponse(httpExchange, responseResult); } } catch (e) { var errMsg = '[ERROR] Request: ' + httpExchange.getRequestURI() + ', exception: ' + e; println('[ERROR] ' + getDateYmd() + ' ' + errMsg); if (e.printStackTrace) { e.printStackTrace(); } writeResponse(httpExchange, { "status": 500, "responseText": opts.printErrors ? errMsg : '500 - Server Error' }); } }); println('[INFO] ' + getDateYmd() + ' ' + "Start listen at: " + port + ' ...'); httpServer.start(); };