blob: 04637e392da88789a24771953f33b2039a675b14 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.common;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.CloseStatus;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.SuspendToken;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
import org.eclipse.jetty.websocket.common.events.EventDriver;
import org.eclipse.jetty.websocket.common.io.IOState;
import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
@ManagedObject("A Jetty WebSocket Session")
public class WebSocketSession extends ContainerLifeCycle implements Session, IncomingFrames, ConnectionStateListener
{
private static final Logger LOG = Log.getLogger(WebSocketSession.class);
private final URI requestURI;
private final EventDriver websocket;
private final LogicalConnection connection;
private final SessionListener[] sessionListeners;
private final Executor executor;
private ExtensionFactory extensionFactory;
private String protocolVersion;
private Map<String, String[]> parameterMap = new HashMap<>();
private WebSocketRemoteEndpoint remote;
private IncomingFrames incomingHandler;
private OutgoingFrames outgoingHandler;
private WebSocketPolicy policy;
private UpgradeRequest upgradeRequest;
private UpgradeResponse upgradeResponse;
public WebSocketSession(URI requestURI, EventDriver websocket, LogicalConnection connection, SessionListener... sessionListeners)
{
if (requestURI == null)
{
throw new RuntimeException("Request URI cannot be null");
}
this.requestURI = requestURI;
this.websocket = websocket;
this.connection = connection;
this.sessionListeners = sessionListeners;
this.executor = connection.getExecutor();
this.outgoingHandler = connection;
this.incomingHandler = websocket;
this.connection.getIOState().addListener(this);
}
@Override
public void close()
{
this.close(StatusCode.NORMAL,null);
}
@Override
public void close(CloseStatus closeStatus)
{
this.close(closeStatus.getCode(),closeStatus.getPhrase());
}
@Override
public void close(int statusCode, String reason)
{
connection.close(statusCode,reason);
}
/**
* Harsh disconnect
*/
@Override
public void disconnect()
{
connection.disconnect();
// notify of harsh disconnect
notifyClose(StatusCode.NO_CLOSE,"Harsh disconnect");
}
public void dispatch(Runnable runnable)
{
executor.execute(runnable);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
dumpThis(out);
out.append(indent).append(" +- incomingHandler : ");
if (incomingHandler instanceof Dumpable)
{
((Dumpable)incomingHandler).dump(out,indent + " ");
}
else
{
out.append(incomingHandler.toString()).append(System.lineSeparator());
}
out.append(indent).append(" +- outgoingHandler : ");
if (outgoingHandler instanceof Dumpable)
{
((Dumpable)outgoingHandler).dump(out,indent + " ");
}
else
{
out.append(outgoingHandler.toString()).append(System.lineSeparator());
}
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
WebSocketSession other = (WebSocketSession)obj;
if (connection == null)
{
if (other.connection != null)
{
return false;
}
}
else if (!connection.equals(other.connection))
{
return false;
}
return true;
}
public ByteBufferPool getBufferPool()
{
return this.connection.getBufferPool();
}
public LogicalConnection getConnection()
{
return connection;
}
public ExtensionFactory getExtensionFactory()
{
return extensionFactory;
}
/**
* The idle timeout in milliseconds
*/
@Override
public long getIdleTimeout()
{
return connection.getMaxIdleTimeout();
}
@ManagedAttribute(readonly = true)
public IncomingFrames getIncomingHandler()
{
return incomingHandler;
}
@Override
public InetSocketAddress getLocalAddress()
{
return connection.getLocalAddress();
}
@ManagedAttribute(readonly = true)
public OutgoingFrames getOutgoingHandler()
{
return outgoingHandler;
}
@Override
public WebSocketPolicy getPolicy()
{
return policy;
}
@Override
public String getProtocolVersion()
{
return protocolVersion;
}
@Override
public RemoteEndpoint getRemote()
{
ConnectionState state = connection.getIOState().getConnectionState();
if ((state == ConnectionState.OPEN) || (state == ConnectionState.CONNECTED))
{
return remote;
}
throw new WebSocketException("RemoteEndpoint unavailable, current state [" + state + "], expecting [OPEN or CONNECTED]");
}
@Override
public InetSocketAddress getRemoteAddress()
{
return remote.getInetSocketAddress();
}
public URI getRequestURI()
{
return requestURI;
}
@Override
public UpgradeRequest getUpgradeRequest()
{
return this.upgradeRequest;
}
@Override
public UpgradeResponse getUpgradeResponse()
{
return this.upgradeResponse;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = (prime * result) + ((connection == null)?0:connection.hashCode());
return result;
}
/**
* Incoming Errors from Parser
*/
@Override
public void incomingError(Throwable t)
{
if (connection.getIOState().isInputAvailable())
{
// Forward Errors to User WebSocket Object
websocket.incomingError(t);
}
}
/**
* Incoming Raw Frames from Parser
*/
@Override
public void incomingFrame(Frame frame)
{
if (connection.getIOState().isInputAvailable())
{
// Forward Frames Through Extension List
incomingHandler.incomingFrame(frame);
}
}
@Override
public boolean isOpen()
{
if (this.connection == null)
{
return false;
}
return this.connection.isOpen();
}
@Override
public boolean isSecure()
{
if (upgradeRequest == null)
{
throw new IllegalStateException("No valid UpgradeRequest yet");
}
URI requestURI = upgradeRequest.getRequestURI();
return "wss".equalsIgnoreCase(requestURI.getScheme());
}
public void notifyClose(int statusCode, String reason)
{
if (LOG.isDebugEnabled())
{
LOG.debug("notifyClose({},{})",statusCode,reason);
}
websocket.onClose(new CloseInfo(statusCode,reason));
}
public void notifyError(Throwable cause)
{
incomingError(cause);
}
@SuppressWarnings("incomplete-switch")
@Override
public void onConnectionStateChange(ConnectionState state)
{
switch (state)
{
case CLOSED:
// notify session listeners
for (SessionListener listener : sessionListeners)
{
try
{
LOG.debug("{}.onSessionClosed()",listener.getClass().getSimpleName());
listener.onSessionClosed(this);
}
catch (Throwable t)
{
LOG.ignore(t);
}
}
IOState ioState = this.connection.getIOState();
CloseInfo close = ioState.getCloseInfo();
// confirmed close of local endpoint
notifyClose(close.getStatusCode(),close.getReason());
break;
case OPEN:
// notify session listeners
for (SessionListener listener : sessionListeners)
{
try
{
listener.onSessionOpened(this);
}
catch (Throwable t)
{
LOG.ignore(t);
}
}
break;
}
}
/**
* Open/Activate the session
*/
public void open()
{
if (remote != null)
{
// already opened
return;
}
// Upgrade success
connection.getIOState().onConnected();
// Connect remote
remote = new WebSocketRemoteEndpoint(connection,outgoingHandler,getBatchMode());
try
{
// Open WebSocket
websocket.openSession(this);
// Open connection
connection.getIOState().onOpened();
if (LOG.isDebugEnabled())
{
LOG.debug("open -> {}",dump());
}
}
catch (Throwable t)
{
// Exception on end-user WS-Endpoint.
// Fast-fail & close connection with reason.
int statusCode = StatusCode.SERVER_ERROR;
if(policy.getBehavior() == WebSocketBehavior.CLIENT)
{
statusCode = StatusCode.POLICY_VIOLATION;
}
close(statusCode,t.getMessage());
}
}
public void setExtensionFactory(ExtensionFactory extensionFactory)
{
this.extensionFactory = extensionFactory;
}
/**
* Set the timeout in milliseconds
*/
@Override
public void setIdleTimeout(long ms)
{
connection.setMaxIdleTimeout(ms);
}
public void setOutgoingHandler(OutgoingFrames outgoing)
{
this.outgoingHandler = outgoing;
}
public void setPolicy(WebSocketPolicy policy)
{
this.policy = policy;
}
public void setUpgradeRequest(UpgradeRequest request)
{
this.upgradeRequest = request;
this.protocolVersion = request.getProtocolVersion();
this.parameterMap.clear();
if (request.getParameterMap() != null)
{
for (Map.Entry<String, List<String>> entry : request.getParameterMap().entrySet())
{
List<String> values = entry.getValue();
if (values != null)
{
this.parameterMap.put(entry.getKey(),values.toArray(new String[values.size()]));
}
else
{
this.parameterMap.put(entry.getKey(),new String[0]);
}
}
}
}
public void setUpgradeResponse(UpgradeResponse response)
{
this.upgradeResponse = response;
}
@Override
public SuspendToken suspend()
{
return connection.suspend();
}
/**
* @return the default (initial) value for the batching mode.
*/
public BatchMode getBatchMode()
{
return BatchMode.AUTO;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("WebSocketSession[");
builder.append("websocket=").append(websocket);
builder.append(",behavior=").append(policy.getBehavior());
builder.append(",connection=").append(connection);
builder.append(",remote=").append(remote);
builder.append(",incoming=").append(incomingHandler);
builder.append(",outgoing=").append(outgoingHandler);
builder.append("]");
return builder.toString();
}
}