1use std::io::{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> {
148 let mut out = Vec::new();
149 if let Some(ref mut body) = self.body {
150 body.read_to_end(&mut out).map_err(|_| HttpError::BodyRead)?;
151 }
152 if let Some(body) = self.body.take() {
153 self.trailer = body.into_trailers();
154 }
155 Ok(out)
156 }
157
158 pub fn body_string(&mut self) -> Result<String, HttpError> {
160 let bytes = self.body_bytes()?;
161 String::from_utf8(bytes)
162 .map_err(|_| HttpError::BodyRead)
163 }
164}
165
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 fn make_writer() -> ConnResponseWriter<Vec<u8>> {
172 ConnResponseWriter::new(Vec::new())
173 }
174
175 #[test]
176 fn default_200() {
177 let mut w = make_writer();
178 w.write(b"hello").unwrap();
179 w.finish().unwrap();
180 let out = String::from_utf8(w.inner).unwrap();
181 assert!(out.starts_with("HTTP/1.1 200 OK\r\n"));
182 assert!(out.contains("hello"));
183 }
184
185 #[test]
186 fn custom_status() {
187 let mut w = make_writer();
188 w.write_header(404);
189 w.write(b"not found").unwrap();
190 w.finish().unwrap();
191 let out = String::from_utf8(w.inner).unwrap();
192 assert!(out.starts_with("HTTP/1.1 404 Not Found\r\n"));
193 }
194
195 #[test]
196 fn write_header_idempotent() {
197 let mut w = make_writer();
198 w.write_header(201);
199 w.write(b"body").unwrap(); w.write_header(500); w.finish().unwrap();
202 let out = String::from_utf8(w.inner).unwrap();
203 assert!(out.starts_with("HTTP/1.1 201"));
204 }
205}