diff --git a/web3-proxy/src/frontend/errors.rs b/web3-proxy/src/frontend/errors.rs new file mode 100644 index 00000000..2fe25d6d --- /dev/null +++ b/web3-proxy/src/frontend/errors.rs @@ -0,0 +1,31 @@ +use crate::jsonrpc::JsonRpcForwardedResponse; +use axum::{http::StatusCode, response::IntoResponse, Json}; +use serde_json::value::RawValue; +use tracing::warn; + +/// TODO: pretty 404 page? or us a json error fine? +pub async fn handler_404() -> impl IntoResponse { + let err = anyhow::anyhow!("nothing to see here"); + + handle_anyhow_error(err, Some(StatusCode::NOT_FOUND)).await +} + +/// handle errors by converting them into something that implements `IntoResponse` +/// TODO: use this. i can't get https://docs.rs/axum/latest/axum/error_handling/index.html to work +pub async fn handle_anyhow_error( + err: anyhow::Error, + code: Option, +) -> impl IntoResponse { + // TODO: what id can we use? how do we make sure the incoming id gets attached to this? + let id = RawValue::from_string("0".to_string()).unwrap(); + + let err = JsonRpcForwardedResponse::from_anyhow_error(err, id); + + warn!("Responding with error: {:?}", err); + + let code = code.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR); + + (code, Json(err)) +} + +// i think we want a custom result type. it has an anyhow result inside. if it impl IntoResponse I think we'll get this for free diff --git a/web3-proxy/src/frontend/http.rs b/web3-proxy/src/frontend/http.rs new file mode 100644 index 00000000..f313f7f8 --- /dev/null +++ b/web3-proxy/src/frontend/http.rs @@ -0,0 +1,29 @@ +use crate::app::Web3ProxyApp; +use axum::{http::StatusCode, response::IntoResponse, Extension, Json}; +use serde_json::json; +use std::sync::Arc; + +/// a page for configuring your wallet with all the rpcs +/// TODO: check auth (from authp?) here +/// TODO: return actual html +pub async fn index() -> impl IntoResponse { + "Hello, World!" +} + +/// Very basic status page +pub async fn status(app: Extension>) -> impl IntoResponse { + let app = app.0.as_ref(); + + let balanced_rpcs = app.get_balanced_rpcs(); + let private_rpcs = app.get_private_rpcs(); + let num_active_requests = app.get_active_requests().len(); + + // TODO: what else should we include? uptime? prometheus? + let body = json!({ + "balanced_rpcs": balanced_rpcs, + "private_rpcs": private_rpcs, + "num_active_requests": num_active_requests, + }); + + (StatusCode::INTERNAL_SERVER_ERROR, Json(body)) +} diff --git a/web3-proxy/src/frontend/http_proxy.rs b/web3-proxy/src/frontend/http_proxy.rs new file mode 100644 index 00000000..0b02ff91 --- /dev/null +++ b/web3-proxy/src/frontend/http_proxy.rs @@ -0,0 +1,14 @@ +use super::errors::handle_anyhow_error; +use crate::{app::Web3ProxyApp, jsonrpc::JsonRpcRequestEnum}; +use axum::{http::StatusCode, response::IntoResponse, Extension, Json}; +use std::sync::Arc; + +pub async fn proxy_web3_rpc( + payload: Json, + app: Extension>, +) -> impl IntoResponse { + match app.0.proxy_web3_rpc(payload.0).await { + Ok(response) => (StatusCode::OK, Json(&response)).into_response(), + Err(err) => handle_anyhow_error(err, None).await.into_response(), + } +} diff --git a/web3-proxy/src/frontend/mod.rs b/web3-proxy/src/frontend/mod.rs new file mode 100644 index 00000000..fb4d2132 --- /dev/null +++ b/web3-proxy/src/frontend/mod.rs @@ -0,0 +1,41 @@ +/// this should move into web3-proxy once the basics are working +mod errors; +mod http; +mod http_proxy; +mod ws_proxy; +use axum::{ + handler::Handler, + routing::{get, post}, + Extension, Router, +}; +use std::net::SocketAddr; +use std::sync::Arc; + +use crate::app::Web3ProxyApp; + +pub async fn run(port: u16, proxy_app: Arc) -> anyhow::Result<()> { + // TODO: check auth (from authp?) here + // build our application with a route + let app = Router::new() + // `POST /` goes to `proxy_web3_rpc` + .route("/", post(http_proxy::proxy_web3_rpc)) + // `websocket /` goes to `proxy_web3_ws` + .route("/", get(ws_proxy::websocket_handler)) + // `GET /index.html` goes to `index` + .route("/index.html", get(http::index)) + // `GET /status` goes to `status` + .route("/status", get(http::status)) + .layer(Extension(proxy_app)); + + // 404 for any unknown routes + let app = app.fallback(errors::handler_404.into_service()); + + // run our app with hyper + // `axum::Server` is a re-export of `hyper::Server` + let addr = SocketAddr::from(([0, 0, 0, 0], port)); + tracing::info!("listening on port {}", addr); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .map_err(Into::into) +} diff --git a/web3-proxy/src/frontend.rs b/web3-proxy/src/frontend/ws_proxy.rs similarity index 60% rename from web3-proxy/src/frontend.rs rename to web3-proxy/src/frontend/ws_proxy.rs index 721c3372..3e8d5198 100644 --- a/web3-proxy/src/frontend.rs +++ b/web3-proxy/src/frontend/ws_proxy.rs @@ -1,11 +1,11 @@ -/// this should move into web3-proxy once the basics are working +use crate::{ + app::Web3ProxyApp, + jsonrpc::{JsonRpcForwardedResponse, JsonRpcForwardedResponseEnum, JsonRpcRequest}, +}; use axum::{ extract::ws::{Message, WebSocket, WebSocketUpgrade}, - handler::Handler, - http::StatusCode, response::IntoResponse, - routing::{get, post}, - Extension, Json, Router, + Extension, }; use futures::SinkExt; use futures::{ @@ -13,64 +13,12 @@ use futures::{ stream::{SplitSink, SplitStream, StreamExt}, }; use hashbrown::HashMap; -use serde_json::json; use serde_json::value::RawValue; -use std::net::SocketAddr; use std::str::from_utf8_mut; use std::sync::Arc; -use tracing::{debug, error, info, warn}; +use tracing::{debug, error, info}; -use crate::{ - app::Web3ProxyApp, - jsonrpc::{ - JsonRpcForwardedResponse, JsonRpcForwardedResponseEnum, JsonRpcRequest, JsonRpcRequestEnum, - }, -}; - -pub async fn run(port: u16, proxy_app: Arc) -> anyhow::Result<()> { - // build our application with a route - let app = Router::new() - // `GET /` goes to `root` - .route("/", get(root)) - // `POST /` goes to `proxy_web3_rpc` - .route("/", post(proxy_web3_rpc)) - // `websocket /` goes to `proxy_web3_ws` - .route("/ws", get(websocket_handler)) - // `GET /status` goes to `status` - .route("/status", get(status)) - .layer(Extension(proxy_app)); - - // 404 for any unknown routes - let app = app.fallback(handler_404.into_service()); - - // run our app with hyper - // `axum::Server` is a re-export of `hyper::Server` - let addr = SocketAddr::from(([0, 0, 0, 0], port)); - tracing::info!("listening on port {}", addr); - axum::Server::bind(&addr) - .serve(app.into_make_service()) - .await - .map_err(Into::into) -} - -/// a page for configuring your wallet with all the rpcs -/// TODO: check auth (from authp?) here -async fn root() -> impl IntoResponse { - "Hello, World!" -} - -/// TODO: check auth (from authp?) here -async fn proxy_web3_rpc( - payload: Json, - app: Extension>, -) -> impl IntoResponse { - match app.0.proxy_web3_rpc(payload.0).await { - Ok(response) => (StatusCode::OK, Json(&response)).into_response(), - Err(err) => _handle_anyhow_error(err, None).await.into_response(), - } -} - -async fn websocket_handler( +pub async fn websocket_handler( app: Extension>, ws: WebSocketUpgrade, ) -> impl IntoResponse { @@ -212,45 +160,3 @@ async fn write_web3_socket( }; } } - -/// Very basic status page -async fn status(app: Extension>) -> impl IntoResponse { - let app = app.0.as_ref(); - - let balanced_rpcs = app.get_balanced_rpcs(); - let private_rpcs = app.get_private_rpcs(); - let num_active_requests = app.get_active_requests().len(); - - // TODO: what else should we include? uptime? prometheus? - let body = json!({ - "balanced_rpcs": balanced_rpcs, - "private_rpcs": private_rpcs, - "num_active_requests": num_active_requests, - }); - - (StatusCode::INTERNAL_SERVER_ERROR, Json(body)) -} - -/// TODO: pretty 404 page? or us a json error fine? -async fn handler_404() -> impl IntoResponse { - let err = anyhow::anyhow!("nothing to see here"); - - _handle_anyhow_error(err, Some(StatusCode::NOT_FOUND)).await -} - -/// handle errors by converting them into something that implements `IntoResponse` -/// TODO: use this. i can't get https://docs.rs/axum/latest/axum/error_handling/index.html to work -async fn _handle_anyhow_error(err: anyhow::Error, code: Option) -> impl IntoResponse { - // TODO: what id can we use? how do we make sure the incoming id gets attached to this? - let id = RawValue::from_string("0".to_string()).unwrap(); - - let err = JsonRpcForwardedResponse::from_anyhow_error(err, id); - - warn!("Responding with error: {:?}", err); - - let code = code.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR); - - (code, Json(err)) -} - -// i think we want a custom result type. it has an anyhow result inside. it impl IntoResponse