Request body is empty while making post request from "reqwest" crate rust - 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)
}

Related

How to send a file to Discord webhook in Rust?

I tried to send a file to the Discord webhook I've read Webhook Resource and Uploading Files but I still can't get it to work. Here is what wrote based on the API document:
use anyhow::Result;
use reqwest::multipart;
#[tokio::main]
async fn main() -> Result<()> {
let client = reqwest::Client::new();
let part = multipart::Part::bytes(image.as_slice())
.file_name("text.png")
.mime_str("image/png")?;
let form = multipart::Form::new()
.text("Content-Disposition", "form-data")
.part("files[0]", part);
let res = client
.post("https://discord.com/api/webhooks/6969696969/XD_XD_XD_XD_XD_XD_XD_XD")
.header("Content-Type", "multipart/form-data")
.query(&[("wait", "true")])
.multipart(form)
.send()
.await?;
println!("Response: {:#?}", res);
Ok(())
}
The response status is 400 and I found out it means "Bad Request". How can I send a file?
I was able to do this with a crate called serenity
use anyhow::Result; // You don't really need to use anyhow Result you can just use Result<(), Box<dyn Error>>
use serenity::{http::Http, model::webhook::Webhook};
#[tokio::main]
async fn main() -> Result<()> {
let http = Http::new("token");
let url = "https://discord.com/api/webhooks/{id}/{token}";
let webhook = Webhook::from_url(&http, url).await?;
webhook
.execute(&http, true, |w| {
w.content("Test")
.username("Test")
.add_file("/path/to/file")
})
.await?;
Ok(())
}

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

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"

Grabbing a response header value with reqwest in rust

Ive mainly been experimenting with the reqwest module over the past few days to see what i can accomplish, but i came over a certain problem which im not able to resolve. Im trying to retrieve the a response headers value after doing a post request. The code in which i tried is
extern crate reqwest;
fn main() {
let client = reqwest::Client::new();
let res = client
.post("https://google.com")
.header("testerheader", "test")
.send();
println!("Headers:\n{:#?}", res.headers().get("content-length").unwrap());
}
This code seems to return this error
error[E0599]: no method named `headers` found for opaque type `impl std::future::Future` in the current scope
The latest reqwest is async by default, so in your example res is a future, not the actual response. Either you need to await the response or use reqwest's blocking API.
async/await
In your Cargo.toml add tokio as a dependency.
[dependencies]
tokio = { version = "0.2.22", features = ["full"] }
reqwest = "0.10.8"
Use tokio as the async runtime and await the response.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let res = client
.post("https://google.com")
.header("testerheader", "test")
.send()
.await?;
println!(
"Headers:\n{:#?}",
res.headers().get("content-length").unwrap()
);
Ok(())
}
Blocking API
In your Cargo.toml enable the blocking feature.
[dependencies]
reqwest = { version = "0.10.8", features = ["blocking"] }
Now you can use the Client from the reqwest::blocking module.
fn main() {
let client = reqwest::blocking::Client::new();
let res = client
.post("https://google.com")
.header("testerheader", "test")
.send()
.unwrap();
println!(
"Headers:\n{:#?}",
res.headers().get("content-length").unwrap()
);
}

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)
}
}

Is there an easy way to get the system's external IP in Rust? [duplicate]

How can I make an HTTP request from Rust? I can't seem to find anything in the core library.
I don't need to parse the output, just make a request and check the HTTP response code.
Bonus marks if someone can show me how to URL encode the query parameters on my URL!
The easiest way to make HTTP requests in Rust is with the reqwest crate:
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let resp = reqwest::blocking::get("https://httpbin.org/ip")?.text()?;
println!("{:#?}", resp);
Ok(())
}
In Cargo.toml:
[dependencies]
reqwest = { version = "0.11", features = ["blocking"] }
Async
Reqwest also supports making asynchronous HTTP requests using Tokio:
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let resp = reqwest::get("https://httpbin.org/ip")
.await?
.text()
.await?;
println!("{:#?}", resp);
Ok(())
}
In Cargo.toml:
[dependencies]
reqwest = "0.11"
tokio = { version = "1", features = ["full"] }
Hyper
Reqwest is an easy to use wrapper around Hyper, which is a popular HTTP library for Rust. You can use it directly if you need more control over managing connections. A Hyper-based example is below and is largely inspired by an example in its documentation:
use hyper::{body::HttpBody as _, Client, Uri};
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let client = Client::new();
let res = client
.get(Uri::from_static("http://httpbin.org/ip"))
.await?;
println!("status: {}", res.status());
let buf = hyper::body::to_bytes(res).await?;
println!("body: {:?}", buf);
}
In Cargo.toml:
[dependencies]
hyper = { version = "0.14", features = ["full"] }
tokio = { version = "1", features = ["full"] }
Original answer (Rust 0.6)
I believe what you're looking for is in the standard library. now in rust-http and Chris Morgan's answer is the standard way in current Rust for the foreseeable future. I'm not sure how far I can take you (and hope I'm not taking you the wrong direction!), but you'll want something like:
// Rust 0.6 -- old code
extern mod std;
use std::net_ip;
use std::uv;
fn main() {
let iotask = uv::global_loop::get();
let result = net_ip::get_addr("www.duckduckgo.com", &iotask);
io::println(fmt!("%?", result));
}
As for encoding, there are some examples in the unit tests in src/libstd/net_url.rs.
Update: This answer refers to fairly ancient history. For the current best practices, please look at Isaac Aggrey's answer instead.
I've been working on rust-http, which has become the de facto HTTP library for Rust (Servo uses it); it's far from complete and very poorly documented at present. Here's an example of making a request and doing something with the status code:
extern mod http;
use http::client::RequestWriter;
use http::method::Get;
use http::status;
use std::os;
fn main() {
let request = RequestWriter::new(Get, FromStr::from_str(os::args()[1]).unwrap());
let response = match request.read_response() {
Ok(response) => response,
Err(_request) => unreachable!(), // Uncaught condition will have failed first
};
if response.status == status::Ok {
println!("Oh goodie, I got me a 200 OK response!");
} else {
println!("That URL ain't returning 200 OK, it returned {} instead", response.status);
}
}
Run this code with a URL as the sole command-line argument and it'll check the status code! (HTTP only; no HTTPS.)
Compare with src/examples/client/client.rs for an example that does a little more.
rust-http is tracking the master branch of rust. At present it'll work in the just-released Rust 0.8, but there are likely to be breaking changes soon. Actually, no version of rust-http works on Rust 0.8—there was a breaking change which can't be worked around in privacy rules just before the release, leaving something that rust-http depends on in extra::url inaccessible. This has since been fixed, but it leaves rust-http incompatible with Rust 0.8.
As for the query string encoding matter, at present that should be done with extra::url::Query (a typedef for ~[(~str, ~str)]). Appropriate functions for conversions:
extra::url::query_to_str
extra::url::query_from_str (sorry, can't use this just at present as it's private. PR to make it public about to come. In the mean time, this link actually shouldn't work, it's only available because of https://github.com/mozilla/rust/issues/7476.)
Using curl bindings. Stick this in your Cargo.toml:
[dependencies.curl]
git = "https://github.com/carllerche/curl-rust"
...and this in the src/main.rs:
extern crate curl;
use curl::http;
fn main(){
let resp = http::handle()
.post("http://localhost:3000/login", "username=dude&password=sikrit")
.exec().unwrap();
println!("code={}; headers={}; body={}",
resp.get_code(), resp.get_headers(), resp.get_body());
}
I prefer Crates with low dependency count, so I would recommend these:
MinReq (0 deps)
use minreq;
fn main() -> Result<(), minreq::Error> {
let o = minreq::get("https://speedtest.lax.hivelocity.net").send()?;
let s = o.as_str()?;
print!("{}", s);
Ok(())
}
HTTP_Req (35 deps)
use {http_req::error, http_req::request, std::io, std::io::Write};
fn main() -> Result<(), error::Error> {
let mut a = Vec::new();
request::get("https://speedtest.lax.hivelocity.net", &mut a)?;
io::stdout().write(&a)?;
Ok(())
}
To elaborate on Isaac Aggrey's answer, here's an example of making a POST request with query parameters using the reqwest library.
Cargo.toml
[package]
name = "play_async"
version = "0.1.0"
edition = "2018"
[dependencies]
reqwest = "0.10.4"
tokio = { version = "0.2.21", features = ["macros"] }
Code
use reqwest::Client;
type Error = Box<dyn std::error::Error>;
type Result<T, E = Error> = std::result::Result<T, E>;
async fn post_greeting() -> Result<()> {
let client = Client::new();
let req = client
// or use .post, etc.
.get("https://webhook.site/1dff66fd-07ff-4cb5-9a77-681efe863747")
.header("Accepts", "application/json")
.query(&[("hello", "1"), ("world", "ABCD")]);
let res = req.send().await?;
println!("{}", res.status());
let body = res.bytes().await?;
let v = body.to_vec();
let s = String::from_utf8_lossy(&v);
println!("response: {} ", s);
Ok(())
}
#[tokio::main]
async fn main() -> Result<()> {
post_greeting().await?;
Ok(())
}
Go to https://webhook.site and create your webhook link and change the code to match. You'll see the request was received on server in realtime.
This example was originally based on Bastian Gruber's example and has been updated for modern Rust syntax and newer crate versions.
Building upon Patrik Stas' answer, if you want to do an HTTP form URL-encoded POST, here is what you have to do. In this case, it's to get an OAuth client_credentials token.
Cargo.toml
[dependencies]
reqwest = "0.10.4"
tokio = { version = "0.2.21", features = ["macros"] }
Code
use reqwest::{Client, Method};
type Error = Box<dyn std::error::Error>;
type Result<T, E = Error> = std::result::Result<T, E>;
async fn print_access_token() -> Result<()> {
let client = Client::new();
let host = "login.microsoftonline.com";
let tenant = "TENANT";
let client_id = "CLIENT_ID";
let client_secret = "CLIENT_SECRET";
let scope = "https://graph.microsoft.com/.default";
let grant_type = "client_credentials";
let url_string = format!("https://{}/{}/oauth2/v2.0/token", host, tenant);
let body = format!(
"client_id={}&client_secret={}&scope={}&grant_type={}",
client_id, client_secret, scope, grant_type,
);
let req = client.request(Method::POST, &url_string).body(body);
let res = req.send().await?;
println!("{}", res.status());
let body = res.bytes().await?;
let v = body.to_vec();
let s = String::from_utf8_lossy(&v);
println!("response: {} ", s);
Ok(())
}
#[tokio::main]
async fn main() -> Result<()> {
print_access_token().await?;
Ok(())
}
This will print something like the following.
200 OK
response: {"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"ACCESS_TOKEN"}
Dropping a version here that uses the surf crate (dual to the tide crate):
let res = surf::get("https://httpbin.org/get").await?;
assert_eq!(res.status(), 200);
Using hyper "0.13"
Also using hyper-tls for HTTPS support.
File Cargo.toml
hyper = "0.13"
hyper-tls = "0.4.1"
tokio = { version = "0.2", features = ["full"] }
Code
extern crate hyper;
use hyper::Client;
use hyper::body::HttpBody as _;
use tokio::io::{stdout, AsyncWriteExt as _};
use hyper_tls::HttpsConnector;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// HTTP only
// let client = Client::new();
// http or https connections
let client = Client::builder().build::<_, hyper::Body>(HttpsConnector::new());
let mut resp = client.get("https://catfact.ninja/fact".parse()?).await?;
println!("Response: {}", resp.status());
while let Some(chunk) = resp.body_mut().data().await {
stdout().write_all(&chunk?).await?;
}
Ok(())
}
Adapted from https://hyper.rs/guides/client/basic/
Simple http request with this crate: wsd
fn test() {
wsd::http::get("https://docs.rs/", |data| {
println!("status = {}, data = {}", data.status(), data.text());
});
}

Resources