UNPKG

scoped-http-client

Version:
206 lines (170 loc) 5.47 kB
path = require 'path' http = require 'http' https = require 'https' url = require 'url' qs = require 'querystring' class ScopedClient # Those properties are in @options but they are either not passed to the # request as options or some processing is made on them. They will not be # added to the request's option param. @nonPassThroughOptions = ['headers', 'hostname', 'encoding', 'auth', 'port', 'protocol', 'agent', 'query', 'host', 'path', 'pathname', 'slashes', 'hash'] constructor: (url, options) -> @options = @buildOptions url, options @passthroughOptions = reduce(extend({}, @options), ScopedClient.nonPassThroughOptions) request: (method, reqBody, callback) -> if typeof(reqBody) == 'function' callback = reqBody reqBody = null try headers = extend {}, @options.headers sendingData = reqBody and reqBody.length > 0 headers.Host = @options.hostname # If `callback` is `undefined` it means the caller isn't going to stream # the body of the request using `callback` and we can set the # content-length header ourselves. # # There is no way to conveniently assert in an else clause because the # transfer encoding could be chunked or using a newer framing mechanism. if callback is undefined headers['Content-Length'] = if sendingData then Buffer.byteLength(reqBody, @options.encoding) else 0 if @options.auth headers['Authorization'] = 'Basic ' + new Buffer(@options.auth).toString('base64'); port = @options.port || ScopedClient.defaultPort[@options.protocol] || 80 requestOptions = { port: port host: @options.hostname method: method path: @fullPath() headers: headers agent: @options.agent } # Extends the previous request options with all remaining options extend requestOptions, @passthroughOptions req = (if @options.protocol == 'https:' then https else http).request(requestOptions) if @options.timeout req.setTimeout @options.timeout, () -> req.abort() if callback req.on 'error', callback req.write reqBody, @options.encoding if sendingData callback null, req if callback catch err callback err, req if callback (callback) => if callback req.on 'response', (res) => res.setEncoding @options.encoding body = '' res.on 'data', (chunk) -> body += chunk res.on 'end', -> callback null, res, body req.on 'error', (error) -> callback error, null, null req.end() @ # Adds the query string to the path. fullPath: (p) -> search = qs.stringify @options.query full = this.join p full += "?#{search}" if search.length > 0 full scope: (url, options, callback) -> override = @buildOptions url, options scoped = new ScopedClient(@options) .protocol(override.protocol) .host(override.hostname) .path(override.pathname) if typeof(url) == 'function' callback = url else if typeof(options) == 'function' callback = options callback scoped if callback scoped join: (suffix) -> p = @options.pathname || '/' if suffix and suffix.length > 0 if suffix.match /^\// suffix else path.join p, suffix else p path: (p) -> @options.pathname = @join p @ query: (key, value) -> @options.query ||= {} if typeof(key) == 'string' if value @options.query[key] = value else delete @options.query[key] else extend @options.query, key @ host: (h) -> @options.hostname = h if h and h.length > 0 @ port: (p) -> if p and (typeof(p) == 'number' || p.length > 0) @options.port = p @ protocol: (p) -> @options.protocol = p if p && p.length > 0 @ encoding: (e = 'utf-8') -> @options.encoding = e @ timeout: (time) -> @options.timeout = time @ auth: (user, pass) -> if !user @options.auth = null else if !pass and user.match(/:/) @options.auth = user else @options.auth = "#{user}:#{pass}" @ header: (name, value) -> @options.headers[name] = value @ headers: (h) -> extend @options.headers, h @ buildOptions: -> options = {} i = 0 while arguments[i] ty = typeof arguments[i] if ty == 'string' extend options, url.parse(arguments[i], true) delete options.url delete options.href delete options.search else if ty != 'function' extend options, arguments[i] i += 1 options.headers ||= {} options.encoding ?= 'utf-8' options ScopedClient.methods = ["GET", "POST", "PATCH", "PUT", "DELETE", "HEAD"] ScopedClient.methods.forEach (method) -> ScopedClient.prototype[method.toLowerCase()] = (body, callback) -> @request method, body, callback ScopedClient.prototype.del = ScopedClient.prototype['delete'] ScopedClient.defaultPort = {'http:':80, 'https:':443, http:80, https:443} extend = (a, b) -> Object.keys(b).forEach (prop) -> a[prop] = b[prop] a # Removes keys specified in second parameter from first parameter reduce = (a, b) -> for propName in b delete a[propName] a exports.create = (url, options) -> new ScopedClient url, options