UNPKG

arcane-middleware

Version:

Middleware for Arcane framework.

692 lines (576 loc) 17.4 kB
#!package export MiddlewareHandler #!import http-proxy #!import fs #!import path #!import url #!import http #!import net #!import fresh #!import parseurl #!import proxy-addr #!import type-is #!import accepts #!import range-parser #!import vary #!import content-type #!import etag #!import mime #!import cookie #!import cookie-signature #!import utils-merge class MiddlewareFlow constructor: (req, res, app) -> req.__proto__ = MiddlewareHandler.req_serv res.__proto__ = MiddlewareHandler.res_serv res.req = req req.res = res @res = res @req = req @loaded = { $req: req $res: res $app: @ } for i in app.Store.stack if i[0]? # console.log i[0] @loaded["$#{i[0]}"] = i[1].apply i[2], [req, res, @] #new_p else # console.log i[1] i[1].apply i[2], [req, res, @] break if res.finished break if res._header use: (handler, result) -> if typeof handler is 'function' param_list = MiddlewareHandler.parm.get handler new_p = [] for i in param_list new_p.push @loaded[i] ? null ret = null err = null try ret = handler.apply null, new_p catch err if typeof err in ['string', 'number'] throw err else console.log err.stack ? err result(err, ret) if typeof result is 'function' return ret class MiddlewareHandler charsetRegExp = /;\s*charset\s*=/ settings: {} # modules_list: {} @parm: require('./params').ParameterHandler @res_serv = __proto__: http.ServerResponse.prototype @req_serv = __proto__: http.IncomingMessage.prototype # push_params: (handler) -> # ret = [] # param_list = MiddlewareHandler.parm.get handler # # for i in param_list # name = i.replace ///^\$///g, '' # ret.push name # # if not @Store.flow[name]? and @modules_list?[name]? # @Store.flow[name] = true # @Store.flow[name] = @push_params @modules_list[name].__middle # @Store.stack.push [name, @modules_list[name].__middle, @modules_list[name]] # # return ret flow: MiddlewareFlow constructor: (@name) -> @Store = stack: [] # flow: {} # settings: {} # @loaded = {} # flow: (req, res) -> # req.__proto__ = MiddlewareHandler.req_serv # res.__proto__ = MiddlewareHandler.res_serv # # res.req = req # req.res = res # # self = this # self.loaded = { # $req: req # $res: res # $app: @ # } # # # console.log req.url, self.Store.stack # # # console.log 'ORIGINAL URL', req.url # # console.log @Store # # for i in self.Store.stack # # new_p = [] # # for j in Store.flow[i[0]] # # new_p.push self.loaded["$#{j}"] ? null # # if i[0]? # # console.log i[0] # self.loaded["$#{i[0]}"] = i[1].apply i[2], [req, res, self] #new_p # else # # console.log i[1] # i[1].apply i[2], [req, res, self] # # break if res.finished use: (handler, result) -> if typeof handler is 'function' param_list = MiddlewareHandler.parm.get handler new_p = [] for i in param_list new_p.push @loaded[i] ? null ret = null err = null try ret = handler.apply null, new_p catch err if typeof err in ['string', 'number'] throw err else console.log err.stack ? err result(err, ret) if typeof result is 'function' return ret get: (name) -> set: (name, value) -> useMod: (name, handler) -> handle = handler if handler.constructor.name is 'Function' or name.constructor.name is 'Object' handle = name if name.constructor.name is 'Function' or name.constructor.name is 'Object' n = name if name.constructor.name is 'String' if handle instanceof MiddlewareHandler and typeof handle.middleware is 'function' # @Store.flow[handle.name] = true # @Store.flow[handle.name] = @push_params handle.middleware @Store.stack.push [handle.name, handle.middleware, handle] else if handle and n # @Store.flow[n] = true # @Store.flow[n] = @push_params handle @Store.stack.push [n, handle, null] else if handle @Store.stack.push [null, handle, null] @get: (name) -> @set: (name, value) -> ###---########################################################################################################################## # # ################################################################################################################################ __etag = (body, encoding) -> buf = if !Buffer.isBuffer(body) then new Buffer(body, encoding) else body etag buf, weak: false __wetag = (body, encoding) -> buf = if !Buffer.isBuffer(body) then new Buffer(body, encoding) else body etag buf, weak: true setCharset = (type, charset) -> if !type or !charset return type # parse type parsed = contentType.parse(type) # set charset parsed.parameters.charset = charset # format type contentType.format parsed acceptParams = (str, index) -> parts = str.split(RegExp(' *; *')) ret = value: parts[0] quality: 1 params: {} originalIndex: index i = 1 while i < parts.length pms = parts[i].split(RegExp(' *= *')) if 'q' == pms[0] ret.quality = parseFloat(pms[1]) else ret.params[pms[0]] = pms[1] ++i ret normalizeType = (type) -> if ~type.indexOf('/') then acceptParams(type) else value: mime.lookup(type) params: {} normalizeTypes = (types) -> ret = [] i = 0 while i < types.length ret.push normalizeType(types[i]) ++i ret escapeHtml = (html) -> String(html).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace />/g, '&gt;' # http.IncomingMessage.prototype.testing123 = -> # console.log 'heloloolooloolo' MiddlewareHandler.res_serv.moveFileUpload = (name, to, cb) -> if $req.files and $req.files[name] try fs.unlinkSync $req.root + to catch err throw err source = fs.createReadStream $req.files[name].path dest = fs.createWriteStream $req.root + to source.pipe dest source.on 'end', () -> if cb and typeof cb is 'function' then cb null, true source.on 'error', (err) -> if cb and typeof cb is 'function' then cb err, null MiddlewareHandler.res_serv.download = (_mime, filename, file) -> @set 'Content-disposition', "attachment; filename=#{encodeURIComponent filename}" @set 'Content-type', _mime @updateHeaders 200 filestream = fs.createReadStream file filestream.pipe @ throw 'stream' MiddlewareHandler.res_serv.sendStatus = (statusCode) -> body = http.STATUS_CODES[statusCode] or String(statusCode) @statusCode = statusCode @type 'txt' @send body MiddlewareHandler.res_serv.status = (code) -> @statusCode = code return @ MiddlewareHandler.res_serv.get = (field) -> @getHeader field MiddlewareHandler.res_serv.set = MiddlewareHandler.res_serv.header = (field, val) -> throw 'done' if @finished if arguments.length == 2 value = if Array.isArray(val) then val.map(String) else String(val) # add charset to content-type if field.toLowerCase() == 'content-type' and !charsetRegExp.test(value) charset = mime.charsets.lookup(value.split(';')[0]) if charset value += '; charset=' + charset.toLowerCase() @setHeader field, value else for key of field @set key, field[key] this MiddlewareHandler.res_serv.location = (url) -> loc = url # "back" is an alias for the referrer if url == 'back' loc = @req.get('Referrer') or '/' # set location @set 'Location', loc throw 'location' this MiddlewareHandler.res_serv.redirect = (url) -> address = url body = undefined status = 302 # allow status / url if arguments.length == 2 if typeof arguments[0] == 'number' status = arguments[0] address = arguments[1] else console.log 'res.redirect(url, status): Use res.redirect(status, url) instead' status = arguments[1] # Set location header @location address address = @get('Location') # Support text/{plain,html} by default @format text: -> body = http.STATUS_CODES[status] + '. Redirecting to ' + encodeURI(address) return html: -> u = escapeHtml(address) body = '<p>' + http.STATUS_CODES[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>' return default: -> body = '' return # Respond # body = statusCodes[status] + '. Redirecting to ' + encodeURI(address) ################################### @statusCode = status @set 'Content-Length', Buffer.byteLength(body) if @req.method == 'HEAD' @end() else @end body throw 'redirect' return MiddlewareHandler.res_serv.send = (body) -> # console.log (new Error).stack chunk = body encoding = undefined len = undefined req = @req type = undefined # settings app = @app # allow status / body if arguments.length == 2 # res.send(body, status) backwards compat if typeof arguments[0] != 'number' and typeof arguments[1] == 'number' # console.log 'res.send(body, status): Use res.status(status).send(body) instead' @statusCode = arguments[1] else # console.log 'res.send(status, body): Use res.status(status).send(body) instead' @statusCode = arguments[0] chunk = arguments[1] # disambiguate res.send(status) and res.send(status, num) if typeof chunk == 'number' and arguments.length == 1 # res.send(status) will set status message as text string if !@get('Content-Type') @type 'txt' # console.log 'res.send(status): Use res.sendStatus(status) instead' @statusCode = chunk chunk = http.STATUS_CODES[chunk] switch typeof chunk # string defaulting to html when 'string' if !@get('Content-Type') @type 'html' when 'boolean', 'number', 'object' if chunk == null chunk = '' else if Buffer.isBuffer(chunk) if !@get('Content-Type') @type 'bin' else return @json(chunk) # write strings in utf-8 if typeof chunk == 'string' encoding = 'utf8' type = @get('Content-Type') # reflect this in content-type if typeof type == 'string' @set 'Content-Type', setCharset(type, 'utf-8') # populate Content-Length if chunk != undefined if !Buffer.isBuffer(chunk) # convert chunk to Buffer; saves later double conversions chunk = new Buffer(chunk, encoding) encoding = undefined len = chunk.length @set 'Content-Length', len # populate ETag # etag = undefined generateETag = len != undefined and __etag # app.get('etag fn') if typeof generateETag == 'function' and !@get('ETag') if _etag = generateETag(chunk, encoding) @set 'ETag', _etag # freshness if req.fresh @statusCode = 304 # strip irrelevant headers if 204 == @statusCode or 304 == @statusCode @removeHeader 'Content-Type' @removeHeader 'Content-Length' @removeHeader 'Transfer-Encoding' chunk = '' if req.method == 'HEAD' # skip body for HEAD @end() else # respond @end chunk, encoding throw 'done' this MiddlewareHandler.res_serv.contentType = MiddlewareHandler.res_serv.type = (type) -> ct = if type.indexOf('/') == -1 then mime.lookup(type) else type @set 'Content-Type', ct MiddlewareHandler.res_serv.format = (obj) -> req = @req # next = req.next fn = obj.default if fn delete obj.default keys = Object.keys(obj) key = if keys.length > 0 then req.accepts(keys) else false @vary 'Accept' if key @set 'Content-Type', normalizeType(key).value obj[key] req, this #, next else if fn fn() else throw new Error 'Not Acceptable' this MiddlewareHandler.res_serv.vary = (field) -> # checks for back-compat if !field or Array.isArray(field) and !field.length # deprecate 'res.vary(): Provide a field name' return this vary this, field this MiddlewareHandler.res_serv.json = (obj) -> val = obj # allow status / body if arguments.length == 2 # res.json(body, status) backwards compat if typeof arguments[1] == 'number' # deprecate 'res.json(obj, status): Use res.status(status).json(obj) instead' @statusCode = arguments[1] else # deprecate 'res.json(status, obj): Use res.status(status).json(obj) instead' @statusCode = arguments[0] val = arguments[1] body = JSON.stringify(val, null, 4) if !@get('Content-Type') @set 'Content-Type', 'application/json' @send body MiddlewareHandler.res_serv.cookie = (name, value, options) -> opts = utilsMerge({}, options) secret = @req.secret signed = opts.signed if signed and !secret throw new Error('cookieParser("secret") required for signed cookies') val = if typeof value == 'object' then 'j:' + JSON.stringify(value) else String(value) if signed val = 's:' + cookieSignature.sign(val, secret) if 'maxAge' of opts opts.expires = new Date(Date.now() + opts.maxAge) opts.maxAge /= 1000 if opts.path == null opts.path = '/' @append 'Set-Cookie', cookie.serialize(name, String(val), opts) this MiddlewareHandler.res_serv.append = (field, val) -> prev = @get(field) value = val if prev # concat the new and prev vals value = if Array.isArray(prev) then prev.concat(val) else if Array.isArray(val) then [ prev ].concat(val) else [ prev val ] @set field, value MiddlewareHandler.res_serv.clearCookie = (name, options) -> opts = utilsMerge({ expires: new Date(1) path: '/' }, options) @cookie name, '', opts ###* # Helper function for creating a getter on an object. # # @param {Object} obj # @param {String} name # @param {Function} getter # @private ### defineGetter = (obj, name, getter) -> Object.defineProperty obj, name, configurable: true enumerable: true get: getter return MiddlewareHandler.req_serv.is = (types) -> arr = types # support flattened arguments if !Array.isArray(types) arr = new Array(arguments.length) i = 0 while i < arr.length arr[i] = arguments[i] i++ typeIs this, arr MiddlewareHandler.req_serv.accepts = -> accept = accepts(this) accept.types.apply accept, arguments MiddlewareHandler.req_serv.acceptsEncodings = -> accept = accepts(this) accept.encodings.apply accept, arguments MiddlewareHandler.req_serv.acceptsLanguages = -> accept = accepts(this) accept.languages.apply accept, arguments MiddlewareHandler.req_serv.get = MiddlewareHandler.req_serv.header = (name) -> lc = name.toLowerCase() switch lc when 'referer', 'referrer' return @headers.referrer or @headers.referer else return @headers[lc] return MiddlewareHandler.req_serv.range = (size) -> range = @get('Range') if !range return rangeParser size, range ###* # Compile "proxy trust" value to function. # # @param {Boolean|String|Number|Array|Function} val # @return {Function} # @api private ### compileTrust = (val) -> if typeof val == 'function' return val if val == true # Support plain true/false return -> true if typeof val == 'number' # Support trusting hop count return (a, i) -> i < val if typeof val == 'string' # Support comma-separated values val = val.split(RegExp(' *, *')) proxyAddr.compile val or [] defineGetter MiddlewareHandler.req_serv, 'protocol', -> proto = if @connection.encrypted then 'https' else 'http' # compileTrust(false) default value 'false' posible value ['192.168.1.0', '192.168.1.255'] if !compileTrust(false)(@connection.remoteAddress, 0) return proto # Note: X-Forwarded-Proto is normally only ever a # single value, but this is to be safe. proto = @get('X-Forwarded-Proto') or proto proto.split(/\s*,\s*/)[0] defineGetter MiddlewareHandler.req_serv, 'secure', -> @protocol is 'https' defineGetter MiddlewareHandler.req_serv, 'ip', -> # compileTrust(false) default value 'false' posible value ['192.168.1.0', '192.168.1.255'] proxyAddr this, compileTrust(false) defineGetter MiddlewareHandler.req_serv, 'ips', -> # compileTrust(false) default value 'false' posible value ['192.168.1.0', '192.168.1.255'] addrs = proxyAddr.all(this, compileTrust(false)) addrs.slice(1).reverse() defineGetter MiddlewareHandler.req_serv, 'subdomains', -> hostname = @hostname if !hostname return [] subdomains = if !net.isIP(hostname) then hostname.split('.').reverse() else [ hostname ] # offset to get the sub domain, 2 default subdomains.slice 2 defineGetter MiddlewareHandler.req_serv, 'hostname', -> host = @get('X-Forwarded-Host') # compileTrust(false) default value 'false' posible value ['192.168.1.0', '192.168.1.255'] if !host or !compileTrust(false)(@connection.remoteAddress, 0) host = @get('Host') if !host return # IPv6 literal support offset = if host[0] == '[' then host.indexOf(']') + 1 else 0 index = host.indexOf(':', offset) if index != -1 then host.substring(0, index) else host defineGetter MiddlewareHandler.req_serv, 'path', -> parseurl(this).pathname defineGetter MiddlewareHandler.req_serv, 'fresh', -> method = @method s = @res.statusCode # GET or HEAD for weak freshness validation only if 'GET' != method and 'HEAD' != method return false # 2xx or 304 as per rfc2616 14.26 if s >= 200 and s < 300 or 304 == s return fresh(@headers, @res._headers or {}) false defineGetter MiddlewareHandler.req_serv, 'stale', -> !@fresh defineGetter MiddlewareHandler.req_serv, 'xhr', -> val = @get('X-Requested-With') or '' val.toLowerCase() == 'xmlhttprequest' defineGetter MiddlewareHandler.req_serv, 'base_url', -> @baseUrl ? ''