I'm learning how to code a server in rust that accepts connections from any client with any request. So far my server can respond with any form of data I've tested it with, but there's something keeping me restless. Clients doing post|put|patch requests with file data as body arrive with empty bodies except 'application/x-www-form-urlencoded' data. How can I solve this? Here is my server code(just for learning purpose):
use std::{
net::TcpListener,
io::prelude::*,
thread::{
self,
Scope
}
};
static RESPONSE: [&'static str; 10] = [
"HTTP/1.1 200 Ok",
"Content-Length: 25",
"Content-Type: text/plain",
"Content-Encoding: identity",
"Access-Control-Allow-Methods: *",
"Access-Control-Allow-Origin: *",
"Access-Control-Allow-Headers: *",
"Accept-Encoding: *",
"Accept: */*\r\n",
"Grateful checking on me.\n"
];
fn main() {
let listener = TcpListener::bind("127.0.0.1:8000").unwrap();
println!("Listening at localhost:8000");
thread::scope(|here| handle_connections(here, &listener));
}
fn handle_connections<'scope, 'env>(scope: &'scope Scope<'scope, 'env>, listener: &'env TcpListener) {
let actor = thread::Builder::new().spawn_scoped(scope, move || loop {
match listener.accept() {
Ok((mut client, addr)) => {
println!("Connection from: {}.", addr);
let mut buff = [0; 1024];
client.read(&mut buff).unwrap();
match client.write_all(RESPONSE.join("\r\n").as_bytes()) {
Ok(_) => println!("Send successful."),
Err(err) => eprintln!("Send failed: {}", err),
}
println!("{}\n", String::from_utf8(buff.to_vec()).unwrap()); // for debugging
}
Err(_) => eprintln!("Connection failed or disrupted!"),
}
});
if let Err(err) = actor {
eprintln!("Recovering from: {}", err);
handle_connections(scope, listener);
}
}
Related
My 20th day learning Rust and I try to code a reverse proxy that can authenticate.
Got error in line 30: error[E0282]: type annotations needed for &Target. I tried to follow the help by compiler "consider giving this closure parameter an explicit type where the type for type parameter Target is specified"
But i got even more errors and confuse.
Please help.
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server};
use std::convert::Infallible;
use std::net::{IpAddr, SocketAddr};
async fn authenticate(req: Request<Body>) -> Result<Request<Body>, Infallible> {
// Perform authentication checks here.
// For example, you can check for a valid API key in the request header.
Ok(req)
}
async fn handle(client_ip: IpAddr, req: Request<Body>) -> Result<Response<Body>, Infallible> {
// Forward the request to another server.
let target_url = "http://127.0.0.1:13901";
match hyper_reverse_proxy::call(client_ip, target_url, req).await {
Ok(response) => Ok(response),
Err(error) => {
// println!("Error forwarding request: {}", error);
Ok(Response::builder()
.status(hyper::StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::empty())
.unwrap())
}
}
}
#[tokio::main]
async fn main() {
let make_service = make_service_fn(|conn| async { // <<-- Error!
let client_ip = conn.remote_addr().ip();
Ok::<_, Infallible>(service_fn(move |req| {
let client_ip = client_ip.clone();
async {
match authenticate(req).await {
Ok(req) => handle(client_ip, req).await,
Err(_) => {
eprintln!("Authentication failed");
Ok(Response::builder()
.status(hyper::StatusCode::UNAUTHORIZED)
.body(Body::empty())
.unwrap())
}
}
}
}))
});
let bind_addr = "127.0.0.1:8000";
let addr: SocketAddr = bind_addr.parse().expect("Could not parse ip:port.");
let server = Server::bind(&addr).serve(make_service);
println!("Running reverse proxy on {:?}", addr);
if let Err(error) = server.await {
eprintln!("Error running server: {}", error);
}
}
my cargo.toml
[dependencies]
hyper-reverse-proxy = "0.5"
hyper = { version = "0.14", features = ["full"] }
tokio = { version = "1", features = ["full"] }
the signature of the functions is
pub fn make_service_fn<F, Target, Ret>(f: F) -> MakeServiceFn<F>
where
F: FnMut(&Target) -> Ret,
Ret: Future,
The target is a generic parameter and according to example from documentation
use std::convert::Infallible;
use hyper::{Body, Request, Response, Server};
use hyper::server::conn::AddrStream;
use hyper::service::{make_service_fn, service_fn};
let addr = ([127, 0, 0, 1], 3000).into();
let make_svc = make_service_fn(|socket: &AddrStream| {
let remote_addr = socket.remote_addr();
async move {
Ok::<_, Infallible>(service_fn(move |_: Request<Body>| async move {
Ok::<_, Infallible>(
Response::new(Body::from(format!("Hello, {}!", remote_addr)))
)
}))
}
});
// Then bind and serve...
let server = Server::bind(&addr)
.serve(make_svc);
// Finally, spawn `server` onto an Executor...
if let Err(e) = server.await {
eprintln!("server error: {}", e);
}
we can pass reference to AddrStream which you have to specify since rust cannot infer this...
so the fixed code looks like this
let make_service = make_service_fn(|conn: &AddrStream| {
let client_ip = conn.remote_addr().ip();
async move {
Ok::<_, Infallible>(service_fn(move |req| {
let client_ip = client_ip.clone();
async move {
match authenticate(req).await {
Ok(req) => handle(client_ip, req).await,
Err(_) => {
eprintln!("Authentication failed");
Ok(Response::builder()
.status(hyper::StatusCode::UNAUTHORIZED)
.body(Body::empty())
.unwrap())
}
}
}
}))
}
});
I want to be able to store a bearer token from the response body temporarily for about 900 seconds and be able to use the token whenever another function calls upon it. I have created the basic structure of creating the client and posting the request and getting a token back. Here is what I have so far:
#[derive(Debug, Deserialize)]
struct SecureToken {
access_token: String,
expiration_date_time: i32,
}
#[tokio::main]
async fn get_new_secure_token() {
let mut map = HashMap::new();
map.insert("client_id", "client_id");
map.insert("client_secret", "client_secret");
map.insert("scope", "scope");
map.insert("grant_type", "client_credentials");
let client = Client::new();
let token_request = client
.post("https://gatway.address")
.form(&map)
.send()
.await
.expect("");
//.json::<TokenResponse>()
//.await;
match token_request.status() {
StatusCode::OK => {
let query_result = token_request.json::<SecureToken>().await;
println!("{:#?}", query_result);
},
StatusCode::FORBIDDEN => {
println!("Unable to connect to auth service");
},
StatusCode::BAD_REQUEST => {
let bad_request = token_request.json::<BadRequest>().await;
println!("Error: {:?}", bad_request);
}
s => println!("STATUS CODE: {:?}", s),
};
}
Instead of printing the response, I want it to store the token temporarily for about 900 seconds along the lines of this:
let token_request = client
.post("https://gatway.address")
.form(&map)
.send()
.await
.expect("");
//.json::<TokenResponse>()
//.await;
match token_request.status() {
StatusCode::OK => {
let query_result = token_request.json::<SecureToken>().await;
let token = quer
},
StatusCode::FORBIDDEN => {
println!("Unable to connect to auth service");
},
StatusCode::BAD_REQUEST => {
let bad_request = token_request.json::<BadRequest>().await;
println!("Error: {:?}", bad_request);
}
s => println!("STATUS CODE: {:?}", s),
};
}
But I don't know how to store the token nor add a expiration time to the token.
I'm using rumqttc tls with rustls
Error = Tls(Io(Custom { kind: InvalidData, error: InvalidCertificateData("invalid peer certificate: CertNotValidForName") }))
this is my function:
#[cfg(feature = "use-rustls")]
#[tokio::main]
pub async fn test_mqtt() -> Result<(), Box<dyn Error>> {
use rumqttc::{self, AsyncClient, Key, MqttOptions, TlsConfiguration, Transport};
let mut mqttoptions = MqttOptions::new("test", "test.test.com", 1000);
mqttoptions.set_credentials("test", "test");
mqttoptions.set_keep_alive(std::time::Duration::from_secs(5));
let ca = include_bytes!("../certs/ca.crt");
let client_cert = include_bytes!("../certs/client.crt");
let client_key = include_bytes!("../certs/client.key");
let transport = Transport::Tls(TlsConfiguration::Simple {
ca: ca.to_vec(),
alpn: None,
client_auth: Some((client_cert.to_vec(), Key::RSA(client_key.to_vec()))),
});
mqttoptions.set_transport(transport);
let (client, mut eventloop) = AsyncClient::new(mqttoptions, 10);
client.subscribe("test/test/test", QoS::AtMostOnce).await.unwrap();
client.subscribe("test/test/test/logs", QoS::AtMostOnce).await.unwrap();
thread::spawn(move || {
client.publish("test/test/test", QoS::AtLeastOnce, false, "test");
client.publish("test/test/test/logs", QoS::AtLeastOnce, false, "test");
thread::sleep(Duration::from_millis(100));
});
loop {
match eventloop.poll().await {
Ok(v) => {
println!("Event = {:?}", v);
}
Err(e) => {
println!("Error = {:?}", e);
break;
}
}
}
Ok(())
}
This results in
2022-11-18 16:11:46 - WARN: Sending fatal alert BadCertificate
Error = Tls(Io(Custom { kind: InvalidData, error: InvalidCertificateData("invalid peer certificate: CertNotValidForName") }))
The rustls certificate verification through webpki is quite strict. You may circumvent it by implementing a custom ServerCertVerifier.
Take a look at: https://users.rust-lang.org/t/rustls-connecting-without-certificate-in-local-network/83822/4
So I want to use tokio_postgres in an actix API, I just started and I am having difficulties with sharing the client in my get request.
Here is my code:
use actix_web::{web, App, HttpServer, HttpResponse};
use tokio_postgres::{NoTls};
use std::cell::RefCell;
mod api;
use api::routes::hello_actix;
fn main() {
actix_rt::System::run(|| {
println!("connecting to postgres");
let pro = tokio::spawn(
async
{
let (client , conn) = match tokio_postgres::connect(
"foo",
NoTls)
.await {
Ok(t) => t,
Err(e) => panic!("{}", e)
};
tokio::spawn(async move {
if let Err(e) = conn.await {
eprintln!("connection error: {}", e);
}
});
println!("connected to postgres");
HttpServer::new(|| {
App::new()
.data(RefCell::new(conn))
.route("/hello", web::get().to(hello_actix))
.default_service(web::route().to(|| HttpResponse::NotFound().body("404 Not Found")))
})
.bind("127.0.0.1:8080")
.unwrap()
.run();
Ok(())
});
});
}
i base this code on this but it does not seem to be working.
I would also like to stay "Pure" so I don't want to have a global client if possible.
Is it even possible to do what I want?
I am trying to match against a file extension:
let file_path = std::path::Path::new("index.html");
let content_type = match file_path.extension() {
None => "",
Some(os_str) => match os_str {
"html" => "text/html",
"css" => "text/css",
"js" => "application/javascript",
},
};
The compiler says:
error[E0308]: mismatched types
--> src/main.rs:6:13
|
6 | "html" => "text/html",
| ^^^^^^ expected struct `std::ffi::OsStr`, found str
|
= note: expected type `&std::ffi::OsStr`
found type `&'static str`
OsStr and OsString exist precisely because filenames are not UTF-8. A Rust string literal is UTF-8. That means you must deal with converting between the two representations.
One solution is to give up the match and use if-else statements. See Stargateur's answer for an example.
You can also convert the extension to a string. Since the extension might not be UTF-8, this returns another Option:
fn main() {
let file_path = std::path::Path::new("index.html");
let content_type = match file_path.extension() {
None => "",
Some(os_str) => {
match os_str.to_str() {
Some("html") => "text/html",
Some("css") => "text/css",
Some("js") => "application/javascript",
_ => panic!("You forgot to specify this case!"),
}
}
};
}
If you want all cases to use an empty string as the fallback, you can do something like:
use std::ffi::OsStr;
fn main() {
let file_path = std::path::Path::new("index.html");
let content_type = match file_path.extension().and_then(OsStr::to_str) {
Some("html") => "text/html",
Some("css") => "text/css",
Some("js") => "application/javascript",
_ => "",
};
}
Or if you want to use None as the fallback:
use std::ffi::OsStr;
fn main() {
let file_path = std::path::Path::new("index.html");
let content_type = file_path.extension().and_then(OsStr::to_str).and_then(|ext| {
match ext {
"html" => Some("text/html"),
"css" => Some("text/css"),
"js" => Some("application/javascript"),
_ => None,
}
});
}
You could use PartialEq<str> trait for OsStr.
fn main() {
let file_path = std::path::Path::new("index.html");
let content_type = match file_path.extension() {
None => "",
Some(os_str) => {
if os_str == "html" {
"text/html"
} else if os_str == "css" {
"text/css"
} else if os_str == "js" {
"application/javascript"
} else {
""
}
}
};
println!("{:?}", content_type);
}