6 ms·
A small reivew of the Poem Rust web framework
- rendaw 4y agoAfter wasting a couple days trying to wrangle Axum, Hyper, Rocket and a couple others I tried Poem and it solves every problem I had with the other frameworks. Waiting for the other shoe to drop. 1. The response object type isn't generic (as per project goals). In other frameworks, if I had branches returning different response types the types would conflict - no such issue here. 2. The cookie middleware is easy to use. In axum you had to return the cookie jar from your handler and trust the response tuple magic to do something with it to set the cookie in the response. I wasn't using tuples though, and due to the above it was non-obvious (and awkward) how to set the cookie in the response using response objects. 3. When you enable TLS via rustls it automatically makes non-default config changes to negotiate http2 (this is boilerplate in other frameworks). 4. Endpoint handlers are non-closures, and state must be shared via extensions/middleware (not captures). Rust closures are a hornet's nest so I'm plenty happy just being able to avoid them, but I also like that there's a single way to do things (and that way has good DX). In other frameworks you need to use async closures in order to get the compiler to intuit the future type since they're complex and you'd have to define it manually with a non-closure. 5. TLS configs are a stream to handle live cert refreshes. It feels like a batteries included/one size fits all solution, but it's very well integrated and there are a lot of batteries. It's pretty locked down, where you can't easily tweak the internals. I haven't hit limitations from that yet, despite having some somewhat weird use cases. There are also a lot of examples in the repo. I was fighting with other frameworks for the last 3 days (a good 20+ hours), and when I hit another future-handler-generic-incompatibility error I threw my hands up and tried Poem, and got my code compiling in 30m.
- pdimitar 4y agoPretty useful to know, thanks for sharing. <3 Shame you didn't try ActixWeb. That's the one I'd start with -- I heard a lot of praise for it but haven't tried it yet. I'll make sure to check Poem next time I want to write a web service in Rust, thanks to you.
- capableweb 4y ago> Shame you didn't try ActixWeb. That's the one I'd start with -- I heard a lot of praise for it but haven't tried it yet. actix_web is what I go for as well, really simple to setup and haven't hit any major blockers for as long as I've been using it (~7 months, 3-4 projects)
- electromech 4y ago> The cookie middleware is easy to use. In axum... Been there, fought that! By the end, I had thoroughly abused the Axum API to get it working.
- electromech 4y agoAh shoot, poem's websocket implementation uses tokio_tungstenite like most of the other hyper-adjacent Rust web frameworks. It's unfortunate that library has become the default for websockets because the performance is rather lacking. (new allocations for each incoming message IIRC)
- paulgb 4y agoIs there a good alternative in the tokio world currently? Actix-web does WebSockets but it expects to run on an actix executor AFAIK.
- electromech 4y agosoketto has a hyper example. I haven't found a websocket benchmark that compares them, but you can see here that soketto let's you re-use a buffer: https://docs.rs/soketto/latest/src/soketto/connection.rs.html#335 https://docs.rs/soketto/latest/src/soketto/connection.rs.htm... It's on my to-do list to try soketto, but the thought of benchmarking websocket implementations sounds tedious.
- nextaccountic 4y agoWhat's your preferred websocket implementation?
- electromech 4y ago(copy-pasting what I said in sibling comment) soketto has a hyper example. I haven't found a websocket benchmark that compares them, but you can see here that soketto let's you re-use a buffer: https://docs.rs/soketto/latest/src/soketto/connection.rs.html#335 https://docs.rs/soketto/latest/src/soketto/connection.rs.htm... It's on my to-do list to try soketto, but the thought of benchmarking websocket implementations sounds tedious.
- 0x457 4y agoCan't talk about hyper and rocket, but I use Axum extensively. 1. This one is annoying, but easily solvable by making your own return type that implements IntoResponse 2. I'm not sure what you're talking about? You just add `Cookies` extractor to your handler and use that. You don't need to add it to the return, middleware takes care of it. Middleware in axum split into two: Tower middleware and Axum middleware. Tower middleware is...hard to write, but it's faster, axum middleware is easier to write, but it's slower. 4. axum doesn't force using closures, and state is shared via extensions? I'm currently working on a service where I have tonic (gRPC) and axum on the same port, and they share a lot of middleware.
- davidpdrsn 4y ago> This one is annoying, but easily solvable by making your own return type that implements IntoResponse You can just call `.into_response()` like documented here https://docs.rs/axum/latest/axum/response/index.html#returning-different-response-types https://docs.rs/axum/latest/axum/response/index.html#returni... > I'm not sure what you're talking about? You just add `Cookies` extractor to your handler and use that. You don't need to add it to the return, middleware takes care of it. I think you're thinking of tower-cookies which works the same way poem's CookieJarManager does. axum-extra's cookies work differently in that you have to return the updated jar from handlers. We chose this design because it composes better when you have multiple libraries that all wanna set cookies. tower-cookies doesn't work if you accidentally add multiple cookie middleware, whereas axum-extra's approach does. So its a trade-off.
- 0x457 4y ago> I think you're thinking of tower-cookies which works the same way poem's CookieJarManager does. Yes, that's what I'm using. > You can just call `.into_response()` like documented here https://docs.rs/axum/latest/axum/response/index.html#returni https://docs.rs/axum/latest/axum/response/index.html#returni... Oh yeah, totally forgot about that one.
- rendaw 4y agoI was trying to return `Response` directly, which reading the above one would assume is the end-all be-all of `IntoResponse`. But `Response` is generic and you need to harmonize Body types across branches, etc... Anyways implementing a custom `IntoResponse` shouldn't be necessary in the first 30m of using a framework (not saying I didn't just miss something, but I've read the docs multiple times).
- the__alchemist 4y agoDoes anyone else think generics detract from the usefulness of Rust docs? I often hit walls on learning libs when trying to figure out what type to use when the API uses a generic. A non-generic flow might be like this: fn accepts param y. Param y is of type z. Docs for z shows it constructed with z::new(), or z { (fields }. When it's a generic instead, there's no link to click, and you're on your own to figure it out, possibly by browsing source code. This is exacerbated by the same libs using minimal examples that don't show the code used in a function, struct etc where explicit types are required.
- Waterluvian 4y agoI find that rust docs are entirely unhelpful for learning a library if they don’t come with plenty of realistic examples. I find the docs are just good for reference while you already generally know what you’re doing. Perhaps this is universally true of any language.
- jamesfinlayson 4y agoI started a Rust project last year and found the documentation pretty unhelpful for a beginner. I think Java is one that (depending on the part of the standard library) can lack any decent examples. Python and PHP's documentation generally seem to have good examples though (at least in my experience).
- rendaw 4y agoYeah, I feel like learning the conventions is 90% of the battle especially with Rust. Otherwise you're playing a puzzle trying to figure out which combination of traits and objects will solve all the constraints.
- bryanlarsen 4y agoPoem has a ton of excellent examples.
- the__alchemist 4y ago
- rendaw 4y agoJust to be clear, I didn't mean to be critical of Axum here. I wanted a simple web server I could get going quickly with, and I'm not (overly) concerned with maximum performance or flexibility, etc. I think this may differentiate my use case with the typical Axum use case.
- brundolf 4y agoIs the URL wrong? This says "a small review of" but it just links to Poem's GitHub repo
- homarp 4y agoAnother comment is the review: https://news.ycombinator.com/item?id=32800797 https://news.ycombinator.com/item?id=32800797
- rendaw 4y agoYeah, I intended to post this as a text submission and link poem, but the submission form says you can add both text + a url so I did that and this is how it ended up. In retrospect maybe I should have just added the link to the text body.
- the__alchemist 4y ago"unsafe forbidden" (GH tag) Is this practical for web programming? In the domains I'm used to (embedded and graphics programming), this isn't feasible. I recall some Actix drama re `unsafe` that this tag presumably alludes to. Perhaps web programming is abstract enough where this is a reasonable goal (?) Unrelated: What have y'all been using these minimal Rust web frameworks for? I've found them too sparse for websites, eg missing automatic DB migrations, auth, admin, email etc. On the plus side, Serde and Chrono are both things that apply to web programming and are (IMO) best-in-class. > Minimizing the use of generics. From the readme. Love this!
- 0x457 4y agoMost frameworks in rust a like Sinatra and Flask. There aren't any Rails level frameworks. I don't think there are any Rails level frameworks besides Django and a few JVM frameworks. Rust web frameworks just give you tools to get from request to response, the rest is up to you. I think that is fine, yeah it leads to some boilerplate to wire up a few libraries together, but you did it once, and you're done.
- theironhammer 4y agoThere's this: https://rust-on-nails.com https://rust-on-nails.com
- 0x457 4y agoYes, anyone can call `X on Rails`, but look at the first example here: https://rust-on-nails.com/docs/full-stack-web/web-server/ https://rust-on-nails.com/docs/full-stack-web/web-server/ Does it look like anything close to Ruby on Rails in terms of tooling? No, because Rust on Nails doesn't provide any tools at all. It literally doesn't take care of anything, but provides some (questionable) decisions made for you. You know what I've never done in RoR? This: use std::env; use std::str::FromStr; #[derive(Clone, Debug)] pub struct Config { pub database_url: String, } impl Config { pub fn new() -> Config { let database_url = env::var("DATABASE_URL").expect("DATABASE_URL not set"); Config { database_url, } } pub fn create_pool(&self) -> deadpool_postgres::Pool { let config = tokio_postgres::Config::from_str(&self.database_url).unwrap(); let manager = if self.database_url.contains("sslmode=require") { let mut root_store = rustls::RootCertStore::empty(); root_store.add_server_trust_anchors( webpki_roots::TLS_SERVER_ROOTS .0 .iter() .map(|ta| { rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( ta.subject, ta.spki, ta.name_constraints, ) }) ); let tls_config = rustls::ClientConfig::builder() .with_safe_defaults() .with_root_certificates(root_store) .with_no_client_auth(); let tls = tokio_postgres_rustls::MakeRustlsConnect::new(tls_config); deadpool_postgres::Manager::new(config, tls) } else { deadpool_postgres::Manager::new(config, tokio_postgres::NoTls) }; deadpool_postgres::Pool::builder(manager).build().unwrap() } }
- bryanlarsen 4y agoThe author of poem is not a native English speaker so the documentation is not extensive. However, the examples in the repo are superb, so treat those as the primary documentation. He's also responsive to GitHub issues. I love the native openapi integration. Its also convenient to exercise your API ad hoc. Two thumbs up from me.