runtime/globals/fetch/response/
mod.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use bytes::Bytes;
8use http::{HeaderMap, StatusCode};
9use hyper::ext::ReasonPhrase;
10use ion::class::{NativeObject as _, Reflector};
11use ion::function::Opt;
12use ion::typedarray::{ArrayBufferWrapper, Uint8ArrayWrapper};
13use ion::{ClassDefinition as _, Context, Error, ErrorKind, Object, Promise, Result, TracedHeap, js_class};
14use mozjs::jsapi::{Heap, JSObject};
15pub use options::*;
16use url::Url;
17
18use crate::globals::fetch::Headers;
19use crate::globals::fetch::body::{Body, FetchBody};
20use crate::globals::fetch::header::HeadersKind;
21use crate::globals::fetch::response::body::ResponseBody;
22use crate::promise::future_to_promise;
23
24mod body;
25mod options;
26
27#[js_class]
28pub struct Response {
29	reflector: Reflector,
30
31	pub(crate) headers: Box<Heap<*mut JSObject>>,
32	pub(crate) body: Option<ResponseBody>,
33
34	pub(crate) kind: ResponseKind,
35	#[trace(no_trace)]
36	pub(crate) url: Option<Url>,
37	pub(crate) redirected: bool,
38
39	#[trace(no_trace)]
40	pub(crate) status: Option<StatusCode>,
41	pub(crate) status_text: Option<String>,
42
43	pub(crate) range_requested: bool,
44}
45
46impl Response {
47	pub fn from_hyper(response: hyper::Response<Body>, url: Url) -> (HeaderMap, Response) {
48		let (parts, body) = response.into_parts();
49
50		let status_text = if let Some(reason) = parts.extensions.get::<ReasonPhrase>() {
51			Some(String::from_utf8(reason.as_bytes().to_vec()).unwrap())
52		} else {
53			parts.status.canonical_reason().map(String::from)
54		};
55
56		let response = Response {
57			reflector: Reflector::default(),
58
59			headers: Box::default(),
60			body: Some(ResponseBody::Hyper(body)),
61
62			kind: ResponseKind::default(),
63			url: Some(url),
64			redirected: false,
65
66			status: Some(parts.status),
67			status_text,
68
69			range_requested: false,
70		};
71
72		(parts.headers, response)
73	}
74
75	pub fn new_from_bytes(bytes: Bytes, url: Url) -> Response {
76		Response {
77			reflector: Reflector::default(),
78
79			headers: Box::default(),
80			body: Some(ResponseBody::Hyper(Body::from(bytes))),
81
82			kind: ResponseKind::Basic,
83			url: Some(url),
84			redirected: false,
85
86			status: Some(StatusCode::OK),
87			status_text: Some(String::from("OK")),
88
89			range_requested: false,
90		}
91	}
92}
93
94#[js_class]
95impl Response {
96	#[ion(constructor)]
97	pub fn constructor(cx: &Context, Opt(body): Opt<FetchBody>, Opt(init): Opt<ResponseInit>) -> Result<Response> {
98		let init = init.unwrap_or_default();
99
100		let mut response = Response {
101			reflector: Reflector::default(),
102
103			headers: Box::default(),
104			body: Some(ResponseBody::Hyper(Body::Empty)),
105
106			kind: ResponseKind::default(),
107			url: None,
108			redirected: false,
109
110			status: Some(init.status),
111			status_text: init.status_text,
112
113			range_requested: false,
114		};
115
116		let mut headers = init.headers.into_headers(HeaderMap::new(), HeadersKind::Response)?;
117
118		if let Some(body) = body {
119			if init.status == StatusCode::NO_CONTENT
120				|| init.status == StatusCode::RESET_CONTENT
121				|| init.status == StatusCode::NOT_MODIFIED
122			{
123				return Err(Error::new(
124					"Received non-null body with null body status.",
125					ErrorKind::Type,
126				));
127			}
128
129			body.add_content_type_header(&mut headers.headers);
130			response.body = Some(ResponseBody::Fetch(body));
131		}
132
133		response.headers.set(Headers::new_object(cx, Box::new(headers)));
134
135		Ok(response)
136	}
137
138	#[ion(get)]
139	pub fn get_type(&self) -> String {
140		self.kind.to_string()
141	}
142
143	#[ion(get)]
144	pub fn get_url(&self) -> String {
145		self.url.as_ref().map(Url::to_string).unwrap_or_default()
146	}
147
148	#[ion(get)]
149	pub fn get_redirected(&self) -> bool {
150		self.redirected
151	}
152
153	#[ion(get)]
154	pub fn get_status(&self) -> u16 {
155		self.status.as_ref().map(StatusCode::as_u16).unwrap_or_default()
156	}
157
158	#[ion(get)]
159	pub fn get_ok(&self) -> bool {
160		self.status.as_ref().is_some_and(StatusCode::is_success)
161	}
162
163	#[ion(get)]
164	pub fn get_status_text(&self) -> String {
165		self.status_text.clone().unwrap_or_default()
166	}
167
168	#[ion(get)]
169	pub fn get_headers(&self) -> *mut JSObject {
170		self.headers.get()
171	}
172
173	#[ion(get)]
174	pub fn get_body_used(&self) -> bool {
175		self.body.is_none()
176	}
177
178	async fn read_to_bytes(&mut self) -> Result<Vec<u8>> {
179		if let Some(body) = self.body.take() {
180			body.read_to_bytes().await
181		} else {
182			Err(Error::new("Response body has already been used.", None))
183		}
184	}
185
186	#[ion(name = "arrayBuffer")]
187	pub fn array_buffer<'cx>(&mut self, cx: &'cx Context) -> Option<Promise<'cx>> {
188		let this = TracedHeap::new(self.reflector().get());
189		future_to_promise::<_, _, _, Error>(cx, |cx| async move {
190			let response = Object::from(this.to_local());
191			let response = Response::get_mut_private(&cx, &response)?;
192			let bytes = response.read_to_bytes().await?;
193			Ok(ArrayBufferWrapper::from(bytes))
194		})
195	}
196
197	pub fn bytes<'cx>(&mut self, cx: &'cx Context) -> Option<Promise<'cx>> {
198		let this = TracedHeap::new(self.reflector().get());
199		future_to_promise::<_, _, _, Error>(cx, |cx| async move {
200			let response = Object::from(this.to_local());
201			let response = Response::get_mut_private(&cx, &response)?;
202			let bytes = response.read_to_bytes().await?;
203			Ok(Uint8ArrayWrapper::from(bytes))
204		})
205	}
206
207	pub fn text<'cx>(&mut self, cx: &'cx Context) -> Option<Promise<'cx>> {
208		let this = TracedHeap::new(self.reflector().get());
209		future_to_promise::<_, _, _, Error>(cx, |cx| async move {
210			let response = Object::from(this.to_local());
211			let response = Response::get_mut_private(&cx, &response)?;
212			let bytes = response.read_to_bytes().await?;
213			String::from_utf8(bytes).map_err(|e| Error::new(format!("Invalid UTF-8 sequence: {e}"), None))
214		})
215	}
216}
217
218pub fn network_error() -> Response {
219	Response {
220		reflector: Reflector::default(),
221
222		headers: Box::default(),
223		body: None,
224
225		kind: ResponseKind::Error,
226		url: None,
227		redirected: false,
228
229		status: None,
230		status_text: None,
231
232		range_requested: false,
233	}
234}