use super::loader::Loader;
use std::cmp;
use std::convert::Infallible;
use std::pin::Pin;
use std::task::{Context, Poll};
pub struct StaticBody {
pending_content: &'static [u8],
}
impl StaticBody {
const CHUNK_SIZE: usize = 8 * 1024;
pub fn new(content: &'static [u8]) -> Self {
Self {
pending_content: content,
}
}
}
impl Default for StaticBody {
fn default() -> Self {
Self {
pending_content: &[],
}
}
}
impl hyper::body::HttpBody for StaticBody {
type Data = &'static [u8];
type Error = Infallible;
fn poll_data(
self: Pin<&mut Self>,
_cx: &mut Context,
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
let self_ = self.get_mut();
if self_.is_end_stream() {
return Poll::Ready(None);
}
let pending_content_end = cmp::min(Self::CHUNK_SIZE, self_.pending_content.len());
let chunk = &self_.pending_content[..pending_content_end];
self_.pending_content = &self_.pending_content[pending_content_end..];
Poll::Ready(Some(Ok(chunk)))
}
fn poll_trailers(
self: Pin<&mut Self>,
_cx: &mut Context,
) -> Poll<Result<Option<hyper::HeaderMap>, Self::Error>> {
Poll::Ready(Ok(None))
}
fn is_end_stream(&self) -> bool {
self.pending_content.len() <= 0
}
fn size_hint(&self) -> http_body::SizeHint {
http_body::SizeHint::with_exact(self.pending_content.len() as u64)
}
}
pub enum ResponderError {
HttpMethodNotSupported,
LoaderPathNotFound,
UnparsableAcceptEncoding,
}
impl ResponderError {
pub fn as_http_status_code(&self) -> http::StatusCode {
match self {
ResponderError::HttpMethodNotSupported => http::StatusCode::METHOD_NOT_ALLOWED,
ResponderError::LoaderPathNotFound => http::StatusCode::NOT_FOUND,
ResponderError::UnparsableAcceptEncoding => http::StatusCode::BAD_REQUEST,
}
}
pub fn as_default_response(&self) -> hyper::Response<StaticBody> {
return hyper::Response::builder()
.status(self.as_http_status_code())
.body(StaticBody::default())
.unwrap();
}
}
pub struct Responder<'l> {
loader: &'l Loader,
}
impl<'l> Responder<'l> {
pub fn new(loader: &'l Loader) -> Self {
Self { loader }
}
pub fn request_respond_or_error(
&self,
request: &hyper::Request<hyper::Body>,
) -> Result<hyper::Response<StaticBody>, ResponderError> {
return self.parts_respond_or_error(request.method(), request.uri(), request.headers());
}
pub fn parts_respond_or_error(
&self,
method: &http::Method,
uri: &http::Uri,
headers: &http::HeaderMap,
) -> Result<hyper::Response<StaticBody>, ResponderError> {
match *method {
http::Method::GET => (),
_ => {
return Err(ResponderError::HttpMethodNotSupported);
}
};
let file_descriptor = match self.loader.get(uri.path()) {
Some(file_descriptor) => file_descriptor,
None => {
return Err(ResponderError::LoaderPathNotFound);
}
};
if let Some(ref etag_request) = headers.get(http::header::IF_NONE_MATCH) {
if etag_request.as_bytes() == file_descriptor.etag().as_bytes() {
return Ok(hyper::Response::builder()
.status(http::StatusCode::NOT_MODIFIED)
.body(StaticBody::default())
.unwrap());
}
};
let mut accepted_encoding_gzip = false;
if let Some(accept_encoding) = headers.get(http::header::ACCEPT_ENCODING) {
let accept_encoding = match accept_encoding.to_str() {
Ok(accept_encoding) => accept_encoding,
Err(_) => {
return Err(ResponderError::UnparsableAcceptEncoding);
}
};
accept_encoding
.split(", ")
.for_each(|accept_encoding| match accept_encoding {
"gzip" => {
accepted_encoding_gzip = true;
}
_ => {}
});
}
let chunk_encoding = if accepted_encoding_gzip && file_descriptor.content_gzip().is_some() {
(file_descriptor.content_gzip().unwrap(), "gzip")
} else {
(file_descriptor.content(), "identity")
};
let response = hyper::Response::builder()
.header(http::header::CONTENT_TYPE, file_descriptor.content_type())
.header(http::header::CONTENT_LENGTH, chunk_encoding.0.len())
.header(http::header::CONTENT_ENCODING, chunk_encoding.1)
.header(http::header::ETAG, file_descriptor.etag())
.body(StaticBody::new(chunk_encoding.0))
.unwrap();
Ok(response)
}
pub fn request_respond(
&self,
request: &hyper::Request<hyper::Body>,
) -> hyper::Response<StaticBody> {
match self.request_respond_or_error(request) {
Ok(response) => response,
Err(error) => error.as_default_response(),
}
}
pub fn parts_respond(
&self,
method: &http::Method,
uri: &http::Uri,
headers: &http::HeaderMap,
) -> hyper::Response<StaticBody> {
match self.parts_respond_or_error(method, uri, headers) {
Ok(response) => response,
Err(error) => error.as_default_response(),
}
}
}