restify用法翻译&整理

restify是我经常用到的node server模块,实际应用时经常因为不熟悉细节用法,需要查看官方文档,比较麻烦。

这次把官方文档扒了一遍,整理此文,侧重不太常用或不太熟悉的用法,方便查找。

restify

create

1
2
3
4
5
6
7
8
9
10
11
12
13
restify = require 'restify'
server = restify.createServer
certificate: # https
key: # https
formatters: # object 自定义res formatters for res.send()
log: # object 非必要,可以传入一个bunyan实例
name: # string ,设置一个response header中server字段,默认为restify
spdy: # object 其他选项 accepted by node-spdy
version: # string 版本
handleUpgrades: # bool hook the upgrade event from the node http server.默认false
httpsServerOptions: # object.
server.listen 8080,->
console.log '%s listening at %s',server.name,server.url

server.use(fn)

传入一个function,该function接收(req,res,next)三个参数

define routes

  • server.post
  • server.put
  • server.get
  • server.head
  • server.del

define routes parameters

  • path or {name:'foo',path:'/foo'} 当提供name后,可以由next跳转
  • function(req,res,next)
  • function(req,res,next) 可选。支持多个处理过程

Hypermedia

1
2
3
4
5
6
7
8
9
10
11
server.get {name:'city',path:'cities/:slug'},(req,res,next)->

# 在另外一个处理过程中可以交给上面的处理过程
res.send
country:'Australia'
capital:server.router.render('city',{slug:'canberra'},{details:true})
# 相当于
{
"country":'Australia'
capital:'cities/canberral?details=true'
}

versioned routes

1
2
3
server.get {path:'/foo',version:'1.1'},(req,res,next)->
server.get {path:'/foo',version:'1.2'},(req,res,next)->
server.get {path:'/foo',version:['1.1','1.2']},(req,res,next)->

请求时可在http头添加accept-version字段。

upgrade requests

  • http requests contain Connection: Upgrade
  • enable handleUpgrade when creating the server
1
2
3
4
5
6
7
8
9
10
ws = new Watershed()
server.get '/websocket/attach',(req,res,next)->
if not res.claimUpgrade
return next new Error 'Connction Must Upgrade For WebSockets.'
upgrade = res.claimUpgrade()
shed = ws.accept req,upgrade.socket,upgrade.head
shed.on 'text',(msg)->
console.log 'Recevied message from websocket client:'+msg
shed.send 'hello world.'
next false

content negotiation

默认情况下res.send()会自动选择content-type返回,也可以自定义parser
restify自身有以下几种三种formatters对应的content-types

  • application/json
  • text/plain
  • application/octet-stream
1
2
3
4
5
6
7
8
server = restify.createServer
formatters:
'application/foo':(req,res,body)->
if body instanceof Error
return body.stack
if Buffer.isBuffer body
return body.toString 'base64'
return util.inspect body

then:

1
2
res.setHeader 'content-type','application/foo'
res.send hello:'world'

如果覆盖默认的几种formatters,会改变priority优先级,可以设置一个q-value,例如:

1
2
3
restify.createServer
formatters:
'application/foo;q=0.9',(req,res,body)->

错误处理

examples:

1
2
3
4
next err # 返回500
res.send 4xx,new Error('xxx') # 返回指定http状态码
res.send {} # 返回200
next.ifError err # restify 2.1 supports

handle an error condition:

1
2
3
server.on 'InternalServerError',(req,res,err,cb)->
err._customContent = 'something...'
return cb()

常见错误列表

  • BadRequestError (400 bad request)
  • UnauthorizedError (401 Unauthorized)
  • PaymentRequiredError (402 Payment Required)
  • ForbiddenError (403 Forbidden)
  • NotFoundError (404 Not Found)
  • MethodNotAllowedError (405 Method Not Allowed)
  • NotAcceptableError (406 Not Acceptable)
  • ProxyAuthenticationRequiredError (407 Proxy Authentication Required)
  • RequestTimeoutError (408 Request Time-out)
  • ConflictError (409 Conflict)
  • GoneError (410 Gone)
  • LengthRequiredError (411 Length Required)
  • PreconditionFailedError (412 Precondition Failed)
  • RequestEntityTooLargeError (413 Request Entity Too Large)
  • RequesturiTooLargeError (414 Request-URI Too Large)
  • UnsupportedMediaTypeError (415 Unsupported Media Type)
  • RequestedRangeNotSatisfiableError (416 Requested Range Not Satisfiable)
  • ExpectationFailedError (417 Expectation Failed)
  • ImATeapotError (418 I’m a teapot)
  • UnprocessableEntityError (422 Unprocessable Entity)
  • LockedError (423 Locked)
  • FailedDependencyError (424 Failed Dependency)
  • UnorderedCollectionError (425 Unordered Collection)
  • UpgradeRequiredError (426 Upgrade Required)
  • PreconditionRequiredError (428 Precondition Required)
  • TooManyRequestsError (429 Too Many Requests)
  • RequestHeaderFieldsTooLargeError (431 Request Header Fields Too Large)
  • InternalServerError (500 Internal Server Error)
  • NotImplementedError (501 Not Implemented)
  • BadGatewayError (502 Bad Gateway)
  • ServiceUnavailableError (503 Service Unavailable)
  • GatewayTimeoutError (504 Gateway Time-out)
  • HttpVersionNotSupportedError (505 HTTP Version Not Supported)
  • VariantAlsoNegotiatesError (506 Variant Also Negotiates)
  • InsufficientStorageError (507 Insufficient Storage)
  • BandwidthLimitExceededError (509 Bandwidth Limit Exceeded)
  • NotExtendedError (510 Not Extended)
  • NetworkAuthenticationRequiredError (511 Network Authentication Required)
  • BadDigestError (400 Bad Request)
  • BadMethodError (405 Method Not Allowed)
  • InternalError (500 Internal Server Error)
  • InvalidArgumentError (409 Conflict)
  • InvalidContentError (400 Bad Request)
  • InvalidCredentialsError (401 Unauthorized)
  • InvalidHeaderError (400 Bad Request)
  • InvalidVersionError (400 Bad Request)
  • MissingParameterError (409 Conflict)
  • NotAuthorizedError (403 Forbidden)
  • RequestExpiredError (400 Bad Request)
  • RequestThrottledError (429 Too Many Requests)
  • ResourceNotFoundError (404 Not Found)
  • WrongAcceptError (406 Not Acceptable)

自定义错误

1
2
3
4
5
6
7
8
9
10
util = require 'util'
MyError = (message)->
restify.RestError.call this,{
restCode:'MyError'
statusCode:418
message:message
contructorOpt: MyError
}
this.name = 'MyError'
util.inherits MyError,restify.RestError

events

  • NotFound (req,res,cb) url dones not exist.
  • MethodNotAllowed (req,res,cb) url exist,but no route.
  • VersionNotAllowed (req,res,cb) route exist,but does not match the version.
  • UnsupportedMediaType (req,res,cb) route exist, but does not match content-type
  • after (req,res,route,error) after a route handler.when 404/405/Badversion,still be fired,but route will be null.
  • uncaughtException when some handler throws an uncaughtException.

server其他属性

  • name 名称
  • version default version to user in all routes.
  • log Object bunyan instance
  • acceptable Array(String) list of content-types then server can respond with
  • url String Once listen() is called,this will be filled in tieh where the server is running.

server其他方法

address()

wraps node’s address(),returns the bound address.

e.g.

1
{ "port": 12346, "family": "IPv4", "address": "127.0.0.1" }

listen(port,[host],[callback]) or listen(path,[callback])

wraps node’s listen()

(path,[callback]) start a local socket server listening for connections on the given path.

close()

wraps node’s close()

pre()

allows you to add in handlers then run before routing occurs.
e.g.

1
2
3
server.pre (req,res,next)->
res.headers.accept = 'application/jsn'
next()

use()

allows you to add in handlers then run no matter what the route.

Bundled Plugins

内置的多个中间件

Accept header parsing

parses out then Accept header.

authorization header parsing

parses out then Authorization header

cors handling plugin

supports tacking CORS headers into actual requests.
当添加其他头部信息时
e.g:

1
2
3
4
server.use restify.CORS
origins:['http://a.com','http://b.com'.'http://c.com:8081'] # defaults to ['*']
credentials:true # defaults to false
headers:['x-foo'] # sets expose-headers

date header parsing

parses out then HTTP Date header(if present) and checks for clock skew(default allowed clock skew is 300s,lite Kerberos).It can pass in a number,whick is interpreted in seconds,to allow for clock skew.
server.use restify.dateParser(60) 60s

jsonp support

you should set the queryParser plugin to run before this.

gzip response

query string parsing

parses the HTTP query string (i.e.,/foo?id=bar&name=mark)
if use this,the parsed content will in req.query , and params are merged into req.params.you can disable by passing in mapParams:false in the options object:
server.use restify.queryParser({mapParams:false)})

body parsing (JSON/URL-encoded/multipart form)

Blocks your chain on reading and parsing the HTTP request body.Switchs on Content-Type and does then appropriate logic.application/json,application/x-www-form-urlencoded and multipart/form-data are currently supported.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server.use restify.bodyParser
maxBodySize:0 # then maximum size in bytes allowed in the HTTP body.
mapParams:true # if req.params shoud be filled with parsed parameters form HTTP body
mapFiles:false # if req.params should be filled with the contents of files sent through a multipart request.
overrideParams:false
multipartHandler:(part)->
part.on 'data',(data)->
# do something with the multipart data
multipartFileHandler:(part)->
part.on 'data',(data)->
# do something with the multipart file data
keepExtensions:false
uploadDir: os.tmpdir()
multiples: true

static file serving

1
2
3
server.get '/',restify.serveStatic
directory:'./public'
default:'index.html'

throttling 流量限制

1
2
3
4
5
6
7
8
server.use restify.throttle
burst:100
rate:50
ip:true
overrides:
'192.168.1.1':
rate:0 # unlimited.
burset:0

conditional request handling

1
2
3
4
5
6
server.use (req,res,next)->
res.header 'ETag','myETag'
res.header 'Last-Modified',new Date()
server.use restify.conditionalRequest()
server.get '/hello/:name',(req,res,next)->
res.send 'hello'+req.params.name

audit logger

1
2
3
4
server.on 'after',restify.auditLogger
log:bunyan.createLogger
name:'audit'
stream:process.stdout

request API

header(key,[defaultValue])

req.header 'Accept','*/*'

accepts(type)

req.accepts 'html html,text/html,text/plain,application/json

is(type)

req.is 'html',req.is 'json

isSceure() 是否加密

isChunked() 是否分块

isKeepAlive() 是否kept alive

log

1
2
3
myHandler = (req,res,next)->
log = req.log
log.debug {params:req.params},'Hello there %s','foo'

getLogger(component)

shorthand to grab a new bunyan instance then is a child component of the one restify has:

1
log = req.getLogger 'MyFoo'

time()

请求到达的时候,单位ms

startHandlerTimer(handlerName) 开始一个计时器

endHandlerTimer(handlerName) 结束一个计时器

Properties

  • contentLength Number
  • contentType String
  • href String url.parse(req.url) href
  • log Object bunyan logger you can piggyback on
  • id string A unique request id (x-request-id)
  • path String cleaned up URL path

response API

header(key,value)

1
2
res.header 'Content-Length',123
res.header 'foo',new Date()

charSet(type)

Appends the provided character set to the response’s Content-Type

1
res.charSet 'utf-8'

Will change the normal json Content-Type to application/json;charset=utf-8

cache([type],[options])

Sets then cache-control header.type defaults to _public_,and options curretly only taks maxAge.

status(code)

Sets then response statusCode.e.g.res.cache()

send([status],body)

You can use send() to wrap up all the usual writeHead(),write(),end() calls on the HTTP API of node.

1
2
3
res.send hello:'world'
res.send 201,{hello:'world'}
res.send new BadRequestError 'meh'

json([status],body)

等同于以下代码:

1
2
res.contentType = 'json'
res.send {hello:'world'}

properties

  • code Number HTTP status code
  • contentLength Number short hand for the header content-length
  • contentType String short hand for the header content-type
  • headers Object response headers
  • id String A unique request id (x-request-id)

设置默认headers

You can change what headers restify sends by default by setting the top-level property defaultResponseHeaders.This should be a function that taks one argument data,whick is the already serialized response body.data can be either a String or Buffer (or null). The this object will be the response itself.

1
2
3
4
restify = require 'restify'
restify.defaultResponseHeaders = (data)->
this.header 'Server','hello world.'
restify.defaultResponseHeaders = false # disable altogether.

DTrace

Dynamic Tracing 动态跟踪

1
2
3
4
5
6
$ dtrace -l -P restify*
ID PROVIDER MODULE FUNCTION NAME
24 restify38789 mod-88f3f88 route-start route-start
25 restify38789 mod-88f3f88 handler-start handler-start
26 restify38789 mod-88f3f88 handler-done handler-done
27 restify38789 mod-88f3f88 route-done route-done

Client API

restify支持3种client

  • JsonClient sends and expects application/json
  • StringCLient sends url-encoded request and expects text/plain
  • HttpClient thin wrapper over node’s http/https libraries.

JsonClient

createJsonClient(options)

1
2
3
client = restify.createJsonClient
url:'http://a.com'
version:'*'

options:

  • accept String
  • connectTimeout Number Amount of time to wait for a socket.
  • requestTimeout Number Amount of time to wait for the request to finish
  • dtrace Object node-dtrace-provider handle
  • gzip Object will compress data when sent using content-encodeing:gzip
  • headers Object HTTP headers to set in all requests.
  • log Object bunyan instance
  • retry Object options to provide to node-retry;’false’ disables retry.defaults to 4 resties.
  • signRequest Function synchronous callback for interpossing headers before request is sent.
  • url String Fully-qualified URL to connect to.
  • userAgent String UA
  • version String semver string to set the accpet-version.

get(path,callback)

1
client.get '/foo/bar',(err,req,res,obj)->

head(path,callback)

类似get方法,但不传入obj参数

post(path,object,callback)

put(path,object,callback)

del(path,callback)

StringClient

createStringClient(options)

get(path,callback)

head(path,callback)

post(path,object,callback)

put(path,object,callback)

del(path,callback)

HttpClient

HttpClient 是restify提供的最底层的client.只是在node’s http/https modules中添加了部分语法糖。

basicAuth(username,password)

1
client.basicAuth 'mark','mysupersecretpassword'

Upgrades