-
Notifications
You must be signed in to change notification settings - Fork 56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Split staticfile into two APIs #87
Comments
Sounds reasonable to me. |
I'd love to also have a layer 0. which handles GET and HEAD serving (including conditional GETs and range requests) on top of an abstract "entity" (HTTP's term) without involving the filesystem. Go provides something like this; see http.ServeContent. I needed 0. for my security camera NVR project, which dynamically creates /// A static HTTP entity for GET and HEAD serving.
pub trait Entity<Error> where Error: From<io::Error> {
/// Returns the length of the entity in bytes.
fn len(&self) -> u64;
/// Writes bytes within this entity indicated by `range` to `out`.
fn write_to(&self, range: Range<u64>, out: &mut io::Write) -> Result<(), Error>;
fn content_type(&self) -> mime::Mime;
fn etag(&self) -> Option<&header::EntityTag>;
fn last_modified(&self) -> &header::HttpDate;
}
/// Serves GET and HEAD requests for a given byte-ranged entity.
/// Handles conditional & subrange requests.
///
/// TODO: does it make sense for the caller to add Expires, Cache-Control, and Vary headers first?
/// or do they need to be added conditionally based on the response type we serve?
pub fn serve<Error>(entity: &Entity<Error>, req: &Request, mut res: Response<Fresh>)
-> Result<(), Error> where Error: From<io::Error> { ... } My code directly uses hyper today, not iron. But if iron provided 0., that'd be a compelling reason for projects such as mine to use it. (There are probably other things I'll need that iron already provides, but my code's just a prototype now, I'm just learning the language, and I started learning by doing things with the lower layer.) Also, my code is using the deprecated hyper 0.9.x synchronous interface rather than the not-ready-yet hyper 0.10.x async master or tokio interfaces. I'm looking forward to changing it once something else is stabilized; giving it chunks of preassembled |
@scottlamb You can pass arbitrary |
Oh. That's totally wrong for my use case, actually, it introduces another copy here: impl <R: io::Read + Send> WriteBody for BodyReader<R> {
fn write_body(&mut self, res: &mut ResponseBody) -> io::Result<()> {
io::copy(&mut self.0, res).map(|_| ())
}
} I'm hoping a future version of hyper will allow me to just hand over chunks of And I'm hoping iron will provide a layer above hyper's (current or future) interface that handles conditional GET, range serving, and whatnot without forcing me to write stuff out to the filesystem first (which I absolutely cannot afford to do; I'm generating huge files from small pieces on demand). |
This is not copying to a new section in RAM or any storage medium on the server, it is writing to the output stream. |
It takes my #[stable(feature = "rust1", since = "1.0.0")]
pub fn copy<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W) -> io::Result<u64>
where R: Read, W: Write
{
let mut buf = [0; super::DEFAULT_BUF_SIZE];
let mut written = 0;
loop {
let len = match reader.read(&mut buf) {
Ok(0) => return Ok(written),
Ok(len) => len,
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
};
writer.write_all(&buf[..len])?;
written += len as u64;
}
} |
If performance is that much more important than convenience, why not override |
Why should it be necessary to choose between the two? The |
I'm just saying that Iron already provides the necessary low-level access to Hypers APIs for your project. One variant (using a T: Read as Modifier) is a shortcut for the other (manually setting the response body), and IMO covers a majority of the usecases with very little boilerplate and very little complexity in Iron as well. I'm not opposed to your abstraction at all, at the same time I would strongly prefer shortcuts like mentioned in the OP for the majority of usecases. Either way I think those things shouldn't be part of Iron core for the simple reason that they don't need to be. Particularly etag and last-modified semantics pull in dependencies that minimalist API servers wouldn't want to pay the cost for |
Ahh. Yes, I think you're right that I could implement When you say "shouldn't be a part of iron core", do you mean it shouldn't be a part of crate "iron"? I don't feel strongly that it should be. I'd be happy with in "iron", in "staticfile", or in a new crate between. I do think that staticfile should be built on top of it—it doesn't make sense to provide static file serving without conditional GET and range handling, and it doesn't make sense to have two implementations of that. But if you disagree, I'll just go do my own thing. I don't agree with what you're saying about etag and last-modified pulling in dependencies. The |
If you want to build a high-level abstraction around Last-Modified you're probably going to depend on the time crate. |
That's true. Currently my
But why is this a problem? hyper depends on time. So what problem does it create for iron to also depend on time? |
Ah, I was assuming there's an extra cost because staticfile has everything related to that functionality within an optional feature. It appears https://github.com/alexcrichton/filetime would come as a new dependency. I don't think that's necessary for the abstraction, but probably for the impls that should IMO be included. |
Anyway at this point I'd like to see a PoC pull request or new crate that does what you want to do. We can then move that into staticfile and expose the new API as well. |
Sounds good. I will start polishing my code and adapting it to iron. (At my usual work-a-little-bit-every-night-after-my-kid-goes-to-bed pace...) |
Quick note: I still haven't adapted my code to the Iron interface (I'm slow and trying to do a lot of things with my project at once), but I have at least extracted it into a separate crate and published it to github as scottlamb/http-entity, so you can look at the current code if you're curious. |
Currently staticfile consists of two main features:
Currently staticfile exposes only 2., I'd like it to expose 1. as completely independent feature, for cases where I want to write the logic that maps from request URL to filepath myself.
Flask has a
send_file
function, which takes an absolute filepath (and implicitly a request) and gives back a response. Here's my API proposal for Iron:Perhaps returning a
IronResult<T>
where T: Modifier` is more consistent with the rest of core-Iron's API, but I seriously doubt the usefulness of that.The text was updated successfully, but these errors were encountered: