Merge pull request #69 from actix/master

-
This commit is contained in:
Zhang Zhongyu 2020-06-24 11:34:21 +08:00 committed by GitHub
commit 384066d5b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 270 additions and 35 deletions

View File

@ -1,6 +1,6 @@
--- ---
name: bug report name: Bug Report
about: create a bug report about: Create a bug report.
--- ---
Your issue may already be reported! Your issue may already be reported!

28
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,28 @@
## PR Type
What kind of change does this PR make?
<!-- Check the one that applies to this PR using "[x]". -->
- [ ] Bug fix
- [ ] Feature
- [ ] Refactor / code style change (no functional or public API changes)
- [ ] Other
## PR Checklist
Check your PR fulfills the following:
<!-- For draft PRs check the boxes as you complete them. -->
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] A changelog entry has been made for the appropriate packages.
## Overview
<!-- Describe the current and new behavior. -->
<!-- Emphasize any breaking changes. -->
<!-- If this PR fixes or closes an issue, reference it here. -->
<!-- Closes #000 -->

View File

@ -5,6 +5,8 @@
### Added ### Added
* Re-export `actix_rt::main` as `actix_web::main`. * Re-export `actix_rt::main` as `actix_web::main`.
* `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched
resource pattern.
### Changed ### Changed

View File

@ -97,8 +97,8 @@ serde_json = "1.0"
serde_urlencoded = "0.6.1" serde_urlencoded = "0.6.1"
time = { version = "0.2.7", default-features = false, features = ["std"] } time = { version = "0.2.7", default-features = false, features = ["std"] }
url = "2.1" url = "2.1"
open-ssl = { version="0.10", package = "openssl", optional = true } open-ssl = { package = "openssl", version = "0.10", optional = true }
rust-tls = { version = "0.17.0", package = "rustls", optional = true } rust-tls = { package = "rustls", version = "0.17.0", optional = true }
tinyvec = { version = "0.3", features = ["alloc"] } tinyvec = { version = "0.3", features = ["alloc"] }
[dev-dependencies] [dev-dependencies]

View File

@ -94,7 +94,7 @@ You may consider checking out
## Benchmarks ## Benchmarks
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r18) * [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r19)
## License ## License

View File

@ -11,7 +11,6 @@ documentation = "https://docs.rs/actix-files/"
categories = ["asynchronous", "web-programming::http-server"] categories = ["asynchronous", "web-programming::http-server"]
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
edition = "2018" edition = "2018"
workspace = ".."
[lib] [lib]
name = "actix_files" name = "actix_files"

View File

@ -1,5 +1,10 @@
# Changes # Changes
## [Unreleased] - XXXX-XX-XX
* Add main entry-point macro that uses re-exported runtime.
## [0.2.2] - 2020-05-23 ## [0.2.2] - 2020-05-23
* Add resource middleware on actix-web-codegen [#1467] * Add resource middleware on actix-web-codegen [#1467]

View File

@ -9,7 +9,6 @@ documentation = "https://docs.rs/actix-web-codegen"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
edition = "2018" edition = "2018"
workspace = ".."
[lib] [lib]
proc-macro = true proc-macro = true

View File

@ -1,4 +1,4 @@
# Macros for actix-web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web-codegen)](https://crates.io/crates/actix-web-codegen) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) # Helper and convenience macros for Actix-web. [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web-codegen)](https://crates.io/crates/actix-web-codegen) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Documentation & Resources ## Documentation & Resources

View File

@ -1,11 +1,12 @@
#![recursion_limit = "512"] #![recursion_limit = "512"]
//! Actix-web codegen module
//! Helper and convenience macros for Actix-web.
//! //!
//! Generators for routes and scopes //! ## Runtime Setup
//! //!
//! ## Route //! - [main](attr.main.html)
//! //!
//! Macros: //! ## Resource Macros:
//! //!
//! - [get](attr.get.html) //! - [get](attr.get.html)
//! - [post](attr.post.html) //! - [post](attr.post.html)
@ -23,12 +24,12 @@
//! - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard` //! - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard`
//! - `wrap="Middleware"` - Registers a resource middleware. //! - `wrap="Middleware"` - Registers a resource middleware.
//! //!
//! ## Notes //! ### Notes
//! //!
//! Function name can be specified as any expression that is going to be accessible to the generate //! Function name can be specified as any expression that is going to be accessible to the generate
//! code (e.g `my_guard` or `my_module::my_guard`) //! code (e.g `my_guard` or `my_module::my_guard`)
//! //!
//! ## Example: //! ### Example:
//! //!
//! ```rust //! ```rust
//! use actix_web::HttpResponse; //! use actix_web::HttpResponse;
@ -139,3 +140,43 @@ pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream {
pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream { pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream {
route::generate(args, input, route::GuardType::Patch) route::generate(args, input, route::GuardType::Patch)
} }
/// Marks async main function as the actix system entry-point.
///
/// ## Usage
///
/// ```rust
/// #[actix_web::main]
/// async fn main() {
/// async { println!("Hello world"); }.await
/// }
/// ```
#[proc_macro_attribute]
#[cfg(not(test))] // Work around for rust-lang/rust#62127
pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
use quote::quote;
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
let attrs = &input.attrs;
let vis = &input.vis;
let sig = &mut input.sig;
let body = &input.block;
let name = &sig.ident;
if sig.asyncness.is_none() {
return syn::Error::new_spanned(sig.fn_token, "only async fn is supported")
.to_compile_error()
.into();
}
sig.asyncness = None;
(quote! {
#(#attrs)*
#vis #sig {
actix_web::rt::System::new(stringify!(#name))
.block_on(async move { #body })
}
})
.into()
}

1
rust-toolchain Normal file
View File

@ -0,0 +1 @@
1.40.0

View File

@ -42,6 +42,7 @@ pub struct App<T, B> {
impl App<AppEntry, Body> { impl App<AppEntry, Body> {
/// Create application builder. Application can be configured with a builder-like pattern. /// Create application builder. Application can be configured with a builder-like pattern.
#[allow(clippy::new_without_default)]
pub fn new() -> Self { pub fn new() -> Self {
let fref = Rc::new(RefCell::new(None)); let fref = Rc::new(RefCell::new(None));
App { App {

View File

@ -25,7 +25,7 @@ impl ConnectionInfo {
Ref::map(req.extensions(), |e| e.get().unwrap()) Ref::map(req.extensions(), |e| e.get().unwrap())
} }
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity, clippy::borrow_interior_mutable_const)]
fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo { fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo {
let mut host = None; let mut host = None;
let mut scheme = None; let mut scheme = None;

View File

@ -1,18 +1,11 @@
#![warn(rust_2018_idioms, warnings)] #![warn(rust_2018_idioms, warnings)]
#![allow( #![allow(clippy::needless_doctest_main, clippy::type_complexity)]
clippy::needless_doctest_main,
clippy::type_complexity,
clippy::borrow_interior_mutable_const
)]
//! Actix web is a small, pragmatic, and extremely fast web framework //! Actix web is a small, pragmatic, and extremely fast web framework
//! for Rust. //! for Rust.
//! //!
//! ## Example //! ## Example
//! //!
//! The `#[actix_rt::main]` macro in the example below is provided by the Actix runtime
//! crate, [`actix-rt`](https://crates.io/crates/actix-rt). You will need to include
//! `actix-rt` in your dependencies for it to run.
//!
//! ```rust,no_run //! ```rust,no_run
//! use actix_web::{web, App, Responder, HttpServer}; //! use actix_web::{web, App, Responder, HttpServer};
//! //!
@ -20,7 +13,7 @@
//! format!("Hello {}! id:{}", info.0, info.1) //! format!("Hello {}! id:{}", info.0, info.1)
//! } //! }
//! //!
//! #[actix_rt::main] //! #[actix_web::main]
//! async fn main() -> std::io::Result<()> { //! async fn main() -> std::io::Result<()> {
//! HttpServer::new(|| App::new().service( //! HttpServer::new(|| App::new().service(
//! web::resource("/{name}/{id}/index.html").to(index)) //! web::resource("/{name}/{id}/index.html").to(index))
@ -80,9 +73,7 @@
//! * `compress` - enables content encoding compression support (default enabled) //! * `compress` - enables content encoding compression support (default enabled)
//! * `openssl` - enables ssl support via `openssl` crate, supports `http/2` //! * `openssl` - enables ssl support via `openssl` crate, supports `http/2`
//! * `rustls` - enables ssl support via `rustls` crate, supports `http/2` //! * `rustls` - enables ssl support via `rustls` crate, supports `http/2`
//! * `secure-cookies` - enables secure cookies support, includes `ring` crate as //! * `secure-cookies` - enables secure cookies support
//! dependency
#![allow(clippy::type_complexity, clippy::new_without_default)]
mod app; mod app;
mod app_service; mod app_service;
@ -106,13 +97,12 @@ pub mod test;
mod types; mod types;
pub mod web; pub mod web;
#[doc(hidden)]
pub use actix_web_codegen::*; pub use actix_web_codegen::*;
pub use actix_rt as rt;
// re-export for convenience // re-export for convenience
pub use actix_http::Response as HttpResponse; pub use actix_http::Response as HttpResponse;
pub use actix_http::{body, cookie, http, Error, HttpMessage, ResponseError, Result}; pub use actix_http::{body, cookie, http, Error, HttpMessage, ResponseError, Result};
pub use actix_macros::{main, test as test_rt};
pub use crate::app::App; pub use crate::app::App;
pub use crate::extract::FromRequest; pub use crate::extract::FromRequest;
@ -230,6 +220,7 @@ pub mod client {
//! println!("Response: {:?}", response); //! println!("Response: {:?}", response);
//! } //! }
//! ``` //! ```
pub use awc::error::{ pub use awc::error::{
ConnectError, InvalidUrl, PayloadError, SendRequestError, WsClientError, ConnectError, InvalidUrl, PayloadError, SendRequestError, WsClientError,
}; };

View File

@ -90,6 +90,7 @@ where
self.service.poll_ready(cx) self.service.poll_ready(cx)
} }
#[allow(clippy::borrow_interior_mutable_const)]
fn call(&mut self, req: ServiceRequest) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
// negotiate content-encoding // negotiate content-encoding
let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) { let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) {

View File

@ -128,6 +128,7 @@ where
self.service.poll_ready(cx) self.service.poll_ready(cx)
} }
#[allow(clippy::borrow_interior_mutable_const)]
fn call(&mut self, req: ServiceRequest) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
let inner = self.inner.clone(); let inner = self.inner.clone();
let fut = self.service.call(req); let fut = self.service.call(req);

View File

@ -478,7 +478,7 @@ impl FormatText {
} }
FormatText::RemoteAddr => { FormatText::RemoteAddr => {
let s = if let Some(ref peer) = req.connection_info().remote_addr() { let s = if let Some(ref peer) = req.connection_info().remote_addr() {
FormatText::Str(peer.to_string()) FormatText::Str((*peer).to_string())
} else { } else {
FormatText::Str("-".to_string()) FormatText::Str("-".to_string())
}; };

View File

@ -126,6 +126,17 @@ impl HttpRequest {
&mut Rc::get_mut(&mut self.0).unwrap().path &mut Rc::get_mut(&mut self.0).unwrap().path
} }
/// The resource definition pattern that matched the path. Useful for logging and metrics.
///
/// For example, when a resource with pattern `/user/{id}/profile` is defined and a call is made
/// to `/user/123/profile` this function would return `Some("/user/{id}/profile")`.
///
/// Returns a None when no resource is fully matched, including default services.
#[inline]
pub fn match_pattern(&self) -> Option<String> {
self.0.rmap.match_pattern(self.path())
}
/// Request extensions /// Request extensions
#[inline] #[inline]
pub fn extensions(&self) -> Ref<'_, Extensions> { pub fn extensions(&self) -> Ref<'_, Extensions> {
@ -141,7 +152,6 @@ impl HttpRequest {
/// Generate url for named resource /// Generate url for named resource
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web;
/// # use actix_web::{web, App, HttpRequest, HttpResponse}; /// # use actix_web::{web, App, HttpRequest, HttpResponse};
/// # /// #
/// fn index(req: HttpRequest) -> HttpResponse { /// fn index(req: HttpRequest) -> HttpResponse {
@ -599,4 +609,36 @@ mod tests {
assert!(tracker.borrow().dropped); assert!(tracker.borrow().dropped);
} }
#[actix_rt::test]
async fn extract_path_pattern() {
let mut srv = init_service(
App::new().service(
web::scope("/user/{id}")
.service(web::resource("/profile").route(web::get().to(
move |req: HttpRequest| {
assert_eq!(
req.match_pattern(),
Some("/user/{id}/profile".to_owned())
);
HttpResponse::Ok().finish()
},
)))
.default_service(web::to(move |req: HttpRequest| {
assert!(req.match_pattern().is_none());
HttpResponse::Ok().finish()
})),
),
)
.await;
let req = TestRequest::get().uri("/user/22/profile").to_request();
let res = call_service(&mut srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
let req = TestRequest::get().uri("/user/22/not-exist").to_request();
let res = call_service(&mut srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
}
} }

View File

@ -43,9 +43,7 @@ impl ResourceMap {
} }
} }
} }
}
impl ResourceMap {
/// Generate url for named resource /// Generate url for named resource
/// ///
/// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method. /// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method.
@ -95,6 +93,45 @@ impl ResourceMap {
false false
} }
/// Returns the full resource pattern matched against a path or None if no full match
/// is possible.
pub fn match_pattern(&self, path: &str) -> Option<String> {
let path = if path.is_empty() { "/" } else { path };
// ensure a full match exists
if !self.has_resource(path) {
return None;
}
Some(self.traverse_resource_pattern(path))
}
/// Takes remaining path and tries to match it up against a resource definition within the
/// current resource map recursively, returning a concatenation of all resource prefixes and
/// patterns matched in the tree.
///
/// Should only be used after checking the resource exists in the map so that partial match
/// patterns are not returned.
fn traverse_resource_pattern(&self, remaining: &str) -> String {
for (pattern, rmap) in &self.patterns {
if let Some(ref rmap) = rmap {
if let Some(prefix_len) = pattern.is_prefix_match(remaining) {
let prefix = pattern.pattern().to_owned();
return [
prefix,
rmap.traverse_resource_pattern(&remaining[prefix_len..]),
]
.concat();
}
} else if pattern.is_match(remaining) {
return pattern.pattern().to_owned();
}
}
String::new()
}
fn patterns_for<U, I>( fn patterns_for<U, I>(
&self, &self,
name: &str, name: &str,
@ -188,3 +225,81 @@ impl ResourceMap {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extract_matched_pattern() {
let mut root = ResourceMap::new(ResourceDef::root_prefix(""));
let mut user_map = ResourceMap::new(ResourceDef::root_prefix(""));
user_map.add(&mut ResourceDef::new("/"), None);
user_map.add(&mut ResourceDef::new("/profile"), None);
user_map.add(&mut ResourceDef::new("/article/{id}"), None);
user_map.add(&mut ResourceDef::new("/post/{post_id}"), None);
user_map.add(
&mut ResourceDef::new("/post/{post_id}/comment/{comment_id}"),
None,
);
root.add(&mut ResourceDef::new("/info"), None);
root.add(&mut ResourceDef::new("/v{version:[[:digit:]]{1}}"), None);
root.add(
&mut ResourceDef::root_prefix("/user/{id}"),
Some(Rc::new(user_map)),
);
let root = Rc::new(root);
root.finish(Rc::clone(&root));
// sanity check resource map setup
assert!(root.has_resource("/info"));
assert!(!root.has_resource("/bar"));
assert!(root.has_resource("/v1"));
assert!(root.has_resource("/v2"));
assert!(!root.has_resource("/v33"));
assert!(root.has_resource("/user/22"));
assert!(root.has_resource("/user/22/"));
assert!(root.has_resource("/user/22/profile"));
// extract patterns from paths
assert!(root.match_pattern("/bar").is_none());
assert!(root.match_pattern("/v44").is_none());
assert_eq!(root.match_pattern("/info"), Some("/info".to_owned()));
assert_eq!(
root.match_pattern("/v1"),
Some("/v{version:[[:digit:]]{1}}".to_owned())
);
assert_eq!(
root.match_pattern("/v2"),
Some("/v{version:[[:digit:]]{1}}".to_owned())
);
assert_eq!(
root.match_pattern("/user/22/profile"),
Some("/user/{id}/profile".to_owned())
);
assert_eq!(
root.match_pattern("/user/602CFB82-7709-4B17-ADCF-4C347B6F2203/profile"),
Some("/user/{id}/profile".to_owned())
);
assert_eq!(
root.match_pattern("/user/22/article/44"),
Some("/user/{id}/article/{id}".to_owned())
);
assert_eq!(
root.match_pattern("/user/22/post/my-post"),
Some("/user/{id}/post/{post_id}".to_owned())
);
assert_eq!(
root.match_pattern("/user/22/post/other-post/comment/42"),
Some("/user/{id}/post/{post_id}/comment/{comment_id}".to_owned())
);
}
}

View File

@ -46,6 +46,7 @@ pub struct Route {
impl Route { impl Route {
/// Create new route which matches any request. /// Create new route which matches any request.
#[allow(clippy::new_without_default)]
pub fn new() -> Route { pub fn new() -> Route {
Route { Route {
service: Box::new(RouteNewService::new(Extract::new(Handler::new(|| { service: Box::new(RouteNewService::new(Extract::new(Handler::new(|| {

View File

@ -195,6 +195,12 @@ impl ServiceRequest {
pub fn match_info(&self) -> &Path<Url> { pub fn match_info(&self) -> &Path<Url> {
self.0.match_info() self.0.match_info()
} }
/// Counterpart to [`HttpRequest::match_pattern`](../struct.HttpRequest.html#method.match_pattern).
#[inline]
pub fn match_pattern(&self) -> Option<String> {
self.0.match_pattern()
}
#[inline] #[inline]
/// Get a mutable reference to the Path parameters. /// Get a mutable reference to the Path parameters.

View File

@ -252,6 +252,7 @@ pub struct UrlEncoded<U> {
fut: Option<LocalBoxFuture<'static, Result<U, UrlencodedError>>>, fut: Option<LocalBoxFuture<'static, Result<U, UrlencodedError>>>,
} }
#[allow(clippy::borrow_interior_mutable_const)]
impl<U> UrlEncoded<U> { impl<U> UrlEncoded<U> {
/// Create a new future to URL encode a request /// Create a new future to URL encode a request
pub fn new(req: &HttpRequest, payload: &mut Payload) -> UrlEncoded<U> { pub fn new(req: &HttpRequest, payload: &mut Payload) -> UrlEncoded<U> {

View File

@ -319,6 +319,7 @@ where
U: DeserializeOwned + 'static, U: DeserializeOwned + 'static,
{ {
/// Create `JsonBody` for request. /// Create `JsonBody` for request.
#[allow(clippy::borrow_interior_mutable_const)]
pub fn new( pub fn new(
req: &HttpRequest, req: &HttpRequest,
payload: &mut Payload, payload: &mut Payload,

View File

@ -315,6 +315,7 @@ pub struct HttpMessageBody {
impl HttpMessageBody { impl HttpMessageBody {
/// Create `MessageBody` for request. /// Create `MessageBody` for request.
#[allow(clippy::borrow_interior_mutable_const)]
pub fn new(req: &HttpRequest, payload: &mut dev::Payload) -> HttpMessageBody { pub fn new(req: &HttpRequest, payload: &mut dev::Payload) -> HttpMessageBody {
let mut len = None; let mut len = None;
if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) {

View File

@ -14,7 +14,6 @@ categories = ["network-programming", "asynchronous",
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
exclude = [".gitignore", ".cargo/config"] exclude = [".gitignore", ".cargo/config"]
edition = "2018" edition = "2018"
workspace = ".."
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = [] features = []