Skip to content

Instantly share code, notes, and snippets.

@walfie
Last active February 16, 2018 02:42
Show Gist options
  • Save walfie/ceef93b10e80d895b390aae2d0223b82 to your computer and use it in GitHub Desktop.
Save walfie/ceef93b10e80d895b390aae2d0223b82 to your computer and use it in GitHub Desktop.
h2 with tokio-rustls
[package]
name = "http2-example"
version = "0.1.0"
authors = ["Walfie"]
[dependencies]
futures = "0.1.18"
h2 = "0.1.0"
http = "0.1.4"
rustls = "0.12.0"
tokio = "0.1.1"
tokio-rustls = "0.5.0"
tokio-timer = "0.1.2"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<ul id="items"></ul>
<script>
// User needs to go to `https://localhost:9000` and dismiss the security
// warning first before this will work
var url = 'https://localhost:9000/';
var items = document.getElementById('items');
fetch(url).then(function(response) {
var next = function(reader) {
return reader.read().then(function(result) {
var li = document.createElement('li');
if (result.done) {
li.innerHTML = 'done';
items.appendChild(li);
return;
}
// retrieve the multi-byte chunk of data
var chunk = result.value;
var encodedString = String.fromCharCode.apply(null, chunk);
var decodedString = decodeURIComponent(escape(encodedString));
li.innerHTML = decodedString;
items.appendChild(li);
return next(reader);
});
};
return next(response.body.getReader());
}).catch(function(e) {
var li = document.createElement('li');
li.innerHTML = e;
items.appendChild(li);
});
</script>
</body>
</html>
/// Based on https://github.com/quininer/tokio-rustls/blob/master/examples/server.rs
///
/// ```bash
/// openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem
/// CERT_FILE=./certificate.pem KEY_FILE=./key.pem cargo run
/// curl --insecure https://localhost:9000
/// ```
extern crate futures;
extern crate h2;
extern crate http;
extern crate rustls;
extern crate tokio;
extern crate tokio_rustls;
extern crate tokio_timer;
use futures::{Future, Stream};
use h2::server;
use http::{Response, StatusCode};
use rustls::{Certificate, NoClientAuth, PrivateKey, ServerConfig};
use rustls::internal::pemfile::{certs, rsa_private_keys, pkcs8_private_keys};
use std::fs::File;
use std::io::BufReader;
use std::time::Duration;
use tokio::executor::current_thread;
use tokio::net::TcpListener;
use tokio_rustls::ServerConfigExt;
use tokio_timer::Timer;
fn load_file(path: &str) -> BufReader<File> {
BufReader::new(File::open(path).expect(&format!("Could not load file: {}", path)))
}
fn load_certs(path: &str) -> Vec<Certificate> {
certs(&mut load_file(path)).expect("failed to load certs")
}
fn load_private_key(path: &str) -> PrivateKey {
let pkcs8 = pkcs8_private_keys(&mut load_file(path)).expect("failed to load pkcs8 keys");
let rsa = rsa_private_keys(&mut load_file(path)).expect("failed to load rsa keys");
if !pkcs8.is_empty() {
pkcs8[0].clone()
} else {
assert!(!rsa.is_empty());
rsa[0].clone()
}
}
fn main() {
let addr = "127.0.0.1:9000".parse().unwrap();
let listener = TcpListener::bind(&addr).unwrap();
let cert_file = std::env::var("CERT_FILE").expect("CERT_FILE undefined");
let key_file = std::env::var("KEY_FILE").expect("KEY_FILE undefined");
let tls_config = std::sync::Arc::new({
let mut config = ServerConfig::new(NoClientAuth::new());
config.set_single_cert(load_certs(&cert_file), load_private_key(&key_file));
config.alpn_protocols.push("h2".to_owned());
config
});
let worker = listener
.incoming()
.for_each(move |socket| {
println!("Accepted connection: {:?}", socket);
let connection = tls_config
.accept_async(socket)
.map_err(|e| eprintln!("Error: {:?}", e))
.and_then(|stream| {
// Start the HTTP/2.0 connection handshake
server::handshake(stream)
.and_then(|h2| {
// Accept all inbound HTTP/2.0 streams sent over the
// connection.
h2.for_each(|(request, mut respond)| {
println!("Received request: {:?}", request);
// Build a response with no body
let response = Response::builder()
.header("Access-Control-Allow-Origin", "*")
.status(StatusCode::OK)
.body(())
.unwrap();
// Send the response back to the client
let mut send_stream =
respond.send_response(response, false).unwrap();
let mut count = 0;
let body = Timer::default()
.interval(Duration::from_secs(1))
.map_err(|_| ())
.for_each(move |_| {
let bytes = format!("{}\n", count).as_bytes().into();
count += 1;
send_stream
.send_data(bytes, false)
.map_err(|e| eprintln!("Error sending data: {:?}", e))
});
current_thread::spawn(body);
Ok(())
})
})
.map_err(|e| eprintln!("Error: {:?}", e))
});
// Spawn a new task to process each connection.
current_thread::spawn(connection);
Ok(())
})
.map_err(|e| eprintln!("Error: {:?}", e));
current_thread::run(|_| {
println!("Listening on {}", addr);
current_thread::spawn(worker);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment