1use tetcore_std::str;
52use tetcore_std::prelude::Vec;
53#[cfg(not(feature = "std"))]
54use tetcore_std::prelude::vec;
55use tet_core::RuntimeDebug;
56use tet_core::offchain::{
57 Timestamp,
58 HttpRequestId as RequestId,
59 HttpRequestStatus as RequestStatus,
60 HttpError,
61};
62
63#[derive(Clone, PartialEq, Eq, RuntimeDebug)]
65pub enum Method {
66 Get,
68 Post,
70 Put,
72 Patch,
74 Delete,
76 Other(&'static str),
78}
79
80impl AsRef<str> for Method {
81 fn as_ref(&self) -> &str {
82 match *self {
83 Method::Get => "GET",
84 Method::Post => "POST",
85 Method::Put => "PUT",
86 Method::Patch => "PATCH",
87 Method::Delete => "DELETE",
88 Method::Other(m) => m,
89 }
90 }
91}
92
93mod header {
94 use super::*;
95
96 #[derive(Clone, PartialEq, Eq, RuntimeDebug)]
98 pub struct Header {
99 name: Vec<u8>,
100 value: Vec<u8>,
101 }
102
103 impl Header {
104 pub fn new(name: &str, value: &str) -> Self {
106 Header {
107 name: name.as_bytes().to_vec(),
108 value: value.as_bytes().to_vec(),
109 }
110 }
111
112 pub fn name(&self) -> &str {
114 unsafe { str::from_utf8_unchecked(&self.name) }
118 }
119
120 pub fn value(&self) -> &str {
122 unsafe { str::from_utf8_unchecked(&self.value) }
126 }
127 }
128}
129
130#[derive(Clone, PartialEq, Eq, RuntimeDebug)]
132pub struct Request<'a, T = Vec<&'static [u8]>> {
133 pub method: Method,
135 pub url: &'a str,
137 pub body: T,
139 pub deadline: Option<Timestamp>,
141 headers: Vec<header::Header>,
143}
144
145impl<T: Default> Default for Request<'static, T> {
146 fn default() -> Self {
147 Request {
148 method: Method::Get,
149 url: "http://localhost",
150 headers: Vec::new(),
151 body: Default::default(),
152 deadline: None,
153 }
154 }
155}
156
157impl<'a> Request<'a> {
158 pub fn get(url: &'a str) -> Self {
160 Self::new(url)
161 }
162}
163
164impl<'a, T> Request<'a, T> {
165 pub fn post(url: &'a str, body: T) -> Self {
167 let req: Request = Request::default();
168
169 Request {
170 url,
171 body,
172 method: Method::Post,
173 headers: req.headers,
174 deadline: req.deadline,
175 }
176 }
177}
178
179impl<'a, T: Default> Request<'a, T> {
180 pub fn new(url: &'a str) -> Self {
182 Request::default().url(url)
183 }
184
185 pub fn method(mut self, method: Method) -> Self {
187 self.method = method;
188 self
189 }
190
191 pub fn url(mut self, url: &'a str) -> Self {
193 self.url = url;
194 self
195 }
196
197 pub fn body(mut self, body: T) -> Self {
199 self.body = body;
200 self
201 }
202
203 pub fn add_header(mut self, name: &str, value: &str) -> Self {
205 self.headers.push(header::Header::new(name, value));
206 self
207 }
208
209 pub fn deadline(mut self, deadline: Timestamp) -> Self {
211 self.deadline = Some(deadline);
212 self
213 }
214}
215
216impl<'a, I: AsRef<[u8]>, T: IntoIterator<Item=I>> Request<'a, T> {
217 pub fn send(self) -> Result<PendingRequest, HttpError> {
222 let meta = &[];
223
224 let id = tet_io::offchain::http_request_start(
226 self.method.as_ref(),
227 self.url,
228 meta,
229 ).map_err(|_| HttpError::IoError)?;
230
231 for header in &self.headers {
233 tet_io::offchain::http_request_add_header(
234 id,
235 header.name(),
236 header.value(),
237 ).map_err(|_| HttpError::IoError)?
238 }
239
240 for chunk in self.body {
242 tet_io::offchain::http_request_write_body(id, chunk.as_ref(), self.deadline)?;
243 }
244
245 tet_io::offchain::http_request_write_body(id, &[], self.deadline)?;
247
248 Ok(PendingRequest {
249 id,
250 })
251 }
252}
253
254#[derive(Clone, PartialEq, Eq, RuntimeDebug)]
256pub enum Error {
257 DeadlineReached,
259 IoError,
261 Unknown,
263}
264
265#[derive(PartialEq, Eq, RuntimeDebug)]
267pub struct PendingRequest {
268 pub id: RequestId,
270}
271
272pub type HttpResult = Result<Response, Error>;
274
275impl PendingRequest {
276 pub fn wait(self) -> HttpResult {
280 match self.try_wait(None) {
281 Ok(res) => res,
282 Err(_) => panic!("Since `None` is passed we will never get a deadline error; qed"),
283 }
284 }
285
286 pub fn try_wait(self, deadline: impl Into<Option<Timestamp>>) -> Result<HttpResult, PendingRequest> {
289 Self::try_wait_all(vec![self], deadline).pop().expect("One request passed, one status received; qed")
290 }
291
292 pub fn wait_all(requests: Vec<PendingRequest>) -> Vec<HttpResult> {
294 Self::try_wait_all(requests, None)
295 .into_iter()
296 .map(|r| match r {
297 Ok(r) => r,
298 Err(_) => panic!("Since `None` is passed we will never get a deadline error; qed"),
299 })
300 .collect()
301 }
302
303 pub fn try_wait_all(
307 requests: Vec<PendingRequest>,
308 deadline: impl Into<Option<Timestamp>>
309 ) -> Vec<Result<HttpResult, PendingRequest>> {
310 let ids = requests.iter().map(|r| r.id).collect::<Vec<_>>();
311 let statuses = tet_io::offchain::http_response_wait(&ids, deadline.into());
312
313 statuses
314 .into_iter()
315 .zip(requests.into_iter())
316 .map(|(status, req)| match status {
317 RequestStatus::DeadlineReached => Err(req),
318 RequestStatus::IoError => Ok(Err(Error::IoError)),
319 RequestStatus::Invalid => Ok(Err(Error::Unknown)),
320 RequestStatus::Finished(code) => Ok(Ok(Response::new(req.id, code))),
321 })
322 .collect()
323 }
324}
325
326#[derive(RuntimeDebug)]
328pub struct Response {
329 pub id: RequestId,
331 pub code: u16,
333 headers: Option<Headers>,
335}
336
337impl Response {
338 fn new(id: RequestId, code: u16) -> Self {
339 Self {
340 id,
341 code,
342 headers: None,
343 }
344 }
345
346 pub fn headers(&mut self) -> &Headers {
348 if self.headers.is_none() {
349 self.headers = Some(
350 Headers { raw: tet_io::offchain::http_response_headers(self.id) },
351 );
352 }
353 self.headers.as_ref().expect("Headers were just set; qed")
354 }
355
356 pub fn body(&self) -> ResponseBody {
358 ResponseBody::new(self.id)
359 }
360}
361
362#[derive(Clone)]
371pub struct ResponseBody {
372 id: RequestId,
373 error: Option<HttpError>,
374 buffer: [u8; 4096],
375 filled_up_to: Option<usize>,
376 position: usize,
377 deadline: Option<Timestamp>,
378}
379
380#[cfg(feature = "std")]
381impl std::fmt::Debug for ResponseBody {
382 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
383 fmt.debug_struct("ResponseBody")
384 .field("id", &self.id)
385 .field("error", &self.error)
386 .field("buffer", &self.buffer.len())
387 .field("filled_up_to", &self.filled_up_to)
388 .field("position", &self.position)
389 .field("deadline", &self.deadline)
390 .finish()
391 }
392}
393
394impl ResponseBody {
395 fn new(id: RequestId) -> Self {
396 ResponseBody {
397 id,
398 error: None,
399 buffer: [0_u8; 4096],
400 filled_up_to: None,
401 position: 0,
402 deadline: None,
403 }
404 }
405
406 pub fn deadline(&mut self, deadline: impl Into<Option<Timestamp>>) {
408 self.deadline = deadline.into();
409 self.error = None;
410 }
411
412 pub fn error(&self) -> &Option<HttpError> {
417 &self.error
418 }
419}
420
421impl Iterator for ResponseBody {
422 type Item = u8;
423
424 fn next(&mut self) -> Option<Self::Item> {
425 if self.error.is_some() {
426 return None;
427 }
428
429 if self.filled_up_to.is_none() {
430 let result = tet_io::offchain::http_response_read_body(
431 self.id,
432 &mut self.buffer,
433 self.deadline);
434 match result {
435 Err(e) => {
436 self.error = Some(e);
437 return None;
438 }
439 Ok(0) => {
440 return None;
441 }
442 Ok(size) => {
443 self.position = 0;
444 self.filled_up_to = Some(size as usize);
445 }
446 }
447 }
448
449 if Some(self.position) == self.filled_up_to {
450 self.filled_up_to = None;
451 return self.next();
452 }
453
454 let result = self.buffer[self.position];
455 self.position += 1;
456 Some(result)
457 }
458}
459
460#[derive(Clone, PartialEq, Eq, RuntimeDebug)]
462pub struct Headers {
463 pub raw: Vec<(Vec<u8>, Vec<u8>)>,
465}
466
467impl Headers {
468 pub fn find(&self, name: &str) -> Option<&str> {
475 let raw = name.as_bytes();
476 for &(ref key, ref val) in &self.raw {
477 if &**key == raw {
478 return str::from_utf8(&val).ok()
479 }
480 }
481 None
482 }
483
484 pub fn into_iter(&self) -> HeadersIterator {
486 HeadersIterator { collection: &self.raw, index: None }
487 }
488}
489
490#[derive(Clone, RuntimeDebug)]
492pub struct HeadersIterator<'a> {
493 collection: &'a [(Vec<u8>, Vec<u8>)],
494 index: Option<usize>,
495}
496
497impl<'a> HeadersIterator<'a> {
498 pub fn next(&mut self) -> bool {
502 let index = self.index.map(|x| x + 1).unwrap_or(0);
503 self.index = Some(index);
504 index < self.collection.len()
505 }
506
507 pub fn current(&self) -> Option<(&str, &str)> {
511 self.collection.get(self.index?)
512 .map(|val| (str::from_utf8(&val.0).unwrap_or(""), str::from_utf8(&val.1).unwrap_or("")))
513 }
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519 use tet_io::TestExternalities;
520 use tet_core::offchain::{
521 OffchainExt,
522 testing,
523 };
524
525 #[test]
526 fn should_send_a_basic_request_and_get_response() {
527 let (offchain, state) = testing::TestOffchainExt::new();
528 let mut t = TestExternalities::default();
529 t.register_extension(OffchainExt::new(offchain));
530
531 t.execute_with(|| {
532 let request: Request = Request::get("http://localhost:1234");
533 let pending = request
534 .add_header("X-Auth", "hunter2")
535 .send()
536 .unwrap();
537 state.write().fulfill_pending_request(
539 0,
540 testing::PendingRequest {
541 method: "GET".into(),
542 uri: "http://localhost:1234".into(),
543 headers: vec![("X-Auth".into(), "hunter2".into())],
544 sent: true,
545 ..Default::default()
546 },
547 b"1234".to_vec(),
548 None,
549 );
550
551 let mut response = pending.wait().unwrap();
553
554 let mut headers = response.headers().into_iter();
556 assert_eq!(headers.current(), None);
557 assert_eq!(headers.next(), false);
558 assert_eq!(headers.current(), None);
559
560 let body = response.body();
561 assert_eq!(body.clone().collect::<Vec<_>>(), b"1234".to_vec());
562 assert_eq!(body.error(), &None);
563 })
564 }
565
566 #[test]
567 fn should_send_a_post_request() {
568 let (offchain, state) = testing::TestOffchainExt::new();
569 let mut t = TestExternalities::default();
570 t.register_extension(OffchainExt::new(offchain));
571
572 t.execute_with(|| {
573 let pending = Request::default()
574 .method(Method::Post)
575 .url("http://localhost:1234")
576 .body(vec![b"1234"])
577 .send()
578 .unwrap();
579 state.write().fulfill_pending_request(
581 0,
582 testing::PendingRequest {
583 method: "POST".into(),
584 uri: "http://localhost:1234".into(),
585 body: b"1234".to_vec(),
586 sent: true,
587 ..Default::default()
588 },
589 b"1234".to_vec(),
590 Some(("Test".to_owned(), "Header".to_owned())),
591 );
592
593 let mut response = pending.wait().unwrap();
595
596 let mut headers = response.headers().into_iter();
598 assert_eq!(headers.current(), None);
599 assert_eq!(headers.next(), true);
600 assert_eq!(headers.current(), Some(("Test", "Header")));
601
602 let body = response.body();
603 assert_eq!(body.clone().collect::<Vec<_>>(), b"1234".to_vec());
604 assert_eq!(body.error(), &None);
605 })
606 }
607}