[HN Gopher] Migrating from Warp to Axum
       ___________________________________________________________________
        
       Migrating from Warp to Axum
        
       Author : hasheddan
       Score  : 87 points
       Date   : 2022-11-23 13:18 UTC (9 hours ago)
        
 (HTM) web link (fasterthanli.me)
 (TXT) w3m dump (fasterthanli.me)
        
       | masklinn wrote:
       | > 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
       | 
       | 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 wrote:
         | 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.
        
       | jph wrote:
       | Warp/Rocket/Actix to Axum is a good improvement IMHO, especially
       | because you get Hyper and Tower as well. I wrote an intro to
       | these at https://github.com/joelparkerhenderson/demo-rust-axum
        
       | tipsytoad wrote:
       | Noob to rust, is there a reason to use if / if let over match
       | statements or is it just a style preference?
        
         | khuey wrote:
         | 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.)
        
           | fasterthanlime wrote:
           | Yeah, clippy will yell at you if you use a match with a
           | single arm - I personally don't hate matches with a single
           | arm, but I'd rather not fight clippy (ie. come up with my own
           | clippy config, enforce it onto every project I maintain etc.)
        
             | ratmice wrote:
             | I've hit this warning a few times where I expect to add
             | additional match arms to be added in the future, always
             | bugs me that clippy in that case steers you towards some
             | change you know you'll end up undoing.
        
               | khuey wrote:
               | This is what #[allow(clippy::whatever)] is for.
        
               | pitaj wrote:
               | That's why they're warnings, not errors. I don't really
               | understand the inability of some people to ignore
               | warnings while they're working.
        
           | pitaj wrote:
           | > And I suspect that now that "if let ... else" is available
           | that that will become the preferred style for handling two
           | cases.                   if let Pattern(binding) = thing {
           | ... } else { ... }
           | 
           | has been available for a long time. What was recently
           | stabilized is                   let Pattern(binding) = thing
           | else { ... };
           | 
           | which is syntax sugar for                   let binding = if
           | let Pattern(binding) { binding } else { ... };
           | 
           | And facilitates easier unwrapping in early exit or default
           | scenarios, without an extra level of indentation.
        
             | khuey wrote:
             | Yes, you're right of course. That's what I get for
             | commenting while not fully awake :)
        
         | steveklabnik wrote:
         | In the sense that "match is a superset of if and if let's
         | functionality", yeah it's just a style preference.
         | 
         | However, while style truly is subjective, I'm not sure anyone
         | could convince me that                 match foo {
         | true => println!("hello"),           _ => {}       }
         | 
         | is the same or better than                 if foo {
         | println!("hello");       }
         | 
         | The RFC for "if let" has some good motivating examples as well
         | https://rust-lang.github.io/rfcs/0160-if-let.html
        
           | tipsytoad wrote:
           | What about this code from the article
           | 
           | ``` if cx.path == "/tags" { return
           | tags::serve_list(cx).await; } else if
           | cx.path.starts_with("/tags/") { return
           | tags::serve_single(cx).await; }                   if cx.path
           | == "/settings" {             return
           | settings::serve(cx).await;         }              if cx.path
           | == "/search" {             return search::serve(cx).await;
           | }              if cx.path == "/login" {             return
           | login::serve_login(cx).await;         }              if
           | cx.path == "/patreon/oauth" {             return
           | login::serve_patreon_oauth(cx).await;         }
           | if cx.path == "/logout" {             return
           | login::serve_logout(cx).await;         }              if
           | cx.path == "/debug-credentials" {             return
           | login::serve_debug_credentials(cx).await;         }
           | if cx.path == "/comments" {             return
           | comments::serve(cx).await;         }              if cx.path
           | == "/latest-video" {             return
           | latest_video::serve(cx).await;         }              if
           | cx.path == "/patron-list" {             return
           | patron_list::serve(cx).await;         }              if
           | cx.path == "/index.xml" {             return cx
           | .serve_template("index.xml", Default::default(),
           | mime::atom())                 .await;         }
           | 
           | ```
        
             | steveklabnik wrote:
             | (Hacker News doesn't support markdown; indent any line two
             | spaces to make it render as code)
             | 
             | I would probably write that with a match, personally. I
             | didn't say match was useless, just that if and if let pull
             | their weight as features, in my opinion, even though you
             | could express them as a match if you really wanted to.
        
             | fasterthanlime wrote:
             | Oh I wrote that code in anger forever ago, it had the merit
             | of still working when I did the last round of cleanups on
             | my website.
             | 
             | I could've sworn at some point some paths had something
             | slightly more involved (strip slashes, some starts_with or
             | trim_prefix, etc.) but the snippet as you've copied it is
             | certainly... not great.
        
       | kibwen wrote:
       | 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.
        
       | losvedir wrote:
       | > _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.
       | 
       | [0] https://github.com/losvedir/transit-lang-cmp
        
         | masklinn wrote:
         | > 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.
        
       | sivakon wrote:
       | i really like elixir plug style API for http. trillium.rs comes
       | very close but it's not as popular.
        
       | satvikpendem wrote:
       | 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?
        
       | newaccount2021 wrote:
       | I'm using Axum now...its nice but still could use much more
       | "batteries included"
       | 
       | For example, I would like something like Go's Context for passing
       | temporary per-request data through middlewares...maybe this is
       | possible with Axum but I'm not seeing a straightforward approach
       | 
       | Extensions also seem to be kinda magical and not straightforward
       | 
       | Immature ergonomics are still a hallmark of many Rust libs
        
         | jamincan wrote:
         | Maybe tower-request-id is an example of how to implement
         | something like that?
         | 
         | https://github.com/imbolc/tower-request-id
        
           | newaccount2021 wrote:
           | This is exactly what I am doing, but you shouldn't have to
           | modify the request itself to pass temporary state
        
             | fasterthanlime wrote:
             | Eh, that's definitely a matter of taste. You definitely
             | shouldn't have to pass an additional parameter everywhere
             | you want context there's no absolutes here, just
             | preferences.
        
             | masklinn wrote:
             | Why not? Given Rust's semantics it's really no different
             | than how Go's Context seems to be used, just more
             | efficient.
        
         | fasterthanlime wrote:
         | Request extensions (a feature of the `http` crate, not
         | specifically hyper/warp/axum) are "just" a typemap, which is
         | "just" a HashMap where keys are TypeIDs rather than strings.
         | It's slightly less footgunny than something like Go's Context,
         | although I still dislike it because checking for the presence
         | of something happens at runtime only, so if you're messing with
         | middleware composition, it's still too easy to accidentally get
         | it wrong.
        
         | masklinn wrote:
         | > For example, I would like something like Go's Context for
         | passing temporary per-request data through middlewares...maybe
         | this is possible with Axum but I'm not seeing a straightforward
         | approach
         | 
         | That's probably more of a Tower(-http) thing. It's the bit
         | which handles all the middleware, Tower is layered over that.
         | 
         | > Extensions also seem to be kinda magical and not
         | straightforward
         | 
         | Yeah, the problem is that they come directly from the http
         | crates so everything that's built on top assumes you know what
         | it is.
         | 
         | The reality is that Extensions are just a typemap, meaning it's
         | a k:v where keys are types. So you can define your own state
         | type, shove it in the Extension, and know that you'll be able
         | to retrieve that and there will be no collision (because it's
         | _your_ state type). For a more complete introduction, see
         | https://blog.adamchalmers.com/what-are-extensions/
         | 
         | It's one of those topic which is so obvious afterwards that
         | it's easy to forget it's completely opaque and obscure
         | beforewards.
        
       | ptx wrote:
       | 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.
        
         | fasterthanlime wrote:
         | I'm afraid I'm a bit too young for OS/2 Warp, but I can write
         | about Haiku OS in the future if that'd help?
        
       | java_expert_123 wrote:
       | I'd like a single-threaded async pull-based http library
        
         | fasterthanlime wrote:
         | I'm not sure what you mean by "pull-based", but you can make
         | the whole thing single-threaded with `#[tokio::main(flavor =
         | "current_thread")]`
         | 
         | See https://docs.rs/tokio/latest/tokio/attr.main.html
        
       | rob74 wrote:
       | 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?
        
         | satvikpendem wrote:
         | There was a post just yesterday about this topic, for which I
         | gave my (general) rebuttal:
         | https://news.ycombinator.com/context?id=33714300
        
         | chromatin wrote:
         | I previously wrote web applications in Python.
         | 
         | 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 wrote:
       | Yup, working on that one too.
       | 
       | 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 :)
        
       | mdtusz wrote:
       | 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.
        
         | Serow225 wrote:
         | 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.
        
         | metadaemon wrote:
         | 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.
        
         | ibz wrote:
         | Very similar conclusions to what I got to.
         | 
         | 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
        
       | LAC-Tech wrote:
       | I always thought Actix-Web was the tried & true rust web
       | framework. I'd vaguely heard of Warp and never heard of Axum.
       | 
       | What's behind this sudden explosion of them? Are they all skins
       | over the same core libs or what?
        
         | dralley wrote:
         | >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.
        
       | rwaksmunski wrote:
       | 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.
        
       | jonathan_s wrote:
       | 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.
        
       | rychco wrote:
       | 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.
        
         | [deleted]
        
       | chromatin wrote:
       | 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.
        
       | zemo wrote:
       | 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.
        
         | masklinn wrote:
         | > 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?
         | 
         | 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:                   async fn handler(request:
         | Request<Body>) {             // ...         }
         | 
         | 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.                   let some_route = path!("foo" / usize /
         | "bar");         some_route.and(json()).map(handler_json)
         | .or(some_route.and(form()).map(handler_form)
         | .or(some_route.and(query()).map(handler_query)
         | 
         | or you could tell it to unify these three filters and pass the
         | data from whatever source it got to the same handler:
         | path!("foo" / usize / "bar").and(
         | json().or(form()).or(query())         ).map(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).
        
           | zemo wrote:
           | sure you can get the underlying request, but if that's the
           | answer to everything that the framework author didn't think
           | of, that's just an admission that the abstraction is wrong,
           | which is kinda what I'm getting at. The entire conceptual
           | model that views the Json<T> type as a handler argument type
           | and then parsing the body based off of that is what Rocket
           | does too. I think the entire strategy is conceptually
           | incorrect. Axum may do Rocket better than Rocket, but if it's
           | using the same conceptual model, it seems like a lateral
           | move. I'm looking for a new abstraction and conceptual model,
           | not a better implementation of the same concepts or the same
           | concepts with a larger pool of maintainers.
        
             | infogulch wrote:
             | That's not a wrong abstraction, it's just not the
             | abstraction level that you want to work at for this
             | problem. So... create multiple endpoints that all call the
             | same function to perform the behavior you want? Maybe call
             | it a "controller".
        
               | zemo wrote:
               | I spent ten years writing Go HTTP servers and the
               | abstraction used in net/http has served me well for a
               | decade straight. I've been writing http servers in rust
               | for 3 months. Rocket provides an abstraction with a lot
               | of holes that makes me jump through a lot of hoops to do
               | things that have been trivial and common in http
               | programming for over a decade. I've written stuff using
               | only Hyper, it's very manual. Warp has the problems the
               | article describes. With Go, I used the standard library
               | HTTP implementation for a decade and was happy the whole
               | time. I also wasn't experienced with Go going into it, it
               | was how I learned Go.
               | 
               | Axum and Rocket have the same general thrust, the
               | abstraction being that you define types to see a thing
               | that's not an HTTP request. That's the abstraction that I
               | think is incorrect. That design strategy is actively
               | making things complicated for me on a daily basis at my
               | dayjob writing services in Rust.
               | 
               | I want something higher level than Hyper but with a
               | different theory of abstraction than Rocket or Hyper want
               | to provide. The theory of Rocket's abstraction is that
               | the framework handles the http request and response for
               | you, what you see is something else. That's not the
               | toolkit I'm looking for. The toolkit I'm looking for
               | makes it easy to interact with http request and response
               | streams instead of making it easy to hide their
               | existence.
        
               | infogulch wrote:
               | You don't want to use request guards / automatic
               | deserialization into strict types because it's not
               | flexible enough. You're not willing to use the tiniest
               | abstraction (a function) to perform the same behavior for
               | different strictly encoded requests. (Well, you didn't
               | really respond to the content of my comment at all, but
               | nevermind.) You rejected ancestor's suggestion of using
               | Request<Body>... which is exactly what is provided by Go.
               | 
               | I can't tell what you want, all of these opinions stacked
               | together are incoherent.
               | 
               | > The toolkit I'm looking for makes it easy to interact
               | with http request and response streams
               | 
               | > I want something higher level than Hyper
               | 
               | So higher level than hyper, but no higher level than
               | hyper. Crystal clear.
        
               | zemo wrote:
               | no that's ... a pretty extreme misreading of what I'm
               | saying. I'm not saying "I don't want any abstraction",
               | I'm saying "I don't think this abstraction is a very good
               | one, I think it has problems, and I don't think I would
               | rewrite my existing services to use this framework as a
               | result". Here, I'll provide two high-level alternatives.
               | 
               | Here's some pseudo-code of an endpoint that can accept
               | json or form data as an alternative to the Json<T>
               | abstraction that Rocket and Axum both currently utilize:
               | async fn handler(thing: Thing) {             // the Thing
               | is read from the request by a request decoder.
               | // The request decoder is chosen from a set of available
               | // request decoders based on the Content-Type header.
               | }              let mut app = App::new();
               | app.register_decoder(jsonDecoder);
               | app.register_decoder(formDecoder);
               | app.post("/thing", handler);         app.run()
               | 
               | > You rejected ancestor's suggestion of using
               | Request<Body>... which is exactly what is provided by Go.
               | 
               | That's not really accurate. net/http provides an
               | abstraction that has survived for a decade that has been
               | leveraged by a lot of tools to make middleware
               | interchangeable. For example, gorilla/mux uses the
               | net/http standard, that has worked great for me for like
               | 8 years running (unfortunately, that project lost its
               | maintainer). The argument I'm making is that not all
               | abstractions are equally good; Json<T> is an example of
               | an abstraction that is used in Rocket that I have found
               | to be cumbersome and Axum is repeating that abstraction.
               | It's one of the very first examples in their docs. Why
               | couple handler logic to request encoding? I think that
               | abstraction is wrong, I don't think it will withstand the
               | test of time, and in another year or two, will be back at
               | it, updating our Axum services to use [some new thing].
               | 
               | So instead of the core abstraction being "every endpoint
               | accepts whatever type it wants", the core abstraction
               | could be "every endpoint accepts one value of the same
               | type":                   async fn handler(req:
               | Request<Body>) {             // req.decoder looks at the
               | content-type header and             // picks from a list
               | of registered decoders. If             // the client
               | picks an unsupported decoder it fails.             let
               | dec = req.decoder()?;             let thing =
               | dec.parse::<Thing>()?;         }              let mut app
               | = App::new();         app.register_decoder(jsonDecoder);
               | app.register_decoder(formDecoder);
               | app.post("/thing", handler);         app.run()
               | 
               | So a really cool, useful, powerful, and general
               | abstraction that I love in Rust is the string parse
               | method: https://doc.rust-
               | lang.org/std/string/struct.String.html#meth...
               | 
               | I honestly would rather have _that_ for HTTP requests
               | than making an assumption about the content encoding in
               | the handler 's signature.
               | 
               | > (Well, you didn't really respond to the content of my
               | comment at all, but nevermind.)
               | 
               | I mean my argument is "a thing that is trivially
               | expressible and easy to do in other stacks has poor
               | ergonomics in this framework" and your response is
               | basically "ok so take the product of all of your
               | endpoints and all of your encodings, ez pz", which ... is
               | also not ergonomic? Literally the opening prompt was me
               | saying I think that coupling the encoding to the
               | endpoint's logic means you'd have to write another
               | endpoint and that feels wrong to me, so ... you're just
               | telling me to do the thing that I specifically said is
               | the thing that makes me think this abstraction is weak.
        
               | jamincan wrote:
               | For what it's worth, it's not that difficult to manually
               | implement the `FromRequest` trait for `Thing` so that it
               | parses the request based on the Content-Type.
        
               | masklinn wrote:
               | It's literally how warp works but apparently they skipped
               | right over that so...
               | 
               | The objection is also incredibly weird, I think I've
               | "needed" a variable type intake all of once, and it was a
               | mistake to do so (as it's an easy path towards
               | inconsistent handling at different levels of processing).
        
               | zemo wrote:
               | it's a thread on an article about moving away from warp.
               | I have a handful of warp services currently and we're
               | actively moving those services away from warp for other
               | reasons. I'm not going to try to convince everyone at my
               | org to stay on warp, it has the issues this article
               | mentions.
               | 
               | My argument is not that it's impossible, it's that the
               | whole value proposition of these frameworks is that they
               | make you jump through fewer hoops than building on top of
               | Hyper yourself, but it looks like a lot of the problems
               | that I've encountered with Rocket are being replicated
               | with Axum. There's a very good chance we -will- move our
               | services to Axum, I'm just not confident that this is
               | really stable ground.
               | 
               | As for the specific example, I think you're missing the
               | forest through the trees. I used that specific example
               | because it's in the article and it's in Axum's readme, so
               | it's safe to assume that people discussing the article
               | would be familiar with that case.
        
         | Kinrany wrote:
         | A perfect API would consist of a few parts:
         | 
         | 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.
        
           | zemo wrote:
           | 1, 3, and 4 are already there, they're just a part of Hyper,
           | not Axum/Warp/Rocket. 2 is basically the thing that
           | Axum/Warp/Rocket provide. Hyper is kinda like net/http and
           | Axum/Warp/Rocket are more feature rich. The thing is, they're
           | super early. They don't remind me of the pared-down quality
           | simplicity of the Gorilla Toolkit or even the feature
           | paradise of Gin.
           | 
           | Honestly it a lot of the Rust http frameworks strike me as
           | eerily similar to either Falcore or Revel. Falcore was a very
           | early Go http application framework built by ngmoco, a now-
           | defunct game company. Falcore didn't really gain a lot of
           | traction, partially because it provided abstractions that
           | weren't very ergonomic. It's whole thing was that the core
           | abstraction was a modular pipeline.
           | https://github.com/ngmoco/falcore
           | 
           | I think most people know Revel, it's a little less obscure.
           | It's philosophically the precursor to Gin.
        
             | Kinrany wrote:
             | The frameworks provide 2 by hiding 1. This makes it
             | impossible to use the request and response types for other
             | purposes.
        
           | xena wrote:
           | Fun fact: you just described Go's net/http package.
        
             | metadaemon wrote:
             | It's also pretty much Express.js
        
             | zemo wrote:
             | not really, point 2 isn't in the standard library.
        
               | xena wrote:
               | https://pkg.go.dev/net/http?utm_source=godoc#ReadRequest
        
               | zemo wrote:
               | that's not what the Axum example does. The function
               | you're linking turns an opaque run of bytes into an http
               | request object. What the Axum example does is turn an
               | http request object into a value of some other type T.
        
           | masklinn wrote:
           | That's essentially what happens in Rust, but it's a layer of
           | crates.
        
         | nerdponx wrote:
         | 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".
        
           | masklinn wrote:
           | > You trade off flexibility for having a very clean and
           | simple "happy path".
           | 
           | You don't trade anything though, if you want the raw
           | information you can just ask for that.
        
         | elysian-breeze wrote:
         | 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())
         | }             }         }
        
           | zemo wrote:
           | > 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)
           | 
           | Any server that has clients that aren't fully under your
           | control that you can't force-update, where the clients today
           | and the clients yesterday encode their requests differently.
           | 
           | I mean, it's the entire purpose of the `Content-Type` header.
           | The whole concept is that the server has a set of encodings
           | that it can understand, and the client can pick between them.
           | 
           | This isn't a new idea. Here it is in RFC 2068, from 1997:
           | https://www.rfc-editor.org/rfc/rfc2068#page-116
           | 
           | These concepts have existed for decades.
           | 
           | In your comment, you make an implicit assumption: the
           | assumption is that the person that writes the server is in
           | control of the client. When we make tools that make it easier
           | to construct software that assumes the server operator is in
           | control of the client but do nothing to make it easier to
           | build software in which the server operator is _not_ in
           | control of the client, we are making a political choice to
           | place power in the hands of server operators at the expense
           | of end users. That 's not a political choice that I'm
           | comfortable with.
        
         | jamincan wrote:
         | 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.
        
           | zemo wrote:
           | https://news.ycombinator.com/item?id=33721070
           | 
           | same line of reasoning as here
        
       | jgbyrne wrote:
       | 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.
       | 
       | [1] https://github.com/jgbyrne/plainchant
        
       ___________________________________________________________________
       (page generated 2022-11-23 23:01 UTC)