How do I connect to ldap using Rust?
All I could find from the online docs was this:
use ldap3::result::Result;
use ldap3::{LdapConn, Scope, SearchEntry};
fn main() -> Result<()> {
let mut ldap = LdapConn::new("ldap://localhost:2389")?;
let (rs, _res) = ldap
.search(
"ou=Places,dc=example,dc=org",
Scope::Subtree,
"(&(objectClass=locality)(l=ma*))",
vec!["l"],
)?
.success()?;
for entry in rs {
println!("{:?}", SearchEntry::construct(entry));
}
Ok(ldap.unbind()?)
}
I'm looking for something like python's ldap3
After defining the LdapConn, you need to bind. You have to use distinguished name or email in the bind.
let result = ldap
.simple_bind("username#example.com", "password")
.unwrap()
.success();
if result.is_err() {
println!("This failed")
} else {
println!("IT WORKED!")
}
A more practical application:
assert!(ldap
.simple_bind("username#example.com", "password")
.unwrap()
.success()
.is_ok());
If you need more options like python's ldap3 you can use LdapConnSettings to specify some of the settings like tls
let mut ldap: LdapConn = LdapConn::with_settings(
LdapConnSettings::new()
.set_no_tls_verify(true)
.set_starttls(true),
"ldap://localhost:2389"
)
.unwrap();
Related
I'm a new to Rust.
I created a structure to hold system information.
pub struct systemConfig {
pub admin_address: String,
pub engine_name: Option<String>,
pub group_name: Option<String>
}
I want to pass this structure to the make_msg function to create a json body and send it as a request to another server.
fn make_msg(config: systemConfig) -> String{
let (host_name, cpus) = get_system_info();
let engine_name = match config.engine_name {
Some(name) => name,
None => host_name.clone(),
};
let group_name = match config.group_name {
Some(name) => name,
None => String::from("")
};
let msg = json!({
"engineName": engine_name,
"groupName": group_name,
"hostName": host_name,
});
msg.to_string()
}
fn get_system_info() -> (String, usize){
use sysinfo::{ System, SystemExt };
// monitoring info
let mut my_system = System::new_all();
my_system.refresh_all();
// hostname
let hostname = get_hostname(&my_system);
// logical cpu count
let cpus = get_logical_cpus(&my_system);
(hostname, cpus)
}
I have two questions.
engine_name and group_name are values obtained from process argument. The reason that type is defined as Option is that its value is not required. If the engine name is not entered, the hostname is filled in. And If the group name is not entered, it is sent as ""(empty String).
I used the match syntax, but is there a more appropriate syntax? (if let Some/None,,
more concise and intuitive)
None => host_name.clone(),
If clone() is not performed here, a borrow issue occurs. I'm looking for advice on whether using clone() is the right way, or if there is a better way.
I add test code
//cargo.toml
[dependencies]
sysinfo = "0.23.12"
serde_json = { version = "1.0", features = ["arbitrary_precision"] }
use sysinfo::{System, SystemExt};
use serde_json::json;
struct systemConfig {
pub admin_address: String,
pub engine_name: Option<String>,
pub group_name: Option<String>
}
fn main() {
let config = systemConfig {
admin_address: String::from("127.0.0.1:8080"),
engine_name: Some(String::from("hello")),
group_name: Some(String::from("world"))
};
let msg = make_msg(config);
println!("msg: {}", msg);
}
fn make_msg(config: systemConfig) -> String{
let host_name = get_system_info();
let engine_name = match config.engine_name {
Some(name) => name,
None => host_name.clone(),
};
let group_name = match config.group_name {
Some(name) => name,
None => String::from("")
};
let msg = json!({
"engineName": engine_name,
"groupName": group_name,
"hostName": host_name,
});
msg.to_string()
}
fn get_system_info() -> String {
use sysinfo::{ System, SystemExt };
// monitoring info
let mut my_system = System::new_all();
my_system.refresh_all();
// hostname
let hostname = get_hostname(&my_system);
hostname
}
pub fn get_hostname(s: &System) -> String {
s.host_name().unwrap()
}
I used the match syntax, but is there a more appropriate syntax? (if let Some/None,, more concise and intuitive)
Option has a few utilities that you could use. In the engine_name case, unwrap_or_else() is less verbose than your match:
let engine_name = config.engine_name
.unwrap_or_else(|| host_name.clone());
For group_name you can use unwrap_or_default() since the Default implementation on String returns an empty string:
let group_name = config.group_name.unwrap_or_default();
Note that both of these options are superior in this case to unwrap_or() since they do not require building the alternative value unless it's needed. For example, in the engine_name case this won't clone host_name unless config.engine_name is None.
I'm looking for advice on whether using clone() is the right way, or if there is a better way.
You can make it work using only references like this:
let engine_name = match &config.engine_name {
Some(ref name) => name,
None => &host_name,
};
Or, like above, you can use unwrap_or() (combined with as_ref()):
let engine_name = config.engine_name.as_ref().unwrap_or(&host_name);
However, the JSON Value::String variant requires an owned string, so not cloning here isn't really an optimization -- the json! macro will just clone it anyway.
Two questions, two answers:
The situation of unwrapping or replacing an Option is common enough that it got its own function: Option::unwrap_or:
let engine_name = config.engine_name.unwrap_or(host_name.clone());
let group_name = config.group_name.unwrap_or(String::from(""));
Clone is the right way. In some situations, engineName and hostName will contain the same string, so a .clone() will be required at some point either way.
I built a microservice in Rust. I receive messages, request a document based on the message, and call a REST api with the results. I built the REST api with warp and send out the result with reqwest. We use jaeger for tracing and the "b3" format. I have no experience with tracing and am a Rust beginner.
Question: What do I need to add the the warp / reqwest source below to propagate the tracing information and add my own span?
My version endpoint (for simplicity) looks like:
pub async fn version() -> Result<impl warp::Reply, Infallible> {
Ok(warp::reply::with_status(VERSION, http::StatusCode::OK))
}
I assume I have to extract e.g. the traceid / trace information here.
A reqwest call I do looks like this:
pub async fn get_document_content_as_text(
account_id: &str,
hash: &str,
) -> Result<String, Box<dyn std::error::Error>> {
let client = reqwest::Client::builder().build()?;
let res = client
.get(url)
.bearer_auth(TOKEN)
.send()
.await?;
if res.status().is_success() {}
let text = res.text().await?;
Ok(text)
}
I assume I have to add the traceid / trace information here.
You need to add a tracing filter into your warp filter pipeline.
From the documentation example:
use warp::Filter;
let route = warp::any()
.map(warp::reply)
.with(warp::trace(|info| {
// Create a span using tracing macros
tracing::info_span!(
"request",
method = %info.method(),
path = %info.path(),
)
}));
I'll assume that you're using tracing within your application and using opentelemetry and opentelemetry-jaeger to wire it up to an external service. The specific provider you're using doesn't matter. Here's a super simple setup to get that all working that I'll assume you're using on both applications:
# Cargo.toml
[dependencies]
opentelemetry = "0.17.0"
opentelemetry-jaeger = "0.16.0"
tracing = "0.1.33"
tracing-subscriber = { version = "0.3.11", features = ["env-filter"] }
tracing-opentelemetry = "0.17.2"
reqwest = "0.11.11"
tokio = { version = "1.21.1", features = ["macros", "rt", "rt-multi-thread"] }
warp = "0.3.2"
opentelemetry::global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new());
tracing_subscriber::registry()
.with(tracing_opentelemetry::layer().with_tracer(
opentelemetry_jaeger::new_pipeline()
.with_service_name("client") // or "server"
.install_simple()
.unwrap())
).init();
Let's say the "client" application is set up like so:
#[tracing::instrument]
async fn call_hello() {
let client = reqwest::Client::default();
let _resp = client
.get("http://127.0.0.1:3030/hello")
.send()
.await
.unwrap()
.text()
.await
.unwrap();
}
#[tokio::main]
async fn main() {
// ... initialization above ...
call_hello().await;
}
The traces produced by the client are a bit chatty because of other crates but fairly simple, and does not include the server-side:
Let's say the "server" application is set up like so:
#[tracing::instrument]
fn hello_handler() -> &'static str {
tracing::info!("got hello message");
"hello world"
}
#[tokio::main]
async fn main() {
// ... initialization above ...
let routes = warp::path("hello")
.map(hello_handler);
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
Likewise, the traces produced by the server are pretty bare-bones:
The key part to marrying these two traces is to declare the client-side trace as the parent of the server-side trace. This can be done over HTTP requests with the traceparent and tracestate headers as designed by the W3C Trace Context Standard. There is a TraceContextPropagator available from the opentelemetry crate that can be used to "extract" and "inject" these values (though as you'll see, its not very easy to work with since it only works on HashMap<String, String>s).
For the "client" to send these headers, you'll need to:
get the current tracing Span
get the opentelemetry Context from the Span (if you're not using tracing at all, you can skip the first step and use Context::current() directly)
create the propagator and fields to propagate into and "inject" then from the Context
use those fields as headers for reqwest
#[tracing::instrument]
async fn call_hello() {
let span = tracing::Span::current();
let context = span.context();
let propagator = TraceContextPropagator::new();
let mut fields = HashMap::new();
propagator.inject_context(&context, &mut fields);
let headers = fields
.into_iter()
.map(|(k, v)| {(
HeaderName::try_from(k).unwrap(),
HeaderValue::try_from(v).unwrap(),
)})
.collect();
let client = reqwest::Client::default();
let _resp = client
.get("http://127.0.0.1:3030/hello")
.headers(headers)
.send()
.await
.unwrap()
.text()
.await
.unwrap();
}
For the "server" to make use of those headers, you'll need to:
pull them out from the request and store them in a HashMap
use the propagator to "extract" the values into a Context
set that Context as the parent of the current tracing Span (if you didn't use tracing, you could .attach() it instead)
#[tracing::instrument]
fn hello_handler(traceparent: Option<String>, tracestate: Option<String>) -> &'static str {
let fields: HashMap<_, _> = [
dbg!(traceparent).map(|value| ("traceparent".to_owned(), value)),
dbg!(tracestate).map(|value| ("tracestate".to_owned(), value)),
]
.into_iter()
.flatten()
.collect();
let propagator = TraceContextPropagator::new();
let context = propagator.extract(&fields);
let span = tracing::Span::current();
span.set_parent(context);
tracing::info!("got hello message");
"hello world"
}
#[tokio::main]
async fn main() {
// ... initialization above ...
let routes = warp::path("hello")
.and(warp::header::optional("traceparent"))
.and(warp::header::optional("tracestate"))
.map(hello_handler);
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
With all that, hopefully your traces have now been associated with one another!
Full code is available here and here.
Please, someone let me know if there is a better way! It seems ridiculous to me that there isn't better integration available. Sure some of this could maybe be a bit simpler and/or wrapped up in some nice middleware for your favorite client and server of choice... But I haven't found a crate or snippet of that anywhere!
Please help me to understand why i need to display variable. I use ssh2 crate to create ssh connect.
My code is here:
use ssh2::Session;
use std::io::prelude::*;
use std::net::{TcpStream};
fn main() {
// Connect to the SSH server
let tcp = TcpStream::connect("192.168.1.251:22").unwrap();
let mut sess = Session::new().unwrap();
sess.set_tcp_stream(tcp);
sess.handshake().unwrap();
sess.userauth_password("root", "password").unwrap();
let mut s = String::new();
let last_stat = String::from("unknown");
loop {
let mut channel = sess.channel_session().unwrap();
channel.exec("systemctl is-active firewalld").unwrap();
channel.read_to_string(&mut s).unwrap();
if &s.to_string().trim() == &last_stat {
print!("stopped");
} else {
print!("{}",&s);
}
&s.clear();
std::thread::sleep(std::time::Duration::from_secs(1));
}
}
If the firewallв is stopped, nothing is displayed. But if I show the variable in the output, then the code works and display "unknown".
use ssh2::Session;
use std::io::prelude::*;
use std::net::{TcpStream};
fn main() {
// Connect to the SSH server
let tcp = TcpStream::connect("192.168.1.251:22").unwrap();
let mut sess = Session::new().unwrap();
sess.set_tcp_stream(tcp);
sess.handshake().unwrap();
sess.userauth_password("root", "password").unwrap();
let mut s = String::new();
let last_stat = String::from("unknown");
loop {
let mut channel = sess.channel_session().unwrap();
channel.exec("systemctl is-active firewalld").unwrap();
channel.read_to_string(&mut s).unwrap();
if &s.to_string().trim() == &last_stat {
print!("{}",&s);
} else {
print!("{}",&s);
}
&s.clear();
std::thread::sleep(std::time::Duration::from_secs(1));
}
}
Why i must display variable &s to code works?
many thanks.
I'm having the same problem as Is there any straightforward way for Clap to display help when no command is provided?, but the solution proposed in that question is not good enough for me.
.setting(AppSettings::ArgRequiredElseHelp) stops the program if no arguments are provided, and I need the program to carry on execution even if no arguments are provided. I need the help to be displayed in addition.
You could write the string before.
use clap::{App, SubCommand};
use std::str;
fn main() {
let mut app = App::new("myapp")
.version("0.0.1")
.about("My first CLI APP")
.subcommand(SubCommand::with_name("ls").about("List anything"));
let mut help = Vec::new();
app.write_long_help(&mut help).unwrap();
let _ = app.get_matches();
println!("{}", str::from_utf8(&help).unwrap());
}
Or you could use get_matches_safe
use clap::{App, AppSettings, ErrorKind, SubCommand};
fn main() {
let app = App::new("myapp")
.setting(AppSettings::ArgRequiredElseHelp)
.version("0.0.1")
.about("My first CLI APP")
.subcommand(SubCommand::with_name("ls").about("List anything"));
let matches = app.get_matches_safe();
match matches {
Err(e) => {
if e.kind == ErrorKind::MissingArgumentOrSubcommand {
println!("{}", e.message)
}
}
_ => (),
}
}
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());
});
}