Tide v0.12.0 Release Notes

Release Date: 2020-07-17 // almost 4 years ago
  • ๐Ÿ“„ Docs

    ๐Ÿš€ This release includes 35 PRs merged over the course of the last month. Most
    notably we've streamlined how errors are propagated inside middleware,
    ๐Ÿ‘ฏ introduced a new ResponseBuilder type, State must now be Clone, and we
    are introducing an extensible API for Server::listen.

    ResponseBuilder

    Returning responses from endpoints is often different from operating on
    response in middleware. In order to make it easier for people to author
    responses we're introducing tide::ResponseBuilder in this patch!

    ๐Ÿ— You can create a new response builder by calling Response::builder and
    passing it a status code. This enables writing some really concise endpoints:

    app.at("/").get(|\_| async { let res = Response::builder(203) .body(json!({ "hello": "cats!" })) .header("X-Nori", "me-ow") .header("X-Chashu", "meewwww"); Ok(res) })
    

    This sets Tide up really nicely for the future too; once we have async
    closures, and a resolution for Ok-wrapping (fingers crossed) this will be
    even more concise. We're excited for developments in the language!

    Server listen

    ๐Ÿ‘ Tide now supports extensions for
    App::listen. This patch introduces a new Listener trait that is
    implemented for std types such as TcpStream, SocketAddr and
    UnixStream. But can also be implemented by users of Tide to provide custom
    transports.

    ๐Ÿ‘ In particular, what this enables us to do is to start trialing TLS support in
    external crates. We'll soon have
    tide-rustls available as an external
    ๐Ÿ— crate that will enable building TLS-terminating Tide servers:

    let mut app = tide::new();let listener = TlsListener::build() .addrs("localhost:4433") .cert(cert) .key(key); app.listen(listener).await?;
    

    In addition we're shipping tide::listener::ConcurrentListener, a convenient
    constructor to have a single server respond to incoming requests from
    multiple transports. For example, some applications may want to listen on
    both IPv4 and IPv6. With ConcurrentListener that's possible:

    use std::net::{Ipv4Addr, Ipv6Addr};use tide::listener;let mut app = tide::new();let mut listener = listener::ConcurrentListener::new(); listener.add((Ipv4Addr::new(127, 0, 0, 1), 8000)); listener.add((Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 8000)); app.listen(listener).await?;
    

    ๐Ÿ‘ฏ State must be Clone

    One problem we had for a while was that when manually nesting or
    parallelizing applications, State would be wrapped in an Arc multiple
    times. In this patch we're solving that by providing people with more control
    ๐Ÿ‘ฏ around how State is shared by requiring State to implement Clone.

    ๐Ÿ‘ฏ In most existing applications State can be made Clone by manually
    wrapping it in an Arc::new. But it's also possible to wrap individual
    ๐Ÿ‘ฏ fields in an Arc and deriving `Clone for the whole struct, as we did in one
    of our examples:

    // Example state before this patch.struct State { users: RwLock\<Vec\<User\>\>, }// Example state after this patch.#[derive(Clone)]struct State { users: Arc\<RwLock\<Vec\<User\>\>\>, }
    

    There is no right answer how to structure State; but we wanted to enable
    people to factor it in the way that makes most sense for their applications.

    Using of async-trait

    We've migrated all of our traits to use async-trait. This should make it easier to author Middleware implementations. For convenience Tide re-exports as tide::utils::async_trait.

    ๐Ÿ”„ Changes in middleware error handling

    Before this patch, calling next().await? in middleware would return a
    Result<Response>. The idea was that the Err variant could freely be
    thrown up the middleware stack, and transformed into a Response at the top
    of the stack. However in practice this didn't turn out great: middleware such
    as CORS needed to manipulate the Err path to ensure the right headers were
    set. And we didn't provide an interface for this.

    So instead this patch changes the way we handle errors in middleware. We
    still enable ? to be used inside middleware, but between each middleware we
    convert Result<Response, tide::Error> into a Response, and if an error
    occurred, we populate the newly introduced Response::error field.

    This means that middleware can always assume there is a valid Response
    coming through, and no longer needs to check both Ok and Err branch
    returned by next().await. An example:

    /// Before this patch: need to check both branches.async fn my\_middleware\<State\>(req: Request\<State\>, next: Next) -\> Result\<Response\> { println!("before"); match next().await { Err(err) =\> { println!("status code {}", err.status()); Err(err) } Ok(res) =\> { println!("status code {}", res.status()); Ok(res) } } }/// With this patch: there's only a single branch to operate on.async fn my\_middleware\<State\>(req: Request\<State\>, next: Next) -\> Result\<Response\> { println!("before"); let res = next().await; println!("status code {}", res.status()); Ok(res) }
    

    Note: neither of these examples will quite compile until we have async
    closures, but it serves to illustrate the point.

    โž• Added

    • โž• Add a doc example for Request::body_form #631
    • โž• Add a doc example for Request::query #630
    • โž• Add an upload example #619
    • โž• Add extensible entrypoint for Server::listen #610
    • โž• Add From<Body> for Response #584
    • โž• Add ResponseBuilder #580
    • โž• Add Response::error #570
    • โž• Add CORS headers to error responses #546

    ๐Ÿ”„ Changed

    • ๐Ÿ‘‰ Use async_trait to simplify async signatures #639
    • ๐ŸŒฒ Also include port and host in the log string #634
    • Don't panic on missing path param #615
    • Return a result from sse::Sender::send #598
    • ๐Ÿ˜Œ Relax the lifetime requirements of Server::at #597
    • In middleware Next::run now returns Response instead of Result<Response> #570
    • ๐Ÿ“‡ Rename tide::middleware to tide::utils #567
    • ๐Ÿ‘ฏ Require State to be Clone #644

    ๐Ÿ›  Fixed

    • ๐Ÿ‘‰ Make ServeDir return 404 if file does not exists #637
    • ๐Ÿ‘‰ Remove #[must_use] for Response::set_status() #612
    • Do not await the spawned task in Server::listen #606
    • ๐Ÿ‘ Allow route based function middlewares #600
    • ๐Ÿ›  Fix CORS middleware to retain cookies #599
    • Enable SSE senders to break out of loops #598
    • โœ‚ Remove extra unwraps from insert_header calls #590 #588 #583
    • Don't crash the server when there's a listen error #587
    • โž• Add CORS headers to error responses #546

    Internal

    • โœ‚ Remove executable mode from lib.rs #635
    • โšก๏ธ Update to async-sse 4.0.0 #632
    • ๐Ÿ›  Comment cleanup fixes #622
    • โž• Add clippy to ci #618
    • โช Restore .github docs #616
    • ๐Ÿ‘‰ Use route-recognizer 0.2 #607
    • โœ… Introduce an extension trait for testing servers #601
    • โšก๏ธ Update the readme with the latest versions #594
    • ๐Ÿ›  Fix tempfile usage in tests #592
    • ๐Ÿ›  Fix CI #589
    • โœ‚ Remove unnecessary moves from route handlers #581