use std::fs::File;
use std::sync::Arc;
use super::{Headers, StatusCode};
pub(crate) const STREAM_CHUNK: usize = 64 * 1024;
#[derive(Debug, Clone)]
pub struct Body {
inner: BodyInner,
}
#[derive(Debug, Clone)]
enum BodyInner {
Bytes(Vec<u8>),
File {
file: Arc<File>,
offset: u64,
len: u64,
},
}
pub(crate) enum BodyKind {
Bytes(Vec<u8>),
File {
file: Arc<File>,
offset: u64,
len: u64,
},
}
impl Default for Body {
fn default() -> Body {
Body::empty()
}
}
impl Body {
pub fn empty() -> Body {
Body {
inner: BodyInner::Bytes(Vec::new()),
}
}
pub fn as_bytes(&self) -> &[u8] {
match &self.inner {
BodyInner::Bytes(b) => b,
BodyInner::File { .. } => &[],
}
}
pub fn len(&self) -> usize {
self.len_u64() as usize
}
pub fn is_empty(&self) -> bool {
self.len_u64() == 0
}
pub(crate) fn file(file: Arc<File>, offset: u64, len: u64) -> Body {
Body {
inner: BodyInner::File { file, offset, len },
}
}
pub(crate) fn len_u64(&self) -> u64 {
match &self.inner {
BodyInner::Bytes(b) => b.len() as u64,
BodyInner::File { len, .. } => *len,
}
}
#[cfg(feature = "compress")]
pub(crate) fn is_file(&self) -> bool {
matches!(self.inner, BodyInner::File { .. })
}
pub(crate) fn into_kind(self) -> BodyKind {
match self.inner {
BodyInner::Bytes(b) => BodyKind::Bytes(b),
BodyInner::File { file, offset, len } => BodyKind::File { file, offset, len },
}
}
pub(crate) fn into_bytes(self) -> Vec<u8> {
match self.inner {
BodyInner::Bytes(b) => b,
BodyInner::File { file, offset, len } => {
let mut buf = vec![0u8; len as usize];
match read_at_exact(&file, offset, &mut buf) {
Ok(n) => {
buf.truncate(n);
buf
}
Err(_) => Vec::new(),
}
}
}
}
}
impl From<Vec<u8>> for Body {
fn from(bytes: Vec<u8>) -> Body {
Body {
inner: BodyInner::Bytes(bytes),
}
}
}
impl From<&[u8]> for Body {
fn from(bytes: &[u8]) -> Body {
Body {
inner: BodyInner::Bytes(bytes.to_vec()),
}
}
}
impl From<String> for Body {
fn from(s: String) -> Body {
Body {
inner: BodyInner::Bytes(s.into_bytes()),
}
}
}
impl From<&str> for Body {
fn from(s: &str) -> Body {
Body {
inner: BodyInner::Bytes(s.as_bytes().to_vec()),
}
}
}
#[cfg(unix)]
fn read_at(file: &File, offset: u64, buf: &mut [u8]) -> std::io::Result<usize> {
use std::os::unix::fs::FileExt;
file.read_at(buf, offset)
}
#[cfg(windows)]
fn read_at(file: &File, offset: u64, buf: &mut [u8]) -> std::io::Result<usize> {
use std::os::windows::fs::FileExt;
file.seek_read(buf, offset)
}
pub(crate) fn read_at_exact(file: &File, offset: u64, buf: &mut [u8]) -> std::io::Result<usize> {
let mut total = 0;
while total < buf.len() {
match read_at(file, offset + total as u64, &mut buf[total..]) {
Ok(0) => break, Ok(n) => total += n,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
Ok(total)
}
#[cfg(any(feature = "h2", feature = "h3"))]
pub(crate) enum OutBody {
Bytes { data: Vec<u8>, pos: usize },
File {
file: Arc<File>,
offset: u64,
remaining: u64,
},
}
#[cfg(any(feature = "h2", feature = "h3"))]
impl OutBody {
pub(crate) fn from_body(body: Body) -> OutBody {
match body.into_kind() {
BodyKind::Bytes(data) => OutBody::Bytes { data, pos: 0 },
BodyKind::File { file, offset, len } => OutBody::File {
file,
offset,
remaining: len,
},
}
}
pub(crate) fn remaining(&self) -> u64 {
match self {
OutBody::Bytes { data, pos } => (data.len() - *pos) as u64,
OutBody::File { remaining, .. } => *remaining,
}
}
pub(crate) fn take_chunk(&mut self, n: usize) -> Result<Vec<u8>, ()> {
match self {
OutBody::Bytes { data, pos } => {
let start = *pos;
*pos += n;
Ok(data[start..start + n].to_vec())
}
OutBody::File {
file,
offset,
remaining,
} => {
let mut buf = vec![0u8; n];
match read_at_exact(file, *offset, &mut buf) {
Ok(got) if got == n => {
*offset += n as u64;
*remaining -= n as u64;
Ok(buf)
}
_ => Err(()),
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct Response {
status: StatusCode,
headers: Headers,
body: Body,
}
impl Response {
pub fn new(status: StatusCode) -> Response {
Response {
status,
headers: Headers::new(),
body: Body::empty(),
}
}
pub fn text(body: impl Into<String>) -> Response {
Response::new(StatusCode::OK)
.header("Content-Type", "text/plain; charset=utf-8")
.body(body.into())
}
pub fn html(body: impl Into<String>) -> Response {
Response::new(StatusCode::OK)
.header("Content-Type", "text/html; charset=utf-8")
.body(body.into())
}
pub fn status(status: StatusCode) -> Response {
let page = format!("{} {}\n", status.code(), status.reason());
if status.is_bodyless() {
Response::new(status)
} else {
Response::new(status)
.header("Content-Type", "text/plain; charset=utf-8")
.body(page)
}
}
pub fn redirect(status: StatusCode, location: impl Into<String>) -> Response {
Response::new(status).header("Location", location.into())
}
pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Response {
self.headers.append(name, value);
self
}
pub fn body(mut self, body: impl Into<Body>) -> Response {
self.body = body.into();
self
}
pub fn with_status(mut self, status: StatusCode) -> Response {
self.status = status;
self
}
pub fn status_code(&self) -> StatusCode {
self.status
}
pub fn headers_mut(&mut self) -> &mut Headers {
&mut self.headers
}
pub fn headers(&self) -> &Headers {
&self.headers
}
pub fn body_ref(&self) -> &Body {
&self.body
}
pub(crate) fn into_parts(self) -> (StatusCode, Headers, Body) {
(self.status, self.headers, self.body)
}
#[cfg_attr(not(any(feature = "compress", feature = "http")), allow(dead_code))]
pub(crate) fn from_parts(status: StatusCode, headers: Headers, body: Body) -> Response {
Response {
status,
headers,
body,
}
}
}