Skip to content

Instantly share code, notes, and snippets.

@nojima
Last active July 27, 2022 01:05
Show Gist options
  • Save nojima/92e180b69f384ecbacdd718a7114dcb5 to your computer and use it in GitHub Desktop.
Save nojima/92e180b69f384ecbacdd718a7114dcb5 to your computer and use it in GitHub Desktop.
hyper + openssl で HTTPS server を立てる example
[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"
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