-
Notifications
You must be signed in to change notification settings - Fork 52
/
main.rs
535 lines (449 loc) · 16.8 KB
/
main.rs
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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
//! A simple HTTP server, for learning and local development.
#[macro_use]
extern crate derive_more;
use bytes::BytesMut;
use env_logger::{Builder, Env};
use futures::future;
use futures::stream::StreamExt;
use futures::FutureExt;
use handlebars::Handlebars;
use http::header::{HeaderMap, HeaderValue};
use http::status::StatusCode;
use http::Uri;
use hyper::service::{make_service_fn, service_fn};
use hyper::{header, Body, Method, Request, Response, Server};
use log::{debug, error, info, trace, warn};
use percent_encoding::percent_decode_str;
use serde::Serialize;
use std::error::Error as StdError;
use std::io;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use structopt::StructOpt;
use tokio::codec::{BytesCodec, FramedRead};
use tokio::fs::File;
use tokio::runtime::Runtime;
// Developer extensions. These are contained in their own module so that the
// principle HTTP server behavior is not obscured.
mod ext;
fn main() {
// Set up error handling immediately
if let Err(e) = run() {
log_error_chain(&e);
}
}
/// Basic error reporting, including the "cause chain". This is used both by the
/// top-level error reporting and to report internal server errors.
fn log_error_chain(mut e: &dyn StdError) {
error!("error: {}", e);
while let Some(source) = e.source() {
error!("caused by: {}", source);
e = source;
}
}
/// The configuration object, parsed from command line options.
#[derive(Clone, StructOpt)]
#[structopt(about = "A basic HTTP file server")]
pub struct Config {
/// The IP:PORT combination.
#[structopt(
name = "ADDR",
short = "a",
long = "addr",
parse(try_from_str),
default_value = "127.0.0.1:4000"
)]
addr: SocketAddr,
/// The root directory for serving files.
#[structopt(name = "ROOT", parse(from_os_str), default_value = ".")]
root_dir: PathBuf,
/// Enable developer extensions.
#[structopt(short = "x")]
use_extensions: bool,
}
fn run() -> Result<()> {
// Initialize logging, and log the "info" level for this crate only, unless
// the environment contains `RUST_LOG`.
let env = Env::new().default_filter_or("basic_http_server=info");
Builder::from_env(env)
.default_format_module_path(false)
.default_format_timestamp(false)
.init();
// Create the configuration from the command line arguments. It
// includes the IP address and port to listen on and the path to use
// as the HTTP server's root directory.
let config = Config::from_args();
// Display the configuration to be helpful
info!("basic-http-server {}", env!("CARGO_PKG_VERSION"));
info!("addr: http://{}", config.addr);
info!("root dir: {}", config.root_dir.display());
info!("extensions: {}", config.use_extensions);
// Create the MakeService object that creates a new Hyper service for every
// connection. Both these closures need to return a Future of Result, and we
// use two different mechanisms to achieve that.
let make_service = make_service_fn(|_| {
let config = config.clone();
let service = service_fn(move |req| {
let config = config.clone();
// Handle the request, returning a Future of Response,
// and map it to a Future of Result of Response.
serve(config, req).map(Ok::<_, Error>)
});
// Convert the concrete (non-future) service function to a Future of Result.
future::ok::<_, Error>(service)
});
// Create a Hyper Server, binding to an address, and use
// our service builder.
let server = Server::bind(&config.addr).serve(make_service);
// Create a Tokio runtime and block on Hyper forever.
let rt = Runtime::new()?;
rt.block_on(server)?;
Ok(())
}
/// Create an HTTP Response future for each Request.
///
/// Errors are turned into an appropriate HTTP error response, and never
/// propagated upward for hyper to deal with.
async fn serve(config: Config, req: Request<Body>) -> Response<Body> {
// Serve the requested file.
let resp = serve_or_error(config, req).await;
// Transform internal errors to error responses.
let resp = transform_error(resp);
resp
}
/// Handle all types of requests, but don't deal with transforming internal
/// errors to HTTP error responses.
async fn serve_or_error(config: Config, req: Request<Body>) -> Result<Response<Body>> {
// This server only supports the GET method. Return an appropriate
// response otherwise.
if let Some(resp) = handle_unsupported_request(&req) {
return resp;
}
// Serve the requested file.
let resp = serve_file(&req, &config.root_dir).await;
// Give developer extensions an opportunity to post-process the request/response pair.
let resp = ext::serve(config, req, resp).await;
resp
}
/// Serve static files from a root directory.
async fn serve_file(req: &Request<Body>, root_dir: &PathBuf) -> Result<Response<Body>> {
// First, try to do a redirect. If that doesn't happen, then find the path
// to the static file we want to serve - which may be `index.html` for
// directories - and send a response containing that file.
let maybe_redir_resp = try_dir_redirect(req, &root_dir)?;
if let Some(redir_resp) = maybe_redir_resp {
return Ok(redir_resp);
}
let path = local_path_with_maybe_index(req.uri(), &root_dir)?;
Ok(respond_with_file(path).await?)
}
/// Try to do a 302 redirect for directories.
///
/// If we get a URL without trailing "/" that can be mapped to a directory, then
/// return a 302 redirect to the path with the trailing "/".
///
/// Without this we couldn't correctly return the contents of `index.html` for a
/// directory - for the purpose of building absolute URLs from relative URLs,
/// agents appear to only treat paths with trailing "/" as directories, so we
/// have to redirect to the proper directory URL first.
///
/// In other words, if we returned the contents of `index.html` for URL `docs`
/// then all the relative links in that file would be broken, but that is not
/// the case for URL `docs/`.
///
/// This seems to match the behavior of other static web servers.
fn try_dir_redirect(req: &Request<Body>, root_dir: &PathBuf) -> Result<Option<Response<Body>>> {
if req.uri().path().ends_with("/") {
return Ok(None);
}
debug!("path does not end with /");
let path = local_path_for_request(req.uri(), root_dir)?;
if !path.is_dir() {
return Ok(None);
}
let mut new_loc = req.uri().path().to_string();
new_loc.push_str("/");
if let Some(query) = req.uri().query() {
new_loc.push_str("?");
new_loc.push_str(query);
}
info!("redirecting {} to {}", req.uri(), new_loc);
Response::builder()
.status(StatusCode::FOUND)
.header(header::LOCATION, new_loc)
.body(Body::empty())
.map(Some)
.map_err(Error::from)
}
/// Construct a 200 response with the file as the body, streaming it to avoid
/// loading it fully into memory.
///
/// If the I/O here fails then an error future will be returned, and `serve`
/// will convert it into the appropriate HTTP error response.
async fn respond_with_file(path: PathBuf) -> Result<Response<Body>> {
let mime_type = file_path_mime(&path);
let file = File::open(path).await?;
let meta = file.metadata().await?;
let len = meta.len();
// Here's the streaming code. How to do this isn't documented in the
// Tokio/Hyper API docs. Codecs are how Tokio creates Streams; a FramedRead
// turns an AsyncRead plus a Decoder into a Stream; and BytesCodec is a
// Decoder. FramedRead though creates a Stream<Result<BytesMut>> and Hyper's
// Body wants a Stream<Result<Bytes>>, and BytesMut::freeze will give us a
// Bytes.
let codec = BytesCodec::new();
let stream = FramedRead::new(file, codec);
let stream = stream.map(|b| b.map(BytesMut::freeze));
let body = Body::wrap_stream(stream);
let resp = Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_LENGTH, len as u64)
.header(header::CONTENT_TYPE, mime_type.as_ref())
.body(body)?;
Ok(resp)
}
/// Get a MIME type based on the file extension.
///
/// If the extension is unknown then return "application/octet-stream".
fn file_path_mime(file_path: &Path) -> mime::Mime {
mime_guess::from_path(file_path).first_or_octet_stream()
}
/// Find the local path for a request URI, converting directories to the
/// `index.html` file.
fn local_path_with_maybe_index(uri: &Uri, root_dir: &Path) -> Result<PathBuf> {
local_path_for_request(uri, root_dir).map(|mut p: PathBuf| {
if p.is_dir() {
p.push("index.html");
debug!("trying {} for directory URL", p.display());
} else {
trace!("trying path as from URL");
}
p
})
}
/// Map the request's URI to a local path
fn local_path_for_request(uri: &Uri, root_dir: &Path) -> Result<PathBuf> {
debug!("raw URI: {}", uri);
let request_path = uri.path();
debug!("raw URI to path: {}", request_path);
// Trim off the url parameters starting with '?'
let end = request_path.find('?').unwrap_or(request_path.len());
let request_path = &request_path[0..end];
// Convert %-encoding to actual values
let decoded = percent_decode_str(&request_path);
let request_path = if let Ok(p) = decoded.decode_utf8() {
p
} else {
error!("non utf-8 URL: {}", request_path);
return Err(Error::UriNotUtf8);
};
// Append the requested path to the root directory
let mut path = root_dir.to_owned();
if request_path.starts_with('/') {
path.push(&request_path[1..]);
} else {
warn!("found non-absolute path {}", request_path);
return Err(Error::UriNotAbsolute);
}
debug!("URL · path : {} · {}", uri, path.display());
Ok(path)
}
/// Create an error response if the request contains unsupported methods,
/// headers, etc.
fn handle_unsupported_request(req: &Request<Body>) -> Option<Result<Response<Body>>> {
get_unsupported_request_message(req)
.map(|unsup| make_error_response_from_code_and_headers(unsup.code, unsup.headers))
}
/// Description of an unsupported request.
struct Unsupported {
code: StatusCode,
headers: HeaderMap,
}
/// Create messages for unsupported requests.
fn get_unsupported_request_message(req: &Request<Body>) -> Option<Unsupported> {
use std::iter::FromIterator;
// https://tools.ietf.org/html/rfc7231#section-6.5.5
if req.method() != Method::GET {
return Some(Unsupported {
code: StatusCode::METHOD_NOT_ALLOWED,
headers: HeaderMap::from_iter(vec![(header::ALLOW, HeaderValue::from_static("GET"))]),
});
}
None
}
/// Turn any errors into an HTTP error response.
fn transform_error(resp: Result<Response<Body>>) -> Response<Body> {
match resp {
Ok(r) => r,
Err(e) => {
let resp = make_error_response(e);
match resp {
Ok(r) => r,
Err(e) => {
// Last-ditch error reporting if even making the error response failed.
error!("unexpected internal error: {}", e);
Response::new(Body::from(format!("unexpected internal error: {}", e)))
}
}
}
}
}
/// Convert an error to an HTTP error response future, with correct response code.
fn make_error_response(e: Error) -> Result<Response<Body>> {
let resp = match e {
Error::Io(e) => make_io_error_response(e)?,
Error::Ext(ext::Error::Io(e)) => make_io_error_response(e)?,
e => make_internal_server_error_response(e)?,
};
Ok(resp)
}
/// Convert an error into a 500 internal server error, and log it.
fn make_internal_server_error_response(err: Error) -> Result<Response<Body>> {
log_error_chain(&err);
let resp = make_error_response_from_code(StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(resp)
}
/// Handle the one special IO error (file not found) by returning a 404, otherwise
/// return a 500.
fn make_io_error_response(error: io::Error) -> Result<Response<Body>> {
let resp = match error.kind() {
io::ErrorKind::NotFound => {
debug!("{}", error);
make_error_response_from_code(StatusCode::NOT_FOUND)?
}
_ => make_internal_server_error_response(Error::Io(error))?,
};
Ok(resp)
}
/// Make an error response given an HTTP status code.
fn make_error_response_from_code(status: StatusCode) -> Result<Response<Body>> {
make_error_response_from_code_and_headers(status, HeaderMap::new())
}
/// Make an error response given an HTTP status code and response headers.
fn make_error_response_from_code_and_headers(
status: StatusCode,
headers: HeaderMap,
) -> Result<Response<Body>> {
let body = render_error_html(status)?;
let resp = html_str_to_response_with_headers(body, status, headers)?;
Ok(resp)
}
/// Make an HTTP response from a HTML string.
fn html_str_to_response(body: String, status: StatusCode) -> Result<Response<Body>> {
html_str_to_response_with_headers(body, status, HeaderMap::new())
}
/// Make an HTTP response from a HTML string and response headers.
fn html_str_to_response_with_headers(
body: String,
status: StatusCode,
headers: HeaderMap,
) -> Result<Response<Body>> {
let mut builder = Response::builder();
builder.headers_mut().map(|h| h.extend(headers));
builder
.status(status)
.header(header::CONTENT_LENGTH, body.len())
.header(header::CONTENT_TYPE, mime::TEXT_HTML.as_ref())
.body(Body::from(body))
.map_err(Error::from)
}
/// A handlebars HTML template.
static HTML_TEMPLATE: &str = include_str!("template.html");
/// The data for the handlebars HTML template. Handlebars will use serde to get
/// the data out of the struct and mapped onto the template.
#[derive(Serialize)]
struct HtmlCfg {
title: String,
body: String,
}
/// Render an HTML page with handlebars, the template and the configuration data.
fn render_html(cfg: HtmlCfg) -> Result<String> {
let reg = Handlebars::new();
let rendered = reg
.render_template(HTML_TEMPLATE, &cfg)
.map_err(Error::TemplateRender)?;
Ok(rendered)
}
/// Render an HTML page from an HTTP status code
fn render_error_html(status: StatusCode) -> Result<String> {
render_html(HtmlCfg {
title: format!("{}", status),
body: String::new(),
})
}
/// A custom `Result` typedef
pub type Result<T> = std::result::Result<T, Error>;
/// The basic-http-server error type.
///
/// This is divided into two types of errors: "semantic" errors and "blanket"
/// errors. Semantic errors are custom to the local application semantics and
/// are usually preferred, since they add context and meaning to the error
/// chain. They don't require boilerplate `From` implementations, but do require
/// `map_err` to create when they have interior `causes`.
///
/// Blanket errors are just wrappers around other types, like `Io(io::Error)`.
/// These are common errors that occur in many places so are easier to code and
/// maintain, since e.g. every occurrence of an I/O error doesn't need to be
/// given local semantics.
///
/// The criteria of when to use which type of error variant, and their pros and
/// cons, aren't obvious.
///
/// These errors use `derive(Display)` from the `derive-more` crate to reduce
/// boilerplate.
#[derive(Debug, Display)]
pub enum Error {
// blanket "pass-through" error types
#[display(fmt = "Extension error")]
Ext(ext::Error),
#[display(fmt = "HTTP error")]
Http(http::Error),
#[display(fmt = "Hyper error")]
Hyper(hyper::Error),
#[display(fmt = "I/O error")]
Io(io::Error),
// custom "semantic" error types
#[display(fmt = "failed to parse IP address")]
AddrParse(std::net::AddrParseError),
#[display(fmt = "failed to render template")]
TemplateRender(handlebars::TemplateRenderError),
#[display(fmt = "requested URI is not an absolute path")]
UriNotAbsolute,
#[display(fmt = "requested URI is not UTF-8")]
UriNotUtf8,
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
use Error::*;
match self {
Ext(e) => Some(e),
Io(e) => Some(e),
Http(e) => Some(e),
Hyper(e) => Some(e),
AddrParse(e) => Some(e),
TemplateRender(e) => Some(e),
UriNotAbsolute => None,
UriNotUtf8 => None,
}
}
}
impl From<ext::Error> for Error {
fn from(e: ext::Error) -> Error {
Error::Ext(e)
}
}
impl From<http::Error> for Error {
fn from(e: http::Error) -> Error {
Error::Http(e)
}
}
impl From<hyper::Error> for Error {
fn from(e: hyper::Error) -> Error {
Error::Hyper(e)
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Error {
Error::Io(e)
}
}