1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//! Operations related to handling the "downstream" (end-client) request
use std::net::SocketAddr;
use crate::body::Body;
use crate::error::DownstreamRequestError;
use crate::handoff::HandoffInfo;
use crate::sandbox::PendingResponse;
use http::{HeaderMap, Request, Response};
use hyper::Uri;
use tokio::sync::mpsc::{self, Receiver, Sender};
#[derive(Debug)]
pub struct DownstreamMetadata {
// A unique request ID.
pub req_id: u64,
/// The IP address and port that received this request.
pub server_addr: SocketAddr,
/// The downstream IP address and port for this request.
pub client_addr: SocketAddr,
/// The compliance region that this request was received in.
///
/// For now this is just always `"none"`, but we place the field in the sandbox
/// to make it easier to implement custom configuration values later on.
pub compliance_region: String,
/// The originally received headers in this request, before the
/// guest potentially modified them.
pub original_headers: HeaderMap,
}
#[derive(Debug)]
pub struct DownstreamRequest {
pub req: hyper::Request<Body>,
pub metadata: DownstreamMetadata,
pub sender: Sender<DownstreamResponse>,
}
impl DownstreamRequest {
pub fn new(
req: hyper::Request<Body>,
metadata: DownstreamMetadata,
) -> (Self, Receiver<DownstreamResponse>) {
let (sender, receiver) = mpsc::channel(10);
let req = Self {
req,
metadata,
sender,
};
(req, receiver)
}
}
#[derive(Debug)]
pub enum DownstreamResponse {
/// An HTTP response to send back downstream.
Http(Response<Body>),
/// A potential [Response] to an in-flight request that should be sent
/// downstream once it resolves.
///
/// If the in-flight request fails, then the `Error` will be converted
/// to an appropriate 5XX HTTP status code.
Pending(Box<PendingResponse>),
/// Redirect the WebSocket request to pushpin.
HandoffToPushpin(HandoffInfo),
/// Redirect the WebSocket request to a backend.
HandoffToBackend(HandoffInfo),
}
/// Canonicalize the incoming request into the form expected by host calls.
///
/// The primary canonicalization is to provide an absolute URL (with authority), using the HOST
/// header of the request.
pub fn prepare_request(req: Request<hyper::Body>) -> Result<Request<Body>, DownstreamRequestError> {
let (mut metadata, body) = req.into_parts();
let uri_parts = metadata.uri.into_parts();
// Prefer to find the host from the HOST header, rather than the URL.
let http_host = if let Some(host_header) = metadata.headers.get(http::header::HOST) {
std::str::from_utf8(host_header.as_bytes())
.map_err(|_| DownstreamRequestError::InvalidHost)?
} else {
uri_parts
.authority
.as_ref()
.ok_or(DownstreamRequestError::InvalidHost)?
.host()
};
// rebuild the request URI, replacing the authority with only the host and ensuring there is
// a path and scheme
let path_and_query = uri_parts
.path_and_query
.ok_or(DownstreamRequestError::InvalidUrl)?;
let scheme = uri_parts.scheme.unwrap_or(http::uri::Scheme::HTTP);
metadata.uri = Uri::builder()
.scheme(scheme)
.authority(http_host)
.path_and_query(path_and_query)
.build()
.map_err(|_| DownstreamRequestError::InvalidUrl)?;
Ok(Request::from_parts(metadata, Body::from(body)))
}