feat: add components

This commit is contained in:
2025-04-04 17:19:07 +08:00
parent 6f494ec9ca
commit c37b9f0ab2
40 changed files with 2088 additions and 0 deletions

View 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>'
+ ' &nbsp; <span style="font-size:12px;"><i>' + new Date(f.lastModified()) + '</i></span>'
+ '<br>\n');
} else {
sb.push('<a href="' + f.getName() + '">' + f.getName() + '</a>'
+ ' &nbsp; <span style="font-size:12px;">'
+ bytes.showBytes(f.length())
+ ' &nbsp; <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();
};