Correlated messages at HTTP adapter + WebSocket level
Issue: SPR-16966
This commit is contained in:
@@ -48,6 +48,9 @@ public class HandshakeInfo {
|
||||
|
||||
private final Map<String, Object> attributes;
|
||||
|
||||
@Nullable
|
||||
private final String logPrefix;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor with information about the handshake.
|
||||
@@ -57,7 +60,7 @@ public class HandshakeInfo {
|
||||
* @param protocol the negotiated sub-protocol (may be {@code null})
|
||||
*/
|
||||
public HandshakeInfo(URI uri, HttpHeaders headers, Mono<Principal> principal, @Nullable String protocol) {
|
||||
this(uri, headers, principal, protocol, Collections.emptyMap());
|
||||
this(uri, headers, principal, protocol, Collections.emptyMap(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,10 +70,12 @@ public class HandshakeInfo {
|
||||
* @param principal the principal for the session
|
||||
* @param protocol the negotiated sub-protocol (may be {@code null})
|
||||
* @param attributes initial attributes to use for the WebSocket session
|
||||
* @param logPrefix log prefix used during the handshake for correlating log
|
||||
* messages, if any.
|
||||
* @since 5.1
|
||||
*/
|
||||
public HandshakeInfo(URI uri, HttpHeaders headers, Mono<Principal> principal,
|
||||
@Nullable String protocol, Map<String, Object> attributes) {
|
||||
@Nullable String protocol, Map<String, Object> attributes, @Nullable String logPrefix) {
|
||||
|
||||
Assert.notNull(uri, "URI is required");
|
||||
Assert.notNull(headers, "HttpHeaders are required");
|
||||
@@ -82,6 +87,7 @@ public class HandshakeInfo {
|
||||
this.principalMono = principal;
|
||||
this.protocol = protocol;
|
||||
this.attributes = attributes;
|
||||
this.logPrefix = logPrefix;
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +132,16 @@ public class HandshakeInfo {
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* A log prefix used in the handshake to correlate log messages, if any.
|
||||
* @return a log prefix, or {@code null}
|
||||
* @since 5.1
|
||||
*/
|
||||
@Nullable
|
||||
public String getLogPrefix() {
|
||||
return this.logPrefix;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -134,10 +134,31 @@ public class WebSocketMessage {
|
||||
return this.type.hashCode() * 29 + this.payload.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WebSocket " + this.type.name() + " message (" + this.payload.readableByteCount() + " bytes)";
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket message types.
|
||||
*/
|
||||
public enum Type { TEXT, BINARY, PING, PONG }
|
||||
public enum Type {
|
||||
/**
|
||||
* Text WebSocket message.
|
||||
*/
|
||||
TEXT,
|
||||
/**
|
||||
* Binary WebSocket message.
|
||||
*/
|
||||
BINARY,
|
||||
/**
|
||||
* WebSocket ping.
|
||||
*/
|
||||
PING,
|
||||
/**
|
||||
* WebSocket pong.
|
||||
*/
|
||||
PONG;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
|
||||
@Nullable
|
||||
private final MonoProcessor<Void> completionMono;
|
||||
|
||||
private final WebSocketReceivePublisher receivePublisher = new WebSocketReceivePublisher();
|
||||
private final WebSocketReceivePublisher receivePublisher;
|
||||
|
||||
@Nullable
|
||||
private volatile WebSocketSendProcessor sendProcessor;
|
||||
@@ -90,13 +90,19 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
|
||||
* Alternative constructor with completion {@code Mono<Void>} to propagate
|
||||
* the session completion (success or error) (for client-side use).
|
||||
*/
|
||||
public AbstractListenerWebSocketSession(T delegate, String id, HandshakeInfo handshakeInfo,
|
||||
public AbstractListenerWebSocketSession(T delegate, String id, HandshakeInfo info,
|
||||
DataBufferFactory bufferFactory, @Nullable MonoProcessor<Void> completionMono) {
|
||||
|
||||
super(delegate, id, handshakeInfo, bufferFactory);
|
||||
super(delegate, id, info, bufferFactory);
|
||||
this.receivePublisher = new WebSocketReceivePublisher(initLogPrefix(info, id));
|
||||
this.completionMono = completionMono;
|
||||
}
|
||||
|
||||
private static String initLogPrefix(HandshakeInfo info, String id) {
|
||||
return info.getLogPrefix() != null ? info.getLogPrefix() : "[" + id + "] ";
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected WebSocketSendProcessor getSendProcessor() {
|
||||
WebSocketSendProcessor sendProcessor = this.sendProcessor;
|
||||
@@ -223,15 +229,23 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
|
||||
private volatile Queue<Object> pendingMessages = Queues.unbounded(Queues.SMALL_BUFFER_SIZE).get();
|
||||
|
||||
|
||||
WebSocketReceivePublisher(String logPrefix) {
|
||||
super(logPrefix);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(getLogPrefix() + "Session id '" + getId() + "' for " + getHandshakeInfo().getUri());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void checkOnDataAvailable() {
|
||||
resumeReceiving();
|
||||
if (!this.pendingMessages.isEmpty()) {
|
||||
logger.trace("checkOnDataAvailable, " + this.pendingMessages.size() + " pending messages");
|
||||
onDataAvailable();
|
||||
int size = this.pendingMessages.size();
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(getLogPrefix() + "checkOnDataAvailable (" + size + " pending)");
|
||||
}
|
||||
else {
|
||||
logger.trace("checkOnDataAvailable, 0 pending messages");
|
||||
if (size > 0) {
|
||||
onDataAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +262,7 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
|
||||
|
||||
void handleMessage(WebSocketMessage message) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Received " + message);
|
||||
logger.trace(getLogPrefix() + "Received " + message);
|
||||
}
|
||||
if (!this.pendingMessages.offer(message)) {
|
||||
throw new IllegalStateException(
|
||||
@@ -266,10 +280,16 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
|
||||
|
||||
private volatile boolean isReady = true;
|
||||
|
||||
|
||||
WebSocketSendProcessor() {
|
||||
super(receivePublisher.getLogPrefix());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean write(WebSocketMessage message) throws IOException {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Sending " + message);
|
||||
logger.trace(getLogPrefix() + "Sending " + message);
|
||||
}
|
||||
return sendMessage(message);
|
||||
}
|
||||
@@ -290,8 +310,8 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
|
||||
* async completion callback into simple flow control.
|
||||
*/
|
||||
public void setReadyToSend(boolean ready) {
|
||||
if (ready) {
|
||||
logger.trace("Ready to send again");
|
||||
if (ready && logger.isTraceEnabled()) {
|
||||
logger.trace(getLogPrefix() + "Ready to send");
|
||||
}
|
||||
this.isReady = ready;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.net.URI;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
@@ -154,17 +155,17 @@ public class JettyWebSocketClient implements WebSocketClient, Lifecycle {
|
||||
}
|
||||
|
||||
private Object createHandler(URI url, WebSocketHandler handler, MonoProcessor<Void> completion) {
|
||||
return new JettyWebSocketHandlerAdapter(handler,
|
||||
session -> {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Connected to " + url);
|
||||
}
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
session.getUpgradeResponse().getHeaders().forEach(responseHeaders::put);
|
||||
String protocol = responseHeaders.getFirst("Sec-WebSocket-Protocol");
|
||||
HandshakeInfo info = new HandshakeInfo(url, responseHeaders, Mono.empty(), protocol);
|
||||
return new JettyWebSocketSession(session, info, this.bufferFactory, completion);
|
||||
});
|
||||
return new JettyWebSocketHandlerAdapter(handler, session -> {
|
||||
HandshakeInfo info = createHandshakeInfo(url, session);
|
||||
return new JettyWebSocketSession(session, info, this.bufferFactory, completion);
|
||||
});
|
||||
}
|
||||
|
||||
private HandshakeInfo createHandshakeInfo(URI url, Session jettySession) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
jettySession.getUpgradeResponse().getHeaders().forEach(headers::put);
|
||||
String protocol = headers.getFirst("Sec-WebSocket-Protocol");
|
||||
return new HandshakeInfo(url, headers, Mono.empty(), protocol);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -76,24 +76,26 @@ public class ReactorNettyWebSocketClient implements WebSocketClient {
|
||||
|
||||
@Override
|
||||
public Mono<Void> execute(URI url, HttpHeaders requestHeaders, WebSocketHandler handler) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Connecting to " + url);
|
||||
}
|
||||
return getHttpClient()
|
||||
.headers(nettyHeaders -> setNettyHeaders(requestHeaders, nettyHeaders))
|
||||
.websocket(StringUtils.collectionToCommaDelimitedString(handler.getSubProtocols()))
|
||||
.uri(url.toString())
|
||||
.handle((inbound, outbound) -> {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Connected to " + url);
|
||||
}
|
||||
HttpHeaders responseHeaders = toHttpHeaders(inbound);
|
||||
String protocol = responseHeaders.getFirst("Sec-WebSocket-Protocol");
|
||||
HandshakeInfo info = new HandshakeInfo(url, responseHeaders, Mono.empty(), protocol);
|
||||
NettyDataBufferFactory factory = new NettyDataBufferFactory(outbound.alloc());
|
||||
WebSocketSession session = new ReactorNettyWebSocketSession(inbound, outbound, info, factory);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Started session '" + session.getId() + "' for " + url);
|
||||
}
|
||||
return handler.handle(session);
|
||||
})
|
||||
.doOnRequest(n -> {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Connecting to " + url);
|
||||
}
|
||||
})
|
||||
.next();
|
||||
}
|
||||
|
||||
|
||||
@@ -115,15 +115,14 @@ public class StandardWebSocketClient implements WebSocketClient {
|
||||
private StandardWebSocketHandlerAdapter createEndpoint(URI url, WebSocketHandler handler,
|
||||
MonoProcessor<Void> completion, DefaultConfigurator configurator) {
|
||||
|
||||
return new StandardWebSocketHandlerAdapter(handler, session -> {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Connected to " + url);
|
||||
}
|
||||
HttpHeaders responseHeaders = configurator.getResponseHeaders();
|
||||
String protocol = responseHeaders.getFirst("Sec-WebSocket-Protocol");
|
||||
HandshakeInfo info = new HandshakeInfo(url, responseHeaders, Mono.empty(), protocol);
|
||||
return createWebSocketSession(session, info, completion);
|
||||
});
|
||||
return new StandardWebSocketHandlerAdapter(handler, session ->
|
||||
createWebSocketSession(session, createHandshakeInfo(url, configurator), completion));
|
||||
}
|
||||
|
||||
private HandshakeInfo createHandshakeInfo(URI url, DefaultConfigurator configurator) {
|
||||
HttpHeaders responseHeaders = configurator.getResponseHeaders();
|
||||
String protocol = responseHeaders.getFirst("Sec-WebSocket-Protocol");
|
||||
return new HandshakeInfo(url, responseHeaders, Mono.empty(), protocol);
|
||||
}
|
||||
|
||||
protected StandardWebSocketSession createWebSocketSession(Session session, HandshakeInfo info,
|
||||
|
||||
@@ -196,12 +196,7 @@ public class UndertowWebSocketClient implements WebSocketClient {
|
||||
private void handleChannel(URI url, WebSocketHandler handler, MonoProcessor<Void> completion,
|
||||
DefaultNegotiation negotiation, WebSocketChannel channel) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Connected to " + url);
|
||||
}
|
||||
HttpHeaders responseHeaders = negotiation.getResponseHeaders();
|
||||
String protocol = responseHeaders.getFirst("Sec-WebSocket-Protocol");
|
||||
HandshakeInfo info = new HandshakeInfo(url, responseHeaders, Mono.empty(), protocol);
|
||||
HandshakeInfo info = createHandshakeInfo(url, negotiation);
|
||||
UndertowWebSocketSession session = new UndertowWebSocketSession(channel, info, this.bufferFactory, completion);
|
||||
UndertowWebSocketHandlerAdapter adapter = new UndertowWebSocketHandlerAdapter(session);
|
||||
|
||||
@@ -211,6 +206,12 @@ public class UndertowWebSocketClient implements WebSocketClient {
|
||||
handler.handle(session).subscribe(session);
|
||||
}
|
||||
|
||||
private HandshakeInfo createHandshakeInfo(URI url, DefaultNegotiation negotiation) {
|
||||
HttpHeaders responseHeaders = negotiation.getResponseHeaders();
|
||||
String protocol = responseHeaders.getFirst("Sec-WebSocket-Protocol");
|
||||
return new HandshakeInfo(url, responseHeaders, Mono.empty(), protocol);
|
||||
}
|
||||
|
||||
|
||||
private static final class DefaultNegotiation extends WebSocketClientNegotiation {
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.web.reactive.socket.server.support;
|
||||
|
||||
import java.net.URI;
|
||||
import java.security.Principal;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -269,8 +270,11 @@ public class HandshakeWebSocketService implements WebSocketService, Lifecycle {
|
||||
private HandshakeInfo createHandshakeInfo(ServerWebExchange exchange, ServerHttpRequest request,
|
||||
@Nullable String protocol, Map<String, Object> attributes) {
|
||||
|
||||
URI uri = request.getURI();
|
||||
HttpHeaders headers = request.getHeaders();
|
||||
Mono<Principal> principal = exchange.getPrincipal();
|
||||
return new HandshakeInfo(request.getURI(), request.getHeaders(), principal, protocol, attributes);
|
||||
String logPrefix = exchange.getLogPrefix();
|
||||
return new HandshakeInfo(uri, headers, principal, protocol, attributes, logPrefix);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user