DEV Community

Cover image for Basic http proxy in rust with ntex
leone
leone

Posted on

2 1 1 1 1

Basic http proxy in rust with ntex

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
Enter fullscreen mode Exit fullscreen mode

Add ntex as dependency:

cargo add ntex --features tokio
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Let's break down the code:

  • forward is an async function that returns a web::HttpResponse or a web::Error.
  • main is the entry point of our application. It's an async function that returns a std::io::Result<()>.
  • We create a new ntex web server with web::server and pass a closure that returns a web::App.
  • We create a new http::Client and 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 forward handler.
  • We bind the server to 0.0.0.0:9090 and run it.

Let's test the server by running it:

cargo run
curl http://localhost:9090
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Let's break down the code:

  • We add the url and futures_util crates to our dependencies.
  • We change the forward function 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
Enter fullscreen mode Exit fullscreen mode

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.

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, we’ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, we’ll see how we can identify what makes our TTFB high so we can fix it.

Read more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay