1use alloc::{str, vec, vec::Vec};
52use sp_core::offchain::{
53 HttpError, HttpRequestId as RequestId, HttpRequestStatus as RequestStatus, Timestamp,
54};
55
56#[derive(Clone, PartialEq, Eq, Debug)]
58pub enum Method {
59 Get,
61 Post,
63 Put,
65 Patch,
67 Delete,
69 Other(&'static str),
71}
72
73impl AsRef<str> for Method {
74 fn as_ref(&self) -> &str {
75 match *self {
76 Method::Get => "GET",
77 Method::Post => "POST",
78 Method::Put => "PUT",
79 Method::Patch => "PATCH",
80 Method::Delete => "DELETE",
81 Method::Other(m) => m,
82 }
83 }
84}
85
86mod header {
87 use super::*;
88
89 #[derive(Clone, PartialEq, Eq, Debug)]
91 pub struct Header {
92 name: Vec<u8>,
93 value: Vec<u8>,
94 }
95
96 impl Header {
97 pub fn new(name: &str, value: &str) -> Self {
99 Header { name: name.as_bytes().to_vec(), value: value.as_bytes().to_vec() }
100 }
101
102 pub fn name(&self) -> &str {
104 unsafe { str::from_utf8_unchecked(&self.name) }
108 }
109
110 pub fn value(&self) -> &str {
112 unsafe { str::from_utf8_unchecked(&self.value) }
116 }
117 }
118}
119
120#[derive(Clone, PartialEq, Eq, Debug)]
122pub struct Request<'a, T = Vec<&'static [u8]>> {
123 pub method: Method,
125 pub url: &'a str,
127 pub body: T,
129 pub deadline: Option<Timestamp>,
131 headers: Vec<header::Header>,
133}
134
135impl<T: Default> Default for Request<'static, T> {
136 fn default() -> Self {
137 Request {
138 method: Method::Get,
139 url: "http://localhost",
140 headers: Vec::new(),
141 body: Default::default(),
142 deadline: None,
143 }
144 }
145}
146
147impl<'a> Request<'a> {
148 pub fn get(url: &'a str) -> Self {
150 Self::new(url)
151 }
152}
153
154impl<'a, T> Request<'a, T> {
155 pub fn post(url: &'a str, body: T) -> Self {
157 let req: Request = Request::default();
158
159 Request { url, body, method: Method::Post, headers: req.headers, deadline: req.deadline }
160 }
161}
162
163impl<'a, T: Default> Request<'a, T> {
164 pub fn new(url: &'a str) -> Self {
166 Request::default().url(url)
167 }
168
169 pub fn method(mut self, method: Method) -> Self {
171 self.method = method;
172 self
173 }
174
175 pub fn url(mut self, url: &'a str) -> Self {
177 self.url = url;
178 self
179 }
180
181 pub fn body(mut self, body: T) -> Self {
183 self.body = body;
184 self
185 }
186
187 pub fn add_header(mut self, name: &str, value: &str) -> Self {
189 self.headers.push(header::Header::new(name, value));
190 self
191 }
192
193 pub fn deadline(mut self, deadline: Timestamp) -> Self {
195 self.deadline = Some(deadline);
196 self
197 }
198}
199
200impl<'a, I: AsRef<[u8]>, T: IntoIterator<Item = I>> Request<'a, T> {
201 pub fn send(self) -> Result<PendingRequest, HttpError> {
206 let meta = &[];
207
208 let id = sp_io::offchain::http_request_start(self.method.as_ref(), self.url, meta)
210 .map_err(|_| HttpError::IoError)?;
211
212 for header in &self.headers {
214 sp_io::offchain::http_request_add_header(id, header.name(), header.value())
215 .map_err(|_| HttpError::IoError)?
216 }
217
218 for chunk in self.body {
220 sp_io::offchain::http_request_write_body(id, chunk.as_ref(), self.deadline)?;
221 }
222
223 sp_io::offchain::http_request_write_body(id, &[], self.deadline)?;
225
226 Ok(PendingRequest { id })
227 }
228}
229
230#[derive(Clone, PartialEq, Eq, Debug)]
232pub enum Error {
233 DeadlineReached,
235 IoError,
237 Unknown,
239}
240
241#[derive(PartialEq, Eq, Debug)]
243pub struct PendingRequest {
244 pub id: RequestId,
246}
247
248pub type HttpResult = Result<Response, Error>;
250
251impl PendingRequest {
252 pub fn wait(self) -> HttpResult {
256 match self.try_wait(None) {
257 Ok(res) => res,
258 Err(_) => panic!("Since `None` is passed we will never get a deadline error; qed"),
259 }
260 }
261
262 pub fn try_wait(
265 self,
266 deadline: impl Into<Option<Timestamp>>,
267 ) -> Result<HttpResult, PendingRequest> {
268 Self::try_wait_all(vec![self], deadline)
269 .pop()
270 .expect("One request passed, one status received; qed")
271 }
272
273 pub fn wait_all(requests: Vec<PendingRequest>) -> Vec<HttpResult> {
275 Self::try_wait_all(requests, None)
276 .into_iter()
277 .map(|r| match r {
278 Ok(r) => r,
279 Err(_) => panic!("Since `None` is passed we will never get a deadline error; qed"),
280 })
281 .collect()
282 }
283
284 pub fn try_wait_all(
289 requests: Vec<PendingRequest>,
290 deadline: impl Into<Option<Timestamp>>,
291 ) -> Vec<Result<HttpResult, PendingRequest>> {
292 let ids = requests.iter().map(|r| r.id).collect::<Vec<_>>();
293 let statuses = sp_io::offchain::http_response_wait(&ids, deadline.into());
294
295 statuses
296 .into_iter()
297 .zip(requests.into_iter())
298 .map(|(status, req)| match status {
299 RequestStatus::DeadlineReached => Err(req),
300 RequestStatus::IoError => Ok(Err(Error::IoError)),
301 RequestStatus::Invalid => Ok(Err(Error::Unknown)),
302 RequestStatus::Finished(code) => Ok(Ok(Response::new(req.id, code))),
303 })
304 .collect()
305 }
306}
307
308#[derive(Debug)]
310pub struct Response {
311 pub id: RequestId,
313 pub code: u16,
315 headers: Option<Headers>,
317}
318
319impl Response {
320 fn new(id: RequestId, code: u16) -> Self {
321 Self { id, code, headers: None }
322 }
323
324 pub fn headers(&mut self) -> &Headers {
326 if self.headers.is_none() {
327 self.headers = Some(Headers { raw: sp_io::offchain::http_response_headers(self.id) });
328 }
329 self.headers.as_ref().expect("Headers were just set; qed")
330 }
331
332 pub fn body(&self) -> ResponseBody {
334 ResponseBody::new(self.id)
335 }
336}
337
338#[derive(Clone)]
347pub struct ResponseBody {
348 id: RequestId,
349 error: Option<HttpError>,
350 buffer: [u8; 4096],
351 filled_up_to: Option<usize>,
352 position: usize,
353 deadline: Option<Timestamp>,
354}
355
356#[cfg(feature = "std")]
357impl std::fmt::Debug for ResponseBody {
358 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
359 fmt.debug_struct("ResponseBody")
360 .field("id", &self.id)
361 .field("error", &self.error)
362 .field("buffer", &self.buffer.len())
363 .field("filled_up_to", &self.filled_up_to)
364 .field("position", &self.position)
365 .field("deadline", &self.deadline)
366 .finish()
367 }
368}
369
370impl ResponseBody {
371 fn new(id: RequestId) -> Self {
372 ResponseBody {
373 id,
374 error: None,
375 buffer: [0_u8; 4096],
376 filled_up_to: None,
377 position: 0,
378 deadline: None,
379 }
380 }
381
382 pub fn deadline(&mut self, deadline: impl Into<Option<Timestamp>>) {
384 self.deadline = deadline.into();
385 self.error = None;
386 }
387
388 pub fn error(&self) -> &Option<HttpError> {
393 &self.error
394 }
395}
396
397impl Iterator for ResponseBody {
398 type Item = u8;
399
400 fn next(&mut self) -> Option<Self::Item> {
401 if self.error.is_some() {
402 return None
403 }
404
405 if self.filled_up_to.is_none() {
406 let result =
407 sp_io::offchain::http_response_read_body(self.id, &mut self.buffer, self.deadline);
408 match result {
409 Err(e) => {
410 self.error = Some(e);
411 return None
412 },
413 Ok(0) => return None,
414 Ok(size) => {
415 self.position = 0;
416 self.filled_up_to = Some(size as usize);
417 },
418 }
419 }
420
421 if Some(self.position) == self.filled_up_to {
422 self.filled_up_to = None;
423 return self.next()
424 }
425
426 let result = self.buffer[self.position];
427 self.position += 1;
428 Some(result)
429 }
430}
431
432#[derive(Clone, PartialEq, Eq, Debug)]
434pub struct Headers {
435 pub raw: Vec<(Vec<u8>, Vec<u8>)>,
437}
438
439impl Headers {
440 pub fn find(&self, name: &str) -> Option<&str> {
447 let raw = name.as_bytes();
448 for (key, val) in &self.raw {
449 if &**key == raw {
450 return str::from_utf8(val).ok()
451 }
452 }
453 None
454 }
455
456 pub fn into_iter(&self) -> HeadersIterator<'_> {
458 HeadersIterator { collection: &self.raw, index: None }
459 }
460}
461
462#[derive(Clone, Debug)]
464pub struct HeadersIterator<'a> {
465 collection: &'a [(Vec<u8>, Vec<u8>)],
466 index: Option<usize>,
467}
468
469impl<'a> HeadersIterator<'a> {
470 pub fn next(&mut self) -> bool {
474 let index = self.index.map(|x| x + 1).unwrap_or(0);
475 self.index = Some(index);
476 index < self.collection.len()
477 }
478
479 pub fn current(&self) -> Option<(&str, &str)> {
483 self.collection
484 .get(self.index?)
485 .map(|val| (str::from_utf8(&val.0).unwrap_or(""), str::from_utf8(&val.1).unwrap_or("")))
486 }
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492 use sp_core::offchain::{testing, OffchainWorkerExt};
493 use sp_io::TestExternalities;
494
495 #[test]
496 fn should_send_a_basic_request_and_get_response() {
497 let (offchain, state) = testing::TestOffchainExt::new();
498 let mut t = TestExternalities::default();
499 t.register_extension(OffchainWorkerExt::new(offchain));
500
501 t.execute_with(|| {
502 let request: Request = Request::get("http://localhost:1234");
503 let pending = request.add_header("X-Auth", "hunter2").send().unwrap();
504 state.write().fulfill_pending_request(
506 0,
507 testing::PendingRequest {
508 method: "GET".into(),
509 uri: "http://localhost:1234".into(),
510 headers: vec![("X-Auth".into(), "hunter2".into())],
511 sent: true,
512 ..Default::default()
513 },
514 b"1234".to_vec(),
515 None,
516 );
517
518 let mut response = pending.wait().unwrap();
520
521 let mut headers = response.headers().into_iter();
523 assert_eq!(headers.current(), None);
524 assert_eq!(headers.next(), false);
525 assert_eq!(headers.current(), None);
526
527 let body = response.body();
528 assert_eq!(body.clone().collect::<Vec<_>>(), b"1234".to_vec());
529 assert_eq!(body.error(), &None);
530 })
531 }
532
533 #[test]
534 fn should_send_huge_response() {
535 let (offchain, state) = testing::TestOffchainExt::new();
536 let mut t = TestExternalities::default();
537 t.register_extension(OffchainWorkerExt::new(offchain));
538
539 t.execute_with(|| {
540 let request: Request = Request::get("http://localhost:1234");
541 let pending = request.add_header("X-Auth", "hunter2").send().unwrap();
542 state.write().fulfill_pending_request(
544 0,
545 testing::PendingRequest {
546 method: "GET".into(),
547 uri: "http://localhost:1234".into(),
548 headers: vec![("X-Auth".into(), "hunter2".into())],
549 sent: true,
550 ..Default::default()
551 },
552 vec![0; 5923],
553 None,
554 );
555
556 let response = pending.wait().unwrap();
558
559 let body = response.body();
560 assert_eq!(body.clone().collect::<Vec<_>>(), vec![0; 5923]);
561 assert_eq!(body.error(), &None);
562 })
563 }
564
565 #[test]
566 fn should_send_a_post_request() {
567 let (offchain, state) = testing::TestOffchainExt::new();
568 let mut t = TestExternalities::default();
569 t.register_extension(OffchainWorkerExt::new(offchain));
570
571 t.execute_with(|| {
572 let pending = Request::default()
573 .method(Method::Post)
574 .url("http://localhost:1234")
575 .body(vec![b"1234"])
576 .send()
577 .unwrap();
578 state.write().fulfill_pending_request(
580 0,
581 testing::PendingRequest {
582 method: "POST".into(),
583 uri: "http://localhost:1234".into(),
584 body: b"1234".to_vec(),
585 sent: true,
586 ..Default::default()
587 },
588 b"1234".to_vec(),
589 Some(("Test".to_owned(), "Header".to_owned())),
590 );
591
592 let mut response = pending.wait().unwrap();
594
595 let mut headers = response.headers().into_iter();
597 assert_eq!(headers.current(), None);
598 assert_eq!(headers.next(), true);
599 assert_eq!(headers.current(), Some(("Test", "Header")));
600
601 let body = response.body();
602 assert_eq!(body.clone().collect::<Vec<_>>(), b"1234".to_vec());
603 assert_eq!(body.error(), &None);
604 })
605 }
606}