Search code examples
node.jshttprestify

Handling Accept headers in node.js restify


I am trying to properly handle Accept headers in RESTful API in node.js/restify by using WrongAcceptError as follows.

var restify = require('restify')
  ; server = restify.createServer()

// Write some content as JSON together with appropriate HTTP headers. 
function respond(status,response,contentType,content)
{ var json = JSON.stringify(content)
; response.writeHead(status,
  { 'Content-Type': contentType
  , 'Content-Encoding': 'UTF-8'
  , 'Content-Length': Buffer.byteLength(json,'utf-8')
  })
; response.write(json)
; response.end()
}

server.get('/api',function(request,response,next)
{ var contentType = "application/vnd.me.org.api+json"
; var properContentType = request.accepts(contentType)
; if (properContentType!=contentType)
  { return next(new restify.WrongAcceptError("Only provides "+contentType)) }
  respond(200,response,contentType,
  { "uri": "http://me.org/api"
  , "users": "/users"
  , "teams": "/teams"
  })
  ; return next()
});

server.listen(8080, function(){});

which works fine if the client provides the right Accept header, or no header as seen here:

$ curl -is http://localhost:8080/api
HTTP/1.1 200 OK
Content-Type: application/vnd.me.org.api+json
Content-Encoding: UTF-8
Content-Length: 61
Date: Tue, 02 Apr 2013 10:19:45 GMT
Connection: keep-alive

{"uri":"http://me.org/api","users":"/users","teams":"/teams"}

The problem is that if the client do indeed provide a wrong Accept header, the server will not send the error message:

$ curl -is http://localhost:8080/api -H 'Accept: application/vnd.me.org.users+json'
HTTP/1.1 500 Internal Server Error
Date: Tue, 02 Apr 2013 10:27:23 GMT
Connection: keep-alive
Transfer-Encoding: chunked

because the client is not assumed to understand the error message, which is in JSON, as seen by this:

$ curl -is http://localhost:8080/api -H 'Accept: application/json'
HTTP/1.1 406 Not Acceptable
Content-Type: application/json
Content-Length: 80
Date: Tue, 02 Apr 2013 10:30:28 GMT
Connection: keep-alive

{"code":"WrongAccept","message":"Only provides application/vnd.me.org.api+json"}

My question is therefore, how do I force restify to send back the right error status code and body, or am I doing things wrong?


Solution

  • The problem is actually that you're returning a JSON object with a content-type (application/vnd.me.org.api+json) that Restify doesn't know (and therefore, creates an error no formatter found).

    You need to tell Restify how your responses should be formatted:

    server = restify.createServer({
      formatters : {
        '*/*' : function(req, res, body) { // 'catch-all' formatter
          if (body instanceof Error) { // see text
            body = JSON.stringify({
              code    : body.body.code,
              message : body.body.message
            });
          };
          return body;
        }
      }
    });
    

    The body instanceof Error is also required, because it has to be converted to JSON before it can be sent back to the client.

    The */* construction creates a 'catch-all' formatter, which is used for all mime-types that Restify can't handle itself (that list is application/javascript, application/json, text/plain and application/octet-stream). I can imagine that for certain cases the catch-all formatter could pose issues, but that depends on your exact setup.