1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
use std::{
	io,io::Read,io::Write,
	net::{SocketAddr,TcpStream},
	str
};
use super::os_windows;

/// This struct represents a client which has connected to the µHTTP server.microhttp
///
/// If an instance of this struct is dropped, the connection is closed.
#[derive(Debug)]
pub struct Client {
	stream: TcpStream,
	addr: SocketAddr,
	request: Option<String>
}

// Read all data from an incoming stream
fn read_all(stream: &mut TcpStream) -> Result<Vec<u8>,io::ErrorKind> {
	let mut result = Vec::new();

	loop {
		const BUF_SIZE: usize = 4096;
		let mut buf: [u8; BUF_SIZE] = [0u8; BUF_SIZE];
		match stream.read(&mut buf) {
			Ok(val) => if val > 0 {
				result.append(&mut Vec::from(&buf[0..val]));
				if val < BUF_SIZE {
					return Ok(result);
				}
			} else {
				// Stop reading if we don't have anything left to
				// read at the moment.
				return Ok(result);
			},
			Err(e) => match e.kind() {
				::std::io::ErrorKind::WouldBlock => return Ok(result),
				::std::io::ErrorKind::TimedOut => match os_windows() {
					true => return Ok(result),
					false => return Err(::std::io::ErrorKind::TimedOut)
				},
				kind => return Err(kind)
			}
		};
	}
}

fn extract_request_url(buf: &[u8]) -> Option<String> {
	let s = str::from_utf8(buf).unwrap();

	for line in s.split("\r\n") {
		if line.starts_with("GET ") {
			let components = line.split(" ").collect::<Vec<&str>>();
			if components.len() < 2 {
				warn!("Invalid GET line: {}", line);
				continue;
			}
			return Some(String::from(*components.get(1).unwrap()));
		}
	}

	None
}

impl Client {
	pub(crate) fn new(mut stream : TcpStream, addr : SocketAddr) -> Result<Client,::std::io::Error> {
		// Read all data now, since we only expect simple requests like "HTTP 1.0 GET /"
		let data = read_all(&mut stream)?;

		// Extract the request
		let request = extract_request_url(&data);

		Ok(Client {
			stream: stream,
			addr: addr,
			request: match request {
				Some(s) => s.into(),
				None => None
			}
		})
	}

	/// Return the address of the requesting client, for example "1.2.3.4:9435".
	pub fn addr(&self) -> SocketAddr {
		self.addr
	}

	/// Return the request the client made or None if the client
	/// didn't make any or an invalid one.
	///
	/// **Note**: At the moment, only HTTP GET is supported.
	/// Any other requests will not be collected.
	pub fn request(&self) -> &Option<String> {
		&self.request
	}

	/// Send a HTTP 200 OK response to the client + the provided data.
	/// The data may be an empty array, for example the following
	/// implementation echos all requests except "/hello":
	///
	/// Consider using ``respond_ok_chunked`` for sending file-backed data.
	///
	/// ```
	/// use micro_http_server::MicroHTTP;
	/// use std::io::*;
	/// let server = MicroHTTP::new("127.0.0.1:4000").expect("Could not create server.");
	/// # let mut connection = ::std::net::TcpStream::connect("127.0.0.1:4000").unwrap();
	/// # connection.write("GET /\r\n\r\n".as_bytes());
	/// let mut client = server.next_client().unwrap().unwrap();
	/// let request_str: String = client.request().as_ref().unwrap().clone();
	///
	/// match request_str.as_ref() {
	/// 	"/hello" => client.respond_ok(&[]),
	///     _ => client.respond_ok(request_str.as_bytes())  // Echo request
	/// };
	/// ```
	pub fn respond_ok(&mut self, data: &[u8]) -> io::Result<usize> {
		self.respond_ok_chunked(data, data.len())
	}

	// The test in this doc comment is no_run because it refers to an arbitrary
	// file that may not exist on the current system.

	/// Send a HTTP 200 OK response to the client + the provided data.
	/// The data may be any type implementing [Read](std::io::Read) and
	/// will be read in chunks. This is useful for serving file-backed
	/// data that should not be loaded into memory all at once.
	///
	/// ```no_run
	/// use micro_http_server::MicroHTTP;
	/// use std::io::*;
	/// use std::fs::*;
	/// let server = MicroHTTP::new("127.0.0.1:4000").expect("Could not create server.");
	/// # let mut connection = ::std::net::TcpStream::connect("127.0.0.1:4000").unwrap();
	/// # connection.write("GET /\r\n\r\n".as_bytes());
	/// let mut client = server.next_client().unwrap().unwrap();
	/// client.request();
	///
	/// let mut file_handle = OpenOptions::new()
	///		.read(true)
	///		.write(false)
	///		.open("/some/local/file")
	///		.unwrap();
	///	let file_len = file_handle.metadata().unwrap().len() as usize;
	///
	/// client.respond_ok_chunked(file_handle, file_len);
	///
	/// ```
	pub fn respond_ok_chunked(&mut self, data: impl Read, content_size: usize) -> io::Result<usize> {
		self.respond_chunked("200 OK", data, content_size, &vec!())
	}

	/// Send response data to the client.
	///
	/// This is similar to ``respond_ok``, but you may control the details yourself.
	///
	/// Consider using ``respond_chunked`` for sending file-backed data.
	///
	/// # Parameters
	/// * ``status_code``: Select the status code of the response, e.g. ``200 OK``.
	/// * ``data``: Data to transmit. May be empty.
	/// * ``headers``: Additional headers to add to the response. May be empty.
	///
	/// Calling ``respond("200 OK", data, &vec!())`` is the same as calling ``respond_ok(data)``.
	pub fn respond(
		&mut self,
		status_code: &str,
		data: &[u8],
		headers: &Vec<String>) -> io::Result<usize>
	{
		self.respond_chunked(status_code, data, data.len(), headers)
	}

	/// Send repsonse data to the client.
	/// 
	/// This is similar to ``respond_ok_chunked``, but you may control the details
	/// yourself.
	///
	/// # Parameters
	/// * ``status_code``: Select the status code of the response, e.ge ``200 OK``.
	/// * ``data``: Data to transmit. May be empty
	/// * ``content_size``: Size of the data to transmit in bytes
	/// * ``headers``: Additional headers to add to the response. May be empty.
	///
	/// Calling ``respond_chunked("200 OK", data, content_size, &vec!())`` is the same as calling
	/// ``repsond_ok_chunked(data, content_size)``.
	pub fn respond_chunked(
		&mut self,
		status_code: &str,
		mut data: impl Read,
		content_size: usize,
		headers: &Vec<String>) -> io::Result<usize> 
	{
		// Write status line
		let mut bytes_written =
			self.stream.write(format!("HTTP/1.0 {}\r\nContent-Length: {}\r\n", status_code, content_size).as_bytes())?;

		for h in headers {
			bytes_written += self.stream.write(format!("{}\r\n", h).as_ref())?;
		}
		bytes_written += self.stream.write("\r\n".as_bytes())?;

		let mut buffer = [0; Self::CHUNK_SIZE];
		loop {
			let bytes_read = data.read(&mut buffer)?;
			if bytes_read == 0 { break; }
			bytes_written += self.stream.write(&buffer[..bytes_read])?;
		}

		Ok(bytes_written)
	}

	const CHUNK_SIZE: usize = 4096;
}