Running an app on heroku requires at least one entry point responding to http. An easy way to do this is to use hyper to create a simple web service.
Setup
cargo new hello_rust --bin
cd hello_rust
git init
git add .
git commit -m "cargo new hello_rust --bin"
add to Cargo.toml
:
[dependencies]
hyper = "0.13"
Web service code
The core code to set up a little web service has a few key parts:
- the service (
async fn hello
)- an async function that takes a
hyper::Request
and returns ahyper::Response
in theResult
- Request is generic over the Body, so it seems nifty to be able to provide our own Rust types for specific content (like JSON) and also for validating API POST params
- Result is Infallible: a Rust error type signifying that the function never returns an error
- an async function that takes a
- make_service_fn — docs are a bit sparse on this, but I think all it does it generate an instance of the service with the Request context that so that each request can run concurrently
- Server::bind(&addr).serve(…) — Hyper uses a builder pattern where Server::bind generates a Builder, where you can configure http1/2 support and then a running instance of the Server is created by calling the serve method with the service function.
To see this in action, replace main.rs
with this code:
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server};
use std::convert::Infallible;
async fn hello(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new(Body::from(
"<HTML><H1>Hello World!</H1><HTML>",
)))
}
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let make_svc = make_service_fn(|_conn| {
async { Ok::<_, Infallible>(service_fn(hello)) }
});
let addr = ([0, 0, 0, 0], 3000).into();
let server = Server::bind(&addr).serve(make_svc);
println!("Listening on {}", addr);
server.await?;
Ok(())
}
to run the app locally:
cargo run
then in the browser, go to http://localhost:3000/
Heroku setup
1. Heroku CLI
Install heroku CLI or if you already have it:
heroku update
Then to set up the app on heroku:
heroku create --buildpack emk/rust
2. Procfile
Add a Procfile for heroku to know what to call when it receives a web request:
echo "web: ./target/release/hello_rust" >> Procfile
3. Port configuration
Heroku requires that we listen on the port specified with the PORT
env var. So, add the following code and replace the hard-coded port number with this variable:
let port = env::var("PORT")
.unwrap_or_else(|_| "3000".to_string())
.parse()
.expect("PORT must be a number");
4. Deploy!
Deploy the app by pushing code to the Heroku remote repository that was set up by the CLI in step 1.
git push heroku master
Full code for the app is on github.com/ultrasaurus/hello-heroku-rust
Background
My environment info (rustup show
):
stable-x86_64-apple-darwin (default)
rustc 1.39.0 (4560ea788 2019-11-04)