feat: add components
This commit is contained in:
415
components/component-httpserver-ex.js
Normal file
415
components/component-httpserver-ex.js
Normal file
@@ -0,0 +1,415 @@
|
||||
|
||||
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('<html>\n'
|
||||
+ '<head>\n'
|
||||
+ '<title>Redirecting ...</title>\n'
|
||||
+ '<meta http-equiv="refresh" content="0; URL=' + httpExchange.getRequestURI().getPath() + '/' + '" />\n'
|
||||
+ '</head>\n'
|
||||
+ '<body>\n'
|
||||
+ 'Redirect to new location: ' + httpExchange.getRequestURI().getPath() + '/' + '\n'
|
||||
+ '</body>\n'
|
||||
+ '</html>').getBytes('UTF-8')
|
||||
};
|
||||
}
|
||||
|
||||
var files = dir.getAbsoluteFile().listFiles();
|
||||
sb.push('<h1>Directory Listing</h1>\n');
|
||||
sb.push('<br>\n');
|
||||
$EACH(files, (f) => {
|
||||
var isDir = f.isDirectory();
|
||||
if (isDir) {
|
||||
sb.push('<a href="' + f.getName() + '/">[' + f.getName() + ']</a>'
|
||||
+ ' <span style="font-size:12px;"><i>' + new Date(f.lastModified()) + '</i></span>'
|
||||
+ '<br>\n');
|
||||
} else {
|
||||
sb.push('<a href="' + f.getName() + '">' + f.getName() + '</a>'
|
||||
+ ' <span style="font-size:12px;">'
|
||||
+ bytes.showBytes(f.length())
|
||||
+ ' <i>' + new Date(f.lastModified()) + '</i>'
|
||||
+ '</span>'
|
||||
+ '<br>\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();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user