ninenines / cowboy

Small, fast, modern HTTP server for Erlang/OTP.

Home Page:https://ninenines.eu

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

content_types_provided should be allowed to return the empty list when no Accept header is specified

asabil opened this issue · comments

The implementation today unconditionally returns a 406 if the content_types_provided callback for cowboy_rest:

{[], Req2, State2} ->
    not_acceptable(Req2, State2);

I think that it should take into account the situation where the client doesn't specify any Accept header, in which case it should skip the content negotiation.

If you don't provide content then doing a GET request will always fail, so I'm not sure what you are trying to say. What is the expected response?

Cowboy does handle the case where accept isn't defined when content is provided. https://github.com/ninenines/cowboy/blob/master/src/cowboy_rest.erl#L476-L486

Content negotiation is also useful for other methods. For example if you return a body following a POST request, it can tell you which content type to use.

The only cases where we don't care about content negotiation is when the resource never returns a body. In that case simply do not implement content_types_provided. Returning [] is for explicitly rejecting the request, for example if you apply additional heuristics on top of what Cowboy is doing.

Imagine an endpoint where:

allowed_methods(Req, State) ->
    {[<<"OPTIONS">>, <<"HEAD">>, <<"GET">>, <<"POST">>, <<"DELETE">>], Req, State}.

and where only the GET method should send a content, whereas POST and DELETE should return 204, ideally we should be able to define content_types_provided as:

content_types_provided(#{method := <<"GET">>} = Req, State) ->
    {[
        {<<"application/json">>, to_json}
    ], Req, State};
content_types_provided(Req, State) ->
    {[], Req, State}.

However, this doesn't work, because the current cowboy_rest logic is tied to the fact that content_types_provided and doesn't handle the case where content_types_provided but returns the empty list.

You don't need to go that far, you can just do:

content_types_provided(Req, State) ->
    {[
        {<<"application/json">>, to_json}
    ], Req, State}.

I still think that implementing content_types_provided/2 and returning the [] (1) should be equivalent to not implementing it (2).

The current implementation handles (1) as reject all requests with 406, whereas (2) only returns 406 if the client actually sends an Accept header.

I suggest changing the implementation such as (1) and (2) are equivalent. Effectively handling the case where the client doesn't send any Accept header.

They can't be equivalent because when the callback is not implemented it is equivalent to returning [{<<"text/html">>, to_html}].

If you need to have the equivalent you need to return this and not just [].