I've gone through the process of auditioning a bunch of rust webservers as well and came away with the opinion that of them all, Axum and Tide have the best interfaces and features. We went with tide, and I still believe that it is the _easiest_ to use in most cases - just with a few quirks that need to be changed and features added (or in some cases just be made public). Sadly however it is not very actively maintained and I fear for its future as that compounds with less and less people choosing it over time.
Axum by comparison has a very active community, but I found it's request handling and middleware concepts much less ergonomic. If you're debating between rust webservers it's worth taking a look and giving tide a chance. The async-std choice hasn't been an issue for us at all either.
Just a couple of days ago I watched this video on YT - Designing Tide by Yoshua Wuyrts [1] and I really loved everything about tide.
On the other hand, it seems that this guy was (is) sponsored by Microsoft for working on these things, which is a red flag. Not that I am against something sponsored by them, but having a framework maintained by pretty much a one person-team at Microsoft seems like it could end any time if some manager decides there is no budget for "Rust web research" anymore.
But I have also asked myself - how mature is tide now, and how much development does it really need? I can't answer, since I have used it only very little, over the last days. I am curious if somebody else, more in the know, would explain.
So the question really is - Could tide, as it is now, be considered mature enough such that it does not matter who maintains it (if even)? If so, then it might be the perfect framework. Anything extra could be developed as additional packages on top of it. Plus, it could always go through a revival (somebody else forking it and continuing to add features)?
I came to similar conclusions.
Tide is IMO the most ergonomic, but its future is unclear in terms of community/maintenance. Its choice to use async-std ended up kind of biting it, since projects have by and large chosen tokio. I have hope for the eventual “swappable async runtimes” initiative, but it’s probably going to be too late to help in these regards.
Yeah I think it's mostly a choice between Tokio and async-std. FWIW I chose Axum in a recent migration from Node. It's not perfect, but I found it to be pretty simple, even with controller/service/data abstractions.
> Oh and that type never "gets bigger" in a way that would cause compile-time explosions.
FWIW Axum did get hit by last year's "huge types" regression, the maintainer opted to bypass the issue by boxing routes internally: https://github.com/tokio-rs/axum/pull/404
> ...I didn't actually trust warp that much. It's perfectly fine! It just.. has a tendency to make for very very long types, like the one we see above. Just like tower.
Fair, but if you were writing a web application on top of hyper directly, you probably wouldn't use one tower layer per route, which is somewhat equivalent to what warp does.
When I wrote this I worked at a company where the main Rust codebase had, uh, perhaps too many tower layers.
As someone that uses actix-web, what are the pros and cons of moving to Axum? I hear about it a lot these day. I know it integrates into the Tokio ecosystem well, including Tower, but I'm not sure what that concretely means for someone already using actix-web. When would I use Tower?
I'm writing a toy image sharing webapp with Axum, $40/m server is able to process 200,000 dynamic requests per second. A bit more than nginx and a bit less than varnish serving static files on the same hardware. This whole Rust thing has some potential.
I have a handful of services, some in Warp and some in Rocket, and I dislike both of those frameworks. I've been looking into axum so this is a nice read.
Honestly I don't think that Axum is right either. So for example, this:
async fn create_user(
Json(payload): Json<CreateUser>,
) -> impl IntoResponse {
let user = User {
id: 1337,
username: payload.username,
};
(StatusCode::CREATED, Json(user))
}
For context, if you haven't checked out Axum, this is from the Axum docs.
Rocket has a similar thing with its request guards, it has a similar json type that you put into the signature of a handler function and it automatically plucks the value from the body and parses it as json.
What's weird to me is that it's coupling the request and response format to the logic. What if a client wants to post this as a form body? Write a separate endpoint? What if some old clients put the arguments in the query string? When I see this snippet as one of the intro examples for Axum, it feels like a red flag to me.
> What's weird to me is that it's coupling the request and response format to the logic.
It does not though? It lets you do it for your personal convenience. If you define a JSON API, you can just tell the framework that it takes JSON data, and it'll do the deserialisation for you.
> What if a client wants to post this as a form body? Write a separate endpoint? What if some old clients put the arguments in the query string?
I'm sure this wouldn't work as-is and would require some tuning up or `unify()` calls, but you get the gist.
IIRC Axum only routes on the URL, so it can't do that, that's both why it doesn't build types as giantic as warp, and why you have to specify the extractors in the function where warp doesn't need that (you'd just tell it that `payload: CreateUser).
The Python framework FastAPI does this too. I think it does so because it's convenient, easy, and it's one less line of boilerplate. You trade off flexibility for having a very clean and simple "happy path".
You don't have to specify the type in the signature; you can just as easily parse the request body manually. But in the instance where the endpoint only accepts json, it's simpler to write it this way.
You can just have `Request` as a parameter and do those edge cases yourself. Or write your own Extractor which would handle that pretty easily.
I'm not sure of your use case where clients can send any format they want and the HTTP server is supposed to know and handle any format automatically (form, json, querystring, etc), but seems more like a legacy edge case than something you would do building a server from scratch.
Something like this (completely untested) but pretty straight forward to handle your usecase.
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub struct FormOrJson<T>(pub T);
#[async_trait]
impl<T, S, B> FromRequest<S, B> for FormOrJson<T>
where
T: DeserializeOwned,
B: HttpBody + Send + 'static,
B::Data: Send,
B::Error: Into<BoxError>,
S: Send + Sync,
{
type Rejection = InvalidFormOrJson;
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
if json_content_type(req.headers()) {
let bytes = Bytes::from_request(req, state).await?;
let deserializer = &mut serde_json::Deserializer::from_slice(&bytes);
let value = match serde_path_to_error::deserialize(deserializer) {
Ok(value) => value,
Err(err) => {
let rejection = match err.inner().classify() {
serde_json::error::Category::Data => JsonDataError::from_err(err).into(),
serde_json::error::Category::Syntax | serde_json::error::Category::Eof => {
JsonSyntaxError::from_err(err).into()
}
serde_json::error::Category::Io => {
if cfg!(debug_assertions) {
// we don't use `serde_json::from_reader` and instead always buffer
// bodies first, so we shouldn't encounter any IO errors
unreachable!()
} else {
JsonSyntaxError::from_err(err).into()
}
}
};
return Err(rejection);
}
};
Ok(FormOrJson(value))
} else if has_content_type(req, &mime::APPLICATION_WWW_FORM_URLENCODED) {
let bytes = Bytes::from_request(req).await?;
let value = serde_urlencoded::from_bytes(&bytes)
.map_err(FailedToDeserializeQueryString::__private_new::<(), _>)?;
Ok(FormOrJson(value))
} else {
Err(InvalidFormOrJson.into())
}
}
}
Coincidentally, last night I started porting an actix-web project to Axum. In my very brief experience, I have found Axum (0.6.0-rc5) to be more ergonomic compared to actix-web. However, I haven't rewritten all the existing features yet so my opinion could change in a few days.
I respect the people behind Warp, but it always struck me as a design that actively worked against the grain of the language rather than a design that worked gracefully with the language. A bit too "functional" for what Rust really supports.
Really glad to see this! More eyes on axum will make it better and better.
We recently decided to move from Rocket to axum for non-user-facing service to support our platform. Haven't made the decision yet to move the main API, but strongly considering it.
Rocket is really nice, and it's recent stall at 0.5-rc has been jump-started, but I feel that axum has much more momentum. Sergio (Benitez, of Rocket) is fantastic, but only one guy. OTOH, Axum is mostly the pet project of one person as well. If I had to pick analogies, I'd say Rocket is aiming to be more like Django and Axum more like Flask. The latter scope seems much more sustainable by a single person.
> The axum::debug_handler macro is invaluable to debug type errors (there's some with axum too), like for example, accidentally having a non-Send type slip in.
Heh, yeah. For my recent project where I explored implementing the same little app in a few different languages[0], I chose Axum for the rust version.
The whole "extractor" system was pretty magical, and when I had this exact issue (non-Send argument), the compiler error was totally useless. I did see the docs about adding this extra macro crate for error messages but it seemed like a bit of a red flag that the framework was going against the grain of the language. Still, on the whole, I did enjoy working with Axum.
> I did see the docs about adding this extra macro crate for error messages but it seemed like a bit of a red flag that the framework was going against the grain of the language.
Is it really going against the grain of the language? Or really that Rust still has issues with error messages on complex / deep types and that should be reported / fixed?
Because leveraging the type system seems to be pretty in-line with normal Rust ideals, and Send issues is a load-bearing type error of the concurrency system.
I created my own web framework (<https://docs.rs/under/latest/under/>) to address some of my own perceived issues with a lot of current rust web frameworks - typing issues being one of them. Personally, I don't like the idea of endpoints requiring `#[handler]` or other "magic" derives, or guards - I wanted something with an endpoint that takes a request, and returns a result with a response. I'm still working on it, though.
>I always thought Actix-Web was the tried & true rust web framework.
That is mostly still true, but Axum is developed under the same organization as Tokio and integrates very well with that ecosystem, including Tower middleware. So in that sense it has a feeling of being "official" insofar as such a thing exists. The community is big enough for it to not die if the maintainer were to disappear tomorrow.
It's also a pretty nice framework and has better compile times than Actix-web. It is less an explosion of frameworks so much as a consolidation, if anything. Axum and Actix are the de-facto frontrunners.
I've gone very recently through a rewrite from Rocket to Axum and very much love it so far.
The initial motivation was the need for web socket support, which Rocket doesn't have (yet). But I love how simple it is, and also that it does not want to be the entry point of the application. (I like an http server that's a library that can be embedded at any place in the application.) Another great thing is the examples/ directory in the Axum repository.
I had to use the latest version from GitHub though to get some of the features I needed, but maybe that's not the case anymore.
Judging by these comments, everyone is writing web apps in Rust these days. While I can understand the fascination that many HNers seem to have with Rust (been wanting to give it a try myself for some time now, but work and life always get in the way), I'm still not sure web apps are really a good fit for a language that has a reputation for being high performance, but more difficult than other languages commonly used for web development. I mean, you wouldn't build a web app in C/C++, and not only because of the potential security issues?
My primary reason for moving was to capture the advantages of a strongly-typed language. I suppose Go would have been another reasonable choice in this regard.
Not about OS/2 Warp, apparently. I was imagining an OS/2 diehard user either migrating to some new version of ArcaOS or finally finding another worthy successor to the system.
I did the exact same thing with my imageboard, plainchant [1]. I had the same experience that you did with Warp: the routing model was extremely clever, but never came to feel intuitive or ergonomic to me.
Using "if let" is generally preferred style when there's only a single case that needs to be handled. (And I suspect that now that "if let ... else" is available that that will become the preferred style for handling two cases.)
[+] [-] mdtusz|3 years ago|reply
Axum by comparison has a very active community, but I found it's request handling and middleware concepts much less ergonomic. If you're debating between rust webservers it's worth taking a look and giving tide a chance. The async-std choice hasn't been an issue for us at all either.
[+] [-] ibz|3 years ago|reply
Just a couple of days ago I watched this video on YT - Designing Tide by Yoshua Wuyrts [1] and I really loved everything about tide.
On the other hand, it seems that this guy was (is) sponsored by Microsoft for working on these things, which is a red flag. Not that I am against something sponsored by them, but having a framework maintained by pretty much a one person-team at Microsoft seems like it could end any time if some manager decides there is no budget for "Rust web research" anymore.
But I have also asked myself - how mature is tide now, and how much development does it really need? I can't answer, since I have used it only very little, over the last days. I am curious if somebody else, more in the know, would explain.
So the question really is - Could tide, as it is now, be considered mature enough such that it does not matter who maintains it (if even)? If so, then it might be the perfect framework. Anything extra could be developed as additional packages on top of it. Plus, it could always go through a revival (somebody else forking it and continuing to add features)?
[1] - https://www.youtube.com/watch?v=laJA4QCjmxk
[+] [-] Serow225|3 years ago|reply
[+] [-] metadaemon|3 years ago|reply
[+] [-] masklinn|3 years ago|reply
FWIW Axum did get hit by last year's "huge types" regression, the maintainer opted to bypass the issue by boxing routes internally: https://github.com/tokio-rs/axum/pull/404
Which is not really surprising, as it's built on tower, which at the time Lime noted was affected: https://fasterthanli.me/articles/why-is-my-rust-build-so-slo...
> ...I didn't actually trust warp that much. It's perfectly fine! It just.. has a tendency to make for very very long types, like the one we see above. Just like tower.
[+] [-] fasterthanlime|3 years ago|reply
When I wrote this I worked at a company where the main Rust codebase had, uh, perhaps too many tower layers.
[+] [-] satvikpendem|3 years ago|reply
[+] [-] jph|3 years ago|reply
[+] [-] rwaksmunski|3 years ago|reply
[+] [-] zemo|3 years ago|reply
Honestly I don't think that Axum is right either. So for example, this:
For context, if you haven't checked out Axum, this is from the Axum docs.Rocket has a similar thing with its request guards, it has a similar json type that you put into the signature of a handler function and it automatically plucks the value from the body and parses it as json.
What's weird to me is that it's coupling the request and response format to the logic. What if a client wants to post this as a form body? Write a separate endpoint? What if some old clients put the arguments in the query string? When I see this snippet as one of the intro examples for Axum, it feels like a red flag to me.
[+] [-] masklinn|3 years ago|reply
It does not though? It lets you do it for your personal convenience. If you define a JSON API, you can just tell the framework that it takes JSON data, and it'll do the deserialisation for you.
> What if a client wants to post this as a form body? Write a separate endpoint? What if some old clients put the arguments in the query string?
Take a raw body and query strings and do the dispatching internally. Hell, you can ask for the request (https://docs.rs/http/latest/http/request/struct.Request.html) itself:
There you go, knock yourself out.I think Warp would actually let you write different handlers for each of those cases, because it routes on the entire thing e.g.
or you could tell it to unify these three filters and pass the data from whatever source it got to the same handler: I'm sure this wouldn't work as-is and would require some tuning up or `unify()` calls, but you get the gist.IIRC Axum only routes on the URL, so it can't do that, that's both why it doesn't build types as giantic as warp, and why you have to specify the extractors in the function where warp doesn't need that (you'd just tell it that `payload: CreateUser).
[+] [-] Kinrany|3 years ago|reply
1. A request type and an associated response type
2. Impls for converting an HTTP request into the request type, and the reverse for response
3. A server type with an impl for handling the request
4. A client type with an impl for sending the request
The remaining challenge is making all this ergonomic for the simple cases.
[+] [-] nerdponx|3 years ago|reply
[+] [-] jamincan|3 years ago|reply
[+] [-] elysian-breeze|3 years ago|reply
I'm not sure of your use case where clients can send any format they want and the HTTP server is supposed to know and handle any format automatically (form, json, querystring, etc), but seems more like a legacy edge case than something you would do building a server from scratch.
Something like this (completely untested) but pretty straight forward to handle your usecase.
[+] [-] dman-os|3 years ago|reply
[+] [-] rychco|3 years ago|reply
[+] [-] unknown|3 years ago|reply
[deleted]
[+] [-] kibwen|3 years ago|reply
[+] [-] chromatin|3 years ago|reply
We recently decided to move from Rocket to axum for non-user-facing service to support our platform. Haven't made the decision yet to move the main API, but strongly considering it.
Rocket is really nice, and it's recent stall at 0.5-rc has been jump-started, but I feel that axum has much more momentum. Sergio (Benitez, of Rocket) is fantastic, but only one guy. OTOH, Axum is mostly the pet project of one person as well. If I had to pick analogies, I'd say Rocket is aiming to be more like Django and Axum more like Flask. The latter scope seems much more sustainable by a single person.
[+] [-] losvedir|3 years ago|reply
Heh, yeah. For my recent project where I explored implementing the same little app in a few different languages[0], I chose Axum for the rust version.
The whole "extractor" system was pretty magical, and when I had this exact issue (non-Send argument), the compiler error was totally useless. I did see the docs about adding this extra macro crate for error messages but it seemed like a bit of a red flag that the framework was going against the grain of the language. Still, on the whole, I did enjoy working with Axum.
[0] https://github.com/losvedir/transit-lang-cmp
[+] [-] masklinn|3 years ago|reply
Is it really going against the grain of the language? Or really that Rust still has issues with error messages on complex / deep types and that should be reported / fixed?
Because leveraging the type system seems to be pretty in-line with normal Rust ideals, and Send issues is a load-bearing type error of the concurrency system.
[+] [-] telios|3 years ago|reply
[+] [-] LAC-Tech|3 years ago|reply
What's behind this sudden explosion of them? Are they all skins over the same core libs or what?
[+] [-] dralley|3 years ago|reply
That is mostly still true, but Axum is developed under the same organization as Tokio and integrates very well with that ecosystem, including Tower middleware. So in that sense it has a feeling of being "official" insofar as such a thing exists. The community is big enough for it to not die if the maintainer were to disappear tomorrow.
It's also a pretty nice framework and has better compile times than Actix-web. It is less an explosion of frameworks so much as a consolidation, if anything. Axum and Actix are the de-facto frontrunners.
[+] [-] jonathan_s|3 years ago|reply
The initial motivation was the need for web socket support, which Rocket doesn't have (yet). But I love how simple it is, and also that it does not want to be the entry point of the application. (I like an http server that's a library that can be embedded at any place in the application.) Another great thing is the examples/ directory in the Axum repository.
I had to use the latest version from GitHub though to get some of the features I needed, but maybe that's not the case anymore.
[+] [-] rob74|3 years ago|reply
[+] [-] satvikpendem|3 years ago|reply
[+] [-] chromatin|3 years ago|reply
My primary reason for moving was to capture the advantages of a strongly-typed language. I suppose Go would have been another reasonable choice in this regard.
[+] [-] OtomotO|3 years ago|reply
I liked the API of warp a lot, it somehow reminded me of Akka http a bit.
But all the points mentioned in the blog post resonate with me, which is why I've begun to migrate to axum.rs as well :)
[+] [-] ptx|3 years ago|reply
[+] [-] fasterthanlime|3 years ago|reply
[+] [-] jgbyrne|3 years ago|reply
[1] https://github.com/jgbyrne/plainchant
[+] [-] tipsytoad|3 years ago|reply
[+] [-] khuey|3 years ago|reply
[+] [-] steveklabnik|3 years ago|reply
However, while style truly is subjective, I'm not sure anyone could convince me that
is the same or better than The RFC for "if let" has some good motivating examples as well https://rust-lang.github.io/rfcs/0160-if-let.html[+] [-] sivakon|3 years ago|reply
[+] [-] java_expert_123|3 years ago|reply
[+] [-] fasterthanlime|3 years ago|reply
See https://docs.rs/tokio/latest/tokio/attr.main.html