http_server.coffee | |
|---|---|
| Where the magic happens. Pow's | fs = require "fs"
url = require "url"
connect = require "connect"
request = require "request"
RackApplication = require "./rack_application"
{pause} = require "./util"
{dirname, join, exists} = require "path"
{version} = JSON.parse fs.readFileSync __dirname + "/../package.json", "utf8" |
|
| module.exports = class HttpServer extends connect.HTTPServer |
| Connect depends on Function.prototype.length to determine whether a given middleware is an error handler. These wrappers provide compatibility with bound instance methods. | o = (fn) -> (req, res, next) -> fn req, res, next
x = (fn) -> (err, req, res, next) -> fn err, req, res, next |
| Helper that loads the named template, creates a new context from
the given context with itself and an optional | renderTemplate = (templateName, renderContext, yieldContents) ->
template = require "./templates/http_server/#{templateName}.html"
context = {renderTemplate, yieldContents}
context[key] = value for key, value of renderContext
template context |
| Helper to render | renderResponse = (res, status, templateName, context = {}) ->
res.writeHead status, "Content-Type": "text/html; charset=utf8", "X-Pow-Template": templateName
res.end renderTemplate templateName, context |
| Create an HTTP server for the given configuration. This sets up
the middleware stack, gets a | constructor: (@configuration) ->
super [
o @logRequest
o @annotateRequest
o @handlePowRequest
o @findHostConfiguration
o @handleStaticRequest
o @findRackApplication
o @handleProxyRequest
o @handleRvmDeprecationRequest
o @handleApplicationRequest
x @handleErrorStartingApplication
o @handleFaviconRequest
o @handleApplicationNotFound
o @handleWelcomeRequest
o @handleRailsAppWithoutRackupFile
o @handleLocationNotFound
]
@staticHandlers = {}
@rackApplications = {}
@requestCount = 0
@accessLog = @configuration.getLogger "access"
@on "close", =>
for root, application of @rackApplications
application.quit() |
| Gets an object describing the server's current status that can be
passed to | toJSON: ->
pid: process.pid
version: version
requestCount: @requestCount |
| The first middleware in the stack logs each incoming request's
source address, method, hostname, and path to the access log
( | logRequest: (req, res, next) =>
@accessLog.info "[#{req.socket.remoteAddress}] #{req.method} #{req.headers.host} #{req.url}"
@requestCount++
next() |
| Annotate the request object with a | annotateRequest: (req, res, next) ->
host = req.headers.host?.replace /(\.$)|(\.?:.*)/, ""
req.pow = {host}
next() |
| Serve requests for status information at
Third-party utilities may use these endpoints to inspect a running Pow server. | handlePowRequest: (req, res, next) =>
return next() unless req.pow.host is "pow"
switch req.url
when "/config.json"
res.writeHead 200
res.end JSON.stringify @configuration
when "/env.json"
res.writeHead 200
res.end JSON.stringify @configuration.env
when "/status.json"
res.writeHead 200
res.end JSON.stringify this
else
@handleLocationNotFound req, res, next |
| After the request has been annotated, attempt to match its hostname using the server's configuration. If a host configuration is found, annotate the request object with the application's root path or the port number so we can use it further down the stack. | findHostConfiguration: (req, res, next) =>
resume = pause req
@configuration.findHostConfiguration req.pow.host, (err, domain, config) =>
if config
req.pow.root = config.root if config.root
req.pow.url = config.url if config.url
req.pow.domain = domain
req.pow.resume = resume
else
resume()
next err |
| If this is a | handleStaticRequest: (req, res, next) =>
unless req.method in ["GET", "HEAD"]
return next()
unless (root = req.pow.root) and typeof root is "string"
return next()
if req.url.match /\.\./
return next()
handler = @staticHandlers[root] ?= connect.static join(root, "public")
handler req, res, next |
| Check to see if the application root contains a | findRackApplication: (req, res, next) =>
return next() unless root = req.pow.root
exists join(root, "config.ru"), (rackConfigExists) =>
if rackConfigExists
req.pow.application = @rackApplications[root] ?=
new RackApplication @configuration, root, req.pow.host |
| If | else if application = @rackApplications[root]
delete @rackApplications[root]
application.quit()
next() |
| If the request object is annotated with a url, proxy the request off to the hostname and port. | handleProxyRequest: (req, res, next) =>
return next() unless req.pow.url
{hostname, port} = url.parse req.pow.url
headers = {}
for key, value of req.headers
headers[key] = value
headers['X-Forwarded-For'] = req.connection.address().address
headers['X-Forwarded-Host'] = req.pow.host
headers['X-Forwarded-Server'] = req.pow.host
proxy = request
method: req.method
url: "#{req.pow.url}#{req.url}"
headers: headers
jar: false
followRedirect: false
req.pipe proxy
proxy.pipe res
proxy.on 'error', (err) ->
renderResponse res, 500, "proxy_error",
{err, hostname, port}
req.pow.resume() |
| Handle requests for the mini-app that serves RVM deprecation
notices. Manually requesting | handleRvmDeprecationRequest: (req, res, next) =>
return next() unless application = req.pow.application
if match = req.url.match /^\/__pow__\/rvm_deprecation(.*)/
action = match[1]
return next() unless action is "" or req.method is "POST"
switch action
when ""
true
when "/add_to_powrc"
application.writeRvmBoilerplate()
when "/enable"
@configuration.enableRvmDeprecationNotices()
when "/disable"
@configuration.disableRvmDeprecationNotices()
else
return next()
renderResponse res, 200, "rvm_deprecation_notice",
boilerplate: RackApplication.rvmBoilerplate
else
next() |
| If the request object is annotated with an application, pass the
request off to the application's | handleApplicationRequest: (req, res, next) ->
if application = req.pow.application
application.handle req, res, next, req.pow.resume
else
next() |
| Serve an empty 200 response for any | handleFaviconRequest: (req, res, next) ->
return next() unless req.url is "/favicon.ico"
res.writeHead 200
res.end() |
| Show a friendly message when accessing a hostname that hasn't been set up with Pow yet (but only for hosts that the server is configured to handle). | handleApplicationNotFound: (req, res, next) =>
return next() if req.pow.root
host = req.pow.host
pattern = @configuration.httpDomainPattern
return next() unless domain = host?.match(pattern)?[1]
name = host.slice 0, host.length - domain.length
return next() unless name.length
renderResponse res, 503, "application_not_found", {name, host} |
| If the request is for | handleWelcomeRequest: (req, res, next) =>
return next() if req.pow.root or req.url isnt "/"
{domains} = @configuration
domain = if "dev" in domains then "dev" else domains[0]
renderResponse res, 200, "welcome", {version, domain} |
| If the request is for an app that looks like a Rails 2 app but
doesn't have a | handleRailsAppWithoutRackupFile: (req, res, next) ->
return next() unless root = req.pow.root
exists join(root, "config/environment.rb"), (looksLikeRailsApp) ->
return next() unless looksLikeRailsApp
renderResponse res, 503, "rackup_file_missing" |
| If the request ends up here, it's for a static site, but the requested file doesn't exist. Show a basic 404 message. | handleLocationNotFound: (req, res, next) ->
res.writeHead 404, "Content-Type": "text/html"
res.end "<!doctype html><html><body><h1>404 Not Found</h1>" |
| If there's an exception thrown while handling a request, show a nicely formatted error page along with the full backtrace. | handleErrorStartingApplication: (err, req, res, next) ->
return next() unless root = req.pow.root |
| Replace | home = process.env.HOME
stackLines = for line in err.stack.split "\n"
if line.slice(0, home.length) is home
"~" + line.slice home.length
else
line |
| Split the backtrace lines into the first five lines and all remaining lines, if there are more than 10 lines total. | if stackLines.length > 10
stack = stackLines.slice 0, 5
rest = stackLines.slice 5
else
stack = stackLines
renderResponse res, 500, "error_starting_application",
{err, root, stack, rest}
|