Giter VIP home page Giter VIP logo

simple-server's Introduction

simple-server

a crate for building a simple blocking HTTP server

Build Status crates.io

NOTE: this crate has not been evaluated for security concerns. therefore, we recommend that you take caution before using it in a production application.

up and running

this crate is written in the rust programming language. you'll need rust to run this crate. you can install rust using rustup. simple-server requires that you use rust version 1.20+.

to get this crate running locally:

  1. fork and clone this repository
  2. cd simple-server
  3. cargo build

to use this crate in your project, add the following line to your Cargo.toml in the [dependencies] section:

simple-server = "0.3.0"

to see this crate in action, check out the examples.

tests

to test this crate locally, run:

cargo test

docs

this crate has documentation. to build and open the docs locally:

cargo doc --open

examples

there are several examples provided with this crate:

to run an example:

cargo run --example <name of example>

this crate uses the log crate for logging. in the example, we use the env-logger crate to display the logs. by default, env-logger only prints out error-level logs. to enable info-level logging as well, you'll need to do one of the following depending on your system:

on Linux/OS X:

RUST_LOG="simple_server=info" cargo run --example server

on Windows PowerShell:

$env:RUST_LOG="simple_server=info";
cargo run --example server

license

simple-server is licensed under both the Apache2 and MIT licenses.

simple-server's People

Contributors

ashleygwilliams avatar binarybana avatar denis-golubev avatar divarvel avatar gsquire avatar kardeiz avatar nbigaouette avatar quadrupleslap avatar scurest avatar skade avatar steveklabnik 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

simple-server's Issues

crates.io isn't using docs.rs fallback

This may be because the crate is new and there aren't docs there yet. It may be because that's broken or I misunderstood somehow. Investigation needed.

Location header seems to not be working

was trying to implement a workaround for #88 :

pub fn serve(directory: &Path) -> Result<()> {
    let host = "127.0.0.1";
    let port = "7878";

    let server = Server::new(|request, mut response| {
        println!("got {}", request.uri().path());
        response.status(301);
        response.header(http::header::LOCATION, "/index.html".as_bytes());
        Ok(response.body("".as_bytes())?)
    });

    env::set_current_dir(directory)?;

    println!("serving at http://{}:{}", host, port);

    server.listen(host, port);

    Ok(())
}

the 301 comes through, but not the location header. I don't know why...

consider stronger typing for `Server::listen` arguments

the Server::listen call currently takes an &str for both host and port. This gets fed into a format! call, using the a str impl of ToSocketAddr.

IMHO, there is no opinion to stringly-type these parameters. Taking a std::net::IpAddr and a u16 does exactly the same, with stronger typing. The combination (IpAddr, u16) even has a ToSocketAddrs-impl, so it can be directly passed into the TcpListener.

As discussed on users.rust-lang, this would add some boilerplate on the user-side to create the IpAddr:

IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); // verbose, guaranteed to work...
"127.0.0.1".parse().expect("couldn't parse IP address") // not much better...

Consider an `after_start` hook of some kind

We only have the log crate because we want to print out that the server is starting; we only print out the server is starting because there's no way to customize this kind of thing. The Node api we're emulating does, however.

It might look something like this:

#[macro_use]
extern crate log;
extern crate env_logger;

extern crate simple_server;

use simple_server::Server;

fn main() {
    env_logger::init().unwrap();

    let host = "127.0.0.1";
    let port = "7878";

    let server = Server::new(|request, mut response| {
        info!("Request received. {} {}", request.method(), request.uri());
        Ok(response.body("Hello Rust!".as_bytes())?)
    });

    server.listen(host, port, || {
        info!("Server started on {}:{}", host, port);
    });
}

Thoughts?

Content-Length or Transfer-Encoding header (RFC 7230)

The presence of a message body in a request is signaled by a Content-Length or Transfer-Encoding header field.

https://tools.ietf.org/html/rfc7230#section-3.3

Without this some tools don't work well, for example wrk.

let data = String::from_utf8_lossy(request.body()).into_owned();
let body = format!(r#"{{"data": {}, "url": "{}"}}"#, request.uri().path(), data);
Ok(response
    .header("content-length", body.len().to_string().as_str())   //  ~:o
    .body(body.into_bytes())?)

Is valid code, although has some issues, the first one is the header is not set (or any other header).

< HTTP/1.1 200 OK
* no chunk, no close, no size. Assume close to signal end

Compared with the minimal out of the box set by the http module of node:

< HTTP/1.1 200 OK
< Date: Sun, 29 Oct 2017 08:19:45 GMT
< Connection: keep-alive
< Content-Length: 21

Can these values be set automatically?

body() sometimes returns zeroed slice

In the handler I have eprintln!("{:?}", request.body()). The client sending the request is Chrome using AJAX. I have a button on the page which sends JSON when one clicks it. If I try to click it few times in a row, one of the requests prints out zeroed slice of different length, the rest are correct. I've checked with Wireshark and the request seems correct, so the issue is in simple-server.

Move from `fn` to `Fn`

We originally used fn because there was no need, but building more complex stuff needs Fn; for example, a framework writing their own listen method will want to use stuff from self inside of the server, but you can't create that stuff inside the fn passed to new.

request method not used when matching routes

For example, the routes example exhibits incorrect behavior:

curl -X POST localhost:7878/hello

returns

<h1>Hi!</h1><p>Hello Rust!</p>

but it should return 404.

I haven't done any investigating, but didn't see this on the issues list.

thread '<unnamed>' panicked at 'Tried to unwrap Status::Partial'

I get this error regularly on an application hosted on clever cloud: it's behind haproxy with a tcp check, and it's also pinged by zabbix (with a tcp check as well).

thread '<unnamed>' panicked at 'Tried to unwrap Status::Partial', /home/bas/.cargo/registry/src/github.com-1ecc6299db9ec823/httparse-1.2.3/src/lib.rs:254:31
2017-10-17T13:43:41.361Z: thread 'main' panicked at 'Thread pool worker panicked', /home/bas/.cargo/registry/src/github.com-1ecc6299db9ec823/scoped_threadpool-0.1.8/src/lib.rs:236:12

I haven't been able to reproduce this exact error locally (with only haproxy), but I got this one instead:

thread '<unnamed>' panicked at 'Error handling connection.: Io(Error { repr: Os { code: 54, message: "Connection reset by peer" } })', src/libcore/result.rs:860:4

Can't use owned bytes for body in Server handler?

It seems like you can't use owned data (e.g., String or Vec<u8>) for the response body in the Server handler.

Unless I am missing something, this seems like a huge limitation: it prevents you from doing anything with the request data (at least easily).

It seems like you could use Cow<'static, [u8]> for the response body, or Bytes.

Am I missing something?

panics when benchmarking with wrk

I wanted to do a quick benchmark of this with the server example and wrk.

When running wrk (wrk --latency -t2 -c100 -d10s "http://localhost:7878"), the example immediately panics with:

thread '<unnamed>' panicked at 'Error handling connection.: HttpParse(Token)', /checkout/src/libcore/result.rs:860:4
note: Run with `RUST_BACKTRACE=1` for a backtrace.
thread 'main' panicked at 'Thread pool worker panicked', ...

I tried changing a few flags in wrk and running the example in both debug and release mode, but didn't want to spend much time digging in.

Not sure if wrk does anything weird to HTTP connections, but I've never seen this in any Hyper-based web servers.

This is with Rust 1.20, the master branch of this project, and wrk 4.0.2 (on Linux).

Examples crash when trying to open in Safari

Works fine in FireFox (including Nightly) and Chrome.

Versions:

  • OS: MacOS High Sierra
  • Safari: Version 11.0 (12604.1.38.1.7)
  • Kernel: Darwin hodor.local 16.7.0 Darwin Kernel Version 16.7.0: Thu Jun 15 17:36:27 PDT 2017; root:xnu-3789.70.16~2/RELEASE_X86_64 x86_64
  • rustc 1.21.0 (3b72af97e 2017-10-09)
  • cargo 0.22.0 (3423351a5 2017-10-06)
  • rustup 1.6.0 (a11c01e8c 2017-08-30)
 $ RUST_BACKTRACE=1 cargo run --example server
   Compiling simple-server v0.1.0 (file:///Users/booyaa/Dev/rust/clowns/simple-server)
    Finished dev [unoptimized + debuginfo] target(s) in 3.57 secs
     Running `target/debug/examples/server`
thread '<unnamed>' panicked at 'Tried to unwrap Status::Partial', /Users/booyaa/.cargo/registry/src/github.com-1ecc6299db9ec823/httparse-1.2.3/src/lib.rs:254:31
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
stack backtrace:
   0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
   1: std::panicking::default_hook::{{closure}}
   2: std::panicking::default_hook
   3: std::panicking::rust_panic_with_hook
   4: std::panicking::begin_panic
   5: <httparse::Status<T>>::unwrap
   6: simple_server::parse_request
   7: simple_server::Server::handle_connection
   8: simple_server::Server::listen::{{closure}}::{{closure}}
   9: <F as scoped_threadpool::FnBox>::call_box
  10: scoped_threadpool::Pool::new::{{closure}}
thread 'main' panicked at 'Thread pool worker panicked', /Users/booyaa/.cargo/registry/src/github.com-1ecc6299db9ec823/scoped_threadpool-0.1.8/src/lib.rs:236:12
stack backtrace:
   0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
   1: std::panicking::default_hook::{{closure}}
   2: std::panicking::default_hook
   3: std::panicking::rust_panic_with_hook
   4: std::panicking::begin_panic
   5: scoped_threadpool::Scope::join_all
   6: <scoped_threadpool::Scope<'pool, 'scope> as core::ops::drop::Drop>::drop
   7: core::ptr::drop_in_place
   8: scoped_threadpool::Pool::scoped
   9: simple_server::Server::listen
  10: server::main
  11: __rust_maybe_catch_panic
  12: std::rt::lang_start
  13: main

0.2.0

let's get these good changes out!

Should we add optional async?

We could leverage async in this crate in various ways.

We didn't build this on top of tokio because we wanted to keep the code simple. However, we could optionally add it in one of two ways:

First, the bit that's the same in both of them: add an async Cargo feature that lets you turn async on. That way, without the flag, you get only sync io and little deps, but if you wanted it to be async, you could do so.

Option 1: new method

This option would add an alternate Server, AsyncServer. It would look something like this:

extern crate simple_server;

use simple_server::{AsyncServer, ok};


fn main() {
    let host = "127.0.0.1";
    let port = "7878";

    let server = AsycncServer::new(|request, mut response| {
        ok(response.body("Hello Rust!".as_bytes())?)
    });

    server.listen(host, port);
}

Option 2: add futures

Option two is, change the interface of Server to be futures-enabled generally. The futures crate has no dependencies, and so is still pretty small. Then, the switch would only be in the backend, not in the interface.


The tradeoff is basically, with one, we split the "ecosystem", as it were. Option 2 adds a small amount of complexity for the simple case, but unifies everything into one API.

take handler as a closure not a function

from #1

    fn handler(request: Request<&[u8]>, response: &mut Response<&[u8]>) {
      println!("Request received. {} {}", request.method(), request.uri());
      *response.body_mut() = "Hello Rust!".as_bytes();
    };

A method that listens on a TcpListener.

I need to get the port from a TcpListener between when it is bound, and when the server starts listening, because the port isn't known until it is bound (ephemeral ports). But right now, Server::listen creates the TcpListener, so this can't be done. A method, maybe Server::listen_on_socket or something, that accepted a TcpListener would make this possible, and is very easy to add. I can open a PR if you're too busy, as long as I don't have to decide the method's name.

thread '<unnamed>' panicked at 'Error handling connection.: HttpParse(Token)'

Hi! Sorry I'm extremely new to Rust so I apologize in advance if this issue is not very useful, I'll try to provide as much information as I can!

I receive the following error very intermittently, maybe only 2-3 times over several dozen requests. I can't seem to trigger it with any pattern.

This repro occurred while requesting a 404-ing route from the routes example, but the first time I saw it I was requesting 127.0.0.1/hello which routes successfully, I just didn't have RUST_BACKTRACE=1 enabled so it didnt have a backtrace, it was the same HttpParse(Token) issue though.

  • MacOS 10.12.6 (High Sierra)
  • rustc 1.21.0 (3b72af97e 2017-10-09)
  • cargo 0.22.0 (3423351a5 2017-10-06)
  • Chrome Version 61.0.3163.100 (Official Build) (64-bit)
BHulcher-MBP:simple-server bhulcher$ RUST_LOG="simple_server=info,routes=info" RUST_BACKTRACE=1 cargo run --example routes
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/examples/routes`
INFO:simple_server: Server started at http://127.0.0.1:7878
INFO:routes: Request received. GET /fdafdhsajfdlksfhdkjsadlhjafdlkshfkjhfkl
INFO:routes: Request received. GET /fdafdhsajfdlksfhdkjsadlhjafdlkshfkjhfkl
INFO:routes: Request received. GET /fdafdhsajfdlksfhdkjsadlhjafdlkshfkjhfkl
INFO:routes: Request received. GET /fdafdhsajfdlksfhdkjsadlhjafdlkshfkjhfkl
INFO:routes: Request received. GET /fdafdhsajfdlksfhdkjsadlhjafdlkshfkjhfkl
thread '<unnamed>' panicked at 'Error handling connection.: HttpParse(Token)', src/libcore/result.rs:906:4
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
stack backtrace:
   0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
   1: std::panicking::default_hook::{{closure}}
   2: std::panicking::default_hook
   3: std::panicking::rust_panic_with_hook
   4: std::panicking::begin_panic
   5: std::panicking::begin_panic_fmt
   6: rust_begin_unwind
   7: core::panicking::panic_fmt
   8: core::result::unwrap_failed
   9: <core::result::Result<T, E>>::expect
  10: simple_server::Server::listen::{{closure}}::{{closure}}
  11: <F as scoped_threadpool::FnBox>::call_box
  12: scoped_threadpool::Pool::new::{{closure}}
thread 'main' panicked at 'Thread pool worker panicked', /Users/bhulcher/.cargo/registry/src/github.com-1ecc6299db9ec823/scoped_threadpool-0.1.8/src/lib.rs:236:12
stack backtrace:
   0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
   1: std::panicking::default_hook::{{closure}}
   2: std::panicking::default_hook
   3: std::panicking::rust_panic_with_hook
   4: std::panicking::begin_panic
   5: scoped_threadpool::Scope::join_all
   6: <scoped_threadpool::Scope<'pool, 'scope> as core::ops::drop::Drop>::drop
   7: core::ptr::drop_in_place
   8: scoped_threadpool::Pool::scoped
   9: simple_server::Server::listen
  10: routes::main
  11: __rust_maybe_catch_panic
  12: std::rt::lang_start
  13: main

Allow closures in Server::new

Server::new currently accepts only fn pointer, so if one wants to access a variable, it must be static.

Accepting closures would resolve this problem.

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.