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))?
.stop_runtime()
.run()
.await
}
Let's break down the code:
-
forward
is an async function that returns aweb::HttpResponse
or aweb::Error
. -
main
is 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::server
and pass a closure that returns aweb::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
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
url
andfutures_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
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)