What is ntex ?
Ntex is a powerful, pragmatic, and extremely fast framework for composable networking services for Rust.
It's one of the fastest web frameworks available in Rust, and provides powerful abstractions for web server development.
Why ntex ?
This are my top reasons for using ntex:
- Performance: Ntex is one of the fastest web frameworks available in Rust.
 - Ergonomic: Ntex provides powerful abstractions for web server development.
 - Composable: Ntex is designed to be composable, allowing you to build complex web servers from simple components.
 - Ecosystem: Ntex has a rich ecosystem of middleware, extensions and libraries.
 - Built-in http client: Ntex provides a built-in http client for making requests to other servers.
 - Runtime: Ntex allow you to choose between different runtimes, including tokio and async-std.
 
Setting up the project
Let's start by creating a new project with cargo:
cargo new ntex-http-proxy
cd ntex-http-proxy
Add ntex as dependency:
cargo add ntex --features tokio
Starting with a basic http handler
Let's start by creating a basic http handler that will return Hello, World! in plain text format:
use ntex::{http, web};
async fn forward() -> Result<web::HttpResponse, web::Error> {
  Ok(
    web::HttpResponse::Ok()
      .content_type("text/plain")
      .body("Hello, world!"),
  )
}
#[ntex::main]
async fn main() -> std::io::Result<()> {
  web::server(move || {
    web::App::new()
      .state(http::Client::new())
      .wrap(web::middleware::Logger::default())
      .default_service(web::route().to(forward))
  })
  .bind(("0.0.0.0", 9090))?
  .run()
  .await
}
Let's break down the code:
- 
forwardis an async function that returns aweb::HttpResponseor aweb::Error. - 
mainis the entry point of our application. It's an async function that returns astd::io::Result<()>. - We create a new ntex web server with 
web::serverand pass a closure that returns aweb::App. - We create a new 
http::Clientand add it to the app state. - We add a logger middleware to the app.
 - We define a default service that will forward all requests to the 
forwardhandler. - We bind the server to 
0.0.0.0:9090and run it. 
Let's test the server by running it:
cargo run
curl http://localhost:9090
You should see Hello, world! in the response.
Adding a proxy handler
We start by adding the url and futures_util crates to our dependencies to be able to parse urls and convert responses to streams:
cargo add url futures-util
Then we change the the code to forward requests to another server:
use futures_util::TryStreamExt;
use ntex::{http, web};
async fn forward(
  req: web::HttpRequest,
  body: ntex::util::Bytes,
  client: web::types::State<http::Client>,
  forward_url: web::types::State<url::Url>,
) -> Result<web::HttpResponse, web::Error> {
  let mut new_url = forward_url.get_ref().clone();
  new_url.set_path(req.uri().path());
  new_url.set_query(req.uri().query());
  let forwarded_req = client.request_from(new_url.as_str(), req.head());
  let res = forwarded_req
    .send_body(body)
    .await
    .map_err(web::Error::from)?;
  let mut client_resp = web::HttpResponse::build(res.status());
  let stream = res.into_stream();
  Ok(client_resp.streaming(stream))
}
#[ntex::main]
async fn main() -> std::io::Result<()> {
  let forward_url = "https://www.rust-lang.org".to_owned();
  let forward_url = url::Url::parse(&forward_url)
    .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))?;
  web::server(move || {
    web::App::new()
      .state(http::Client::new())
      .state(forward_url.clone())
      .wrap(web::middleware::Logger::default())
      .default_service(web::route().to(forward))
  })
  .bind(("0.0.0.0", 9090))?
  .run()
  .await
}
Let's break down the code:
- We add the 
urlandfutures_utilcrates to our dependencies. - We change the 
forwardfunction to take the request, body, client and forward_url as arguments. - We create a new url by cloning the forward_url and setting the path and query from the request.
 - We create a new request using the client and the new url.
 - We send the body of the request and await the response.
 - We build a new response with the status code of the response.
 - We convert the response into a stream and return it.
 
Let's test the server by running it:
cargo run
curl http://localhost:9090
You should see the rust-lang.org homepage in the response.
Conclusion
In this tutorial, we created a basic http proxy server using ntex. We started by creating a simple http handler that returns Hello, World! in plain text format. Then we added a proxy handler that forwards requests to another server. We used the url and futures_util crates to parse urls and convert responses to streams. We tested the server by running it and making a request to it. We saw that the server successfully forwarded the request to the target server and returned the response.
We have little to no code to write a basic http proxy server (less than 50 lines of code) and we can easily extend it with more features like caching, rate limiting, authentication, etc.
I hope you enjoyed this tutorial and found it useful. If you have any questions or feedback, feel free to leave a comment below.
              
    
Top comments (0)