Skip to content
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

Alllow customizing HTTP headers for NamedFile, StaticFiles #95

Closed
8 tasks
eliovir opened this issue Jan 2, 2017 · 6 comments
Closed
8 tasks

Alllow customizing HTTP headers for NamedFile, StaticFiles #95

eliovir opened this issue Jan 2, 2017 · 6 comments
Labels
accepted An accepted request or suggestion request Request for new functionality
Milestone

Comments

@eliovir
Copy link

eliovir commented Jan 2, 2017

This has been converted into a tracking issue for "Alllow customizing HTTP headers for NamedFile, StaticFiles". Original text after the fold.

This tracking issue is a merger of the following:

In short, NamedFile, and by implication StaticFiles, should support options to compute and set more response headers. In summary, these are:


Hi,

happy new year with successfull Rust projects!

I'm building a little personal project with Rust and Rocket. I have to serve static pages (js, images) and using the Firefox Inspector (to see how fast is this little app) I saw that static files served with NamedFile are not cached by the browser... as the only HTTP headers are Content-Type, Date, Server and Transfert-Encoding.

I thing that at least the header Last-Modified should be added (maybe also Expires and Cache-Control).

I'm still new to Rust and do not know how to derive/override the standard NamedFile to add the header using Response.set_header(). Looking at NamedFile source, it seems that adding new header could be done inside the original code.

Thanks for your project.

@SergioBenitez SergioBenitez added the request Request for new functionality label Jan 3, 2017
@majkcramer
Copy link

@eliovir: In the meantime i found a 'dirty' and maybe expensive workarround to fulfill our need.

#[get("/static/<file..>")]
fn files<'a>(file: PathBuf) -> Response<'a>
{
    let result_read: io::Result<NamedFile> = NamedFile::open(Path::new("static/").join(file));
    let result_namefile1 = result_read.ok();
    let mut response_build = Response::build();
    match result_namefile1
    {
        None => {  }
        Some(mut nfile) => { 
            let nfile_result: Result<Response,Status> = nfile.respond();
            match nfile_result {
                Ok(nfile_result_response) => {
                    response_build = Response::build_from(nfile_result_response);
                    response_build.raw_header("Cache-control","max-age=86400"); //  24h (24*60*60)
                },
                Err(nfile_result_status) => {
                    response_build.raw_status(nfile_result_status.code, nfile_result_status.reason);
                },
            }
        }
    }
    response_build.finalize()
}

@csirkeee
Copy link

csirkeee commented Jan 2, 2018

The code from @majkcramer is now obsolete, I've found this way to do it with current Rocket:

struct CachedFile(NamedFile);

impl<'r> Responder<'r> for CachedFile {
    fn respond_to(self, req: &Request) -> response::Result<'r> {
        Response::build_from(self.0.respond_to(req)?)
            .raw_header("Cache-control", "max-age=86400") //  24h (24*60*60)
            .ok()
    }
}

#[get("/resources/<file..>")]
fn files(file: PathBuf) -> Option<CachedFile> {
    NamedFile::open(Path::new("resources/").join(file)).ok().map(|nf| CachedFile(nf))
}

@Directory
Copy link

any progress on this, it has been nearly 5 years

@SergioBenitez
Copy link
Member

For anybody wanting to take a stab at this, I encourage doing so, but please note that several PR attempts haven't made it through for a variety of reasons. For example, from #1802:

I don't think the API or implementation here are ones that I'm in favor of. Specifically:

  • This API does not leave room for growth into headers which are not simply flags, that is, which require a value like Cache-Control.
  • This API reuses Options from StaticFiles for NamedFile when only one bitfield actually applies.
  • This implementation introduces a dependency on header for two types.
  • The constructor approach (with_last_modified_date()) results in lengthy invocations.
  • The Responder impl indexes into a vector without checking it contains a value and then unwrap()s. This can trivially lead to panics.
  • There are a many vec![], to_string() allocations. This functionality should require none.

Before revisting an implementation for this and #95 in general, I would like to agree on an API design and implementation strategy that has none of these drawbacks. Until then, I am closing this pull request.

From #1802 (comment):

Unfortunately, I cannot accept the PR as is because it reimplements functionality that already exists in Rocket. I do not understand why a new responder type StaticFile is being added when Rocket has (and has had) NamedFile. I also don't understand how these changes apply to anything existing, including FileServer and NamedFile. It feels to me that this is a dump of an external library into Rocket's core, which is generally not something we'd like to do.

Since this can live outside of Rocket and doesn't integrate into any of Rocket's existing infrastructure (and duplicates much of it), I'm closing this out. If you'd like to pursue this further, please submit a PR that begins with a design idea and perhaps a minimal implementation of it. For example, an implementation that includes two headers, one which has a value and one which doesn't, and an example enabling those headers for FileServer and NamedFile.

A successful PR would:

  • Suggest an API that is as uniform as possible across NamedFile and FileServer.
  • Allow setting and unsetting the headers outlined in the issue.
  • Allows setting headers with values and without values.
  • Integrates well and ideally follows existing motifs in Rocket.

I imagine something like the following:

let headers = ETag | LastModified | Cache::MaxAge(3.days()) | Cache::MustRevalidate;
NamedFile::open("foo").await.headers(headers.clone());
FileServer::from("/foo").headers(headers);

I'm not sure how to get this particular API (the | chaining in headers), but notably, it's 1) possible to create a set of headers the you can apply to both NamedFile and FileServer without chaining a bunch of methods, and 2) the API to apply them is identical. I'd be highly in favor of an API of this nature.

@DavidGoldman
Copy link

Here's an alternative workaround solution which still makes use of the validations in FileServer by creating a thin layer around it:

#[derive(Debug, Clone)]
struct CachedFileServer {
    root: PathBuf,
    server: FileServer,
}

impl CachedFileServer {
    /// The default rank use by `FileServer` routes.
    const DEFAULT_RANK: isize = 10;

    #[track_caller]
    pub fn from<P: AsRef<Path>>(path: P) -> Self {
        CachedFileServer { root: path.as_ref().into(), server: FileServer::from(path) }
    }
}

impl From<CachedFileServer> for Vec<Route> {
    fn from(server: CachedFileServer) -> Self {
        let source = figment::Source::File(server.root.clone());
        let mut route = Route::ranked(CachedFileServer::DEFAULT_RANK, Method::Get, "/<path..>", server);
        route.name = Some(format!("CachedFileServer: {}", source).into());
        vec![route]
    }
}

#[async_trait::async_trait]
impl Handler for CachedFileServer {
    async fn handle<'r>(&self, req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r> {
        match self.server.handle(req, data).await {
            Outcome::Success(mut resp) => {
                resp.set_raw_header("Cache-control", "max-age=86400");  // 24h (24 * 60 * 60)
                Outcome::Success(resp)
            },
            i => i,
        }
    }
}

@the10thWiz
Copy link
Collaborator

I think this issue can now be closed, as of #2789. These (and any headers) can be added via the rewrite API introduced in that PR.

@github-project-automation github-project-automation bot moved this from Backlog to Done in Rocket v0.6 Jul 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted An accepted request or suggestion request Request for new functionality
Projects
Status: Done
Development

No branches or pull requests

7 participants