runtime/globals/fetch/response/
mod.rs1use 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}