Skip to content
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

feat(http): support websocket server #481

Merged
merged 32 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
7b141ea
feat(http): support websocket server
StellarisW Aug 6, 2024
9ad6efd
feat(http): support websocket server
StellarisW Aug 6, 2024
06e6dad
feat(http): support websocket server
StellarisW Aug 7, 2024
5ccc9ee
feat(http): support websocket server
StellarisW Aug 7, 2024
6da65f5
feat(http): support websocket server
StellarisW Aug 7, 2024
712586d
feat(http): support websocket server
StellarisW Aug 7, 2024
44f12ac
feat(http): support websocket server
StellarisW Aug 7, 2024
4e160dc
feat(http): support websocket server
StellarisW Aug 7, 2024
618d088
feat(http): support websocket server
StellarisW Aug 7, 2024
aaee04e
feat(http): support websocket server
StellarisW Aug 7, 2024
e8dc558
feat(http): support websocket server
StellarisW Aug 7, 2024
f6ab717
feat(http): support websocket server
StellarisW Aug 7, 2024
152086b
feat(http): support websocket server
StellarisW Aug 7, 2024
b1aab0f
feat(http): support websocket server
StellarisW Aug 7, 2024
62cf986
feat(http): support websocket server
StellarisW Aug 7, 2024
3800ea5
feat(http): support websocket server
StellarisW Aug 7, 2024
78d36fd
feat(http): support websocket server
StellarisW Aug 7, 2024
9599b63
feat(http): support websocket server
StellarisW Aug 8, 2024
0adfbd6
feat(http): support websocket server
StellarisW Aug 8, 2024
7672b4e
feat(http): support websocket server
StellarisW Aug 8, 2024
cc17a1d
feat(http): support websocket server
StellarisW Aug 8, 2024
4bd0e16
feat(http): support websocket server
StellarisW Aug 8, 2024
05b2e82
feat(http): support websocket server
StellarisW Aug 8, 2024
d8fa434
feat(http): support websocket server
StellarisW Aug 8, 2024
105c741
feat(http): support websocket server
StellarisW Aug 8, 2024
f047d68
feat(http): support websocket server
StellarisW Aug 8, 2024
ffde3a4
feat(http): support websocket server
StellarisW Aug 8, 2024
2942754
feat(http): support websocket server
StellarisW Aug 8, 2024
63a55e8
feat(http): support websocket server
StellarisW Aug 8, 2024
15ae4c7
feat(http): support websocket server
StellarisW Aug 8, 2024
865439a
feat(http): support websocket server
StellarisW Aug 8, 2024
d83b57d
Merge branch 'main' into feat(http)/ws
yukiiiteru Aug 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ webpki-roots = "0.26"
tokio-rustls = "0.25"
native-tls = "0.2"
tokio-native-tls = "0.3"
tokio-tungstenite="0.23.1"

[profile.release]
opt-level = 3
Expand Down
8 changes: 7 additions & 1 deletion volo-http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,13 @@ tokio = { workspace = true, features = [
tokio-util = { workspace = true, features = ["io"] }
tracing.workspace = true

# =====optional=====
# server optional
matchit = { workspace = true, optional = true }

# protocol optional
tokio-tungstenite = { workspace = true, optional = true }

# tls optional
tokio-rustls = { workspace = true, optional = true }
tokio-native-tls = { workspace = true, optional = true }
Expand All @@ -86,11 +90,13 @@ default = []
default_client = ["client", "json"]
default_server = ["server", "query", "form", "json"]

full = ["client", "server", "rustls", "cookie", "query", "form", "json", "tls"]
full = ["client", "server", "rustls", "cookie", "query", "form", "json", "tls", "ws"]

client = ["hyper/client", "hyper/http1"] # client core
server = ["hyper/server", "hyper/http1", "dep:matchit"] # server core

ws = ["dep:tokio-tungstenite"]

tls = ["rustls"]
__tls = []
rustls = ["__tls", "dep:tokio-rustls", "volo/rustls"]
Expand Down
66 changes: 66 additions & 0 deletions volo-http/src/error/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,69 @@ pub fn body_collection_error() -> ExtractBodyError {
pub fn invalid_content_type() -> ExtractBodyError {
ExtractBodyError::Generic(GenericRejectionError::InvalidContentType)
}

/// Rejection used for [`WebSocketUpgrade`](crate::server::utils::WebSocketUpgrade).
#[derive(Debug)]
#[non_exhaustive]
pub enum WebSocketUpgradeRejectionError {
/// The request method must be `GET`
MethodNotGet,
/// The HTTP version is not supported
InvalidHttpVersion,
/// The `Connection` header is invalid
InvalidConnectionHeader,
/// The `Upgrade` header is invalid
InvalidUpgradeHeader,
/// The `Sec-WebSocket-Version` header is invalid
InvalidWebSocketVersionHeader,
/// The `Sec-WebSocket-Key` header is missing
WebSocketKeyHeaderMissing,
/// The connection is not upgradable
ConnectionNotUpgradable,
}

impl WebSocketUpgradeRejectionError {
/// Convert the [`WebSocketUpgradeRejectionError`] to the corresponding [`StatusCode`]
pub fn to_status_code(self) -> StatusCode {
yukiiiteru marked this conversation as resolved.
Show resolved Hide resolved
match self {
Self::MethodNotGet => StatusCode::METHOD_NOT_ALLOWED,
Self::InvalidHttpVersion => StatusCode::HTTP_VERSION_NOT_SUPPORTED,
Self::InvalidConnectionHeader => StatusCode::BAD_REQUEST,
Self::InvalidUpgradeHeader => StatusCode::BAD_REQUEST,
Self::InvalidWebSocketVersionHeader => StatusCode::BAD_REQUEST,
Self::WebSocketKeyHeaderMissing => StatusCode::BAD_REQUEST,
Self::ConnectionNotUpgradable => StatusCode::UPGRADE_REQUIRED,
}
}
}

impl Error for WebSocketUpgradeRejectionError {}

impl fmt::Display for WebSocketUpgradeRejectionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MethodNotGet => write!(f, "Request method must be 'GET'"),
Self::InvalidHttpVersion => {
write!(f, "Http version not support, only support HTTP 1.1 for now")
}
Self::InvalidConnectionHeader => {
write!(f, "Connection header did not include 'upgrade'")
}
Self::InvalidUpgradeHeader => write!(f, "`Upgrade` header did not include 'websocket'"),
Self::InvalidWebSocketVersionHeader => {
write!(f, "`Sec-WebSocket-Version` header did not include '13'")
}
Self::WebSocketKeyHeaderMissing => write!(f, "`Sec-WebSocket-Key` header missing"),
Self::ConnectionNotUpgradable => write!(
f,
"WebSocket request couldn't be upgraded since no upgrade state was present"
),
}
}
}

impl IntoResponse for WebSocketUpgradeRejectionError {
fn into_response(self) -> ServerResponse {
self.to_status_code().into_response()
}
}
6 changes: 4 additions & 2 deletions volo-http/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,13 +443,15 @@ async fn serve_conn<S>(
let notified = exit_notify.notified();
tokio::pin!(notified);

let mut http_conn = server.serve_connection(TokioIo::new(conn), service);
let mut http_conn = server
.serve_connection(TokioIo::new(conn), service)
.with_upgrades();

tokio::select! {
_ = &mut notified => {
tracing::trace!("[VOLO] closing a pending connection");
// Graceful shutdown.
hyper::server::conn::http1::Connection::graceful_shutdown(
hyper::server::conn::http1::UpgradeableConnection::graceful_shutdown(
Pin::new(&mut http_conn)
);
// Continue to poll this connection until shutdown can finish.
Expand Down
5 changes: 5 additions & 0 deletions volo-http/src/server/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ mod serve_dir;

pub use file_response::FileResponse;
pub use serve_dir::ServeDir;

#[cfg(feature = "ws")]
pub mod ws;
#[cfg(feature = "ws")]
pub use self::ws::{Config as WebSocketConfig, Message, WebSocket, WebSocketUpgrade};
Loading
Loading