Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions java/javax/websocket/Endpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package javax.websocket;

import org.apache.tomcat.websocket.IdleStateEventType;

public abstract class Endpoint {

/**
Expand Down Expand Up @@ -46,4 +48,13 @@ public void onClose(Session session, CloseReason closeReason) {
public void onError(Session session, Throwable throwable) {
// NO-OP by default
}

/**
* Event that is triggered when a session is idle for more than maxIdleTime.
* @param session
* @param idleStateEventType
*/
public void onIdleSession(Session session, IdleStateEventType idleStateEventType) {
//NO-OP by default
}
}
14 changes: 14 additions & 0 deletions java/javax/websocket/Session.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@ public interface Session extends Closeable {
*/
void setMaxIdleTimeout(long timeout);

/**
* Get the flag to decide whether the session will be closed if idle Time is passed
* @return true if the session is to be closed on expire
* false if the session will remain open and an IdleSession event is thrown instead
*/
boolean getCloseOnIdleTimeout();

/**
* Get the flag to decide whether the session will be closed if idle Time is passed
* @param closeOnIdleTimeout- true for closing the session on idle timeout
* false for keeping the session open and throwing IdleSession event instead.
*/
void setCloseOnIdleTimeout(boolean closeOnIdleTimeout);

/**
* Set the current maximum buffer size for binary messages.
* @param max The new maximum buffer size in bytes
Expand Down
6 changes: 6 additions & 0 deletions java/org/apache/tomcat/websocket/IdleStateEventType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.apache.tomcat.websocket;

public enum IdleStateEventType {

IDLE_READ_EVENT, IDLE_WRITE_EVENT
}
12 changes: 12 additions & 0 deletions java/org/apache/tomcat/websocket/OnIdleSession.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.apache.tomcat.websocket;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnIdleSession {

}
2 changes: 1 addition & 1 deletion java/org/apache/tomcat/websocket/WsFrameBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public WsFrameBase(WsSession wsSession, Transformation transformation) {

protected void processInputBuffer() throws IOException {
while (!isSuspended()) {
wsSession.updateLastActive();
wsSession.updateLastReadTime();
if (state == State.NEW_FRAME) {
if (!processInitialHeader()) {
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ private long getTimeoutExpiry() {

private void sendMessageBlock(byte opCode, ByteBuffer payload, boolean last,
long timeoutExpiry) throws IOException {
wsSession.updateLastActive();
wsSession.updateLastWriteTime();

BlockingSendHandler bsh = new BlockingSendHandler();

Expand Down Expand Up @@ -340,7 +340,7 @@ private void sendMessageBlock(byte opCode, ByteBuffer payload, boolean last,
void startMessage(byte opCode, ByteBuffer payload, boolean last,
SendHandler handler) {

wsSession.updateLastActive();
wsSession.updateLastWriteTime();

List<MessagePart> messageParts = new ArrayList<>();
messageParts.add(new MessagePart(last, 0, opCode, payload,
Expand Down Expand Up @@ -423,7 +423,7 @@ void endMessage(SendHandler handler, SendResult result) {
writeMessagePart(mpNext);
}

wsSession.updateLastActive();
wsSession.updateLastWriteTime();

// Some handlers, such as the IntermediateMessageHandler, do not have a
// nested handler so handler may be null.
Expand Down
55 changes: 44 additions & 11 deletions java/org/apache/tomcat/websocket/WsSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ public class WsSession implements Session {
private volatile int maxBinaryMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;
private volatile int maxTextMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;
private volatile long maxIdleTimeout = 0;
private volatile long lastActive = System.currentTimeMillis();
private volatile long lastWriteTime = System.currentTimeMillis();
private volatile long lastReadTime = System.currentTimeMillis();
private volatile boolean closeOnIdle = true;
private Map<FutureToSendHandler, FutureToSendHandler> futures = new ConcurrentHashMap<>();

/**
Expand Down Expand Up @@ -385,6 +387,17 @@ public void setMaxIdleTimeout(long timeout) {
this.maxIdleTimeout = timeout;
}

@Override
public boolean getCloseOnIdleTimeout() {
checkState();
return this.closeOnIdle;
}

@Override
public void setCloseOnIdleTimeout(boolean closeOnIdleTimeout) {
checkState();
this.closeOnIdle = closeOnIdleTimeout;
}

@Override
public void setMaxBinaryMessageBufferSize(int max) {
Expand Down Expand Up @@ -575,8 +588,6 @@ private void fireEndpointOnClose(CloseReason closeReason) {
}
}



private void fireEndpointOnError(Throwable throwable) {

// Fire the onError event
Expand All @@ -591,6 +602,10 @@ private void fireEndpointOnError(Throwable throwable) {
}


private void fireEndpointOnIdle(IdleStateEventType idleStateEventType) {
localEndpoint.onIdleSession(this, idleStateEventType);
}

private void sendCloseMessage(CloseReason closeReason) {
// 125 is maximum size for the payload of a control message
ByteBuffer msg = ByteBuffer.allocate(125);
Expand Down Expand Up @@ -805,24 +820,42 @@ protected MessageHandler.Whole<PongMessage> getPongMessageHandler() {
}


protected void updateLastActive() {
lastActive = System.currentTimeMillis();
protected void updateLastWriteTime() {
lastWriteTime = System.currentTimeMillis();
}

protected void updateLastReadTime() {
lastReadTime = System.currentTimeMillis();
}

protected void checkExpiration() {
long timeout = maxIdleTimeout;
if (timeout < 1) {
return;
}
long currentTime = System.currentTimeMillis();
boolean isWriteTimeout = currentTime - lastWriteTime > timeout;
boolean isReadTimeout = currentTime - lastReadTime > timeout;

if (System.currentTimeMillis() - lastActive > timeout) {
String msg = sm.getString("wsSession.timeout", getId());
if (log.isDebugEnabled()) {
log.debug(msg);
if (isWriteTimeout || isReadTimeout) {
if (closeOnIdle) {
if (isWriteTimeout && isReadTimeout) {
String msg = sm.getString("wsSession.timeout", getId());
if (log.isDebugEnabled()) {
log.debug(msg);
}
doClose(new CloseReason(CloseCodes.GOING_AWAY, msg),
new CloseReason(CloseCodes.CLOSED_ABNORMALLY, msg));
}
} else {
String msg = sm.getString("wsSession.timeout", getId());
if (log.isDebugEnabled()) {
log.debug(msg);
}
IdleStateEventType idleType = isWriteTimeout ? IdleStateEventType.IDLE_WRITE_EVENT :
IdleStateEventType.IDLE_READ_EVENT;
fireEndpointOnIdle(idleType);
}
doClose(new CloseReason(CloseCodes.GOING_AWAY, msg),
new CloseReason(CloseCodes.CLOSED_ABNORMALLY, msg));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pojoEndpointBase.onCloseFail=Failed to call onClose method of POJO end point for
pojoEndpointBase.onError=No error handling configured for [{0}] and the following error occurred
pojoEndpointBase.onErrorFail=Failed to call onError method of POJO end point for POJO of type [{0}]
pojoEndpointBase.onOpenFail=Failed to call onOpen method of POJO end point for POJO of type [{0}]
pojoEndpointBase.onIdleSessionFail=Failed to call onIdleSession method of POJO end point for POJO of type [{0}]

pojoEndpointServer.getPojoInstanceFail=Failed to create instance of POJO of type [{0}]

Expand Down
16 changes: 16 additions & 0 deletions java/org/apache/tomcat/websocket/pojo/PojoEndpointBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.websocket.IdleStateEventType;

/**
* Base implementation (client and server have different concrete
Expand Down Expand Up @@ -139,6 +140,21 @@ public final void onError(Session session, Throwable throwable) {
}
}

@Override
public void onIdleSession(Session session, IdleStateEventType idleStateEventType) {

try {
methodMapping.getOnIdleSession().invoke(
pojo,
methodMapping.getOnIdleSessionArgs(pathParameters, session,
idleStateEventType));
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("pojoEndpointBase.onIdleSessionFail",
pojo.getClass().getName()), t);
}
}

protected Object getPojo() { return pojo; }
protected void setPojo(Object pojo) { this.pojo = pojo; }

Expand Down
51 changes: 43 additions & 8 deletions java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@

import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.websocket.DecoderEntry;
import org.apache.tomcat.websocket.IdleStateEventType;
import org.apache.tomcat.websocket.OnIdleSession;
import org.apache.tomcat.websocket.Util;
import org.apache.tomcat.websocket.Util.DecoderMatch;

Expand All @@ -63,9 +65,11 @@ public class PojoMethodMapping {
private final Method onOpen;
private final Method onClose;
private final Method onError;
private final Method onIdleSession;
private final PojoPathParam[] onOpenParams;
private final PojoPathParam[] onCloseParams;
private final PojoPathParam[] onErrorParams;
private final PojoPathParam[] onIdleSessionParams;
private final List<MessageHandlerInfo> onMessage = new ArrayList<>();
private final String wsPath;

Expand All @@ -80,6 +84,7 @@ public PojoMethodMapping(Class<?> clazzPojo,
Method open = null;
Method close = null;
Method error = null;
Method idleSession = null;
Method[] clazzPojoMethods = null;
Class<?> currentClazz = clazzPojo;
while (!currentClazz.equals(Object.class)) {
Expand Down Expand Up @@ -134,6 +139,19 @@ public PojoMethodMapping(Class<?> clazzPojo,
OnError.class, currentClazz));
}
}
} else if (method.getAnnotation(OnIdleSession.class) != null) {
checkPublic(method);
if (idleSession == null) {
idleSession = method;
} else {
if (currentClazz == clazzPojo ||
!isMethodOverride(idleSession, method)) {
// Duplicate annotation
throw new DeploymentException(sm.getString(
"pojoMethodMapping.duplicateAnnotation",
OnIdleSession.class, currentClazz));
}
}
} else if (method.getAnnotation(OnMessage.class) != null) {
checkPublic(method);
MessageHandlerInfo messageHandler = new MessageHandlerInfo(method, decoders);
Expand Down Expand Up @@ -189,9 +207,11 @@ && isOverridenWithoutAnnotation(clazzPojoMethods, messageHandler.m, OnMessage.cl
this.onOpen = open;
this.onClose = close;
this.onError = error;
this.onIdleSession = idleSession;
onOpenParams = getPathParams(onOpen, MethodType.ON_OPEN);
onCloseParams = getPathParams(onClose, MethodType.ON_CLOSE);
onErrorParams = getPathParams(onError, MethodType.ON_ERROR);
onIdleSessionParams = getPathParams(onIdleSession, MethodType.ON_IDLE_SESSION);
}


Expand Down Expand Up @@ -235,7 +255,7 @@ public Method getOnOpen() {
public Object[] getOnOpenArgs(Map<String,String> pathParameters,
Session session, EndpointConfig config) throws DecodeException {
return buildArgs(onOpenParams, pathParameters, session, config, null,
null);
null, null);
}


Expand All @@ -247,21 +267,29 @@ public Method getOnClose() {
public Object[] getOnCloseArgs(Map<String,String> pathParameters,
Session session, CloseReason closeReason) throws DecodeException {
return buildArgs(onCloseParams, pathParameters, session, null, null,
closeReason);
closeReason, null);
}


public Method getOnError() {
return onError;
}



public Object[] getOnErrorArgs(Map<String,String> pathParameters,
Session session, Throwable throwable) throws DecodeException {
return buildArgs(onErrorParams, pathParameters, session, null,
throwable, null);
throwable, null, null);
}

public Method getOnIdleSession() {
return onIdleSession;
}

public Object[] getOnIdleSessionArgs(Map<String,String> pathParameters,
Session session, IdleStateEventType idleStateEventType) throws DecodeException {
return buildArgs(onIdleSessionParams, pathParameters, session, null,
null, null, idleStateEventType);
}

public boolean hasMessageHandlers() {
return !onMessage.isEmpty();
Expand Down Expand Up @@ -303,6 +331,9 @@ private static PojoPathParam[] getPathParams(Method m,
} else if (methodType == MethodType.ON_CLOSE &&
type.equals(CloseReason.class)) {
result[i] = new PojoPathParam(type, null);
} else if (methodType == MethodType.ON_IDLE_SESSION &&
type.equals(IdleStateEventType.class)) {
result[i] = new PojoPathParam(type, null);
} else {
Annotation[] paramAnnotations = paramsAnnotations[i];
for (Annotation paramAnnotation : paramAnnotations) {
Expand Down Expand Up @@ -332,7 +363,8 @@ private static PojoPathParam[] getPathParams(Method m,

private static Object[] buildArgs(PojoPathParam[] pathParams,
Map<String,String> pathParameters, Session session,
EndpointConfig config, Throwable throwable, CloseReason closeReason)
EndpointConfig config, Throwable throwable, CloseReason closeReason,
IdleStateEventType IdleStateEventType)
throws DecodeException {
Object[] result = new Object[pathParams.length];
for (int i = 0; i < pathParams.length; i++) {
Expand All @@ -345,7 +377,9 @@ private static Object[] buildArgs(PojoPathParam[] pathParams,
result[i] = throwable;
} else if (type.equals(CloseReason.class)) {
result[i] = closeReason;
} else {
} else if(type.equals(IdleStateEventType.class)) {
result[i] = IdleStateEventType;
}else {
String name = pathParams[i].getName();
String value = pathParameters.get(name);
try {
Expand Down Expand Up @@ -720,6 +754,7 @@ public Set<MessageHandler> getMessageHandlers(Object pojo,
private enum MethodType {
ON_OPEN,
ON_CLOSE,
ON_ERROR
ON_ERROR,
ON_IDLE_SESSION
}
}