kilork / actix-web-static-files Goto Github PK
View Code? Open in Web Editor NEWactix-web static files as resources support
License: The Unlicense
actix-web static files as resources support
License: The Unlicense
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!
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");
}
Related to #41
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)
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
same inside docker build context and outside
$ cargo -V
cargo 1.59.0 (49d8809dc 2022-02-10)
$ git clone https://github.com/kilork/actix-web-static-files-examples.git
$ cd actix-web-static-files-examples/generate-resources-mapping
# 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" ]
$ 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
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();
Currently only npm install
is supported. It would be nice to have more than that.
We need to have full support of these specs: Range requests on MDN
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.
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:
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.
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.
The dependency in Cargo.toml should ideally be:
- actix-web = "3.0.2"
+ actix-web = { version = "3.0.2", default-features = false }
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
))
...
}
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:
ResourceFiles::new("", resources::generate())
and ResourceFiles::new("/", resources::generate())
at the same time.resolve_not_found_to_root
with ResourceFiles::new("", resources::generate())
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);
}
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?
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.
Breaking changes for version v3.1.0
.
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.
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.
To continue development for modern actix - create 2.x series
Actix starts worker for each processor. With current API it also makes a full copy of generated data for this. Should not be like this.
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.
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.
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.
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.
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.
It could reduce executable size, if there were an option to gzip the static files, and have the service set the content type. Just a thought.
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.
Example from typical build.rs
:
println!("cargo:rerun-if-changed=build.rs");
This allows to run build.rs
only if build.rs
changed. Similar we can add static files to list of changes. This needs to be tested for new files case also.
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.
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.