Giter Site home page Giter Site logo

tower-web's Introduction

Tower Web

A web framework for Rust with a focus on removing boilerplate.

Build Status License: MIT Crates.io Gitter

API Documentation

Tower Web is:

  • Fast: Fully asynchronous, built on Tokio and Hyper.
  • Ergonomic: Tower-web decouples HTTP from your application logic, removing all boilerplate.
  • Works on Rust stable: You can use it today.

Hello World

#[macro_use]
extern crate tower_web;
extern crate tokio;

use tower_web::ServiceBuilder;
use tokio::prelude::*;

/// This type will be part of the web service as a resource.
#[derive(Clone, Debug)]
struct HelloWorld;

/// This will be the JSON response
#[derive(Response)]
struct HelloResponse {
    message: &'static str,
}

impl_web! {
    impl HelloWorld {
        #[get("/")]
        #[content_type("json")]
        fn hello_world(&self) -> Result<HelloResponse, ()> {
            Ok(HelloResponse {
                message: "hello world",
            })
        }
    }
}

pub fn main() {
    let addr = "127.0.0.1:8080".parse().expect("Invalid address");
    println!("Listening on http://{}", addr);

    ServiceBuilder::new()
        .resource(HelloWorld)
        .run(&addr)
        .unwrap();
}

Overview

Tower Web aims to decouple all HTTP concepts from the application logic. You define a "plain old Rust method" (PORM?). This method takes only the data it needs to complete and returns a struct representing the response. Tower Web does the rest.

The impl_web macro looks at the definition and generates the glue code, allowing the method to respond to HTTP requests.

Getting Started

The best way to get started is to read the examples and API docs.

License

This project is licensed under the MIT license.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in tower-web by you, shall be licensed as MIT, without any additional terms or conditions.

tower-web's People

Contributors

0xpr03 avatar abogical avatar anderejd avatar bryanburgers avatar carllerche avatar chastabor avatar dd10-e avatar dtolnay avatar ebkalderon avatar elpiel avatar illicitonion avatar jtdowney avatar jtgeibel avatar jwilm avatar kornholi avatar lnicola avatar manifest avatar mbrobbel avatar mexus avatar niklasf avatar shepmaster avatar steveklabnik avatar sunng87 avatar toidiu avatar ubnt-intrepid avatar whitfin 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

tower-web's Issues

Having both `Service` and `HttpService` in scope is annoying due to name clashes

Updating the CORS PR to current master, I'm running into a lot of this in the tests:

error[E0034]: multiple applicable items in scope
   --> src/middleware/cors/service.rs:136:17
    |
136 |         service.poll_ready()?;
    |                 ^^^^^^^^^^ multiple `poll_ready` found
    |
note: candidate #1 is defined in an impl of the trait `tower_service::Service` for the type `middleware::cors::service::CorsService<_>`
   --> src/middleware/cors/service.rs:32:5
    |
32  |     fn poll_ready(&mut self) -> Poll<(), Self::Error> {
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl of the trait `util::http::service::HttpService` for the type `_`

Ditto for call.

I can work around this by never actually importing HttpService and using it in some qualified manner, but it's a bit annoying.

2018 edition of Rust requires a bunch of imports

To use impl_web! in rust-2018 requires typing in this monstrocity:

use tower_web::{
    derive_resource, derive_resource_impl, impl_web, impl_web_clean_nested,
    impl_web_clean_top_level,
};

Is there perhaps a cleaner way?

How can I parse input data as JSON that doesn't set the content type?

I have an endpoint that is /evaluate.json which does not guarantee that the request has a content type. This is triggering the assert:

assert!({
use http::header;
ctx.request().headers().get(header::CONTENT_TYPE)
.map(|content_type| {
content_type.as_bytes().to_ascii_lowercase() ==
b"application/json"
})
.unwrap_or(false)
});

What kind of transformation do we need to make?

Need to be able to set timeouts / limits to handle misbehaving clients

If tower-web is exposed to the internet, it needs to be robust when handling misbehaving clients. I'm thinking of

  • request header read timeout: max time it can take for the client to send all headers
  • request body timeout
  • request reply timeout (if a client simply doesn't read the reply)
  • there should be a way for a request handler to reset/restart the timeouts, e.g. to facilitate large bodies or large replies.
  • idle timeout for keepalive sessions
  • max number of simultaneous requests per client (source ip address)
  • max number of requests/sec per client

The last two could probably be implemented as middleware if there was a way to get the client's IP address.

It could be that this should be implemented in hyper instead of in tower, if so tell me and I will open an issue with hyper.

Content types

  • The required annotation is very annoying.
  • Allow specifying content type at the impl level and response level.
  • Infer content-type from file.

Unable to derive `Response`

I haven't minimized this yet, but I have:

#[derive(Debug, Response)]
struct CompileResponse {
    success: bool,
    code: String,
    stdout: String,
    stderr: String,
}
error[E0244]: wrong number of type arguments: expected 1, found 2
   --> src/main.rs:572:17
    |
572 | #[derive(Debug, Response)]
    |                 ^^^^^^^^ expected 1 type argument

Consider websocket support

These items (non exhaustive list) may have to be discussed/prioritized:

  • API-friendliness ร  la Tower-web
  • websocket standard compliance
  • client/server
  • fine tuning: headers, cookie, ...
  • ws:// and wss:// support

assertion failed: self.remaining_mut() >= src.remaining()

thread 'tokio-runtime-worker-0' panicked at 'assertion failed: self.remaining_mut() >= src.remaining()', /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/bytes-0.4.8/src/buf/buf_mut.rs:229:9
   0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
             at libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
   1: std::sys_common::backtrace::print
             at libstd/sys_common/backtrace.rs:71
             at libstd/sys_common/backtrace.rs:59
   2: std::panicking::default_hook::{{closure}}
             at libstd/panicking.rs:211
   3: std::panicking::default_hook
             at libstd/panicking.rs:227
   4: <std::panicking::begin_panic::PanicPayload<A> as core::panic::BoxMeUp>::get
             at libstd/panicking.rs:475
   5: std::ffi::c_str::CStr::to_bytes
             at /Users/travis/build/rust-lang/rust/src/libstd/panicking.rs:409
   6: bytes::buf::buf_mut::BufMut::put
             at ./<panic macros>:3
   7: <tower_web::extract::serde::SerdeFuture<T, B> as tower_web::extract::ExtractFuture>::poll
             at /Users/shep/Projects/tower-web/src/extract/serde.rs:145
   8: <ui::__IMPL_EXTRACT_FOR_CompileRequest::ExtractFuture<B> as tower_web::extract::ExtractFuture>::poll
             at src/main.rs:552
   9: <tower_web::util::tuple::Join1<T0> as futures::future::Future>::poll
             at /Users/shep/Projects/tower-web/src/util/tuple.rs:642
  10: <tower_web::util::tuple::Either1<A> as futures::future::Future>::poll
             at /Users/shep/Projects/tower-web/src/util/tuple.rs:81
  11: <ui::tower_web_server::__IMPL_WEB_1_FOR_Sandbox::ResponseFuture<S, B> as futures::future::Future>::poll
             at ./<derive_resource macros>:2
  12: <T as tower_web::service::future::HttpResponseFuture>::poll_http_response
             at /Users/shep/Projects/tower-web/src/service/future.rs:44
  13: <tower_web::util::tuple::Either2<A, B> as tower_web::service::future::HttpResponseFuture>::poll_http_response
             at /Users/shep/Projects/tower-web/src/util/tuple.rs:275
  14: <tower_web::util::tuple::LiftHttpResponse<T> as futures::future::Future>::poll
             at /Users/shep/Projects/tower-web/src/util/tuple.rs:27
  15: <futures::future::map::Map<A, F> as futures::future::Future>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/future/map.rs:30
  16: <alloc::boxed::Box<F> as futures::future::Future>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/future/mod.rs:113
  17: <hyper::proto::h1::dispatch::Server<S> as hyper::proto::h1::dispatch::Dispatch>::poll_msg
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.12.7/src/proto/h1/dispatch.rs:379
  18: <hyper::proto::h1::dispatch::Dispatcher<D, Bs, I, T>>::poll_write
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.12.7/src/proto/h1/dispatch.rs:237
  19: <hyper::proto::h1::dispatch::Dispatcher<D, Bs, I, T>>::poll_inner
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.12.7/src/proto/h1/dispatch.rs:101
  20: <hyper::proto::h1::dispatch::Dispatcher<D, Bs, I, T>>::poll_catch
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.12.7/src/proto/h1/dispatch.rs:86
  21: <hyper::proto::h1::dispatch::Dispatcher<D, Bs, I, T> as futures::future::Future>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.12.7/src/proto/h1/dispatch.rs:348
  22: <futures::future::either::Either<A, B> as futures::future::Future>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/future/either.rs:35
  23: futures::future::option::<impl futures::future::Future for core::option::Option<F>>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/future/option.rs:12
  24: <hyper::server::conn::Connection<I, S> as futures::future::Future>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.12.7/src/server/conn.rs:502
  25: <futures::future::map::Map<A, F> as futures::future::Future>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/future/map.rs:30
  26: <futures::future::map_err::MapErr<A, F> as futures::future::Future>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/future/map_err.rs:30
  27: core::num::<impl usize>::max_value
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/future/mod.rs:113
  28: <bool as core::fmt::Debug>::fmt
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/task_impl/mod.rs:289
  29: <bool as core::fmt::Debug>::fmt
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/task_impl/mod.rs:363
  30: futures::task_impl::std::CURRENT_THREAD_NOTIFY::__init
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/task_impl/std/mod.rs:78
  31: <bool as core::fmt::Debug>::fmt
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/task_impl/mod.rs:363
  32: <bool as core::fmt::Debug>::fmt
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/task_impl/mod.rs:289
  33: tokio_threadpool::task::TaskFuture::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/task/mod.rs:292
  34: tokio_threadpool::task::Task::run::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/task/mod.rs:165
  35: core::ops::function::FnOnce::call_once
             at /Users/travis/build/rust-lang/rust/src/libcore/ops/function.rs:223
  36: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panic.rs:313
  37: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panicking.rs:310
  38: panic_unwind::dwarf::eh::read_encoded_pointer
             at libpanic_unwind/lib.rs:106
  39: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panicking.rs:289
  40: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panic.rs:392
  41: tokio_threadpool::task::Task::run
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/task/mod.rs:151
  42: tokio_threadpool::worker::Worker::run_task2
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:545
  43: tokio_threadpool::worker::Worker::run_task
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:445
  44: tokio_threadpool::worker::Worker::try_run_owned_task
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:385
  45: tokio_threadpool::worker::Worker::try_run_task
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:293
  46: tokio_threadpool::worker::Worker::with_current::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:239
  47: tokio::runtime::builder::Builder::build::{{closure}}::{{closure}}::{{closure}}::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.1.7/src/runtime/builder.rs:125
  48: <std::collections::hash::table::SafeHash as core::cmp::PartialEq>::eq
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-timer-0.2.4/src/timer/handle.rs:64
  49: std::sync::mpsc::blocking::SignalToken::cast_from_usize
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:294
  50: std::sync::mpsc::blocking::SignalToken::cast_from_usize
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:248
  51: <std::collections::hash::table::SafeHash as core::cmp::PartialEq>::eq
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-timer-0.2.4/src/timer/handle.rs:56
  52: tokio::runtime::builder::Builder::build::{{closure}}::{{closure}}::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.1.7/src/runtime/builder.rs:124
  53: futures::task_impl::core::get_ptr
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-timer-0.2.4/src/clock/clock.rs:136
  54: std::sync::mpsc::blocking::SignalToken::cast_from_usize
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:294
  55: std::sync::mpsc::blocking::SignalToken::cast_from_usize
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:248
  56: futures::task_impl::core::get_ptr
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-timer-0.2.4/src/clock/clock.rs:119
  57: tokio::runtime::builder::Builder::build::{{closure}}::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.1.7/src/runtime/builder.rs:123
  58: tokio::runtime::Runtime::inner
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-reactor-0.1.2/src/lib.rs:231
  59: std::sync::mpsc::blocking::SignalToken::cast_from_usize
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:294
  60: std::sync::mpsc::blocking::SignalToken::cast_from_usize
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:248
  61: tokio::runtime::Runtime::inner
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-reactor-0.1.2/src/lib.rs:214
  62: tokio::runtime::builder::Builder::build::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.1.7/src/runtime/builder.rs:122
  63: tokio_threadpool::callback::Callback::call
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/callback.rs:21
  64: tokio_threadpool::worker::Worker::do_run::{{closure}}::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:121
  65: tokio_threadpool::task::queue::Queue::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-executor-0.1.2/src/global.rs:176
  66: crossbeam_epoch::collector::Handle::is_pinned
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:294
  67: crossbeam_epoch::collector::Handle::is_pinned
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:248
  68: tokio_threadpool::task::queue::Queue::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-executor-0.1.2/src/global.rs:150
  69: tokio_threadpool::worker::Worker::do_run::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:119
  70: crossbeam_epoch::collector::Handle::is_pinned
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:294
  71: crossbeam_epoch::collector::Handle::is_pinned
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:248
  72: tokio_threadpool::worker::Worker::do_run
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:110
  73: tokio_threadpool::pool::Pool::spawn_thread::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/pool/mod.rs:417
  74: <alloc::collections::CollectionAllocErr as core::convert::From<core::alloc::AllocErr>>::from
             at /Users/travis/build/rust-lang/rust/src/libstd/sys_common/backtrace.rs:136
  75: crossbeam_epoch::collector::Handle::is_pinned
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/mod.rs:409
  76: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panic.rs:313
  77: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panicking.rs:310
  78: panic_unwind::dwarf::eh::read_encoded_pointer
             at libpanic_unwind/lib.rs:106
  79: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panicking.rs:289
  80: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panic.rs:392
  81: crossbeam_epoch::collector::Handle::is_pinned
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/mod.rs:408
  82: crossbeam_epoch::collector::Handle::is_pinned
             at /Users/travis/build/rust-lang/rust/src/liballoc/boxed.rs:640
  83: std::sys::unix::thread::Thread::new::thread_start
             at /Users/travis/build/rust-lang/rust/src/liballoc/boxed.rs:650
             at libstd/sys_common/thread.rs:24
             at libstd/sys/unix/thread.rs:90
  84: _pthread_body
  85: _pthread_start

Generate client code corresponding to defined server endpoints

Would it be possible to also generate client code? Take the ArgResource path_multi_str example, would be awesome if we could generate something like:

ArgResourceClient::path_multi_str(foo: String, bar: String) -> Result<String, ()>

Double awesome if we could generate multiple versions for different platforms, like using normal reqwest for CLI apps, and then using yew for wasm.

Consider `mount("/foo", resource)` API

Currently, all resources are implicitly mounted at the root. It would be worthwhile to allow mounting resources at sub paths.

The two options are to either:

a) Add ServiceBuilder::mount("/foo", resource)
b) Replace ServiceBuilder::resource(resource) with a mount API.

What is the recommended way of conditionally adding middleware?

I'd like to write this code:

    let builder = ServiceBuilder::new()
        .resource(Index::new(config.root.clone()))
        .resource(Assets::new(config.root))
        .resource(SandboxFixme)
        .resource(Meta::default())
        .resource(Gist::new(config.gh_token));

    if config.cors_enabled {
        let cors = CorsBuilder::new()
            .allow_origins(AllowedOrigins::Any { allow_null: true })
            .allow_headers(vec![header::CONTENT_TYPE])
            .allow_methods(vec![Method::GET, Method::POST])
            .allow_credentials(false)
            .max_age(Duration::from_secs(ONE_HOUR_IN_SECONDS as u64))
            .prefer_wildcard(true)
            .build();

        builder = builder.middleware(cors);
    }

But it results in

error[E0308]: mismatched types
   --> src/tower_web_server.rs:373:19
    |
373 |         builder = builder.middleware(cors);
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `tower_web::middleware::Identity`, found struct `tower_web::middleware::Chain`
    |
    = note: expected type `tower_web::ServiceBuilder<_, _, tower_web::middleware::Identity>`
               found type `tower_web::ServiceBuilder<_, _, tower_web::middleware::Chain<tower_web::middleware::Identity, tower_web::middleware::cors::CorsMiddleware>>`

I see a few avenues:

  1. Make each middleware have an internal on/off switch. For CORS, this is "easy" as the restrictive settings should be as good as off. Every middleware would have to remember to add this, all would probably be named differently, it's not as "blatantly correct" as never inserting it, etc.

  2. Implement Middleware for Box<Middleware> and add some type erasure.

  3. Implement Middleware for Either<L, R> and then do Either<Cors, Identity>

Use attribute syntax instead of magic comments

You'd asked me to open an issue to discuss strategy, so here we go. There are basically two options. Ultimately the whole thing is getting passed to a derive, so the simplest option is to not have impl_web! spit out the tokens its given unmodified, and instead have derive_resource! spit out its input (with the recognized attributes stripped) in addition to what it spits out today.

You mentioned you had a solution which works and messes up error messages, so I'm assuming it's the solution above. The second option would be to run the tokens passed to impl_web! through another macro before outputting them. This would be a basic macro_rules! macro, which strips out regonized meta items.

Both of these options are pretty simple to implement. Diesel does both in multiple places (depending on whether our macro is fundamentally a macro_rules! or procedural macro). Would love to hear more about whether this would somehow affect your error messages.

Self-hosting examples

It would be very powerful to have the examples be self-hosting and self-documenting. Doing cargo run --example foo and going to / can have HTML with descriptions and links to each route.

Caching

How should caching work?

  • Support for various caching related HTTP headers.
  • In-process caching middleware?

Cannot call ServiceBuilder with more than 2 resources

I expect to have a large number of these. I see I can build up a large nested amount of tuples, but that's kind of annoying:

    let r = (
        (
            Index::new(config.root.clone()),
            Assets::new(config.root),
        ),
        (
            Sandbox,
            Meta,
        ),
    );

I would prefer to be able to do

    ServiceBuilder::new()
        .resource(Index::new(config.root.clone()))
        .resource(Assets::new(config.root))
        .resource(Sandbox)
        .run(&addr)
        .unwrap();

but this happens:

error[E0599]: no method named `run` found for type `tower_web::ServiceBuilder<(tower_web_server::Index, tower_web_server::Assets, tower_web_server::Sandbox), _>` in the current scope
   --> src/tower_web_server.rs:238:10
    |
238 |         .run(&addr)
    |          ^^^
    |
    = note: the method `run` exists but the following trait bounds were not satisfied:
            `(tower_web_server::Index, tower_web_server::Assets, tower_web_server::Sandbox) : tower_web::service::IntoResource<tower_web::response::DefaultSerializer, tower_web::run::LiftReqBody>`
            `(tower_web_server::Index, tower_web_server::Assets, tower_web_server::Sandbox) : tower_web::service::IntoResource<tower_web::response::DefaultSerializer, tower_web::run::LiftReqBody>`
            `(tower_web_server::Index, tower_web_server::Assets, tower_web_server::Sandbox) : tower_web::service::IntoResource<tower_web::response::DefaultSerializer, tower_web::run::LiftReqBody>`
            `(tower_web_server::Index, tower_web_server::Assets, tower_web_server::Sandbox) : tower_web::service::IntoResource<tower_web::response::DefaultSerializer, tower_web::run::LiftReqBody>`
            `(tower_web_server::Index, tower_web_server::Assets, tower_web_server::Sandbox) : tower_web::service::IntoResource<tower_web::response::DefaultSerializer, tower_web::run::LiftReqBody>`
            `(tower_web_server::Index, tower_web_server::Assets, tower_web_server::Sandbox) : tower_web::service::IntoResource<tower_web::response::DefaultSerializer, tower_web::run::LiftReqBody>`

Stream of consciousness thoughts from a first-time user

My context is evaluating replacements for Iron for the Rust Playground.

I've just skimmed hello_world so far. My raw thoughts are:

Why's all this code commented out?

How do I serve a file?

Ah, there's custom headers

How do I handle CORS?

Is there an overall vision for "features" and extensibility for tower-web?
Do you want to farm things out to multiple crates, or have "most" / "common" things in one crate?
Based on Tokio, I'm assuming many crates and perhaps re-export them in -web?

How do I return a Future from a method?

#[derive(Clone, Debug)]
pub struct SelfServing;

impl_web! {
    impl SelfServing {
        /// @get("/")
        /// @content_type("plain")
        fn x(&self) -> impl Future<Item = String, Error = ()> {
error[E0599]: no method named `run` found for type `tower_web::ServiceBuilder<SelfServing, _>` in the current scope
  --> examples/static_file.rs:37:10
   |
37 |         .run(&addr)
   |          ^^^
   |
   = note: the method `run` exists but the following trait bounds were not satisfied:
           `__IMPL_WEB_0_FOR_SelfServing::DispatchFuture<tower_web::response::DefaultSerializer, tower_web::run::LiftReqBody> : std::marker::Send`

Content type feels like it should be a property of the return type, but maybe optionally overridable by an attribute

Add `SendFile` response

It would be nice to have a SendFile response type that took a path to a file to respond with. This would allow the HTTP implementation to optimize by using the sendfile syscall.

struct SendFile {
    path: PathBuf,
}

Relates to #5

Swagger/Open API schema generation

As an interface description addict I would like to open this issue early just to inject this idea into the collective consciousness of this project. It could be worth thinking about this feature and planning ahead.

BTW, I love the stated project goals described in the README ๐Ÿ‘

Documentation

Meta issue tracking topics that require documentation.

  • Comments vs. attributes. (Why's all this code commented out?)
  • How to handle CORS.
  • Returning futures. Note Sync requirement.
  • BufStream vs. Response.
  • impl_web! limitations: #31.

Stream of consciousness: #4

Using `@content_type("text/plain")` with structs that should be JSON has an inscrutable message

thread 'tokio-runtime-worker-2' panicked at 'called `Result::unwrap()` on an `Err` value: ImpossibleSerialization("struct")', libcore/result.rs:945:5
stack backtrace:
   0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
             at libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
   1: std::sys_common::backtrace::print
             at libstd/sys_common/backtrace.rs:71
             at libstd/sys_common/backtrace.rs:59
   2: std::panicking::default_hook::{{closure}}
             at libstd/panicking.rs:211
   3: std::panicking::default_hook
             at libstd/panicking.rs:227
   4: <std::panicking::begin_panic::PanicPayload<A> as core::panic::BoxMeUp>::get
             at libstd/panicking.rs:475
   5: std::panicking::continue_panic_fmt
             at libstd/panicking.rs:390
   6: std::panicking::try::do_call
             at libstd/panicking.rs:325
   7: core::ptr::drop_in_place
             at libcore/panicking.rs:77
   8: core::result::unwrap_failed
             at /Users/travis/build/rust-lang/rust/src/libcore/macros.rs:26
   9: <core::result::Result<T, E>>::unwrap
             at /Users/travis/build/rust-lang/rust/src/libcore/result.rs:782
  10: <tower_web::response::serializer::DefaultSerializer as tower_web::response::serializer::Serializer>::serialize
             at /Users/shep/Projects/tower-web/src/response/serializer.rs:79
  11: <tower_web::response::context::Context<'a, S>>::serialize
             at /Users/shep/Projects/tower-web/src/response/context.rs:29
  12: ui::__IMPL_RESPONSE_FOR_MetaCratesResponse::<impl tower_web::response::response::Response for ui::MetaCratesResponse>::into_http
             at src/main.rs:632
  13: <ui::tower_web_server::__IMPL_WEB_3_FOR_Meta::ResponseFuture<S, B> as futures::future::Future>::poll
             at ./<derive_resource macros>:2
  14: <T as tower_web::service::future::HttpResponseFuture>::poll_http_response
             at /Users/shep/Projects/tower-web/src/service/future.rs:44
  15: <tower_web::util::tuple::Either2<A, B> as tower_web::service::future::HttpResponseFuture>::poll_http_response
             at /Users/shep/Projects/tower-web/src/util/tuple.rs:275
  16: <tower_web::util::tuple::LiftHttpResponse<T> as futures::future::Future>::poll
             at /Users/shep/Projects/tower-web/src/util/tuple.rs:27
  17: <T as tower_web::service::future::HttpResponseFuture>::poll_http_response
             at /Users/shep/Projects/tower-web/src/service/future.rs:44
  18: <tower_web::util::tuple::Either2<A, B> as tower_web::service::future::HttpResponseFuture>::poll_http_response
             at /Users/shep/Projects/tower-web/src/util/tuple.rs:275
  19: <tower_web::util::tuple::LiftHttpResponse<T> as futures::future::Future>::poll
             at /Users/shep/Projects/tower-web/src/util/tuple.rs:27
  20: <futures::future::map::Map<A, F> as futures::future::Future>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/future/map.rs:30
  21: <alloc::boxed::Box<F> as futures::future::Future>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/future/mod.rs:113
  22: <hyper::proto::h1::dispatch::Server<S> as hyper::proto::h1::dispatch::Dispatch>::poll_msg
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.12.7/src/proto/h1/dispatch.rs:379
  23: <hyper::proto::h1::dispatch::Dispatcher<D, Bs, I, T>>::poll_write
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.12.7/src/proto/h1/dispatch.rs:237
  24: <hyper::proto::h1::dispatch::Dispatcher<D, Bs, I, T>>::poll_inner
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.12.7/src/proto/h1/dispatch.rs:101
  25: <hyper::proto::h1::dispatch::Dispatcher<D, Bs, I, T>>::poll_catch
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.12.7/src/proto/h1/dispatch.rs:86
  26: <hyper::proto::h1::dispatch::Dispatcher<D, Bs, I, T> as futures::future::Future>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.12.7/src/proto/h1/dispatch.rs:348
  27: <futures::future::either::Either<A, B> as futures::future::Future>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/future/either.rs:35
  28: futures::future::option::<impl futures::future::Future for core::option::Option<F>>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/future/option.rs:12
  29: <hyper::server::conn::Connection<I, S> as futures::future::Future>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.12.7/src/server/conn.rs:502
  30: <futures::future::map::Map<A, F> as futures::future::Future>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/future/map.rs:30
  31: <futures::future::map_err::MapErr<A, F> as futures::future::Future>::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/future/map_err.rs:30
  32: core::num::<impl usize>::max_value
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/future/mod.rs:113
  33: <bool as core::fmt::Debug>::fmt
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/task_impl/mod.rs:289
  34: <bool as core::fmt::Debug>::fmt
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/task_impl/mod.rs:363
  35: futures::task_impl::std::CURRENT_THREAD_NOTIFY::__init
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/task_impl/std/mod.rs:78
  36: <bool as core::fmt::Debug>::fmt
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/task_impl/mod.rs:363
  37: <bool as core::fmt::Debug>::fmt
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-0.1.23/src/task_impl/mod.rs:289
  38: tokio_threadpool::task::TaskFuture::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/task/mod.rs:292
  39: tokio_threadpool::task::Task::run::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/task/mod.rs:165
  40: core::ops::function::FnOnce::call_once
             at /Users/travis/build/rust-lang/rust/src/libcore/ops/function.rs:223
  41: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panic.rs:313
  42: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panicking.rs:310
  43: panic_unwind::dwarf::eh::read_encoded_pointer
             at libpanic_unwind/lib.rs:106
  44: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panicking.rs:289
  45: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panic.rs:392
  46: tokio_threadpool::task::Task::run
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/task/mod.rs:151
  47: tokio_threadpool::worker::Worker::run_task2
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:545
  48: tokio_threadpool::worker::Worker::run_task
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:445
  49: tokio_threadpool::worker::Worker::try_steal_task
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:412
  50: tokio_threadpool::worker::Worker::try_run_task
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:297
  51: tokio_threadpool::worker::Worker::with_current::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:239
  52: tokio::runtime::builder::Builder::build::{{closure}}::{{closure}}::{{closure}}::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.1.7/src/runtime/builder.rs:125
  53: <std::collections::hash::table::SafeHash as core::cmp::PartialEq>::eq
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-timer-0.2.4/src/timer/handle.rs:64
  54: std::sync::mpsc::blocking::SignalToken::cast_from_usize
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:294
  55: std::sync::mpsc::blocking::SignalToken::cast_from_usize
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:248
  56: <std::collections::hash::table::SafeHash as core::cmp::PartialEq>::eq
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-timer-0.2.4/src/timer/handle.rs:56
  57: tokio::runtime::builder::Builder::build::{{closure}}::{{closure}}::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.1.7/src/runtime/builder.rs:124
  58: futures::task_impl::core::get_ptr
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-timer-0.2.4/src/clock/clock.rs:136
  59: std::sync::mpsc::blocking::SignalToken::cast_from_usize
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:294
  60: std::sync::mpsc::blocking::SignalToken::cast_from_usize
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:248
  61: futures::task_impl::core::get_ptr
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-timer-0.2.4/src/clock/clock.rs:119
  62: tokio::runtime::builder::Builder::build::{{closure}}::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.1.7/src/runtime/builder.rs:123
  63: tokio::runtime::Runtime::inner
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-reactor-0.1.2/src/lib.rs:231
  64: std::sync::mpsc::blocking::SignalToken::cast_from_usize
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:294
  65: std::sync::mpsc::blocking::SignalToken::cast_from_usize
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:248
  66: tokio::runtime::Runtime::inner
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-reactor-0.1.2/src/lib.rs:214
  67: tokio::runtime::builder::Builder::build::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.1.7/src/runtime/builder.rs:122
  68: tokio_threadpool::callback::Callback::call
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/callback.rs:21
  69: tokio_threadpool::worker::Worker::do_run::{{closure}}::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:121
  70: tokio_threadpool::task::queue::Queue::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-executor-0.1.2/src/global.rs:176
  71: crossbeam_epoch::collector::Handle::is_pinned
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:294
  72: crossbeam_epoch::collector::Handle::is_pinned
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:248
  73: tokio_threadpool::task::queue::Queue::poll
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-executor-0.1.2/src/global.rs:150
  74: tokio_threadpool::worker::Worker::do_run::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:119
  75: crossbeam_epoch::collector::Handle::is_pinned
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:294
  76: crossbeam_epoch::collector::Handle::is_pinned
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/local.rs:248
  77: tokio_threadpool::worker::Worker::do_run
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/worker/mod.rs:110
  78: tokio_threadpool::pool::Pool::spawn_thread::{{closure}}
             at /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-threadpool-0.1.5/src/pool/mod.rs:417
  79: <alloc::collections::CollectionAllocErr as core::convert::From<core::alloc::AllocErr>>::from
             at /Users/travis/build/rust-lang/rust/src/libstd/sys_common/backtrace.rs:136
  80: crossbeam_epoch::collector::Handle::is_pinned
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/mod.rs:409
  81: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panic.rs:313
  82: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panicking.rs:310
  83: panic_unwind::dwarf::eh::read_encoded_pointer
             at libpanic_unwind/lib.rs:106
  84: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panicking.rs:289
  85: <tokio_threadpool::notifier::Forget<T>>::new
             at /Users/travis/build/rust-lang/rust/src/libstd/panic.rs:392
  86: crossbeam_epoch::collector::Handle::is_pinned
             at /Users/travis/build/rust-lang/rust/src/libstd/thread/mod.rs:408
  87: crossbeam_epoch::collector::Handle::is_pinned
             at /Users/travis/build/rust-lang/rust/src/liballoc/boxed.rs:640
  88: std::sys::unix::thread::Thread::new::thread_start
             at /Users/travis/build/rust-lang/rust/src/liballoc/boxed.rs:650
             at libstd/sys_common/thread.rs:24
             at libstd/sys/unix/thread.rs:90
  89: _pthread_body
  90: _pthread_start
        /// @get("/meta/crates")
        /// @content_type("text/plain")
        fn meta_crates(&self) -> Result<::MetaCratesResponse, ::Error>
#[derive(Debug, Clone, Serialize)]
struct CrateInformation {
    name: String,
    version: String,
    id: String,
}

#[derive(Debug, Clone, Response)]
struct MetaCratesResponse {
    crates: Vec<CrateInformation>,
}

tower-web's macros replace serde-derive's

After adding

#[macro_use]
extern crate tower_web;

I now get this on my import of serde_derive

warning: unused `#[macro_use]` import
  --> src/main.rs:14:1
   |
14 | #[macro_use]
   | ^^^^^^^^^^^^
   |
   = note: #[warn(unused_imports)] on by default

Routing

  • Mounting resources at a prefix.
  • Specify prefix on impl.
  • Use a trie.

impl_web only allowed once (per module?)

error[E0428]: the name `ProcMacroHack` is defined multiple times
   --> src/tower_web_server.rs:29:1
    |
29  | / impl_web! {
30  | |     impl Assets {
31  | |         /// @get("/")
32  | |         /// @content_type("text/plain")
...   |
129 | |     }
130 | | }
    | |_^ `ProcMacroHack` redefined here
...
133 | / impl_web! {
134 | |     impl Sandbox {
135 | |         /// @get("/compile")
136 | |         /// @content_type("text/plain")
...   |
140 | |     }
141 | | }
    | |_- previous definition of the type `ProcMacroHack` here
    |
    = note: `ProcMacroHack` must be defined only once in the type namespace of this module
    = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

There should be a way to get a peer's IP address

There should be a way to get the peer's IP address, e.g. tcpstream.peer_addr(). A peer_addr() or client_addr() method should be added to the request struct.

If this issue should be filed with hyper instead let me know.

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.