1use std::io::{self, Read, Write};
6
7use crate::error::HttpError;
8use crate::header::Header;
9use crate::parse::transfer::Body;
10use crate::status;
11
12pub trait ResponseWriter: Send {
19 fn header(&mut self) -> &mut Header;
21
22 fn write(&mut self, buf: &[u8]) -> Result<usize, HttpError>;
24
25 fn write_header(&mut self, status_code: u16);
28}
29
30pub struct ConnResponseWriter<W: Write + Send> {
36 pub(crate) inner: W,
37 header: Header,
38 status: u16,
39 header_written: bool,
40 chunked: bool,
42}
43
44impl<W: Write + Send> ConnResponseWriter<W> {
45 pub fn new(inner: W) -> Self {
46 let mut header = Header::new();
47 header.set("Content-Type", "text/plain; charset=utf-8");
49 Self {
50 inner,
51 header,
52 status: 200,
53 header_written: false,
54 chunked: true, }
56 }
57
58 fn flush_headers(&mut self) -> Result<(), HttpError> {
59 if self.header_written {
60 return Ok(());
61 }
62 self.header_written = true;
63
64 let has_cl = self.header.get("Content-Length").is_some();
66 if !has_cl {
67 self.header.set("Transfer-Encoding", "chunked");
68 }
69
70 let status_text = status::status_text(self.status);
71 write!(self.inner, "HTTP/1.1 {} {}\r\n", self.status, status_text)?;
72 self.header.write_to(&mut self.inner)?;
73 self.inner.write_all(b"\r\n")?;
74 Ok(())
75 }
76}
77
78impl<W: Write + Send> ResponseWriter for ConnResponseWriter<W> {
79 fn header(&mut self) -> &mut Header {
80 &mut self.header
81 }
82
83 fn write(&mut self, buf: &[u8]) -> Result<usize, HttpError> {
84 self.flush_headers()?;
85 if self.chunked && self.header.get("Content-Length").is_none() {
86 write!(self.inner, "{:X}\r\n", buf.len())?;
88 self.inner.write_all(buf)?;
89 self.inner.write_all(b"\r\n")?;
90 Ok(buf.len())
91 } else {
92 self.inner.write_all(buf)?;
93 Ok(buf.len())
94 }
95 }
96
97 fn write_header(&mut self, status_code: u16) {
98 if self.header_written {
99 return;
100 }
101 self.status = status_code;
102 }
103}
104
105impl<W: Write + Send> ConnResponseWriter<W> {
106 pub fn finish(&mut self) -> Result<(), HttpError> {
108 self.flush_headers()?;
109 if self.chunked && self.header.get("Content-Length").is_none() {
110 self.inner.write_all(b"0\r\n\r\n")?;
111 }
112 self.inner.flush()?;
113 Ok(())
114 }
115}
116
117pub struct Response {
124 pub status: u16,
126 pub status_text: String,
128 pub proto: String,
130 pub proto_major: u8,
131 pub proto_minor: u8,
132 pub header: Header,
134 pub body: Option<Body>,
136 pub content_length: i64,
138 pub transfer_encoding: Vec<String>,
140 pub trailer: Header,
142}
143
144impl Response {
145 pub fn body_bytes(&mut self) -> Result<Vec<u8>, HttpError> {
147 let mut out = Vec::new();
148 if let Some(body) = self.body.take() {
149 BodyReader(body)
150 .read_to_end(&mut out)
151 .map_err(|_| HttpError::BodyRead)?;
152 }
153 Ok(out)
154 }
155
156 pub fn body_string(&mut self) -> Result<String, HttpError> {
158 let bytes = self.body_bytes()?;
159 String::from_utf8(bytes)
160 .map_err(|_| HttpError::BodyRead)
161 }
162}
163
164struct BodyReader(Body);
165impl Read for BodyReader {
166 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
167 self.0.read(buf)
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 fn make_writer() -> ConnResponseWriter<Vec<u8>> {
176 ConnResponseWriter::new(Vec::new())
177 }
178
179 #[test]
180 fn default_200() {
181 let mut w = make_writer();
182 w.write(b"hello").unwrap();
183 w.finish().unwrap();
184 let out = String::from_utf8(w.inner).unwrap();
185 assert!(out.starts_with("HTTP/1.1 200 OK\r\n"));
186 assert!(out.contains("hello"));
187 }
188
189 #[test]
190 fn custom_status() {
191 let mut w = make_writer();
192 w.write_header(404);
193 w.write(b"not found").unwrap();
194 w.finish().unwrap();
195 let out = String::from_utf8(w.inner).unwrap();
196 assert!(out.starts_with("HTTP/1.1 404 Not Found\r\n"));
197 }
198
199 #[test]
200 fn write_header_idempotent() {
201 let mut w = make_writer();
202 w.write_header(201);
203 w.write(b"body").unwrap(); w.write_header(500); w.finish().unwrap();
206 let out = String::from_utf8(w.inner).unwrap();
207 assert!(out.starts_with("HTTP/1.1 201"));
208 }
209}