fix(http): reject unsupported codecs with 415 and the codec as body

This commit is contained in:
2026-08-08 13:42:08 +01:00
parent aa1d4e8f67
commit 7d9b3b6285
4 changed files with 35 additions and 31 deletions
+4 -7
View File
@@ -63,7 +63,7 @@ pub struct ServerInfo {
pub struct HttpServer { pub struct HttpServer {
pub offer_tx: Sender<(i32, i32, String)>, pub offer_tx: Sender<(i32, i32, String)>,
pub accept_rx: async_broadcast::InactiveReceiver<(i32, Option<String>)>, pub accept_rx: async_broadcast::InactiveReceiver<(i32, Result<String, String>)>,
pub appstate: Arc<Mutex<AppState>>, pub appstate: Arc<Mutex<AppState>>,
pub request_count: AtomicI32, pub request_count: AtomicI32,
pub db: DatabaseConnection, pub db: DatabaseConnection,
@@ -456,18 +456,15 @@ async fn stream_handler(
}) })
.await .await
{ {
Ok(Some(Some(reply))) => { Ok(Some(Ok(reply))) => {
return Ok(Response::builder() return Ok(Response::builder()
.status(StatusCode::CREATED) .status(StatusCode::CREATED)
.header("content-type", "application/sdp") .header("content-type", "application/sdp")
.body(reply) .body(reply)
.unwrap()); .unwrap());
} }
Ok(Some(None)) => { Ok(Some(Err(codec))) => {
return Ok(Response::builder() return Err(HttpError::WhepCodecError(codec));
.status(StatusCode::UNSUPPORTED_MEDIA_TYPE)
.body(String::new())
.unwrap());
} }
Ok(None) => { Ok(None) => {
info!( info!(
+11 -6
View File
@@ -25,6 +25,8 @@ pub enum HttpError {
Unprocessable(String), Unprocessable(String),
#[error("not acceptable: {0}")] #[error("not acceptable: {0}")]
NotAcceptable(String), NotAcceptable(String),
#[error("unsupported codec: {0}")]
WhepCodecError(String),
#[error("internal error")] #[error("internal error")]
Internal, Internal,
} }
@@ -40,6 +42,7 @@ impl HttpError {
Self::BadRequest(_) => StatusCode::BAD_REQUEST, Self::BadRequest(_) => StatusCode::BAD_REQUEST,
Self::Unprocessable(_) => StatusCode::UNPROCESSABLE_ENTITY, Self::Unprocessable(_) => StatusCode::UNPROCESSABLE_ENTITY,
Self::NotAcceptable(_) => StatusCode::NOT_ACCEPTABLE, Self::NotAcceptable(_) => StatusCode::NOT_ACCEPTABLE,
Self::WhepCodecError(_) => StatusCode::UNSUPPORTED_MEDIA_TYPE,
} }
} }
} }
@@ -47,12 +50,14 @@ impl HttpError {
impl IntoResponse for HttpError { impl IntoResponse for HttpError {
fn into_response(self) -> Response<Body> { fn into_response(self) -> Response<Body> {
let status = self.status(); let status = self.status();
// Only surface a message body for client (4xx) errors; keep an empty let body = match self {
// body for 5xx so internal details aren't leaked. // The WHEP client (frontend) reads this body as the rejected
let body = if status.is_client_error() { // codec, so send it bare rather than the full error string.
self.to_string() Self::WhepCodecError(codec) => codec,
} else { // Only surface a message body for client (4xx) errors; keep an
String::new() // empty body for 5xx so internal details aren't leaked.
e if status.is_client_error() => e.to_string(),
_ => String::new(),
}; };
(status, body).into_response() (status, body).into_response()
} }
+1 -1
View File
@@ -102,7 +102,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
// String_Label, // String_Label,
// Offer_body // Offer_body
let (answer_tx, answer_rx) = broadcast::<(i32, Option<String>)>(64); let (answer_tx, answer_rx) = broadcast::<(i32, Result<String, String>)>(64);
// Request_Id, // Request_Id,
// Answer_body // Answer_body
+16 -14
View File
@@ -18,11 +18,11 @@ use crate::{
}; };
pub struct Webrtc { pub struct Webrtc {
pub offer_rx: Receiver<(i32, i32, String)>, pub offer_rx: Receiver<(i32, i32, String)>,
pub accept_tx: async_broadcast::Sender<(i32, Option<String>)>, pub accept_tx: async_broadcast::Sender<(i32, Result<String, String>)>,
pub sessions_ref: Arc<DashMap<i32, StreamSession>>, pub sessions_ref: Arc<DashMap<i32, StreamSession>>,
pub proxy: Arc<WebrtcProxy>, pub proxy: Arc<WebrtcProxy>,
pub db: DatabaseConnection, pub db: DatabaseConnection,
} }
impl Webrtc { impl Webrtc {
@@ -40,7 +40,7 @@ impl Webrtc {
request_id, request_id,
stream_id, "stream key not found in DB, rejecting offer" stream_id, "stream key not found in DB, rejecting offer"
); );
self.accept_tx.broadcast((request_id, None)).await.unwrap(); self.accept_tx.broadcast((request_id, Err(String::new()))).await.unwrap();
continue; continue;
} }
if let Err(ref e) = stream_key { if let Err(ref e) = stream_key {
@@ -48,7 +48,7 @@ impl Webrtc {
request_id, request_id,
stream_id, "DB error looking up stream key: {:?}", e stream_id, "DB error looking up stream key: {:?}", e
); );
self.accept_tx.broadcast((request_id, None)).await.unwrap(); self.accept_tx.broadcast((request_id, Err(String::new()))).await.unwrap();
continue; continue;
} }
@@ -134,7 +134,7 @@ impl Webrtc {
Ok(sdp) => sdp, Ok(sdp) => sdp,
Err(e) => { Err(e) => {
warn!(request_id, stream_id, "malformed SDP offer: {:?}", e); warn!(request_id, stream_id, "malformed SDP offer: {:?}", e);
self.accept_tx.broadcast((request_id, None)).await.unwrap(); self.accept_tx.broadcast((request_id, Err(String::new()))).await.unwrap();
continue; continue;
} }
}; };
@@ -179,7 +179,10 @@ impl Webrtc {
"no video codec negotiated — browser likely doesn't support {:?}; rejecting offer", "no video codec negotiated — browser likely doesn't support {:?}; rejecting offer",
stream_codec stream_codec
); );
self.accept_tx.broadcast((request_id, None)).await.unwrap(); self.accept_tx
.broadcast((request_id, Err(format!("{:?}", stream_codec))))
.await
.unwrap();
continue; continue;
} }
@@ -193,7 +196,7 @@ impl Webrtc {
debug!(request_id, "sending answer back"); debug!(request_id, "sending answer back");
self.accept_tx self.accept_tx
.broadcast((request_id, Some(answer_sdp))) .broadcast((request_id, Ok(answer_sdp)))
.await .await
.unwrap(); .unwrap();
@@ -310,8 +313,8 @@ impl Webrtc {
} }
_ => {} _ => {}
}, },
Err(e) => { Err(_e) => {
error!("poll_output error (connection closing): {:?}", e); // error!("poll_output error (connection closing): {:?}", e);
return; return;
} }
} }
@@ -487,6 +490,5 @@ impl Webrtc {
{ {
warn!("RTP write error: {:?}", e); warn!("RTP write error: {:?}", e);
} }
}
} }
}