Skip to main content

io_http/rfc1945/
send.rs

1//! I/O-free coroutine sending an HTTP/1.0 request and receiving its response
2//! ([RFC 1945]).
3//!
4//! Same shape as [`crate::rfc9112::send::Http11Send`] but with the HTTP/1.0
5//! wire format: no chunked transfer coding, connections close by default unless
6//! the server returns `Connection: keep-alive`.
7//!
8//! | Strategy     | Trigger               |
9//! |--------------|-----------------------|
10//! | Fixed-length | `Content-Length: <n>` |
11//! | Read-to-EOF  | Header absent         |
12//!
13//! # Example
14//!
15//! ```rust,no_run
16//! use std::{io::{Read, Write}, net::TcpStream};
17//!
18//! use io_http::{
19//!     coroutine::*,
20//!     rfc1945::send::Http10Send,
21//!     rfc9110::{request::HttpRequest, send::HttpSendYield},
22//! };
23//! use url::Url;
24//!
25//! let url = Url::parse("http://example.com/").unwrap();
26//! let request = HttpRequest::get(url.clone())
27//!     .header("Host", url.host_str().unwrap());
28//!
29//! let mut stream = TcpStream::connect("example.com:80").unwrap();
30//! let mut send = Http10Send::new(request);
31//! let mut arg: Option<&[u8]> = None;
32//! let mut buf = [0u8; 4096];
33//!
34//! let response = loop {
35//!     match send.resume(arg.take()) {
36//!         HttpCoroutineState::Complete(Ok(out)) => break out.response,
37//!         HttpCoroutineState::Complete(Err(err)) => panic!("{err}"),
38//!         HttpCoroutineState::Yielded(HttpSendYield::WantsWrite(bytes)) => {
39//!             stream.write_all(&bytes).unwrap();
40//!         }
41//!         HttpCoroutineState::Yielded(HttpSendYield::WantsRead) => {
42//!             let n = stream.read(&mut buf).unwrap();
43//!             arg = Some(&buf[..n]);
44//!         }
45//!         HttpCoroutineState::Yielded(HttpSendYield::WantsRedirect { .. }) => unimplemented!(),
46//!     }
47//! };
48//!
49//! println!("{}", *response.status);
50//! ```
51//!
52//! [RFC 1945]: https://www.rfc-editor.org/rfc/rfc1945
53
54use core::{fmt, mem};
55
56use alloc::{borrow::ToOwned, string::String, vec::Vec};
57
58use httparse::Error as HttparseError;
59use log::trace;
60use thiserror::Error;
61use url::Url;
62
63use crate::{
64    coroutine::*,
65    rfc9110::{
66        headers::{CONTENT_LENGTH, LOCATION},
67        request::HttpRequest,
68        response::HttpResponse,
69        send::{HttpSendOutput, HttpSendYield},
70    },
71    rfc9112::read_headers::{Http11ReadHeaders, Http11ReadHeadersError},
72};
73
74/// Failure causes during the HTTP/1.0 send flow.
75#[derive(Debug, Error)]
76pub enum Http10SendError {
77    #[error("HTTP/1.0 send failed: reached unexpected EOF")]
78    Eof,
79    #[error("HTTP/1.0 send failed: parse response headers: {0}")]
80    ParseResponseHeaders(HttparseError),
81    #[error("HTTP/1.0 send failed: invalid content length `{0}`")]
82    InvalidContentLength(String),
83}
84
85impl From<Http11ReadHeadersError> for Http10SendError {
86    fn from(err: Http11ReadHeadersError) -> Self {
87        match err {
88            Http11ReadHeadersError::Eof => Self::Eof,
89            Http11ReadHeadersError::ParseResponseHeaders(e) => Self::ParseResponseHeaders(e),
90        }
91    }
92}
93
94/// I/O-free coroutine to send an HTTP/1.0 request and receive its response.
95#[derive(Debug)]
96pub struct Http10Send {
97    request_url: Url,
98    state: State,
99    wants_write: Option<Vec<u8>>,
100    keep_alive: bool,
101    response: Option<HttpResponse>,
102    buf: Vec<u8>,
103}
104
105impl Http10Send {
106    /// Creates a new coroutine that will send the given request and
107    /// receive its response.
108    pub fn new(req: HttpRequest) -> Self {
109        trace!("prepares HTTP/1.0 request to be sent: {req:?}");
110
111        let request_url = req.url.clone();
112        let bytes = req.to_http_10_vec();
113
114        Self {
115            request_url,
116            state: State::ReadHeaders(Http11ReadHeaders::default()),
117            wants_write: Some(bytes),
118            keep_alive: false,
119            response: None,
120            buf: Vec::new(),
121        }
122    }
123
124    fn finish(
125        &self,
126        response: HttpResponse,
127        remaining: Vec<u8>,
128    ) -> HttpCoroutineState<HttpSendYield, Result<HttpSendOutput, Http10SendError>> {
129        let keep_alive = self.keep_alive;
130
131        if response.status.is_redirection() {
132            if let Some(location) = response.header(LOCATION) {
133                if let Ok(url) = self.request_url.join(location) {
134                    let same_scheme = self.request_url.scheme() == url.scheme();
135                    let same_host = self.request_url.host() == url.host()
136                        && self.request_url.port() == url.port();
137                    let same_origin = same_scheme && same_host;
138
139                    return HttpCoroutineState::Yielded(HttpSendYield::WantsRedirect {
140                        url,
141                        response,
142                        keep_alive,
143                        same_origin,
144                    });
145                }
146            }
147        }
148
149        HttpCoroutineState::Complete(Ok(HttpSendOutput {
150            response,
151            remaining,
152            keep_alive,
153        }))
154    }
155}
156
157impl HttpCoroutine for Http10Send {
158    type Yield = HttpSendYield;
159    type Return = Result<HttpSendOutput, Http10SendError>;
160
161    fn resume(&mut self, mut arg: Option<&[u8]>) -> HttpCoroutineState<Self::Yield, Self::Return> {
162        loop {
163            trace!("http/1.0 send: {}", self.state);
164
165            if let Some(bytes) = self.wants_write.take() {
166                return HttpCoroutineState::Yielded(HttpSendYield::WantsWrite(bytes));
167            }
168
169            match &mut self.state {
170                State::ReadHeaders(rh) => match rh.resume(arg.take()) {
171                    HttpCoroutineState::Yielded(HttpYield::WantsRead) => {
172                        return HttpCoroutineState::Yielded(HttpSendYield::WantsRead);
173                    }
174                    HttpCoroutineState::Yielded(HttpYield::WantsWrite(_)) => {
175                        unreachable!("Http11ReadHeaders never writes");
176                    }
177                    HttpCoroutineState::Complete(Err(err)) => {
178                        return HttpCoroutineState::Complete(Err(err.into()));
179                    }
180                    HttpCoroutineState::Complete(Ok(out)) => {
181                        let response = out.response;
182                        self.keep_alive = out.keep_alive;
183                        let status = *response.status;
184
185                        if status == 204 || status == 304 {
186                            return self.finish(response, out.remaining);
187                        }
188
189                        if let Some(len_str) = response.header(CONTENT_LENGTH) {
190                            let len_str = len_str.trim();
191                            let Ok(len) = len_str.parse::<usize>() else {
192                                let err = Http10SendError::InvalidContentLength(len_str.to_owned());
193                                return HttpCoroutineState::Complete(Err(err));
194                            };
195                            self.buf = out.remaining;
196                            self.response = Some(response);
197                            self.state = State::BodyLength(len);
198                            continue;
199                        }
200
201                        self.buf = out.remaining;
202                        self.response = Some(response);
203                        self.state = State::BodyEof;
204                    }
205                },
206                State::BodyLength(len) => {
207                    if let Some(data) = arg.take() {
208                        self.buf.extend_from_slice(data);
209                    }
210
211                    if *len > self.buf.len() {
212                        trace!("received incomplete body {len}/{}", self.buf.len());
213                        return HttpCoroutineState::Yielded(HttpSendYield::WantsRead);
214                    }
215
216                    let body = self.buf.drain(..*len).collect();
217                    let remaining = mem::take(&mut self.buf);
218                    let mut response = self.response.take().expect("response missing");
219                    response.body = body;
220                    return self.finish(response, remaining);
221                }
222                State::BodyEof => match arg.take() {
223                    Some(&[]) => {
224                        let buf = mem::take(&mut self.buf);
225                        let mut response = self.response.take().expect("response missing");
226                        response.body = buf;
227                        return self.finish(response, Vec::new());
228                    }
229                    Some(data) => {
230                        self.buf.extend_from_slice(data);
231                        return HttpCoroutineState::Yielded(HttpSendYield::WantsRead);
232                    }
233                    None => {
234                        return HttpCoroutineState::Yielded(HttpSendYield::WantsRead);
235                    }
236                },
237            }
238        }
239    }
240}
241
242#[derive(Debug)]
243enum State {
244    ReadHeaders(Http11ReadHeaders),
245    BodyLength(usize),
246    BodyEof,
247}
248
249impl fmt::Display for State {
250    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251        match self {
252            Self::ReadHeaders(_) => f.write_str("read headers"),
253            Self::BodyLength(_) => f.write_str("read body length"),
254            Self::BodyEof => f.write_str("read body until eof"),
255        }
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn body_length_completes() {
265        let req = HttpRequest::get("http://example.com".try_into().unwrap());
266        let mut coroutine = Http10Send::new(req);
267
268        let bytes = expect_wants_write(&mut coroutine, None);
269        assert_eq!(bytes, b"GET / HTTP/1.0\r\ncontent-length: 0\r\n\r\n");
270
271        expect_wants_read(&mut coroutine, None);
272
273        let reply = b"HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nhello";
274        let out = expect_complete_ok(&mut coroutine, Some(reply));
275        assert_eq!(out.response.version, "HTTP/1.0");
276        assert_eq!(*out.response.status, 200);
277        assert_eq!(out.response.body, b"hello");
278        assert!(!out.keep_alive);
279    }
280
281    #[test]
282    fn body_eof_completes() {
283        let req = HttpRequest::get("http://example.com".try_into().unwrap());
284        let mut coroutine = Http10Send::new(req);
285
286        expect_wants_write(&mut coroutine, None);
287        expect_wants_read(&mut coroutine, None);
288        expect_wants_read(&mut coroutine, Some(b"HTTP/1.0 200 OK\r\n\r\nhello "));
289        expect_wants_read(&mut coroutine, Some(b"world"));
290
291        let out = expect_complete_ok(&mut coroutine, Some(b""));
292        assert_eq!(out.response.body, b"hello world");
293        assert!(!out.keep_alive);
294    }
295
296    #[test]
297    fn keep_alive_when_server_says_so() {
298        let req = HttpRequest::get("http://example.com".try_into().unwrap());
299        let mut coroutine = Http10Send::new(req);
300
301        expect_wants_write(&mut coroutine, None);
302        expect_wants_read(&mut coroutine, None);
303
304        let reply = b"HTTP/1.0 200 OK\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
305        let out = expect_complete_ok(&mut coroutine, Some(reply));
306        assert!(out.keep_alive);
307    }
308
309    #[test]
310    fn invalid_content_length_errors() {
311        let req = HttpRequest::get("http://example.com".try_into().unwrap());
312        let mut coroutine = Http10Send::new(req);
313
314        expect_wants_write(&mut coroutine, None);
315        expect_wants_read(&mut coroutine, None);
316
317        let reply = b"HTTP/1.0 200 OK\r\nContent-Length: notanumber\r\n\r\n";
318        let err = expect_complete_err(&mut coroutine, Some(reply));
319        let Http10SendError::InvalidContentLength(s) = err else {
320            panic!("expected InvalidContentLength, got {err:?}");
321        };
322        assert_eq!(s, "notanumber");
323    }
324
325    // --- utils
326
327    fn expect_wants_write(cor: &mut Http10Send, arg: Option<&[u8]>) -> Vec<u8> {
328        match cor.resume(arg) {
329            HttpCoroutineState::Yielded(HttpSendYield::WantsWrite(bytes)) => bytes,
330            state => panic!("expected WantsWrite, got {state:?}"),
331        }
332    }
333
334    fn expect_wants_read(cor: &mut Http10Send, arg: Option<&[u8]>) {
335        match cor.resume(arg) {
336            HttpCoroutineState::Yielded(HttpSendYield::WantsRead) => {}
337            state => panic!("expected WantsRead, got {state:?}"),
338        }
339    }
340
341    fn expect_complete_ok(cor: &mut Http10Send, arg: Option<&[u8]>) -> HttpSendOutput {
342        match cor.resume(arg) {
343            HttpCoroutineState::Complete(Ok(out)) => out,
344            state => panic!("expected Complete(Ok), got {state:?}"),
345        }
346    }
347
348    fn expect_complete_err(cor: &mut Http10Send, arg: Option<&[u8]>) -> Http10SendError {
349        match cor.resume(arg) {
350            HttpCoroutineState::Complete(Err(err)) => err,
351            state => panic!("expected Complete(Err), got {state:?}"),
352        }
353    }
354}