Add WebSocketSession attributes + initialization

In addition to adding the attributes, there is now mechanism for
initializing WebSocketSession instances from attributes of the
handshake request.
This commit is contained in:
Rossen Stoyanchev 2013-05-02 13:47:18 -04:00
parent 166ca7a5a3
commit 2a7935a913
24 changed files with 510 additions and 190 deletions

View File

@ -16,6 +16,8 @@
package org.springframework.http.server;
import java.security.Principal;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpRequest;
import org.springframework.util.MultiValueMap;
@ -33,4 +35,21 @@ public interface ServerHttpRequest extends HttpRequest, HttpInputMessage {
*/
MultiValueMap<String, String> getQueryParams();
/**
* Return a {@link java.security.Principal} instance containing the name of the
* authenticated user. If the user has not been authenticated, the method returns
* <code>null</code>.
*/
Principal getPrincipal();
/**
* Return the host name of the endpoint on the other end.
*/
String getRemoteHostName();
/**
* Return the IP address of the endpoint on the other end.
*/
String getRemoteAddress();
}

View File

@ -26,6 +26,7 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.Principal;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
@ -131,6 +132,21 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
return this.headers;
}
@Override
public Principal getPrincipal() {
return this.servletRequest.getUserPrincipal();
}
@Override
public String getRemoteHostName() {
return this.servletRequest.getRemoteHost();
}
@Override
public String getRemoteAddress() {
return this.servletRequest.getRemoteAddr();
}
public Cookies getCookies() {
if (this.cookies == null) {
this.cookies = new Cookies();

View File

@ -18,6 +18,7 @@ package org.springframework.sockjs;
import java.io.IOException;
import java.net.URI;
import java.security.Principal;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -25,7 +26,7 @@ import org.springframework.util.Assert;
import org.springframework.websocket.CloseStatus;
import org.springframework.websocket.TextMessage;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.WebSocketSession;
import org.springframework.websocket.adapter.ConfigurableWebSocketSession;
/**
@ -34,12 +35,20 @@ import org.springframework.websocket.WebSocketSession;
* @author Rossen Stoyanchev
* @since 4.0
*/
public abstract class AbstractSockJsSession implements WebSocketSession {
public abstract class AbstractSockJsSession implements ConfigurableWebSocketSession {
protected final Log logger = LogFactory.getLog(getClass());
private final String sessionId;
private final String id;
private URI uri;
private String remoteHostName;
private String remoteAddress;
private Principal principal;
private WebSocketHandler handler;
@ -57,24 +66,51 @@ public abstract class AbstractSockJsSession implements WebSocketSession {
public AbstractSockJsSession(String sessionId, WebSocketHandler webSocketHandler) {
Assert.notNull(sessionId, "sessionId is required");
Assert.notNull(webSocketHandler, "webSocketHandler is required");
this.sessionId = sessionId;
this.id = sessionId;
this.handler = webSocketHandler;
}
public String getId() {
return this.sessionId;
return this.id;
}
@Override
public URI getUri() {
return this.uri;
}
@Override
public void setUri(URI uri) {
this.uri = uri;
}
@Override
public boolean isSecure() {
// TODO
return false;
return "wss".equals(this.uri.getSchemeSpecificPart());
}
@Override
public URI getURI() {
// TODO
return null;
public String getRemoteHostName() {
return this.remoteHostName;
}
public void setRemoteHostName(String remoteHostName) {
this.remoteHostName = remoteHostName;
}
public String getRemoteAddress() {
return this.remoteAddress;
}
public void setRemoteAddress(String remoteAddress) {
this.remoteAddress = remoteAddress;
}
public Principal getPrincipal() {
return this.principal;
}
public void setPrincipal(Principal principal) {
this.principal = principal;
}
public boolean isNew() {
@ -213,7 +249,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession {
@Override
public String toString() {
return "SockJS session id=" + this.sessionId;
return "SockJS session id=" + this.id;
}

View File

@ -17,7 +17,6 @@
package org.springframework.sockjs;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.WebSocketSession;
/**
* A factory for creating a SockJS session.
@ -26,7 +25,7 @@ import org.springframework.websocket.WebSocketSession;
* @author Rossen Stoyanchev
* @since 4.0
*/
public interface SockJsSessionFactory<S extends WebSocketSession>{
public interface SockJsSessionFactory {
/**
* Create a new SockJS session.
@ -34,6 +33,6 @@ public interface SockJsSessionFactory<S extends WebSocketSession>{
* @param webSocketHandler the underlying {@link WebSocketHandler}
* @return a new non-null session
*/
S createSession(String sessionId, WebSocketHandler webSocketHandler);
AbstractSockJsSession createSession(String sessionId, WebSocketHandler webSocketHandler);
}

View File

@ -51,7 +51,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf
private static final int ONE_YEAR = 365 * 24 * 60 * 60;
private String name = getClass().getSimpleName() + "@" + ObjectUtils.getIdentityHexString(this);
private String name = "SockJS Service " + ObjectUtils.getIdentityHexString(this);
private String clientLibraryUrl = "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js";

View File

@ -68,7 +68,7 @@ public enum TransportType {
return this.httpMethod;
}
public boolean setsNoCacheHeader() {
public boolean setsNoCache() {
return this.headerHints.contains("no_cache");
}
@ -76,7 +76,7 @@ public enum TransportType {
return this.headerHints.contains("cors");
}
public boolean setsJsessionIdCookie() {
public boolean setsJsessionId() {
return this.headerHints.contains("jsessionid");
}

View File

@ -54,6 +54,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.server.DefaultHandshakeHandler;
import org.springframework.websocket.server.HandshakeHandler;
import org.springframework.websocket.server.support.ServerWebSocketSessionInitializer;
/**
@ -69,6 +70,8 @@ public class DefaultSockJsService extends AbstractSockJsService {
private final Map<String, AbstractSockJsSession> sessions = new ConcurrentHashMap<String, AbstractSockJsSession>();
private final ServerWebSocketSessionInitializer sessionInitializer = new ServerWebSocketSessionInitializer();
private ScheduledFuture sessionCleanupTask;
@ -187,14 +190,15 @@ public class DefaultSockJsService extends AbstractSockJsService {
return;
}
AbstractSockJsSession session = getSockJsSession(sessionId, webSocketHandler, transportHandler);
AbstractSockJsSession session = getSockJsSession(sessionId, webSocketHandler,
transportHandler, request, response);
if (session != null) {
if (transportType.setsNoCacheHeader()) {
if (transportType.setsNoCache()) {
addNoCacheHeaders(response);
}
if (transportType.setsJsessionIdCookie() && isJsessionIdCookieRequired()) {
if (transportType.setsJsessionId() && isJsessionIdCookieRequired()) {
Cookie cookie = request.getCookies().getCookie("JSESSIONID");
String jsid = (cookie != null) ? cookie.getValue() : "dummy";
// TODO: bypass use of Cookie object (causes Jetty to set Expires header)
@ -209,8 +213,8 @@ public class DefaultSockJsService extends AbstractSockJsService {
transportHandler.handleRequest(request, response, webSocketHandler, session);
}
public AbstractSockJsSession getSockJsSession(String sessionId,
WebSocketHandler webSocketHandler, TransportHandler transportHandler) {
protected AbstractSockJsSession getSockJsSession(String sessionId, WebSocketHandler handler,
TransportHandler transportHandler, ServerHttpRequest request, ServerHttpResponse response) {
AbstractSockJsSession session = this.sessions.get(sessionId);
if (session != null) {
@ -218,7 +222,7 @@ public class DefaultSockJsService extends AbstractSockJsService {
}
if (transportHandler instanceof SockJsSessionFactory) {
SockJsSessionFactory<?> sessionFactory = (SockJsSessionFactory<?>) transportHandler;
SockJsSessionFactory sessionFactory = (SockJsSessionFactory) transportHandler;
synchronized (this.sessions) {
session = this.sessions.get(sessionId);
@ -229,7 +233,8 @@ public class DefaultSockJsService extends AbstractSockJsService {
scheduleSessionTask();
}
logger.debug("Creating new session with session id \"" + sessionId + "\"");
session = (AbstractSockJsSession) sessionFactory.createSession(sessionId, webSocketHandler);
session = sessionFactory.createSession(sessionId, handler);
this.sessionInitializer.initialize(request, response, session);
this.sessions.put(sessionId, session);
return session;
}

View File

@ -39,7 +39,7 @@ import org.springframework.websocket.WebSocketHandler;
* @since 4.0
*/
public abstract class AbstractHttpSendingTransportHandler
implements ConfigurableTransportHandler, SockJsSessionFactory<AbstractSockJsSession> {
implements ConfigurableTransportHandler, SockJsSessionFactory {
protected final Log logger = LogFactory.getLog(this.getClass());

View File

@ -16,22 +16,16 @@
package org.springframework.sockjs.server.transport;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.sockjs.server.AbstractServerSockJsSession;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.SockJsFrame;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.websocket.CloseStatus;
import org.springframework.websocket.TextMessage;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.WebSocketSession;
import org.springframework.websocket.adapter.TextWebSocketHandlerAdapter;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* A wrapper around a {@link WebSocketHandler} instance that parses and adds SockJS
@ -52,21 +46,20 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter {
private final SockJsConfiguration sockJsConfig;
private final WebSocketHandler webSocketHandler;
private WebSocketServerSockJsSession sockJsSession;
private WebSocketServerSockJsSession session;
private final AtomicInteger sessionCount = new AtomicInteger(0);
// TODO: JSON library used must be configurable
private final ObjectMapper objectMapper = new ObjectMapper();
public SockJsWebSocketHandler(SockJsConfiguration config,
WebSocketHandler webSocketHandler, WebSocketServerSockJsSession session) {
public SockJsWebSocketHandler(SockJsConfiguration config, WebSocketHandler webSocketHandler) {
Assert.notNull(config, "sockJsConfig is required");
Assert.notNull(webSocketHandler, "webSocketHandler is required");
Assert.notNull(session, "session is required");
this.sockJsConfig = config;
this.webSocketHandler = webSocketHandler;
this.session = session;
}
protected SockJsConfiguration getSockJsConfig() {
@ -76,100 +69,22 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter {
@Override
public void afterConnectionEstablished(WebSocketSession wsSession) throws Exception {
Assert.isTrue(this.sessionCount.compareAndSet(0, 1), "Unexpected connection");
this.sockJsSession = new WebSocketServerSockJsSession(getSockJsSessionId(wsSession), getSockJsConfig());
this.sockJsSession.initWebSocketSession(wsSession);
this.session.initWebSocketSession(wsSession);
}
@Override
public void handleTextMessage(WebSocketSession wsSession, TextMessage message) throws Exception {
this.sockJsSession.handleMessage(message, wsSession);
this.session.handleMessage(message, wsSession);
}
@Override
public void afterConnectionClosed(WebSocketSession wsSession, CloseStatus status) throws Exception {
this.sockJsSession.delegateConnectionClosed(status);
this.session.delegateConnectionClosed(status);
}
@Override
public void handleTransportError(WebSocketSession webSocketSession, Throwable exception) throws Exception {
this.sockJsSession.delegateError(exception);
}
private static String getSockJsSessionId(WebSocketSession wsSession) {
Assert.notNull(wsSession, "wsSession is required");
String path = wsSession.getURI().getPath();
String[] segments = StringUtils.tokenizeToStringArray(path, "/");
Assert.isTrue(segments.length > 3, "SockJS request should have at least 3 path segments: " + path);
return segments[segments.length-2];
}
private class WebSocketServerSockJsSession extends AbstractServerSockJsSession {
private WebSocketSession wsSession;
public WebSocketServerSockJsSession(String sessionId, SockJsConfiguration config) {
super(sessionId, config, SockJsWebSocketHandler.this.webSocketHandler);
}
public void initWebSocketSession(WebSocketSession wsSession) throws Exception {
this.wsSession = wsSession;
try {
TextMessage message = new TextMessage(SockJsFrame.openFrame().getContent());
this.wsSession.sendMessage(message);
}
catch (IOException ex) {
tryCloseWithSockJsTransportError(ex, null);
return;
}
scheduleHeartbeat();
delegateConnectionEstablished();
}
@Override
public boolean isActive() {
return this.wsSession.isOpen();
}
public void handleMessage(TextMessage message, WebSocketSession wsSession) throws Exception {
String payload = message.getPayload();
if (StringUtils.isEmpty(payload)) {
logger.trace("Ignoring empty message");
return;
}
String[] messages;
try {
messages = objectMapper.readValue(payload, String[].class);
}
catch (IOException ex) {
logger.error("Broken data received. Terminating WebSocket connection abruptly", ex);
tryCloseWithSockJsTransportError(ex, CloseStatus.BAD_DATA);
return;
}
delegateMessages(messages);
}
@Override
public void sendMessageInternal(String message) throws IOException {
cancelHeartbeat();
writeFrame(SockJsFrame.messageFrame(message));
scheduleHeartbeat();
}
@Override
protected void writeFrameInternal(SockJsFrame frame) throws IOException {
if (logger.isTraceEnabled()) {
logger.trace("Write " + frame);
}
TextMessage message = new TextMessage(frame.getContent());
this.wsSession.sendMessage(message);
}
@Override
protected void disconnect(CloseStatus status) throws IOException {
this.wsSession.close(status);
}
this.session.delegateError(exception);
}
}

View File

@ -0,0 +1,107 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.sockjs.server.transport;
import java.io.IOException;
import org.springframework.sockjs.server.AbstractServerSockJsSession;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.SockJsFrame;
import org.springframework.util.StringUtils;
import org.springframework.websocket.CloseStatus;
import org.springframework.websocket.TextMessage;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.WebSocketSession;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Rossen Stoyanchev
* @since 4.0
*/
public class WebSocketServerSockJsSession extends AbstractServerSockJsSession {
private WebSocketSession webSocketSession;
// TODO: JSON library used must be configurable
private final ObjectMapper objectMapper = new ObjectMapper();
public WebSocketServerSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) {
super(sessionId, config, handler);
}
public void initWebSocketSession(WebSocketSession session) throws Exception {
this.webSocketSession = session;
try {
TextMessage message = new TextMessage(SockJsFrame.openFrame().getContent());
this.webSocketSession.sendMessage(message);
}
catch (IOException ex) {
tryCloseWithSockJsTransportError(ex, null);
return;
}
scheduleHeartbeat();
delegateConnectionEstablished();
}
@Override
public boolean isActive() {
return this.webSocketSession.isOpen();
}
public void handleMessage(TextMessage message, WebSocketSession wsSession) throws Exception {
String payload = message.getPayload();
if (StringUtils.isEmpty(payload)) {
logger.trace("Ignoring empty message");
return;
}
String[] messages;
try {
messages = objectMapper.readValue(payload, String[].class);
}
catch (IOException ex) {
logger.error("Broken data received. Terminating WebSocket connection abruptly", ex);
tryCloseWithSockJsTransportError(ex, CloseStatus.BAD_DATA);
return;
}
delegateMessages(messages);
}
@Override
public void sendMessageInternal(String message) throws IOException {
cancelHeartbeat();
writeFrame(SockJsFrame.messageFrame(message));
scheduleHeartbeat();
}
@Override
protected void writeFrameInternal(SockJsFrame frame) throws IOException {
if (logger.isTraceEnabled()) {
logger.trace("Write " + frame);
}
TextMessage message = new TextMessage(frame.getContent());
this.webSocketSession.sendMessage(message);
}
@Override
protected void disconnect(CloseStatus status) throws IOException {
this.webSocketSession.close(status);
}
}

View File

@ -21,6 +21,7 @@ import java.io.IOException;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.sockjs.AbstractSockJsSession;
import org.springframework.sockjs.SockJsSessionFactory;
import org.springframework.sockjs.server.ConfigurableTransportHandler;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.TransportErrorException;
@ -39,7 +40,8 @@ import org.springframework.websocket.server.HandshakeHandler;
* @author Rossen Stoyanchev
* @since 4.0
*/
public class WebSocketTransportHandler implements ConfigurableTransportHandler, HandshakeHandler {
public class WebSocketTransportHandler implements ConfigurableTransportHandler,
HandshakeHandler, SockJsSessionFactory {
private final HandshakeHandler handshakeHandler;
@ -61,12 +63,18 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler,
this.sockJsConfig = sockJsConfig;
}
@Override
public AbstractSockJsSession createSession(String sessionId, WebSocketHandler webSocketHandler) {
return new WebSocketServerSockJsSession(sessionId, this.sockJsConfig, webSocketHandler);
}
@Override
public void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws TransportErrorException {
try {
WebSocketHandler sockJsWrapper = new SockJsWebSocketHandler(this.sockJsConfig, webSocketHandler);
WebSocketServerSockJsSession wsSession = (WebSocketServerSockJsSession) session;
WebSocketHandler sockJsWrapper = new SockJsWebSocketHandler(this.sockJsConfig, webSocketHandler, wsSession);
this.handshakeHandler.doHandshake(request, response, sockJsWrapper);
}
catch (Throwable t) {

View File

@ -17,7 +17,9 @@
package org.springframework.websocket;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.security.Principal;
/**
* Allows sending messages over a WebSocket connection as well as closing it.
@ -33,9 +35,9 @@ public interface WebSocketSession {
String getId();
/**
* Return whether the connection is still open.
* Return the URI used to open the WebSocket connection.
*/
boolean isOpen();
URI getUri();
/**
* Return whether the underlying socket is using a secure transport.
@ -43,9 +45,26 @@ public interface WebSocketSession {
boolean isSecure();
/**
* Return the URI used to open the WebSocket connection.
* Return a {@link java.security.Principal} instance containing the name of the
* authenticated user. If the user has not been authenticated, the method returns
* <code>null</code>.
*/
URI getURI();
Principal getPrincipal();
/**
* Return the host name of the endpoint on the other end.
*/
String getRemoteHostName();
/**
* Return the IP address of the endpoint on the other end.
*/
String getRemoteAddress();
/**
* Return whether the connection is still open.
*/
boolean isOpen();
/**
* Send a WebSocket message either {@link TextMessage} or

View File

@ -1,5 +1,4 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -34,11 +33,13 @@ import org.springframework.websocket.WebSocketSession;
* @author Rossen Stoyanchev
* @since 4.0
*/
public abstract class AbstractWebSocketSesssionAdapter implements WebSocketSession {
public abstract class AbstractWebSocketSesssionAdapter<T> implements ConfigurableWebSocketSession {
protected final Log logger = LogFactory.getLog(getClass());
public abstract void initSession(T session);
@Override
public final void sendMessage(WebSocketMessage message) throws IOException {
if (logger.isTraceEnabled()) {

View File

@ -0,0 +1,39 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.websocket.adapter;
import java.net.URI;
import java.security.Principal;
import org.springframework.websocket.WebSocketSession;
/**
* @author Rossen Stoyanchev
* @since 4.0
*/
public interface ConfigurableWebSocketSession extends WebSocketSession {
void setUri(URI uri);
void setRemoteHostName(String name);
void setRemoteAddress(String address);
void setPrincipal(Principal principal);
}

View File

@ -25,7 +25,6 @@ import org.springframework.websocket.BinaryMessage;
import org.springframework.websocket.CloseStatus;
import org.springframework.websocket.TextMessage;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.WebSocketSession;
import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator;
/**
@ -40,18 +39,20 @@ public class JettyWebSocketListenerAdapter implements WebSocketListener {
private final WebSocketHandler webSocketHandler;
private WebSocketSession wsSession;
private JettyWebSocketSessionAdapter wsSession;
public JettyWebSocketListenerAdapter(WebSocketHandler webSocketHandler) {
public JettyWebSocketListenerAdapter(WebSocketHandler webSocketHandler, JettyWebSocketSessionAdapter wsSession) {
Assert.notNull(webSocketHandler, "webSocketHandler is required");
Assert.notNull(wsSession, "wsSession is required");
this.webSocketHandler = webSocketHandler;
this.wsSession = wsSession;
}
@Override
public void onWebSocketConnect(Session session) {
this.wsSession = new JettyWebSocketSessionAdapter(session);
this.wsSession.initSession(session);
try {
this.webSocketHandler.afterConnectionEstablished(this.wsSession);
}

View File

@ -17,9 +17,12 @@
package org.springframework.websocket.adapter;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.security.Principal;
import org.eclipse.jetty.websocket.api.Session;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.websocket.BinaryMessage;
import org.springframework.websocket.CloseStatus;
@ -31,38 +34,78 @@ import org.springframework.websocket.WebSocketSession;
* Adapts Jetty's {@link Session} to Spring's {@link WebSocketSession}.
*
* @author Phillip Webb
* @author Rossen Stoyanchev
* @since 4.0
*/
public class JettyWebSocketSessionAdapter extends AbstractWebSocketSesssionAdapter {
public class JettyWebSocketSessionAdapter
extends AbstractWebSocketSesssionAdapter<org.eclipse.jetty.websocket.api.Session> {
private Session session;
private Principal principal;
public JettyWebSocketSessionAdapter(Session session) {
@Override
public void initSession(Session session) {
Assert.notNull(session, "session is required");
this.session = session;
}
@Override
public String getId() {
return ObjectUtils.getIdentityHexString(this.session);
}
@Override
public boolean isOpen() {
return this.session.isOpen();
}
@Override
public boolean isSecure() {
return this.session.isSecure();
}
@Override
public URI getURI() {
public URI getUri() {
return this.session.getUpgradeRequest().getRequestURI();
}
@Override
public void setUri(URI uri) {
}
@Override
public Principal getPrincipal() {
return this.principal;
}
@Override
public void setPrincipal(Principal principal) {
this.principal = principal;
}
@Override
public String getRemoteHostName() {
return this.session.getRemoteAddress().getHostName();
}
@Override
public void setRemoteHostName(String address) {
// ignore
}
@Override
public String getRemoteAddress() {
InetSocketAddress address = this.session.getRemoteAddress();
return address.isUnresolved() ? null : address.getAddress().getHostAddress();
}
@Override
public void setRemoteAddress(String address) {
// ignore
}
@Override
public boolean isOpen() {
return this.session.isOpen();
}
@Override
protected void sendTextMessage(TextMessage message) throws IOException {
this.session.getRemote().sendString(message.getPayload());

View File

@ -30,12 +30,11 @@ import org.springframework.websocket.BinaryMessage;
import org.springframework.websocket.CloseStatus;
import org.springframework.websocket.TextMessage;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.WebSocketSession;
import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator;
/**
* An {@link Endpoint} that delegates to a {@link WebSocketHandler}.
* A wrapper around a {@link WebSocketHandler} that adapts it to {@link Endpoint}.
*
* @author Rossen Stoyanchev
* @since 4.0
@ -46,19 +45,22 @@ public class StandardEndpointAdapter extends Endpoint {
private final WebSocketHandler handler;
private WebSocketSession wsSession;
private final StandardWebSocketSessionAdapter wsSession;
public StandardEndpointAdapter(WebSocketHandler webSocketHandler) {
Assert.notNull(webSocketHandler, "webSocketHandler is required");
this.handler = webSocketHandler;
public StandardEndpointAdapter(WebSocketHandler handler, StandardWebSocketSessionAdapter wsSession) {
Assert.notNull(handler, "handler is required");
Assert.notNull(wsSession, "wsSession is required");
this.handler = handler;
this.wsSession = wsSession;
}
@Override
public void onOpen(final javax.websocket.Session session, EndpointConfig config) {
this.wsSession = new StandardWebSocketSessionAdapter(session);
this.wsSession.initSession(session);
try {
this.handler.afterConnectionEstablished(this.wsSession);
}

View File

@ -18,6 +18,7 @@ package org.springframework.websocket.adapter;
import java.io.IOException;
import java.net.URI;
import java.security.Principal;
import javax.websocket.CloseReason;
import javax.websocket.CloseReason.CloseCodes;
@ -35,35 +36,76 @@ import org.springframework.websocket.WebSocketSession;
* @author Rossen Stoyanchev
* @since 4.0
*/
public class StandardWebSocketSessionAdapter extends AbstractWebSocketSesssionAdapter {
public class StandardWebSocketSessionAdapter extends AbstractWebSocketSesssionAdapter<javax.websocket.Session> {
private final javax.websocket.Session session;
private javax.websocket.Session session;
private URI uri;
private String remoteHostName;
private String remoteAddress;
public StandardWebSocketSessionAdapter(javax.websocket.Session session) {
public void initSession(javax.websocket.Session session) {
Assert.notNull(session, "session is required");
this.session = session;
}
@Override
public String getId() {
return this.session.getId();
}
@Override
public boolean isOpen() {
return this.session.isOpen();
public URI getUri() {
return this.uri;
}
@Override
public void setUri(URI uri) {
this.uri = uri;
}
@Override
public boolean isSecure() {
return this.session.isSecure();
}
@Override
public URI getURI() {
return this.session.getRequestURI();
public Principal getPrincipal() {
return this.session.getUserPrincipal();
}
@Override
public void setPrincipal(Principal principal) {
// ignore
}
@Override
public String getRemoteHostName() {
return this.remoteHostName;
}
@Override
public void setRemoteHostName(String name) {
this.remoteHostName = name;
}
@Override
public String getRemoteAddress() {
return this.remoteAddress;
}
@Override
public void setRemoteAddress(String address) {
this.remoteAddress = address;
}
@Override
public boolean isOpen() {
return this.session.isOpen();
}
@Override

View File

@ -28,12 +28,12 @@ import javax.websocket.ClientEndpointConfig.Configurator;
import javax.websocket.ContainerProvider;
import javax.websocket.Endpoint;
import javax.websocket.HandshakeResponse;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.WebSocketSession;
@ -67,15 +67,26 @@ public class StandardWebSocketClient implements WebSocketClient {
public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVariables)
throws WebSocketConnectFailureException {
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode().toUri();
return doHandshake(webSocketHandler, null, uri);
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode();
return doHandshake(webSocketHandler, null, uriComponents);
}
@Override
public WebSocketSession doHandshake(WebSocketHandler webSocketHandler,
final HttpHeaders httpHeaders, URI uri) throws WebSocketConnectFailureException {
Endpoint endpoint = new StandardEndpointAdapter(webSocketHandler);
return doHandshake(webSocketHandler, httpHeaders, UriComponentsBuilder.fromUri(uri).build());
}
public WebSocketSession doHandshake(WebSocketHandler webSocketHandler,
final HttpHeaders httpHeaders, UriComponents uriComponents) throws WebSocketConnectFailureException {
URI uri = uriComponents.toUri();
StandardWebSocketSessionAdapter session = new StandardWebSocketSessionAdapter();
session.setUri(uri);
session.setRemoteHostName(uriComponents.getHost());
Endpoint endpoint = new StandardEndpointAdapter(webSocketHandler, session);
ClientEndpointConfig.Builder configBuidler = ClientEndpointConfig.Builder.create();
if (httpHeaders != null) {
@ -109,8 +120,9 @@ public class StandardWebSocketClient implements WebSocketClient {
}
try {
Session session = this.webSocketContainer.connectToServer(endpoint, configBuidler.build(), uri);
return new StandardWebSocketSessionAdapter(session);
// TODO: do not block
this.webSocketContainer.connectToServer(endpoint, configBuidler.build(), uri);
return session;
}
catch (Exception e) {
throw new WebSocketConnectFailureException("Failed to connect to " + uri, e);

View File

@ -17,13 +17,12 @@
package org.springframework.websocket.client.jetty;
import java.net.URI;
import java.util.concurrent.Future;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jetty.websocket.api.Session;
import org.springframework.context.SmartLifecycle;
import org.springframework.http.HttpHeaders;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.WebSocketSession;
@ -126,23 +125,34 @@ public class JettyWebSocketClient implements WebSocketClient, SmartLifecycle {
public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVariables)
throws WebSocketConnectFailureException {
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode().toUri();
return doHandshake(webSocketHandler, null, uri);
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode();
return doHandshake(webSocketHandler, null, uriComponents);
}
@Override
public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, HttpHeaders headers, URI uri)
throws WebSocketConnectFailureException {
return doHandshake(webSocketHandler, headers, UriComponentsBuilder.fromUri(uri).build());
}
public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, HttpHeaders headers, UriComponents uriComponents)
throws WebSocketConnectFailureException {
// TODO: populate headers
JettyWebSocketListenerAdapter listener = new JettyWebSocketListenerAdapter(webSocketHandler);
URI uri = uriComponents.toUri();
JettyWebSocketSessionAdapter session = new JettyWebSocketSessionAdapter();
session.setUri(uri);
session.setRemoteHostName(uriComponents.getHost());
JettyWebSocketListenerAdapter listener = new JettyWebSocketListenerAdapter(webSocketHandler, session);
try {
// block for now
Future<org.eclipse.jetty.websocket.api.Session> future = this.client.connect(listener, uri);
Session session = future.get();
return new JettyWebSocketSessionAdapter(session);
// TODO: do not block
this.client.connect(listener, uri).get();
return session;
}
catch (Exception e) {
throw new WebSocketConnectFailureException("Failed to connect to " + uri, e);

View File

@ -26,6 +26,8 @@ import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.adapter.StandardEndpointAdapter;
import org.springframework.websocket.adapter.StandardWebSocketSessionAdapter;
import org.springframework.websocket.server.HandshakeFailureException;
import org.springframework.websocket.server.RequestUpgradeStrategy;
/**
@ -39,15 +41,20 @@ public abstract class AbstractEndpointUpgradeStrategy implements RequestUpgradeS
protected final Log logger = LogFactory.getLog(getClass());
private final ServerWebSocketSessionInitializer wsSessionInitializer = new ServerWebSocketSessionInitializer();
@Override
public void upgrade(ServerHttpRequest request, ServerHttpResponse response,
String protocol, WebSocketHandler webSocketHandler) throws IOException {
String protocol, WebSocketHandler handler) throws IOException, HandshakeFailureException {
upgradeInternal(request, response, protocol, new StandardEndpointAdapter(webSocketHandler));
StandardWebSocketSessionAdapter session = new StandardWebSocketSessionAdapter();
this.wsSessionInitializer.initialize(request, response, session);
StandardEndpointAdapter endpoint = new StandardEndpointAdapter(handler, session);
upgradeInternal(request, response, protocol, endpoint);
}
protected abstract void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response,
String selectedProtocol, Endpoint endpoint) throws IOException;
String selectedProtocol, Endpoint endpoint) throws IOException, HandshakeFailureException;
}

View File

@ -34,6 +34,7 @@ import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.adapter.JettyWebSocketListenerAdapter;
import org.springframework.websocket.adapter.JettyWebSocketSessionAdapter;
import org.springframework.websocket.server.HandshakeFailureException;
import org.springframework.websocket.server.RequestUpgradeStrategy;
@ -53,11 +54,13 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy {
// FIXME when to call factory.cleanup();
private static final String HANDLER_PROVIDER_ATTR_NAME = JettyRequestUpgradeStrategy.class.getName()
private static final String WEBSOCKET_LISTENER_ATTR_NAME = JettyRequestUpgradeStrategy.class.getName()
+ ".HANDLER_PROVIDER";
private WebSocketServerFactory factory;
private final ServerWebSocketSessionInitializer wsSessionInitializer = new ServerWebSocketSessionInitializer();
public JettyRequestUpgradeStrategy() {
this.factory = new WebSocketServerFactory();
@ -65,17 +68,14 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy {
@Override
public Object createWebSocket(UpgradeRequest request, UpgradeResponse response) {
Assert.isInstanceOf(ServletWebSocketRequest.class, request);
ServletWebSocketRequest servletRequest = (ServletWebSocketRequest) request;
WebSocketHandler webSocketHandler =
(WebSocketHandler) servletRequest.getServletAttributes().get(HANDLER_PROVIDER_ATTR_NAME);
return new JettyWebSocketListenerAdapter(webSocketHandler);
return ((ServletWebSocketRequest) request).getServletAttributes().get(WEBSOCKET_LISTENER_ATTR_NAME);
}
});
try {
this.factory.init();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
throw new IllegalStateException("Unable to initialize Jetty WebSocketServerFactory", ex);
}
}
@ -95,17 +95,18 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy {
Assert.isInstanceOf(ServletServerHttpResponse.class, response);
HttpServletResponse servletResponse = ((ServletServerHttpResponse) response).getServletResponse();
upgrade(servletRequest, servletResponse, selectedProtocol, webSocketHandler);
}
if (!this.factory.isUpgradeRequest(servletRequest, servletResponse)) {
// should never happen
throw new HandshakeFailureException("Not a WebSocket request");
}
private void upgrade(HttpServletRequest request, HttpServletResponse response,
String selectedProtocol, final WebSocketHandler webSocketHandler) throws IOException {
JettyWebSocketSessionAdapter session = new JettyWebSocketSessionAdapter();
this.wsSessionInitializer.initialize(request, response, session);
JettyWebSocketListenerAdapter listener = new JettyWebSocketListenerAdapter(webSocketHandler, session);
Assert.state(this.factory.isUpgradeRequest(request, response), "Expected websocket upgrade request");
servletRequest.setAttribute(WEBSOCKET_LISTENER_ATTR_NAME, listener);
request.setAttribute(HANDLER_PROVIDER_ATTR_NAME, webSocketHandler);
if (!this.factory.acceptWebSocket(request, response)) {
if (!this.factory.acceptWebSocket(servletRequest, servletResponse)) {
// should never happen
throw new HandshakeFailureException("WebSocket request not accepted by Jetty");
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.websocket.server.support;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.websocket.adapter.ConfigurableWebSocketSession;
/**
* @author Rossen Stoyanchev
* @since 4.0
*/
public class ServerWebSocketSessionInitializer {
public void initialize(ServerHttpRequest request, ServerHttpResponse response, ConfigurableWebSocketSession session) {
session.setUri(request.getURI());
session.setRemoteHostName(request.getRemoteHostName());
session.setRemoteAddress(request.getRemoteAddress());
session.setPrincipal(request.getPrincipal());
}
}

View File

@ -41,7 +41,7 @@ public class LoggingWebSocketHandlerDecorator extends WebSocketHandlerDecorator
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("Connection established, " + session + ", uri=" + session.getURI());
logger.debug("Connection established, " + session + ", uri=" + session.getUri());
}
super.afterConnectionEstablished(session);
}