dnschain
Version:
A blockchain-based DNS + HTTPS server that fixes HTTPS security, and more!
150 lines (123 loc) • 6.84 kB
text/coffeescript
###
dnschain
http://dnschain.net
Copyright (c) 2014 okTurtles Foundation
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
###
express = require 'express'
module.exports = (dnschain) ->
# expose these into our namespace
for k of dnschain.globals
eval "var #{k} = dnschain.globals.#{k};"
class HTTPServer
constructor: (@dnschain) ->
@log = gNewLogger 'HTTP'
@log.debug "Loading HTTPServer..."
@rateLimiting = gConf.get 'rateLimiting:http'
app = express()
if gConf.get('http:cors')
@log.warn "CORS enabled! Accessing DNSChain from a webpage JS relies on X.509 and therefore does not preserve the security properties of Namecoin!".bold
app.use (req, res, next) =>
res.header "Access-Control-Allow-Origin", "*"
res.header "Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"
next()
# Openname spec defined here:
# - https://github.com/okTurtles/openname-specifications/blob/resolvers/resolvers.md
# - https://github.com/openname/openname-specifications/blob/master/resolvers.md
opennameRoute = express.Router()
# Resolver specific API
opennameRoute.get /\/(?:resolver|dnschain)\/([^\/\.]+)(?:\.([a-z]+))?/, (req, res) =>
@log.debug gLineInfo("resolver API called"), {params: req.params}
[resource, format] = req.params
if resource == "fingerprint"
if !format or format is 'json'
res.json {fingerprint: @dnschain.encryptedserver.getFingerprint()}
else
@sendErr req, res, 400, "Unsupported format: #{format}"
else
@sendErr req, res, 400, "Bad resource: #{resource}"
# Datastore API
# Note: JavaScript doesn't have negative lookbehind support
# so we use the negative lookahead hacks mentioned here:
# https://stackoverflow.com/questions/977251/regular-expressions-and-negating-a-whole-character-group
opennameRoute.route(/// ^
\/(\w+) # the datastore name
\/(\w+) # the corresponding resource
(?:\/((?:(?!\.json|\.xml)[^\/])+))? # optional property (or action on resource)
(?:\/((?:(?!\.json|\.xml)[^\/])+))? # optional action on property
(?:\.(json|xml))? # optional response format
$ ///
).get (req, res) =>
@log.debug gLineInfo("get v1"), {params: req.params, queryArgs: req.query}
@callback req, res, [_.values(req.params)..., req.query]
opennameRoute.use (req, res) =>
@sendErr req, res, 400, "Bad v1 request"
app.use "/v1", opennameRoute
app.get "*", (req, res) => # Old, deprecated API usage.
path = S(url.parse(req.originalUrl).pathname).chompLeft('/').s
options = url.parse(req.originalUrl, true).query
@log.debug gLineInfo('deprecated request'), {path:path, options:options, url:req.originalUrl}
[...,datastoreName] =
if S(header = req.headers.blockchain || req.headers.host).endsWith('.dns')
S(header).chompRight('.dns').s.split('.')
else
['none']
@callback req, res, [datastoreName, "key", path, null, null, options]
app.use (err, req, res, next) =>
@log.warn gLineInfo('error handler triggered'),
errMessage: err?.message
stack: err?.stack
req: _.at(req, ['originalUrl','ip','ips','protocol','hostname','headers'])
res.status(500).send "Internal Error: #{err?.message}"
@server = http.createServer (req, res) =>
key = "http-#{req.connection?.remoteAddress}"
@log.debug gLineInfo("creating bottleneck on: #{key}")
limiter = gThrottle key, => new Bottleneck _.at(@rateLimiting, ['maxConcurrent','minTime','highWater','strategy'])...
# Since Express doesn't take a callback function
# we capture the callback that Bottleneck requires
# in `bottleCB` and call it by hooking into `res.end`
savedEnd = res.end.bind(res)
bottleCB = null
res.end = (args...) =>
savedEnd args...
bottleCB()
limiter.submit (cb) ->
bottleCB = cb
app req, res
, null
gErr("http create") unless @server
@server.on 'error', (err) -> gErr err
gFillWithRunningChecks @
start: ->
@startCheck (cb) =>
@server.listen gConf.get('http:port'), gConf.get('http:host'), =>
cb null, gConf.get 'http'
shutdown: ->
@shutdownCheck (cb) =>
@log.debug 'shutting down!'
@server.close cb
callback: (req, res, args) ->
[datastoreName, resourceName, propOrAction] = args
if not (datastore = @dnschain.chains[datastoreName])
return @sendErr req, res, 400, "Unsupported datastore: #{datastoreName}"
if not (resourceFn = datastore.resources[resourceName])
return @sendErr req, res, 400, "Unsupported resource: #{resourceName}"
resourceRequest = (cb) =>
resourceFn.call datastore, args[2..]..., cb
@dnschain.cache.resolveResource datastore, resourceRequest, JSON.stringify(args), (err,result) =>
if err
err.httpCode ?= 404
@log.debug gLineInfo('resolver failed'), {err:err.message}
@sendErr req, res, err.httpCode, "Not Found: #{propOrAction}"
else
@log.debug gLineInfo('postResolve'), {path:propOrAction, result:result}
res.json result
sendErr: (req, res, code=404, comment="Not Found") ->
if req.originalUrl != '/favicon.ico' # avoid logging these
@log.warn gLineInfo('sendErr'),
comment: comment
code: code
req: _.at(req, ['originalUrl','protocol','hostname'])
res.status(code).send comment