Content-Negotiation

A few years back I had a great live-experience regarding failed Content-Negotiation. I was sitting in Montreal in a restaurant waiting for the waiter. Once they came they asked me something in french and I immediately told them that I don’t understand french and whether we could speak english. We could. So I got a menu (in english and french) and a few minutes later they came back to take my order. And they talked to me – again – in french. So we had to go through the whole dance again.

What does that have to do with Content-Negotiation? Well: We did negotiate that we would communicate in english. Comming back to me with some language that we already had established I can’t understand doesn’t make sense. And I might just not understand what they want.

HTTP has a similar process, described and defined in RFC 7231. There are in general 2 possible options: A proactive and a reactive approach. The main thing in both is that the client can provide a preference and that then

applies to any content in the response, including representations of the target resource, representations of error or processing status, and potentially even the miscellaneous text strings that might appear within the protocol.

So regardless of what happens in between, when the client sends a content-negotiation header like Accept: text/html then the server responds with data in HTML format. Whether that is the requested resource or an error-message explaining what went wrong or something else.

This is in my opinion important to understand! When the client sends an Accept header all responses have to be in the requested format!

The server can not send JSON back just because it ran into an error. That’s like my waiter talking to me in french again because they ran into a problem with the order. I don’t understand what they want. I might not even realize that they ran into an error.

Content Negotiation usually uses one or multiple of Accept, Accept-Encoding, Accept-Charset and/or Accept-Language.

And the Server then responds with Content-Type, Language, Charset.

To make things easier for the server one can define ranges, like “I accept German, but I am also able to understand swiss-german and in the worst case English”. That would look like this

The same is possible with mime-types:

I prefer HTML, if that’s not possible, send me XML, if that’s also not possible, send me JSON. If that is also not possible please anything that is text before sending me whatever you got.

In that case the server has a choice to send something they understand.

But when the client sends Accept: application/json then the only thing the server can return is… JSON. The client doesn’t understand anything else.

And according to the link from the beginning even an error would need to be in JSON format. Because that is what the client understands. Nothing else.

Does that raise a concern?

Hard Coded Responses

Exactly! In a lot of applications we return hard-coded some hard-coded formatted response.

🤯

We break the Content-Negotiation for mime-types.

One of the – in my eyes – biggest drawbacks of PSR-7 is actually that we are returning a response. And that response is already formatted ready to be sent to the client.

Instead of leaving the actual formatting to a – for me as a programmer – transparent process that uses the result of the content negotiation to format some DTO in the preferred way. Either by passing it to some kind of templating engine or some serializer for some format.

Most frameworks already allow that in the error case where an Exception is converted into the (hopefully) correct format. But on the actual content we are not doing that.

I actually at one point created a library for a project of mine that wraps a DTO into a Response and then relies upon a Middleware to format that DTO into the actual response-content.

But that is a workaround to fix the shortcoming in PSR-7 as well as in Symfonys Response implementation.

I have no good idea on how to solve that apart from such a quirk.

If you have a good idea, feel free to tell me!

The main thing though is: Make sure your server always returns the expected content in terms of type and language. And we should not have to talk about encoding (UTF-8) and charset (Unicode) any more! Though… There are some very esoteric clients out there….

And when you can’t resolve to a common ground: Send a 406 response!

Leave a Reply

Your email address will not be published. Required fields are marked *

I accept that my given data and my IP address is sent to a server in the USA only for the purpose of spam prevention through the Akismet program.More information on Akismet and GDPR.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

To respond on your own website, enter the URL of your response which should contain a link to this post's permalink URL. Your response will then appear (possibly after moderation) on this page. Want to update or remove your response? Update or delete your post and re-enter your post's URL again. (Find out more about Webmentions.)