Skip to content

Commit

Permalink
More beginner-friendly TCP server example (#102)
Browse files Browse the repository at this point in the history
I wanted to write an example to help guide people new to asynchronous programming, with fewer external dependencies (as some people requested in #66). The example starts with the [single threaded TCP server from the Rust book](https://doc.rust-lang.org/book/ch20-01-single-threaded.html) and uses async functionality to add concurrency.
The example goes through:
- some examples of blocking code and how to make them non blocking (should address #64 as well)
- spawning tasks vs using combinators to run them concurrently on a single thread
- testing code by creating mocks and implementing async read/write traits for the mocks
  • Loading branch information
lbernick committed Sep 16, 2020
1 parent 1865ce0 commit 87015e2
Show file tree
Hide file tree
Showing 27 changed files with 597 additions and 199 deletions.
12 changes: 12 additions & 0 deletions ci/dictionary.txt
Expand Up @@ -24,6 +24,8 @@ FutOne
FutTwo
FuturesUnordered
GenFuture
gRPC
html
http
Hyper's
impl
Expand All @@ -33,8 +35,13 @@ IoBlocker
IOCP
IoObject
kqueue
localhost
LocalExecutor
metadata
MockTcpStream
multi
multithreaded
multithreading
Mutex
MyError
MyFut
Expand All @@ -51,15 +58,18 @@ proxying
pseudocode
ReadIntoBuf
recognise
refactor
RefCell
repurposed
requeue
ResponseFuture
reusability
runtime
runtimes
rustc
rustup
SimpleFuture
smol
SocketRead
SomeType
spawner
Expand All @@ -69,6 +79,8 @@ struct
subfuture
subfutures
subpar
TcpListener
TcpStream
threadpool
TimerFuture
TODO
Expand Down
18 changes: 0 additions & 18 deletions examples/01_05_http_server/Cargo.toml

This file was deleted.

91 changes: 0 additions & 91 deletions examples/01_05_http_server/src/lib.rs

This file was deleted.

15 changes: 10 additions & 5 deletions examples/02_04_executor/src/lib.rs
Expand Up @@ -3,13 +3,13 @@
// ANCHOR: imports
use {
futures::{
future::{FutureExt, BoxFuture},
task::{ArcWake, waker_ref},
future::{BoxFuture, FutureExt},
task::{waker_ref, ArcWake},
},
std::{
future::Future,
sync::mpsc::{sync_channel, Receiver, SyncSender},
sync::{Arc, Mutex},
sync::mpsc::{sync_channel, SyncSender, Receiver},
task::{Context, Poll},
time::Duration,
},
Expand Down Expand Up @@ -74,7 +74,10 @@ impl ArcWake for Task {
// Implement `wake` by sending this task back onto the task channel
// so that it will be polled again by the executor.
let cloned = arc_self.clone();
arc_self.task_sender.send(cloned).expect("too many tasks queued");
arc_self
.task_sender
.send(cloned)
.expect("too many tasks queued");
}
}
// ANCHOR_END: arcwake_for_task
Expand Down Expand Up @@ -128,4 +131,6 @@ fn main() {
// ANCHOR_END: main

#[test]
fn run_main() { main() }
fn run_main() {
main()
}
11 changes: 11 additions & 0 deletions examples/08_01_sync_tcp_server/404.html
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello!</title>
</head>
<body>
<h1>Oops!</h1>
<p>Sorry, I don't know what you're asking for.</p>
</body>
</html>
9 changes: 9 additions & 0 deletions examples/08_01_sync_tcp_server/Cargo.toml
@@ -0,0 +1,9 @@
[package]
name = "sync_tcp_server"
version = "0.1.0"
authors = ["Your Name <you@example.com"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
11 changes: 11 additions & 0 deletions examples/08_01_sync_tcp_server/hello.html
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello!</title>
</head>
<body>
<h1>Hello!</h1>
<p>Hi from Rust</p>
</body>
</html>
39 changes: 39 additions & 0 deletions examples/08_01_sync_tcp_server/src/main.rs
@@ -0,0 +1,39 @@
use std::fs;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;

fn main() {
// Listen for incoming TCP connections on localhost port 7878
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();

// Block forever, handling each request that arrives at this IP address
for stream in listener.incoming() {
let stream = stream.unwrap();

handle_connection(stream);
}
}

fn handle_connection(mut stream: TcpStream) {
// Read the first 1024 bytes of data from the stream
let mut buffer = [0; 1024];
stream.read(&mut buffer).unwrap();

let get = b"GET / HTTP/1.1\r\n";

// Respond with greetings or a 404,
// depending on the data in the request
let (status_line, filename) = if buffer.starts_with(get) {
("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
} else {
("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
};
let contents = fs::read_to_string(filename).unwrap();

// Write response back to the stream,
// and flush the stream to ensure the response is sent back to the client
let response = format!("{}{}", status_line, contents);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
11 changes: 11 additions & 0 deletions examples/08_02_async_tcp_server/Cargo.toml
@@ -0,0 +1,11 @@
[package]
name = "async_tcp_server"
version = "0.1.0"
authors = ["Your Name <you@example.com"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies.async-std]
version = "1.6"
features = ["attributes"]
20 changes: 20 additions & 0 deletions examples/08_02_async_tcp_server/src/main.rs
@@ -0,0 +1,20 @@
use std::net::TcpListener;
use std::net::TcpStream;

// ANCHOR: main_func
#[async_std::main]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
// Warning: This is not concurrent!
handle_connection(stream).await;
}
}
// ANCHOR_END: main_func

// ANCHOR: handle_connection_async
async fn handle_connection(mut stream: TcpStream) {
//<-- snip -->
}
// ANCHOR_END: handle_connection_async
11 changes: 11 additions & 0 deletions examples/08_03_slow_request/Cargo.toml
@@ -0,0 +1,11 @@
[package]
name = "slow_request"
version = "0.1.0"
authors = ["Your Name <you@example.com"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies.async-std]
version = "1.6"
features = ["attributes"]
40 changes: 40 additions & 0 deletions examples/08_03_slow_request/src/main.rs
@@ -0,0 +1,40 @@
use std::fs;
use std::io::{Read, Write};
use std::net::TcpListener;
use std::net::TcpStream;
use std::time::Duration;

#[async_std::main]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
handle_connection(stream).await;
}
}

// ANCHOR: handle_connection
use async_std::task;

async fn handle_connection(mut stream: TcpStream) {
let mut buffer = [0; 1024];
stream.read(&mut buffer).unwrap();

let get = b"GET / HTTP/1.1\r\n";
let sleep = b"GET /sleep HTTP/1.1\r\n";

let (status_line, filename) = if buffer.starts_with(get) {
("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
} else if buffer.starts_with(sleep) {
task::sleep(Duration::from_secs(5)).await;
("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
} else {
("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
};
let contents = fs::read_to_string(filename).unwrap();

let response = format!("{}{}", status_line, contents);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
// ANCHOR_END: handle_connection
14 changes: 14 additions & 0 deletions examples/08_04_concurrent_tcp_server/Cargo.toml
@@ -0,0 +1,14 @@
[package]
name = "concurrent_tcp_server"
version = "0.1.0"
authors = ["Your Name <you@example.com"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
futures = "0.3"

[dependencies.async-std]
version = "1.6"
features = ["attributes"]

0 comments on commit 87015e2

Please sign in to comment.