I'm trying to enable the TLS feature to Open an encrypted AMQP connection on a stream.
In the amiquip crate docs, there's an example https://docs.rs/amiquip/0.3.0/amiquip/struct.Connection.html#examples
I copied the code example and implement the tcp_stream function requested as simple as possible to just return the mio::net::TcpStream instance as said.
use amiquip::{Auth, Connection, ConnectionOptions, ConnectionTuning, Result};
use mio::net::TcpStream;
use std::net::SocketAddr;
use std::{io::Read, time::Duration};
// Examples below assume a helper function to open a TcpStream from an address string with
// a signature like this is available:
fn tcp_stream() -> mio::net::TcpStream {
let address: SocketAddr = "127.0.0.0:5671".parse().unwrap();
mio::net::TcpStream::connect(address).unwrap()
}
fn main() -> Result<()> {
// Empty amqp URL is equivalent to default options; handy for initial debugging and
// development.
// let temp = Connection::open_tls_stream(connector, domain, stream, options, tuning)
let conn1 = Connection::insecure_open("amqp://")?;
let conn1 = Connection::insecure_open_stream(
tcp_stream(),
ConnectionOptions::<Auth>::default(),
ConnectionTuning::default(),
)?;
// All possible options specified in the URL except auth_mechanism=external (which would
// override the username and password).
let conn3 = Connection::insecure_open(
"amqp://user:pass#example.com:12345/myvhost?heartbeat=30&channel_max=1024&connection_timeout=10000",
)?;
let conn3 = Connection::insecure_open_stream(
tcp_stream(),
ConnectionOptions::default()
.auth(Auth::Plain {
username: "user".to_string(),
password: "pass".to_string(),
})
.heartbeat(30)
.channel_max(1024)
.connection_timeout(Some(Duration::from_millis(10_000))),
ConnectionTuning::default(),
)?;
Ok(())
}
However, I get compilation error (on every version of the crate) as following
error[E0277]: the trait bound `mio::net::TcpStream: IoStream` is not satisfied
--> src\main.rs:28:17
|
28 | let conn3 = Connection::insecure_open_stream(
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IoStream` is not implemented for `mio::net::TcpStream`
|
::: C:\Users\Tamir Cohen\.cargo\registry\src\github.com-1ecc6299db9ec823\amiquip-0.3.3\src\connection.rs:283:48
|
283 | pub fn insecure_open_stream<Auth: Sasl, S: IoStream>(
| -------- required by this bound in `Connection::insecure_open_stream`
and I just can't figure out what went wrong.
Which version of mio do you use?
You should use mio version 0.6, like the crate amiquip.
Cargo file:
[package]
name = "message-broker"
version = "0.1.0"
edition = "2018"
[dependencies]
amiquip = "0.4.0"
mio = { version = "0.6.23" }
native-tls = "0.2.8"
Code with tls:
use amiquip::{Auth, Connection, ConnectionOptions, ConnectionTuning, Exchange, Publish, Result};
use mio::net::TcpStream;
use native_tls::TlsConnector;
use std::time::Duration;
fn tcp_stream(addr: &str) -> Result<TcpStream> {
Ok(TcpStream::connect(&addr.parse().unwrap()).unwrap())
}
fn main() -> Result<()> {
let mut connection = Connection::open_tls_stream(
TlsConnector::new().unwrap(),
"domain",
tcp_stream("127.0.0.0:5671")?,
ConnectionOptions::default()
.auth(Auth::Plain {
username: "user".to_string(),
password: "pass".to_string(),
})
.heartbeat(30)
.channel_max(1024)
.connection_timeout(Some(Duration::from_millis(10_000))),
ConnectionTuning::default(),
)?;
let channel = connection.open_channel(None)?;
let exchange = Exchange::direct(&channel);
exchange.publish(Publish::new("hello there".as_bytes(), "hello"))?;
connection.close()
}
Related
I'm using teloxide a bot that coordinates information between players of a game. As a simplified example, I want player A to mark task_1 as complete, and then I want player B to be able to ask the bot if task_1 has been completed or not and for the bot to respond appropriately.
So far I've tried the in-memory storage and the Redis storage, and both seem to be storing independent states. For example Player A can mark a task as complete, but when player B asks about the state of that task, it remains incomplete.
I haven't got sqlite setup on my machine so haven't tried it yet, but I wouldn't imagine it's setup differently to redis(?).
I could theoretically write the bot's state to a file on disk and then update the internal state every time, but that seems very round about for something I'm hoping teloxide already has built in?
Is there any way I can have multiple users mutating the same internal state of the bot?
Here's the code:
Here's the code I'm using. This is lightly adjusted from the db_remember.rs example so that I can get a number, reset the number, and set a number to something new using \set XXX
src/main.rs:
use teloxide::{
dispatching::dialogue::{
serializer::Bincode,
ErasedStorage, RedisStorage, Storage,
},
prelude::*,
utils::command::BotCommands,
};
use serde::{self, Deserialize, Serialize};
type MyDialogue = Dialogue<State, ErasedStorage<State>>;
type MyStorage = std::sync::Arc<ErasedStorage<State>>;
type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
#[derive(Clone, Default, Serialize, Deserialize)]
pub enum State {
#[default]
Start,
GotNumber(i32),
}
#[derive(Clone, BotCommands)]
#[command(rename_rule = "lowercase", description = "These commands are supported:")]
pub enum Command {
#[command(description = "set your number.")]
Set { num: i32 },
#[command(description = "get your number.")]
Get,
#[command(description = "reset your number.")]
Reset,
}
#[tokio::main]
async fn main() {
pretty_env_logger::init();
log::info!("Starting DB remember bot...");
let bot = Bot::from_env();
let storage: MyStorage = RedisStorage::open("redis://127.0.0.1:6379", Bincode).await.unwrap().erase();
let handler = Update::filter_message()
.enter_dialogue::<Message, ErasedStorage<State>, State>()
.branch(dptree::case![State::Start].endpoint(start))
.branch(
dptree::case![State::GotNumber(n)]
.branch(dptree::entry().filter_command::<Command>().endpoint(got_number))
.branch(dptree::endpoint(invalid_command)),
);
Dispatcher::builder(bot, handler)
.dependencies(dptree::deps![storage])
.enable_ctrlc_handler()
.build()
.dispatch()
.await;
}
async fn start(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
match msg.text().map(|text| text.parse::<i32>()) {
Some(Ok(n)) => {
log::info!("[{:?}] Number set to {n}", msg.chat.username());
dialogue.update(State::GotNumber(n)).await?;
bot.send_message(
msg.chat.id,
format!("Remembered number {n}. Now use /get or /reset."),
)
.await?;
}
_ => {
bot.send_message(msg.chat.id, "Please, send me a number.").await?;
}
}
Ok(())
}
async fn got_number(
bot: Bot,
dialogue: MyDialogue,
num: i32, // Available from `State::GotNumber`.
msg: Message,
cmd: Command,
) -> HandlerResult {
let old_num = num;
match cmd {
Command::Set { num } => {
log::info!("[{:?}] Number changed from {} to {}", msg.chat.username(), old_num, num);
dialogue.update(State::GotNumber(num)).await?;
bot.send_message(msg.chat.id, format!("Set your number from {} to {}", old_num, num)).await?;
}
Command::Get => {
bot.send_message(msg.chat.id, format!("Here is your number: {num}.")).await?;
}
Command::Reset => {
dialogue.reset().await?;
bot.send_message(msg.chat.id, "Number reset.").await?;
}
}
Ok(())
}
async fn invalid_command(bot: Bot, msg: Message) -> HandlerResult {
bot.send_message(msg.chat.id, "Please, send /get or /reset.").await?;
Ok(())
}
Cargo.toml:
[package]
name = "tmp"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
teloxide = { version = "0.12", features = ["macros", "redis-storage", "bincode-serializer"] }
log = "0.4"
pretty_env_logger = "0.4"
tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] }
serde_yaml = "0.9.17"
serde = "1.0.152"
This was answered by WaffleLapkin on the Teloxide github discussion page:
The dialogue storages in teloxide store state per chat, if you need something else, you'll need to work with databases directly.
So I ended up using a Redis database to store the game state, and regular in-memory for storing teloxide's state. Something like:
// Cargo.toml: redis = { version = "*" }
extern crate redis;
use redis::{Commands, Connection};
/// Establish a connection to the redis database,
/// used for sharing Game state across
/// different instances of the bot
fn connect_to_redis() -> Result<Connection, Box<dyn std::error::Error + Sync + Send>> {
let client = redis::Client::open("redis://127.0.0.1/")?;
let conn = client.get_connection()?;
// Cargo.toml: log = "0.4"
log::info!("Redis connection established");
Ok(conn)
}
But then you also can't directly store a rust struct into redis, so I used serde_yaml to serialise the game state:
fn set_game_state(
conn: &mut Connection,
game: Game,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let game_key = "game_state";
conn.set::<&str, String, ()>(game_key, serde_yaml::to_string(&game)?)?;
Ok(())
}
And then stored it all as one long string in a single key in the DB.
To retrieve the game state is a similar operation, but I also added in some logic to create the game from an existing "template" YAML file game.yaml if the key doesn't exist:
fn get_game_state(conn: &mut Connection) -> Result<Game, Box<dyn Error + Send + Sync>> {
let game_state_key = "game_state";
let game_state: Game = if conn.exists(game_state_key)? {
log::debug!("Getting game state");
conn.get::<&str, String>(game_state_key)
.and_then(|game_state| Ok(serde_yaml::from_str::<Game>(&game_state).unwrap().into()))?
} else {
log::debug!("Creating game state");
let s = Game::new("challenges.yaml")?;
let serialised = serde_yaml::to_string(&s)?;
conn.set::<&str, String, ()>(game_state_key, serialised)?;
s
};
Ok(game_state)
}
A bit overkill, but it works.
I’m trying to write a client to communicate with my grpc server written with tonic using Rust, but I’m having trouble understanding where to define and connect to the client, thus getting errors with my import statement. I’ve followed several tutorials and am having trouble finding information on how to create and import a client. My error is:
error[E0432]: unresolved import `user::client`
--> user/src/client.rs:2:36
|
2 | use user::client::{UserClient};
| ^^^^^^ could not find `client` in `user`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0432`.
error: could not compile `user`
In my proto.rs file:
syntax = "proto3";
package user;
message CreateUser {
string name = 1;
}
[package]
name = "user"
version = "0.1.0"
edition = "2018"
[lib]
[[bin]]
name = "server"
path = "src/server.rs"
[[bin]]
name = "client"
path = "src/client.rs"
[dependencies]
tonic = "0.5"
prost = "0.8"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
[build-dependencies]
tonic-build = "0.5"
My lib.rs file:
pub mod user {
tonic::include_proto!("user");
}
pub mod server;
pub mod client{
tonic::include_proto!("user");
}
main.rs:
use user::server;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing::info!(message = "Started user server");
server::start_server().await?;
Ok(())
}
client.rs:
use user::{UserRequest };
use user::client::{UserClient}; // what should this import be, where does the client et created?
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = UserClient::connect("http://[::1]:50051").await?; // how do I create UserClient
let request = tonic::Request::new(UserRequest {
id: "1".into(),
});
println!("Sending request to gRPC Server...");
let response = client.create_user(request).await?;
println!("RESPONSE={:?}", response);
Ok(())
}
For reference, I’m following:
https://tjtelan.com/blog/lets-build-a-single-binary-grpc-server-client-with-rust-in-2020/
https://blog.logrocket.com/rust-and-grpc-a-complete-guide/
https://dev.to/anshulgoyal15/a-beginners-guide-to-grpc-with-rust-3c7o
I want to use the async function to parse the inbound stream progressively, but actix-web requires impl Future<Item = HttpResponse, Error = Error> as the return value.
How can I convert the future returned by async function to what actix-web requires?
I'm using Rust 1.39 nightly and actix-web 1.0.7.
http_srv.rs :
use futures::compat::Stream01CompatExt;
use futures::future::{FutureExt, TryFutureExt};
use futures::stream::TryStreamExt;
use futures01::future::Future;
use futures01::stream::Stream;
use futures01::sync::mpsc; // for `try_next`
use actix_web::*;
use bytes::Bytes;
use futures_timer::Delay;
use std::time::Duration;
fn inbound(
req: HttpRequest,
stream: web::Payload,
) -> impl Future<Item = HttpResponse, Error = Error> {
let fut = async_inbound(&req, &stream);
fut.unit_error().boxed_local().compat() // <--- compliation error here.
}
async fn async_inbound(req: &HttpRequest, stream: &web::Payload) -> HttpResponse {
let mut compat_stream = stream.compat();
loop {
let result = compat_stream.try_next().await;
if let Err(e) = result {
warn!("Failed to read stream from {} : {}", req.path(), e);
break;
}
if let Ok(option) = result {
match option {
None => {
info!("Request ends");
break;
}
Some(data) => {
println!("{:?}", data);
}
}
}
}
HttpResponse::Ok().content_type("text/html").body("RESP")
}
pub fn start(port: u16) {
info!("Starting HTTP server listening at port {} ...", port);
let _ = HttpServer::new(|| {
App::new()
.wrap(middleware::DefaultHeaders::new().header(http::header::CACHE_CONTROL, "no-cache"))
.wrap(middleware::Logger::default())
.service(web::resource("/").route(web::put().to_async(inbound)))
})
.bind(format!("0.0.0.0:{}", port))
.expect(&format!("Unable to bind on port {}", port))
.run()
.expect("Failed to start HTTP server");
}
Cargo.toml:
dependencies]
log = "0.4.8"
env_logger = "0.6.2"
chrono = "0.4.8"
actix = "0.8.3"
bytes = "0.4.12"
actix-utils = "0.4.5"
futures-timer = "0.3"
futures01 = { package = "futures", version = "0.1", optional = false }
[dependencies.actix-web]
version = "1.0.7"
features = ["ssl"]
# https://rust-lang-nursery.github.io/futures-rs/blog/2019/04/18/compatibility-layer.html
# Rust’s futures ecosystem is currently split in two:
# On the one hand we have the vibrant ecosystem built around futures#0.1 with its many libraries working on stable Rust
# and on the other hand there’s std::future ecosystem with support for the ergonomic and powerful async/await language feature.
# To bridge the gap between these two worlds we have introduced a compatibility layer as part of the futures#0.3 extension to std::future.
[dependencies.futures-preview]
version = "0.3.0-alpha.18"
default-features = false
features = ["compat", "async-await", "nightly"]
Compilation Error:
error[E0271]: type mismatch resolving `<std::pin::Pin<std::boxed::Box<dyn core::future::future::Future<Output = std::result::Result<actix_http::response::Response, ()>>>> as core::future::future::Future>::Output == std::result::Result<_, actix_http::error::Error>`
--> src/http_server.rs:39:55
|
39 | fn inbound(req: HttpRequest, stream: web::Payload) -> impl Future<Item=HttpResponse, Error=Error> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected (), found struct `actix_http::error::Error`
|
= note: expected type `std::result::Result<actix_http::response::Response, ()>`
found type `std::result::Result<_, actix_http::error::Error>`
= note: required because of the requirements on the impl of `futures_core::future::TryFuture` for `std::pin::Pin<std::boxed::Box<dyn core::future::future::Future<Output = std::result::Result<actix_http::response::Response, ()>>>>`
= note: the return type of a function must have a statically known size
std::future -> future#0.1 conversion steps:
The future needs to be TryFuture (Output = Result<T, E>)
The future needs to be Unpin (you can use boxed combinator)
Finally, you can call the compat combinator
Your inbound function:
fn inbound(
req: HttpRequest,
stream: web::Payload,
) -> impl Future<Item = HttpResponse, Error = Error> {
let fut = async_inbound(&req, &stream);
fut.unit_error().boxed_local().compat()
}
The inbound function signature is fine, but the conversion isn't.
The async_inbound function isn't TryFuture (because of -> HttpResponse). You're trying to convert it with the unit_error combinator, but the result is Result<HttpResponse, ()> and you want Result<HttpResponse, Error>. Fixed inbound function:
fn inbound(
req: HttpRequest,
stream: web::Payload,
) -> impl Future<Item = HttpResponse, Error = Error> {
let fut = async_inbound(req, stream);
fut.boxed_local().compat()
}
Your async_inbound function:
async fn async_inbound(req: &HttpRequest, stream: &web::Payload) -> HttpResponse {
// ...
}
The first issue here is to replace -> HttpResponse with -> Result<HttpResponse>. Another problem is that you're passing reg and stream by reference. Move them as there's no need to take a reference and you'll need 'static. Fixed async_inbound function:
async fn async_inbound(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse> {
let mut compat_stream = stream.compat();
while let Some(data) = compat_stream.try_next().await? {
println!("{:?}", data);
}
Ok(HttpResponse::Ok().content_type("text/html").body("RESP"))
}
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());
});
}
I'm trying to use a closure in a thread but after 2 hours of trying I can't seem to find how. Here's the file discord_async.rs:
use discord::*;
use discord::model::Event;
use std::sync::Arc;
use shared_mutex::SharedMutex;
use std::thread;
pub struct DiscordAsync {
client: Arc<SharedMutex<Discord>>
}
impl DiscordAsync {
pub fn new(bot_token: &str) -> DiscordAsync {
let client = Arc::new(SharedMutex::new(Discord::from_bot_token(bot_token).unwrap()));
DiscordAsync {
client: client
}
}
pub fn start<F>(&self, mut event_handle: F) -> () where F: FnMut(Arc<Event>, Arc<SharedMutex<Discord>>) + Send + 'static {
let (mut connection, _) = self.client.read().unwrap().connect().unwrap();
let event_handle = Arc::new(SharedMutex::new(event_handle));
loop {
let event = Arc::new(connection.recv_event().unwrap());
let event_handle = event_handle.read().unwrap();
// Start a thread so we don't block shit
thread::spawn(move || {
// Match event to actions we want to handle
event_handle(event.clone(), self.client);
});
}
}
}
I use it like this in main.rs:
extern crate colored;
extern crate discord;
extern crate shared_mutex;
mod discord_async;
use std::thread;
use colored::*;
use discord::*;
use discord::model::{Event, Channel, ServerId};
use discord_async::DiscordAsync;
fn main() {
// Server info
let bot_token = "...";
let server_id = ServerId(12345);
let dis_async = DiscordAsync::new(bot_token);
dis_async.start(|event, _| {
println!("{:?}", event);
});
}
Compiler message:
Compiling bottest1 v0.1.0 (file:///home/kindlyfire/Documents/dev/rust/bottest1)
error[E0477]: the type `[closure#src/discord_async.rs:29:18: 33:5 event_handle:shared_mutex::SharedMutexReadGuard<'_, F>, event:std::sync::Arc<discord::model::Event>, self:&discord_async::DiscordAsync]` does not fulfill the required lifetime
--> src/discord_async.rs:29:4
|
29 | thread::spawn(move || {
| ^^^^^^^^^^^^^
|
= note: type must outlive the static lifetime
And my Cargo.toml:
[package]
name = "bottest1"
version = "0.1.0"
authors = ["kindlyfire"]
[dependencies]
discord = "0.7.0"
colored = "1.4"
shared-mutex = "0.2"
I've looked at a lot of different ways to do this, including on SO, but I can't find any that work.
You lock the mutex and then try to move the locked object into the thread. That's the wrong way around. You need to clone the Arc and move that into the thread.
Edit: I haven't tested this, but something like this should work:
pub fn start<F>(&self, mut event_handle: F) -> ()
where F: FnMut(Arc<Event>, Arc<SharedMutex<Discord>>) + Send + 'static
{
let (mut connection, _) = self.client.read().unwrap().connect().unwrap();
let event_handle = Arc::new(SharedMutex::new(event_handle));
loop {
let event = Arc::new(connection.recv_event().unwrap());
let event_handle = event_handle.clone();
let client = self.client.clone();
// Start a thread so we don't block shit
thread::spawn(move || {
// Match event to actions we want to handle
event_handle.read().unwrap()(event, client);
});
}
}
Note that we create clones of the Arcs outside the lambda, and then use them inside. Except for event, which we don't clone, because we're fine with moving the one pointer we have. This moves the clones into the lambda. You could probably get rid of the Arc around Event, though. You don't have any other pointers to it, don't need to keep it alive, and it has to be Send anyway (I think &T is only Sync if T is Send).
If this doesn't work, please update with the new compiler error.
As a side note on terminology, a "handle" is an object that refers to some resource. A function that deals with events is a "handler".