use http_body_util::Full; use hyper::body::{Bytes, Incoming}; use hyper::header::{self, HeaderValue}; use hyper::{HeaderMap, Request, Response, StatusCode}; use serde_json::Value; use crate::SocketError; use super::SocketHub; pub(super) type HttpRequest = Request; pub(super) type HttpResponse = Response>; pub(super) async fn authenticate( request: &HttpRequest, hub: &SocketHub, ) -> Result<(i64, String), HttpResponse> { // 三条来源,按可信度排序: // 1. `Authorization: Bearer`(服务端/Node 客户端); // 2. 会话 cookie(浏览器握手拿不到自定义头,这是唯一可用的带 token 方式); // 3. 都不带 → 返回 0,等客户端在 Socket.IO CONNECT 包里用 `auth.token` 补交 // (有 `socket.auth_timeout` 兜底)。 let token = bearer_token(request) .map(str::to_owned) .or_else(|| session_token(request, &hub.options().session_cookie)); let Some(token) = token else { if request.headers().contains_key(header::AUTHORIZATION) { return Err(unauthorized("missing bearer token")); } return Ok((0, String::new())); }; hub.authenticate_token(&token) .await .map(|user| (user, token.clone())) .map_err(auth_error_response) } /// 从 `Cookie` 头里取会话 token(名字由 `socket.session_cookie` 指定)。 fn session_token(request: &HttpRequest, cookie: &str) -> Option { if cookie.trim().is_empty() { return None; } let header = request.headers().get(header::COOKIE)?.to_str().ok()?; header .split(';') .filter_map(|pair| pair.split_once('=')) .find_map(|(name, value)| { (name.trim() == cookie && !value.trim().is_empty()) .then(|| value.trim().trim_matches('"').to_string()) }) } pub(super) fn empty_response(status: StatusCode) -> HttpResponse { let mut response = Response::new(Full::new(Bytes::new())); *response.status_mut() = status; response } pub(super) fn json_response(status: StatusCode, body: Value) -> HttpResponse { let mut response = Response::new(Full::new(Bytes::from(body.to_string()))); *response.status_mut() = status; response.headers_mut().insert( header::CONTENT_TYPE, HeaderValue::from_static("application/json"), ); response .headers_mut() .insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store")); response } pub(super) fn engine_error(code: u16, message: &str) -> HttpResponse { json_response( StatusCode::BAD_REQUEST, serde_json::json!({ "code": code, "message": message, }), ) } pub(super) fn text_response(body: String) -> HttpResponse { let mut response = Response::new(Full::new(Bytes::from(body))); response.headers_mut().insert( header::CONTENT_TYPE, HeaderValue::from_static("text/plain; charset=utf-8"), ); response .headers_mut() .insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store")); response } fn unauthorized(message: &str) -> HttpResponse { json_response( StatusCode::UNAUTHORIZED, serde_json::json!({ "code": "UNAUTHENTICATED", "message": message, }), ) } fn auth_error_response(error: SocketError) -> HttpResponse { match error { SocketError::Rejected(message) => unauthorized(&message), SocketError::Unavailable(message) => json_response( StatusCode::SERVICE_UNAVAILABLE, serde_json::json!({ "code": "UNAVAILABLE", "message": message, }), ), SocketError::Config(message) => json_response( StatusCode::INTERNAL_SERVER_ERROR, serde_json::json!({ "code": "INTERNAL", "message": message, }), ), } } pub(super) fn cors(headers: &HeaderMap, mut response: HttpResponse) -> HttpResponse { let origin = headers .get(header::ORIGIN) .and_then(|value| value.to_str().ok()) .unwrap_or("*"); if let Ok(value) = HeaderValue::from_str(origin) { response .headers_mut() .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, value); if origin != "*" { response.headers_mut().insert( header::ACCESS_CONTROL_ALLOW_CREDENTIALS, HeaderValue::from_static("true"), ); } } response .headers_mut() .insert(header::VARY, HeaderValue::from_static("Origin")); response.headers_mut().insert( header::ACCESS_CONTROL_ALLOW_METHODS, HeaderValue::from_static("GET, POST, OPTIONS"), ); response.headers_mut().insert( header::ACCESS_CONTROL_ALLOW_HEADERS, HeaderValue::from_static("authorization, content-type"), ); response } fn bearer_token(request: &HttpRequest) -> Option<&str> { let value = request .headers() .get(header::AUTHORIZATION)? .to_str() .ok()?; let (scheme, token) = value.split_once(' ')?; if !scheme.eq_ignore_ascii_case("bearer") { return None; } let token = token.trim(); (!token.is_empty()).then_some(token) }