Is possible to pass more than one parameter into axtic_web route ?
// srv.rs (frag.)
HttpServer::new(|| {
App::new()
.route(
"/api/ext/{name}/set/config/{id}",
web::get().to(api::router::setExtConfig),
)
})
.start();
// router.rs (frag.)
pub fn setExtConfig(
name: web::Path<String>,
id: web::Path<String>,
_req: HttpRequest,
) -> HttpResponse {
println!("{} {}", name, id);
HttpResponse::Ok()
.content_type("text/html")
.body("OK")
}
For routes with one param everything is ok, but for this example
i see only message in the browser: wrong number of parameters: 2 expected 1, and the response stauts code is 404.
I realy need to pass more parameters (from one to three or four)...
That is a perfect fit for tuple:
pub fn setExtConfig(
param: web::Path<(String, String)>,
_req: HttpRequest,
) -> HttpResponse {
println!("{} {}", param.0, param.1);
HttpResponse::Ok().content_type("text/html").body("OK")
}
Related
I am using actix-web to run a webserver and want to be able to mutate state through websocket messages.
My current way of using websockets is through implementing the handle method from actix::StreamHandler. However this limits my ability of passing data to it. How can I access the data (actix_web::web::Data) in my handle method?
The only way I can think of solving this issue is to somehow overwrite the function signature of handle, however that doesn't seem possible
Hers is some important code snippets, we have app_name and nonces in app_data:
// main.rs
let nonces = Arc::new(Mutex::new(nonces::Nonces::new()));
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(app_data::AppData {
app_name: String::from("Actix Web"),
nonces: Arc::clone(&nonces),
}))
...
// app_data.rs
pub struct AppData {
pub app_name: String,
pub nonces: Arc<Mutex<nonces::Nonces>>,
}
// ws.rs
struct Ws {
app_data: web::Data<app_data::AppData>,
}
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Ws {
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
let app_name = &self.app_data.app_name;
let mut nonces = self.app_data.nonces.lock().unwrap();
println!(">>> {app_name}");
println!(">>> {:?}", nonces.nonces); // I have a nonces data in nonces
...
}
}
async fn index(
req: HttpRequest,
stream: web::Payload,
app_data: web::Data<app_data::AppData>,
) -> Result<HttpResponse, Error> {
ws::start(Ws { app_data: app_data.clone() }, &req, stream)
}
I have the following route handler, which sends a 400 BAD REQUEST in case of parse error in FormData.
#[post("/newsletter")]
pub async fn publish_newsletter(
form: web::Form<FormData>,
...
) -> Result<HttpResponse, PublishError> {
...
}
To provide better UX, I want to opt out of this behavior. I'd like to redirect the user to the same page and display the error as a flash message.
But I can't seem to figure out how to extract FormData using Form::from_request
I have tried using HttpRequest and web::dev::Payload extractors:
#[post("/newsletter")]
pub async fn publish_newsletter(
...
req: HttpRequest,
mut payload: dev::Payload,
) -> Result<HttpResponse, PublishError> {
let form: web::Form<FormData> = match web::Form::from_request(&req, &mut payload).await {
Ok(data) => data,
Err(e) => {
// send flash message
// redirect to same page
return Ok(see_other("/newsletter"));
}
};
...
}
But ultimately I'm faced with this error:
the trait bound `actix_web::dev::Payload: FromRequest` is not satisfied
I've solved it in the following way:
#[post("/newsletter")]
pub async fn publish_newsletter(
form: Result<web::Form<FormData>, actix_web::Error>,
...
) -> Result<HttpResponse, PublishError> {
let form = match form {
Ok(form) => form,
Err(e) => return Ok(send_flash_message_and_redirect(e, "/newsletter")),
};
...
}
Shoutout to the helpful folks at Actix Web Discord.
In the Actix Web Framework, how does one use the route attributes macros (#[http_method("route")]) to bind multiple http methods to one function?
For example, I have this trivial endpoint:
/// Returns a UUID4.
#[get("/uuid")]
async fn uuid_v4() -> impl Responder {
HttpResponse::Ok().json(Uuid {
uuid: uuid::Uuid::new_v4(),
})
}
I would like to have the same endpoint handle HEAD requests, how do I do this?
My initial approach was to just stack up the macros:
/// Returns a UUID4.
#[get("/uuid")]
#[head("/uuid")]
async fn uuid_v4() -> impl Responder {
HttpResponse::Ok().json(Uuid {
uuid: uuid::Uuid::new_v4(),
})
}
But I do get a compilation error:
|
249 | async fn uuid_v4() -> impl Responder {
| ^^^^^^^ the trait `actix_web::handler::Factory<_, _, _>` is not implemented for `<uuid_v4 as actix_web::service::HttpServiceFactory>::register::uuid_v4`
I have gone through the actix-web and actix-web-codegen docs and didn't find anything addressing this
you can do
#[route("/", method="GET", method="POST", method="PUT")]
async fn index() -> impl Responder {
HttpResponse::Ok().body("Hello world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
.service(index)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
An example with multiple path and multiple methods for one resource
async fn index() -> impl Responder {
HttpResponse::Ok().body("Hello world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
.service(
actix_web::web::resource(vec!["/", "/index"])
.route(actix_web::web::get().to(index))
.route(actix_web::web::post().to(index))
)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
I assume you are using actix-web: 2.0.0 with actix-rt: 1.0.0 and this handler you are passing to App.service method like below
HttpServer::new(move || {
App::new()
.wrap(middleware::Logger::default())
.service(index)
})
.bind(("127.0.0.1", self.port))?
.workers(8)
.run()
.await
then you will need to write handler like this ->
/// Returns a UUID4.
#[get("/uuid")]
async fn uuid_v4(req: HttpRequest) -> Result<web::Json<IndexResponse>> {
let uuid_header = req
.headers()
.get("uuid")
.and_then(|v| v.to_str().ok())
.unwrap_or_else(|| "some-id");
//curl -H "uuid: username" localhost:8080
println!("make use of {}", uuid_header);
Ok(web::Json(Uuid {
uuid: uuid::Uuid::new_v4(),
}))
}
I found in the docs an example of how to create global state, protected by Mutex, shared among processing threads that is made available to all your route handlers. Perfect! However, I prefer to use attributes attached to my functions to wire up my route handlers. I do not know the syntax (if permitted) to use attributed functions and also pass in the global state.
Here is the example from the actix-web docs, from https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Data.html
use std::sync::Mutex;
use actix_web::{web, App};
struct MyData {
counter: usize,
}
/// Use `Data<T>` extractor to access data in handler.
fn index(data: web::Data<Mutex<MyData>>) {
let mut data = data.lock().unwrap();
data.counter += 1;
}
fn main() {
let data = web::Data::new(Mutex::new(MyData{ counter: 0 }));
let app = App::new()
// Store `MyData` in application storage.
.register_data(data.clone())
.service(
web::resource("/index.html").route(
web::get().to(index)));
}
Notice how the route handler named index is being passed the web::Data.
Now here are some snippets of my code.
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
pub mod request;
pub mod routes;
const SERVICE_NAME : &str = "Shy Rules Engine";
const SERVICE_VERSION : &str = "0.1";
#[get("/")]
fn index() -> impl Responder {
HttpResponse::Ok().body(format!("{} version {}", SERVICE_NAME, SERVICE_VERSION))
}
mod expression_execute {
#[post("/expression/execute")]
fn route(req: web::Json<ExpressionExecuteRequest>) -> HttpResponse {
// ... lots of code omitted ...
if response.has_error() {
HttpResponse::Ok().json(response)
}
else {
HttpResponse::BadRequest().json(response)
}
}
}
pub fn shy_service(ip : &str, port : &str) {
HttpServer::new(|| {
App::new()
.service(index)
.service(expression_execute::route)
})
.bind(format!("{}:{}", ip, port))
.unwrap()
.run()
.unwrap();
}
Notice how I am calling method App::service to wire up my route handlers.
Also notice how my route handler does not receive global state (because I have not yet added it to my app). If I used a similar pattern as the docs using register_data to create global App data, what changes do I make to my method signature, the get and post attributes and anything else so that I can pass that global state to the handler?
Or is it not possible using get and post attributes to gain access to global state?
The two cases you listed really don't have much difference:
//# actix-web = "1.0.8"
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use std::sync::Mutex;
const SERVICE_NAME : &str = "Shy Rules Engine";
const SERVICE_VERSION : &str = "0.1";
struct MyData {
counter: usize,
}
#[get("/")]
fn index(data: web::Data<Mutex<MyData>>) -> impl Responder {
let mut data = data.lock().unwrap();
data.counter += 1;
println!("Endpoint visited: {}", data.counter);
HttpResponse::Ok().body(format!("{} version {}", SERVICE_NAME, SERVICE_VERSION))
}
pub fn shy_service(ip : &str, port : &str) {
let data = web::Data::new(Mutex::new(MyData{ counter: 0 }));
HttpServer::new(move || {
App::new()
.register_data(data.clone())
.service(index)
})
.bind(format!("{}:{}", ip, port))
.unwrap()
.run()
.unwrap();
}
fn main() {
shy_service("127.0.0.1", "8080");
}
You can verify that it works by simply curl the http endpoint. For multiple extractors, you'll have to use tuple:
#[post("/expression/execute")]
fn route((req, data): (web::Json<ExpressionExecuteRequest>, web::Data<Mutex<MyData>>)) -> HttpResponse {
unimplemented!()
}
Im developing an app with rust and actix-web framwork, and I inserted some middleware instance to the app.
I planed the middleware would modify the response's body text and return the response in the call() method, but I couldn't find the solution. I can't find a sample code that enables getting text from ServiceResponse and modifying it.
Could you guys help me with some sample code that getting response's body text and modifying it?
Following is sample code that I used. I added a comment to inform what I want in SayHiMiddleware->call() of "sample.rs"
// sample.rs
use actix_service::{Service, Transform};
use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error};
use futures::future::{ok, FutureResult};
use futures::{Future, Poll};
// There are two steps in middleware processing.
// 1. Middleware initialization, middleware factory gets called with
// next service in chain as parameter.
// 2. Middleware's call method gets called with normal request.
pub struct SayHi;
// Middleware factory is `Transform` trait from actix-service crate
// `S` - type of the next service
// `B` - type of response's body
impl<S, B> Transform<S> for SayHi
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = SayHiMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
ok(SayHiMiddleware { service })
}
}
pub struct SayHiMiddleware<S> {
service: S,
}
impl<S, B> Service for SayHiMiddleware<S>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Box<dyn Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
println!("Hi from start. You requested: {}", req.path());
Box::new(self.service.call(req).and_then(|res| {
// I want to get response body text and print the text.
// But I couldnt get the text from ServiceResponse instance..... help me guys T.T
// And is there way to combine with previous response body text and new text?????
// example (res->body->text is "aaaaa")) and I want to append new text to the string ( "aaaaa" + "bbbbb" )
println!("Hi from response");
Ok(res)
}))
}
}
// main.rs
use actix_web::{web, App, HttpServer};
use actix_service::Service;
use futures::future::Future;
#[allow(dead_code)]
mod redirect;
#[allow(dead_code)]
mod read_request_body;
#[allow(dead_code)]
mod read_response_body;
#[allow(dead_code)]
mod simple;
fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=debug");
env_logger::init();
HttpServer::new(|| {
App::new()
.wrap(redirect::CheckLogin)
.wrap(read_request_body::Logging)
.wrap(read_response_body::Logging)
.wrap(simple::SayHi)
.wrap_fn(|req, srv| {
println!("Hi from start. You requested: {}", req.path());
srv.call(req).map(|res| {
println!("Hi from response");
res
})
})
.service(web::resource("/login").to(|| {
"You are on /login. Go to src/redirect.rs to change this behavior."
}))
.service(
web::resource("/").to(|| {
"Hello, middleware! Check the console where the server is run."
}),
)
})
.bind("127.0.0.1:8080")?
.run()
}
Thank you...
This is what I got working. I think there might be a better way but examples are light.
/// Extracts a response body of type T from `input`
/// Example:
///
/// let result: MyResponseBodyContract = TestContext::map_body(&mut resp).await;
pub async fn map_body<T>(input: &mut ServiceResponse<Body>) -> T
where T: DeserializeOwned
{
let mut body = input.take_body();
let mut bytes = BytesMut::new();
while let Some(item) = body.next().await {
bytes.extend_from_slice(&item.unwrap());
}
let result: T = serde_json::from_slice(&bytes)
.unwrap_or_else(|_| panic!("map_body failed during deserialization"));
return result;
}
My buddy #stephaneyfx helped me refactor this into something nicer.
#[async_trait(?Send)]
pub trait ParseResponse {
/// Extracts a response body of type T from `input`
/// Example:
///
/// let result: MyContractType = resp.parse().await.unwrap();
async fn parse<T: DeserializeOwned>(&mut self) -> Result<T, Box<dyn std::error::Error>>;
}
#[async_trait(?Send)]
impl ParseResponse for ServiceResponse<Body> {
async fn parse<T>(&mut self) -> Result<T, Box<dyn std::error::Error>>
where
T: DeserializeOwned,
{
let bytes = self.take_body().try_fold(Vec::new(), |mut acc, chunk| async {
acc.extend(chunk);
Ok(acc)
});
Ok(serde_json::from_slice(&bytes.await?)?)
}
}