use std::future::Future; use actix_utils::future::{ok, Ready}; use actix_web::{ dev::{Service, ServiceRequest, ServiceResponse, Transform}, http::{ self, header::{HeaderName, HeaderValue}, StatusCode, }, web, App, Error, HttpRequest, HttpResponse, Responder, }; use actix_web_codegen_const_routes::{ connect, delete, get, head, options, patch, post, put, route, routes, trace, }; use futures_core::future::LocalBoxFuture; // Make sure that we can name function as 'config' #[get("/config")] async fn config() -> impl Responder { HttpResponse::Ok() } const PATH: &str = "/path"; #[get(path = "PATH")] async fn path() -> impl Responder { HttpResponse::Ok() } struct FieldPath(&'static str); const FIELD_PATH: FieldPath = FieldPath("/path/new/"); #[get(path = "FIELD_PATH.0")] async fn field_path() -> impl Responder { HttpResponse::Ok() } #[get("/test")] async fn test_handler() -> impl Responder { HttpResponse::Ok() } #[put("/test")] async fn put_test() -> impl Responder { HttpResponse::Created() } #[patch("/test")] async fn patch_test() -> impl Responder { HttpResponse::Ok() } #[post("/test")] async fn post_test() -> impl Responder { HttpResponse::NoContent() } #[head("/test")] async fn head_test() -> impl Responder { HttpResponse::Ok() } #[connect("/test")] async fn connect_test() -> impl Responder { HttpResponse::Ok() } #[options("/test")] async fn options_test() -> impl Responder { HttpResponse::Ok() } #[trace("/test")] async fn trace_test() -> impl Responder { HttpResponse::Ok() } #[get("/test")] fn auto_async() -> impl Future> { ok(HttpResponse::Ok().finish()) } #[get("/test")] fn auto_sync() -> impl Future> { ok(HttpResponse::Ok().finish()) } #[put("/test/{param}")] async fn put_param_test(_: web::Path) -> impl Responder { HttpResponse::Created() } #[delete("/test/{param}")] async fn delete_param_test(_: web::Path) -> impl Responder { HttpResponse::NoContent() } #[get("/test/{param}")] async fn get_param_test(_: web::Path) -> impl Responder { HttpResponse::Ok() } #[route("/hello", method = "HELLO")] async fn custom_route_test() -> impl Responder { HttpResponse::Ok() } #[route( "/multi", method = "GET", method = "POST", method = "HEAD", method = "HELLO" )] async fn route_test() -> impl Responder { HttpResponse::Ok() } #[routes] #[get("/routes/test")] #[get("/routes/test2")] #[post("/routes/test")] async fn routes_test() -> impl Responder { HttpResponse::Ok() } // routes overlap with the more specific route first, therefore accessible #[routes] #[get("/routes/overlap/test")] #[get("/routes/overlap/{foo}")] async fn routes_overlapping_test(req: HttpRequest) -> impl Responder { // foo is only populated when route is not /routes/overlap/test match req.match_info().get("foo") { None => assert!(req.uri() == "/routes/overlap/test"), Some(_) => assert!(req.uri() != "/routes/overlap/test"), } HttpResponse::Ok() } // routes overlap with the more specific route last, therefore inaccessible #[routes] #[get("/routes/overlap2/{foo}")] #[get("/routes/overlap2/test")] async fn routes_overlapping_inaccessible_test(req: HttpRequest) -> impl Responder { // foo is always populated even when path is /routes/overlap2/test assert!(req.match_info().get("foo").is_some()); HttpResponse::Ok() } #[get("/custom_resource_name", name = "custom")] async fn custom_resource_name_test<'a>(req: HttpRequest) -> impl Responder { assert!(req.url_for_static("custom").is_ok()); assert!(req.url_for_static("custom_resource_name_test").is_err()); HttpResponse::Ok() } mod guard_module { use actix_web::{guard::GuardContext, http::header}; pub fn guard(ctx: &GuardContext) -> bool { ctx.header::() .map(|h| h.preference() == "image/*") .unwrap_or(false) } } #[get("/test/guard", guard = "guard_module::guard")] async fn guard_test() -> impl Responder { HttpResponse::Ok() } pub struct ChangeStatusCode; impl Transform for ChangeStatusCode where S: Service, Error = Error>, S::Future: 'static, B: 'static, { type Response = ServiceResponse; type Error = Error; type Transform = ChangeStatusCodeMiddleware; type InitError = (); type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(ChangeStatusCodeMiddleware { service }) } } pub struct ChangeStatusCodeMiddleware { service: S, } impl Service for ChangeStatusCodeMiddleware where S: Service, Error = Error>, S::Future: 'static, B: 'static, { type Response = ServiceResponse; type Error = Error; type Future = LocalBoxFuture<'static, Result>; actix_web::dev::forward_ready!(service); fn call(&self, req: ServiceRequest) -> Self::Future { let fut = self.service.call(req); Box::pin(async move { let mut res = fut.await?; let headers = res.headers_mut(); let header_name = HeaderName::from_lowercase(b"custom-header").unwrap(); let header_value = HeaderValue::from_str("hello").unwrap(); headers.insert(header_name, header_value); Ok(res) }) } } #[get("/test/wrap", wrap = "ChangeStatusCode")] async fn get_wrap(_: web::Path) -> impl Responder { // panic!("actually never gets called because path failed to extract"); HttpResponse::Ok() } /// Using expression, not just path to type, in wrap attribute. /// /// Regression from . #[route( "/catalog", method = "GET", method = "HEAD", wrap = "actix_web::middleware::Compress::default()" )] async fn get_catalog() -> impl Responder { HttpResponse::Ok().body("123123123") } #[actix_rt::test] async fn test_params() { let srv = actix_test::start(|| { App::new() .service(get_param_test) .service(put_param_test) .service(delete_param_test) }); let request = srv.request(http::Method::GET, srv.url("/test/it")); let response = request.send().await.unwrap(); assert_eq!(response.status(), http::StatusCode::OK); let request = srv.request(http::Method::PUT, srv.url("/test/it")); let response = request.send().await.unwrap(); assert_eq!(response.status(), http::StatusCode::CREATED); let request = srv.request(http::Method::DELETE, srv.url("/test/it")); let response = request.send().await.unwrap(); assert_eq!(response.status(), http::StatusCode::NO_CONTENT); } #[actix_rt::test] async fn test_body() { let srv = actix_test::start(|| { App::new() .service(post_test) .service(put_test) .service(head_test) .service(connect_test) .service(options_test) .service(trace_test) .service(patch_test) .service(test_handler) .service(route_test) .service(routes_overlapping_test) .service(routes_overlapping_inaccessible_test) .service(routes_test) .service(custom_resource_name_test) .service(guard_test) .service(path) .service(field_path) }); let request = srv.request(http::Method::GET, srv.url("/test")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::HEAD, srv.url("/test")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::CONNECT, srv.url("/test")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::OPTIONS, srv.url("/test")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::TRACE, srv.url("/test")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::PATCH, srv.url("/test")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::PUT, srv.url("/test")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); assert_eq!(response.status(), http::StatusCode::CREATED); let request = srv.request(http::Method::POST, srv.url("/test")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); assert_eq!(response.status(), http::StatusCode::NO_CONTENT); let request = srv.request(http::Method::GET, srv.url("/test")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::GET, srv.url("/multi")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::POST, srv.url("/multi")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::HEAD, srv.url("/multi")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::PATCH, srv.url("/multi")); let response = request.send().await.unwrap(); assert!(!response.status().is_success()); let request = srv.request(http::Method::GET, srv.url("/routes/test")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::GET, srv.url("/routes/test2")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::POST, srv.url("/routes/test")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::GET, srv.url("/routes/not-set")); let response = request.send().await.unwrap(); assert!(response.status().is_client_error()); let request = srv.request(http::Method::GET, srv.url("/routes/overlap/test")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::GET, srv.url("/routes/overlap/bar")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::GET, srv.url("/routes/overlap2/test")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::GET, srv.url("/routes/overlap2/bar")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::GET, srv.url("/custom_resource_name")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv .request(http::Method::GET, srv.url("/test/guard")) .insert_header(("Accept", "image/*")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); let request = srv.request(http::Method::GET, srv.url(FIELD_PATH.0)); let response = request.send().await.unwrap(); assert_eq!(response.status(), http::StatusCode::OK); let request = srv.request(http::Method::GET, srv.url(PATH)); let response = request.send().await.unwrap(); assert_eq!(response.status(), http::StatusCode::OK); } #[actix_rt::test] async fn test_auto_async() { let srv = actix_test::start(|| App::new().service(auto_async)); let request = srv.request(http::Method::GET, srv.url("/test")); let response = request.send().await.unwrap(); assert!(response.status().is_success()); } #[actix_web::test] async fn test_wrap() { let srv = actix_test::start(|| App::new().service(get_wrap)); let request = srv.request(http::Method::GET, srv.url("/test/wrap")); let mut response = request.send().await.unwrap(); assert_eq!(response.status(), StatusCode::NOT_FOUND); assert!(response.headers().contains_key("custom-header")); let body = response.body().await.unwrap(); let body = String::from_utf8(body.to_vec()).unwrap(); assert!(body.contains("wrong number of parameters")); }