1use alloc::{str, vec, vec::Vec};
52use pezsp_core::{
53 offchain::{
54 HttpError, HttpRequestId as RequestId, HttpRequestStatus as RequestStatus, Timestamp,
55 },
56 RuntimeDebug,
57};
58
59#[derive(Clone, PartialEq, Eq, RuntimeDebug)]
61pub enum Method {
62 Get,
64 Post,
66 Put,
68 Patch,
70 Delete,
72 Other(&'static str),
74}
75
76impl AsRef<str> for Method {
77 fn as_ref(&self) -> &str {
78 match *self {
79 Method::Get => "GET",
80 Method::Post => "POST",
81 Method::Put => "PUT",
82 Method::Patch => "PATCH",
83 Method::Delete => "DELETE",
84 Method::Other(m) => m,
85 }
86 }
87}
88
89mod header {
90 use super::*;
91
92 #[derive(Clone, PartialEq, Eq, RuntimeDebug)]
94 pub struct Header {
95 name: Vec<u8>,
96 value: Vec<u8>,
97 }
98
99 impl Header {
100 pub fn new(name: &str, value: &str) -> Self {
102 Header { name: name.as_bytes().to_vec(), value: value.as_bytes().to_vec() }
103 }
104
105 pub fn name(&self) -> &str {
107 unsafe { str::from_utf8_unchecked(&self.name) }
111 }
112
113 pub fn value(&self) -> &str {
115 unsafe { str::from_utf8_unchecked(&self.value) }
119 }
120 }
121}
122
123#[derive(Clone, PartialEq, Eq, RuntimeDebug)]
125pub struct Request<'a, T = Vec<&'static [u8]>> {
126 pub method: Method,
128 pub url: &'a str,
130 pub body: T,
132 pub deadline: Option<Timestamp>,
134 headers: Vec<header::Header>,
136}
137
138impl<T: Default> Default for Request<'static, T> {
139 fn default() -> Self {
140 Request {
141 method: Method::Get,
142 url: "http://localhost",
143 headers: Vec::new(),
144 body: Default::default(),
145 deadline: None,
146 }
147 }
148}
149
150impl<'a> Request<'a> {
151 pub fn get(url: &'a str) -> Self {
153 Self::new(url)
154 }
155}
156
157impl<'a, T> Request<'a, T> {
158 pub fn post(url: &'a str, body: T) -> Self {
160 let req: Request = Request::default();
161
162 Request { url, body, method: Method::Post, headers: req.headers, deadline: req.deadline }
163 }
164}
165
166impl<'a, T: Default> Request<'a, T> {
167 pub fn new(url: &'a str) -> Self {
169 Request::default().url(url)
170 }
171
172 pub fn method(mut self, method: Method) -> Self {
174 self.method = method;
175 self
176 }
177
178 pub fn url(mut self, url: &'a str) -> Self {
180 self.url = url;
181 self
182 }
183
184 pub fn body(mut self, body: T) -> Self {
186 self.body = body;
187 self
188 }
189
190 pub fn add_header(mut self, name: &str, value: &str) -> Self {
192 self.headers.push(header::Header::new(name, value));
193 self
194 }
195
196 pub fn deadline(mut self, deadline: Timestamp) -> Self {
198 self.deadline = Some(deadline);
199 self
200 }
201}
202
203impl<'a, I: AsRef<[u8]>, T: IntoIterator<Item = I>> Request<'a, T> {
204 pub fn send(self) -> Result<PendingRequest, HttpError> {
209 let meta = &[];
210
211 let id = pezsp_io::offchain::http_request_start(self.method.as_ref(), self.url, meta)
213 .map_err(|_| HttpError::IoError)?;
214
215 for header in &self.headers {
217 pezsp_io::offchain::http_request_add_header(id, header.name(), header.value())
218 .map_err(|_| HttpError::IoError)?
219 }
220
221 for chunk in self.body {
223 pezsp_io::offchain::http_request_write_body(id, chunk.as_ref(), self.deadline)?;
224 }
225
226 pezsp_io::offchain::http_request_write_body(id, &[], self.deadline)?;
228
229 Ok(PendingRequest { id })
230 }
231}
232
233#[derive(Clone, PartialEq, Eq, RuntimeDebug)]
235pub enum Error {
236 DeadlineReached,
238 IoError,
240 Unknown,
242}
243
244#[derive(PartialEq, Eq, RuntimeDebug)]
246pub struct PendingRequest {
247 pub id: RequestId,
249}
250
251pub type HttpResult = Result<Response, Error>;
253
254impl PendingRequest {
255 pub fn wait(self) -> HttpResult {
259 match self.try_wait(None) {
260 Ok(res) => res,
261 Err(_) => panic!("Since `None` is passed we will never get a deadline error; qed"),
262 }
263 }
264
265 pub fn try_wait(
268 self,
269 deadline: impl Into<Option<Timestamp>>,
270 ) -> Result<HttpResult, PendingRequest> {
271 Self::try_wait_all(vec![self], deadline)
272 .pop()
273 .expect("One request passed, one status received; qed")
274 }
275
276 pub fn wait_all(requests: Vec<PendingRequest>) -> Vec<HttpResult> {
278 Self::try_wait_all(requests, None)
279 .into_iter()
280 .map(|r| match r {
281 Ok(r) => r,
282 Err(_) => panic!("Since `None` is passed we will never get a deadline error; qed"),
283 })
284 .collect()
285 }
286
287 pub fn try_wait_all(
292 requests: Vec<PendingRequest>,
293 deadline: impl Into<Option<Timestamp>>,
294 ) -> Vec<Result<HttpResult, PendingRequest>> {
295 let ids = requests.iter().map(|r| r.id).collect::<Vec<_>>();
296 let statuses = pezsp_io::offchain::http_response_wait(&ids, deadline.into());
297
298 statuses
299 .into_iter()
300 .zip(requests.into_iter())
301 .map(|(status, req)| match status {
302 RequestStatus::DeadlineReached => Err(req),
303 RequestStatus::IoError => Ok(Err(Error::IoError)),
304 RequestStatus::Invalid => Ok(Err(Error::Unknown)),
305 RequestStatus::Finished(code) => Ok(Ok(Response::new(req.id, code))),
306 })
307 .collect()
308 }
309}
310
311#[derive(RuntimeDebug)]
313pub struct Response {
314 pub id: RequestId,
316 pub code: u16,
318 headers: Option<Headers>,
320}
321
322impl Response {
323 fn new(id: RequestId, code: u16) -> Self {
324 Self { id, code, headers: None }
325 }
326
327 pub fn headers(&mut self) -> &Headers {
329 if self.headers.is_none() {
330 self.headers =
331 Some(Headers { raw: pezsp_io::offchain::http_response_headers(self.id) });
332 }
333 self.headers.as_ref().expect("Headers were just set; qed")
334 }
335
336 pub fn body(&self) -> ResponseBody {
338 ResponseBody::new(self.id)
339 }
340}
341
342#[derive(Clone)]
351pub struct ResponseBody {
352 id: RequestId,
353 error: Option<HttpError>,
354 buffer: [u8; 4096],
355 filled_up_to: Option<usize>,
356 position: usize,
357 deadline: Option<Timestamp>,
358}
359
360#[cfg(feature = "std")]
361impl std::fmt::Debug for ResponseBody {
362 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
363 fmt.debug_struct("ResponseBody")
364 .field("id", &self.id)
365 .field("error", &self.error)
366 .field("buffer", &self.buffer.len())
367 .field("filled_up_to", &self.filled_up_to)
368 .field("position", &self.position)
369 .field("deadline", &self.deadline)
370 .finish()
371 }
372}
373
374impl ResponseBody {
375 fn new(id: RequestId) -> Self {
376 ResponseBody {
377 id,
378 error: None,
379 buffer: [0_u8; 4096],
380 filled_up_to: None,
381 position: 0,
382 deadline: None,
383 }
384 }
385
386 pub fn deadline(&mut self, deadline: impl Into<Option<Timestamp>>) {
388 self.deadline = deadline.into();
389 self.error = None;
390 }
391
392 pub fn error(&self) -> &Option<HttpError> {
397 &self.error
398 }
399}
400
401impl Iterator for ResponseBody {
402 type Item = u8;
403
404 fn next(&mut self) -> Option<Self::Item> {
405 if self.error.is_some() {
406 return None;
407 }
408
409 if self.filled_up_to.is_none() {
410 let result = pezsp_io::offchain::http_response_read_body(
411 self.id,
412 &mut self.buffer,
413 self.deadline,
414 );
415 match result {
416 Err(e) => {
417 self.error = Some(e);
418 return None;
419 },
420 Ok(0) => return None,
421 Ok(size) => {
422 self.position = 0;
423 self.filled_up_to = Some(size as usize);
424 },
425 }
426 }
427
428 if Some(self.position) == self.filled_up_to {
429 self.filled_up_to = None;
430 return self.next();
431 }
432
433 let result = self.buffer[self.position];
434 self.position += 1;
435 Some(result)
436 }
437}
438
439#[derive(Clone, PartialEq, Eq, RuntimeDebug)]
441pub struct Headers {
442 pub raw: Vec<(Vec<u8>, Vec<u8>)>,
444}
445
446impl Headers {
447 pub fn find(&self, name: &str) -> Option<&str> {
454 let raw = name.as_bytes();
455 for (key, val) in &self.raw {
456 if &**key == raw {
457 return str::from_utf8(val).ok();
458 }
459 }
460 None
461 }
462
463 pub fn into_iter(&self) -> HeadersIterator<'_> {
465 HeadersIterator { collection: &self.raw, index: None }
466 }
467}
468
469#[derive(Clone, RuntimeDebug)]
471pub struct HeadersIterator<'a> {
472 collection: &'a [(Vec<u8>, Vec<u8>)],
473 index: Option<usize>,
474}
475
476impl<'a> HeadersIterator<'a> {
477 pub fn next(&mut self) -> bool {
481 let index = self.index.map(|x| x + 1).unwrap_or(0);
482 self.index = Some(index);
483 index < self.collection.len()
484 }
485
486 pub fn current(&self) -> Option<(&str, &str)> {
490 self.collection
491 .get(self.index?)
492 .map(|val| (str::from_utf8(&val.0).unwrap_or(""), str::from_utf8(&val.1).unwrap_or("")))
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499 use pezsp_core::offchain::{testing, OffchainWorkerExt};
500 use pezsp_io::TestExternalities;
501
502 #[test]
503 fn should_send_a_basic_request_and_get_response() {
504 let (offchain, state) = testing::TestOffchainExt::new();
505 let mut t = TestExternalities::default();
506 t.register_extension(OffchainWorkerExt::new(offchain));
507
508 t.execute_with(|| {
509 let request: Request = Request::get("http://localhost:1234");
510 let pending = request.add_header("X-Auth", "hunter2").send().unwrap();
511 state.write().fulfill_pending_request(
513 0,
514 testing::PendingRequest {
515 method: "GET".into(),
516 uri: "http://localhost:1234".into(),
517 headers: vec![("X-Auth".into(), "hunter2".into())],
518 sent: true,
519 ..Default::default()
520 },
521 b"1234".to_vec(),
522 None,
523 );
524
525 let mut response = pending.wait().unwrap();
527
528 let mut headers = response.headers().into_iter();
530 assert_eq!(headers.current(), None);
531 assert_eq!(headers.next(), false);
532 assert_eq!(headers.current(), None);
533
534 let body = response.body();
535 assert_eq!(body.clone().collect::<Vec<_>>(), b"1234".to_vec());
536 assert_eq!(body.error(), &None);
537 })
538 }
539
540 #[test]
541 fn should_send_huge_response() {
542 let (offchain, state) = testing::TestOffchainExt::new();
543 let mut t = TestExternalities::default();
544 t.register_extension(OffchainWorkerExt::new(offchain));
545
546 t.execute_with(|| {
547 let request: Request = Request::get("http://localhost:1234");
548 let pending = request.add_header("X-Auth", "hunter2").send().unwrap();
549 state.write().fulfill_pending_request(
551 0,
552 testing::PendingRequest {
553 method: "GET".into(),
554 uri: "http://localhost:1234".into(),
555 headers: vec![("X-Auth".into(), "hunter2".into())],
556 sent: true,
557 ..Default::default()
558 },
559 vec![0; 5923],
560 None,
561 );
562
563 let response = pending.wait().unwrap();
565
566 let body = response.body();
567 assert_eq!(body.clone().collect::<Vec<_>>(), vec![0; 5923]);
568 assert_eq!(body.error(), &None);
569 })
570 }
571
572 #[test]
573 fn should_send_a_post_request() {
574 let (offchain, state) = testing::TestOffchainExt::new();
575 let mut t = TestExternalities::default();
576 t.register_extension(OffchainWorkerExt::new(offchain));
577
578 t.execute_with(|| {
579 let pending = Request::default()
580 .method(Method::Post)
581 .url("http://localhost:1234")
582 .body(vec![b"1234"])
583 .send()
584 .unwrap();
585 state.write().fulfill_pending_request(
587 0,
588 testing::PendingRequest {
589 method: "POST".into(),
590 uri: "http://localhost:1234".into(),
591 body: b"1234".to_vec(),
592 sent: true,
593 ..Default::default()
594 },
595 b"1234".to_vec(),
596 Some(("Test".to_owned(), "Header".to_owned())),
597 );
598
599 let mut response = pending.wait().unwrap();
601
602 let mut headers = response.headers().into_iter();
604 assert_eq!(headers.current(), None);
605 assert_eq!(headers.next(), true);
606 assert_eq!(headers.current(), Some(("Test", "Header")));
607
608 let body = response.body();
609 assert_eq!(body.clone().collect::<Vec<_>>(), b"1234".to_vec());
610 assert_eq!(body.error(), &None);
611 })
612 }
613}