Giter VIP home page Giter VIP logo

tide's Introduction

Tide

Serve the web

Tide is a minimal and pragmatic Rust web application framework built for rapid development. It comes with a robust set of features that make building async web applications and APIs easier and more fun.

Getting started

In order to build a web app in Rust you need an HTTP server, and an async runtime. After running cargo init add the following lines to your Cargo.toml file:

# Example, use the version numbers you need
tide = "0.17.0"
async-std = { version = "1.8.0", features = ["attributes"] }
serde = { version = "1.0", features = ["derive"] }

Examples

Create an HTTP server that receives a JSON body, validates it, and responds with a confirmation message.

use tide::Request;
use tide::prelude::*;

#[derive(Debug, Deserialize)]
struct Animal {
    name: String,
    legs: u16,
}

#[async_std::main]
async fn main() -> tide::Result<()> {
    let mut app = tide::new();
    app.at("/orders/shoes").post(order_shoes);
    app.listen("127.0.0.1:8080").await?;
    Ok(())
}

async fn order_shoes(mut req: Request<()>) -> tide::Result {
    let Animal { name, legs } = req.body_json().await?;
    Ok(format!("Hello, {}! I've put in an order for {} shoes", name, legs).into())
}
$ curl localhost:8080/orders/shoes -d '{ "name": "Chashu", "legs": 4 }'
Hello, Chashu! I've put in an order for 4 shoes
$ curl localhost:8080/orders/shoes -d '{ "name": "Mary Millipede", "legs": 750 }'
Hello, Mary Millipede! I've put in an order for 750 shoes

See more examples in the examples directory.

Tide's design:

Community Resources

To add a link to this list, edit the markdown file and submit a pull request (github login required)
Listing here does not constitute an endorsement or recommendation from the tide team. Use at your own risk.

Listeners

  • tide-rustls TLS for tide based on async-rustls
  • tide-acme HTTPS for tide with automatic certificates, via Let's Encrypt and ACME tls-alpn-01 challenges

Template engines

Routers

Auth

Testing

Middleware

Session Stores

Example applications

  • dot dot vote
  • tide-example (sqlx + askama)
  • playground-tide-mongodb
  • tide-morth-example
  • broker (backend as a service)
  • tide-basic-crud (sqlx + tera)
  • tide-graphql-mongodb
    • Clean boilerplate for graphql services using tide, rhai, async-graphql, surf, graphql-client, handlebars-rust, jsonwebtoken, and mongodb.
    • Graphql Services: User register, Salt and hash a password with PBKDF2 , Sign in, JSON web token authentication, Change password, Profile Update, User's query & mutation, and Project's query & mutation.
    • Web Application: Client request, bring & parse GraphQL data, Render data to template engine(handlebars-rust), Define custom helper with Rhai scripting language.
  • surfer
    • The Blog built on Tide stack, generated from tide-graphql-mongodb.
    • Backend for graphql services using tide, async-graphql, jsonwebtoken, mongodb and so on.
    • Frontend for web application using tide, rhai, surf, graphql_client, handlebars-rust, cookie and so on.
  • tide-server-example

Contributing

Want to join us? Check out our The "Contributing" section of the guide and take a look at some of these issues:

Conduct

The Tide project adheres to the Contributor Covenant Code of Conduct. This describes the minimum behavior expected from all contributors.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

tide's People

Contributors

1015bit avatar aturon avatar bigbv avatar caulagi avatar daraghd avatar deltamaniac avatar dependabot-preview[bot] avatar dignifiedquire avatar eopb avatar fairingrey avatar fishrock123 avatar halvko avatar ibaryshnikov avatar jbr avatar joshtriplett avatar k-nasa avatar mendelt avatar mmrath avatar nemo157 avatar nhellwig avatar nyxtom avatar prasannavl avatar realcr avatar rossmacarthur avatar taiki-e avatar tirr-c avatar tzilist avatar xenith avatar yoshuawuyts avatar yusuke-ota avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tide's Issues

Support "mounting" subrouters and route-sensitive middleware

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.

Project Name Conflicts

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".

Proofs

Add `finish` to `Middleware`

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.

Customize route failure response

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.

Proposal: Rename `App`

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

Add more body types

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.

Handle HEAD requests

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).

TLS

Add support for TLS.

Consider "around" middleware design

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.

Work out a testing/mocking story

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.

Session management

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.

Use reference types in endpoints

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).

Always-applied middleware

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:

  • Only apply middleware of the top-level router.
  • Run a chain of middleware with empty RouteMatch. Alternatively, we can make RouteMatch optional to indicate the routing has failed.

"Catch-all" endpoint

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.

Handle chunk-level errors

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.

Middleware for compression

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).

Build a larger example app

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.

Factor out low-level http server concerns

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.

Support named path extraction

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> { ... }

Use the same port in the examples

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

Nail down wildcard/fallback rules

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!

Shouldn't Middleware functions take &mut self?

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?

Separating app-global data and request-local data

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:

  • Make middleware get an immutable reference to App data. This way it becomes clear that the data is shared in the app and across threads.
  • Use 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!

Ergonomic content negotiation

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.

Internal redirects

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?

Proposal: Introduce the notion of a middleware stack

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.

Remove usage of `FutureObj` and `StreamObj`

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>.

High level guide for Tide

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.

Flesh out the README

The README should provide, at least:

  • A hello-world-style example
  • A quick introduction to the main concepts of Tide
  • Links for where to find more information

Allow the routing methods to open a static file

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

Add configuration (for extractors and middleware)

Most extractors and middleware should be configurable. But that presents some challenges:

  • Extractors are given as argument types to endpoints, so there's no obvious value to attach configuration to.
  • Middleware configuration may want to be tweaked in route-specific ways.

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!

URL generation

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.

What useful computed properties can we provide?

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!

Logging middleware

Design and implement middleware for logging, drawing inspiration from (or even re-using) that from existing Rust web frameworks.

Ergonomics of `head` and `request` modules

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)
    }
}

Add app-level tests

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.

Implement OPTIONS http method

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.