web3-proxy/web3_proxy/src/frontend/mod.rs

135 lines
5.1 KiB
Rust
Raw Normal View History

2022-10-18 00:47:58 +03:00
//! `frontend` contains HTTP and websocket endpoints for use by users and admins.
pub mod authorization;
2022-06-05 22:58:47 +03:00
mod errors;
2022-10-18 00:47:58 +03:00
// TODO: these are only public so docs are generated. What's a better way to do this?
pub mod rpc_proxy_http;
pub mod rpc_proxy_ws;
pub mod status;
pub mod users;
2022-07-07 06:22:09 +03:00
2022-08-16 22:29:00 +03:00
use crate::app::Web3ProxyApp;
2022-06-05 22:58:47 +03:00
use axum::{
2022-08-16 03:33:26 +03:00
body::Body,
2022-06-05 22:58:47 +03:00
handler::Handler,
routing::{get, post},
Extension, Router,
};
2022-09-25 19:35:01 +03:00
use http::header::AUTHORIZATION;
use http::Request;
use std::iter::once;
use std::net::SocketAddr;
2022-06-05 22:58:47 +03:00
use std::sync::Arc;
2022-09-25 07:26:13 +03:00
use tower_http::cors::CorsLayer;
2022-09-25 19:35:01 +03:00
use tower_http::sensitive_headers::SetSensitiveRequestHeadersLayer;
2022-08-16 03:33:26 +03:00
use tower_http::trace::TraceLayer;
use tower_request_id::{RequestId, RequestIdLayer};
use tracing::{error_span, info};
2022-06-15 04:02:26 +03:00
2022-10-18 00:47:58 +03:00
/// Start the frontend server.
pub async fn serve(port: u16, proxy_app: Arc<Web3ProxyApp>) -> anyhow::Result<()> {
2022-08-19 23:18:12 +03:00
// create a tracing span for each request with a random request id and the method
// GET: websocket or static pages
// POST: http rpc or login
2022-08-16 03:33:26 +03:00
let request_tracing_layer =
TraceLayer::new_for_http().make_span_with(|request: &Request<Body>| {
// We get the request id from the extensions
let request_id = request
.extensions()
.get::<RequestId>()
.map(ToString::to_string)
.unwrap_or_else(|| "unknown".into());
// And then we put it along with other information into the `request` span
error_span!(
"http_request",
id = %request_id,
// TODO: do we want these?
method = %request.method(),
// uri = %request.uri(),
)
});
2022-08-21 11:18:57 +03:00
// build our axum Router
2022-06-05 22:58:47 +03:00
let app = Router::new()
2022-10-18 00:47:58 +03:00
// routes should be ordered most to least common
2022-09-24 07:31:06 +03:00
.route("/rpc", post(rpc_proxy_http::proxy_web3_rpc))
2022-09-24 08:53:45 +03:00
.route("/rpc", get(rpc_proxy_ws::websocket_handler))
2022-09-24 07:31:06 +03:00
.route(
"/rpc/:user_key",
post(rpc_proxy_http::proxy_web3_rpc_with_key),
)
2022-09-24 08:53:45 +03:00
.route(
"/rpc/:user_key",
get(rpc_proxy_ws::websocket_handler_with_key),
)
2022-09-25 19:37:45 +03:00
.route("/health", get(status::health))
2022-10-18 00:47:58 +03:00
.route("/user/login/:user_address", get(users::user_login_get))
.route(
2022-09-25 19:37:45 +03:00
"/user/login/:user_address/:message_eip",
2022-10-18 00:47:58 +03:00
get(users::user_login_get),
)
2022-10-18 00:47:58 +03:00
.route("/user/login", post(users::user_login_post))
.route("/user/balance", get(users::user_balance_get))
.route("/user/balance/:txid", post(users::user_balance_post))
.route("/user/profile", get(users::user_profile_get))
.route("/user/profile", post(users::user_profile_post))
.route("/user/stats", get(users::user_stats_get))
2022-10-19 03:56:57 +03:00
.route(
"/user/stats/aggregate",
get(users::user_stats_aggregate_get),
)
2022-10-18 00:47:58 +03:00
.route("/user/logout", post(users::user_logout_post))
.route("/status", get(status::status))
// TODO: make this optional or remove it since it is available on another port
.route("/prometheus", get(status::prometheus))
2022-08-21 11:18:57 +03:00
// layers are ordered bottom up
// the last layer is first for requests and last for responses
2022-09-25 19:35:01 +03:00
// Mark the `Authorization` request header as sensitive so it doesn't show in logs
.layer(SetSensitiveRequestHeadersLayer::new(once(AUTHORIZATION)))
2022-08-21 11:18:57 +03:00
// add the request id to our tracing logs
2022-08-16 03:33:26 +03:00
.layer(request_tracing_layer)
2022-09-25 07:26:13 +03:00
// handle cors
.layer(CorsLayer::very_permissive())
2022-08-21 11:18:57 +03:00
// create a unique id for each request
2022-08-16 03:33:26 +03:00
.layer(RequestIdLayer)
2022-09-25 19:35:01 +03:00
// application state
.layer(Extension(proxy_app))
2022-08-07 22:35:24 +03:00
// 404 for any unknown routes
.fallback(errors::handler_404.into_service());
2022-06-05 22:58:47 +03:00
// run our app with hyper
2022-08-16 03:33:26 +03:00
// TODO: allow only listening on localhost? top_config.app.host.parse()?
2022-06-05 22:58:47 +03:00
let addr = SocketAddr::from(([0, 0, 0, 0], port));
2022-08-06 04:17:25 +03:00
info!("listening on port {}", port);
2022-08-11 05:57:01 +03:00
2022-08-26 20:26:17 +03:00
// TODO: into_make_service is enough if we always run behind a proxy. make into_make_service_with_connect_info optional?
2022-08-11 05:57:01 +03:00
/*
It sequentially looks for an IP in:
- x-forwarded-for header (de-facto standard)
- x-real-ip header
- forwarded header (new standard)
- axum::extract::ConnectInfo (if not behind proxy)
*/
let service = app.into_make_service_with_connect_info::<SocketAddr>();
// let service = app.into_make_service();
// `axum::Server` is a re-export of `hyper::Server`
2022-06-05 22:58:47 +03:00
axum::Server::bind(&addr)
2022-08-07 09:48:57 +03:00
// TODO: option to use with_connect_info. we want it in dev, but not when running behind a proxy, but not
2022-08-11 05:57:01 +03:00
.serve(service)
.with_graceful_shutdown(signal_shutdown())
2022-06-05 22:58:47 +03:00
.await
.map_err(Into::into)
}
/// Tokio signal handler that will wait for a user to press CTRL+C.
2022-10-18 00:47:58 +03:00
/// Used in our hyper `Server` method `with_graceful_shutdown`.
async fn signal_shutdown() {
2022-10-18 00:47:58 +03:00
// TODO: take a shutdown_receiver and select on ctrl_c and it
info!("ctrl-c to quit");
tokio::signal::ctrl_c()
.await
.expect("expect tokio signal ctrl-c");
info!("signal shutdown");
}