- 
      
 - 
        
Save jianwu/8e76eaec95d9b1300c59596fbfc21b10 to your computer and use it in GitHub Desktop.  
    Node.JS static file web server, also provides CORSProxy, Http/Https proxy function. Put it in your path to fire up servers in any directory, takes an optional port argument. If provide second https port argument, it will also start https. For https to work, need to put key and cert file in the folder.
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | #!/usr/bin/env node | |
| const { argv } = require('process'); | |
| /** | |
| Static http server implemented with NodeJS. | |
| Features: | |
| 1. No external dependencies | |
| 2. Support http/https, with custom port. | |
| 3. Support CORSProxy function (Access-Control-Allow-Origin: *) | |
| 4. Zero configuration, conversion over configuration | |
| 5. Support gzip compression | |
| 6. Support server gz file directly if it exists | |
| Usage: | |
| 1. Static file directory | |
| It will always serve the static files under current directory where the process is started. | |
| 2. Start http server with default port: (http on 80), it will server the static file under current directory | |
| node static_server.js | |
| 3. Start http on 8080 and https on 8443, to support https, you need to put the key.pem and cert.pem under current directory | |
| node static_server.js 8080 8443. | |
| To generate cert.pem: openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 10000 -nodes | |
| 4. To access it as CORSProxy: | |
| https://host/https://www.somewebsite.com | |
| or | |
| http://host/http://www.somewebsite.com | |
| 5. To bypass SSL certification check (when using proxy mode) | |
| NODE_TLS_REJECT_UNAUTHORIZED=0 node static_server.js | |
| */ | |
| function findFirstArg() { | |
| for (let i = 0; i < argv.length; i++) { | |
| if (argv[i].endsWith(__filename)) | |
| return i; | |
| } | |
| return 1; // default: node static_server.js | |
| } | |
| let argIdx = findFirstArg(); | |
| console.log(`argIdx=${argIdx}, _fileName=${__filename}, argv=${argv}`) | |
| const http = require('http'), | |
| https = require('https'), | |
| url = require('url'), | |
| Path = require('path'), | |
| fs = require('fs'), | |
| zlib = require('zlib'), | |
| port = process.argv[argIdx + 1] || 8888, | |
| httpsPort = process.argv[argIdx + 2] || 0; | |
| console.log("Command Line Format: node static_server.js [port] [httpsPort]"); | |
| http.createServer(serve).listen(parseInt(port, 10)); | |
| console.log(`Static file server running at\n => http://localhost:${port}`); | |
| if (httpsPort){ | |
| var opt; | |
| try { | |
| opt = { | |
| key: fs.readFileSync("key.pem"), | |
| cert: fs.readFileSync("cert.pem"), | |
| }; | |
| } catch(e) { | |
| console.log("Error start https: missing key.pem and cert.pem in current directory"); | |
| } | |
| if (opt) { | |
| https.createServer(opt, serve).listen(parseInt(httpsPort, 10)); | |
| console.log(`https at\n => https://localhost:${httpsPort}`); | |
| } | |
| } | |
| console.log(`CTRL + C to shutdown`); | |
| function serve(request, response) { | |
| try { | |
| const path = url.parse(request.url).pathname | |
| if (path.startsWith('/https://') || path.startsWith('/http://')) | |
| serveProxy(path, request, response); | |
| else | |
| serveLocalFile(path, request, response); | |
| } catch(e) { | |
| response.writeHead(500, {'Content-Type': 'text/plain'}); | |
| response.write(`Error: exception=${e}\n`); | |
| response.end(); | |
| } | |
| } | |
| function serveProxy(path, request, response) { | |
| const uri = url.parse(path.substring(1)); | |
| const isHttps = uri.protocol === 'https:'; | |
| const opt = { | |
| hostname: uri.hostname, | |
| port: uri.port || (isHttps ? 443 : 80), | |
| path: uri.path, | |
| method: request.method, | |
| headers: request.headers, | |
| }; | |
| delete opt.headers.host; | |
| const ht = isHttps ? https : http; | |
| const proxy = ht.request(opt, (res) => { | |
| const headers = Object.assign(res.headers, { | |
| 'Access-Control-Allow-Origin': '*', | |
| 'Access-Control-Allow-Method': request.headers['access-control-request-method'] || '*', // TODO: case insensitive | |
| 'Access-Control-Allow-Headers': request.headers['access-control-request-headers'] || '*', | |
| }); | |
| response.writeHead(res.statusCode, headers); | |
| res.on('data', (data) => { | |
| response.write(data); | |
| }); | |
| res.on('end', () => { | |
| response.end() | |
| }); | |
| }); | |
| proxy.on('error', (error) => { | |
| response.writeHead(500, {'Content-Type': 'text/plain'}); | |
| response.write(`Error connect to remote server: exception=${error}\n`); | |
| response.end(); | |
| }); | |
| request.on('data', (data) => { | |
| proxy.write(data) | |
| }); | |
| request.on('end', () => { | |
| proxy.end(); | |
| }); | |
| } | |
| function serveLocalFile(path, request, response) { | |
| // Security: Block access to sensitive files | |
| if (path.endsWith('.pem') || path.includes('key.') || path.includes('cert.') || path.includes('..')) { | |
| console.log(`🚨 Security: Blocked access to sensitive file: ${path}`); | |
| response.writeHead(403, {'Content-Type': 'text/plain'}); | |
| response.write('403 Forbidden - Access denied\n'); | |
| response.end(); | |
| return; | |
| } | |
| var filename = Path.join(process.cwd(), path); | |
| if (fs.existsSync(filename) && fs.statSync(filename).isDirectory()) { | |
| filename += '/index.html'; | |
| } | |
| var acceptEncoding = request.headers['accept-encoding'] || ''; | |
| const contentType = getMineType(path); | |
| const gzFilename = filename + '.gz'; | |
| if (fs.existsSync(gzFilename)) { | |
| response.writeHead(200, { | |
| 'Access-Control-Allow-Origin': '*', | |
| 'content-encoding': 'gzip', | |
| 'content-type': contentType | |
| }); | |
| var gzRaw = fs.createReadStream(gzFilename); | |
| gzRaw.pipe(response); | |
| return; | |
| } | |
| if (!fs.existsSync(filename)) { | |
| response.writeHead(404, {'Content-Type': 'text/plain'}); | |
| response.write(`404 Not Found: ${filename}\n`); | |
| response.end(); | |
| return; | |
| } | |
| // Fallback to original compression logic | |
| var raw = fs.createReadStream(filename); | |
| if (acceptEncoding.match(/\bdeflate\b/)) { | |
| response.writeHead(200, { | |
| 'Access-Control-Allow-Origin': '*', | |
| 'content-encoding': 'deflate', | |
| 'content-type': contentType | |
| }); | |
| raw.pipe(zlib.createDeflate()).pipe(response); | |
| } else if (acceptEncoding.match(/\bgzip\b/)) { | |
| response.writeHead(200, { | |
| 'Access-Control-Allow-Origin': '*', | |
| 'content-encoding': 'gzip', | |
| 'content-type': contentType | |
| }); | |
| raw.pipe(zlib.createGzip()).pipe(response); | |
| } else { | |
| response.writeHead(200, { | |
| 'Access-Control-Allow-Origin': '*', | |
| 'content-type': contentType | |
| }); | |
| raw.pipe(response); | |
| } | |
| // fs.readFile(filename, 'binary', function(err, file) { | |
| // if(err) { | |
| // response.writeHead(500, {'Content-Type': 'text/plain'}); | |
| // response.write(err + '\n'); | |
| // response.end(); | |
| // return; | |
| // } | |
| // response.writeHead(200, { 'Access-Control-Allow-Origin': '*' }); | |
| // response.write(file, 'binary'); | |
| // response.end(); | |
| // }); | |
| } | |
| /** @param {string} url */ | |
| function getMineType(url) { | |
| const ext = getExtension(url); | |
| switch(ext) { | |
| case 'html': | |
| case 'htm': | |
| return "text/html"; | |
| case 'jpeg': | |
| case 'jpg': | |
| case 'jpe': | |
| return text/plain; | |
| case 'mpeg': | |
| case 'mpg': | |
| case 'mpe': | |
| return 'video/mpeg'; | |
| case 'txt': | |
| return 'text/html'; | |
| case 'js': | |
| return 'text/javascript' | |
| case 'json': | |
| return 'application/json' | |
| default: | |
| return ''; | |
| } | |
| } | |
| /** @param {string} url */ | |
| function getExtension(url) { | |
| let p = url.indexOf("?"); | |
| if (p >= 0) | |
| url = url.substring(0, p); | |
| p = url.indexOf("#") | |
| if (p >= 0) | |
| url = url.substring(0, p); | |
| p = url.lastIndexOf("."); | |
| return p < 0 ? "" : url.substring(p+1).toLocaleLowerCase(); | |
| } | 
cors
Support http proxy
support https, and proxy
make the js file executable
Would you be willing to port this to run via nanohttpd in Java? https://github.com/NanoHttpd/nanohttpd
I would but my experience with Java isn't good enough to tackle this.
Fix few bugs related to proxy to an https server with custom port number
Added a usage example to by pass ssl certificate check.
Updated to support serving gz file and avoid security issue to access arbitrary files.
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment
  
            
change file.exits to fs.exists