Created
February 2, 2012 14:57
-
-
Save ThomasBurleson/1723834 to your computer and use it in GitHub Desktop.
Node HTTP & HTTP-Proxy Server
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 | |
| /** | |
| * ****************************************************************************** | |
| * | |
| * This modified Node script creates two (2) lightweight, local web servers: | |
| * | |
| * - standard web server running on http://localhost:8000 | |
| * - proxy web server running on http://localhost:8080 | |
| * | |
| * Requirements of use: | |
| * | |
| * 1) Developers MUST specify their own custom API_HOST and API_PATH_EXPRESSION | |
| * 2) Developers should use the http://localhost:8080 contextRoot for all application | |
| * calls. | |
| * | |
| * Requests are proxied/routed based on matches to API_PATH_EXPRESSION. | |
| * All other requests are delegated to the standard web server | |
| * | |
| * | |
| * ****************************************************************************** | |
| */ | |
| var sys = require('util'), | |
| http = require('http'), | |
| httpProxy = require( 'http-proxy' ), | |
| fs = require('fs'), | |
| url = require('url'), | |
| events = require('events'); | |
| /** | |
| * Proxy Server settings | |
| * | |
| * Routes all http://localhost:8080 traffic | |
| * to either DOCUMENT_HOST or to API_HOST. Only | |
| * remote JSON data services are proxied to API_HOST | |
| * | |
| */ | |
| var DOCUMENT_PORT = 8000, | |
| DOCUMENT_HOST = 'localhost', | |
| PROXY_SERVER_PORT = 8080, | |
| API_HOST = 'xxxx.dev.company.com', | |
| API_PORT = 80, | |
| API_PATH_EXPRESSION = /^\/api\/json/; | |
| // *************************************************************** | |
| // Server util methods | |
| // *************************************************************** | |
| /** | |
| * | |
| * Start the standard HTTP server | |
| * | |
| */ | |
| function createServer_HTTP(argv) | |
| { | |
| DOCUMENT_PORT = Number(argv[2]) || DOCUMENT_PORT; | |
| new HttpServer({ | |
| 'GET': createServlet(StaticServlet), | |
| 'HEAD': createServlet(StaticServlet) | |
| }).start( DOCUMENT_PORT ); | |
| } | |
| /** | |
| * | |
| * Start the HTTP Proxy server [to client dataservices] | |
| * | |
| */ | |
| function createServer_HTTP_Proxy(argv) | |
| { | |
| function doForwardRequest( request, response, proxy ) | |
| { | |
| if ( API_PATH_EXPRESSION.test( request.url ) ) { | |
| request.headers.host = API_HOST; | |
| proxy.proxyRequest( request, response, { host: API_HOST, port: API_PORT } ); | |
| } else { | |
| request.headers.host = DOCUMENT_HOST; | |
| proxy.proxyRequest( request, response, { host: DOCUMENT_HOST, port: DOCUMENT_PORT } ); | |
| } | |
| } | |
| httpProxy.createServer( doForwardRequest ) | |
| .listen( PROXY_SERVER_PORT ); | |
| sys.puts( "Http-Proxy Server running at http://localhost:" + PROXY_SERVER_PORT + "/" ); | |
| } | |
| // *************************************************************** | |
| // Util Methods | |
| // *************************************************************** | |
| function escapeHtml(value) { | |
| return value.toString(). | |
| replace('<', '<'). | |
| replace('>', '>'). | |
| replace('"', '"'); | |
| } | |
| function createServlet(Class) { | |
| var servlet = new Class(); | |
| return servlet.handleRequest.bind(servlet); | |
| } | |
| // *************************************************************** | |
| // HttpServer Class | |
| // *************************************************************** | |
| /** | |
| * An Http server implementation that uses a map of methods to decide | |
| * action routing. | |
| * | |
| * @param {Object} Map of method => Handler function | |
| */ | |
| function HttpServer(handlers) { | |
| this.handlers = handlers; | |
| this.server = http.createServer(this.handleRequest_.bind(this)); | |
| } | |
| HttpServer.prototype.start = function(port) { | |
| this.port = port; | |
| this.server.listen(port); | |
| sys.puts('Http Server running at http://localhost:' + port + '/'); | |
| }; | |
| HttpServer.prototype.parseUrl_ = function(urlString) { | |
| var parsed = url.parse(urlString); | |
| parsed.pathname = url.resolve('/', parsed.pathname); | |
| return url.parse(url.format(parsed), true); | |
| }; | |
| HttpServer.prototype.handleRequest_ = function(req, res) { | |
| var logEntry = req.method + ' ' + req.url; | |
| if (req.headers['user-agent']) { | |
| logEntry += ' ' + req.headers['user-agent']; | |
| } | |
| sys.puts(logEntry); | |
| req.url = this.parseUrl_(req.url); | |
| var handler = this.handlers[req.method]; | |
| if (!handler) { | |
| res.writeHead(501); | |
| res.end(); | |
| } else { | |
| handler.call(this, req, res); | |
| } | |
| }; | |
| // *************************************************************** | |
| // StaticServlet Class | |
| // *************************************************************** | |
| /** | |
| * Handles static content. | |
| */ | |
| function StaticServlet() {} | |
| StaticServlet.MimeMap = { | |
| 'txt': 'text/plain', | |
| 'html': 'text/html', | |
| 'css': 'text/css', | |
| 'xml': 'application/xml', | |
| 'json': 'application/json', | |
| 'js': 'application/javascript', | |
| 'jpg': 'image/jpeg', | |
| 'jpeg': 'image/jpeg', | |
| 'gif': 'image/gif', | |
| 'png': 'image/png' | |
| }; | |
| StaticServlet.prototype.handleRequest = function(req, res) { | |
| var self = this; | |
| var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/, function(match, hex){ | |
| return String.fromCharCode(parseInt(hex, 16)); | |
| }); | |
| var parts = path.split('/'); | |
| if (parts[parts.length-1].charAt(0) === '.') | |
| return self.sendForbidden_(req, res, path); | |
| fs.stat(path, function(err, stat) { | |
| if (err) | |
| return self.sendMissing_(req, res, path); | |
| if (stat.isDirectory()) | |
| return self.sendDirectory_(req, res, path); | |
| return self.sendFile_(req, res, path); | |
| }); | |
| } | |
| StaticServlet.prototype.sendError_ = function(req, res, error) { | |
| res.writeHead(500, { | |
| 'Content-Type': 'text/html' | |
| }); | |
| res.write('<!doctype html>\n'); | |
| res.write('<title>Internal Server Error</title>\n'); | |
| res.write('<h1>Internal Server Error</h1>'); | |
| res.write('<pre>' + escapeHtml(sys.inspect(error)) + '</pre>'); | |
| sys.puts('500 Internal Server Error'); | |
| sys.puts(sys.inspect(error)); | |
| }; | |
| StaticServlet.prototype.sendMissing_ = function(req, res, path) { | |
| path = path.substring(1); | |
| res.writeHead(404, { | |
| 'Content-Type': 'text/html' | |
| }); | |
| res.write('<!doctype html>\n'); | |
| res.write('<title>404 Not Found</title>\n'); | |
| res.write('<h1>Not Found</h1>'); | |
| res.write( | |
| '<p>The requested URL ' + | |
| escapeHtml(path) + | |
| ' was not found on this server.</p>' | |
| ); | |
| res.end(); | |
| sys.puts('404 Not Found: ' + path); | |
| }; | |
| StaticServlet.prototype.sendForbidden_ = function(req, res, path) { | |
| path = path.substring(1); | |
| res.writeHead(403, { | |
| 'Content-Type': 'text/html' | |
| }); | |
| res.write('<!doctype html>\n'); | |
| res.write('<title>403 Forbidden</title>\n'); | |
| res.write('<h1>Forbidden</h1>'); | |
| res.write( | |
| '<p>You do not have permission to access ' + | |
| escapeHtml(path) + ' on this server.</p>' | |
| ); | |
| res.end(); | |
| sys.puts('403 Forbidden: ' + path); | |
| }; | |
| StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) { | |
| res.writeHead(301, { | |
| 'Content-Type': 'text/html', | |
| 'Location': redirectUrl | |
| }); | |
| res.write('<!doctype html>\n'); | |
| res.write('<title>301 Moved Permanently</title>\n'); | |
| res.write('<h1>Moved Permanently</h1>'); | |
| res.write( | |
| '<p>The document has moved <a href="' + | |
| redirectUrl + | |
| '">here</a>.</p>' | |
| ); | |
| res.end(); | |
| sys.puts('301 Moved Permanently: ' + redirectUrl); | |
| }; | |
| StaticServlet.prototype.sendFile_ = function(req, res, path) { | |
| var self = this; | |
| var file = fs.createReadStream(path); | |
| res.writeHead(200, { | |
| 'Content-Type': StaticServlet. | |
| MimeMap[path.split('.').pop()] || 'text/plain' | |
| }); | |
| if (req.method === 'HEAD') { | |
| res.end(); | |
| } else { | |
| file.on('data', res.write.bind(res)); | |
| file.on('close', function() { | |
| res.end(); | |
| }); | |
| file.on('error', function(error) { | |
| self.sendError_(req, res, error); | |
| }); | |
| } | |
| }; | |
| StaticServlet.prototype.sendDirectory_ = function(req, res, path) { | |
| var self = this; | |
| if (path.match(/[^\/]$/)) { | |
| req.url.pathname += '/'; | |
| var redirectUrl = url.format(url.parse(url.format(req.url))); | |
| return self.sendRedirect_(req, res, redirectUrl); | |
| } | |
| fs.readdir(path, function(err, files) { | |
| if (err) | |
| return self.sendError_(req, res, error); | |
| if (!files.length) | |
| return self.writeDirectoryIndex_(req, res, path, []); | |
| var remaining = files.length; | |
| files.forEach(function(fileName, index) { | |
| fs.stat(path + '/' + fileName, function(err, stat) { | |
| if (err) | |
| return self.sendError_(req, res, err); | |
| if (stat.isDirectory()) { | |
| files[index] = fileName + '/'; | |
| } | |
| if (!(--remaining)) | |
| return self.writeDirectoryIndex_(req, res, path, files); | |
| }); | |
| }); | |
| }); | |
| }; | |
| StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) { | |
| path = path.substring(1); | |
| res.writeHead(200, { | |
| 'Content-Type': 'text/html' | |
| }); | |
| if (req.method === 'HEAD') { | |
| res.end(); | |
| return; | |
| } | |
| res.write('<!doctype html>\n'); | |
| res.write('<title>' + escapeHtml(path) + '</title>\n'); | |
| res.write('<style>\n'); | |
| res.write(' ol { list-style-type: none; font-size: 1.2em; }\n'); | |
| res.write('</style>\n'); | |
| res.write('<h1>Directory: ' + escapeHtml(path) + '</h1>'); | |
| res.write('<ol>'); | |
| files.forEach(function(fileName) { | |
| if (fileName.charAt(0) !== '.') { | |
| res.write('<li><a href="' + | |
| escapeHtml(fileName) + '">' + | |
| escapeHtml(fileName) + '</a></li>'); | |
| } | |
| }); | |
| res.write('</ol>'); | |
| res.end(); | |
| }; | |
| // *************************************************************** | |
| // main() - Server startup | |
| // *************************************************************** | |
| /** | |
| * | |
| * Create and start BOTH HTTP and HTTP-PROXY servers | |
| * | |
| */ | |
| function main(argv) { | |
| createServer_HTTP( argv ); | |
| createServer_HTTP_Proxy( argv ); | |
| } | |
| // Must be last, | |
| main(process.argv); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment