Giter VIP home page Giter VIP logo

axum-server's People

Contributors

abs0luty avatar aeledfyr avatar chitoku-k avatar davidpdrsn avatar daxpedda avatar finnbear avatar firstyear avatar garlic-hub avatar hansonchar avatar james58899 avatar madoshakalaka avatar neoeinstein avatar paolobarbolini avatar programatik29 avatar wicpar avatar yusdacra avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

axum-server's Issues

Unable to pass `SocketAddr` to `bind` / `bind_rustls`

I'm getting the following error when I try to pass a SocketAddr type into bind / bind_rustls:

         axum_server::bind_rustls(addr)
         ^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `std::vec::IntoIter`, found struct `std::option::IntoIter`

Details:
I'm trying to implement TLS on my axum server. I have a variable addr of type SocketAddr. I thought I would be able to pass this directly into bind / bind_rustls because it accepts A: ToSocketAddrs and there's an impl ToSocketAddrs for SocketAddr.

I see that impl ToSocketAddrs for String uses std::vec::IntoIter while impl ToSocketAddrs for SocketAddr uses std::option::IntoIter (possibly because it's a single address). I tried forking the repo and changing std::vec::IntoIter to std::iter::Iterable to support both, but seems like that was a bit naive. I get another error "doesn't have a size known at compile-time" because of the BoxedToSocketAddrs type.

For now, I've gotten past it by doing addr.to_string(). Happy to submit a PR if I get some guidance on how to make this work. Thanks!

Acceptor errors are ignored

Hi there,

I wrote a custom acceptor that enforces client hostname checking during mTLS. I noticed in serve that errors from the acceptor are ignored, leading the the socket being closed with out reporting an error back to the client. It would be helpful to report these errors back to the client before closing the connection.

serve in server.rs

 tokio::spawn(async move {
     if let Ok((stream, send_service)) = acceptor.accept(addr_stream, service).await   
     // ^^^^ maybe match on the result and write the error down the socket before closing the connection.
.....

HTTP and HTTPS on the same port

I wrote an Acceptor that allows handling of HTTP and HTTPS on the same port.
The implementation is fairly minimal and simple. Is it in scope for axum-server to add something like this? Happy to start a PR of course.

My use-case is hosting a HTTPS server on a custom port, unlike the example, there is only one port. So connecting with HTTP to that server errors out.
The idea is if I accept both protocols, I can design a middleware that handles the redirect.

tls-rustls missing impl from_pem_chain_file

When i switched from tls-openssl to tls-rustls i noticed that the RustlsConfig doesn't implement from_pem_chain_file like the OpenSSLConfig.
I managed to solve this in my Project by using rustls's ServerConfig::builder() and rustls_pemfile.
I think it would be good if axum_server implemented this functionality because working with such pem files is common Practice and it is already implemented for RustlsConfig . This would also decouple the fn from the Feature used.

I would Expect a function with the following Signature:

fn from_pem_chain_file<A: AsRef<Path>, B: AsRef<Path>>(
    chain: A,
    key: B,
) -> Result<RustlsConfig, RustlsError>;

Since the OpenSSLConfig has this Implementation.

My Current Solution:

use axum_server::tls_rustls::RustlsConfig;
use miette::{Diagnostic, Result};
use rustls::{Certificate, ServerConfig};
use rustls_pemfile::Item;
use std::{fs::File, io::BufReader, path::Path, sync::Arc};
use thiserror::Error;

pub fn rustls_from_pem_chain_file<A: AsRef<Path>, B: AsRef<Path>>(
    chain: A,
    key: B,
) -> Result<RustlsConfig, RustlsConfigError> {
    let mut chain_file: BufReader<File> = BufReader::new(File::open(chain)?);
    let chain_cert: Vec<Certificate> = rustls_pemfile::certs(&mut chain_file)?
        .into_iter()
        .map(rustls::Certificate)
        .collect();
    let mut key_file: BufReader<File> = BufReader::new(File::open(key)?);
    let key_cert: rustls::PrivateKey = match rustls_pemfile::read_one(&mut key_file)?
        .ok_or_else(|| RustlsConfigError::ParseError())?
    {
        Item::PKCS8Key(cert) => Ok(rustls::PrivateKey(cert)),
        x => Err(RustlsConfigError::PemError(x)),
    }?;
    let server_config = ServerConfig::builder()
        .with_safe_defaults()
        .with_no_client_auth()
        .with_single_cert(chain_cert, key_cert)?;
    Ok(RustlsConfig::from_config(Arc::new(server_config)))
}

#[derive(Error, Debug, Diagnostic)]
pub enum RustlsConfigError {
    #[error("Could not load pem file")]
    #[diagnostic(code(rustls_file_error))]
    FileError(#[from] std::io::Error),
    #[error("Could parse pem file")]
    #[diagnostic(code(rustls_parse_error))]
    ParseError(),
    #[error("Invalid TLS certificate format, recieved: {0:?}")]
    #[diagnostic(code(rustls_pem_error))]
    PemError(Item),
    #[error("Invalid TLS certificate")]
    #[diagnostic(code(rustls_error))]
    RustlsError(#[from] rustls::Error),
}

Server utilities

It would be nice to know when server starts listening and being able to shutdown or gracefully shutdown it.

What is the best way to use different configs based on the connection ?

My use case is I need to use different server configs based on the SNI value (multiple domains each with different certificates and client certificate authentication requirements). Rustls offers an acceptor for this use case. I can see that there is an Accept trait for similiar use case. However looking at the code the default RustlsAcceptor uses a RustlsAcceptorFuture which actually setups the rustls. Should I make my own Acceptor if so do I need to replicate the behaviour of RustlsAcceptorFuture ?

Feature Request: support PEM files that contain both certificate and key

We are using a PEM file that contains both the CERTIFICATE and PRIVATE KEY in one file. The file looks like this

-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----

-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----

And I load it though the from_pem_file similar to this

let path = "/path/to/combo.pem";
let tls_config = RustlsConfig::from_pem_file(path, path);

This breaks however if the combo PEM file places the certificates above the key with a private key format not supported error. I believe the problem stems from here which assumes the key will always be the first section. I understand this is because you expect two separate files to be passed in and I'm cheating a bit by passing in the same file for both.

Our systems don't easily allow us to edit the PEM files and I would like to avoid reading in the file and manually splitting it into separate byte arrays to pass to the from_pem method.

Please consider supporting this functionality. Maybe with a from_pem_combo_file or iterating over the whole file for a key rather than just the first section.

Thank you

Feature request: Optional mTLS

I have an HTTPS server (:wink:) that accepts two forms of traffic, unauthenticated and authenticated, and would like to use AllowAnyAnonymousOrAuthenticatedClient or similar to support both. However, I don't see a way to tell if client authentication was successful at all and/or call peer_certificates.

I looked into into_make_service_with_connect_info but that accesses an &AddrStream before the TLS handshake.

Any thoughts on how mTLS could be made optional while letting the tower service know the client authentication status?

From the network perspective, I'd like:

  • No client auth => ok (anonymous)
  • Invalid client auth => block connection (or treat as anonymous)
  • Valid client auth => ok (authenticated)

On the Axum side, I'd like the following:

Router::new()
    .route("/idea1", axum::routing::get(move |
        ConnectInfo(addr): ConnectInfo<SocketAddr>,
        AcceptInfo(certs): AcceptInfo<Option<Vec<Certificate>>> // i.e. from peer_certificates
    | { if let Some(certs) && check_certs(certs) { } else { ... } })
    .route("/idea2", axum::routing::get(move |
        ConnectInfo(addr): ConnectInfo<SocketAddr>,
        AcceptInfo(auth): AcceptInfo<ClientAuthentication> // enum ClientAuthentication { Authenticated, Anonymous }
    | { if auth.is_authenticated() { ... } else { ... } })
      

*the name AcceptInfo is just bikeshedding

Edit: I no longer need this. Feel free to close it as not planned if you don't find it useful.

Listen on multiple sockets

I really do like the easy of working with axum and axum-server. It is straightforward to set up TLS and reload it, which is awesome when used with certificates that need to renewed often.

I would be genuinely interested in binding a server to multiple sockets of different types. For example, to accept an arbitrary list of TCP and unix domain sockets via systemd socket activation:

	let tls_config = RustlsConfig::from_pem_file(
		"examples/self-signed-certs/cert.pem",
		"examples/self-signed-certs/key.pem",
	)
	.await?;

	let server = axum_server::new();

	let mut lfd = ListenFd::from_env();
	if lfd.len() > 0 {
		for idx in 0..lfd.len() {
			if let Ok(Some(lst)) = lfd.take_tcp_listener(idx) {
				server = server.from_tcp_rustls(lst, tls_config);
			} else if let Ok(Some(lst)) = lfd.take_unix_listener(idx) {
				server = server.from_uds(lst);
			} else {
				warn!("Unsupported socket type at index {:?}", idx)
			}
		}
	} else {
		server = server.bind("[::]:3000".parse()?)
	}

	let app = Router::new().route("/", get(|| async { "Hello World" }));
	server.serve(app.into_make_service()).await?;

Having the same app listening on multiple sockets greatly improves usability and integration with many systems, such as systemd. Unix domain sockets are often used to provide a service to other local services, such as reverse proxies or load balancers. A second TCP listener is often spawned e.g. for debugging or monitoring purposes.

Does this feature sound reasonable for axum-server?

native-tls implementation

Greetings!

I want to implement version with native-tls to this crate, but I don't sure which way is right.
I can just copy-paste tls_rustls folder and rewrite it to native-tls, but you use tokio-rustls which have tokio-native-tls with same API, and it will be a lot of same code.
Or, I can change current implementation through cfg flag.

Concurrent SSL handshakes

The way the accept loop is currently written, it seems as though accepting is done in serial

axum-server/src/server.rs

Lines 173 to 178 in 04bbd9f

loop {
let addr_stream = tokio::select! {
biased;
result = accept(&mut incoming) => result?,
_ = handle.wait_graceful_shutdown() => return Ok(()),
};

For HTTPS, it seems as though the SSL handshake is done in the acceptor:

pub fn bind_rustls(addr: SocketAddr, config: RustlsConfig) -> Server<RustlsAcceptor> {
let acceptor = RustlsAcceptor::new(config);
Server::bind(addr).acceptor(acceptor)

This implies that SSL handshakes cannot take place concurrently, which can cause unavailability if one user's SSL handshake is taking significant time.

Handle::listening() can result in hanging futures

If the server is dropped before binding to a socket, Handle::listening() can get stuck. This could e.g. happen in the following scenario:

use axum::Router;
use axum_server::Handle;

struct Server {
    addr: SocketAddr,
    task_handle: tokio::task::JoinHandle<()>,
    server_handle: Handle,
}

impl Server {
    async fn start(router: Router, addr: SocketAddr) -> Self {
        let handle = Handle::new();
        let server = axum_server::Server::bind(addr).handle(handle).serve(router.into_make_service());
        let task_handle = tokio::spawn(server);
        let addr = handle.listening().await; // this will hang forever if the `serve()` future fails binding
        Server {
            task_handle,
            addr,
        }
    }
}

#[tokio::main]
async fn main() {
    let srv1 = Server::start(Router::new(), ([127.0.0.1], 0).into()).await;
    let addr1 = srv1.addr;
    let srv2 = Server::start(Router::new(), addr).await; // This call never returns
}

There are some other similar scenarios, but they're all in equal in the listening() future getting stuck indefinitely if the Server never sets the address.


There's a few possible workarounds:

  1. as a caller, wrap the listening() future in a timeout
  2. make listening() return Option<SocketAddr> and let the caller handle the waiting (should be fairly straight forward, address should be available after starting to await the serve() future)
  3. wrap the running server future in a struct with the actually bound SocketAddr and enable access through a getter

I ran into this issue while implementing something similar to the example above but paired with a shutdown listener that immediately evaluated. This introduced a race condition between dropping the server future because of the shutdown listener and the server binding to the socket. If the server managed to bind to the socket, the listening() future finished, if the shutdown listener evaluated first, the listening() future got stuck.

In my case it was solvable by spawning the server task through tokio::spawn() and putting the Handle::listening call in a select!{} together with the server's JoinHandle, thus if the server task exitted (e.g. because it can't bind to the socket), the listening call would stop and I could return the error from start(). Actual code here

Does this guard against TCP connection leaks during SSL handshake?

I recently decided to port my application to the axum framework, and intend for my Rust binary to do its own SSL. This crate seems like the best way to achieve that (especially since it supports hot-swapping certificates 🎉). However, one of the issues I had with a previous web framework was severe, persistent TCP connection leaks, due in part to a lack of a timeout on accepting SSL connections (and also during http2 upgrade, but that's outside the scope of this crate). When I took a look at the source code, I couldn't help but notice that no timeouts or sleeps were incorporated into the relevant Futures. This leads me to believe that, were I to deploy my application in production, I would see TCP connection counts steadily climb as real clients have a habit of silently hanging up their connection at the worst time.

So my questions are:

  1. Does this (or hyper, or axum) impose any timeout for SSL handshake? (or otherwise prevent this form of TCP connection leakage?)
  2. If not, is there a workaround (such as implementing a custom acceptor)?
  3. Would you prefer if I write a short test case to demonstrate an issue or lack there of?

error: no rules expected the token `;`

I seem to be having trouble incorporating the example code into a toy project I am playing with to learn a bit of rust.

Not sure what he cause is, but I'm on 1.64 on fedora if that makes much of a difference

   Compiling axum-server v0.4.2
error: no rules expected the token `;`
  --> /home/fejfighter/.cargo/registry/src/github.com-1ecc6299db9ec823/axum-server-0.4.2/src/handle.rs:94:23
   |
94 |                 biased;
   |                       ^ no rules expected this token in macro call

error: no rules expected the token `;`
   --> /home/fejfighter/.cargo/registry/src/github.com-1ecc6299db9ec823/axum-server-0.4.2/src/server.rs:175:27
    |
175 |                     biased;
    |                           ^ no rules expected this token in macro call

error: no rules expected the token `;`
   --> /home/fejfighter/.cargo/registry/src/github.com-1ecc6299db9ec823/axum-server-0.4.2/src/server.rs:204:35
    |
204 | ...                   biased;
    |                             ^ no rules expected this token in macro call

error: no rules expected the token `;`
   --> /home/fejfighter/.cargo/registry/src/github.com-1ecc6299db9ec823/axum-server-0.4.2/src/server.rs:214:19
    |
214 |             biased;
    |                   ^ no rules expected this token in macro call

error: could not compile `axum-server` due to 4 previous errors

Reuse_addr / Clean shutdown

Hi,

I'm building a server which auto updates, but when it restarts I get "address in use" exceptions. As far as I can tell from hyperium/hyper#1575 I need to set reuse_addr, but I can't seem to find a way to do it through axum_server. Any chance it could be added? (I'm also using rustls).

Reloading private key and certificates

Currently there is no way to reload private key and certificates while server is running. I plan to add it.

How does that API sound:

use axum::{
    handler::get,
    Router,
};
use axum_server::TlsLoader;
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(|| async { "Hello, World!" }));

    let loader = TlsLoader::builder()
        .private_key_file("certs/key.pem")
        .certificate_file("certs/cert.pem")
        .build()
        .await
        .unwrap();

    tokio::spawn(reload_every_day(loader.clone()));

    axum_server::bind_rustls("127.0.0.1:3000")
        .tls_loader(loader)
        .serve(app)
        .await
        .unwrap();
}

async fn reload_every_day(mut loader: TlsLoader) {
    loop {
        // Sleep first since certificates are loaded after loader is built.
        sleep(Duration::from_secs(3600 * 24)).await;

        // Can be loaded with recent settings.
        // For example: Read previously provided file contents again.
        loader.load().await.unwrap();

        // Can overwrite settings and load.
        loader
            .private_key_file("certs/private_key.pem")
            .certificate_file("certs/fullchain.pem")
            .load()
            .await
            .unwrap();
    }
}

Feature Request: Extracting PROXY Protocol Header from TCP requests

As a developer of an axum-server application, which runs behind a layer 3 load balancing technology, I need to extract the clients IP, which is passed through with the PROXY Protocol:

https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt

I'd like to have axum-server optionally decode PROXY from the tcp stream and add it as an X-Forwarded-For or Forwarded header to the HTTP request.

I already started an implementation in axum-server which intercepts the tcp stream to check for PROXY header as a solution for my case.

I like the simplicity and functionality axum-server provides, and it was more straightforward for me to implement in the axum-server codebase than to write a lower level implementation from scratch using tokio to get access to the tcp stream.

I would like to know the receptiveness of the axum-server team for me putting forward a PR for this feature.

Additional context
https://www.haproxy.com/blog/use-the-proxy-protocol-to-preserve-a-clients-ip-address
https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#client-ip-preservation

Rustls 0.22 updates

On master, it seems that rustls version has been bumped but changes haven't been made. I get this when compiling:

error[E0432]: unresolved imports `rustls::Certificate`, `rustls::PrivateKey`
  --> src/tls_rustls/mod.rs:36:14
   |
36 | use rustls::{Certificate, PrivateKey, ServerConfig};
   |              ^^^^^^^^^^^  ^^^^^^^^^^ no `PrivateKey` in the root
   |              |
   |              no `Certificate` in the root
   |
   = help: consider importing one of these items instead:
           rustls::HandshakeType::Certificate
           rustls::internal::msgs::handshake::HandshakePayload::Certificate
error[E0277]: the trait bound `TlsAcceptor: From<Arc<rustls::ServerConfig>>` is not satisfied
  --> src/tls_rustls/future.rs:84:44
   |
84 | ...                   let acceptor = TlsAcceptor::from(server_config);
   |                                      ^^^^^^^^^^^ the trait `From<Arc<rustls::ServerConfig>>` is not implemented for `TlsAcceptor`
   |
   = help: the trait `From<Arc<tokio_rustls::rustls::ServerConfig>>` is implemented for `TlsAcceptor`
   = help: for that trait implementation, expected `tokio_rustls::rustls::ServerConfig`, found `rustls::ServerConfig`

error[E0599]: no method named `with_safe_defaults` found for struct `rustls::ConfigBuilder` in the current scope
   --> src/tls_rustls/mod.rs:273:10
    |
272 |       let mut config = ServerConfig::builder()
    |  ______________________-
273 | |         .with_safe_defaults()
    | |         -^^^^^^^^^^^^^^^^^^ method not found in `ConfigBuilder<ServerConfig, WantsVerifier>`
    | |_________|
    |

error[E0277]: the `?` operator can only be applied to values that implement `Try`
   --> src/tls_rustls/mod.rs:286:16
    |
286 |     let cert = rustls_pemfile::certs(&mut cert.as_ref())?;
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `?` operator cannot be applied to type `impl Iterator<Item = Result<rustls_pki_types::CertificateDer<'static>, std::io::Error>> + '_`
    |
    = help: the trait `Try` is not implemented for `impl Iterator<Item = Result<rustls_pki_types::CertificateDer<'static>, std::io::Error>> + '_`

error[E0277]: the `?` operator can only be applied to values that implement `Try`
   --> src/tls_rustls/mod.rs:288:37
    |
288 |     let mut key_vec: Vec<Vec<u8>> = rustls_pemfile::read_all(&mut key.as_ref())?
    |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `?` operator cannot be applied to type `impl Iterator<Item = Result<Item, std::io::Error>> + '_`
    |
    = help: the trait `Try` is not implemented for `impl Iterator<Item = Result<Item, std::io::Error>> + '_`

error[E0599]: no variant or associated item named `RSAKey` found for enum `Item` in the current scope
   --> src/tls_rustls/mod.rs:291:19
    |
291 |             Item::RSAKey(key) | Item::PKCS8Key(key) | Item::ECKey(key) => Some(key),
    |                   ^^^^^^ variant or associated item not found in `Item`

error[E0599]: no variant or associated item named `PKCS8Key` found for enum `Item` in the current scope
   --> src/tls_rustls/mod.rs:291:39
    |
291 |             Item::RSAKey(key) | Item::PKCS8Key(key) | Item::ECKey(key) => Some(key),
    |                                       ^^^^^^^^
    |                                       |
    |                                       variant or associated item not found in `Item`
    |                                       help: there is a variant with a similar name (notice the capitalization): `Pkcs8Key`

error[E0599]: no variant or associated item named `ECKey` found for enum `Item` in the current scope
   --> src/tls_rustls/mod.rs:291:61
    |
291 |             Item::RSAKey(key) | Item::PKCS8Key(key) | Item::ECKey(key) => Some(key),
    |                                                             ^^^^^ variant or associated item not found in `Item`

`axum::extract::ConnectInfo` is not supported

for use axum::extract::ConnectInfo, we need into_make_service_with_connect_info.

axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
   .serve(
       app.into_make_service_with_connect_info::<SocketAddr, _>()
   )
   .await
   .expect("server failed");

which is not supported in axum-server for now.

use axum::{
    handler::get,
    Router,
};

#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(|| async { "Hello, World!" }));

    axum_server::bind("127.0.0.1:3000")
        .serve(app.into_make_service_with_connect_info::<SocketAddr, _>())
        .await
        .unwrap();
}

we got

error[E0271]: type mismatch resolving `<IntoMakeServiceWithConnectInfo<Route<axum::handler::OnMethod<[closure@src\main.rs:10:44: 10:72], _, (), EmptyRouter>, EmptyRouter<_>>, SocketAddr> as tower_service::Service<Request<axum::body::Body>>>::Response == Response<_>`
  --> src\main.rs:13:10
   |
10 |     let app = Router::new().route("/", get(|| async { "Hello, World!" }));
   |                                            ----------------------------
   |                                            |        |
   |                                            |        the expected `async` block
   |                                            the expected closure
...
13 |         .serve(app.into_make_service_with_connect_info::<SocketAddr, _>())
   |          ^^^^^ expected struct `AddExtension`, found struct `Response`
   | 
  ::: C:\Users\25386\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\core\src\future\mod.rs:61:43
   |
61 | pub const fn from_generator<T>(gen: T) -> impl Future<Output = T::Return>
   |                                           ------------------------------- the expected opaque type
   |
   = note: expected struct `AddExtension<Route<axum::handler::OnMethod<[closure@src\main.rs:10:44: 10:72], _, (), EmptyRouter>, EmptyRouter<_>>, ConnectInfo<_>>`
              found struct `Response<_>`

error[E0277]: the trait bound `IntoMakeServiceWithConnectInfo<Route<axum::handler::OnMethod<[closure@src\main.rs:10:44: 10:72], _, (), EmptyRouter>, EmptyRouter<_>>, SocketAddr>: Clone` is not satisfied
  --> src\main.rs:13:16
   |
13 |         .serve(app.into_make_service_with_connect_info::<SocketAddr, _>())
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `IntoMakeServiceWithConnectInfo<Route<axum::handler::OnMethod<[closure@src\main.rs:10:44: 10:72], _, (), EmptyRouter>, EmptyRouter<_>>, SocketAddr>`

error[E0277]: the trait bound `SocketAddr: Connected<Request<axum::body::Body>>` is not satisfied
  --> src\main.rs:13:16
   |
13 |         .serve(app.into_make_service_with_connect_info::<SocketAddr, _>())
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Connected<Request<axum::body::Body>>` is not implemented for `SocketAddr`
   |
   = help: the following implementations were found:
             <SocketAddr as Connected<&hyper::server::tcp::addr_stream::AddrStream>>
   = note: required because of the requirements on the impl of `tower_service::Service<Request<axum::body::Body>>` for `IntoMakeServiceWithConnectInfo<Route<axum::handler::OnMethod<[closure@src\main.rs:10:44: 10:72], _, (), EmptyRouter>, EmptyRouter<_>>, SocketAddr>`

error: aborting due to 3 previous errors

Memory leak in server

Hi, I was reading through the code and I noticed this list of connections

let mut conns = Vec::new();

it seems to grow every time the server accepts a connection and is never cleared out, so it might be an issue for a long-running server

I tried making lots of requests to the hello_world example server and after ~500,000 requests memory usage was about 1 GB

Hybrid server with axum+tonic

I am trying to build a hybrid HTTP + gRPC server using Axum and Tonic following this example. I am struggling a bit however on how to combine this with axum-server to have both over TLS:

let app = /* axum app */;
let grpc_service = tonic::transport::Server::builder().add_service(/* some gRPC service */).into_service();
let hybrid_make_service = hybrid(app.into_make_service(), grpc_service);

// This works fine
axum::Server::bind(&sc.bind_address).serve(hybrid_make_service).await?;

// This I can't get right...
axum_server::bind_rustls(sc.bind_address.to_string())
				.private_key_file(tls_key_path)
				.certificate_file(tls_cert_path)
				.serve(hybrid_make_service)
				.await?;

The issue seems to be that axum_server requires a 'Service' whereas the regular 'bind' accepts some sort of 'IntoService' (and the hybrid function, as well as grpc_service, create this). Any ideas?

Support for openssl

Hi there!

I was interested to add support for this library to use openssl as an alternative to rustls. Is that something you'd be interested in if I were to contribute that?

Thanks :)

Any way to add hyper layers directly ?

Hi,

due to performance reasons (difference between 1ms and 100ms and 100mb vs 2GB memory usage) i need to add a hyper (not tower) layer that comes before everything for my proxy implementation. I had to rewrite my code based on your serve function and inject the proxy as the lowest service.

I was wondering if you would consider adding a mechanic like Acceptor but used to modify the final tower service before it is served ?

Graceful shutdown doesn't include upgraded connections

Hi,

When using Axum with WebSockets, I noticed having an open WebSocket doesn't stop the server from shutting down gracefully. Here's a demo repository.

My expectation was that the demo would remain running for 30 seconds, printing "beep" every second until the graceful shutdown timeout is reached.

I'm not terribly familiar with Hyper so I don't immediately see a fix, so I figured I'd start with an issue. I'm happy to work on a fix, however.

Give more control on connection tasks

Currently there is no way to shut a connection down except signaling a global shutdown.

Having this ability can be useful to detect slow clients and prevent some attacks.

`util` module for higher level features

An util module with optional util feature can be created to provide higher level features.

Some possible high level features:

  • Recording bytes for each connection. This can be useful to log client data usage.
  • Binding to multiple addresses on a single struct.
  • Http and https server on a single struct.

Naming

axum-server isn't an ideal name because this project is not strictly related to Axum, and that can be confusing. This issue is for discussion regarding options for different names.

Some initial ideas:

  • tower-hyper-server: Do we want to tie this library inherently to Hyper? This name is also long.
  • tower-http-server: Another long name.

Implement `std::future::Future` for the `Server`

It's more a discussion rather than an issue. What do you think about redesigning a library to be similar to the hyper::Server?
It will lead to the same experience with graceful_shutdown.

For example,

#[tokio::main]
async fn main() -> anyhow::Result<()> {
       
        ...
       
        let tls_config = ...;

        axum_server::bind_rustls(addr, tls_config)
            .handle(...)
            .serve(...)
            .await?;
       // vs
        axum::Server::bind(&addr)
            .serve(...)
            .with_graceful_shutdown(shutdown_signal())
            .await?;


    Ok(())
}

async fn shutdown_signal() {
    let ctrl_c = async {
        signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install signal handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }
}

Now, I need to get an handle object and control it in a separate task. Is it worth changing it to the same behavior as in hyper::Server? What do you think?

axum_server doesn't work with unreleased version of axum (0.3.0)

Lately Axum has modified the internal implementation of Router and they have started boxing the routers by default (https://github.com/tokio-rs/axum). Unfortunately these new changes don't work with axum_server.
I get following compilation error:

error[E0277]: `(dyn axum::clone_box_service::CloneService<axum::http::Request<axum::body::Body>, Response = axum::http::Response<http_body::combinators::box_body::UnsyncBoxBody<bytes::Bytes, axum::Error>>, Error = Infallible, Future = Pin<Box<(dyn futures_util::Future<Output = Result<axum::http::Response<http_body::combinators::box_body::UnsyncBoxBody<bytes::Bytes, axum::Error>>, Infallible>> + std::marker::Send + 'static)>>> + std::marker::Send + 'static)` cannot be shared between threads safely

from_pem_file() rejects valid EC keys

from_pem_file() calls eventually to config_from_pem() which restricts the keys to be of type Some(Item::RSAKey(key)) or Some(Item::PKCS8Key(key)) and this rejects a lot of EC keys. One either needs to convert it from SEC1 to PKCS8 or directly encode into DER and load the files by hand to call from_der() instead.

Adding proper support for EC keys should be as simple as adding Some(Item::ECKey(key)) to the match clause of config_from_pem(). I'd be happy to open a pull request for it.

add rationale to docs

I am curious what the main benefit to running this server instead of the default that axum crate uses.

Why should someone consider using this crate in their web API?

Async SNI callback

I would like to load TLS certificates to be used for the request based on the hostname.

A method of doing this is specifying something that implements the ResolvesServerCert trait in the cert_resolver attribute of the rustls ServerConfig. This is nice and easy to do but requires the resolve method to be synchronous.

As discussed in this issue, the correct method of asynchronously resolving certificates based on the hostname would be to use the rustls Acceptor to handle the connection before the configuration is finalized. One would then be able to asynchronously load the certificates, build the config, and generate a ServerConnection with it.

I see that axum-server has the ability to provide a custom Acceptor for a server and has an example of it.

I am, however, unable to get this to asynchronously resolve certificates for the connection. I don't know if this is because I just can't figure out how to do it correctly or if axum-server does not currently support this functionality.

Is this possible in axum-server? If not, I am more than happy to create a PR to do this; I will need some hints on what needs to be done though.

Testing master branch

Would be really nice if interested people test master branch time to time with new features.

I am doing some tests locally but it is never enough.

[Question] Is there a reload for `addr:port`? 

I’ve read the reload example which is fantastic for TLS certificate renewal, and it made me wondering if we can do something similar for the binding addr? (In case of configuration change)

Add `Server::acceptor_mut`

I have a use-case where I implemented my own Acceptor. Additionally I would like to add more configuration options, but currently it's not possible to modify the acceptor once wrapped in Server. My proposal is to add a method that can return a mutable reference to the Acceptor called Server::acceptor_mut(), bikeshedding welcome.

The alternative is to use the Server::acceptor() to add a pre-configured Acceptor, but the user-ergonomics are slightly worse that way.
Happy to make a PR if this is approved.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.