I am requesting comments from everyone who has experience in web development, networking and/or has strong opinions about library stability/documentation/improvement.
Background info
It’s been a few years since I’ve taken over maintainership of the http-types package. I had quickly released version 0.12.4 (4 years after the then latest release) which was mostly a documentation update and releasing what was already merged in the main branch, and I now have some time and space to work more on this package.
There were some small additions here and there that were easy to implement from issues on the previous repo, so together with a more involved test suite, we now have 0.12.5.
I mainly took over as maintainer, because I’ve found lots of functions specific to handling headers in the wai-extra/warp packages, which I felt would be more suited in a package that was more generally about HTTP than in server-specific libraries.
I asked around on the Discourse, and @Kleidukos advised that it’d be best to add those helper functions to the http-types library. Which, in my opinion, is indeed probably the best location.
If anyone else has a better idea/different opinion, do let me know, because we now come to the main reason why I am Requesting For Comments:
The main issue(s)
Just adding extra functions to http-types is one thing, and this will probably happen regardless, but while going through the code and especially the type definitions, I found that the API is at best “good enough”.
There’s a bunch of type synonyms instead of newtypes, and all of the data constructors and fields of data types are exported. This doesn’t help with stability/maintainability, but some definitions are just asking for users to make mistakes.
I’ll take Headers as an example, but I might rework all modules depending on if I find ways to improve the API or performance.
My issue with Headers
Having definitions like
type HeaderName = CI ByteString
type Headers = [(HeaderName, ByteString)]
works, but it also has a lot of shortcomings:
- changing the implementation will almost by definition break every location they are used, since the functions creating and changing them are from
case-insensitiveorData.List.
e.g. so ifHeaderNameis no longer aCI a, hello compiler errors. CI ByteStringhas no guarantee to be a validHeaderName. Header Field Names are only allowed to be a subset of the ASCII visible characters.- A “list of headers” is true to the definition of HTTP Fields, but letting all users use functions from
Data.Listto manipulate the headers makes it easy to create bad header lists. i.e. introducing duplicate headers, which is bad because “a sender MUST NOT generate multiple field lines with the same name […] unless that field’s definition allows multiple field line values to be recombined as a comma-separated list” - RFC 9110 §5.3 - Using a linked list also incentivises to add to the front, which is not bad per se, but you typically want more connection-specific headers to be at the front for HTTP/1.x
- There might be better data structures than a linked list to make handling headers more efficient.
What to do about it
I’ve been working on improving HeaderNames and Headers, and I feel I’ve got a better API and also some promising benchmarks. I’d also like to improve other types where I can so that at least the HTTP types part of HTTP requests and responses are as fast as possible, which would be a boon to any user/company using Haskell for web development.
Once I’ve finished the test suite for this new implementation, I’d like to publish it as a candidate to hackage and hope some people will give me some feedback on it, but for now the following are more pressing matters:
Ecosystem hurdles
Looking at the reverse dependencies of http-types has shown me a few problems:
- Because of how old this library is, and how little it has changed, a lot of dependency constraints from packages depending on
http-typesare either completely missing, or have no upper bound. (i.e. yesod-core:http-types (>=0.7), amazonka:http-types (>=0.12), or hpack’s nakedhttp-types) - Because of the above-mentioned points on type synonyms, there’s no easy way to set up the migration. There will be breakage.
- Adding this new implementation to
http-typesalongside the old implementations (e.g. in other modules likeNetwork.HTTP.Types.Header.New) might result in confusing errors, but replacing the old modules with completely new types and functions will force all packages depending onhttp-typesto change to the new types. (which is not strange for a major version release, but the scale of this impact makes me pause for obvious reasons) - Creating a
http-types-compatpackage is a possible solution so that packages depending onhttp-typescan support all the versions before and after this change, but this would still incur work for all down-stream packages to use the types and functions from the new packages, instead ofCIandData.List.
The big ask
So I ask advice on how to proceed. There are a few options I can see right now:
- Create a new major-major version of
http-types(i.e.http-types-1.0.0) that is completely different from the current implementation.- This will break every package that doesn’t have an upper bound
- If this is implemented in e.g.
waiandwarp, it will force all packages downstream to also update, or they won’t be able to use the newerwai/warpversions.
- Add the new implementation in other modules next to the old implementations in
http-types(probably ashttp-types-0.13.0) to give users the option to use which one they want.- This will not break anything by publishing the library, but might lead to confusion when, for example,
waistarts using the new types, which have the same name. Users will get errors likeexpected `HeaderName` but got `CI ByteString`and then everyone will need to adapt to the new implementations anyway.
- This will not break anything by publishing the library, but might lead to confusion when, for example,
- Create a completely new package, like
http-utils, but this might create a split in the ecosystem of packages usinghttp-typesand those usinghttp-utils, but at least it’s obvious that they are not compatible.