http-rs / tide Goto Github PK
View Code? Open in Web Editor NEWFast and friendly HTTP server framework for async Rust
Home Page: https://docs.rs/tide
License: Apache License 2.0
Fast and friendly HTTP server framework for async Rust
Home Page: https://docs.rs/tide
License: Apache License 2.0
The existing API docs were written quickly and don't have examples yet. Let's get some added!
Let's brainstorm about what we'd like to get done before making a 0.1 release to crates.io!
Work out a good story for session management in Tide. No need to reinvent the wheel here: this can probably follow an approach taken by an existing framework, like actix-web.
Since middleware and endpoints run strictly after routing (by design), we may want to provide an ergonomic way of internally redirecting to other routes.
Some considerations:
How best to express this? E.g. explicitly re-enter the router? Return a revised request? Something else?
How should middleware be applied when handling the redirected request?
There's currently no special handling for HEAD
requests, but usually frameworks give you a nice way to automatically handle them (given an implementation of GET
).
At the moment I already have an existing Rust project called "Rise" -- the GUI library. For the project there is a registered domain rise.rs, and an organization "rise-ui". I would like to avoid any confusion in the future, so let's discuss how would we solve the problem with name conflicts. I did not reserve the project name on crates.io because rise-ui has github webrender dependency, and crates.io does not allow the publication of a project with git dependencies. Though some independent libs like raqm have already been published on crates.io on behalf of the project, with keywords like "rise, rise-ui".
A good test of the middleware system would be to implement gzip compression as middleware (transforming the body and headers on the way in/out).
Currently, there's no way to write a trait bound that corresponds to an async fn
that takes borrowed arguments. Because of that restriction, endpoints have to be passed fully owned data, which forces app data to be a cloneable handle.
Once rust-lang/rust#51004 is closed, revisit these APIs (which will also have an impact on the structure of Middleware
).
Currently, the App
object is a mix between a builder (it's the interface to globally manipulating the router) and can be turned into_server
and a global entry point into building something that can be called into. I'm not sure if App
is a good name for it.
Mainly, it collides with a frequent usage of App
in other frameworks, for example, Django uses App
four a mountable subapp (basically a collection of endpoints). https://docs.djangoproject.com/en/2.1/ref/settings/#installed-apps
Currently there is Data
we can give to App
. It is owned by the App and looks like an app-global state, but acts like a request-local state. It is cloned on every request. I think this can lead to confusion because some states persist between requests (e.g. Arc<Mutex<T>>
) and some do not.
My proposal is to:
req.extensions()
or some other typemap for request-local data from middleware. A new typemap would be better because data would be easily extracted (I suppose).Maybe I'm missing something, so feel free to comment!
Right now, all routes and middleware must be set up at the top-level App
. However, it's often useful to be able to nest an existing router (or potentially App
at a given route inside another App
. Similarly, it should be possible to set up middleware at specific nestings within a router, so that it only applies to paths with a specific prefix.
To sketch, this might look something like:
let mut app = App::new(my_data);
app.at("/foo").nest(|router| {
router.middleware(some_middlware); // only applies to routes within this nesting
router.at("bar").get(bar_endpoint); // ultimately routes `/foo/bar` to `bar_endpoint`
})
Let's nail down the API design and then work out a good implementation approach.
Add support for TLS.
The FutureObj
and StreamObj
types were initially important because the Future
and Stream
traits were not object safe (so Box<dyn Future>
didn't work directly). However, that problem has been addressed in nightly, so the Tide code should be updated to use Box
instead.
Note that FutureObj<'a, T>
is equivalent to Box<dyn Future<Output = T> + Send>
.
The current set of files in examples
are extremely simple. It'd be great to build some more realistic (but still small) apps, both for documentation and as a way to try out the framework.
Currently Tide won't run any middleware if the routing fails. We might want to change this, as some middleware should run regardless of the routing result, such as logging or compression.
The easiest way I can think of is:
RouteMatch
. Alternatively, we can make RouteMatch
optional to indicate the routing has failed.If you don't specify a HEAD
behavior but you do provide GET
, Tide will automatically use the GET
behavior. But it retains the body, which should be stripped.
The Middleware
trait is heavily inspired by actix-web's, but does not currently offer a finish
method (to be called after the response has been transmitted), because it's not trivial to do so using hyper's current APIs.
It should be possible to do this by placing the body into a custom stream wrapper that, when the underlying body stream has been fully yielded, sends a signal.
The README should provide, at least:
Sometimes, especially with static file serving, it's necessary to route all subpaths to one endpoint. For example, if /static
is that kind of "catch-all" endpoint, paths like /static/foo.txt
and /static/images/bar.jpg
will be routed to that endpoint.
We can't make a catch-all endpoint with current router API, so we need to implement one.
Currently, the type of a collection of middlewares is Vec<Arc<dyn Middleware<Data> + Send + Sync>>
. I think it would make sense to introduce a proper MiddlewareStack
type that itself implements Middleware
. This allows better sharing of middleware instances and the way to address them, for example for debugging.
Right now, Tide always returns an empty 404 response when there is no route match. This should be customizable.
Possibly blocked by #5, if we want to use the same configuration mechanism for this.
Based on the discussion in #48 I'm creating this issue to discuss the general threading model in Tide.
Right now, Tide defines several general-purpose data types (like Body
) and mucks around with futures-compat layers for using Hyper. These things aren't Tide-specific, and should be factored out as a separate crate that layers on top of Hyper, so that others can use them more easily.
As a follow up to #80, the cookie example should be polished up and moved into Tide proper, so that applications can use it.
fn main() {
let mut app = tide::App::new(());
// Set static files context
app.middleware(
UrlPath::new()
.prefix("/static/content/web/")
.suffix(".html")
);
app.at("/").get(async || resource("index"));
app.serve("127.0.0.1:7878");
}
P.S. Something similar to Rocket's NamedFile
When a value can be lazily computed by request information, it's much nicer to make that value available through the Compute
trait (and Computed
extractor) rather than e.g. using middleware or plumbing the Head
data manually in an endpoint.
What are some examples of such values? One might be: a parsed version of the query segment. Let's generate a bunch of ideas and then implement them!
We need a story for generating URLs in code, given values for various parameters. One possibility would be to use named routes, like Actix does.
Let's discuss possible designs here, before implementing.
Is there any reason why middleware functions cannot take &mut self
? Middleware could enable storing some state when a request comes in and then use that state during the response phase. I'm facing the same issue right now when implementing #8. I figured that at the minimum when someone firsts starts using Tide, they should see a simple log in the terminal of the format:
<timestamp> <http method> <path> <statuscode>
But in order to do so, I need to store method and path information which is only present on the Request
object and access them when the response
function is called by the call
method in Server
.
I imagine that there are many such usecases where middleware would be storing information when the request
method is called. An example that comes to mind is a caching middleware.
Therefore, I think that the request
and response
methods of the Middleware
trait should take &mut self
. Or is there a better way to go about solving this?
Is there a way to use websockets in tide?
Most extractors and middleware should be configurable. But that presents some challenges:
One approach for solving these problems is to use a typemap for configuration, allowing extractors and middleware to provide their knobs as public types that can be installed into the map. This map would then be built as part of constructing the router:
let mut app = App::new();
// configure json extractors to use a 4k maximum payload by default
app.config(json::MaxPayload(4096));
// set up the `foo` endpoint and override its configuration
app.at("foo")
.put(foo_endpoint)
.config(json::MaxPayload(1024));
// for all routes starting at `bar`...
app.at("bar").nest(|router| {
// configure the json extractor to return a 400 on parsing failure
router.config(json::OnError(StatusCode::BAD_REQUEST));
// ...
})
(Note: the nest
example uses the feature proposed here).
Note that this functionality could be implemented using middleware and the extensions
part of the request, but that would mean the configuration is constructed fresh during request handling, rather than when the app is constructed.
Let's reach consensus on a design, and then implement this!
The current middleware design is based on a "before"/"after" split. We could instead have each middleware invoke the rest of the middleware/endpoint chain.
Design and implement middleware for logging, drawing inspiration from (or even re-using) that from existing Rust web frameworks.
The at
method should be documented with the router syntax and fallback semantics.
The url_table
module implements generic routing that is not tied to any other aspects of Tide. It should be pulled out into its own crate, after deciding on a final name.
Content negotiation is not (currently!) part of routing in Tide, but we should decide whether we want that to change, and if not, how to make it easy for an endpoint to internally perform content negotiation.
The body
module contains a few unwraps where hyper produces an error when getting chunks (see the TODO
comments). Just needs to be thought through and improved.
Right now the only tests are for the generic UrlTable
type, not for App
itself. Figure out a test harness strategy for App
and write some tests.
Some examples contain
app.serve("127.0.0.1:8000");
And others are
app.serve("127.0.0.1:7878")
Can we have the same port for all of the examples?
Also, is it a good idea to print the url? Like
Server is listening on http://127.0.0.1:8000
to be able to navigate to the browser by click
Currently, if a new user wanted to understand some of the core ideas behind Tide, they would have to find their way to the following blogposts:
I think that we need to collect the information presented in these posts and flesh them out as a proper guide talking about the concepts of Tide. This in conjunction with more examples can serve as the initial version of documentation for the framework.
With hello example i get an error in the browser console:
The character encoding of the plain text document was not declared. ...
At the moment, it's not clear how to test a Tide application. Ideally we can build on best practices in the Rust ecosystem, including the stories worked out in other frameworks like Rocket.
Currently the only Body
extractor/constructor we offer is Json
.
We should at the very least offer types for working with the body as a raw Vec<u8>
or String
, but perhaps add some additional serde-based types as well.
Consider merging some of the functions from head.rs
into request.rs
to offer a more ergonomic approach to getting parameters from a request.
Should these be moved into request.rs
from head.rs
:
pub fn uri(&self) -> &http::Uri
pub fn path(&self) -> &str
pub fn query(&self) -> Option<&str>
pub fn method(&self) -> &http::Method
A code example would then look more like the following:
async fn get_message(
mut db: AppData<Database>,
id: request::Path<usize>,
) -> Result<body::Json<Message>, StatusCode> {
if let Some(msg) = db.get(id.0) {
Ok(body::Json(msg))
} else {
Err(StatusCode::NOT_FOUND)
}
}
Instead of:
async fn get_message(
mut db: AppData<Database>,
id: head::Path<usize>,
) -> Result<body::Json<Message>, StatusCode> {
if let Some(msg) = db.get(id.0) {
Ok(body::Json(msg))
} else {
Err(StatusCode::NOT_FOUND)
}
}
The initial implementation of the router uses greedy matching for URL segments. Imagine you have two routes:
foo/{}/baz
foo/new/bar
The URL foo/new/baz
will fail to be routed, because the new
component will be greedily matched (concrete segments are always preferred over wildcards) and there is no backtracking.
This behavior is the simplest to implement, but it's not clear that it's the most obvious behavior. OTOH, it's not clear that the URL should match here. And in general, this kind of routing situation seems like an anti-pattern, and is perhaps something we should detect and disallow.
Thoughts welcome!
This is a pretty common http method when using javascript's fetch
api with CORS enabled. I think Tide should probably be able to automatically be able to handle this based on what routes are set and what corresponding methods are available.
Provide a convenience for extracting the query portion of the URL.
The router supports both named ({foo}
) and anonymous ({}
) wildcards, but Tide only has an extractor (Path
) for the latter.
However, extractors are given RouteMatch
structures which contain named match information.
All that's needed is to add an additional extractor type for extracting path components by name. Here's a sketch:
trait NamedComponent: Send + 'static + std::str::FromStr {
const NAME: &'static str;
}
struct Named<T: NamedComponent>(pub T);
impl<T: NamedComponent, S: 'static> Extract<S> for Named<T> { ... }
The Endpoint
trait documentation should give the key information of what Fn
types are actually endpoints.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.