mirror of https://github.com/http-rs/http-types
Add mime sniffing to Body::from_file
This commit is contained in:
parent
9e1808a46b
commit
568251d18f
|
@ -42,3 +42,4 @@ serde_urlencoded = "0.6.1"
|
|||
|
||||
[dev-dependencies]
|
||||
http = "0.2.0"
|
||||
async-std = { version = "1.4.0", features = ["unstable", "attributes"] }
|
||||
|
|
47
src/body.rs
47
src/body.rs
|
@ -358,12 +358,26 @@ impl Body {
|
|||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
#[cfg(feature = "async_std")]
|
||||
pub async fn from_file<P>(file: P) -> io::Result<Self>
|
||||
pub async fn from_file<P>(path: P) -> io::Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let file = fs::read(file.as_ref()).await?;
|
||||
Ok(file.into())
|
||||
let path = path.as_ref();
|
||||
let mut file = fs::File::open(path).await?;
|
||||
let len = file.metadata().await?.len();
|
||||
|
||||
// Look at magic bytes first, look at extension second, fall back to
|
||||
// octet stream.
|
||||
let mime = peek_mime(&mut file)
|
||||
.await?
|
||||
.or_else(|| guess_ext(path))
|
||||
.unwrap_or(mime::BYTE_STREAM);
|
||||
|
||||
Ok(Self {
|
||||
mime,
|
||||
length: Some(len as usize),
|
||||
reader: Box::new(io::BufReader::new(file)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the length of the body in bytes.
|
||||
|
@ -448,3 +462,30 @@ impl BufRead for Body {
|
|||
Pin::new(&mut self.reader).consume(amt)
|
||||
}
|
||||
}
|
||||
|
||||
/// Look at first few bytes of a file to determine the mime type.
|
||||
/// This is used for various binary formats such as images and videos.
|
||||
#[cfg(feature = "async_std")]
|
||||
async fn peek_mime(file: &mut async_std::fs::File) -> io::Result<Option<Mime>> {
|
||||
let mut buf = [0_u8; 8];
|
||||
file.read_exact(&mut buf).await?;
|
||||
let mime = Mime::sniff(&buf).ok();
|
||||
file.seek(io::SeekFrom::Start(0)).await?;
|
||||
Ok(mime)
|
||||
}
|
||||
|
||||
/// Look at the extension of a file to determine the mime type.
|
||||
/// This is useful for plain-text formats such as HTML and CSS.
|
||||
#[cfg(feature = "async_std")]
|
||||
fn guess_ext(path: &Path) -> Option<Mime> {
|
||||
let ext = path.extension().map(|p| p.to_str()).flatten();
|
||||
match ext {
|
||||
Some("html") => Some(mime::HTML),
|
||||
Some("js") | Some("mjs") => Some(mime::JAVASCRIPT),
|
||||
Some("json") => Some(mime::JSON),
|
||||
Some("css") => Some(mime::CSS),
|
||||
Some("svg") => Some(mime::SVG),
|
||||
Some("wasm") => Some(mime::WASM),
|
||||
None | Some(_) => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,77 @@ pub const HTML: Mime = Mime {
|
|||
static_subtype: Some("html"),
|
||||
};
|
||||
|
||||
/// Content-Type for SVG.
|
||||
///
|
||||
/// # Mime Type
|
||||
///
|
||||
/// ```txt
|
||||
/// image/svg+xml
|
||||
/// ```
|
||||
pub const SVG: Mime = Mime {
|
||||
static_essence: Some("image/svg+xml"),
|
||||
essence: String::new(),
|
||||
basetype: String::new(),
|
||||
subtype: String::new(),
|
||||
params: None,
|
||||
static_basetype: Some("image"),
|
||||
static_subtype: Some("svg+xml"),
|
||||
};
|
||||
|
||||
/// Content-Type for ICO icons.
|
||||
///
|
||||
/// # Mime Type
|
||||
///
|
||||
/// ```txt
|
||||
/// image/x-icon
|
||||
/// ```
|
||||
// There are multiple `.ico` mime types known, but `image/x-icon`
|
||||
// is what most browser use. See:
|
||||
// https://en.wikipedia.org/wiki/ICO_%28file_format%29#MIME_type
|
||||
pub const ICO: Mime = Mime {
|
||||
static_essence: Some("image/x-icon"),
|
||||
essence: String::new(),
|
||||
basetype: String::new(),
|
||||
subtype: String::new(),
|
||||
params: None,
|
||||
static_basetype: Some("image"),
|
||||
static_subtype: Some("x-icon"),
|
||||
};
|
||||
|
||||
/// Content-Type for PNG images.
|
||||
///
|
||||
/// # Mime Type
|
||||
///
|
||||
/// ```txt
|
||||
/// image/png
|
||||
/// ```
|
||||
pub const PNG: Mime = Mime {
|
||||
static_essence: Some("image/png"),
|
||||
essence: String::new(),
|
||||
basetype: String::new(),
|
||||
subtype: String::new(),
|
||||
params: None,
|
||||
static_basetype: Some("image"),
|
||||
static_subtype: Some("png"),
|
||||
};
|
||||
|
||||
/// Content-Type for JPEG images.
|
||||
///
|
||||
/// # Mime Type
|
||||
///
|
||||
/// ```txt
|
||||
/// image/jpeg
|
||||
/// ```
|
||||
pub const JPEG: Mime = Mime {
|
||||
static_essence: Some("image/jpeg"),
|
||||
essence: String::new(),
|
||||
basetype: String::new(),
|
||||
subtype: String::new(),
|
||||
params: None,
|
||||
static_basetype: Some("image"),
|
||||
static_subtype: Some("jpeg"),
|
||||
};
|
||||
|
||||
/// Content-Type for Server Sent Events
|
||||
///
|
||||
/// # Mime Type
|
||||
|
@ -170,3 +241,20 @@ pub const MULTIPART_FORM: Mime = Mime {
|
|||
static_subtype: Some("form-data"),
|
||||
params: None,
|
||||
};
|
||||
|
||||
/// Content-Type for webassembly.
|
||||
///
|
||||
/// # Mime Type
|
||||
///
|
||||
/// ```txt
|
||||
/// application/wasm
|
||||
/// ```
|
||||
pub const WASM: Mime = Mime {
|
||||
static_essence: Some("application/wasm"),
|
||||
essence: String::new(),
|
||||
basetype: String::new(),
|
||||
subtype: String::new(),
|
||||
static_basetype: Some("application"),
|
||||
static_subtype: Some("wasm"),
|
||||
params: None,
|
||||
};
|
||||
|
|
|
@ -98,6 +98,20 @@ impl Mime {
|
|||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Mime> for Mime {
|
||||
fn eq(&self, other: &Mime) -> bool {
|
||||
let left = match self.static_essence {
|
||||
Some(essence) => essence,
|
||||
None => &self.essence,
|
||||
};
|
||||
let right = match other.static_essence {
|
||||
Some(essence) => essence,
|
||||
None => &other.essence,
|
||||
};
|
||||
left == right
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Mime {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
parse::format(self, f)
|
||||
|
@ -188,7 +202,7 @@ impl PartialEq<str> for ParamValue {
|
|||
|
||||
/// This is a hack that allows us to mark a trait as utf8 during compilation. We
|
||||
/// can remove this once we can construct HashMap during compilation.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum ParamKind {
|
||||
Utf8,
|
||||
Vec(Vec<(ParamName, ParamValue)>),
|
||||
|
|
|
@ -367,6 +367,11 @@ impl Response {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the current content type
|
||||
pub fn content_type(&self) -> Option<Mime> {
|
||||
self.header(CONTENT_TYPE)?.last().as_str().parse().ok()
|
||||
}
|
||||
|
||||
/// Get the length of the body stream, if it has been set.
|
||||
///
|
||||
/// This value is set when passing a fixed-size object into as the body. E.g. a string, or a
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<html>
|
||||
|
||||
<head></head>
|
||||
|
||||
<body>This is a fixture!</body>
|
||||
|
||||
</html>
|
Binary file not shown.
After Width: | Height: | Size: 1.0 MiB |
|
@ -0,0 +1,20 @@
|
|||
use http_types::{Response, Body, mime};
|
||||
use async_std::io;
|
||||
|
||||
#[async_std::test]
|
||||
async fn guess_plain_text_mime() -> io::Result<()> {
|
||||
let body = Body::from_file("tests/fixtures/index.html").await?;
|
||||
let mut res = Response::new(200);
|
||||
res.set_body(body);
|
||||
assert_eq!(res.content_type(), Some(mime::HTML));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn guess_binary_mime() -> io::Result<()> {
|
||||
let body = Body::from_file("tests/fixtures/nori.png").await?;
|
||||
let mut res = Response::new(200);
|
||||
res.set_body(body);
|
||||
assert_eq!(res.content_type(), Some(mime::PNG));
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue