Merge branch 'jetty-9.2.x'

Conflicts:
	jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
	jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
This commit is contained in:
Joakim Erdfelt 2015-08-27 15:34:45 -07:00
commit 5312f2c0d7
10 changed files with 115 additions and 56 deletions

View File

@ -86,6 +86,19 @@ public class HttpParserTest
assertEquals(HttpMethod.GET,HttpMethod.lookAheadGet(b));
}
@Test
public void testLineParse_Mock_IP() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer("POST /mock/127.0.0.1 HTTP/1.1\015\012" + "\015\012");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
assertEquals("/mock/127.0.0.1", _uriOrStatus);
assertEquals("HTTP/1.1", _versionOrReason);
assertEquals(-1, _headers);
}
@Test
public void testLineParse0() throws Exception

View File

@ -18,33 +18,38 @@
package org.eclipse.jetty.websocket.api;
import java.nio.charset.StandardCharsets;
public class CloseStatus
{
private static final int MAX_CONTROL_PAYLOAD = 125;
private static final int MAX_REASON_PHRASE = MAX_CONTROL_PAYLOAD - 2;
public static final int MAX_REASON_PHRASE = MAX_CONTROL_PAYLOAD - 2;
/**
* Convenience method for trimming a long reason phrase at the maximum reason phrase length.
* Convenience method for trimming a long reason phrase at the maximum reason phrase length of 123 UTF-8 bytes (per WebSocket spec).
*
* @param reason
* the proposed reason phrase
* @return the reason phrase (trimmed if needed)
* @deprecated use of this method is strongly discouraged, as it creates too many new objects that are just thrown away to accomplish its goals.
*/
@Deprecated
public static String trimMaxReasonLength(String reason)
{
if (reason == null)
{
return null;
}
byte[] reasonBytes = reason.getBytes(StandardCharsets.UTF_8);
if (reasonBytes.length > MAX_REASON_PHRASE)
{
byte[] trimmed = new byte[MAX_REASON_PHRASE];
System.arraycopy(reasonBytes,0,trimmed,0,MAX_REASON_PHRASE);
return new String(trimmed,StandardCharsets.UTF_8);
}
if (reason.length() > MAX_REASON_PHRASE)
{
return reason.substring(0,MAX_REASON_PHRASE);
}
else
{
return reason;
}
return reason;
}
private int code;

View File

@ -152,6 +152,15 @@ public interface RemoteEndpoint
*/
BatchMode getBatchMode();
/**
* Set the batch mode with which messages are sent.
*
* @param mode
* the batch mode to use
* @see #flush()
*/
void setBatchMode(BatchMode mode);
/**
* Flushes messages that may have been batched by the implementation.
*

View File

@ -24,6 +24,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.eclipse.jetty.websocket.api.BatchMode;
/**
* Tags a POJO as being a WebSocket class.
*/
@ -40,4 +42,6 @@ public @interface WebSocket
int maxIdleTime() default -2;
int maxTextMessageSize() default -2;
BatchMode batchMode() default BatchMode.AUTO;
}

View File

@ -19,14 +19,13 @@
package org.eclipse.jetty.websocket.common;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
import org.eclipse.jetty.websocket.api.BadPayloadException;
import org.eclipse.jetty.websocket.api.CloseStatus;
import org.eclipse.jetty.websocket.api.ProtocolException;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.extensions.Frame;
@ -34,19 +33,23 @@ import org.eclipse.jetty.websocket.common.frames.CloseFrame;
public class CloseInfo
{
private static final Logger LOG = Log.getLogger(CloseInfo.class);
private int statusCode;
private String reason;
private byte[] reasonBytes;
public CloseInfo()
{
this(StatusCode.NO_CODE,null);
}
/**
* Parse the Close Frame payload.
*
* @param payload the raw close frame payload.
* @param validate true if payload should be validated per WebSocket spec.
*/
public CloseInfo(ByteBuffer payload, boolean validate)
{
this.statusCode = StatusCode.NO_CODE;
this.reason = null;
if ((payload == null) || (payload.remaining() == 0))
{
@ -77,34 +80,24 @@ public class CloseInfo
if (data.remaining() > 0)
{
// Reason
try
// Reason (trimmed to max reason size)
int len = Math.min(data.remaining(), CloseStatus.MAX_REASON_PHRASE);
reasonBytes = new byte[len];
data.get(reasonBytes,0,len);
// Spec Requirement : throw BadPayloadException on invalid UTF8
if(validate)
{
Utf8StringBuilder utf = new Utf8StringBuilder();
utf.append(data);
reason = utf.toString();
}
catch (NotUtf8Exception e)
{
if (validate)
try
{
Utf8StringBuilder utf = new Utf8StringBuilder();
// if this throws, we know we have bad UTF8
utf.append(reasonBytes,0,reasonBytes.length);
}
catch (NotUtf8Exception e)
{
throw new BadPayloadException("Invalid Close Reason",e);
}
else
{
LOG.warn(e);
}
}
catch (RuntimeException e)
{
if (validate)
{
throw new ProtocolException("Invalid Close Reason",e);
}
else
{
LOG.warn(e);
}
}
}
}
@ -125,10 +118,28 @@ public class CloseInfo
this(statusCode,null);
}
/**
* Create a CloseInfo, trimming the reason to {@link CloseStatus#MAX_REASON_PHRASE} UTF-8 bytes if needed.
*
* @param statusCode the status code
* @param reason the raw reason code
*/
public CloseInfo(int statusCode, String reason)
{
this.statusCode = statusCode;
this.reason = reason;
if (reason != null)
{
byte[] utf8Bytes = reason.getBytes(StandardCharsets.UTF_8);
if (utf8Bytes.length > CloseStatus.MAX_REASON_PHRASE)
{
this.reasonBytes = new byte[CloseStatus.MAX_REASON_PHRASE];
System.arraycopy(utf8Bytes,0,this.reasonBytes,0,CloseStatus.MAX_REASON_PHRASE);
}
else
{
this.reasonBytes = utf8Bytes;
}
}
}
private ByteBuffer asByteBuffer()
@ -140,11 +151,10 @@ public class CloseInfo
}
int len = 2; // status code
byte utf[] = null;
if (StringUtil.isNotBlank(reason))
boolean hasReason = (this.reasonBytes != null) && (this.reasonBytes.length > 0);
if (hasReason)
{
utf = StringUtil.getUtf8Bytes(reason);
len += utf.length;
len += this.reasonBytes.length;
}
ByteBuffer buf = BufferUtil.allocate(len);
@ -152,9 +162,9 @@ public class CloseInfo
buf.put((byte)((statusCode >>> 8) & 0xFF));
buf.put((byte)((statusCode >>> 0) & 0xFF));
if (utf != null)
if (hasReason)
{
buf.put(utf,0,utf.length);
buf.put(this.reasonBytes,0,this.reasonBytes.length);
}
BufferUtil.flipToFlush(buf,0);
@ -178,7 +188,11 @@ public class CloseInfo
public String getReason()
{
return reason;
if (this.reasonBytes == null)
{
return null;
}
return new String(this.reasonBytes,StandardCharsets.UTF_8);
}
public int getStatusCode()
@ -199,6 +213,6 @@ public class CloseInfo
@Override
public String toString()
{
return String.format("CloseInfo[code=%d,reason=%s]",statusCode,reason);
return String.format("CloseInfo[code=%d,reason=%s]",statusCode,getReason());
}
}

View File

@ -436,9 +436,7 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
return batchMode;
}
// Only the JSR needs to have this method exposed.
// In the Jetty implementation the batching is set
// at the moment of opening the session.
@Override
public void setBatchMode(BatchMode batchMode)
{
this.batchMode = batchMode;

View File

@ -25,6 +25,7 @@ import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
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.CloseException;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
@ -208,6 +209,12 @@ public abstract class AbstractEventDriver implements IncomingFrames, EventDriver
{
/* TODO: provide annotation in future */
}
@Override
public BatchMode getBatchMode()
{
return null;
}
@Override
public void openSession(WebSocketSession session)

View File

@ -23,6 +23,7 @@ import java.io.InputStream;
import java.io.Reader;
import java.nio.ByteBuffer;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
@ -34,6 +35,8 @@ public interface EventDriver extends IncomingFrames
public WebSocketPolicy getPolicy();
public WebSocketSession getSession();
public BatchMode getBatchMode();
public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException;

View File

@ -23,6 +23,7 @@ import java.io.InputStream;
import java.io.Reader;
import java.nio.ByteBuffer;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.api.extensions.Frame;
@ -40,6 +41,7 @@ public class JettyAnnotatedEventDriver extends AbstractEventDriver
{
private final JettyAnnotatedMetadata events;
private boolean hasCloseBeenCalled = false;
private BatchMode batchMode;
public JettyAnnotatedEventDriver(WebSocketPolicy policy, Object websocket, JettyAnnotatedMetadata events)
{
@ -64,6 +66,13 @@ public class JettyAnnotatedEventDriver extends AbstractEventDriver
{
this.policy.setIdleTimeout(anno.maxIdleTime());
}
this.batchMode = anno.batchMode();
}
@Override
public BatchMode getBatchMode()
{
return this.batchMode;
}
@Override

View File

@ -27,7 +27,6 @@ import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.CloseStatus;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.ConnectionState;
@ -462,7 +461,6 @@ public class IOState
}
}
reason = CloseStatus.trimMaxReasonLength(reason);
CloseInfo close = new CloseInfo(StatusCode.ABNORMAL,reason);
finalClose.compareAndSet(null,close);
@ -514,7 +512,6 @@ public class IOState
}
}
reason = CloseStatus.trimMaxReasonLength(reason);
CloseInfo close = new CloseInfo(StatusCode.ABNORMAL,reason);
finalClose.compareAndSet(null,close);