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::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!("prepare 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            if let Some(bytes) = self.wants_write.take() {
164                return HttpCoroutineState::Yielded(HttpSendYield::WantsWrite(bytes));
165            }
166
167            match &mut self.state {
168                State::ReadHeaders(rh) => match rh.resume(arg.take()) {
169                    HttpCoroutineState::Yielded(HttpYield::WantsRead) => {
170                        return HttpCoroutineState::Yielded(HttpSendYield::WantsRead);
171                    }
172                    HttpCoroutineState::Yielded(HttpYield::WantsWrite(_)) => {
173                        unreachable!("Http11ReadHeaders never writes");
174                    }
175                    HttpCoroutineState::Complete(Err(err)) => {
176                        return HttpCoroutineState::Complete(Err(err.into()));
177                    }
178                    HttpCoroutineState::Complete(Ok(out)) => {
179                        let response = out.response;
180                        self.keep_alive = out.keep_alive;
181                        let status = *response.status;
182
183                        if status == 204 || status == 304 {
184                            return self.finish(response, out.remaining);
185                        }
186
187                        if let Some(len_str) = response.header(CONTENT_LENGTH) {
188                            let len_str = len_str.trim();
189                            let Ok(len) = len_str.parse::<usize>() else {
190                                let err = Http10SendError::InvalidContentLength(len_str.to_owned());
191                                return HttpCoroutineState::Complete(Err(err));
192                            };
193                            self.buf = out.remaining;
194                            self.response = Some(response);
195                            trace!("reading body of length {len}");
196                            self.state = State::BodyLength(len);
197                            continue;
198                        }
199
200                        self.buf = out.remaining;
201                        self.response = Some(response);
202                        trace!("reading body until connection closes");
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
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    #[test]
254    fn body_length_completes() {
255        let req = HttpRequest::get("http://example.com".try_into().unwrap());
256        let mut coroutine = Http10Send::new(req);
257
258        let bytes = expect_wants_write(&mut coroutine, None);
259        assert_eq!(bytes, b"GET / HTTP/1.0\r\ncontent-length: 0\r\n\r\n");
260
261        expect_wants_read(&mut coroutine, None);
262
263        let reply = b"HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nhello";
264        let out = expect_complete_ok(&mut coroutine, Some(reply));
265        assert_eq!(out.response.version, "HTTP/1.0");
266        assert_eq!(*out.response.status, 200);
267        assert_eq!(out.response.body, b"hello");
268        assert!(!out.keep_alive);
269    }
270
271    #[test]
272    fn body_eof_completes() {
273        let req = HttpRequest::get("http://example.com".try_into().unwrap());
274        let mut coroutine = Http10Send::new(req);
275
276        expect_wants_write(&mut coroutine, None);
277        expect_wants_read(&mut coroutine, None);
278        expect_wants_read(&mut coroutine, Some(b"HTTP/1.0 200 OK\r\n\r\nhello "));
279        expect_wants_read(&mut coroutine, Some(b"world"));
280
281        let out = expect_complete_ok(&mut coroutine, Some(b""));
282        assert_eq!(out.response.body, b"hello world");
283        assert!(!out.keep_alive);
284    }
285
286    #[test]
287    fn keep_alive_when_server_says_so() {
288        let req = HttpRequest::get("http://example.com".try_into().unwrap());
289        let mut coroutine = Http10Send::new(req);
290
291        expect_wants_write(&mut coroutine, None);
292        expect_wants_read(&mut coroutine, None);
293
294        let reply = b"HTTP/1.0 200 OK\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
295        let out = expect_complete_ok(&mut coroutine, Some(reply));
296        assert!(out.keep_alive);
297    }
298
299    #[test]
300    fn invalid_content_length_errors() {
301        let req = HttpRequest::get("http://example.com".try_into().unwrap());
302        let mut coroutine = Http10Send::new(req);
303
304        expect_wants_write(&mut coroutine, None);
305        expect_wants_read(&mut coroutine, None);
306
307        let reply = b"HTTP/1.0 200 OK\r\nContent-Length: notanumber\r\n\r\n";
308        let err = expect_complete_err(&mut coroutine, Some(reply));
309        let Http10SendError::InvalidContentLength(s) = err else {
310            panic!("expected InvalidContentLength, got {err:?}");
311        };
312        assert_eq!(s, "notanumber");
313    }
314
315    // --- utils
316
317    fn expect_wants_write(cor: &mut Http10Send, arg: Option<&[u8]>) -> Vec<u8> {
318        match cor.resume(arg) {
319            HttpCoroutineState::Yielded(HttpSendYield::WantsWrite(bytes)) => bytes,
320            state => panic!("expected WantsWrite, got {state:?}"),
321        }
322    }
323
324    fn expect_wants_read(cor: &mut Http10Send, arg: Option<&[u8]>) {
325        match cor.resume(arg) {
326            HttpCoroutineState::Yielded(HttpSendYield::WantsRead) => {}
327            state => panic!("expected WantsRead, got {state:?}"),
328        }
329    }
330
331    fn expect_complete_ok(cor: &mut Http10Send, arg: Option<&[u8]>) -> HttpSendOutput {
332        match cor.resume(arg) {
333            HttpCoroutineState::Complete(Ok(out)) => out,
334            state => panic!("expected Complete(Ok), got {state:?}"),
335        }
336    }
337
338    fn expect_complete_err(cor: &mut Http10Send, arg: Option<&[u8]>) -> Http10SendError {
339        match cor.resume(arg) {
340            HttpCoroutineState::Complete(Err(err)) => err,
341            state => panic!("expected Complete(Err), got {state:?}"),
342        }
343    }
344}