Skip to content

Commit 7c4a068

Browse files
authored
Add with_headers() method to RemoteHttpPlugin (#15651)
# Objective - fulfill the needs presented in this issue, which requires the ability to set custom HTTP headers for responses in the Bevy Remote Protocol server. #15551 ## Solution - Created a `Headers` struct to store custom HTTP headers as key-value pairs. - Added a `headers` field to the `RemoteHttpPlugin` struct. - Implemented a `with_headers` method in `RemoteHttpPlugin` to allow users to set custom headers. - Passed the headers into the processing chain. ## Testing - I added cors_headers in example/remote/server.rs and tested it with a static html [file](https://github.com/spacemen0/bevy/blob/test_file/test.html) ---
1 parent d9190e4 commit 7c4a068

File tree

1 file changed

+98
-4
lines changed

1 file changed

+98
-4
lines changed

crates/bevy_remote/src/http.rs

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@ use bevy_ecs::system::{Res, Resource};
1717
use bevy_tasks::IoTaskPool;
1818
use core::net::{IpAddr, Ipv4Addr};
1919
use http_body_util::{BodyExt as _, Full};
20+
pub use hyper::header::{HeaderName, HeaderValue};
2021
use hyper::{
2122
body::{Bytes, Incoming},
22-
header::HeaderValue,
2323
server::conn::http1,
2424
service, Request, Response,
2525
};
2626
use serde_json::Value;
2727
use smol_hyper::rt::{FuturesIo, SmolTimer};
28+
use std::collections::HashMap;
2829
use std::net::TcpListener;
2930
use std::net::TcpStream;
3031

@@ -36,6 +37,37 @@ pub const DEFAULT_PORT: u16 = 15702;
3637
/// The default host address that Bevy will use for its server.
3738
pub const DEFAULT_ADDR: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
3839

40+
/// A struct that holds a collection of HTTP headers.
41+
///
42+
/// This struct is used to store a set of HTTP headers as key-value pairs, where the keys are
43+
/// of type [`HeaderName`] and the values are of type [`HeaderValue`].
44+
///
45+
#[derive(Debug, Resource, Clone)]
46+
pub struct Headers {
47+
headers: HashMap<HeaderName, HeaderValue>,
48+
}
49+
50+
impl Headers {
51+
/// Create a new instance of `Headers`.
52+
pub fn new() -> Self {
53+
Self {
54+
headers: HashMap::new(),
55+
}
56+
}
57+
58+
/// Add a key value pair to the `Headers` instance.
59+
pub fn add(mut self, key: HeaderName, value: HeaderValue) -> Self {
60+
self.headers.insert(key, value);
61+
self
62+
}
63+
}
64+
65+
impl Default for Headers {
66+
fn default() -> Self {
67+
Self::new()
68+
}
69+
}
70+
3971
/// Add this plugin to your [`App`] to allow remote connections over HTTP to inspect and modify entities.
4072
/// It requires the [`RemotePlugin`](super::RemotePlugin).
4173
///
@@ -44,18 +76,40 @@ pub const DEFAULT_ADDR: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
4476
/// The defaults are:
4577
/// - [`DEFAULT_ADDR`] : 127.0.0.1.
4678
/// - [`DEFAULT_PORT`] : 15702.
79+
///
80+
/// /// # Example
81+
///
82+
/// ```ignore
83+
///
84+
/// // Create CORS headers
85+
/// let cors_headers = Headers::new()
86+
/// .add(HeaderName::from_static("Access-Control-Allow-Origin"), HeaderValue::from_static("*"))
87+
/// .add(HeaderName::from_static("Access-Control-Allow-Headers"), HeaderValue::from_static("Content-Type, Authorization"));
88+
///
89+
/// // Create the Bevy app and add the RemoteHttpPlugin with CORS headers
90+
/// fn main() {
91+
/// App::new()
92+
/// .add_plugins(DefaultPlugins)
93+
/// .add_plugins(RemoteHttpPlugin::default()
94+
/// .with_headers(cors_headers))
95+
/// .run();
96+
/// }
97+
/// ```
4798
pub struct RemoteHttpPlugin {
4899
/// The address that Bevy will bind to.
49100
address: IpAddr,
50101
/// The port that Bevy will listen on.
51102
port: u16,
103+
/// The headers that Bevy will include in its HTTP responses
104+
headers: Headers,
52105
}
53106

54107
impl Default for RemoteHttpPlugin {
55108
fn default() -> Self {
56109
Self {
57110
address: DEFAULT_ADDR,
58111
port: DEFAULT_PORT,
112+
headers: Headers::new(),
59113
}
60114
}
61115
}
@@ -64,6 +118,7 @@ impl Plugin for RemoteHttpPlugin {
64118
fn build(&self, app: &mut App) {
65119
app.insert_resource(HostAddress(self.address))
66120
.insert_resource(HostPort(self.port))
121+
.insert_resource(HostHeaders(self.headers.clone()))
67122
.add_systems(Startup, start_http_server);
68123
}
69124
}
@@ -75,13 +130,34 @@ impl RemoteHttpPlugin {
75130
self.address = address.into();
76131
self
77132
}
78-
79133
/// Set the remote port that the server will listen on.
80134
#[must_use]
81135
pub fn with_port(mut self, port: u16) -> Self {
82136
self.port = port;
83137
self
84138
}
139+
/// Set the extra headers that the response will include.
140+
#[must_use]
141+
pub fn with_headers(mut self, headers: Headers) -> Self {
142+
self.headers = headers;
143+
self
144+
}
145+
/// Add a single header to the response headers.
146+
#[must_use]
147+
pub fn with_header(
148+
mut self,
149+
name: impl TryInto<HeaderName>,
150+
value: impl TryInto<HeaderValue>,
151+
) -> Self {
152+
let Ok(header_name) = name.try_into() else {
153+
panic!("Invalid header name")
154+
};
155+
let Ok(header_value) = value.try_into() else {
156+
panic!("Invalid header value")
157+
};
158+
self.headers = self.headers.add(header_name, header_value);
159+
self
160+
}
85161
}
86162

87163
/// A resource containing the IP address that Bevy will host on.
@@ -98,17 +174,24 @@ pub struct HostAddress(pub IpAddr);
98174
#[derive(Debug, Resource)]
99175
pub struct HostPort(pub u16);
100176

177+
/// A resource containing the headers that Bevy will include in its HTTP responses.
178+
///
179+
#[derive(Debug, Resource)]
180+
struct HostHeaders(pub Headers);
181+
101182
/// A system that starts up the Bevy Remote Protocol HTTP server.
102183
fn start_http_server(
103184
request_sender: Res<BrpSender>,
104185
address: Res<HostAddress>,
105186
remote_port: Res<HostPort>,
187+
headers: Res<HostHeaders>,
106188
) {
107189
IoTaskPool::get()
108190
.spawn(server_main(
109191
address.0,
110192
remote_port.0,
111193
request_sender.clone(),
194+
headers.0.clone(),
112195
))
113196
.detach();
114197
}
@@ -118,25 +201,29 @@ async fn server_main(
118201
address: IpAddr,
119202
port: u16,
120203
request_sender: Sender<BrpMessage>,
204+
headers: Headers,
121205
) -> AnyhowResult<()> {
122206
listen(
123207
Async::<TcpListener>::bind((address, port))?,
124208
&request_sender,
209+
&headers,
125210
)
126211
.await
127212
}
128213

129214
async fn listen(
130215
listener: Async<TcpListener>,
131216
request_sender: &Sender<BrpMessage>,
217+
headers: &Headers,
132218
) -> AnyhowResult<()> {
133219
loop {
134220
let (client, _) = listener.accept().await?;
135221

136222
let request_sender = request_sender.clone();
223+
let headers = headers.clone();
137224
IoTaskPool::get()
138225
.spawn(async move {
139-
let _ = handle_client(client, request_sender).await;
226+
let _ = handle_client(client, request_sender, headers).await;
140227
})
141228
.detach();
142229
}
@@ -145,12 +232,15 @@ async fn listen(
145232
async fn handle_client(
146233
client: Async<TcpStream>,
147234
request_sender: Sender<BrpMessage>,
235+
headers: Headers,
148236
) -> AnyhowResult<()> {
149237
http1::Builder::new()
150238
.timer(SmolTimer::new())
151239
.serve_connection(
152240
FuturesIo::new(client),
153-
service::service_fn(|request| process_request_batch(request, &request_sender)),
241+
service::service_fn(|request| {
242+
process_request_batch(request, &request_sender, &headers)
243+
}),
154244
)
155245
.await?;
156246

@@ -162,6 +252,7 @@ async fn handle_client(
162252
async fn process_request_batch(
163253
request: Request<Incoming>,
164254
request_sender: &Sender<BrpMessage>,
255+
headers: &Headers,
165256
) -> AnyhowResult<Response<Full<Bytes>>> {
166257
let batch_bytes = request.into_body().collect().await?.to_bytes();
167258
let batch: Result<BrpBatch, _> = serde_json::from_slice(&batch_bytes);
@@ -198,6 +289,9 @@ async fn process_request_batch(
198289
hyper::header::CONTENT_TYPE,
199290
HeaderValue::from_static("application/json"),
200291
);
292+
for (key, value) in &headers.headers {
293+
response.headers_mut().insert(key, value.clone());
294+
}
201295
Ok(response)
202296
}
203297

0 commit comments

Comments
 (0)