Skip to content

Commit 9ae16e7

Browse files
authored
RFC: RequestId for services (#1942)
* RFC 24 RequestId Signed-off-by: Daniele Ahmed <[email protected]>
1 parent b67f492 commit 9ae16e7

File tree

4 files changed

+140
-1
lines changed

4 files changed

+140
-1
lines changed

design/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
- [RFC-0021: Dependency Versions](./rfcs/rfc0021_dependency_versions.md)
4545
- [RFC-0022: Error Context and Compatibility](./rfcs/rfc0022_error_context_and_compatibility.md)
4646
- [RFC-0023: Evolving the new service builder API](./rfcs/rfc0023_refine_builder.md)
47+
- [RFC-0024: RequestID](./rfcs/rfc0024_request_id.md)
4748

4849
- [Contributing](./contributing/overview.md)
4950
- [Writing and debugging a low-level feature that relies on HTTP](./contributing/writing_and_debugging_a_low-level_feature_that_relies_on_HTTP.md)

design/src/rfcs/overview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@
3333
- [RFC-0021: Dependency Versions](./rfc0021_dependency_versions.md)
3434
- [RFC-0022: Error Context and Compatibility](./rfc0022_error_context_and_compatibility.md)
3535
- [RFC-0023: Evolving the new service builder API](./rfc0023_refine_builder.md)
36+
- [RFC-0024: RequestID](./rfc0024_request_id.md)

design/src/rfcs/rfc0022_error_context_and_compatibility.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ What could be improved:
185185

186186
### Hyper 1.0
187187

188-
Hyper is has outlined [some problems they want to address with errors](https://github.com/hyperium/hyper/blob/bd7928f3dd6a8461f0f0fdf7ee0fd95c2f156f88/docs/ROADMAP.md#errors)
188+
Hyper has outlined [some problems they want to address with errors](https://github.com/hyperium/hyper/blob/bd7928f3dd6a8461f0f0fdf7ee0fd95c2f156f88/docs/ROADMAP.md#errors)
189189
for the coming 1.0 release. To summarize:
190190

191191
- It's difficult to match on specific errors (Hyper 0.x's `Error` relies

design/src/rfcs/rfc0024_request_id.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
RFC: RequestID in business logic handlers
2+
=============
3+
4+
> Status: RFC
5+
>
6+
> Applies to: server
7+
8+
For a summarized list of proposed changes, see the [Changes Checklist](#changes-checklist) section.
9+
10+
Terminology
11+
-----------
12+
13+
- **RequestID**: a service-wide request's unique identifier
14+
- **UUID**: a universally unique identifier
15+
16+
RequestID is an element that uniquely identifies a client request. RequestID is used by services to map all logs, events and
17+
specific data to a single operation. This RFC discusses whether and how smithy-rs can make that value available to customers.
18+
19+
Services use a RequestID to collect logs related to the same request and see its flow through the various operations,
20+
help clients debug requests by sharing this value and, in some cases, use this value to perform their business logic. RequestID is unique across a service at least within a certain timeframe.
21+
22+
This value for the purposes above must be set by the service.
23+
24+
Having the client send the value brings the following challenges:
25+
* The client could repeatedly send the same RequestID
26+
* The client could send no RequestID
27+
* The client could send a malformed or malicious RequestID (like in [1](https://en.wikipedia.org/wiki/Shellshock_(software_bug)) and
28+
[2](https://cwiki.apache.org/confluence/display/WW/S2-045)).
29+
30+
To minimise the attack surface and provide a uniform experience to customers, servers should generate the value.
31+
However, services should be free to read the ID sent by clients in HTTP headers: it is common for services to
32+
read the request ID a client sends, record it and send it back upon success. A client may want to send the same value to multiple services.
33+
Services should still decide to have their own unique request ID per actual call.
34+
35+
RequestIDs are not to be used by multiple services, but only within a single service.
36+
37+
<!-- Explain how users will use this new feature and, if necessary, how this compares to the current user experience -->
38+
The user experience if this RFC is implemented
39+
----------------------------------------------
40+
41+
The proposal is to implement a `RequestId` type and make it available to middleware and business logic handlers, through [FromParts](../server/from-parts.md) and as a `Service`.
42+
To aid customers already relying on clients' request IDs, there will be two types: `ClientRequestId` and `ServerRequestId`.
43+
44+
1. Implementing `FromParts` for `Extension<RequestId>` gives customers the ability to write their handlers:
45+
46+
```rust
47+
pub async fn handler(
48+
input: input::Input,
49+
request_id: Extension<ServerRequestId>,
50+
) -> ...
51+
```
52+
```rust
53+
pub async fn handler(
54+
input: input::Input,
55+
request_id: Extension<ClientRequestId>,
56+
) -> ...
57+
```
58+
59+
`ServerRequestId` and `ClientRequestId` will be injected into the extensions by a layer.
60+
This layer can also be used to open a span that will log the request ID: subsequent logs will be in the scope of that span.
61+
62+
2. ServerRequestId format:
63+
64+
Common formats for RequestIDs are:
65+
66+
* UUID: a random string, represented in hex, of 128 bits from IETF RFC 4122: `7c038a43-e499-4162-8e70-2d4d38595930`
67+
* The hash of a sequence such as `date+thread+server`: `734678902ea938783a7200d7b2c0b487`
68+
* A verbose description: `current_ms+hostname+increasing_id`
69+
70+
For privacy reasons, any format that provides service details should be avoided. A random string is preferred.
71+
The proposed format is to use UUID, version 4.
72+
73+
A `Service` that inserts a RequestId in the extensions will be implemented as follows:
74+
```rust
75+
impl<R, S> Service<http::Request<R>> for ServerRequestIdProvider<S>
76+
where
77+
S: Service<http::Request<R>>,
78+
{
79+
type Response = S::Response;
80+
type Error = S::Error;
81+
type Future = S::Future;
82+
83+
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
84+
self.inner.poll_ready(cx)
85+
}
86+
87+
fn call(&mut self, mut req: http::Request<R>) -> Self::Future {
88+
request.extensions_mut().insert(ServerRequestId::new());
89+
self.inner.call(req)
90+
}
91+
}
92+
```
93+
94+
For client request IDs, the process will be, in order:
95+
* If a header is found matching one of the possible ones, use it
96+
* Otherwise, None
97+
98+
`Option` is used to distinguish whether a client had provided an ID or not.
99+
```rust
100+
impl<R, S> Service<http::Request<R>> for ClientRequestIdProvider<S>
101+
where
102+
S: Service<http::Request<R>>,
103+
{
104+
type Response = S::Response;
105+
type Error = S::Error;
106+
type Future = S::Future;
107+
108+
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
109+
self.inner.poll_ready(cx)
110+
}
111+
112+
fn call(&mut self, mut req: http::Request<R>) -> Self::Future {
113+
for possible_header in self.possible_headers {
114+
if let Some(id) = req.headers.get(possible_header) {
115+
req.extensions_mut().insert(Some(ClientRequestId::new(id)));
116+
return self.inner.call(req)
117+
}
118+
}
119+
req.extensions_mut().insert(None);
120+
self.inner.call(req)
121+
}
122+
}
123+
```
124+
125+
The string representation of a generated ID will be valid for this regex:
126+
* For `ServerRequestId`: `/^[A-Za-z0-9_-]{,48}$/`
127+
* For `ClientRequestId`: see [the spec](https://httpwg.org/specs/rfc9110.html#rfc.section.5.5)
128+
129+
Although the generated ID is opaque, this will give guarantees to customers as to what they can expect, if the server ID is ever updated to a different format.
130+
131+
Changes checklist
132+
-----------------
133+
134+
- [ ] Implement `ServerRequestId`: a `new()` function that generates a UUID, with `Display`, `Debug` and `ToStr` implementations
135+
- [ ] Implement `ClientRequestId`: `new()` that wraps a string (the header value) and the header in which the value could be found, with `Display`, `Debug` and `ToStr` implementations
136+
- [x] Implement `FromParts` for `Extension<ServerRequestId>`
137+
- [x] Implement `FromParts` for `Extension<ClientRequestId>`

0 commit comments

Comments
 (0)