feat: prepare v2 http server

This commit is contained in:
Tien Do Nam
2025-11-18 19:39:53 +01:00
parent 03fbd70607
commit 5ac1e66026
4 changed files with 105 additions and 24 deletions
+1
View File
@@ -1,2 +1,3 @@
pub(crate) mod v2;
pub(crate) mod v3;
pub(crate) mod web;
+29
View File
@@ -0,0 +1,29 @@
use hyper::body::Incoming;
use hyper::StatusCode;
use crate::http::dto::{RegisterDto, RegisterResponseDto};
use crate::http::server::{AppState, JsonResponse, RequestClientInfo};
use crate::http::server::collect_to_json::CollectToJson;
use crate::http::server::error::AppError;
pub(crate) async fn register(
body: Incoming,
state: AppState,
client_info: RequestClientInfo,
) -> Result<JsonResponse<RegisterResponseDto>, AppError> {
let payload = body.collect_to_json::<RegisterDto>().await?;
let info = state.info.lock().await.clone();
let has_web_interface = state.web.lock().await.is_some();
Ok(JsonResponse {
status: StatusCode::OK,
body: RegisterResponseDto {
alias: info.alias,
version: info.version,
device_model: info.device_model,
device_type: info.device_type,
token: info.token,
has_web_interface,
},
})
}
+70 -18
View File
@@ -6,6 +6,7 @@ mod error;
use crate::crypto::cert::public_key_from_cert_der;
use crate::http::dto::ErrorResponse;
use crate::http::server::client_cert_verifier::CustomClientCertVerifier;
use crate::http::server::controller::web::WebPageState;
use crate::http::server::error::AppError;
use crate::http::state::ClientInfo;
use bytes::Bytes;
@@ -24,7 +25,6 @@ use std::num::NonZeroUsize;
use std::ops::Deref;
use std::sync::Arc;
use tokio::sync::{oneshot, Mutex};
use crate::http::server::controller::web::WebPageState;
#[derive(Clone)]
struct AppState {
@@ -67,6 +67,7 @@ impl LsHttpServer {
port: u16,
tls_config: Option<TlsConfig>,
info: ClientInfo,
legacy_enabled: bool,
) -> anyhow::Result<LsHttpServer> {
let ipv4_socket_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), port);
let ipv6_socket_addr = SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), port);
@@ -79,11 +80,11 @@ impl LsHttpServer {
let state = state.clone();
async move {
tokio::select! {
_ = start_server_with_addr(ipv4_socket_addr, tls_config.clone(), state.clone()) => {
_ = start_server_with_addr(ipv4_socket_addr, tls_config.clone(), state.clone(), legacy_enabled) => {
tracing::info!("Server stopped on: {}", ipv4_socket_addr);
}
_ = async {
if start_server_with_addr(ipv6_socket_addr, tls_config, state).await.is_err() {
if start_server_with_addr(ipv6_socket_addr, tls_config, state, legacy_enabled).await.is_err() {
tracing::warn!("Failed to start server on: {}", ipv6_socket_addr);
// Keep the future running forever, so we continue using "ipv4 only" even if ipv6 fails.
@@ -124,6 +125,7 @@ async fn start_server_with_addr(
socket_addr: SocketAddr,
tls_config: Option<TlsConfig>,
app_state: AppState,
legacy_enabled: bool,
) -> anyhow::Result<()> {
let _ = rustls::crypto::ring::default_provider().install_default();
@@ -177,7 +179,7 @@ async fn start_server_with_addr(
req.extensions_mut()
.insert::<RequestClientInfo>(client_info.clone());
req.extensions_mut().insert::<AppState>(app_state.clone());
handle_request(req)
handle_request(req, legacy_enabled)
}),
)
.await
@@ -194,7 +196,7 @@ async fn start_server_with_addr(
},
);
req.extensions_mut().insert::<AppState>(app_state.clone());
handle_request(req)
handle_request(req, legacy_enabled)
}),
)
.await
@@ -251,19 +253,24 @@ impl RequestClientInfo {
}
}
async fn handle_request(req: Request<Incoming>) -> Result<Response<Full<Bytes>>, hyper::Error> {
Ok(handle_request_inner(req).await.unwrap_or_else(|err| {
tracing::error!("Error handling request: {err:?}");
JsonResponse {
status: err.status,
body: ErrorResponse {
message: err
.message
.unwrap_or_else(|| "Internal Server Error".to_string()),
},
}
.into_response()
}))
async fn handle_request(
req: Request<Incoming>,
legacy_enabled: bool,
) -> Result<Response<Full<Bytes>>, hyper::Error> {
Ok(handle_request_inner(req, legacy_enabled)
.await
.unwrap_or_else(|err| {
tracing::error!("Error handling request: {err:?}");
JsonResponse {
status: err.status,
body: ErrorResponse {
message: err
.message
.unwrap_or_else(|| "Internal Server Error".to_string()),
},
}
.into_response()
}))
}
struct JsonResponse<T: Serialize> {
@@ -291,6 +298,7 @@ impl<T: Serialize> JsonResponse<T> {
async fn handle_request_inner(
mut req: Request<Incoming>,
legacy_enabled: bool,
) -> Result<Response<Full<Bytes>>, AppError> {
let Some(state) = req.extensions_mut().remove::<AppState>() else {
return Err(AppError::status(StatusCode::INTERNAL_SERVER_ERROR, None));
@@ -301,6 +309,50 @@ async fn handle_request_inner(
};
match (req.method(), req.uri().path()) {
(&Method::POST, "/api/localsend/v2/register") => {
if !legacy_enabled {
return Err(AppError::status(StatusCode::NOT_FOUND, None));
}
Ok(
controller::v2::register(req.into_body(), state, client_info)
.await?
.into_response(),
)
}
(&Method::POST, "/api/localsend/v2/prepare-upload") => {
if !legacy_enabled {
return Err(AppError::status(StatusCode::NOT_FOUND, None));
}
Ok(
controller::v2::register(req.into_body(), state, client_info)
.await?
.into_response(),
)
}
(&Method::POST, "/api/localsend/v2/upload") => {
if !legacy_enabled {
return Err(AppError::status(StatusCode::NOT_FOUND, None));
}
Ok(
controller::v2::register(req.into_body(), state, client_info)
.await?
.into_response(),
)
}
(&Method::POST, "/api/localsend/v2/cancel") => {
if !legacy_enabled {
return Err(AppError::status(StatusCode::NOT_FOUND, None));
}
Ok(
controller::v2::register(req.into_body(), state, client_info)
.await?
.into_response(),
)
}
(&Method::POST, "/api/localsend/v3/nonce") => {
Ok(
controller::v3::nonce_exchange(req.into_body(), state, client_info)
+5 -6
View File
@@ -6,6 +6,7 @@ mod webrtc;
use crate::crypto::token;
use crate::http::client::LsHttpClient;
use crate::http::dto::{PrepareUploadRequestDto, ProtocolType, RegisterDto};
use crate::http::server::TlsConfig;
use crate::model::discovery::DeviceType;
use crate::webrtc::signaling::{ClientInfo, WsServerMessage};
@@ -19,7 +20,6 @@ use tokio::io;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::{mpsc, oneshot};
use tracing::Level;
use crate::http::dto::{PrepareUploadRequestDto, ProtocolType, RegisterDto};
#[tokio::main]
#[cfg(feature = "full")]
@@ -141,6 +141,7 @@ async fn server_test() -> Result<()> {
private_key: PRIVATE_KEY.to_string(),
}),
client_info,
true,
)
.await?;
tokio::time::sleep(std::time::Duration::from_secs(u64::MAX)).await;
@@ -151,11 +152,9 @@ async fn server_test() -> Result<()> {
async fn client_test() -> Result<()> {
let client = LsHttpClient::try_new(PRIVATE_KEY, CERT)?;
let nonce = client.nonce(
&ProtocolType::Https,
"localhost",
53317,
).await?;
let nonce = client
.nonce(&ProtocolType::Https, "localhost", 53317)
.await?;
println!("Received Nonce: {}", nonce);