gateway/src/proxy/route.rs

97 lines
3.0 KiB
Rust

use core::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
use std::path::PathBuf;
use hyper::{
client::{Client, HttpConnector},
http::{
uri::{Authority, Parts, Scheme},
Request, Response,
},
service::Service,
Body, Uri,
};
use serde::Deserialize;
/// Represents a route that a ProxyServer can send requests to. This struct also implements the
/// `hyper::service::Service` trait, meaning it can be used as a service for a hyper server to
/// consume. This is used in combination with `crate::proxy::ProxyServer` to create a modular proxy
/// server.
///
/// # Fields
///
/// * `route` - Which prefix of route should be matched. This route prefix will then be replaced by
/// the provided prefix when routing.
/// * `host` - The host that request should be proxied to. This variable should also include the
/// port number if necessary.
/// * `prefix` - The prefix with which to replace the routed prefix. In practice, this means that
/// the proxy can use a completely different prefix for routes, e.g. `/api/posts` on the proxy site
/// & `/v1/api/posts` on the proxied server site.
/// * `timeout` - How long the proxy should wait before aborting the request.
#[derive(Deserialize)]
pub struct Route
{
route: PathBuf,
host: String,
prefix: PathBuf,
}
impl Route
{
pub fn new(route: PathBuf, host: String, prefix: PathBuf) -> Route
{
Route {
route,
host,
prefix,
}
}
}
impl Service<Request<Body>> for Route
{
type Response = Response<Body>;
type Error = hyper::http::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
/// This function lets hyper know when the service is ready to process requests.
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>
{
Poll::Ready(Ok(()))
}
/// Processes incoming requests by proxying them to other hosts.
fn call(&mut self, req: Request<Body>) -> Self::Future
{
let fut = async {
// Convert the request's uri to match the redirected one
let req_parts = req.uri().clone().into_parts();
let mut parts = Parts::default();
parts.scheme = req_parts.scheme.or_else(|| Some(Scheme::HTTP));
parts.authority = Some(Authority::from_static("localhost:5000"));
parts.path_and_query = req_parts.path_and_query;
let new_uri = Uri::from_parts(parts).unwrap();
*req.uri_mut() = new_uri;
// Create a new client that can send the requests
let client: Client<HttpConnector, Body> = Client::builder().build_http();
// match client.request
// NOTE: when should we return an Err here?
match client.request(req).await {
Ok(res) => Ok(res),
Err(_) => Ok(hyper::http::response::Builder::new()
.status(500)
.body(Body::empty())
.unwrap()),
}
};
Box::pin(fut)
}
}