Skip to content

Commit d591d33

Browse files
authored
fix: experimental headers (#1524)
1 parent 157d50a commit d591d33

File tree

8 files changed

+75
-57
lines changed

8 files changed

+75
-57
lines changed

benches/impl_path_string_for_evaluation_context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ fn request_context() -> RequestContext {
253253
}
254254

255255
fn bench_main(c: &mut Criterion) {
256-
let mut req_ctx = request_context().req_headers(TEST_HEADERS.clone());
256+
let mut req_ctx = request_context().request_headers(TEST_HEADERS.clone());
257257

258258
req_ctx.server.vars = TEST_VARS.clone();
259259
let eval_ctx = EvaluationContext::new(&req_ctx, &MockGraphqlContext);

src/blueprint/server.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use std::collections::{BTreeMap, BTreeSet};
1+
use std::collections::{BTreeMap, BTreeSet, HashSet};
22
use std::net::{AddrParseError, IpAddr};
3+
use std::str::FromStr;
34
use std::sync::Arc;
45
use std::time::Duration;
56

@@ -33,7 +34,7 @@ pub struct Server {
3334
pub pipeline_flush: bool,
3435
pub script: Option<Script>,
3536
pub cors: Option<Cors>,
36-
pub experimental_headers: BTreeSet<String>,
37+
pub experimental_headers: HashSet<HeaderName>,
3738
}
3839

3940
/// Mimic of mini_v8::Script that's wasm compatible
@@ -75,7 +76,7 @@ impl Server {
7576
self.enable_query_validation
7677
}
7778

78-
pub fn get_experimental_headers(&self) -> BTreeSet<String> {
79+
pub fn get_experimental_headers(&self) -> HashSet<HeaderName> {
7980
self.experimental_headers.clone()
8081
}
8182
}
@@ -209,7 +210,7 @@ fn handle_response_headers(resp_headers: Vec<(String, String)>) -> Valid<HeaderM
209210
.trace("schema")
210211
}
211212

212-
fn handle_experimental_headers(headers: BTreeSet<String>) -> Valid<BTreeSet<String>, String> {
213+
fn handle_experimental_headers(headers: BTreeSet<String>) -> Valid<HashSet<HeaderName>, String> {
213214
Valid::from_iter(headers.iter(), |h| {
214215
if !h.to_lowercase().starts_with("x-") {
215216
Valid::fail(
@@ -220,10 +221,10 @@ fn handle_experimental_headers(headers: BTreeSet<String>) -> Valid<BTreeSet<Stri
220221
.to_string(),
221222
)
222223
} else {
223-
Valid::succeed(h.clone())
224+
Valid::from(HeaderName::from_str(h).map_err(|e| ValidationError::new(e.to_string())))
224225
}
225226
})
226-
.map_to(headers)
227+
.map(HashSet::from_iter)
227228
.trace("experimental")
228229
.trace("headers")
229230
.trace("@server")

src/http/request_context.rs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ use crate::runtime::TargetRuntime;
2020
pub struct RequestContext {
2121
pub server: Server,
2222
pub upstream: Upstream,
23-
pub req_headers: HeaderMap,
24-
pub experimental_headers: HeaderMap,
23+
pub request_headers: HeaderMap,
24+
pub x_response_headers: Arc<Mutex<HeaderMap>>,
2525
pub cookie_headers: Option<Arc<Mutex<HeaderMap>>>,
2626
pub http_data_loaders: Arc<Vec<DataLoader<DataLoaderRequest, HttpDataLoader>>>,
2727
pub gql_data_loaders: Arc<Vec<DataLoader<DataLoaderRequest, GraphqlDataLoader>>>,
@@ -37,8 +37,8 @@ impl RequestContext {
3737
RequestContext {
3838
server: Default::default(),
3939
upstream: Default::default(),
40-
req_headers: HeaderMap::new(),
41-
experimental_headers: HeaderMap::new(),
40+
request_headers: HeaderMap::new(),
41+
x_response_headers: Arc::new(Mutex::new(HeaderMap::new())),
4242
cookie_headers: None,
4343
http_data_loaders: Arc::new(vec![]),
4444
gql_data_loaders: Arc::new(vec![]),
@@ -145,6 +145,33 @@ impl RequestContext {
145145
pub fn is_batching_enabled(&self) -> bool {
146146
self.upstream.is_batching_enabled()
147147
}
148+
149+
/// Checks if experimental headers is enabled
150+
pub fn has_experimental_headers(&self) -> bool {
151+
!self.server.experimental_headers.is_empty()
152+
}
153+
154+
/// Inserts the experimental headers into the x_response_headers map
155+
pub fn add_x_headers(&self, headers: &HeaderMap) {
156+
if self.has_experimental_headers() {
157+
let mut x_response_headers = self.x_response_headers.lock().unwrap();
158+
for name in &self.server.experimental_headers {
159+
if let Some(value) = headers.get(name) {
160+
x_response_headers.insert(name, value.clone());
161+
}
162+
}
163+
}
164+
}
165+
166+
/// Modifies existing headers to include the experimental headers
167+
pub fn extend_x_headers(&self, headers: &mut HeaderMap) {
168+
if self.has_experimental_headers() {
169+
let x_response_headers = &self.x_response_headers.lock().unwrap();
170+
for (header, value) in x_response_headers.iter() {
171+
headers.insert(header, value.clone());
172+
}
173+
}
174+
}
148175
}
149176

150177
impl From<&AppContext> for RequestContext {
@@ -157,8 +184,8 @@ impl From<&AppContext> for RequestContext {
157184
Self {
158185
server: app_ctx.blueprint.server.clone(),
159186
upstream: app_ctx.blueprint.upstream.clone(),
160-
req_headers: HeaderMap::new(),
161-
experimental_headers: HeaderMap::new(),
187+
request_headers: HeaderMap::new(),
188+
x_response_headers: Arc::new(Mutex::new(HeaderMap::new())),
162189
cookie_headers,
163190
http_data_loaders: app_ctx.http_data_loaders.clone(),
164191
gql_data_loaders: app_ctx.gql_data_loaders.clone(),

src/http/request_handler.rs

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::borrow::Cow;
22
use std::collections::BTreeSet;
3+
use std::ops::Deref;
34
use std::sync::Arc;
45

56
use anyhow::Result;
@@ -75,11 +76,8 @@ fn create_request_context(req: &Request<Body>, app_ctx: &AppContext) -> RequestC
7576
let allowed = upstream.allowed_headers;
7677
let req_headers = create_allowed_headers(req.headers(), &allowed);
7778

78-
let allowed = app_ctx.blueprint.server.get_experimental_headers();
79-
let experimental_headers = create_allowed_headers(req.headers(), &allowed);
80-
RequestContext::from(app_ctx)
81-
.req_headers(req_headers)
82-
.experimental_headers(experimental_headers)
79+
let _allowed = app_ctx.blueprint.server.get_experimental_headers();
80+
RequestContext::from(app_ctx).request_headers(req_headers)
8381
}
8482

8583
fn update_cache_control_header(
@@ -95,30 +93,25 @@ fn update_cache_control_header(
9593
response
9694
}
9795

98-
fn update_experimental_headers(
99-
response: &mut hyper::Response<hyper::Body>,
100-
app_ctx: &AppContext,
101-
req_ctx: Arc<RequestContext>,
102-
) {
103-
if !app_ctx.blueprint.server.experimental_headers.is_empty() {
104-
response
105-
.headers_mut()
106-
.extend(req_ctx.experimental_headers.clone());
107-
}
108-
}
109-
11096
pub fn update_response_headers(
11197
resp: &mut hyper::Response<hyper::Body>,
112-
cookie_headers: Option<HeaderMap>,
98+
req_ctx: &RequestContext,
11399
app_ctx: &AppContext,
114100
) {
115101
if !app_ctx.blueprint.server.response_headers.is_empty() {
102+
// Add static response headers
116103
resp.headers_mut()
117104
.extend(app_ctx.blueprint.server.response_headers.clone());
118105
}
119-
if let Some(cookie_headers) = cookie_headers {
120-
resp.headers_mut().extend(cookie_headers);
106+
107+
// Insert Cookie Headers
108+
if let Some(ref cookie_headers) = req_ctx.cookie_headers {
109+
let cookie_headers = cookie_headers.lock().unwrap();
110+
resp.headers_mut().extend(cookie_headers.deref().clone());
121111
}
112+
113+
// Insert Experimental Headers
114+
req_ctx.extend_x_headers(resp.headers_mut());
122115
}
123116

124117
#[tracing::instrument(skip_all, fields(otel.name = "graphQL", otel.kind = ?SpanKind::Server))]
@@ -134,15 +127,10 @@ pub async fn graphql_request<T: DeserializeOwned + GraphQLRequestLike>(
134127
match graphql_request {
135128
Ok(request) => {
136129
let mut response = request.data(req_ctx.clone()).execute(&app_ctx.schema).await;
137-
let cookie_headers = req_ctx.cookie_headers.clone();
130+
138131
response = update_cache_control_header(response, app_ctx, req_ctx.clone());
139132
let mut resp = response.to_response()?;
140-
update_response_headers(
141-
&mut resp,
142-
cookie_headers.map(|v| v.lock().unwrap().clone()),
143-
app_ctx,
144-
);
145-
update_experimental_headers(&mut resp, app_ctx, req_ctx);
133+
update_response_headers(&mut resp, &req_ctx, app_ctx);
146134
Ok(resp)
147135
}
148136
Err(err) => {
@@ -250,15 +238,9 @@ async fn handle_rest_apis(
250238
.data(req_ctx.clone())
251239
.execute(&app_ctx.schema)
252240
.await;
253-
let cookie_headers = req_ctx.cookie_headers.clone();
254241
response = update_cache_control_header(response, app_ctx.as_ref(), req_ctx.clone());
255242
let mut resp = response.to_rest_response()?;
256-
update_response_headers(
257-
&mut resp,
258-
cookie_headers.map(|v| v.lock().unwrap().clone()),
259-
app_ctx.as_ref(),
260-
);
261-
update_experimental_headers(&mut resp, app_ctx.as_ref(), req_ctx);
243+
update_response_headers(&mut resp, &req_ctx, &app_ctx);
262244
Ok(resp)
263245
}
264246
.instrument(span)

src/lambda/evaluation_context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ impl<'a, Ctx: ResolverContextLike<'a>> EvaluationContext<'a, Ctx> {
7878
}
7979

8080
pub fn headers(&self) -> &HeaderMap {
81-
&self.req_ctx.req_headers
81+
&self.req_ctx.request_headers
8282
}
8383

8484
pub fn header(&self, key: &str) -> Option<&str> {

src/lambda/io.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ fn set_headers<'ctx, Ctx: ResolverContextLike<'ctx>>(
157157
) {
158158
set_cache_control(ctx, res);
159159
set_cookie_headers(ctx, res);
160+
set_experimental_headers(ctx, res);
160161
}
161162

162163
fn set_cache_control<'ctx, Ctx: ResolverContextLike<'ctx>>(
@@ -170,6 +171,13 @@ fn set_cache_control<'ctx, Ctx: ResolverContextLike<'ctx>>(
170171
}
171172
}
172173

174+
fn set_experimental_headers<'ctx, Ctx: ResolverContextLike<'ctx>>(
175+
ctx: &EvaluationContext<'ctx, Ctx>,
176+
res: &Response<async_graphql::Value>,
177+
) {
178+
ctx.req_ctx.add_x_headers(&res.headers);
179+
}
180+
173181
fn set_cookie_headers<'ctx, Ctx: ResolverContextLike<'ctx>>(
174182
ctx: &EvaluationContext<'ctx, Ctx>,
175183
res: &Response<async_graphql::Value>,
@@ -183,13 +191,16 @@ async fn execute_raw_request<'ctx, Ctx: ResolverContextLike<'ctx>>(
183191
ctx: &EvaluationContext<'ctx, Ctx>,
184192
req: Request,
185193
) -> Result<Response<async_graphql::Value>> {
186-
ctx.req_ctx
194+
let response = ctx
195+
.req_ctx
187196
.runtime
188197
.http
189198
.execute(req)
190199
.await
191200
.map_err(|e| EvaluationError::IOException(e.to_string()))?
192-
.to_json()
201+
.to_json()?;
202+
203+
Ok(response)
193204
}
194205

195206
async fn execute_raw_grpc_request<'ctx, Ctx: ResolverContextLike<'ctx>>(

src/path.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ mod tests {
209209
}
210210

211211
static REQ_CTX: Lazy<RequestContext> = Lazy::new(|| {
212-
let mut req_ctx = RequestContext::default().req_headers(TEST_HEADERS.clone());
212+
let mut req_ctx = RequestContext::default().request_headers(TEST_HEADERS.clone());
213213

214214
req_ctx.server.vars = TEST_VARS.clone();
215215
req_ctx.runtime.env = Arc::new(Env::init(TEST_ENV_VARS.clone()));

tests/execution/experimental-headers.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ type User {
1919
- request:
2020
method: GET
2121
url: http://jsonplaceholder.typicode.com/users
22+
body: null
23+
response:
24+
status: 200
2225
headers:
2326
X-tailcall: "tailcall-header"
2427
x-experimental: "experimental-header"
2528
x-not-allowed: "not-allowed-header"
26-
body: null
27-
response:
28-
status: 200
2929
body:
3030
- id: 1
3131
name: Leanne Graham
@@ -34,9 +34,6 @@ type User {
3434
```yml @assert
3535
- method: POST
3636
url: http://localhost:8080/graphql
37-
headers:
38-
X-tailcall: "tailcall-header"
39-
x-experimental: "experimental-header"
4037
body:
4138
query: query { users { id name } }
4239
```

0 commit comments

Comments
 (0)