/*
* Copyright 2013 JBoss, by Red Hat, Inc
*
* 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 io.undertow.websockets.jsr;
import io.undertow.server.session.SecureRandomSessionIdGenerator;
import io.undertow.servlet.api.InstanceHandle;
import io.undertow.websockets.core.CloseMessage;
import io.undertow.websockets.core.WebSocketChannel;
import io.undertow.websockets.core.WebSockets;
import org.xnio.ChannelListener;
import org.xnio.IoUtils;
import javax.websocket.CloseReason;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.Extension;
import javax.websocket.MessageHandler;
import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import java.io.IOException;
import java.net.URI;
import java.nio.channels.Channel;
import java.security.Principal;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* {@link Session} implementation which makes use of the high-level WebSocket API of undertow under the hood.
*
* @author <a href="mailto:nmaurer@redhat.com">Norman Maurer</a>
*/
public final class UndertowSession implements Session {
private final String sessionId;
private final WebSocketChannel webSocketChannel;
private final FrameHandler frameHandler;
private final ServerWebSocketContainer container;
private final Principal user;
private final WebSocketSessionRemoteEndpoint remote;
private final Map<String, Object> attrs;
private final Map<String, List<String>> requestParameterMap;
private final URI requestUri;
private final String queryString;
private final Map<String, String> pathParameters;
private final InstanceHandle<Endpoint> endpoint;
private final Encoding encoding;
private final AtomicBoolean closed = new AtomicBoolean();
private final Set<Session> openSessions;
private final String subProtocol;
private final List<Extension> extensions;
private volatile int maximumBinaryBufferSize = 0;
private volatile int maximumTextBufferSize = 0;
public UndertowSession(WebSocketChannel webSocketChannel, URI requestUri, Map<String, String> pathParameters,
Map<String, List<String>> requestParameterMap, EndpointSessionHandler handler, Principal user,
InstanceHandle<Endpoint> endpoint, EndpointConfig config, final String queryString,
final Encoding encoding, final Set<Session> openSessions, final String subProtocol,
final List<Extension> extensions) {
this.webSocketChannel = webSocketChannel;
this.queryString = queryString;
this.encoding = encoding;
this.openSessions = openSessions;
container = handler.getContainer();
this.user = user;
this.requestUri = requestUri;
this.requestParameterMap = Collections.unmodifiableMap(requestParameterMap);
this.pathParameters = Collections.unmodifiableMap(pathParameters);
remote = new WebSocketSessionRemoteEndpoint(webSocketChannel, config, encoding);
this.endpoint = endpoint;
webSocketChannel.getCloseSetter().set(new ChannelListener<Channel>() {
@Override
public void handleEvent(final Channel channel) {
close0();
}
});
this.frameHandler = new FrameHandler(this, this.endpoint.getInstance());
webSocketChannel.getReceiveSetter().set(frameHandler);
this.sessionId = new SecureRandomSessionIdGenerator().createSessionId();
this.attrs = Collections.synchronizedMap(new HashMap<String, Object>(config.getUserProperties()));
this.extensions = extensions;
this.subProtocol = subProtocol;
}
@Override
public ServerWebSocketContainer getContainer() {
return container;
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public synchronized void addMessageHandler(MessageHandler messageHandler) throws IllegalStateException {
frameHandler.addHandler(messageHandler);
}
@SuppressWarnings("rawtypes")
@Override
public synchronized Set<MessageHandler> getMessageHandlers() {
return frameHandler.getHandlers();
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public synchronized void removeMessageHandler(MessageHandler messageHandler) {
frameHandler.removeHandler(messageHandler);
}
/**
* sets the recieve listener This should only be used for annotated endpoints.
*
* @param handler The handler
*/
public void setReceiveListener(final ChannelListener<WebSocketChannel> handler) {
webSocketChannel.getReceiveSetter().set(handler);
}
@Override
public String getProtocolVersion() {
return webSocketChannel.getVersion().toHttpHeaderValue();
}
@Override
public String getNegotiatedSubprotocol() {
return subProtocol == null ? "" : subProtocol;
}
@Override
public boolean isSecure() {
return webSocketChannel.isSecure();
}
@Override
public boolean isOpen() {
return webSocketChannel.isOpen();
}
@Override
public long getMaxIdleTimeout() {
return webSocketChannel.getIdleTimeout();
}
@Override
public void setMaxIdleTimeout(final long milliseconds) {
webSocketChannel.setIdleTimeout(milliseconds);
}
@Override
public String getId() {
return sessionId;
}
@Override
public void close() throws IOException {
close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, null));
}
@Override
public void close(CloseReason closeReason) throws IOException {
if(closed.compareAndSet(false, true)) {
try {
if(closeReason == null) {
endpoint.getInstance().onClose(this, new CloseReason(CloseReason.CloseCodes.NO_STATUS_CODE, null));
} else {
endpoint.getInstance().onClose(this, closeReason);
}
if(!webSocketChannel.isCloseFrameReceived()) {
//if we have already recieved a close frame then the close frame handler
//will deal with sending back the reason message
if (closeReason == null) {
webSocketChannel.sendClose();
} else {
WebSockets.sendClose(new CloseMessage(closeReason.getCloseCode().getCode(), closeReason.getReasonPhrase()).toByteBuffer(), webSocketChannel, null);
}
}
} finally {
close0();
}
}
}
public void forceClose() {
IoUtils.safeClose(webSocketChannel);
}
@Override
public URI getRequestURI() {
return requestUri;
}
@Override
public Map<String, List<String>> getRequestParameterMap() {
return requestParameterMap;
}
@Override
public String getQueryString() {
return queryString;
}
@Override
public Map<String, String> getPathParameters() {
return pathParameters;
}
@Override
public Map<String, Object> getUserProperties() {
return attrs;
}
@Override
public Principal getUserPrincipal() {
return user;
}
@Override
public void setMaxBinaryMessageBufferSize(int i) {
maximumBinaryBufferSize = i;
}
@Override
public int getMaxBinaryMessageBufferSize() {
return maximumBinaryBufferSize;
//return (int) webSocketChannel.getMaximumBinaryFrameSize();
}
@Override
public void setMaxTextMessageBufferSize(int i) {
maximumTextBufferSize = i;
}
@Override
public int getMaxTextMessageBufferSize() {
return maximumTextBufferSize;
}
@Override
public RemoteEndpoint.Async getAsyncRemote() {
return remote.getAsync();
}
@Override
public RemoteEndpoint.Basic getBasicRemote() {
return remote.getBasic();
}
@Override
public Set<Session> getOpenSessions() {
return new HashSet<Session>(openSessions);
}
@Override
public List<Extension> getNegotiatedExtensions() {
return extensions;
}
void close0() {
openSessions.remove(this);
try {
endpoint.release();
} finally {
encoding.close();
}
}
public Encoding getEncoding() {
return encoding;
}
public WebSocketChannel getWebSocketChannel() {
return webSocketChannel;
}
}