1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-04 09:56:22 +02:00

Compare commits

..

6 Commits

Author SHA1 Message Date
1ca9d87f0a prep actix-web-codegen release 2019-10-14 21:35:53 +06:00
967f965405 Update syn & quote to 1.0 (#1133)
* chore(actix-web-codegen): Upgrade syn and quote to 1.0

* feat(actix-web-codegen): Generate better error message

* doc(actix-web-codegen): Update CHANGES.md

* fix: Build with stable rust
2019-10-14 21:34:17 +06:00
062e51e8ce prep actix-file release 2019-10-14 21:26:26 +06:00
a48e616def feat(files): add possibility to redirect to slash-ended path (#1134)
When accessing to a folder without a final slash, the index file will be loaded ok, but if it has
references (like a css or an image in an html file) these resources won't be loaded correctly if
they are using relative paths. In order to solve this, this PR adds the possibility to detect
folders without a final slash and make a 302 redirect to mitigate this issue. The behavior is off by
default. We're adding a new method called `redirect_to_slash_directory` which can be used to enable
this behavior.
2019-10-14 21:23:15 +06:00
effa96f5e4 Removed httpcode 'MovedPermanenty'. (#1128) 2019-10-12 06:45:12 +06:00
cc0b4be5b7 Fix typo in response.rs body() comment (#1126)
Fixes https://github.com/actix/actix-web/issues/1125
2019-10-09 19:11:55 +06:00
9 changed files with 238 additions and 127 deletions

View File

@ -1,5 +1,9 @@
# Changes
## [0.1.6] - 2019-10-14
* Add option to redirect to a slash-ended path `Files` #1132
## [0.1.5] - 2019-10-08
* Bump up `mime_guess` crate version to 2.0.1
@ -8,17 +12,14 @@
* Allow user defined request guards for `Files` #1113
## [0.1.4] - 2019-07-20
* Allow to disable `Content-Disposition` header #686
## [0.1.3] - 2019-06-28
* Do not set `Content-Length` header, let actix-http set it #930
## [0.1.2] - 2019-06-13
* Content-Length is 0 for NamedFile HEAD request #914

View File

@ -1,6 +1,6 @@
[package]
name = "actix-files"
version = "0.1.5"
version = "0.1.6"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static files support for actix web."
readme = "README.md"

View File

@ -233,6 +233,7 @@ pub struct Files {
directory: PathBuf,
index: Option<String>,
show_index: bool,
redirect_to_slash: bool,
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>,
@ -246,6 +247,7 @@ impl Clone for Files {
directory: self.directory.clone(),
index: self.index.clone(),
show_index: self.show_index,
redirect_to_slash: self.redirect_to_slash,
default: self.default.clone(),
renderer: self.renderer.clone(),
file_flags: self.file_flags,
@ -273,6 +275,7 @@ impl Files {
directory: dir,
index: None,
show_index: false,
redirect_to_slash: false,
default: Rc::new(RefCell::new(None)),
renderer: Rc::new(directory_listing),
mime_override: None,
@ -289,6 +292,14 @@ impl Files {
self
}
/// Redirects to a slash-ended path when browsing a directory.
///
/// By default never redirect.
pub fn redirect_to_slash_directory(mut self) -> Self {
self.redirect_to_slash = true;
self
}
/// Set custom directory renderer
pub fn files_listing_renderer<F>(mut self, f: F) -> Self
where
@ -389,10 +400,10 @@ impl HttpServiceFactory for Files {
}
impl NewService for Files {
type Config = ();
type Request = ServiceRequest;
type Response = ServiceResponse;
type Error = Error;
type Config = ();
type Service = FilesService;
type InitError = ();
type Future = Box<dyn Future<Item = Self::Service, Error = Self::InitError>>;
@ -402,6 +413,7 @@ impl NewService for Files {
directory: self.directory.clone(),
index: self.index.clone(),
show_index: self.show_index,
redirect_to_slash: self.redirect_to_slash,
default: None,
renderer: self.renderer.clone(),
mime_override: self.mime_override.clone(),
@ -429,6 +441,7 @@ pub struct FilesService {
directory: PathBuf,
index: Option<String>,
show_index: bool,
redirect_to_slash: bool,
default: Option<HttpService>,
renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>,
@ -502,6 +515,16 @@ impl Service for FilesService {
if path.is_dir() {
if let Some(ref redir_index) = self.index {
if self.redirect_to_slash && !req.path().ends_with('/') {
let redirect_to = format!("{}/", req.path());
return Either::A(ok(req.into_response(
HttpResponse::Found()
.header(header::LOCATION, redirect_to)
.body("")
.into_body(),
)));
}
let path = path.join(redir_index);
match NamedFile::open(path) {
@ -1169,6 +1192,34 @@ mod tests {
assert!(format!("{:?}", bytes).contains("/tests/test.png"));
}
#[test]
fn test_redirect_to_slash_directory() {
// should not redirect if no index
let mut srv = test::init_service(
App::new().service(Files::new("/", ".").redirect_to_slash_directory()),
);
let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
// should redirect if index present
let mut srv = test::init_service(
App::new().service(
Files::new("/", ".")
.index_file("test.png")
.redirect_to_slash_directory(),
),
);
let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::FOUND);
// should not redirect if the path is wrong
let req = TestRequest::with_uri("/not_existing").to_request();
let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_static_files_bad_directory() {
let _st: Files = Files::new("/", "missing");

View File

@ -29,7 +29,6 @@ impl Response {
STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED);
STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES);
STATIC_RESP!(MovedPermanenty, StatusCode::MOVED_PERMANENTLY);
STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY);
STATIC_RESP!(Found, StatusCode::FOUND);
STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER);

View File

@ -194,7 +194,7 @@ impl<B> Response<B> {
self.head.extensions.borrow_mut()
}
/// Get body os this response
/// Get body of this response
#[inline]
pub fn body(&self) -> &ResponseBody<B> {
&self.body

View File

@ -1,5 +1,11 @@
# Changes
## [0.1.3] - 2019-10-14
* Bump up `syn` & `quote` to 1.0
* Provide better error message
## [0.1.2] - 2019-06-04
* Add macros for head, options, trace, connect and patch http methods

View File

@ -1,6 +1,6 @@
[package]
name = "actix-web-codegen"
version = "0.1.2"
version = "0.1.3"
description = "Actix web proc macros"
readme = "README.md"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
@ -12,8 +12,9 @@ workspace = ".."
proc-macro = true
[dependencies]
quote = "0.6.12"
syn = { version = "0.15.34", features = ["full", "parsing", "extra-traits"] }
quote = "1"
syn = { version = "1", features = ["full", "parsing"] }
proc-macro2 = "1"
[dev-dependencies]
actix-web = { version = "1.0.0" }

View File

@ -58,7 +58,10 @@ use syn::parse_macro_input;
#[proc_macro_attribute]
pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Get);
let gen = match route::Route::new(args, input, route::GuardType::Get) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate()
}
@ -70,7 +73,10 @@ pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute]
pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Post);
let gen = match route::Route::new(args, input, route::GuardType::Post) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate()
}
@ -82,7 +88,10 @@ pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute]
pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Put);
let gen = match route::Route::new(args, input, route::GuardType::Put) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate()
}
@ -94,7 +103,10 @@ pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute]
pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Delete);
let gen = match route::Route::new(args, input, route::GuardType::Delete) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate()
}
@ -106,7 +118,10 @@ pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute]
pub fn head(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Head);
let gen = match route::Route::new(args, input, route::GuardType::Head) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate()
}
@ -118,7 +133,10 @@ pub fn head(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute]
pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Connect);
let gen = match route::Route::new(args, input, route::GuardType::Connect) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate()
}
@ -130,7 +148,10 @@ pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute]
pub fn options(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Options);
let gen = match route::Route::new(args, input, route::GuardType::Options) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate()
}
@ -142,7 +163,10 @@ pub fn options(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute]
pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Trace);
let gen = match route::Route::new(args, input, route::GuardType::Trace) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate()
}
@ -154,6 +178,9 @@ pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute]
pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Patch);
let gen = match route::Route::new(args, input, route::GuardType::Patch) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate()
}

View File

@ -1,21 +1,23 @@
extern crate proc_macro;
use std::fmt;
use proc_macro::TokenStream;
use quote::quote;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{AttributeArgs, Ident, NestedMeta};
enum ResourceType {
Async,
Sync,
}
impl fmt::Display for ResourceType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ResourceType::Async => write!(f, "to_async"),
ResourceType::Sync => write!(f, "to"),
}
impl ToTokens for ResourceType {
fn to_tokens(&self, stream: &mut TokenStream2) {
let ident = match self {
ResourceType::Async => "to_async",
ResourceType::Sync => "to",
};
let ident = Ident::new(ident, Span::call_site());
stream.append(ident);
}
}
@ -32,63 +34,89 @@ pub enum GuardType {
Patch,
}
impl fmt::Display for GuardType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
GuardType::Get => write!(f, "Get"),
GuardType::Post => write!(f, "Post"),
GuardType::Put => write!(f, "Put"),
GuardType::Delete => write!(f, "Delete"),
GuardType::Head => write!(f, "Head"),
GuardType::Connect => write!(f, "Connect"),
GuardType::Options => write!(f, "Options"),
GuardType::Trace => write!(f, "Trace"),
GuardType::Patch => write!(f, "Patch"),
impl GuardType {
fn as_str(&self) -> &'static str {
match self {
GuardType::Get => "Get",
GuardType::Post => "Post",
GuardType::Put => "Put",
GuardType::Delete => "Delete",
GuardType::Head => "Head",
GuardType::Connect => "Connect",
GuardType::Options => "Options",
GuardType::Trace => "Trace",
GuardType::Patch => "Patch",
}
}
}
pub struct Args {
name: syn::Ident,
path: String,
ast: syn::ItemFn,
resource_type: ResourceType,
pub guard: GuardType,
pub extra_guards: Vec<String>,
impl ToTokens for GuardType {
fn to_tokens(&self, stream: &mut TokenStream2) {
let ident = self.as_str();
let ident = Ident::new(ident, Span::call_site());
stream.append(ident);
}
}
impl fmt::Display for Args {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let ast = &self.ast;
let guards = format!(".guard(actix_web::guard::{}())", self.guard);
let guards = self.extra_guards.iter().fold(guards, |acc, val| {
format!("{}.guard(actix_web::guard::fn_guard({}))", acc, val)
});
struct Args {
path: syn::LitStr,
guards: Vec<Ident>,
}
write!(
f,
"
#[allow(non_camel_case_types)]
pub struct {name};
impl actix_web::dev::HttpServiceFactory for {name} {{
fn register(self, config: &mut actix_web::dev::AppService) {{
{ast}
let resource = actix_web::Resource::new(\"{path}\"){guards}.{to}({name});
actix_web::dev::HttpServiceFactory::register(resource, config)
}}
}}",
name = self.name,
ast = quote!(#ast),
path = self.path,
guards = guards,
to = self.resource_type
)
impl Args {
fn new(args: AttributeArgs) -> syn::Result<Self> {
let mut path = None;
let mut guards = Vec::new();
for arg in args {
match arg {
NestedMeta::Lit(syn::Lit::Str(lit)) => match path {
None => {
path = Some(lit);
}
_ => {
return Err(syn::Error::new_spanned(
lit,
"Multiple paths specified! Should be only one!",
));
}
},
NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
if nv.path.is_ident("guard") {
if let syn::Lit::Str(lit) = nv.lit {
guards.push(Ident::new(&lit.value(), Span::call_site()));
} else {
return Err(syn::Error::new_spanned(
nv.lit,
"Attribute guard expects literal string!",
));
}
} else {
return Err(syn::Error::new_spanned(
nv.path,
"Unknown attribute key is specified. Allowed: guard",
));
}
}
arg => {
return Err(syn::Error::new_spanned(arg, "Unknown attribute"));
}
}
}
Ok(Args {
path: path.unwrap(),
guards,
})
}
}
pub struct Route {
name: syn::Ident,
args: Args,
ast: syn::ItemFn,
resource_type: ResourceType,
guard: GuardType,
}
fn guess_resource_type(typ: &syn::Type) -> ResourceType {
let mut guess = ResourceType::Sync;
@ -111,75 +139,73 @@ fn guess_resource_type(typ: &syn::Type) -> ResourceType {
guess
}
impl Args {
pub fn new(args: &[syn::NestedMeta], input: TokenStream, guard: GuardType) -> Self {
impl Route {
pub fn new(
args: AttributeArgs,
input: TokenStream,
guard: GuardType,
) -> syn::Result<Self> {
if args.is_empty() {
panic!(
"invalid server definition, expected: #[{}(\"some path\")]",
guard
);
return Err(syn::Error::new(
Span::call_site(),
format!(
r#"invalid server definition, expected #[{}("<some path>")]"#,
guard.as_str().to_ascii_lowercase()
),
));
}
let ast: syn::ItemFn = syn::parse(input)?;
let name = ast.sig.ident.clone();
let ast: syn::ItemFn = syn::parse(input).expect("Parse input as function");
let name = ast.ident.clone();
let args = Args::new(args)?;
let mut extra_guards = Vec::new();
let mut path = None;
for arg in args {
match arg {
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
if path.is_some() {
panic!("Multiple paths specified! Should be only one!")
}
let fname = quote!(#fname).to_string();
path = Some(fname.as_str()[1..fname.len() - 1].to_owned())
}
syn::NestedMeta::Meta(syn::Meta::NameValue(ident)) => {
match ident.ident.to_string().to_lowercase().as_str() {
"guard" => match ident.lit {
syn::Lit::Str(ref text) => extra_guards.push(text.value()),
_ => panic!("Attribute guard expects literal string!"),
},
attr => panic!(
"Unknown attribute key is specified: {}. Allowed: guard",
attr
),
}
}
attr => panic!("Unknown attribute{:?}", attr),
}
}
let resource_type = if ast.asyncness.is_some() {
let resource_type = if ast.sig.asyncness.is_some() {
ResourceType::Async
} else {
match ast.decl.output {
syn::ReturnType::Default => panic!(
"Function {} has no return type. Cannot be used as handler",
name
),
match ast.sig.output {
syn::ReturnType::Default => {
return Err(syn::Error::new_spanned(
ast,
"Function has no return type. Cannot be used as handler",
));
}
syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()),
}
};
let path = path.unwrap();
Self {
Ok(Self {
name,
path,
args,
ast,
resource_type,
guard,
extra_guards,
}
})
}
pub fn generate(&self) -> TokenStream {
let text = self.to_string();
let name = &self.name;
let guard = &self.guard;
let ast = &self.ast;
let path = &self.args.path;
let extra_guards = &self.args.guards;
let resource_type = &self.resource_type;
let stream = quote! {
#[allow(non_camel_case_types)]
pub struct #name;
match text.parse() {
Ok(res) => res,
Err(error) => panic!("Error: {:?}\nGenerated code: {}", error, text),
}
impl actix_web::dev::HttpServiceFactory for #name {
fn register(self, config: &mut actix_web::dev::AppService) {
#ast
let resource = actix_web::Resource::new(#path)
.guard(actix_web::guard::#guard())
#(.guard(actix_web::guard::fn_guard(#extra_guards)))*
.#resource_type(#name);
actix_web::dev::HttpServiceFactory::register(resource, config)
}
}
};
stream.into()
}
}