Giter Site home page Giter Site logo

actix-web-static-files's People

Contributors

0xadd1e avatar joepio avatar kilork avatar mephistophiles avatar robjtede avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

actix-web-static-files's Issues

automatically serve index.html if it exists

Hello,

Thank you for making this package, it's really useful.

I'm service a static directory that contains and index.html. To get that
index.html I have to GET /static/index.html. Is is possible to have it served
automatically on /static?

Thanks!

trait bound `ResourceFiles: HttpServiceFactory` is not satisfied

Hey @kilork, I'm trying to use your awesome crate but I'm getting an error saying the trait 'HttpServiceFactory' is not implemented for ResourceFiles`

my Cargo.toml:

[package]
name = "server"
version = "0.1.0"
edition = "2018"

[dependencies]
actix-web-static-files = "3.0.5"
actix-web = "4.0.0-beta.3"  

[build-dependencies]
actix-web-static-files = "3.0.5"

main.rs

use actix_web::{App, HttpServer};
use actix_web_static_files;

include!(concat!(env!("OUT_DIR"), "/generated.rs"));

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(move || {
        let generated = generate();

        App::new().service(actix_web_static_files::ResourceFiles::new(
            "/static", generated,
        ))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

build.rs

use actix_web_static_files::generate_resources;
use std::{env, path::Path};

fn main() {
    let out_dir = env::var("OUT_DIR").unwrap();
    let generated_filename = Path::new(&out_dir).join("generated.rs");
    generate_resources("./static", None, generated_filename, "generate")
        .expect("can't collect resources");
}

Serving subroutes in a static built page

Hey there.

Fair warning, I am quite new at this Rust thing - so bear with me if I'm asking something quite obvious.

I'm trying to serve a ./www/dist folder that is generated by Astro. (static html that has a interactive island).

So currently, I get it to build, but It only points to the root index.html, I can not access any subroutes, even though they too are just static html files in a subfolder(/projects/index.html).

It also does not serve my api::openmeteo endpoint.

This is my current main.rs, that works in cargo run, but not build --release:

use actix_files as fs;
use actix_web::{App, HttpServer};

mod api;
mod models;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    std::env::set_var("RUST_LOG", "debug");
    std::env::set_var("RUST_BACKTRACE", "1");
    env_logger::init();

    HttpServer::new(|| {
        App::new()
        .service(api::openmeteo::openmeteo)
        .service(
            fs::Files::new("/", "./www/dist")
                .index_file("index.html")
                .show_files_listing(),
        )
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

If I follow the guide in the readme I get something like this:

// build.rs
use static_files::resource_dir;

fn main() -> std::io::Result<()> {
    resource_dir("./www/dist").build()
}

// main.rs
include!(concat!(env!("OUT_DIR"), "/generated.rs"));

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    std::env::set_var("RUST_LOG", "debug");
    std::env::set_var("RUST_BACKTRACE", "1");
    env_logger::init();

    HttpServer::new(move || {
        let generated = generate();
        App::new()
        .service(ResourceFiles::new("/", generated))
        .service(api::openmeteo::openmeteo)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

This only ends up serving the index on /, the api/opemeteo route does not exist and only GET/HEAD requests are allowed.

However it does end up building/bundling the _astro folder containing the built css, so at least the index looks pretty.

So, I'm probably just doing something very silly here, but I'd appreciate a little pointer in the right direction. (I tried searching, but couldnt quite find what I was looking for)

actix-web-static-files v4.0 with Docker image - error: environment variable `OUT_DIR` not defined

Hello

first thank for this awesome add on, I use and abuse it, with my embedded react projects and actixweb

but today I'm build a docker image and have a strange issue, that only occurs building docker image

the error is

$ docker build .
error: environment variable `OUT_DIR` not defined

used Cargo Versions

same inside docker build context and outside

$ cargo -V
cargo 1.59.0 (49d8809dc 2022-02-10)

Some Links

Bootstrap minimal project

$ git clone https://github.com/kilork/actix-web-static-files-examples.git
$ cd actix-web-static-files-examples/generate-resources-mapping

Create Dockerfile

# create Dockerfile
$ nano Dockerfile

Dockerfile

#################
## build stage ##
#################
FROM rust:1-slim-bullseye AS builder
WORKDIR /code

RUN apt-get update \
  && apt-get install --no-install-recommends -y pkg-config libssl-dev \
  && rm -rf /var/lib/apt/lists/* \
  && apt clean

# Download crates-io index and fetch dependency code.
# This step avoids needing to spend time on every build downloading the index
# which can take a long time within the docker context. Docker will cache it.
RUN USER=root cargo init
COPY Cargo.toml Cargo.toml
RUN cargo fetch

# copy app files
COPY src src

# compile app
RUN cargo build --release

###############
## run stage ##
###############
FROM debian:bullseye-slim
WORKDIR /app

# copy server binary from build stage
COPY --from=builder /code/target/release/generate-resources-mapping generate-resources-mapping

# set user to non-root unless root is required for your app
USER 1001

# indicate what port the server is running on
EXPOSE 8543

# run server
CMD [ "/app/generate-resources-mapping" ]

Try Build Image

$ docker build .
....
error: environment variable `OUT_DIR` not defined
 --> src/main.rs:8:22
  |
8 |     include!(concat!(env!("OUT_DIR"), "/generated.rs"))
  |                      ^^^^^^^^^^^^^^^
  |
  = note: this error originates in the macro `env` (in Nightly builds, run with -Z macro-backtrace for more info)

error: could not compile `generate-resources-mapping` due to previous error

I try many things like define OUT_DIR etc but I can't figure out out to fix it
use nightly, and other non sense approaches

thanks

Add possibility to generate module for inclusion

This looks like more nice approach, as just directly include generation procedure.

We can achieve a similar thing with the following code:

    let out_dir = env::var("OUT_DIR").unwrap();

    let generated_filename = Path::new(&out_dir).join("generated.rs");
    let generated_rs = "\
mod sets;

pub use sets::generate;";
    std::fs::write(generated_filename, generated_rs).unwrap();

    let generated_dir = Path::new(&out_dir).join("sets");
    std::fs::create_dir_all(&generated_dir).unwrap();
    let mod_filename = generated_dir.join("mod.rs");
    std::fs::write(
        mod_filename,
        "\
mod set_01;
pub use set_01::generate;",
    )
    .unwrap();
    let generated_filename = generated_dir.join("set_01.rs");

    generate_resources(
        "generated",
        None,
        generated_filename,
        "generate",
    )
    .unwrap();

Generate response at compile time?

Is this project generating responses at compile time? It should certainly be possible as the only thing that changes is content length which is also determined at compile time.

Build dependency on actix-web is not required

There is no real need to depend on actix-web to just collect static files.

We need to split this to build and dev parts to allow build less dependencies. This is especially useful for typical setup, when we have crate which supposed to collect resources and other crate, which is doing presentation using actix-web.

Another outcome here is the possibility to support other than actix-web frameworks.

Checklist:

Issues:

Example didn't work: mismatched types actix_web_static_files::Resource vs static_files::Resource

I tried the example from README, and it didn't work. So I started a new clean project just to verify and followed step by step instructions in README for static files, but the result is the same:

error[E0308]: mismatched types
  --> src/main.rs:10:52
   |
10 |         App::new().service(ResourceFiles::new("/", generated))
   |                                                    ^^^^^^^^^ expected struct `actix_web_static_files::Resource`, found struct `static_files::Resource`
   |
   = note: expected struct `std::collections::HashMap<&'static str, actix_web_static_files::Resource>`
              found struct `std::collections::HashMap<&str, static_files::Resource>`

I'm on stable rustc 1.58.1 (db9d1b20b 2022-01-20), MacOS.
Resolved lib versions are:

...
actix-web: 3.3.3
actix-web-static-files: 3.0.5
static-files: 0.2.3
...

I'm not sure if it's me or something broke.

Support dev/debug mode

We support npm based build. Most of such builds supports also development mode, in which requests handled with internal development server.

We should implement this mode as additional feature flag dev to allow start such development servers and proxy requests to them, or just live refresh data from the project directory.

Why this feature is cool? Because Rust compiler is not the fastest and in the reality we want to see our updates fast and not wait long. At the same time we do not bother that much about performance during development.

no default features for actix-web

The dependency in Cargo.toml should ideally be:

- actix-web = "3.0.2"
+ actix-web = { version = "3.0.2", default-features = false }

implementation Clone for Resource

Resource does not implement Clone so I cannot generate the file list before running the server.
I do need this because my server is behind a command, not directly in the main :

      // main.rs

       include!(concat!(env!("OUT_DIR"), "/generated.rs"));

       async fn main() -> std::io::Result<()> {
               let generated_static_files_list = generate();
                 Cmd::Listen { address, port } => {
                           Command::listen(generated_static_files_list).await
                 }
        }

      // cli.rs
       fn listen(generated: HashMap<&'static str, Resource>)
            HttpServer::new(move || { // the trait std::clone::Clone is not implemented for actix_web_static_files::Resource
            App::new()
                .service(actix_web_static_files::ResourceFiles::new(
                    "/static",
                    generated
                )) 
                ...
        }

`ResourceFiles::new("", resources::generate())` as a root resolver doesn't correctly resolve `http://localhost:8080/` to `index.html`

I am using this crate with this pull request: #45 with actix 4.0 beta so it is somewhat unoffical:

However I have found one bug: If I try to add ResourceFiles as a root resolver like this: ResourceFiles::new("/", resources::generate()) this doesn't actually work for any paths other than the actual path of "/" so "/" gets resolved to index.html but no other file can be correctly resolved like "/index.js".

If I add it like this: ResourceFiles::new("", resources::generate()) this works all the paths that are not handled by previous services are handled by this services and paths like "/index.html" and "/index.js" are correctly resolved and served by this crate however now the most simple request to http://localhost:8080/ doesn't get resolved to index.html. (resolve_defaults is true)

Stepping through the code the issue is this:

in resource_files.rs:163 Service<ServiceRequest> for ResourceFilesService::call:

 let req_path = req.match_info().path();

        let mut item = self.files.get(req_path);

        if item.is_none()
            && self.resolve_defaults
            && (req_path.is_empty() || req_path.ends_with('/'))
        {
            let index_req_path = req_path.to_string() + INDEX_HTML;    // <------------
            item = self.files.get(index_req_path.as_str());                           // <--------------
        }

index_req_path becomes "/index.html" and then it is tried to be read from the self.files hashmap.

A way to fix this would be to change this function so that in all places where the self.files hashmap is being read (line numbers: 165, 172, 187, 191) it does it though a helper method that will check the path and if that starts with a slash it will remove that slash before attempting to read the file from the hashmap (so when the path is for example "/index.html" we should try to read from the map using the key "index.html" (the leading slash removed)).

Workarounds:

  1. register both ResourceFiles::new("", resources::generate()) and ResourceFiles::new("/", resources::generate()) at the same time.
  2. if acceptable use resolve_not_found_to_root with ResourceFiles::new("", resources::generate())

Invoking npm.cmd on Windows is failing

When I run cargo build on Windows I get the following error:

error: failed to run custom build command for `web v0.1.0 (C:\repositories\rust\myproject\web)`

Caused by:
  process didn't exit successfully: `C:\repositories\rust\myproject\target\debug\build\web-b0554dea23c73a0f\build-script-build` (exit code: 101)
--- stdout
NPM_CMD is npm.cmd
 running npm install npm.cmd in "./assets"

--- stderr
internal/modules/cjs/loader.js:968
  throw err;
  ^

Error: Cannot find module 'C:\repositories\rust\myproject\web\assets\node_modules\npm\bin\npm-cli.js'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:965:15)
    at Function.Module._load (internal/modules/cjs/loader.js:841:27)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47 {
  code: 'MODULE_NOT_FOUND',
  requireStack: []
}
internal/modules/cjs/loader.js:968
  throw err;
  ^

Error: Cannot find module 'C:\repositories\rust\myproject\web\assets\node_modules\npm\bin\npm-cli.js'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:965:15)
    at Function.Module._load (internal/modules/cjs/loader.js:841:27)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47 {
  code: 'MODULE_NOT_FOUND',
  requireStack: []
}
internal/modules/cjs/loader.js:968
  throw err;
  ^

Error: Cannot find module 'C:\repositories\rust\myproject\web\assets\node_modules\npm\bin\npm-cli.js'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:965:15)
    at Function.Module._load (internal/modules/cjs/loader.js:841:27)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47 {
  code: 'MODULE_NOT_FOUND',
  requireStack: []
}
internal/modules/cjs/loader.js:968
  throw err;
  ^

Error: Cannot find module 'C:\repositories\rust\myproject\web\assets\node_modules\npm\bin\npm-cli.js'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:965:15)
    at Function.Module._load (internal/modules/cjs/loader.js:841:27)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47 {
  code: 'MODULE_NOT_FOUND',
  requireStack: []
}
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 3, kind: NotFound, message: "The system cannot find the path specified." }', web\build.rs:4:5

However, the same project compiles just fine on Linux.

The following code reproduced the same error:

    if let Err(e) = Command::new("npm.cmd")
        .arg("install")
        .current_dir(r#"c:\repositories\rust\myproject\web\assets\"#)
        .status()
    {
        eprintln!("Cannot execute install: {:?}", e);
    }

I think it is due to the fact that npm.cmd needs to set some variables and probably update PATH prior to running in the command shell.

The following code seems to work without any problems.

    if let Err(e) = Command::new("cmd")
        .arg("/c")
        .arg("npm.cmd")
        .arg("install")
        .current_dir(r#"c:\repositories\rust\mdreader\web\assets\"#)
        .status()
    {
        eprintln!("Cannot execute install: {:?}", e);
    }

Tar files in the binary

is it possible to include the tar.gz file in the binary and when the main execute needs to do some operation with the tar file?

Actix_web 4.0 compatibility: trait `HttpServiceFactory` is not implemented for `ResourceFiles`

Hi there, thanks for creating and maintaining this!

I'd like to use this in a project, but it seems like it's not yet compatible with the latest Betas of actix.

The error I run into is this:

error: the trait bound `ResourceFiles: HttpServiceFactory` is not satisfied
label: the trait `HttpServiceFactory` is not implemented for `ResourceFiles`

I assume this is change from earlier actix web versions.

The trait has been implemented, but I think it should be updated.

impl HttpServiceFactory for ResourceFiles {
    fn register(self, config: &mut AppService) {
        let rdef = if config.is_root() {
            ResourceDef::root_prefix(&self.path)
        } else {
            ResourceDef::prefix(&self.path)
        };
        config.register_service(rdef, None, self, None)
    }
}

Maybe I can help, but I'm kind of rust + actix newbie to be honest.

v4.1.0

Breaking changes for version v3.1.0.

Add possibility to generate resource map without function wrapper

rust-analyser fails to resolve functions from included generated files, which shows in the editor errors.

This is not nice for user experience. We would add new functions to generate only hash map.

Then usage is would be like:

fn generate() -> ::std::collections::HashMap<&'static str, actix_web_static_files::Resource> {
  include!(concat!(env!("OUT_DIR"), "/generated.rs"));
}

Looks like it would be more meaningful not only in this case.

Expose API to set cache-control and other headers

Hi, I found this package useful; thanks for maintaining it!

I was looking at the code in this crate, and I noticed that it should be setting ETags, but they aren't showing up at all when I look at the headers in the browser devtools or curl. This is despite the content-type being detected and set correctly. Example:

HTTP/1.1 200 OK
content-length: 14995
content-type: text/css
date: Sun, 02 Jun 2024 04:48:54 GMT

This seems like a bug to me. Looking at the code in respond_to, I don't see how it could both set the content-type but not the etag, so I'm not sure how to fix it. EDIT: I think it's related to my project setup somehow; I'll keep debugging it.

Additionally, I was looking through the API docs and I realized there is no way to set the headers that are sent back, specifically the cache-control headers, which are complimentary to etags.

I was looking at the code and it seemed like somewhere in respond_to would be the right place to set custom headers. Probably the simplest way to implement this is to take a static list of headers to inject, but it would probably be more powerful to add an API (to ResourceFiles) that takes a lambda that is given the file and the in-progress HttpResponseBuilder and can do whatever it wants.

npm variant does not compile on windows

While building Case 2 variant encounter following error with msvc build:

error: failed to run custom build command for `actix-test v0.1.0 (C:\Users\...\projects\actix-test)`

Caused by:
  process didn't exit successfully: `c:\Users\...\projects\actix-test\target\debug\build\actix-test-7074526937b9228a\build-script-build` (exit code: 101)
--- stderr
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "The system cannot find the file specified." }', src\libcore\result.rs:1165:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Create version 1.0.0 to support legacy 0.2.x versions

Most of downloads come from 0.2.x versions. But there is no way to create new features in this branch without breaking backward compatibility. To avoid such issues last version 0.2.6 would be republished as 1.0.0. Future development of legacy will continue in 1.x releases.

Allow for NpmBuild install output to be logged

At the moment all output (to stdout) is discarded and unreachable for a developer. The appropiate function, from my POV, would be NpmBuild::install.

I would love to either specify some file to write the log or to have it output to stdout.

Outputting to stdout or some kid of logger would allow the output to be shown in the cargo debug logs. By defualt there's no need to do any further catching as cargo will prevent any logs to be shown by build scripts.See this issue for more information on this topic.

Simplify generated code

We need to simplify generated code to just a bunch of inserts with minimal size (in text).

Important to check this on large application with many small and large files.

Use HashMap with an absolute path in generated code

Right now, HashMap is used via a directly imported type name, which produces this error if it isn't explicitely imported at the point of use:


1 | #[allow(clippy::unreadable_literal)] pub fn generate() -> HashMap<&'static str, actix_web_static_files::Resource> {
  |                                                           ^^^^^^^ not found in this scope
  |
help: consider importing this struct
  |
3 | use std::collections::HashMap;
  |

It is good practice in macros to use all types with the absolute path, e.g. ::std::collections::HashMap, as to not interfere with the namespace at the point of use and to make it easier to use (no use statements). Of course, this isn't a macro, but it would seem that the same point applies.

Help on getting the examples to run

Hi,

I'm learning Rust and throughly enjoying the process. Thanks a lot for building such a great crate !

I've been stuck on this error for a few days now and am unable to figure out what the correct way to resolve it is.
I'm trying to build a very simple application

error[E0308]: mismatched types
  --> src/main.rs:26:61
   |
26 |                     .service(ResourceFiles::new("/static/", generated))
   |                                                             ^^^^^^^^^ expected struct `actix_web_static_files::Resource`, found struct `static_files::Resource`
   |
   = note: expected struct `std::collections::HashMap<&'static str, actix_web_static_files::Resource>`
              found struct `std::collections::HashMap<&str, static_files::Resource>`

For more information about this error, try `rustc --explain E0308`.

The application code is also fairly simple:

use actix_web::{App, HttpServer};
mod constants;
mod db;
mod license;
mod staticfiles;
use actix_web_static_files::ResourceFiles;

include!(concat!(env!("OUT_DIR"), "/generated.rs"));

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    match db::ensure_migrations() {
        true => {
            println!("Migrations sync complete.");
            HttpServer::new(|| {
                let generated = generate();
                App::new()
                    .data(license::LicenseState {
                        is_valid: license::get_license_state(),
                    })
                    .service(license::index)
                    .service(license::get_license)
                    .service(license::post_license)
                    .service(staticfiles::js_file_server)
                    .service(staticfiles::css_file_server)
                    .service(ResourceFiles::new("/static/", generated))
            })
            .workers(1)
            .bind("127.0.0.1:8080")?
            .run()
            .await
        }
        false => {
            println!("Unable to complete migrations. Shutting down.");
            Ok(())
        }
    }
}

If anyone can point me in the right direction about what I should be reading about/ investigating that would be enough.

Use at root - but continue to next route if none is matched

Hi there,

Thanks for making and maintaining this!

I need to mount the contents of a directory that needs to be on root (robots.txt, a serviceworker.js file), but of course I also want to have other handlers as well.

If I do this, all other routes don't work - they 404. The my_custom_handler isn't called.

.service(ResourceFiles::new("", generated))
.service(web::resource(ANY).to(my_custom_handler))

Is there a way I can use actix-web-static-files in such a manner that I can only let the handler match for files that are present? In other words: can the ResourceFiles handler not return 404s, but simply skip to the next handler?

Cheers!

Edit:

Some ideas:

Currently, the respond_to function checks if there's a file found, and will either return the Item or respond with a 404. Can we respond with something that tells Actix not to use this handler further? I don't think that's how Actix is supposed to work.

Guard

Add a guard that, on init, checks the files in the target folder and only allows request processing with matching files. It might not be idiomatic, as Actix states that guards should not be used for path matching (there's other tools for that), but I don't know how we can use other path matching tools in this case. Since ResourceFiles::new takes a HashMap of files, this shouldn't be too hard. However, since ResourceFiles is a Factory, I'm not actually sure on how to add this guard from outside of this crate.

// This does not work
.service(ResourceFiles::new("/", generated).guard(my_guard)

However, we could add the guard in the register function of the HttpServicefactory impl block.

EDIT: I added the guard to the register function, that's way cleaner

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.