hem
Version:
stitches CommonJS, and ties up other lose ends of web-app development.
154 lines (135 loc) • 5.33 kB
text/coffeescript
connect = require('connect')
mime = require('connect').static.mime
httpProxy = require('http-proxy')
http = require('http')
fs = require('fs')
utils = require('./utils')
log = require('./log')
# ------- Public Functions
server = {}
server.start = (hem) ->
# create app to configure
app = connect()
app.use(server.middleware(hem))
# start server
options = hem.options.hem
if options.host is "*"
http.createServer(app).listen(options.port)
else
http.createServer(app).listen(options.port, options.host)
return app
server.middleware = (hem) ->
backend = connect()
options = hem.options.hem
statics = []
# create array of static routes
for route, value of options?.static
statics.push
url : route
path : value
# determine if there is any dynamic or static routes to add
for app in hem.apps
# if verbose then print our mappings and apply the baseAppRoute if present
log.info "> Apply route mappings for application: <green>#{app.name}</green>"
for pkg in app.packages
if options.baseAppRoute
pkg.route = utils.cleanRoute(options.baseAppRoute, pkg.route)
log.info " - Mapping route <yellow>#{pkg.route}</yellow> to <yellow>#{pkg.target}</yellow>"
# loop over potential static routes and add them to main route array
for route in app.static
if options.baseAppRoute
route.url = utils.cleanRoute(options.baseAppRoute, route.url)
log.info " - Mapping static <yellow>#{route.url}</yellow> to <yellow>#{route.path}</yellow>"
statics.push(route)
# setup separate connect app for static routes and proxy middleware
for route in statics
# make sure path exists
unless fs.existsSync(route.path)
log.errorAndExit "The resource <yellow>#{route.path}</yellow> not found for static mapping <yellow>#{route.url}</yellow>"
# test if file is directory or file....
if fs.lstatSync(route.path).isDirectory()
backend.use(route.url, checkForRedirect())
backend.use(route.url, connect.static(route.path) )
else
backend.use route.url, do (route) ->
(req, res) ->
fs.readFile route.path, (err, data) ->
if err
res.writeHead(404)
res.end(JSON.stringify(err))
else
res.writeHead(200)
res.end(data)
# setup proxy route
for route, value of options.proxy
display = "#{value.host}:#{value.port or 80}#{value.path}"
log.info "> Proxy requests <yellow>#{route}</yellow> to <yellow>#{display}</yellow>"
backend.use(route, createRoutingProxy(value, "#{options.host}:#{options.port}"))
# return the custom middleware for connect to use
return (req, res, next) ->
# get url path
url = require("url").parse(req.url)?.pathname.toLowerCase() or ""
# loop over applications and call compile when there is a match
if url.match(/(\.js|\.css)$/)
for app in hem.apps
if pkg = app.isMatchingRoute(url)
str = pkg.build()
res.charset = 'utf-8'
res.setHeader('Content-Type', mime.lookup(pkg.target))
res.setHeader('Content-Length', Buffer.byteLength(str))
res.end((req.method is 'HEAD' and null) or str)
return
# pass request to static connect app to handle static/proxy requests
backend.handle(req, res, next)
# ------- Private Functions
checkForRedirect = () ->
return (req, res, next) ->
pathname = require("url").parse(req.originalUrl).pathname
if (req.url is "/" and not utils.endsWith(pathname,"/"))
pathname += '/'
res.statusCode = 301
res.setHeader('Location', pathname)
res.end('Redirecting to ' + pathname)
else
next()
createRoutingProxy = (options, returnHost) ->
# handle https differently
if options.https
routingProxyOptions = {target: {https:true}}
defaultPort = 443
else
routingProxyOptions = {}
defaultPort = 80
# create proxy
proxy = new httpProxy.RoutingProxy(routingProxyOptions)
# set options
options.path or= ""
options.port or= defaultPort
options.patchRedirect or= true
# handle redirects
if options.patchRedirect
proxy.once "start", (req, res) ->
# get the requesting hostname and port
patchServerResponseForRedirects(options.host, returnHost)
# return function used by connect to access proxy
return (req, res, next) ->
req.url = "#{options.path}#{req.url}"
req.headers.host = options.host
proxy.proxyRequest(req, res, options)
patchServerResponseForRedirects = (fromHost, returnHost) ->
writeHead = http.ServerResponse.prototype.writeHead
http.ServerResponse.prototype.writeHead = (status) ->
headers = @_headers or {}
if status in [301,302]
oldLocation = new RegExp("s?:\/\/#{fromHost}:?[0-9]*")
newLocation = "://#{returnHost}"
headers.location = headers.location.replace(oldLocation,newLocation)
if 'set-cookie' of headers
newSetCookie = []
for cookie in headers['set-cookie']
# remove the secure setting, so we can get cookie setting to work
newSetCookie.push cookie.replace /; Secure; HttpOnly/, ''
headers['set-cookie'] = newSetCookie
return writeHead.apply(@, arguments)
# export the public functions
module.exports = server