Skip to content

Commit 19b717c

Browse files
johansmitsnlctron
authored andcommitted
Allow injecting request headers on the proxy module
1 parent 60b1d5f commit 19b717c

File tree

8 files changed

+56
-10
lines changed

8 files changed

+56
-10
lines changed

examples/proxy/Trunk.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ ws = true
2121
# E.G., `/api/v1/resource/x/y/z` -> `/resource/x/y/z`
2222
rewrite = "/api/v1/"
2323
backend = "http://localhost:9090/"
24+
request_headers = { "x-api-key" = "some-special-key" }
2425

2526
[[proxy]]
2627
# This proxy specifies only the backend, which is the only required field. In this example,

schemas/config.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,15 @@
360360
"default": false,
361361
"type": "boolean"
362362
},
363+
"request_headers": {
364+
"description": "Configure additional headers to send in proxied requests.",
365+
"default": {},
366+
"deprecated": true,
367+
"type": "object",
368+
"additionalProperties": {
369+
"type": "string"
370+
}
371+
},
363372
"rewrite": {
364373
"description": "An optional URI prefix which is to be used as the base URI for proxying requests, which defaults to the URI of the backend.\n\nWhen a value is specified, requests received on this URI will have this URI segment replaced with the URI of the `backend`.",
365374
"type": [

src/cmd/serve.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ impl Serve {
141141
// we have a single proxy from the command line
142142
config.proxies.0.push(Proxy {
143143
backend: backend.into(),
144+
request_headers: Default::default(),
144145
rewrite: proxy_rewrite,
145146
ws: proxy_ws,
146147
insecure: proxy_insecure,

src/config/models/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ impl ConfigModel for Configuration {
101101
log::warn!("The proxy fields in the configuration are deprecated and will be removed in a future version. Migrate those settings into an entry of the `proxies` field, which allows adding more than one.");
102102
self.proxies.0.push(Proxy {
103103
backend,
104+
request_headers: Default::default(),
104105
rewrite: self.serve.proxy_rewrite.take(),
105106
ws: self.serve.proxy_ws.unwrap_or_default(),
106107
insecure: self.serve.proxy_insecure.unwrap_or_default(),

src/config/models/proxy.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::HashMap;
2+
13
use crate::{config::models::ConfigModel, config::types::Uri};
24
use schemars::JsonSchema;
35
use serde::Deserialize;
@@ -13,6 +15,9 @@ pub struct Proxy {
1315
/// When a value is specified, requests received on this URI will have this URI segment
1416
/// replaced with the URI of the `backend`.
1517
pub rewrite: Option<String>,
18+
/// A set of headers to pass to the proxied backend.
19+
#[serde(default)]
20+
pub request_headers: HashMap<String, String>,
1621
/// Configure the proxy for handling WebSockets.
1722
#[serde(default)]
1823
pub ws: bool,

src/proxy.rs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ use axum::{
66
ws::{Message as MsgAxm, WebSocket, WebSocketUpgrade},
77
Request, State,
88
},
9-
http::{Response, Uri},
9+
http::{HeaderName, Response, Uri},
1010
routing::{any, get, Router},
1111
RequestExt,
1212
};
1313
use bytes::BytesMut;
1414
use futures_util::{sink::SinkExt, stream::StreamExt, TryStreamExt};
1515
use hyper::{header::HOST, HeaderMap};
1616
use reqwest::header::HeaderValue;
17-
use std::sync::Arc;
17+
use std::{collections::HashMap, str::FromStr, sync::Arc};
1818
use tokio_tungstenite::{
1919
connect_async,
2020
tungstenite::{protocol::CloseFrame, Message as MsgTng},
@@ -34,6 +34,8 @@ pub(crate) struct ProxyHandlerHttp {
3434
client: reqwest::Client,
3535
/// The URL of the backend to which requests are to be proxied.
3636
backend: Uri,
37+
/// The headers to inject with the request
38+
request_headers: HashMap<String, String>,
3739
/// An optional rewrite path to be used as the listening URI prefix, but which will be
3840
/// stripped before being sent to the proxy backend.
3941
rewrite: Option<String>,
@@ -102,10 +104,16 @@ fn make_outbound_request(
102104

103105
impl ProxyHandlerHttp {
104106
/// Construct a new instance.
105-
pub fn new(client: reqwest::Client, backend: Uri, rewrite: Option<String>) -> Arc<Self> {
107+
pub fn new(
108+
client: reqwest::Client,
109+
backend: Uri,
110+
request_headers: HashMap<String, String>,
111+
rewrite: Option<String>,
112+
) -> Arc<Self> {
106113
Arc::new(Self {
107114
client,
108115
backend,
116+
request_headers,
109117
rewrite,
110118
})
111119
}
@@ -135,10 +143,21 @@ impl ProxyHandlerHttp {
135143
) -> ServerResult<Response<Body>> {
136144
// Construct the outbound URI & build a new request to be sent to the proxy backend.
137145
let outbound_uri = make_outbound_uri(&state.backend, req.uri())?;
146+
147+
let mut headers = req.headers().clone();
148+
for (header_name, header_value) in state.request_headers.clone() {
149+
headers.insert(
150+
HeaderName::from_str(&header_name).context("Error building the header key")?,
151+
header_value
152+
.parse()
153+
.context("Error building the header value")?,
154+
);
155+
}
156+
138157
let mut outbound_req = state
139158
.client
140159
.request(req.method().clone(), outbound_uri.to_string())
141-
.headers(req.headers().clone())
160+
.headers(headers.clone())
142161
.body(reqwest::Body::from(
143162
// It would be better to use a stream for this. However, right now,
144163
// .into_data_stream() returns a stream which is not Send+Sync, so we can't pass it
@@ -153,10 +172,12 @@ impl ProxyHandlerHttp {
153172
.build()
154173
.context("error building outbound request to proxy backend")?;
155174

156-
// Ensure the host header is set to target the backend.
157-
if let Some(host) = state.backend.authority().map(|authority| authority.host()) {
158-
if let Ok(host) = HeaderValue::from_str(host) {
159-
outbound_req.headers_mut().insert("host", host);
175+
// Ensure the host header is set to target the backend if not given in the header override list.
176+
if !headers.contains_key(HOST) {
177+
if let Some(host) = state.backend.authority().map(|authority| authority.host()) {
178+
if let Ok(host) = HeaderValue::from_str(host) {
179+
outbound_req.headers_mut().insert("host", host);
180+
}
160181
}
161182
}
162183

src/serve/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ fn router(state: Arc<State>, cfg: Arc<RtcServe>) -> Result<Router> {
367367
builder = builder.register_proxy(
368368
proxy.ws,
369369
&proxy.backend,
370+
&proxy.request_headers,
370371
proxy.rewrite.clone(),
371372
ProxyClientOptions {
372373
insecure: proxy.insecure,

src/serve/proxy.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ impl ProxyBuilder {
3030
mut self,
3131
ws: bool,
3232
backend: &Uri,
33+
request_headers: &HashMap<String, String>,
3334
rewrite: Option<String>,
3435
opts: ProxyClientOptions,
3536
) -> anyhow::Result<Self> {
@@ -47,12 +48,18 @@ impl ProxyBuilder {
4748
let no_sys_proxy = opts.no_system_proxy;
4849
let insecure = opts.insecure;
4950
let client = self.clients.get_client(opts)?;
50-
let handler = ProxyHandlerHttp::new(client, backend.clone(), rewrite);
51+
let handler =
52+
ProxyHandlerHttp::new(client, backend.clone(), request_headers.clone(), rewrite);
5153
tracing::info!(
52-
"{}proxying {} -> {}{}{}",
54+
"{}proxying {} -> {} {} {}{}",
5355
SERVER,
5456
handler.path(),
5557
&backend,
58+
&request_headers
59+
.iter()
60+
.map(|(header_name, header_value)| format!("{header_name}={header_value}"))
61+
.collect::<Vec<String>>()
62+
.join(";"),
5663
if no_sys_proxy {
5764
"; ignoring system proxy"
5865
} else {

0 commit comments

Comments
 (0)