0.17.0-beta.1
A testing release for a new router. See https://github.com/http-rs/tide/pull/802
v0.16.0
tide is a pragmatic Rust web app framework built for rapid development. It comes with a robust set of features that make building async web apps and APIs easier and more fun. It is part of the http-rs project and a counterpart to the surf HTTP client. Check out the docs or join us on Zulip.
This release includes a new serve_file method on Route, more ToListener implementations, and new initiatives.
Tide has had support for Route::serve_dir for several releases now. However sometimes it's nice to be able to quickly serve a file from a single file from a route. For that purpose we're introducing Route::serve_file today.
#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
let mut app = tide::new();
app.at("/").serve_file("public/index.html")?;
app.listen("127.0.0.1:8080").await?;
Ok(())
}
This isn't the end of the road for asset serving in tide though; we recognize the need to support more complex asset pipelines in production settings. We expect to eventually kick off an initiative around this; but in the meantime Route::serve_file is a nice convenience for those who want it.
Tide's Server::listen function operates using a ToListener trait, which works much like std::net::ToSocketAddr: it's implemented on a variety of strings, tuples, and concrete types to provide a great deal of flexibility in how it's initialized.
In this patch we're introducing ToListener on three more types: &String, (String, u16) and (&String, u16). This allows for much easier integration with CLI parsers such as structopt, requiring fewer conversions:
use structopt::StructOpt;
#[derive(structopt::StructOpt)]
struct Opts {
host: String,
port: u16,
}
#[async_std]
async fn main() -> tide::Result<()> {
let opts = Opts::from_args();
let mut app = tide::new();
app.listen((opts.host, opts.port)).await?; // this now works without conversions!
Ok(())
Last week we released the last minor version of the http-types 2.x family, and the merge window for http-types 3.x has opened up. This is an effort which will take several weeks of work and planning to see through to success; but we're excited being able to smooth out some of the rough patches we've uncovered in http-types over the past year. The work here has been scoped out, and we've started to make good progress on it.
Once that's over however, it is probably time to revisit the question of a Tide 1.0 release. Over the past year Tide's core design has mostly remained stable; but added plenty of features improvements. However we're noticing two things:
- Not all facilities included in Tide are quite where we'd like them to be.
- Tide being on 0.x and publishing regular breaking releases makes it hard to build ecosystem libraries
For that purpose we're likely to split some parts of Tide we're less happy about into libraries (looking at you tide::log), and spend some time iterating them outside of the release cycle of Tide itself. We hope this will enable more people to experiment in the ecosystem, mature features faster, and enable more people to contribute to Tide and the wider http-rs ecosystem.
- Add
serve_filemethod to route #725 - Add more
ToListenerimplementations #749 - Add simple state example #742
- docs: add tide-sqlx to readme #754
- In
serve_dir, only strip the prefix from the start of the path once. #734 - docs: Do not inline docs from deps that drift #753
- docs: Fix port in
curlinvocation in example. #764 - docs: Update version numbers of deps for README example #765
- docs: Add missing
serderequirement in example #782 - docs: Fix
Route::serve_dirdocs #750
- deps: Reduce dependencies when cookies not enabled #780
- deps: Update async-h1 to v2.3.0 #767
- deps: Update log to use kv_unstable_std instead of std #776
- ci: Run clippy, docs and fmt checks on stable #756
v0.15.0
This patch adds Server::bind, SessionMiddleware::with_cookie_domain, and a new optional cookies feature.
Tide v0.15.0 introduces a new way to start servers: Server::bind. This enables separatining "open the socket" from "start accepting connections" which Server::listen does for you in a single call.
This was introduced as a way to enable users to log messages after ports were successfully opened. But it can also be used to synchronize server initialization. For example: your application may want to connect to a database, a cache, and open an HTTP connection. With Server::bind you can start the connection, but wait to handle inbound traffic until all other components of the server have started up.
When Server::bind is called, it returns an instance of Listener which is able to return information on all ports that are being listened on. By default Server::listen logs these out, but when manually calling Server::bind you get control on how to log this info.
For now ListenInfo only includes a few basics such as the address that's being listened on, and whether the connection is encrypted. But as we seek to stabilize and integrate tide-rustls into tide, we may include more info on the encryption settings. And perhaps in the future we'll include more information on the server's routes too. But for now this serves as an entry point for all that.
use tide::prelude::*;
let mut app = tide::new();
app.at("/").get(|_| async { Ok("Hello, world!") });
let mut listener = app.bind("127.0.0.1:8080").await?;
for info in listener.info().iter() {
println!("Server listening on {}", info);
}
listener.accept().await?;
Our session middleware now supports a with_cookie_domain method to scope a cookie to a specific domain. We already support various cookie options when constructing the session middleware, and now we support scoping the domain as well.
let SECRET = b"please do not hardcode your secret";
let mut app = tide::new();
app.with(SessionMiddleware::new(MemoryStore::new(), SECRET)
.with_cookie_name("custom.cookie.name")
.with_cookie_path("/some/path")
.with_cookie_domain("www.rust-lang.org") // This is new.
.with_same_site_policy(SameSite::Lax)
.with_session_ttl(Some(Duration::from_secs(1)))
.without_save_unchanged(),
);
We've been doing a lot of work on typed headers through http-types, which is the HTTP library underpinning both tide and surf. We're getting close to being done implementing all of the specced HTTP Headers, and will then move to integrate them more closely into Tide. You can find the release notes for http-types here.
- Add
Server::bind#740 - Add
with_cookie_domainmethod toSessionMiddleware#730 - Add an optional cookies feature #717
- Fix param documentation #737
- Fix port in README #732
- Lints #704
v0.14.0
This patch introduces a several feature flags to opt-out of dependencies, a reworked rustdoc landing page, and a variety of bug fixes. Over the past few months we've been hard at work on Surf v2.0.0, and have made a lot of progress on http-types' typed headers. We hope to start bringing some of this work over into Tide soon.
Behind the scenes we're also hard at work at improving our processes. Both Tide and the wider http-rs project have really taken off, and our biggest challenge in ensuring we correctly prioritize, communicate, and empower people who want to get involved. We don't have specifics we can share yet, but it's something we're actively working on with the team. Because as Tide and http-rs grow, so must our processes.
- Implement
EndpointforBox<dyn Endpoint>#710 - Add a
http_client::HttpClientimplementation fortide::Server#697 - Introduce a
loggerfeature to optionally disable thefemmedependency #693 - Add
Server::statemethod #675
- Remove parsing from
Request::param#709 - Rework landing docs #708
- Log: display client error messages when possible as warnings #691
- Make all listeners conditional on
h1-serverfeature #678
- Logger: properly print debug from errors #721
- Fix missing as_ref that caused boxed endpoints to enter an infinite loop #711
- Bugfix, route prefix was always set to false after calling nest #702
- Fix a broken documentation link #685
- Upgrade deps #722
- CI, src: run clippy on nightly, apply fixes #707
- Update to latest Surf alpha in tests #706
- Fix .github #705
- Add driftwood to middleware section #692
- Refactor README #683
- Main branch renamed to
main#679 - Bump version number in README.md #672
- Add community resources to readme instead of wiki #668
v0.13.0
This release introduces first-class support for sessions, fixes a long-standing bug with our default middleware, clarifies our stability guarantees, and renamed the API to register middleware through.
We're excited to announce initial support for sessions in Tide. This feature enables Tide applications to associate multiple separate requests as belonging to the same origin. Which is a pre-requisite to build common web-app features such as user accounts, multi-request transactions, and channels.
Tide sessions are generic over backend stores and signing strategy. It builds on the newly released async-session 2.0.0 library, which is a set of common traits that types that make up a session. But beyond that, much of it is implementation specific.
Tide ships with a memory and cookie store by default. However we have also published several convenient session store implementations for common databases, providing a nice selection to choose from:
- Memory Session (shipped with Tide)
- Cookie Session (shipped with Tide)
- async-sqlx-session (SQLite only for now; we hope to support more)
- async-redis-session
- async-mongodb-session
Using "Redis" as the backing session store for Tide is as easy as writing 3 lines and including a dependency in your Cargo.toml:
use async_redis_session::RedisSessionStore;
use tide::sessions::SessionMiddleware;
use tide::{Redirect, Request};
#[async_std::main]
async fn main() -> tide::Result<()> {
let mut app = tide::new();
// Create a Redis-backed session store and use it in the app
let store = RedisSessionStore::new("redis://127.0.0.1:6379")?;
let secret = std::env::var("SESSION_SECRET").unwrap();
app.with(SessionMiddleware::new(store, secret.as_bytes()));
app.at("/").get(|mut req: Request<()>| async move {
// Store a counter in the session; increment it by one on each visit
let session = req.session_mut();
let visits: usize = session.get("visits").unwrap_or_default();
session.insert("visits", visits + 1).unwrap();
// Render a page that shows the number of requests made in the session
let visits: usize = req.session().get("visits").unwrap();
Ok(format!("you have visited this website {} times", visits))
});
// Close the current session
app.at("/reset").get(|mut req: Request<()>| async move {
req.session_mut().destroy();
Ok(Redirect::new("/"))
});
// Start the server
app.listen("127.0.0.1:8080").await?;
Ok(())
}
It's still early for Tide sessions. But we're incredibly excited for how convenient it already is, and excited for the possibilities this will enable!
This patch renames Server::middleware to Server::with in order to streamline much of the middleware APIs.
// After this patch
let mut app = tide::new();
app.with(MyMiddleware::new());
app.at("/").get(|_| async move { Ok("hello chashu") });
app.listen("localhost:8080").await?;
// Before this patch
let mut app = tide::new();
app.middleware(MyMiddleware::new());
app.at("/").get(|_| async move { Ok("hello chashu") });
app.listen("localhost:8080").await?;
A small change, but ever so convenient.
Ever since we introduced application nesting we've had issues with default middleware running twice. This patch fixes that for our logging middleware in two ways:
- We've introduced a
loggerCargo feature to disable the default logging middleware #661 - We now track calls to the logger inside the state map to ensure it's only called once per app #662
There may be room to optimize this further in the future, and perhaps extend the same mechanisms to work for more built-in middleware. But for now this patch resolves the most common issues people were reporting.
Tide has been deployed to production in many places: independent authors taking control of their publishing pipelines, software professionals building internal tooling, and enterprises running it in key parts of their infrastructure.
In past Tide releases shipped with a warning that actively recommended against using it in any critical path. However we've chosen to no longer include that warning starting this release. Much of the work at the protocol layer and below has completed, and we've received positive reports on how it performs.
For the foreseable future Tide will remain on the 0.x.x semver range. While we're confident in our foundations, we want to keep iterating on the API. Once we find that work has slowed down we may decide when to release a 1.0.0 release.
- Added
From<StatusCode> for Response#650 - Added a feature-flag to disable the default logger middleware #661
- Added
impl Into<Request> for http_types::Request#670
- Rename
Server::middlewaretoServer::with#666 - Relax Sync bound on Fut required on sse::upgrade #647
- Consistency improvements for examples #651
- Bumped version number in docs #652
- Add enable logging in README example #657
- Remove "experimental" warning #667
- Guarantee the log middleware is only run once per request #662
- Enable clippy for tests #655
- Reorder deps in cargo.toml #658
v0.12.0
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.
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!
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?;
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.
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.
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.
- 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
- Use
async_traitto 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::runnow returnsResponseinstead ofResult<Response>#570 - Rename
tide::middlewaretotide::utils#567 - Require
Stateto beClone#644
- Make
ServeDirreturn 404 if file does not exists #637 - Remove
#[must_use]forResponse::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_headercalls #590 #588 #583 - Don't crash the server when there's a listen error #587
- Add CORS headers to error responses #546
- Remove
executablemode 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
v0.11.0
This patch introduces several minor features and fixes. This is a small release which picks up on some of the details we missed in our last few large releases.
- Added
Request::set_body#579 - Added
impl From<Body> for Response#584
Response::set_statusno longer takes and returnsself#572
- Fixed an issue with
unwrapin SSE https://github.com/http-rs/tide/pull/588 - Fixed an
unwrapissue in Endpoint https://github.com/http-rs/tide/pull/590
- Delete the unmaintained CHANGELOG.md file #565
- Renamed cx to req #582
- Fix failing dependency on CI #589
- Use tide::Response interface in the sse endpoint #583
- Remove unnecessary
moves from route examples #581
v0.10.0
This release updates tide's Request and Response types to match http_types's Request and Response, a new Server::listen_unix method to listen on Unix Domain Sockets, and the ability to return json! literals directly from endpoints.
- Added
Server::listen_unix#531 - Added
Request::peer_addr#530 - Added
Request::local_addr#530 - Added
Request::remote#537 - Added
Request::host#537 - Added
Request::header_mut#537 - Added
Request::append_header#537 - Added
Request::remove_header#537 - Added
Request::iter#537 - Added
Request::iter_mut#537 - Added
Request::header_names#537 - Added
Request::header_values#537 - Added
Request::query#537 - Added
Request::content_type#537 - Added warning about domain/path inconsistencies to
remove_cookie#533 - Added an optional
namemethod toMiddleware#545 - Added
AsRef/AsMut<Headers>forRequest/Response#553 - Added
Request::take_body#550 - Added
Response::swap_body#562 - Added
Response::iter#550 - Added
Response::iter_mut#550 - Added
Response::header_names#550 - Added
Response::header_values#550 - Added
Response::content_type#550 - Added
Response::set_content_type#550 - Added
Response::header_mut#562 - Added
tide::{After, Before}middleware constructors #556 - Added support for returning JSON literals from endpoints #523
Response::newnow acceptsu16as well asStatusCodeas arguments #562- Added a new
convertsubmodule which holds various conversion-related types, includingserde#564
- Renamed
Request::uritoRequest::url#537 Request::body_bytesnow returnstide::Result#537Request::body_stringnow returnstide::Result#537Request::body_jsonnow returnstide::Result#537Request::body_formnow returnstide::Result#537Request::set_extno longer takes and returnsSelf#537- Use
http_types::mimeinstead of themimecrate #536 - - Renamed
Reponse::set_cookietoResponse::insert_cookie#562 - Various
Responsemethods no longer returnSelf#562 - Renamed
Response::set_headertoResponse::insert_header#562 - Renamed
Reponse::set_exttoResponse::insert_ext#562
- Removed
Response::redirectin favor oftide::Redirect#562 - Removed
Response::{body_string, body_form, body_json, body}in favor ofResponse::set_body#562
- Update docs from
Request::localtoRequest::ext#539 - Creating a middleware directly from a function is now possible again #545
- Fixed wildcard tests #518
- Fix unix tests #552
- Update
http-typesto 2.0.1 #537 - Update
async-sseto v3.0.0 #559 - Use query from http_types #555
- Fix tests on master #560
- Remove unused serde_qs dependency #569
- Uncomment and fix response tests #516
v0.9.0
This patch updates http-types to 2.0.0, removes http-service in favor of Server::respond, and adds an all-new Redirect struct.
Read the http-types changelog for a full rundown of changes. But the biggest change for Tide is that working with headers in Tide is becoming a lot easier. To see all the changes in action, compare what it was like to compare a header with how it is now:
// http-types 1.x
assert_eq!(req.header(&"X-Forwarded-For".parse().unwrap()), Some(&vec!["127.0.0.1".parse().unwrap()]));
// http-types 2.x
assert_eq!(req.header["X-Forwarded-For"], "127.0.0.1");
Constructing headers from string literals, comparing to string literals, using [] to access by name — this should make it much easier to work with Tide!
http-service has been a tricky one. It originally served as a simplified wrapper around hyperium/hyper with a streaming Body type with the potential to abstract to other backends as well. But as we've evolved Tide and the http-rs ecosystem, we found http-service becoming more a hurdle than a help.
In this patch we're removing http-service from Tide and introducing Server::respond as its replacement. This not only makes Tide easier to maintain, it makes it easier to use with any other backend than ever before. It also provides convenient way to unit test Tide apps as well:
use tide::http::{self, Url, Method};
#[async_std::test]
async fn hello_world() -> tide::Result<()> {
let mut app = tide::new();
app.at("/").get(|_| async move { Ok("hello world")} );
let req = http::Request::new(Method::Get, Url::parse("http://computer.soup")?);
let mut res: http::Response = app.respond(req).await?;
assert_eq!(res.body_string().await?, "hello world".to_owned());
Ok(())
}
In the past we introduced the redirect submodule which provided redirect endpoints. And Response::redirect* which provided methods to create redirects inside endpoints. In this patch we're removing those APIs in favor of tide::Redirect which can be used both to create new redirect endpoint, and be used inside existing endpoints to redirect:
use tide::Redirect;
#[async_std::main]
async fn main() -> tide::Result<()> {
let mut app = tide::new();
app.at("/fish").get(|_| async move { Ok("yum") });
// Either create a redirect endpoint directly.
app.at("/chashu").get(Redirect::new("/fish"));
// Or redirect from inside an existing endpoint
// enabling conditional redirects.
app.at("/nori").get(|_| async move { Redirect::new("/fish") });
app.listen("127.0.0.1:8080").await?;
Ok(())
}
Big thanks to @ethanboxx for introducing this pattern to Tide. We'll be looking to introduce this pattern to more APIs in the future.
- Added
Response::take_bodyhttps://github.com/http-rs/tide/pull/499 - Added
Response::remove_headerhttps://github.com/http-rs/tide/pull/508 - Added
Server::respondhttps://github.com/http-rs/tide/pull/503 - Added @tirr-c as a co-author in Cargo.toml https://github.com/http-rs/tide/pull/507
- Added
Response::from_reshttps://github.com/http-rs/tide/pull/466 - Added
AsRef/AsMutimpls to bridge Tide andhttp-types's req/res types https://github.com/http-rs/tide/pull/510 - Added
Into<http_types::Request>forRequesthttps://github.com/http-rs/tide/pull/510 - Added
Response::headerhttps://github.com/http-rs/tide/pull/515 - Added
tide::log::startwhich starts a logger that pretty-prints in development and prints ndjson in release mode https://github.com/http-rs/tide/pull/495
- Removed
http-servicein favor ofServer::respondhttps://github.com/http-rs/tide/pull/503 - Removed the
redirectsdirectory in favor oftide::Redirecthttps://github.com/http-rs/tide/pull/513
- Unified all redirect functionality into a single
Redirecttype https://github.com/http-rs/tide/pull/513 - The log middleware now logs time more accurately https://github.com/http-rs/tide/pull/517
- Apply clippy https://github.com/http-rs/tide/pull/526
- Bump version number in README https://github.com/http-rs/tide/pull/489
- Changed file structure to match external exports https://github.com/http-rs/tide/pull/502
- Remove unused dependencies https://github.com/http-rs/tide/pull/506
- Added a no-dev-deps check to CI https://github.com/http-rs/tide/pull/512
- Fibonnacci example now uses
Instant::elapsedhttps://github.com/http-rs/tide/pull/522