Creating a grpc client using tonic and rust - rust

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

Related

the trait `std::default::Default` is not implemented for `Result<BillBook, std::string::String>`

I want to add some error handler in the rust function, so I define the rust function response like this so that I could return the error message if encount the recoverable error:
pub fn add_bill_book() -> Result<BillBook,String> {
return Err("failed".parse().unwrap())
}
and box the result to return to client like this;
fn main() {
let result = add_bill_book();
box_rest_response(result);
}
but when I compile the project, shows error like this:
➜ rust-learn git:(multiple-statement) ✗ cargo build
Compiling rust-learn v0.1.0 (/Users/xiaoqiangjiang/source/reddwarf/backend/rust-learn)
error[E0277]: the trait bound `Result<BillBook, std::string::String>: std::default::Default` is not satisfied
--> src/main.rs:8:23
|
8 | box_rest_response(result);
| ----------------- ^^^^^^ the trait `std::default::Default` is not implemented for `Result<BillBook, std::string::String>`
| |
| required by a bound introduced by this call
|
note: required by a bound in `box_rest_response`
--> src/main.rs:11:87
|
11 | pub fn box_rest_response<T>(data: T) -> content::RawJson<String> where T: Serialize + Default {
| ^^^^^^^ required by this bound in `box_rest_response`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `rust-learn` due to previous error
what should I do to fix it? I think it is hard to implement the default trait for all type of Result. This is the full minimal reproduce example:
use rocket::response::content;
use rust_wheel::model::response::api_response::ApiResponse;
use serde::Serialize;
use serde::Deserialize;
fn main() {
let result = add_bill_book();
box_rest_response(result);
}
pub fn box_rest_response<T>(data: T) -> content::RawJson<String> where T: Serialize + Default {
let res = ApiResponse {
result: data,
..Default::default()
};
let response_json = serde_json::to_string(&res).unwrap();
return content::RawJson(response_json);
}
#[derive(Debug,Serialize,Deserialize,Default,Clone)]
pub struct BillBook {
pub id: i64,
pub created_time: i64,
pub updated_time: i64,
pub deleted: i32,
pub creator: i64,
pub bill_book_template_id: i32,
pub remark: Option<String>,
pub contents: Option<String>,
}
pub fn add_bill_book() -> Result<BillBook,String> {
return Err("failed".parse().unwrap())
}
and this is the Cargo.toml dependencies:
[package]
name = "rust-learn"
version = "0.1.0"
edition = "2018"
[dependencies]
rocket = { version = "=0.5.0-rc.2", features = ["json"] }
serde = { version = "1.0.64", features = ["derive"] }
serde_json = "1.0.64"
serde_derive = "1.0"
# database
diesel = { version = "1.4.7", features = ["postgres","serde_json"] }
dotenv = "0.15.0"
jsonwebtoken = "7"
chrono = "0.4"
config = "0.11"
ring = "0.16.20"
md5 = "0.7.0"
data-encoding = "2.3.2"
# reddwarf public component
rust_wheel = { git = "https://github.com/jiangxiaoqiang/rust_wheel.git" }
I tried this way to solve this problem:
return match contents {
Ok(_) => {
box_rest_response(contents.unwrap())
},
Err(_) => {
box_rest_response(contents.unwrap_err())
}
}
but the new problem is that I have to match the result in many places, is it possible to optimized it? I tried to add a new function in public lib like this:
pub fn box_rest_result<T,E>(result: Result<T,E>) -> content::RawJson<String> where T: Serialize + Default, E: std::default::Default{
return match result {
Ok(_) => {
box_rest_response(result.unwrap())
},
Err(_) => {
box_rest_response(result.unwrap_err())
}
}
}
still facing the same problem.
Result does not implement Default because there is no one-fits-all default value.
You can create a newtype struct and implement Default on it, as in How do I implement a trait I don't own for a type I don't own?.
Note that match with unwrap() is a very bad idea, since match can already extract the value:
match contents {
Ok(v) => box_rest_response(v),
Err(e) => box_rest_response(e),
}

Cargo error: duplicate lang item in crate `casper_contract` (which `contract` depends on): `panic_impl`

I am trying to use the standard casper demo project as generated by command "cargo casper my-project" but with some std::io. I want to read a value from the console and then write it to the casper network. The example comes with the #![no_std] define at the start. If I remove this then I get the error message:
error: duplicate lang item in crate casper_contract (which contract depends on): panic__impl.
This is my main.rs with the #![no_std] commented out to try to allow std::io to be used.
//#![no_std]
#![no_main]
#[cfg(not(target_arch = "wasm32"))]
compile_error!("target arch should be wasm32: compile with '--target wasm32-unknown-unknown'");
// We need to explicitly import the std alloc crate and `alloc::string::String` as we're in a
// `no_std` environment.
extern crate alloc;
use alloc::string::String;
use casper_contract::{
contract_api::{runtime, storage},
unwrap_or_revert::UnwrapOrRevert,
};
use casper_types::{ApiError, Key};
const KEY_NAME: &str = "my-key-name";
const RUNTIME_ARG_NAME: &str = "message";
/// An error enum which can be converted to a `u16` so it can be returned as an `ApiError::User`.
#[repr(u16)]
enum Error {
KeyAlreadyExists = 0,
KeyMismatch = 1,
}
impl From<Error> for ApiError {
fn from(error: Error) -> Self {
ApiError::User(error as u16)
}
}
#[no_mangle]
pub extern "C" fn call() {
// The key shouldn't already exist in the named keys.
let missing_key = runtime::get_key(KEY_NAME);
if missing_key.is_some() {
runtime::revert(Error::KeyAlreadyExists);
}
// This contract expects a single runtime argument to be provided. The arg is named "message"
// and will be of type `String`.
let value: String = runtime::get_named_arg(RUNTIME_ARG_NAME);
let mut guess = String::new();
std::io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
// Store this value under a new unforgeable reference a.k.a `URef`.
let value_ref = storage::new_uref(value);
// Store the new `URef` as a named key with a name of `KEY_NAME`.
let key = Key::URef(value_ref);
runtime::put_key(KEY_NAME, key);
// The key should now be able to be retrieved. Note that if `get_key()` returns `None`, then
// `unwrap_or_revert()` will exit the process, returning `ApiError::None`.
let retrieved_key = runtime::get_key(KEY_NAME).unwrap_or_revert();
if retrieved_key != key {
runtime::revert(Error::KeyMismatch);
}
}
And this is my cargo.toml file:
[package]
name = "contract"
version = "0.1.0"
edition = "2018"
[dependencies]
casper-contract = "1.4.3"
casper-types = "1.4.6"
[[bin]]
name = "contract"
path = "src/main.rs"
bench = false
doctest = false
test = false
[profile.release]
codegen-units = 1
lto = true
What am I doing wrong?

Opening tls stream for rabbit client using rust amiquip

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

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

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