Skip to content

Commit

Permalink
Decouple the handshake part ServerWebSocket API.
Browse files Browse the repository at this point in the history
Motivation:

The server WebSocket API can control handshake implicitly (e.g. sending a message) or explicitly (accept or any WebSocket interaction). This result in a more complex implementation than it should be for such API.

Changes:

Extract the handshake API of the ServerWebSocket API in a new ServerWebSocketHandshake API for which an handler can be set when WebSocket handshake needs to be controlled.

This is a backport of Vert.x 5 server WebSocket handshake handler.

The current API is maintained but deprecated.
  • Loading branch information
vietj committed Nov 6, 2024
1 parent fcfc661 commit e87837f
Show file tree
Hide file tree
Showing 9 changed files with 386 additions and 49 deletions.
10 changes: 8 additions & 2 deletions src/main/asciidoc/http.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1907,14 +1907,20 @@ When a WebSocket connection is made to the server, the handler will be called, p
{@link examples.HTTPExamples#example51}
----

You can choose to reject the WebSocket by calling {@link io.vertx.core.http.ServerWebSocket#reject()}.
===== Server WebSocket handshake

By default, the server accepts any inbound WebSocket.

You can set a WebSocket handshake handler to control the outcome of a WebSocket handshake, i.e. accept or reject an incoming WebSocket.

You can choose to reject the WebSocket by calling {@link io.vertx.core.http.ServerWebSocketHandshake#accept()} or {@link io.vertx.core.http.ServerWebSocketHandshake#reject()}.

[source,$lang]
----
{@link examples.HTTPExamples#example52}
----

You can perform an asynchronous handshake by calling {@link io.vertx.core.http.ServerWebSocket#setHandshake} with a `Future`:
You can perform an asynchronous handshake:

[source,$lang]
----
Expand Down
48 changes: 29 additions & 19 deletions src/main/java/examples/HTTPExamples.java
Original file line number Diff line number Diff line change
Expand Up @@ -1047,29 +1047,39 @@ public void example51(HttpServer server) {

public void example52(HttpServer server) {

server.webSocketHandler(webSocket -> {
if (webSocket.path().equals("/myapi")) {
webSocket.reject();
} else {
server
.webSocketHandshakeHandler(handshake -> {
if (handshake.path().equals("/myapi")) {
handshake.reject();
} else {
handshake.accept();
}
})
.webSocketHandler(webSocket -> {
// Do something
}
});
});
}

public void exampleAsynchronousHandshake(HttpServer server) {
server.webSocketHandler(webSocket -> {
Promise<Integer> promise = Promise.promise();
webSocket.setHandshake(promise.future());
authenticate(webSocket.headers(), ar -> {
if (ar.succeeded()) {
// Terminate the handshake with the status code 101 (Switching Protocol)
// Reject the handshake with 401 (Unauthorized)
promise.complete(ar.result() ? 101 : 401);
} else {
// Will send a 500 error
promise.fail(ar.cause());
}
});
server
.webSocketHandshakeHandler(handshake -> {
authenticate(handshake.headers(), ar -> {
if (ar.succeeded()) {
if (ar.result()) {
// Terminate the handshake with the status code 101 (Switching Protocol)
handshake.accept();
} else {
// Reject the handshake with 401 (Unauthorized)
handshake.reject(401);
}
} else {
// Will send a 500 error
handshake.reject(500);
}
});
})
.webSocketHandler(webSocket -> {
// Do something
});
}

Expand Down
12 changes: 12 additions & 0 deletions src/main/java/io/vertx/core/http/HttpServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,18 @@ public interface HttpServer extends Measured {
@Fluent
HttpServer webSocketHandler(Handler<ServerWebSocket> handler);

/**
* Set a handler for WebSocket handshake.
*
* <p>When an inbound HTTP request presents a WebSocket upgrade, this handler is called first. The handler
* can chose to {@link ServerWebSocketHandshake#accept()} or {@link ServerWebSocketHandshake#reject()} the request.</p>
*
* <p>Setting no handler, implicitly accepts any HTTP request connection presenting an upgrade header and upgrades it
* to a WebSocket.</p>
*/
@Fluent
HttpServer webSocketHandshakeHandler(Handler<ServerWebSocketHandshake> handler);

/**
* @return the WebSocket handler
*/
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/io/vertx/core/http/ServerWebSocket.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ public interface ServerWebSocket extends WebSocketBase {
* terminate the WebSocket handshake.
*
* @throws IllegalStateException when the WebSocket handshake is already set
* @deprecated instead use {@link ServerWebSocketHandshake#accept()}
*/
@Deprecated
void accept();

/**
Expand All @@ -143,12 +145,17 @@ public interface ServerWebSocket extends WebSocketBase {
* You might use this method, if for example you only want to accept WebSockets with a particular path.
*
* @throws IllegalStateException when the WebSocket handshake is already set
* @deprecated instead use {@link ServerWebSocketHandshake#reject()}
*/
@Deprecated
void reject();

/**
* Like {@link #reject()} but with a {@code status}.
*
* @deprecated instead use {@link ServerWebSocketHandshake#reject(int)}
*/
@Deprecated
void reject(int status);

/**
Expand All @@ -172,12 +179,17 @@ public interface ServerWebSocket extends WebSocketBase {
* @param future the future to complete with
* @param handler the completion handler
* @throws IllegalStateException when the WebSocket has already an asynchronous result
* @deprecated instead use {@link ServerWebSocketHandshake}
*/
@Deprecated
void setHandshake(Future<Integer> future, Handler<AsyncResult<Integer>> handler);

/**
* Like {@link #setHandshake(Future, Handler)} but returns a {@code Future} of the asynchronous result
*
* @deprecated instead use {@link ServerWebSocketHandshake}
*/
@Deprecated
Future<Integer> setHandshake(Future<Integer> future);

/**
Expand Down
138 changes: 138 additions & 0 deletions src/main/java/io/vertx/core/http/ServerWebSocketHandshake.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright (c) 2011-2024 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.core.http;

import io.vertx.codegen.annotations.CacheReturn;
import io.vertx.codegen.annotations.GenIgnore;
import io.vertx.codegen.annotations.Nullable;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.net.HostAndPort;
import io.vertx.core.net.SocketAddress;

import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import java.security.cert.Certificate;
import java.util.List;

/**
* A server WebSocket handshake, allows to control acceptance or rejection of a WebSocket.
*
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
*/
@VertxGen
public interface ServerWebSocketHandshake {

/**
* Returns the HTTP headers.
*
* @return the headers
*/
MultiMap headers();

/**
* @return the WebSocket handshake scheme
*/
@Nullable
String scheme();

/**
* @return the WebSocket handshake authority
*/
@Nullable
HostAndPort authority();

/*
* @return the WebSocket handshake URI. This is a relative URI.
*/
String uri();

/**
* @return the WebSocket handshake path.
*/
String path();

/**
* @return the WebSocket handshake query string.
*/
@Nullable
String query();

/**
* Accept the WebSocket and terminate the WebSocket handshake.
* <p/>
* This method should be called from the WebSocket handler to explicitly accept the WebSocket and
* terminate the WebSocket handshake.
*
* @throws IllegalStateException when the WebSocket handshake is already set
*/
Future<ServerWebSocket> accept();

/**
* Reject the WebSocket.
* <p>
* Calling this method from the WebSocket handler when it is first passed to you gives you the opportunity to reject
* the WebSocket, which will cause the WebSocket handshake to fail by returning
* a {@literal 502} response code.
* <p>
* You might use this method, if for example you only want to accept WebSockets with a particular path.
*
* @throws IllegalStateException when the WebSocket handshake is already set
*/
default Future<Void> reject() {
// SC_BAD_GATEWAY
return reject(502);
}

/**
* Like {@link #reject()} but with a {@code status}.
*/
Future<Void> reject(int status);

/**
* @return the remote address for this connection, possibly {@code null} (e.g a server bound on a domain socket).
* If {@code useProxyProtocol} is set to {@code true}, the address returned will be of the actual connecting client.
*/
@CacheReturn
SocketAddress remoteAddress();

/**
* @return the local address for this connection, possibly {@code null} (e.g a server bound on a domain socket)
* If {@code useProxyProtocol} is set to {@code true}, the address returned will be of the proxy.
*/
@CacheReturn
SocketAddress localAddress();

/**
* @return true if this {@link io.vertx.core.http.HttpConnection} is encrypted via SSL/TLS.
*/
boolean isSsl();

/**
* @return SSLSession associated with the underlying socket. Returns null if connection is
* not SSL.
* @see javax.net.ssl.SSLSession
*/
@GenIgnore(GenIgnore.PERMITTED_TYPE)
SSLSession sslSession();

/**
* @return an ordered list of the peer certificates. Returns null if connection is
* not SSL.
* @throws javax.net.ssl.SSLPeerUnverifiedException SSL peer's identity has not been verified.
* @see SSLSession#getPeerCertificates() ()
* @see #sslSession()
*/
@GenIgnore()
List<Certificate> peerCertificates() throws SSLPeerUnverifiedException;

}
Loading

0 comments on commit e87837f

Please sign in to comment.