Announcing the `http` crate
users.rust-lang.org
users.rust-lang.org
The Beast HTTP library that just got accepted in to Boost follows this approach. E.g. the following Fields concept specifies the requirements the library has on its any HeaderMap-like type you feed it. Writing an adapter for your own types then becomes straightforward
http://vinniefalco.github.io/beast/beast/concepts/Fields.htm...
Really, you want a concept that's like a trait, but is explicit about the fact that there's exactly one implementation of the trait, and that that implementation is discoverable at compile time. Less like an interface, more like a C typedef in a third-party-library header file—just without the "header file" part.
One problem is construction. When you need to interact with a library at one remove using interfaces everywhere, you can't simply new up instances; you have to go via factories. And that leads to ugly alien code.
Another is extension (OpenGL). If your super duper implementation has nifty new features, how do you expose them if you're living behind an interface? Does the interface enable extension in a usable way? Or is it indirect and awkward again?
In practice, you code and test against one or two implementations (bug compatibility), and other implementations are only supported by accident or effort by the implementors.
(I'm fully on board with the idea that a language ecosystem is constrained by the highest level of abstraction in the type system that's shared across all ecosystem libraries. It's the strongest argument for a large standard library.)
That's mostly a Java problem, though, which lacks first-class types.
Why is a factory more alien than simply newing up an instance? Shouldn't they have basically the same interface? Is it just a matter of giving people rope to hang themselves... because they can do an arbitrary interface, someone inevitably will make a weird one, and then someone else will think it's cute, and the fashion gets perverted?
That does seem like a problem. It could be solved with culture, but solving problems with culture is hard so I understand your decision to write it off as a bad direction.
I am writing a lot of code with this kind of structure lately though, so you've got me a little worried. One of the things I dislike about a library is when it exposes raw data structures to the user, and then presumes other libraries will understand that structure. For example, if I have a client library that is sending HTTP requests, and then another separate library on the server that handles them, there is an opportunity there for miscommunication in that data layer.
On the other hand, if I have one library which gives me both a factory and an interface which consumes that factory object, then the library handles both sides of the data structure management. I am forced to only work through the interface, which means as long as my libs are in order, everything should be able to communicate.
Sorry if that's vague, but I'm programming in JavaScript and this post is about Rust and you're referencing Java and OpenGL, so I'm not sure exactly where the ground is. Maybe I only like my approach because it's JavaScript, so I can't ever assume the type of anything is correct. #stockholmsyndrome
Needing to use a factory means you don't have ambient authority to create instances. Code that constructs needs to be parameterized by the factory, irrespective of how far down a call chain it is. Annotating the call stack with a handle to the library adds clutter and clumsiness throughout.
You can kind of get around this using module systems of various kinds, and the de facto module system in JS of putting your entire program in a function parameterized by the modules it uses - it's far from the worst way of doing things, and it's a lot less clumsy than many alternatives. There's a bonus in that it's the typical idiom in JS. But it isn't in many other languages, so the benefits of the API design pattern needs to be traded off against how it clashes with the language culture. It's not an unalloyed good.
[1] https://www.collinsdictionary.com/dictionary/english/at-one-...
I really do need to give Rust a try though. I'd been holding back because my impressions of it was the core libraries were still in flux so code that compiled today might fail 2 months down the line. Is this still the case now?
It's true that some cases still use nightly, as some people want to opt into cutting-edge stuff. But it's much much less than it was a while back.
Now, I'm willing to use the bleeding edge, since I'm just just learning the language, but it still just bugs me in that way that code smells always do.
The way how interface{} works there is you actually write your own named structure that is bootstrapped by interface{} rather than dealing with interface{} as a generic type that needs casting. So what you're actually working with is an io.Reader / io.Writer structure but which can be transferred to any domain providing your specific implementation of Reader / Writer supports the same methods (since it's the methods that define the interface you avoid all the horrible hacks than normally trouble interface{}). This means you can transfer data from a gzip archive to a base64 encoder or JSON marshaller to a OS STD* file or network device all as if they were the same logical interface and without having to write a lot of additional layers encoding / decoding the data nor describing the interface. It all works surprisingly painlessly. In fact it's literally the only time when working with interface{} that the process isn't painful.
So it's going nothing to do with fanboyism nor stockholm syndrome, interface{} just behaves quite differently to it's usual behavior and works really well in this specific situation in my personal opinion. If the Go developers left interface{} there instead of using it as a hacky alternative to generics then I doubt there would be the same backlash against it. But sadly they didn't.
On the other hand, this is the Nth time that somebody implemented a HTTP library on some platform. It seems that something can be improved still, software-engineering wise.
With golang, for example, it's common to see applications that don't set reasonable cache control headers, content type and length headers, handle head requests properly, compression, and so on.
It's not that golang can't do those things, but the libraries, documentation, and examples don't encourage it. So you see a lot of minimal http implementations that "work", but not well. It's like every (less experienced) end user of the library has to learn all the basics by trial, error, bug reports, etc.
Agreed completely. The lowest-level API should expose them and every other bit of the standard, and higher-level APIs should handle them automatically in sensible ways.
But...but.. Golang core team teaches us that "framework" is a 4 letter word and a core library is enough for everybody. No need to overcomplicate with extra abstractions, just use the standard library they say.
Since there's obviously some time lag before frameworks that use this core library will appear, good documentation on this sort of thing seems advisable.
Then look at the hostile atmosphere in the Go community and tweets of its core team members stating that "http" package is great for everything, no need to use anything else. The framework (or as they prefer to call it toolkit) authors are throwing shit at each other accusing their opponents of creating something not "in the spirit of go" / unconventional, usefulness for the end users is not taken into account. Some others are creating posts about how everybody should stop creating frameworks immediately because that makes them uncomfortable. People hesitating to open-source their code because they are afraid to become a victim of crusade. That's not exactly healthy athmosphere, and it is nourished by core team members.
HTTP requests rarely live in isolation, you are probably going to be making several requests to one or more servers, so most language libraries have another layer on top of the raw HTTP objects. (e.g. in Perl, there's LWP::UserAgent, among others). Here is where the network code tends to exist, for instance. It's more appropriate to put some defaults at this layer - for example, keep-alives and connection re-use are vital concepts for efficient HTTP communication but apply across multiple requests. You can't sensibly control them at the base 'this is a single HTTP request' object layer.
Then requests came along, aimed to make it easy for anyone to consume HTTP, with sensible defaults -- and it rapidly became the defacto standard way to get things done. Hopefully the rust ecosystem will have similar libraries, built on top of this new 'http' base.
First, this crate provides a common layer for the HTTP protocol; if you're interacting with HTTP, you'd use this crate and its types, often directly. Note, in particular, that this crate provides builders to construct HTTP requests and responses, which is often a large part of client and server libraries, respectively.
And second, I don't think any specific HTTP client or server software should claim the crate name "http". A common layer like this, built around the protocol itself, seems to have a much more reasonable claim to it.
401 Unauthenticated
…and then once rough consensus switches, get an update in the RFC.(I'd likely be horrified to learn that something out there critically depends on the Reason Phrase…)
IIRC, the 8xxx RFCs have specified that you must read the reason and that the code itself does not fully specify the result because there have been some custom codes that overlap on number.
> The reason-phrase element exists for the sole purpose of providing a textual description associated with the numeric status code, mostly out of deference to earlier Internet application protocols that were more frequently used with interactive text clients. A client SHOULD ignore the reason-phrase content.
I'm not aware of any highlevel HTTP RFCs in the 8xxx range.
See https://hackernoon.com/three-bytes-and-a-space-8f9fbd1c669b for a related debugging adventure.
Also, technically, you can return 401 for either "unauthenticated" (you need to pass authentication information) or "unauthorized" (you passed authentication information but it wasn't acceptable), depending on the nature of what you need.
403 Forbidden means that your authentication is valid, but that you don’t have access to the resource.
401 should not be used to indicate the latter.
Also, you can fail authorization without passing authentication. For instance, you could be authorized by ip range or something unrelated to any of the data in the http request.
Because those are the semantics as-per the spec.
In relation to your latter point, 403 also covers any other reasons your access is forbidden - e.g. IP ranges etc.
I worked on a site for several years that used HTTP Digest authentication. We finally gave up on it and switched to the standard form-and-cookie approach, because the browser authentication flows had so many bugs, quirks, per-browser idiosyncrasies, and other issues to work around.
HTTP Digest looks interesting, but I think I'd generally feel more comfortable just using HTTP Basic over HTTPS. Or better, of course, just doing it yourself with some signed cookies.
That's still "unauthenticated". "Unauthorized" means that you were authenticated (i.e. the server knows who you are), but you are not allowed (i.e. authorized) to execute the requested operation. So the correct names would be "401 Unauthenticated" and "403 Unauthorized".
Feel free to be horrified, but you'd be a bit naive to be surprised.
>It does not cover transport, but is rather intended for use in libraries like Hyper, and to support an ecosystem of crates that can share HTTP-related types.
So I don't think they added any implementation details to the crate itself, to avoid people taking issue with something being too bloat-ey or not to their need, and rejecting the common types, which seem to be the real goal. It does seem like something you'd always need, though - I can only imagine that a proper parser would be pretty large, and they didn't want people to complain about it.
Is this common practice in Rust? Shouldn't the compiler be smart enough to do that for you? Such code seems quite sensitive to typos.