actix_web How to get the response body from awc::client::ClientResponse - rust

I am trying to get the response out of the awc::client::ClientResponse. But always get the empty string. If I use the same request with curl I get the response.
Here is what I am trying This is a actix_web service which accepts the json request and call other service to get the response. and then sends it back to the client.
async fn get_info(info: actix_web::web::Json<Info>) -> impl Responder {
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_verify(SslVerifyMode::NONE);
let myconnector = builder.build();
let client = Client::builder()
.connector(Connector::new().ssl(myconnector).finish())
.finish();
let fund_card_req:FundingCardRequest = FundingCardRequest::new(vec![Accounts::new("45135612".to_string(), 666666)],
String::from("INCOMING"),
String::from("ACH"), 1095434517);
println!("{:?}", serde_json::to_string(&fund_card_req));
let mut response = client.post("https://mn.sit.jq.com/fundingcard-activity")
.timeout(core::time::Duration::from_secs(10))
.header("Content-Type", "application/json")
.send_json(&fund_card_req).await.unwrap();
match response.status(){
StatusCode::OK => {
println!("status: success");
let k = response.body().limit(20_000_000).await.unwrap();
println!("{:?}", str::from_utf8(&k));
},
_ => println!("status: failed"),
}
HttpResponse::Ok().body("hello world")
}
I have gone through most of the examples client examples on rust
Following is my setup.
actix-web = {version ="3.3.2", features = ["openssl"]}
actix-service = "1.0.1"
openssl = "0.10.29"

Related

How to return Error Response to a user from a middleware

I am new to Rust and the Actix web framework. I have been trying to create a simple server in actix web. I have implemented the auth system but I am trying to create a middleware for verifying the JWT token and extract the claims from the jwt to be used in an handler.
impl<S, B> Service<ServiceRequest> for JwtVerifierMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
let authorization = req.headers().get("Authorization");
let mut extensions = req.extensions_mut();
let fut = self.service.call(req);
let token = if let Some(authorization) = authorization {
authorization.to_str().unwrap()
} else {
return Box::pin(async {
let res = fut.await?;
Ok(res)
});
};
let claims = verify(token).unwrap();
extensions.insert(claims);
Box::pin(async move {
let res = fut.await?;
Ok(res)
})
}
}
I have the code above where I am trying to extract the token from the request and return an error response if the token is a None variant.The issue I have currently is that self.service.call() wants to move the req but it has already been borrowed here req.headers().get("Authorization"). I am not sure how to approach this.
I have tried to clone the req but it seems ServiceRequest does not implement the clone trait. I have also tried to pass a reference to the ServiceRequest as argument to the call function but I get an error that method call has an incompatible type for trait Service

Why do I get the error unsupported_grant_type when trying to make a POST request to Spotify API when trying to get a token?

Trying to fetch a token from the Spotify API using reqwest. Keep getting this error from the response:
{"error":"unsupported_grant_type","error_description":"grant_type parameter is missing"}
Tried multiple solutions: adding grant_type as parameters, stringifying
use std::collections::HashMap;
use reqwest::header;
use reqwest::header::HeaderValue;
use reqwest::{Body, Client};
use crate::header::{AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE};
const CLIENT_ID: &str = "<CLIENT_ID>";
const CLIENT_SECRET: &str = "<CLIENT_SECRET>";
async fn get_token() -> Result<String, Box<dyn std::error::Error>> {
let mut encoded = CLIENT_ID.to_string();
encoded.push_str(":");
encoded.push_str(&CLIENT_SECRET);
encoded = base64::encode(encoded);
let mut header_construct = "Basic ".to_string();
header_construct.push_str(&encoded);
let header_value = header_construct.as_str();
println!("{}", header_value);
let mut headers = header::HeaderMap::new();
headers.insert(AUTHORIZATION, header_value.parse().unwrap());
headers.insert(CONTENT_LENGTH, HeaderValue::from(0));
headers.insert(
CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded"),
);
let grant_string = "grant_type=client_credentials".to_string();
println!("{}", grant_string);
let mut params = HashMap::new();
params.insert("grant_type", "client_credentials");
let client = Client::new();
let body = client
.post("https://accounts.spotify.com/api/token")
.body(Body::from(grant_string))
.headers(headers)
.send()
.await?
.text()
.await?;
Ok(body)
}
#[tokio::main]
async fn main() {
let fact = get_token().await;
println!("fact = {:#?}", fact);
}
The issue is here:
headers.insert(CONTENT_LENGTH, HeaderValue::from(0));
This is setting the header Content-Length: 0 on the post request, which
indicates that the request has no body content. However, we do want it to have
body content, namely the form-encoded grant_type parameter. To fix this, don't
set the Content-Length header at all, as reqwest will do it for us
automatically based on the body. Additionally, using .form() to add the
request parameters will cause reqwest to add Content-Type: application/x-www-form-urlencoded automatically. With this in mind, here's a
simplified get_token():
async fn get_token() -> Result<String, Box<dyn std::error::Error>> {
let auth = format!("Basic {}", base64::encode(format!("{}:{}", CLIENT_ID, CLIENT_SECRET)));
let params = [("grant_type", "client_credentials")];
let client = reqwest::Client::new();
let body = client
.post("https://accounts.spotify.com/api/token")
.header("Authorization", &auth)
.form(&params)
.send()
.await?
.text()
.await?;
Ok(body)
}

How to POST a file using reqwest?

The documentation for reqwest v0.9.18 shows the following example of posting a file:
let file = fs::File::open("from_a_file.txt")?;
let client = reqwest::Client::new();
let res = client.post("http://httpbin.org/post")
.body(file)
.send()?;
The latest documentation for reqwest v0.11 no longer includes this example, and trying to build it fails with the following error when calling body():
the trait `From<std::fs::File>` is not implemented for `Body`
What is the updated method for sending a file?
The specific example you're linking to, was prior to the reqwest crate using async. If you want to use that exact example, then instead of reqwest::Client, you need to use reqwest::blocking::Client. This also requires enabling the blocking feature.
To be clear, you can actually still find that example, it's just located in the docs for reqwest::blocking::RequestBuilder's body() method instead.
// reqwest = { version = "0.11", features = ["blocking"] }
use reqwest::blocking::Client;
use std::fs::File;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = File::open("from_a_file.txt")?;
let client = Client::new();
let res = client.post("http://httpbin.org/post")
.body(file)
.send()?;
Ok(())
}
Also check out reqwest's Form and RequestBuilder's multipart() method, as there for instance is a file() method.
If you do want to use async, then you can use FramedRead from the tokio-util crate. Along with the TryStreamExt trait, from the futures crate.
Just make sure to enable the stream feature for reqwest, and the codec feature for tokio-util.
// futures = "0.3"
use futures::stream::TryStreamExt;
// reqwest = { version = "0.11", features = ["stream"] }
use reqwest::{Body, Client};
// tokio = { version = "1.0", features = ["full"] }
use tokio::fs::File;
// tokio-util = { version = "0.6", features = ["codec"] }
use tokio_util::codec::{BytesCodec, FramedRead};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = File::open("from_a_file.txt").await?;
let client = reqwest::Client::new();
let res = client
.post("http://httpbin.org/post")
.body(file_to_body(file))
.send()
.await?;
Ok(())
}
fn file_to_body(file: File) -> Body {
let stream = FramedRead::new(file, BytesCodec::new());
let body = Body::wrap_stream(stream);
body
}
If you want to use multipart/form-data and you are using Tokio
already, this approach could help you.
1. Setup Dependencies
# Cargo.toml
[dependencies]
tokio = { version = "1.19", features = ["macros", "rt-multi-thread"] }
reqwest = { version = "0.11.11", features = ["stream","multipart","json"] }
tokio-util = { version = "0.7.3", features = ["codec"] }
2. Upload file using multipart/form-data
use reqwest::{multipart, Body, Client};
use tokio::fs::File;
use tokio_util::codec::{BytesCodec, FramedRead};
async fn reqwest_multipart_form(url: &str) -> anyhow::Result<String> {
let client = Client::new();
let file = File::open(".gitignore").await?;
// read file body stream
let stream = FramedRead::new(file, BytesCodec::new());
let file_body = Body::wrap_stream(stream);
//make form part of file
let some_file = multipart::Part::stream(file_body)
.file_name("gitignore.txt")
.mime_str("text/plain")?;
//create the multipart form
let form = multipart::Form::new()
.text("username", "seanmonstar")
.text("password", "secret")
.part("file", some_file);
//send request
let response = client.post(url).multipart(form).send().await?;
let result = response.text().await?;
Ok(result)
}
3. Unit Testing
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_post_form_file() {
let url = "http://httpbin.org/post?a=1&b=true";
let get_json = reqwest_multipart_form(url).await.unwrap();
println!("users: {:#?}", get_json);
}
}
the crate streamer can do that for you with feature hyper enabled:
use hyper::{Body, Request}:
let file = File::open("from_a_file.txt").unwrap();
let mut streaming = Streamer::new(file)
// optional, set the field name
// streaming.meta.set_name("txt");
// optional, set the file name
streaming.meta.set_filename("from_a_file.txt");
// length sent as a chunk, the default is 64kB if not set
streaming.meta.set_buf_len(1024 * 1024);
let body: Body = streaming.streaming();
// build a request
let request: Request<Body> = Request::post("<uri-here>").body(body).expect("failed to build a request");
streamer will stream your file in 1 Mega-bytes chunks

Request body is empty while making post request from "reqwest" crate rust

I am trying to make post request using reqwest crate of rust. Here is the snippet of cargo.toml
[dependencies]
tokio = { version = "0.2", features = ["full"] }
reqwest = { version = "0.10", features = ["json"] }
Here is the snippet of code from which I am making request to simple server.
use reqwest::{Response, StatusCode};
use serde_json::{json, Value};
#[tokio::main]
async fn main() -> Result< (), reqwest::Error> {
let map1 = json!({
"abc":"abc",
"efg":"efg"
});
let body: Value = reqwest::Client::new()
.post("http://localhost:4000/hello")
.json(&map1)
.send()
.await?
.json()
.await?;
println!("Data is : {:?}", body);
Ok(())
}
The snippet of the code written using simple server crate from which I am serving this request is below:
use simple_server::{Server, StatusCode};
fn main() {
let server = Server::new(|req, mut response| {
println!(
"Request received {} {} {:?}",
req.method(),
req.uri(),
&req.body()
);
match (req.method().as_str(), req.uri().path()) {
("GET", "/") => Ok(response.body(format!("Get request").into_bytes())?),
("POST", "/hello") => Ok(response.body(format!("Post request").into_bytes())?),
(_, _) => {
response.status(StatusCode::NOT_FOUND);
Ok(response.body(String::from("Not Found").into_bytes())?)
}
}
});
server.listen("127.0.0.1", "4000");
}
The output I get is :
Request received POST /hello []
The desired output is the vector array of bytes but I am receiving a empty vector array.
The solutions which I have already tried are:
Making a post request using Postman to the same server and it is working fine.
Making a post request using the same reqwest code to any other server such as hyper, actix, etc and it is working fine.
Sending a simple body as a body of post request (no JSON ). But the same issue occurs.
So I think the issue must be with this simple server crate.
Every worthy suggestion will be Encouraged.
use reqwest::{Response, StatusCode};
use serde_json::{json, Value};
#[tokio::main]
async fn main() -> Result<String> {
let map1 = json!({
"abc":"abc",
"efg":"efg"
});
let body: Value = reqwest::Client::new()
.post("http://localhost:4000/hello")
.json(&map1)
.send()
.await?
.json()
.await?;
println!("Data is : {:?}", body);
let resp = serde_json::to_string(&body).unwrap();
Ok(resp)
}

Copy body and headers from hyper HTTP request to a new request while inspecting the body

I would like to create a small Rust HTTP proxy using hyper which accepts requests, forwards them and dumps the request + body.
Based on this example, the proxy part works fine.
However, I can't simply copy & print the request body. My main problem is that the request body can't be simply copied into something like an Vec<u8>. I cannot deconstruct the request to read the body and then create it later since the deconstructed headers can't be added to a new request.
The following code shows my minimal HTTP proxy example:
extern crate futures;
extern crate hyper;
extern crate tokio_core;
use futures::{Future, Stream};
use hyper::{Body, Client, StatusCode};
use hyper::client::HttpConnector;
use hyper::header::{ContentLength, ContentType};
use hyper::server::{Http, Request, Response, Service};
use tokio_core::reactor::Core;
type HTTPClient = Client<HttpConnector, Body>;
struct Server {
client: HTTPClient,
}
impl Server {
pub fn new(client: HTTPClient) -> Server {
Server { client: client }
}
}
impl Service for Server {
type Request = Request;
type Response = Response;
type Error = hyper::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn call(&self, mut req: Request) -> Self::Future {
let req_uri_str = {
let uri = req.uri();
format!(
"http://localhost{}?{}",
uri.path(),
uri.query().unwrap_or_default()
)
};
req.set_uri(req_uri_str.parse().unwrap());
// Try to create a copy of the new request
/*
let (method, uri, version, headers, body) = req.deconstruct();
let mut req_copy: Request<hyper::Body> = Request::new(method, uri);
// Main problem: How can the request body be copied?
// >>> let body_bytes: Vec<u8> = ...
req_copy.set_body(body);
req_copy.set_version(version);
// Try to copy the headers
for header in headers.iter() {
req_copy.headers_mut().set(header.value().unwrap());
}
*/
// This works if the request is not deconstructed
let work = self.client
.request(req)
.and_then(|res| futures::future::ok(res))
.or_else(|err| {
let body = format!("{}\n", err);
futures::future::ok(
Response::new()
.with_status(StatusCode::BadRequest)
.with_header(ContentType::plaintext())
.with_header(ContentLength(body.len() as u64))
.with_body(body),
)
});
Box::new(work)
}
}
fn main() {
// Create HTTP client core + handles
let mut core = Core::new().unwrap();
let handle = core.handle();
let handle_clone = handle.clone();
// Create HTTP server
let server_addr = "127.0.0.1:9999".parse().unwrap();
let server = Http::new()
.serve_addr_handle(&server_addr, &handle, move || {
Ok(Server::new(Client::new(&handle_clone)))
})
.unwrap();
// Connect HTTP client with server
let handle_clone2 = handle.clone();
handle.spawn(
server
.for_each(move |conn| {
handle_clone2.spawn(conn.map(|_| ()).map_err(|err| println!("Error: {:?}", err)));
Ok(())
})
.map_err(|_| ()),
);
core.run(futures::future::empty::<(), ()>()).unwrap();
}
Running this works fine, if you have any HTTP service running on Port 80, connecting with a browser to port 9999 will forward any responses and requests perfectly.
However, if you re-enable the lines regarding the construction of a new, copied request, my approach fails since I don't understand how to copy the headers. (Furthermore, this doesn't really help me when it comes to copying the request body)
I'm aware that there are similar questions here, but none of them match my requirement to re-use the request body after looking at it (or don't have answers at all).
the request body can't be simply copied into something like an Vec<u8>
Sure it can. In the Rust standard library, it's worth memorizing the capabilities of the Iterator trait. When dealing with futures, you should also memorize the capabilities of Future and Stream.
For example, hyper's Body implements Stream. This means you can use the Stream::concat2 method:
Concatenate all results of a stream into a single extendable destination, returning a future representing the end result.
This creates one large Chunk which can be converted to a Vec:
extern crate hyper; // 0.11.22
extern crate futures; // 0.1.18
use futures::{Future, Stream};
fn example(req: hyper::Request) {
req.body().concat2().map(|chunk| {
let body = chunk.to_vec();
println!("{:?}", body);
()
});
// Use this future somehow!
}
Likewise, a Vec<u8> can be converted back into a Body.
since the deconstructed headers can't be added to a new request.
req_copy.headers_mut().extend(headers.iter());
All together:
fn create_localhost_request(req: Request) -> (Request, Body) {
let (method, uri, version, headers, body) = req.deconstruct();
let req_uri_str = {
format!(
"http://localhost{}?{}",
uri.path(),
uri.query().unwrap_or_default()
)
};
let uri = req_uri_str.parse().unwrap();
let mut req_copy = Request::new(method, uri);
req_copy.set_version(version);
req_copy.headers_mut().extend(headers.iter());
(req_copy, body)
}
fn perform_proxy_request(
client: HttpClient,
req: Request,
) -> Box<Future<Item = Response, Error = hyper::Error>> {
Box::new(client.request(req).or_else(|err| {
let body = format!("{}\n", err);
Ok(Response::new()
.with_status(StatusCode::BadRequest)
.with_header(ContentType::plaintext())
.with_header(ContentLength(body.len() as u64))
.with_body(body))
}))
}
impl Service for Server {
type Request = Request;
type Response = Response;
type Error = hyper::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn call(&self, req: Request) -> Self::Future {
let (mut req, body) = create_localhost_request(req);
let client = self.client.clone();
let work = body
.concat2()
.map(|chunk| chunk.to_vec())
// Do whatever we need with the body here, but be careful
// about doing any synchronous work.
.map(move |body| {
req.set_body(body);
req
})
.and_then(|req| perform_proxy_request(client, req));
Box::new(work)
}
}

Resources