-
-
Notifications
You must be signed in to change notification settings - Fork 110
feat: uds #496
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat: uds #496
Conversation
It can be the three types, but let me think a bit about it, maybe we do not need to do this. I will come back to this point soon.
Yes, that method is convoluted and we are aware of that. About this, there was an attempt to extract functionality and improve the code, for example, #377, but unfortunately got stuck. The question is, do you want to do it before UDS or after? |
Im sure there doesn't need to be a
I think it makes sense to do the refactoring together with adding uds support. The uds part isn't that big of a change i think so the changes should still be manageable. |
Honestly, It sounds fine to me. I also think this should allow us to decouple But keep in mind do not disrupt existing functionality and properly advise users to give them a proper path to migrate if the case.
Feel free to go ahead. A prototype of this should be good to see. If you need any help just let me know. |
|
What about an interface like this below that could convert from pub enum Interface {
Address(IpAddr),
Path(PathBuf),
} |
| return match server_type { | ||
| ServerType::Http1(tcp_listener, addr_str) => { | ||
| tcp_listener | ||
| .set_nonblocking(true) | ||
| .with_context(|| "failed to set TCP non-blocking mode")?; | ||
|
|
||
| #[cfg(unix)] | ||
| let signals = signals::create_signals() | ||
| .with_context(|| "failed to register termination signals")?; | ||
| #[cfg(unix)] | ||
| let handle = signals.handle(); | ||
| let http1_server = HyperServer::from_tcp(tcp_listener) | ||
| .unwrap() | ||
| .tcp_nodelay(true) | ||
| .serve(router_service); | ||
|
|
||
| let http2_server = | ||
| HyperServer::builder(TlsAcceptor::new(tls, incoming)).serve(router_service); | ||
| #[cfg(unix)] | ||
| let http1_cancel_recv = Arc::new(Mutex::new(_cancel_recv)); | ||
|
|
||
| #[cfg(unix)] | ||
| let http2_cancel_recv = Arc::new(Mutex::new(_cancel_recv)); | ||
| #[cfg(unix)] | ||
| let redirect_cancel_recv = http2_cancel_recv.clone(); | ||
| #[cfg(unix)] | ||
| let http1_server = http1_server.with_graceful_shutdown(signals::wait_for_signals( | ||
| signals, | ||
| grace_period, | ||
| http1_cancel_recv, | ||
| )); | ||
|
|
||
| #[cfg(unix)] | ||
| let http2_server = http2_server.with_graceful_shutdown(signals::wait_for_signals( | ||
| signals, | ||
| grace_period, | ||
| http2_cancel_recv, | ||
| )); | ||
|
|
||
| #[cfg(windows)] | ||
| let http2_cancel_recv = Arc::new(Mutex::new(_cancel_recv)); | ||
| #[cfg(windows)] | ||
| let redirect_cancel_recv = http2_cancel_recv.clone(); | ||
|
|
||
| #[cfg(windows)] | ||
| let http2_ctrlc_recv = Arc::new(Mutex::new(Some(receiver))); | ||
| #[cfg(windows)] | ||
| let redirect_ctrlc_recv = http2_ctrlc_recv.clone(); | ||
|
|
||
| #[cfg(windows)] | ||
| let http2_server = http2_server.with_graceful_shutdown(async move { | ||
| if general.windows_service { | ||
| signals::wait_for_ctrl_c(http2_cancel_recv, grace_period).await; | ||
| } else { | ||
| signals::wait_for_ctrl_c(http2_ctrlc_recv, grace_period).await; | ||
| } | ||
| }); | ||
| #[cfg(windows)] | ||
| let http1_server = http1_server.with_graceful_shutdown(async move { | ||
| let http1_cancel_recv = if general.windows_service { | ||
| // http1_cancel_recv | ||
| Arc::new(Mutex::new(_cancel_recv)) | ||
| } else { | ||
| // http1_ctrlc_recv | ||
| Arc::new(Mutex::new(Some(receiver))) | ||
| }; | ||
| signals::wait_for_ctrl_c(http1_cancel_recv, grace_period).await; | ||
| }); | ||
|
|
||
| server_info!( | ||
| parent: tracing::info_span!("Server::start_server", ?addr_str, ?threads), | ||
| "http2 server is listening on https://{}", | ||
| addr_str | ||
| ); | ||
| server_info!( | ||
| parent: tracing::info_span!("Server::start_server", ?addr_str, ?threads), | ||
| "http1 server is listening on http://{}", | ||
| addr_str | ||
| ); | ||
|
|
||
| server_info!("press ctrl+c to shut down the server"); | ||
|
|
||
| #[cfg(unix)] | ||
| http1_server.await?; | ||
|
|
||
| // HTTP to HTTPS redirect server | ||
| if general.https_redirect { | ||
| let ip = general | ||
| .host | ||
| .parse::<IpAddr>() | ||
| .with_context(|| format!("failed to parse {} address", general.host))?; | ||
| let addr = SocketAddr::from((ip, general.https_redirect_from_port)); | ||
| let tcp_listener = TcpListener::bind(addr) | ||
| .with_context(|| format!("failed to bind to {addr} address"))?; | ||
| #[cfg(windows)] | ||
| let http1_server_task = tokio::spawn(async move { | ||
| if let Err(err) = http1_server.await { | ||
| tracing::error!("http1 server failed to start up: {:?}", err); | ||
| std::process::exit(1) | ||
| } | ||
| }); | ||
| #[cfg(windows)] | ||
| tokio::try_join!(ctrlc_task, http1_server_task)?; | ||
|
|
||
| #[cfg(windows)] | ||
| _cancel_fn(); | ||
|
|
||
| #[cfg(unix)] | ||
| handle.close(); | ||
|
|
||
| server_warn!("termination signal caught, shutting down the server execution"); | ||
| Ok(()) | ||
| } | ||
| #[cfg(feature = "http2")] | ||
| ServerType::Http2(tcp_listener, addr_str) => { | ||
| // HTTP to HTTPS redirect option | ||
| let https_redirect = general.https_redirect; | ||
| server_info!("http to https redirect: enabled={}", https_redirect); | ||
| server_info!( | ||
| parent: tracing::info_span!("Server::start_server", ?addr, ?threads), | ||
| "http1 redirect server is listening on http://{}", | ||
| addr | ||
| "http to https redirect host: {}", | ||
| general.https_redirect_host | ||
| ); | ||
| server_info!( | ||
| "http to https redirect from port: {}", | ||
| general.https_redirect_from_port | ||
| ); | ||
| server_info!( | ||
| "http to https redirect from hosts: {}", | ||
| general.https_redirect_from_hosts | ||
| ); | ||
|
|
||
| // HTTP/2 + TLS | ||
| tcp_listener | ||
| .set_nonblocking(true) | ||
| .with_context(|| "failed to set TCP non-blocking mode")?; | ||
| let listener = tokio::net::TcpListener::from_std(tcp_listener) | ||
| .with_context(|| "failed to create tokio::net::TcpListener")?; | ||
| let mut incoming = AddrIncoming::from_listener(listener).with_context(|| { | ||
| "failed to create an AddrIncoming from the current tokio::net::TcpListener" | ||
| })?; | ||
| incoming.set_nodelay(true); | ||
|
|
||
| let http2_tls_cert = match general.http2_tls_cert { | ||
| Some(v) => v, | ||
| _ => bail!("failed to initialize TLS because cert file missing"), | ||
| }; | ||
| let http2_tls_key = match general.http2_tls_key { | ||
| Some(v) => v, | ||
| _ => bail!("failed to initialize TLS because key file missing"), | ||
| }; | ||
|
|
||
| let tls = TlsConfigBuilder::new() | ||
| .cert_path(&http2_tls_cert) | ||
| .key_path(&http2_tls_key) | ||
| .build() | ||
| .with_context(|| { | ||
| "failed to initialize TLS probably because invalid cert or key file" | ||
| })?; | ||
|
|
||
| let http2_server = | ||
| HyperServer::builder(TlsAcceptor::new(tls, incoming)).serve(router_service); | ||
|
|
||
| #[cfg(unix)] | ||
| let redirect_signals = signals::create_signals() | ||
| .with_context(|| "failed to register termination signals")?; | ||
| let http2_cancel_recv = Arc::new(Mutex::new(_cancel_recv)); | ||
| #[cfg(unix)] | ||
| let redirect_handle = redirect_signals.handle(); | ||
|
|
||
| // Allowed redirect hosts | ||
| let redirect_allowed_hosts = general | ||
| .https_redirect_from_hosts | ||
| .split(',') | ||
| .map(|s| s.trim().to_owned()) | ||
| .collect::<Vec<_>>(); | ||
| if redirect_allowed_hosts.is_empty() { | ||
| bail!("https redirect allowed hosts is empty, provide at least one host or IP") | ||
| } | ||
| let redirect_cancel_recv = http2_cancel_recv.clone(); | ||
|
|
||
| // Redirect options | ||
| let redirect_opts = Arc::new(https_redirect::RedirectOpts { | ||
| https_hostname: general.https_redirect_host, | ||
| https_port: general.port, | ||
| allowed_hosts: redirect_allowed_hosts, | ||
| }); | ||
| #[cfg(unix)] | ||
| let http2_server = http2_server.with_graceful_shutdown(signals::wait_for_signals( | ||
| signals, | ||
| grace_period, | ||
| http2_cancel_recv, | ||
| )); | ||
|
|
||
| let server_redirect = HyperServer::from_tcp(tcp_listener) | ||
| .unwrap() | ||
| .tcp_nodelay(true) | ||
| .serve(make_service_fn(move |_: &AddrStream| { | ||
| let redirect_opts = redirect_opts.clone(); | ||
| let page404 = page404.clone(); | ||
| let page50x = page50x.clone(); | ||
| async move { | ||
| Ok::<_, error::Error>(service_fn(move |req| { | ||
| let redirect_opts = redirect_opts.clone(); | ||
| let page404 = page404.clone(); | ||
| let page50x = page50x.clone(); | ||
| async move { | ||
| let uri = req.uri(); | ||
| let method = req.method(); | ||
| match https_redirect::redirect_to_https(&req, redirect_opts) { | ||
| Ok(resp) => Ok(resp), | ||
| Err(status) => error_page::error_response( | ||
| uri, method, &status, &page404, &page50x, | ||
| ), | ||
| } | ||
| } | ||
| })) | ||
| } | ||
| })); | ||
| #[cfg(windows)] | ||
| let http2_cancel_recv = Arc::new(Mutex::new(_cancel_recv)); | ||
| #[cfg(windows)] | ||
| let redirect_cancel_recv = http2_cancel_recv.clone(); | ||
|
|
||
| #[cfg(windows)] | ||
| let http2_ctrlc_recv = Arc::new(Mutex::new(Some(receiver))); | ||
| #[cfg(windows)] | ||
| let redirect_ctrlc_recv = http2_ctrlc_recv.clone(); | ||
|
|
||
| #[cfg(unix)] | ||
| let server_redirect = server_redirect.with_graceful_shutdown( | ||
| signals::wait_for_signals(redirect_signals, grace_period, redirect_cancel_recv), | ||
| ); | ||
| #[cfg(windows)] | ||
| let server_redirect = server_redirect.with_graceful_shutdown(async move { | ||
| let http2_server = http2_server.with_graceful_shutdown(async move { | ||
| if general.windows_service { | ||
| signals::wait_for_ctrl_c(redirect_cancel_recv, grace_period).await; | ||
| signals::wait_for_ctrl_c(http2_cancel_recv, grace_period).await; | ||
| } else { | ||
| signals::wait_for_ctrl_c(redirect_ctrlc_recv, grace_period).await; | ||
| signals::wait_for_ctrl_c(http2_ctrlc_recv, grace_period).await; | ||
| } | ||
| }); | ||
|
|
||
| // HTTP/2 server task | ||
| let server_task = tokio::spawn(async move { | ||
| if let Err(err) = http2_server.await { | ||
| tracing::error!("http2 server failed to start up: {:?}", err); | ||
| std::process::exit(1) | ||
| } | ||
| }); | ||
| server_info!( | ||
| parent: tracing::info_span!("Server::start_server", ?addr_str, ?threads), | ||
| "http2 server is listening on https://{}", | ||
| addr_str | ||
| ); | ||
|
|
||
| // HTTP/1 redirect server task | ||
| let redirect_server_task = tokio::spawn(async move { | ||
| if let Err(err) = server_redirect.await { | ||
| tracing::error!("http1 redirect server failed to start up: {:?}", err); | ||
| std::process::exit(1) | ||
| // HTTP to HTTPS redirect server | ||
| if general.https_redirect { | ||
| let ip = general | ||
| .host | ||
| .parse::<IpAddr>() | ||
| .with_context(|| format!("failed to parse {} address", general.host))?; | ||
| let addr = SocketAddr::from((ip, general.https_redirect_from_port)); | ||
| let tcp_listener = TcpListener::bind(addr) | ||
| .with_context(|| format!("failed to bind to {addr} address"))?; | ||
| server_info!( | ||
| parent: tracing::info_span!("Server::start_server", ?addr, ?threads), | ||
| "http1 redirect server is listening on http://{}", | ||
| addr | ||
| ); | ||
| tcp_listener | ||
| .set_nonblocking(true) | ||
| .with_context(|| "failed to set TCP non-blocking mode")?; | ||
|
|
||
| #[cfg(unix)] | ||
| let redirect_signals = signals::create_signals() | ||
| .with_context(|| "failed to register termination signals")?; | ||
| #[cfg(unix)] | ||
| let redirect_handle = redirect_signals.handle(); | ||
|
|
||
| // Allowed redirect hosts | ||
| let redirect_allowed_hosts = general | ||
| .https_redirect_from_hosts | ||
| .split(',') | ||
| .map(|s| s.trim().to_owned()) | ||
| .collect::<Vec<_>>(); | ||
| if redirect_allowed_hosts.is_empty() { | ||
| bail!("https redirect allowed hosts is empty, provide at least one host or IP") | ||
| } | ||
| }); | ||
|
|
||
| server_info!("press ctrl+c to shut down the servers"); | ||
|
|
||
| #[cfg(windows)] | ||
| tokio::try_join!(ctrlc_task, server_task, redirect_server_task)?; | ||
| #[cfg(unix)] | ||
| tokio::try_join!(server_task, redirect_server_task)?; | ||
| // Redirect options | ||
| let redirect_opts = Arc::new(https_redirect::RedirectOpts { | ||
| https_hostname: general.https_redirect_host, | ||
| https_port: general.port, | ||
| allowed_hosts: redirect_allowed_hosts, | ||
| }); | ||
|
|
||
| let server_redirect = HyperServer::from_tcp(tcp_listener) | ||
| .unwrap() | ||
| .tcp_nodelay(true) | ||
| .serve(make_service_fn(move |_: &AddrStream| { | ||
| let redirect_opts = redirect_opts.clone(); | ||
| let page404 = page404.clone(); | ||
| let page50x = page50x.clone(); | ||
| async move { | ||
| Ok::<_, error::Error>(service_fn(move |req| { | ||
| let redirect_opts = redirect_opts.clone(); | ||
| let page404 = page404.clone(); | ||
| let page50x = page50x.clone(); | ||
| async move { | ||
| let uri = req.uri(); | ||
| let method = req.method(); | ||
| match https_redirect::redirect_to_https(&req, redirect_opts) | ||
| { | ||
| Ok(resp) => Ok(resp), | ||
| Err(status) => error_page::error_response( | ||
| uri, method, &status, &page404, &page50x, | ||
| ), | ||
| } | ||
| } | ||
| })) | ||
| } | ||
| })); | ||
|
|
||
| #[cfg(unix)] | ||
| let server_redirect = | ||
| server_redirect.with_graceful_shutdown(signals::wait_for_signals( | ||
| redirect_signals, | ||
| grace_period, | ||
| redirect_cancel_recv, | ||
| )); | ||
| #[cfg(windows)] | ||
| let server_redirect = server_redirect.with_graceful_shutdown(async move { | ||
| if general.windows_service { | ||
| signals::wait_for_ctrl_c(redirect_cancel_recv, grace_period).await; | ||
| } else { | ||
| signals::wait_for_ctrl_c(redirect_ctrlc_recv, grace_period).await; | ||
| } | ||
| }); | ||
|
|
||
| #[cfg(unix)] | ||
| redirect_handle.close(); | ||
| } else { | ||
| server_info!("press ctrl+c to shut down the server"); | ||
| http2_server.await?; | ||
| } | ||
| // HTTP/2 server task | ||
| let server_task = tokio::spawn(async move { | ||
| if let Err(err) = http2_server.await { | ||
| tracing::error!("http2 server failed to start up: {:?}", err); | ||
| std::process::exit(1) | ||
| } | ||
| }); | ||
|
|
||
| #[cfg(unix)] | ||
| handle.close(); | ||
| // HTTP/1 redirect server task | ||
| let redirect_server_task = tokio::spawn(async move { | ||
| if let Err(err) = server_redirect.await { | ||
| tracing::error!("http1 redirect server failed to start up: {:?}", err); | ||
| std::process::exit(1) | ||
| } | ||
| }); | ||
|
|
||
| #[cfg(windows)] | ||
| _cancel_fn(); | ||
| server_info!("press ctrl+c to shut down the servers"); | ||
|
|
||
| server_warn!("termination signal caught, shutting down the server execution"); | ||
| return Ok(()); | ||
| } | ||
| #[cfg(windows)] | ||
| tokio::try_join!(ctrlc_task, server_task, redirect_server_task)?; | ||
| #[cfg(unix)] | ||
| tokio::try_join!(server_task, redirect_server_task)?; | ||
|
|
||
| // HTTP/1 | ||
| #[cfg(unix)] | ||
| redirect_handle.close(); | ||
| } else { | ||
| server_info!("press ctrl+c to shut down the server"); | ||
| http2_server.await?; | ||
| } | ||
|
|
||
| #[cfg(unix)] | ||
| let signals = | ||
| signals::create_signals().with_context(|| "failed to register termination signals")?; | ||
| #[cfg(unix)] | ||
| let handle = signals.handle(); | ||
| #[cfg(unix)] | ||
| handle.close(); | ||
|
|
||
| tcp_listener | ||
| .set_nonblocking(true) | ||
| .with_context(|| "failed to set TCP non-blocking mode")?; | ||
| #[cfg(windows)] | ||
| _cancel_fn(); | ||
|
|
||
| let http1_server = HyperServer::from_tcp(tcp_listener) | ||
| .unwrap() | ||
| .tcp_nodelay(true) | ||
| .serve(router_service); | ||
| server_warn!("termination signal caught, shutting down the server execution"); | ||
| return Ok(()); | ||
| } | ||
| #[cfg(unix)] | ||
| ServerType::Uds(unix_listener) => { | ||
| let addr = unix_listener.listener.local_addr()?; | ||
| let http1_server = HyperServer::builder(unix_listener).serve(router_service); | ||
| let http1_cancel_recv = Arc::new(Mutex::new(_cancel_recv)); | ||
|
|
||
| #[cfg(unix)] | ||
| let http1_cancel_recv = Arc::new(Mutex::new(_cancel_recv)); | ||
| let http1_server = http1_server.with_graceful_shutdown(signals::wait_for_signals( | ||
| signals, | ||
| grace_period, | ||
| http1_cancel_recv, | ||
| )); | ||
|
|
||
| #[cfg(unix)] | ||
| let http1_server = http1_server.with_graceful_shutdown(signals::wait_for_signals( | ||
| signals, | ||
| grace_period, | ||
| http1_cancel_recv, | ||
| )); | ||
| server_info!( | ||
| parent: tracing::info_span!("Server::start_server", ?threads), | ||
| "http1 server is listening at {:?}", | ||
| addr | ||
| ); | ||
|
|
||
| #[cfg(windows)] | ||
| let http1_server = http1_server.with_graceful_shutdown(async move { | ||
| let http1_cancel_recv = if general.windows_service { | ||
| // http1_cancel_recv | ||
| Arc::new(Mutex::new(_cancel_recv)) | ||
| } else { | ||
| // http1_ctrlc_recv | ||
| Arc::new(Mutex::new(Some(receiver))) | ||
| }; | ||
| signals::wait_for_ctrl_c(http1_cancel_recv, grace_period).await; | ||
| }); | ||
| server_info!("press ctrl+c to shut down the server"); | ||
|
|
||
| server_info!( | ||
| parent: tracing::info_span!("Server::start_server", ?addr_str, ?threads), | ||
| "http1 server is listening on http://{}", | ||
| addr_str | ||
| ); | ||
| http1_server.await?; | ||
|
|
||
| server_info!("press ctrl+c to shut down the server"); | ||
| handle.close(); | ||
|
|
||
| #[cfg(unix)] | ||
| http1_server.await?; | ||
| if let Some(path) = addr.as_pathname() { | ||
| tokio::fs::remove_file(path).await?; | ||
| } | ||
|
|
||
| #[cfg(windows)] | ||
| let http1_server_task = tokio::spawn(async move { | ||
| if let Err(err) = http1_server.await { | ||
| tracing::error!("http1 server failed to start up: {:?}", err); | ||
| std::process::exit(1) | ||
| server_warn!("termination signal caught, shutting down the server execution"); | ||
| Ok(()) | ||
| } | ||
| }); | ||
| #[cfg(windows)] | ||
| tokio::try_join!(ctrlc_task, http1_server_task)?; | ||
|
|
||
| #[cfg(windows)] | ||
| _cancel_fn(); | ||
|
|
||
| #[cfg(unix)] | ||
| handle.close(); | ||
|
|
||
| server_warn!("termination signal caught, shutting down the server execution"); | ||
| Ok(()) | ||
| }; |
Check failure
Code scanning / clippy
unneeded return statement Error
Description
This is a very crude proof of concept for implementing unix domain socket support. Currently only uds works and the path is hardcoded to
/tmp/sws.sock. I wanted to have some feedback first, before fully implementing this. Here are a few points i would like to have feedback on / discuss:http2boolean with alisten_type(or similar) String.start_servermethod is a bit too long imo, does it make sense to have seperate start functions for each type? On the other hand there is already some duplicate code in there, and splitting that into functions would make it harder to reuse code across listeners.Related Issue
closes #484
Motivation and Context
See #484
I mainly want this because remembering paths is simpler than remembering ports, and for the performance.
How Has This Been Tested?
Tested functionality with curl, works as expected.
Screenshots (if appropriate):