4

I'm designing a JSON API, and I'd like to version the API using content negotiation of some kind. I'm currently planning on using Vendor MIME Types to do this.

While I can definitely do this at the application level, I'm thinking it would be best to make this happen at the HTTP server level. Is this possible with Apache or nginx?

The Content-Type would look something like: application/vnd.vendorname-v1+json or possibly using parameters: application/vnd.vendorname+json;v=1

Adam Lassek
  • 492
  • 1
  • 8
  • 18
  • `Content-type` is a response's header. In order to select a backend based on request, you must rely on some header from request. Which one? – Alexander Azarov Jul 14 '11 at 07:24
  • 1
    The Accept: header, which the client uses to specify which Content-Types it would like to receive when performing content negotiation. – Stephen Veiss Jul 15 '11 at 02:00

3 Answers3

6

Nginx's idiomatic approach to this kind of problems is via map. Please see my answer at StackOverflow.

Basically, you define a map in http section

map $any_variable $my_upstream {
  # Default value:
  default upstream1;

  # Exact match:
  application/vnd.vendorname+json;v=1 upstream2;

  # Regexp:
  ~^application.*vnd.vendorname-v1\+json upstream3;
}

You may mix exact matches and regexps in one map.

Then you simply use $my_upstream in you server or location section(s):

location / {
  proxy_pass http://$my_upstream$uri;
}

Nginx evaluates map variables lazily, only once (per request) and when you are using them.

Alexander Azarov
  • 3,510
  • 20
  • 19
  • Be careful when using variables with proxy_pass. From the [wiki entry](http://wiki.nginx.org/HttpProxyModule#proxy_pass): "A special case is using variables in the proxy_pass statement: The requested URL is not used and you are fully responsible to construct the target URL yourself." – kolbyjack Jul 14 '11 at 13:39
1

Sure; Apache's mod_rewrite could do this with a little bit of RewriteCond, though I'm a bit too rusty to give you an example off the top of my head. In nginx, though, it'd look something like the following (assuming you had two upstreams defined; one for your jsonapp and the other for... otherstuff):

if ($content_type = application/vnd.vendorname-v1+json) {
    proxy_pass http://jsonapp/
    break;
}
proxy_pass http://otherstuff/
womble
  • 95,029
  • 29
  • 173
  • 228
  • Would this proxy be transparent to the outsider? I want both applications to be accessible by the same url. – Adam Lassek Jul 14 '11 at 04:32
  • Yes, of course, that's the whole point of a proxy. – womble Jul 14 '11 at 10:48
  • but what, exactly, would `jsonapp` and `otherstuff` be in your example? Should I create entries in `/etc/hosts` and map those to the two different apps first? (I'm new to nginx, sorry) – Adam Lassek Jul 14 '11 at 20:39
  • They should be whatever you've defined your "upstreams" to be; see http://wiki.nginx.org/NginxHttpProxyModule#proxy_pass and http://wiki.nginx.org/HttpUpstreamModule – womble Jul 15 '11 at 06:21
0

I'm going to go against what the others have suggested.

I think it's a really bad idea to rely on versioning your JSON API from the HTTP server. The HTTP server knows nothing about the API you're developing. It's like defining the Linux version in a text file instead of building it into the kernel source. It makes upgrades more complicated.

All it needs is a mis-configuration down the road and it might all go belly up for the next guy who didn't know about the complicated setup.

Without knowing much about what you're doing there must be a way of making it obtainable using your scripting language (Are you using a scripting language or is this a custom JSON responder?). i.e. like a global variable that is available in javascript. Or returning it on request, a JSON request to get the API version. Or always sending it in all JSON responses at the front of the response. It's very little text after all.

Use the K.I.S.S. approach and you won't regret this.

hookenz
  • 14,132
  • 22
  • 86
  • 142
  • I disagree. Content negotiation should be the job of the HTTP server. This will actually greatly simplify API development. While misconfiguration could indeed cause a problem, that possibility is not limited to this use case. – Adam Lassek Jul 14 '11 at 21:30
  • More information about the API: we will be developing a JSON API for AJAX clients using Sinatra+Rack. If API versioning is done through content negotiation, then separate versions can be deployed in isolation of one another. This is a simple, elegant design following REST principles. What you describe would actually create more dependencies between versions. – Adam Lassek Jul 14 '11 at 21:33
  • I didn't say you couldn't negotiate the content. PHP does that. I'm talking about the version number! – hookenz Jul 14 '11 at 22:23
  • The version number would define what content you get, such as breaking changes to the api. That is why I think content negotiation is appropriate. – Adam Lassek Jul 14 '11 at 23:12
  • (to clarify further, when I say 'content' I mean the specific format of the JSON being returned. For instance, the resource location `/people/:id` may not change between v1 and v2, but the format of the JSON you get back may change dramatically. The 'identity' of the resource hasn't changed, but it's representation has.) – Adam Lassek Jul 14 '11 at 23:53