Skip to content

Req.Request, Req.Response: Make headers opaque #470

@wojtekmach

Description

@wojtekmach

In Req v0.4 I changed headers from the usual lists of tuples to map of lists.

In hindsight it probably wasn't worth it since every other client, as well as Plug, uses lists of tuples. My rationale was describe here https://hexdocs.pm/req/changelog.html#change-headers-to-be-maps and I think the change does not really live up to the expectations. I suppose pattern matching is kinda nice but headers[name] is still unergonomic because it returns [binary], what we IMHO really want is headers[name] :: String.t | nil (give me first header value or nil). If, which is a big IF (though probably a NO), Access is ever extended to lists of two element tuples, resp.headers["content-type"] #=> "text/plain" | nil would JUST WORK though there are considerations with that mentioned next.

As an aside, I'm planning to add Req.get_header(req | resp, name) :: String.t | nil and Req.get_header_values(req | resp, name) :: [binary()]. The former would be used vast majority of the time, the latter would be for the odd Set-Cookie, see https://www.rfc-editor.org/rfc/rfc9110.html#section-5.3-4.1.

What we additionally did is, per RFC 9110, made headers access case-insensitive, all the accessor functions like Req.Request.put_header, Req.Request.get_header (or the new Req.*header* functions mentioned earlier), etc, downcase under the hood. I think this might be another reason to make headers opaque because otherwise when people access them, they don't necessarily preserve the case insensitiveness.

Making headers opaque would allow us to change the internal representation and frankly it probably would go back to [{binary(), binary()}] which would avoid conversions back'n'forth. (It would be my personal L too but that wouldn't be the first one.) A potentially interesting approach is for well known header names, https://www.rfc-editor.org/rfc/rfc9110.html#section-18.4, represent them as atoms which I believe would use less memory and result in faster comparisons and all the other ones use string name for. And then API functions would use atoms and strings. Such design, if it turned out to be worth it performance wise, would only be sensible with opaque type and forcing people to use the public API.

With opaque type we'd forego pattern matching but I think that's OK after all.

If we do this we should also create functions like Req.get_headers(req | req) :: [{binary, binary}] which would serve as compatibility with the rest of the ecosystem which would again admit the whole headers maps was a mistake.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions