-
Notifications
You must be signed in to change notification settings - Fork 1k
Expand file tree
/
Copy pathhttp.rs
More file actions
167 lines (151 loc) · 5.57 KB
/
http.rs
File metadata and controls
167 lines (151 loc) · 5.57 KB
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! `SpacetimeType`-ified HTTP request, response and error types,
//! for use in the procedure HTTP API.
//!
//! The types here are all mirrors of various types within the `http` crate.
//! That crate's types don't have stable representations or `pub`lic interiors,
//! so we're forced to define our own representation for the SATS serialization.
//! These types are that representation.
//!
//! Users aren't intended to interact with these types,
//! Our user-facing APIs should use the `http` crate's types directly, and convert to and from these types internally.
//!
//! These types are used in BSATN encoding for interchange between the SpacetimeDB host
//! and guest WASM modules in the `procedure_http_request` ABI call.
//! For that reason, the layout of these types must not change.
//! Because we want, to the extent possible,
//! to support both (old host, new guest) and (new host, old guest) pairings,
//! we can't meaningfully make these types extensible, even with tricks like version enum wrappers.
//! Instead, if/when we want to add new functionality which requires sending additional information,
//! we'll define a new versioned ABI call which uses new types for interchange.
use spacetimedb_sats::{time_duration::TimeDuration, SpacetimeType};
/// Represents an HTTP request which can be made from a procedure running in a SpacetimeDB database.
#[derive(Clone, SpacetimeType)]
#[sats(crate = crate, name = "HttpRequest")]
pub struct Request {
pub method: Method,
pub headers: Headers,
pub timeout: Option<TimeDuration>,
/// A valid URI, sourced from an already-validated `http::Uri`.
pub uri: String,
pub version: Version,
}
impl Request {
/// Return the size of this request's URI and [`Headers`]
/// for purposes of metrics reporting.
///
/// Ignores the size of the [`Method`] and [`Version`] as they are effectively constant.
///
/// As the body is stored externally to the `Request`, metrics reporting must count its size separately.
pub fn size_in_bytes(&self) -> usize {
self.uri.len() + self.headers.size_in_bytes()
}
}
/// Represents an HTTP method.
#[derive(Clone, SpacetimeType, PartialEq, Eq)]
#[sats(crate = crate, name = "HttpMethod")]
pub enum Method {
Get,
Head,
Post,
Put,
Delete,
Connect,
Options,
Trace,
Patch,
Extension(String),
}
/// An HTTP version.
#[derive(Clone, SpacetimeType, PartialEq, Eq)]
#[sats(crate = crate, name = "HttpVersion")]
pub enum Version {
Http09,
Http10,
Http11,
Http2,
Http3,
}
/// A set of HTTP headers.
#[derive(Clone, SpacetimeType)]
#[sats(crate = crate, name = "HttpHeaders")]
pub struct Headers {
// SATS doesn't (and won't) have a multimap type, so just use an array of pairs for the ser/de format.
entries: Box<[HttpHeaderPair]>,
}
// `http::header::IntoIter` only returns the `HeaderName` for the first
// `HeaderValue` with that name, so we have to manually assign the names.
struct HeaderIter<I, T> {
prev: Option<(Box<str>, T)>,
inner: I,
}
impl<I, T> Iterator for HeaderIter<I, T>
where
I: Iterator<Item = (Option<Box<str>>, T)>,
{
type Item = (Box<str>, T);
fn next(&mut self) -> Option<Self::Item> {
let (prev_k, prev_v) = self
.prev
.take()
.or_else(|| self.inner.next().map(|(k, v)| (k.unwrap(), v)))?;
self.prev = self
.inner
.next()
.map(|(next_k, next_v)| (next_k.unwrap_or_else(|| prev_k.clone()), next_v));
Some((prev_k, prev_v))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl FromIterator<(Option<Box<str>>, Box<[u8]>)> for Headers {
fn from_iter<T: IntoIterator<Item = (Option<Box<str>>, Box<[u8]>)>>(iter: T) -> Self {
let inner = iter.into_iter();
let entries = HeaderIter { prev: None, inner }
.map(|(name, value)| HttpHeaderPair { name, value })
.collect();
Self { entries }
}
}
impl Headers {
#[allow(clippy::should_implement_trait)]
pub fn into_iter(self) -> impl Iterator<Item = (Box<str>, Box<[u8]>)> {
IntoIterator::into_iter(self.entries).map(|HttpHeaderPair { name, value }| (name, value))
}
/// The sum of the lengths of all the header names and header values.
///
/// For headers with multiple values for the same header name,
/// the length of the header name is counted once for each occurence.
fn size_in_bytes(&self) -> usize {
self.entries
.iter()
.map(|HttpHeaderPair { name, value }| name.len() + value.len())
.sum::<usize>()
}
}
#[derive(Clone, SpacetimeType)]
#[sats(crate = crate, name = "HttpHeaderPair")]
struct HttpHeaderPair {
/// A valid HTTP header name, sourced from an already-validated `http::HeaderName`.
name: Box<str>,
/// A valid HTTP header value, sourced from an already-validated `http::HeaderValue`.
value: Box<[u8]>,
}
#[derive(Clone, SpacetimeType)]
#[sats(crate = crate, name = "HttpResponse")]
pub struct Response {
pub headers: Headers,
pub version: Version,
/// A valid HTTP response status code, sourced from an already-validated `http::StatusCode`.
pub code: u16,
}
impl Response {
/// Return the size of this request's [`Headers`] for purposes of metrics reporting.
///
/// Ignores the size of the `code` and [`Version`] as they are effectively constant.
///
/// As the body is stored externally to the `Response`, metrics reporting must count its size separately.
pub fn size_in_bytes(&self) -> usize {
self.headers.size_in_bytes()
}
}