Last active
July 27, 2022 01:05
-
-
Save nojima/92e180b69f384ecbacdd718a7114dcb5 to your computer and use it in GitHub Desktop.
hyper + openssl で HTTPS server を立てる example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "ssl-exp" | |
version = "0.1.0" | |
edition = "2021" | |
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | |
[dependencies] | |
hyper = { version = "0.14", features = ["full"] } | |
tokio = { version = "1", features = ["full"] } | |
openssl = "0.10" | |
anyhow = "1.0.58" | |
tokio-openssl = "0.6.3" | |
futures = "0.3.21" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::{ | |
io, | |
pin::Pin, | |
task::{Context, Poll}, | |
}; | |
use anyhow; | |
use futures::{Stream, StreamExt}; | |
use hyper::{ | |
server::accept::Accept, | |
service::{make_service_fn, service_fn}, | |
Body, Method, Request, Response, Server, StatusCode, | |
}; | |
use openssl::ssl::{Ssl, SslAcceptor, SslFiletype, SslMethod}; | |
use tokio::net::{TcpListener, TcpStream}; | |
use tokio_openssl::SslStream; | |
#[tokio::main] | |
async fn main() -> anyhow::Result<()> { | |
let mut builder = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls())?; | |
builder.set_private_key_file("key.pem", SslFiletype::PEM)?; | |
builder.set_certificate_chain_file("cert.pem")?; | |
builder.check_private_key()?; | |
let acceptor = builder.build(); | |
let tcp = TcpListener::bind("0.0.0.0:8443").await?; | |
let incoming_stream = futures::stream::unfold((), |_| async { | |
let (conn, _) = match tcp.accept().await { | |
Ok(x) => x, | |
Err(e) => return Some((Err(e), ())), | |
}; | |
let ssl = match Ssl::new(acceptor.context()) { | |
Ok(s) => s, | |
Err(e) => return Some((Err(e.into()), ())), | |
}; | |
let mut ssl_conn = match SslStream::new(ssl, conn) { | |
Ok(c) => c, | |
Err(e) => return Some((Err(e.into()), ())), | |
}; | |
if let Err(e) = Pin::new(&mut ssl_conn).accept().await { | |
let e = io::Error::new(io::ErrorKind::Other, format!("failed to SSL_accept: {e}")); | |
return Some((Err(e), ())); | |
} | |
Some((Ok(ssl_conn), ())) | |
}); | |
// A `Service` is needed for every connection, so this | |
// creates one from our `hello_world` function. | |
let make_svc = make_service_fn(|_conn| async { | |
// service_fn converts our function into a `Service` | |
Ok::<_, hyper::Error>(service_fn(echo)) | |
}); | |
Server::builder(IncomingStreamAcceptor { | |
stream: Box::pin(incoming_stream), | |
}) | |
.serve(make_svc) | |
.await?; | |
Ok(()) | |
} | |
struct IncomingStreamAcceptor<S> { | |
stream: Pin<Box<S>>, | |
} | |
impl<S> Accept for IncomingStreamAcceptor<S> | |
where | |
S: Stream<Item = Result<SslStream<TcpStream>, io::Error>>, | |
{ | |
type Conn = SslStream<TcpStream>; | |
type Error = io::Error; | |
fn poll_accept( | |
mut self: Pin<&mut Self>, | |
cx: &mut Context<'_>, | |
) -> Poll<Option<Result<Self::Conn, Self::Error>>> { | |
self.stream.poll_next_unpin(cx) | |
} | |
} | |
async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> { | |
match (req.method(), req.uri().path()) { | |
// Serve some instructions at / | |
(&Method::GET, "/") => Ok(Response::new(Body::from( | |
"Try POSTing data to /echo such as: `curl localhost:3000/echo -XPOST -d 'hello world'`", | |
))), | |
// Simply echo the body back to the client. | |
(&Method::POST, "/echo") => Ok(Response::new(req.into_body())), | |
// Reverse the entire body before sending back to the client. | |
// | |
// Since we don't know the end yet, we can't simply stream | |
// the chunks as they arrive as we did with the above uppercase endpoint. | |
// So here we do `.await` on the future, waiting on concatenating the full body, | |
// then afterwards the content can be reversed. Only then can we return a `Response`. | |
(&Method::POST, "/echo/reversed") => { | |
let whole_body = hyper::body::to_bytes(req.into_body()).await?; | |
let reversed_body = whole_body.iter().rev().cloned().collect::<Vec<u8>>(); | |
Ok(Response::new(Body::from(reversed_body))) | |
} | |
// Return the 404 Not Found for other routes. | |
_ => { | |
let mut not_found = Response::default(); | |
*not_found.status_mut() = StatusCode::NOT_FOUND; | |
Ok(not_found) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment