Merge branch 'master' into release-9

This commit is contained in:
Jesse McConnell 2014-02-25 14:26:32 -06:00
commit a585f57175
154 changed files with 5186 additions and 2734 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
.classpath
.project
.settings
.gitignore
# maven
target/

View File

@ -24,7 +24,6 @@
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1-b08</version>
<scope>provided</scope>
</dependency>
</dependencies>

View File

@ -66,6 +66,14 @@ public class AbstractRestServlet extends HttpServlet
else
_appid = servletConfig.getInitParameter(APPID_PARAM);
}
public static String sanitize(String s)
{
if (s==null)
return null;
return s.replace("<","?").replace("&","?").replace("\n","?");
}
protected String restURL(String item)
{

View File

@ -97,7 +97,7 @@ public class AsyncRestServlet extends AbstractRestServlet
async.setTimeout(30000);
// extract keywords to search for
String[] keywords=request.getParameter(ITEMS_PARAM).split(",");
String[] keywords=sanitize(request.getParameter(ITEMS_PARAM)).split(",");
final AtomicInteger outstanding=new AtomicInteger(keywords.length);
// Send request each keyword
@ -146,7 +146,7 @@ public class AsyncRestServlet extends AbstractRestServlet
long generate=now-start;
long thread=initial+generate;
out.print("<b>Asynchronous: "+request.getParameter(ITEMS_PARAM)+"</b><br/>");
out.print("<b>Asynchronous: "+sanitize(request.getParameter(ITEMS_PARAM))+"</b><br/>");
out.print("Total Time: "+ms(total)+"ms<br/>");
out.print("Thread held (<span class='red'>red</span>): "+ms(thread)+"ms (" + ms(initial) + " initial + " + ms(generate) + " generate )<br/>");
@ -162,7 +162,7 @@ public class AsyncRestServlet extends AbstractRestServlet
out.println("</body></html>");
out.close();
}
private abstract class AsyncRestRequest extends Response.Listener.Adapter
{
final Utf8StringBuilder _content = new Utf8StringBuilder();

View File

@ -45,7 +45,7 @@ public class SerialRestServlet extends AbstractRestServlet
long start = System.nanoTime();
String[] keywords=request.getParameter(ITEMS_PARAM).split(",");
String[] keywords=sanitize(request.getParameter(ITEMS_PARAM)).split(",");
Queue<Map<String,String>> results = new LinkedList<Map<String,String>>();
// make all requests serially
@ -78,7 +78,7 @@ public class SerialRestServlet extends AbstractRestServlet
long now = System.nanoTime();
long total=now-start;
out.print("<b>Blocking: "+request.getParameter(ITEMS_PARAM)+"</b><br/>");
out.print("<b>Blocking: "+sanitize(request.getParameter(ITEMS_PARAM))+"</b><br/>");
out.print("Total Time: "+ms(total)+"ms<br/>");
out.print("Thread held (<span class='red'>red</span>): "+ms(total)+"ms<br/>");

View File

@ -27,7 +27,6 @@
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1-b08</version>
<scope>provided</scope>
</dependency>
</dependencies>

View File

@ -438,18 +438,29 @@ public class AnnotationConfiguration extends AbstractConfiguration
createServletContainerInitializerAnnotationHandlers(context, getNonExcludedInitializers(context));
if (!_discoverableAnnotationHandlers.isEmpty() || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
scanForAnnotations(context);
scanForAnnotations(context);
// Resolve container initializers
List<ContainerInitializer> initializers =
(List<ContainerInitializer>)context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS);
if (initializers != null && initializers.size()>0)
{
Map<String, Set<String>> map = ( Map<String, Set<String>>) context.getAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP);
if (map == null)
throw new IllegalStateException ("No class hierarchy");
for (ContainerInitializer i : initializers)
i.resolveClasses(context,map);
}
}
/**
* @see org.eclipse.jetty.webapp.AbstractConfiguration#postConfigure(org.eclipse.jetty.webapp.WebAppContext)
*/
@Override
public void postConfigure(WebAppContext context) throws Exception
{
ConcurrentHashMap<String, ConcurrentHashSet<String>> classMap = (ConcurrentHashMap<String, ConcurrentHashSet<String>>)context.getAttribute(CLASS_INHERITANCE_MAP);
ConcurrentHashMap<String, ConcurrentHashSet<String>> classMap = (ClassInheritanceMap)context.getAttribute(CLASS_INHERITANCE_MAP);
List<ContainerInitializer> initializers = (List<ContainerInitializer>)context.getAttribute(CONTAINER_INITIALIZERS);
context.removeAttribute(CLASS_INHERITANCE_MAP);
@ -655,7 +666,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
if (annotation != null)
{
//There is a HandlesTypes annotation on the on the ServletContainerInitializer
Class[] classes = annotation.value();
Class<?>[] classes = annotation.value();
if (classes != null)
{
initializer = new ContainerInitializer(service, classes);
@ -665,12 +676,12 @@ public class AnnotationConfiguration extends AbstractConfiguration
if (context.getAttribute(CLASS_INHERITANCE_MAP) == null)
{
//MultiMap<String> map = new MultiMap<>();
ConcurrentHashMap<String, ConcurrentHashSet<String>> map = new ConcurrentHashMap<String, ConcurrentHashSet<String>>();
ConcurrentHashMap<String, ConcurrentHashSet<String>> map = new ClassInheritanceMap();
context.setAttribute(CLASS_INHERITANCE_MAP, map);
_classInheritanceHandler = new ClassInheritanceHandler(map);
}
for (Class c: classes)
for (Class<?> c: classes)
{
//The value of one of the HandlesTypes classes is actually an Annotation itself so
//register a handler for it
@ -1044,4 +1055,16 @@ public class AnnotationConfiguration extends AbstractConfiguration
{
return (d!=null && d.getMetaDataComplete() == MetaDataComplete.True);
}
public static class ClassInheritanceMap extends ConcurrentHashMap<String, ConcurrentHashSet<String>>
{
@Override
public String toString()
{
return String.format("ClassInheritanceMap@%x{size=%d}",hashCode(),size());
}
}
}

View File

@ -70,7 +70,7 @@ public class MultiPartConfigAnnotationHandler extends AbstractIntrospectableAnno
//let the annotation override it
if (d == null)
{
metaData.setOrigin(holder.getName()+".servlet.multipart-config");
metaData.setOrigin(holder.getName()+".servlet.multipart-config",multi,clazz);
holder.getRegistration().setMultipartConfig(new MultipartConfigElement(multi));
}
}

View File

@ -180,7 +180,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
injections.add(injection);
//TODO - an @Resource is equivalent to a resource-ref, resource-env-ref, message-destination
metaData.setOrigin("resource-ref."+name+".injection");
metaData.setOrigin("resource-ref."+name+".injection",resource,clazz);
}
else if (!Util.isEnvEntryType(type))
{
@ -334,7 +334,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
injection.setMappingName(mappedName);
injections.add(injection);
//TODO - an @Resource is equivalent to a resource-ref, resource-env-ref, message-destination
metaData.setOrigin("resource-ref."+name+".injection");
metaData.setOrigin("resource-ref."+name+".injection",resource,clazz);
}
else if (!Util.isEnvEntryType(paramType))
{

View File

@ -64,7 +64,7 @@ public class RunAsAnnotationHandler extends AbstractIntrospectableAnnotationHand
//let the annotation override it
if (d == null)
{
metaData.setOrigin(holder.getName()+".servlet.run-as");
metaData.setOrigin(holder.getName()+".servlet.run-as",runAs,clazz);
org.eclipse.jetty.plus.annotation.RunAs ra = new org.eclipse.jetty.plus.annotation.RunAs();
ra.setTargetClassName(clazz.getCanonicalName());
ra.setRoleName(role);

View File

@ -59,14 +59,9 @@ public class ServletContainerInitializersStarter extends AbstractLifeCycle imple
List<ContainerInitializer> initializers = (List<ContainerInitializer>)_context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS);
if (initializers == null)
return;
ConcurrentHashMap<String, ConcurrentHashSet<String>> map = ( ConcurrentHashMap<String, ConcurrentHashSet<String>>)_context.getAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP);
for (ContainerInitializer i : initializers)
{
configureHandlesTypes(_context, i, map);
//instantiate ServletContainerInitializers, call doStart
try
{
if (LOG.isDebugEnabled())
@ -80,82 +75,6 @@ public class ServletContainerInitializersStarter extends AbstractLifeCycle imple
}
}
}
private void configureHandlesTypes (WebAppContext context, ContainerInitializer initializer, ConcurrentHashMap<String, ConcurrentHashSet<String>> classMap)
{
doHandlesTypesAnnotations(context, initializer, classMap);
doHandlesTypesClasses(context, initializer, classMap);
}
private void doHandlesTypesAnnotations(WebAppContext context, ContainerInitializer initializer, ConcurrentHashMap<String, ConcurrentHashSet<String>> classMap)
{
if (initializer == null)
return;
if (context == null)
throw new IllegalArgumentException("WebAppContext null");
//We have already found the classes that directly have an annotation that was in the HandlesTypes
//annotation of the ServletContainerInitializer. For each of those classes, walk the inheritance
//hierarchy to find classes that extend or implement them.
Set<String> annotatedClassNames = initializer.getAnnotatedTypeNames();
if (annotatedClassNames != null && !annotatedClassNames.isEmpty())
{
if (classMap == null)
throw new IllegalStateException ("No class hierarchy");
for (String name : annotatedClassNames)
{
//add the class that has the annotation
initializer.addApplicableTypeName(name);
//find and add the classes that inherit the annotation
addInheritedTypes(classMap, initializer, (ConcurrentHashSet<String>)classMap.get(name));
}
}
}
private void doHandlesTypesClasses (WebAppContext context, ContainerInitializer initializer, ConcurrentHashMap<String, ConcurrentHashSet<String>> classMap)
{
if (initializer == null)
return;
if (context == null)
throw new IllegalArgumentException("WebAppContext null");
//Now we need to look at the HandlesTypes classes that were not annotations. We need to
//find all classes that extend or implement them.
if (initializer.getInterestedTypes() != null)
{
if (classMap == null)
throw new IllegalStateException ("No class hierarchy");
for (Class c : initializer.getInterestedTypes())
{
if (!c.isAnnotation())
{
//find and add the classes that implement or extend the class.
//but not including the class itself
addInheritedTypes(classMap, initializer, (ConcurrentHashSet<String>)classMap.get(c.getName()));
}
}
}
}
private void addInheritedTypes (ConcurrentHashMap<String, ConcurrentHashSet<String>> classMap, ContainerInitializer initializer, ConcurrentHashSet<String> names)
{
if (names == null || names.isEmpty())
return;
for (String s : names)
{
//add the name of the class
initializer.addApplicableTypeName(s);
//walk the hierarchy and find all types that extend or implement the class
addInheritedTypes(classMap, initializer, (ConcurrentHashSet<String>)classMap.get(s));
}
}
}

View File

@ -102,7 +102,7 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
{
for (String url : sm.getPathSpecs())
{
_context.getMetaData().setOrigin("constraint.url."+url, Origin.Annotation);
_context.getMetaData().setOrigin("constraint.url."+url,servletSecurity,clazz);
constraintMappings.addAll(ConstraintSecurityHandler.createConstraintsWithMappingsForPath(clazz.getName(), url, securityElement));
}
}

View File

@ -104,15 +104,15 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
holder.setName(name);
holder.setHeldClass(clazz);
metaData.setOrigin(name+".filter.filter-class");
metaData.setOrigin(name+".filter.filter-class",filterAnnotation,clazz);
holder.setDisplayName(filterAnnotation.displayName());
metaData.setOrigin(name+".filter.display-name");
metaData.setOrigin(name+".filter.display-name",filterAnnotation,clazz);
for (WebInitParam ip: filterAnnotation.initParams())
{
holder.setInitParameter(ip.name(), ip.value());
metaData.setOrigin(name+".filter.init-param."+ip.name());
metaData.setOrigin(name+".filter.init-param."+ip.name(),ip,clazz);
}
FilterMapping mapping = new FilterMapping();
@ -120,12 +120,12 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
if (urlPatterns.length > 0)
{
ArrayList paths = new ArrayList();
ArrayList<String> paths = new ArrayList<String>();
for (String s:urlPatterns)
{
paths.add(Util.normalizePattern(s));
}
mapping.setPathSpecs((String[])paths.toArray(new String[paths.size()]));
mapping.setPathSpecs(paths.toArray(new String[paths.size()]));
}
if (filterAnnotation.servletNames().length > 0)
@ -135,7 +135,7 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
{
names.add(s);
}
mapping.setServletNames((String[])names.toArray(new String[names.size()]));
mapping.setServletNames(names.toArray(new String[names.size()]));
}
EnumSet<DispatcherType> dispatcherSet = EnumSet.noneOf(DispatcherType.class);
@ -144,10 +144,10 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
dispatcherSet.add(d);
}
mapping.setDispatcherTypes(dispatcherSet);
metaData.setOrigin(name+".filter.mappings");
metaData.setOrigin(name+".filter.mappings",filterAnnotation,clazz);
holder.setAsyncSupported(filterAnnotation.asyncSupported());
metaData.setOrigin(name+".filter.async-supported");
metaData.setOrigin(name+".filter.async-supported",filterAnnotation,clazz);
_context.getServletHandler().addFilter(holder);
_context.getServletHandler().addFilterMapping(mapping);
@ -165,7 +165,7 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
if (metaData.getOrigin(name+".filter.init-param."+ip.name())==Origin.NotSet)
{
holder.setInitParameter(ip.name(), ip.value());
metaData.setOrigin(name+".filter.init-param."+ip.name());
metaData.setOrigin(name+".filter.init-param."+ip.name(),ip,clazz);
}
}
@ -191,12 +191,12 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
if (urlPatterns.length > 0)
{
ArrayList paths = new ArrayList();
ArrayList<String> paths = new ArrayList<String>();
for (String s:urlPatterns)
{
paths.add(Util.normalizePattern(s));
}
mapping.setPathSpecs((String[])paths.toArray(new String[paths.size()]));
mapping.setPathSpecs(paths.toArray(new String[paths.size()]));
}
if (filterAnnotation.servletNames().length > 0)
{
@ -205,7 +205,7 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
{
names.add(s);
}
mapping.setServletNames((String[])names.toArray(new String[names.size()]));
mapping.setServletNames(names.toArray(new String[names.size()]));
}
EnumSet<DispatcherType> dispatcherSet = EnumSet.noneOf(DispatcherType.class);
@ -215,7 +215,7 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
}
mapping.setDispatcherTypes(dispatcherSet);
_context.getServletHandler().addFilterMapping(mapping);
metaData.setOrigin(name+".filter.mappings");
metaData.setOrigin(name+".filter.mappings",filterAnnotation,clazz);
}
}
}

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.annotations;
import java.util.ArrayList;
import javax.servlet.Servlet;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
@ -62,7 +63,7 @@ public class WebServletAnnotation extends DiscoveredAnnotation
public void apply()
{
//TODO check this algorithm with new rules for applying descriptors and annotations in order
Class clazz = getTargetClass();
Class<? extends Servlet> clazz = (Class<? extends Servlet>)getTargetClass();
if (clazz == null)
{
@ -127,22 +128,22 @@ public class WebServletAnnotation extends DiscoveredAnnotation
//or another annotation (which would be impossible).
holder = _context.getServletHandler().newServletHolder(Holder.Source.ANNOTATION);
holder.setHeldClass(clazz);
metaData.setOrigin(servletName+".servlet.servlet-class");
metaData.setOrigin(servletName+".servlet.servlet-class",annotation,clazz);
holder.setName(servletName);
holder.setDisplayName(annotation.displayName());
metaData.setOrigin(servletName+".servlet.display-name");
metaData.setOrigin(servletName+".servlet.display-name",annotation,clazz);
holder.setInitOrder(annotation.loadOnStartup());
metaData.setOrigin(servletName+".servlet.load-on-startup");
metaData.setOrigin(servletName+".servlet.load-on-startup",annotation,clazz);
holder.setAsyncSupported(annotation.asyncSupported());
metaData.setOrigin(servletName+".servlet.async-supported");
metaData.setOrigin(servletName+".servlet.async-supported",annotation,clazz);
for (WebInitParam ip:annotation.initParams())
{
holder.setInitParameter(ip.name(), ip.value());
metaData.setOrigin(servletName+".servlet.init-param."+ip.name());
metaData.setOrigin(servletName+".servlet.init-param."+ip.name(),ip,clazz);
}
_context.getServletHandler().addServlet(holder);
@ -150,7 +151,7 @@ public class WebServletAnnotation extends DiscoveredAnnotation
mapping.setServletName(holder.getName());
mapping.setPathSpecs( LazyList.toStringArray(urlPatternList));
_context.getServletHandler().addServletMapping(mapping);
metaData.setOrigin(servletName+".servlet.mappings");
metaData.setOrigin(servletName+".servlet.mappings",annotation,clazz);
}
else
{
@ -170,7 +171,7 @@ public class WebServletAnnotation extends DiscoveredAnnotation
if (metaData.getOrigin(servletName+".servlet.init-param."+ip.name())==Origin.NotSet)
{
holder.setInitParameter(ip.name(), ip.value());
metaData.setOrigin(servletName+".servlet.init-param."+ip.name());
metaData.setOrigin(servletName+".servlet.init-param."+ip.name(),ip,clazz);
}
}

View File

@ -206,10 +206,22 @@ public class HttpRequest implements Request
@Override
public Request param(String name, String value)
{
return param(name, value, false);
}
private Request param(String name, String value, boolean fromQuery)
{
params.add(name, value);
this.query = buildQuery();
this.uri = null;
if (!fromQuery)
{
// If we have an existing query string, preserve it and append the new parameter.
if (query != null)
query += "&" + name + "=" + urlEncode(value);
else
query = buildQuery();
uri = null;
}
return this;
}
@ -640,6 +652,9 @@ public class HttpRequest implements Request
private String urlEncode(String value)
{
if (value == null)
return "";
String encoding = "UTF-8";
try
{
@ -663,7 +678,7 @@ public class HttpRequest implements Request
String name = parts[0];
if (name.trim().length() == 0)
continue;
param(name, parts.length < 2 ? "" : urlDecode(parts[1]));
param(name, parts.length < 2 ? "" : urlDecode(parts[1]), true);
}
}
}

View File

@ -36,7 +36,6 @@ public class RequestNotifier
this.client = client;
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyQueued(Request request)
{
// Optimized to avoid allocations of iterator instances
@ -67,7 +66,6 @@ public class RequestNotifier
}
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyBegin(Request request)
{
// Optimized to avoid allocations of iterator instances
@ -98,7 +96,6 @@ public class RequestNotifier
}
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyHeaders(Request request)
{
// Optimized to avoid allocations of iterator instances
@ -129,7 +126,6 @@ public class RequestNotifier
}
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyCommit(Request request)
{
// Optimized to avoid allocations of iterator instances
@ -160,7 +156,6 @@ public class RequestNotifier
}
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyContent(Request request, ByteBuffer content)
{
// Slice the buffer to avoid that listeners peek into data they should not look at.
@ -203,7 +198,6 @@ public class RequestNotifier
}
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public void notifySuccess(Request request)
{
// Optimized to avoid allocations of iterator instances
@ -234,7 +228,6 @@ public class RequestNotifier
}
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyFailure(Request request, Throwable failure)
{
// Optimized to avoid allocations of iterator instances

View File

@ -40,7 +40,6 @@ public class ResponseNotifier
this.client = client;
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyBegin(List<Response.ResponseListener> listeners, Response response)
{
// Optimized to avoid allocations of iterator instances
@ -64,7 +63,6 @@ public class ResponseNotifier
}
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public boolean notifyHeader(List<Response.ResponseListener> listeners, Response response, HttpField field)
{
boolean result = true;
@ -91,7 +89,6 @@ public class ResponseNotifier
}
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyHeaders(List<Response.ResponseListener> listeners, Response response)
{
// Optimized to avoid allocations of iterator instances
@ -115,7 +112,6 @@ public class ResponseNotifier
}
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyContent(List<Response.ResponseListener> listeners, Response response, ByteBuffer buffer)
{
// Slice the buffer to avoid that listeners peek into data they should not look at.
@ -148,7 +144,6 @@ public class ResponseNotifier
}
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public void notifySuccess(List<Response.ResponseListener> listeners, Response response)
{
// Optimized to avoid allocations of iterator instances
@ -172,7 +167,6 @@ public class ResponseNotifier
}
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyFailure(List<Response.ResponseListener> listeners, Response response, Throwable failure)
{
// Optimized to avoid allocations of iterator instances
@ -196,7 +190,6 @@ public class ResponseNotifier
}
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyComplete(List<Response.ResponseListener> listeners, Result result)
{
// Optimized to avoid allocations of iterator instances

View File

@ -277,6 +277,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
{
final String paramName = "a";
final String paramValue = "\u20AC";
final String encodedParamValue = URLEncoder.encode(paramValue, "UTF-8");
start(new AbstractHandler()
{
@Override
@ -293,7 +294,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
}
});
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort() + "/path?" + paramName + "=" + paramValue);
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort() + "/path?" + paramName + "=" + encodedParamValue);
ContentResponse response = client.newRequest(uri)
.method(HttpMethod.PUT)
.timeout(5, TimeUnit.SECONDS)

View File

@ -21,7 +21,6 @@ package org.eclipse.jetty.client;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -339,4 +338,97 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testRawQueryIsPreservedInURI() throws Exception
{
final String name = "a";
final String rawValue = "Hello%20World";
final String rawQuery = name + "=" + rawValue;
final String value = "Hello World";
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Assert.assertEquals(rawQuery, request.getQueryString());
Assert.assertEquals(value, request.getParameter(name));
}
});
String uri = scheme + "://localhost:" + connector.getLocalPort() + "/path?" + rawQuery;
Request request = client.newRequest(uri)
.timeout(5, TimeUnit.SECONDS);
Assert.assertEquals(rawQuery, request.getQuery());
ContentResponse response = request.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testRawQueryIsPreservedInPath() throws Exception
{
final String name = "a";
final String rawValue = "Hello%20World";
final String rawQuery = name + "=" + rawValue;
final String value = "Hello World";
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Assert.assertEquals(rawQuery, request.getQueryString());
Assert.assertEquals(value, request.getParameter(name));
}
});
Request request = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/path?" + rawQuery)
.timeout(5, TimeUnit.SECONDS);
Assert.assertEquals(rawQuery, request.getQuery());
ContentResponse response = request.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testRawQueryIsPreservedWithParam() throws Exception
{
final String name1 = "a";
final String name2 = "b";
final String rawValue1 = "Hello%20World";
final String rawQuery1 = name1 + "=" + rawValue1;
final String value1 = "Hello World";
final String value2 = "alfa omega";
final String encodedQuery2 = name2 + "=" + URLEncoder.encode(value2, "UTF-8");
final String query = rawQuery1 + "&" + encodedQuery2;
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Assert.assertEquals(query, request.getQueryString());
Assert.assertEquals(value1, request.getParameter(name1));
Assert.assertEquals(value2, request.getParameter(name2));
}
});
Request request = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/path?" + rawQuery1)
.param(name2, value2)
.timeout(5, TimeUnit.SECONDS);
Assert.assertEquals(query, request.getQuery());
ContentResponse response = request.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
}

View File

@ -442,6 +442,13 @@ public class HttpParser
break;
else if (ch<0)
throw new BadMessage();
// count this white space as a header byte to avoid DOS
if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
{
LOG.warn("padding is too large >"+_maxHeaderBytes);
throw new BadMessage(HttpStatus.BAD_REQUEST_400);
}
}
return false;
}

View File

@ -311,7 +311,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint
shutdownInput();
if (_ishut)
return -1;
int filled=BufferUtil.flipPutFlip(_in,buffer);
int filled=BufferUtil.append(buffer,_in);
if (filled>0)
notIdle();
return filled;
@ -342,12 +342,12 @@ public class ByteArrayEndPoint extends AbstractEndPoint
if (b.remaining()>BufferUtil.space(_out))
{
ByteBuffer n = BufferUtil.allocate(_out.capacity()+b.remaining()*2);
BufferUtil.flipPutFlip(_out,n);
BufferUtil.append(n,_out);
_out=n;
}
}
if (BufferUtil.flipPutFlip(b,_out)>0)
if (BufferUtil.append(_out,b)>0)
idle=false;
if (BufferUtil.hasContent(b))

View File

@ -181,7 +181,8 @@ public class ChannelEndPoint extends AbstractEndPoint
}
}
}
LOG.debug("flushed {} {}", flushed, this);
if (LOG.isDebugEnabled())
LOG.debug("flushed {} {}", flushed, this);
}
catch (IOException e)
{

View File

@ -469,7 +469,7 @@ public class SslConnection extends AbstractConnection
{
// Do we already have some decrypted data?
if (BufferUtil.hasContent(_decryptedInput))
return BufferUtil.flipPutFlip(_decryptedInput, buffer);
return BufferUtil.append(buffer,_decryptedInput);
// We will need a network buffer
if (_encryptedInput == null)
@ -574,7 +574,7 @@ public class SslConnection extends AbstractConnection
{
if (app_in == buffer)
return unwrapResult.bytesProduced();
return BufferUtil.flipPutFlip(_decryptedInput, buffer);
return BufferUtil.append(buffer,_decryptedInput);
}
switch (handshakeStatus)

View File

@ -162,7 +162,7 @@ public class SelectChannelEndPointTest
}
// Copy to the out buffer
if (BufferUtil.hasContent(_in) && BufferUtil.flipPutFlip(_in, _out) > 0)
if (BufferUtil.hasContent(_in) && BufferUtil.append(_out, _in) > 0)
progress = true;
// Blocking writes

View File

@ -55,17 +55,18 @@ import org.eclipse.jetty.util.log.Logger;
*
* <p><h4>Notes</h4>
* <p>All Names are expected to be Compound, not Composite.
*
*
*/
@SuppressWarnings("unchecked")
public class NamingContext implements Context, Cloneable, Dumpable
{
private final static Logger __log=NamingUtil.__log;
private final static List<Binding> __empty = Collections.emptyList();
public static final String LOCK_PROPERTY = "org.eclipse.jndi.lock";
public static final String UNLOCK_PROPERTY = "org.eclipse.jndi.unlock";
public static final String DEEP_BINDING = "org.eclipse.jetty.jndi.deepBinding";
public static final String LOCK_PROPERTY = "org.eclipse.jetty.jndi.lock";
public static final String UNLOCK_PROPERTY = "org.eclipse.jetty.jndi.unlock";
protected final Hashtable<String,Object> _env = new Hashtable<String,Object>();
private boolean _supportDeepBinding = false;
protected Map<String,Binding> _bindings = new HashMap<String,Binding>();
protected NamingContext _parent = null;
@ -110,15 +111,33 @@ public class NamingContext implements Context, Cloneable, Dumpable
NameParser parser)
{
if (env != null)
{
_env.putAll(env);
// look for deep binding support in _env
Object support = _env.get(DEEP_BINDING);
if (support == null)
_supportDeepBinding = false;
else
_supportDeepBinding = support != null?Boolean.parseBoolean(support.toString()):false;
}
else
{
// no env? likely this is a root context (java or local) that
// was created without an _env. Look for a system property.
String value = System.getProperty(DEEP_BINDING,"false");
_supportDeepBinding = Boolean.parseBoolean(value);
// put what we discovered into the _env for later sub-contexts
// to utilize.
_env.put(DEEP_BINDING,_supportDeepBinding);
}
_name = name;
_parent = parent;
_parser = parser;
if(__log.isDebugEnabled())
__log.debug("supportDeepBinding={}",_supportDeepBinding);
}
/*------------------------------------------------*/
/**
* Clone this NamingContext
@ -170,10 +189,13 @@ public class NamingContext implements Context, Cloneable, Dumpable
}
public void setEnv (Hashtable<String,Object> env)
public final void setEnv (Hashtable<String,Object> env)
{
_env.clear();
if(env == null)
return;
_env.putAll(env);
_supportDeepBinding = _env.containsKey(DEEP_BINDING);
}
@ -240,8 +262,19 @@ public class NamingContext implements Context, Cloneable, Dumpable
{
Binding binding = getBinding (firstComponent);
if (binding == null)
throw new NameNotFoundException (firstComponent+ " is not bound");
if (binding == null) {
if (_supportDeepBinding)
{
Name subname = _parser.parse(firstComponent);
Context subctx = new NamingContext((Hashtable)_env.clone(),firstComponent,this,_parser);
addBinding(subname,subctx);
binding = getBinding(subname);
}
else
{
throw new NameNotFoundException(firstComponent + " is not bound");
}
}
ctx = binding.getObject();
@ -1248,8 +1281,15 @@ public class NamingContext implements Context, Cloneable, Dumpable
if (binding!=null)
{
if (_bindings.containsKey(key))
if (_bindings.containsKey(key)) {
if(_supportDeepBinding) {
// quietly return (no exception)
// this is jndi spec breaking, but is added to support broken
// jndi users like openejb.
return;
}
throw new NameAlreadyBoundException(name.toString());
}
_bindings.put(key,binding);
}
}
@ -1407,4 +1447,28 @@ public class NamingContext implements Context, Cloneable, Dumpable
{
return _listeners.remove(listener);
}
@Override
public String toString()
{
StringBuilder buf = new StringBuilder();
buf.append(this.getClass().getName()).append('@').append(Integer.toHexString(this.hashCode()));
buf.append("[name=").append(this._name);
buf.append(",parent=");
if (this._parent != null)
{
buf.append(this._parent.getClass().getName()).append('@').append(Integer.toHexString(this._parent.hashCode()));
}
buf.append(",bindings");
if (this._bindings == null)
{
buf.append("=<null>");
}
else
{
buf.append(".size=").append(this._bindings.size());
}
buf.append(']');
return buf.toString();
}
}

View File

@ -46,14 +46,6 @@ import org.eclipse.jetty.util.log.Logger;
* <p><h4>Usage</h4>
* <pre>
*/
/*
* </pre>
*
* @see
*
*
* @version 1.0
*/
public class javaRootURLContext implements Context
{
private static Logger __log = NamingUtil.__log;
@ -81,7 +73,7 @@ public class javaRootURLContext implements Context
ContextFactory.class.getName(),
(String)null);
//bind special object factory at comp
// bind special object factory at comp
__nameRoot.bind ("comp", ref);
}
catch (Exception e)
@ -103,7 +95,6 @@ public class javaRootURLContext implements Context
_env = env;
}
public Object lookup(Name name)
throws NamingException
{

View File

@ -85,7 +85,7 @@ public class JettyStopMojo extends AbstractMojo
s.setSoTimeout(stopWait * 1000);
s.getInputStream();
System.err.printf("Waiting %d seconds for jetty to stop%n",stopWait);
getLog().info("Waiting "+stopWait+" seconds for jetty to stop");
LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
String response;
boolean stopped = false;
@ -94,7 +94,7 @@ public class JettyStopMojo extends AbstractMojo
if ("Stopped".equals(response))
{
stopped = true;
System.err.println("Server reports itself as Stopped");
getLog().info("Server reports itself as stopped");
}
}
}

View File

@ -365,13 +365,16 @@ public class MongoSessionManager extends NoSqlSessionManager
// followed by bindings and then activation.
session.willPassivate();
try
{
session.clearAttributes();
DBObject attrs = (DBObject)getNestedValue(o,getContextKey());
if (attrs != null)
{
DBObject attrs = (DBObject)getNestedValue(o,getContextKey());
//if disk version now has no attributes, get rid of them
if (attrs == null || attrs.keySet().size() == 0)
{
session.clearAttributes();
}
else
{
//iterate over the names of the attributes on the disk version, updating the value
for (String name : attrs.keySet())
{
//skip special metadata field which is not one of the session attributes
@ -381,23 +384,25 @@ public class MongoSessionManager extends NoSqlSessionManager
String attr = decodeName(name);
Object value = decodeValue(attrs.get(name));
if (attrs.keySet().contains(name))
{
//session does not already contain this attribute, so bind it
if (session.getAttribute(attr) == null)
{
session.doPutOrRemove(attr,value);
session.bindValue(attr,value);
}
else
else //session already contains this attribute, update its value
{
session.doPutOrRemove(attr,value);
}
}
// cleanup, remove values from session, that don't exist in data anymore:
for (String name : session.getNames())
for (String str : session.getNames())
{
if (!attrs.keySet().contains(name))
if (!attrs.keySet().contains(str))
{
session.doPutOrRemove(name,null);
session.unbindValue(name,session.getAttribute(name));
session.doPutOrRemove(str,null);
session.unbindValue(str,session.getAttribute(str));
}
}
}

View File

@ -18,15 +18,22 @@
package org.eclipse.jetty.plus.annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletContainerInitializer;
import org.eclipse.jetty.util.ConcurrentHashSet;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.WebAppContext;
@ -36,17 +43,41 @@ public class ContainerInitializer
private static final Logger LOG = Log.getLogger(ContainerInitializer.class);
final protected ServletContainerInitializer _target;
final protected Class[] _interestedTypes;
protected Set<String> _applicableTypeNames = new ConcurrentHashSet<String>();
protected Set<String> _annotatedTypeNames = new ConcurrentHashSet<String>();
final protected Class<?>[] _interestedTypes;
final protected Set<String> _applicableTypeNames = new ConcurrentHashSet<String>();
final protected Set<String> _annotatedTypeNames = new ConcurrentHashSet<String>();
public ContainerInitializer (ServletContainerInitializer target, Class[] classes)
public ContainerInitializer (ServletContainerInitializer target, Class<?>[] classes)
{
_target = target;
_interestedTypes = classes;
}
public ContainerInitializer (ClassLoader loader, String toString)
{
Matcher m = Pattern.compile("ContainerInitializer\\{(.*),interested=(.*),applicable=(.*),annotated=(.*)\\}").matcher(toString);
if (!m.matches())
throw new IllegalArgumentException(toString);
try
{
_target = (ServletContainerInitializer)loader.loadClass(m.group(1)).newInstance();
String[] interested = StringUtil.arrayFromString(m.group(2));
_interestedTypes = new Class<?>[interested.length];
for (int i=0;i<interested.length;i++)
_interestedTypes[i]=loader.loadClass(interested[i]);
for (String s:StringUtil.arrayFromString(m.group(3)))
_applicableTypeNames.add(s);
for (String s:StringUtil.arrayFromString(m.group(4)))
_annotatedTypeNames.add(s);
}
catch(Exception e)
{
throw new IllegalArgumentException(toString, e);
}
}
public ServletContainerInitializer getTarget ()
{
return _target;
@ -116,4 +147,67 @@ public class ContainerInitializer
}
}
}
public String toString()
{
List<String> interested = Collections.emptyList();
if (_interestedTypes != null)
{
interested = new ArrayList<>(_interestedTypes.length);
for (Class<?> c : _interestedTypes)
interested.add(c.getName());
}
return String.format("ContainerInitializer{%s,interested=%s,applicable=%s,annotated=%s}",_target.getClass().getName(),interested,_applicableTypeNames,_annotatedTypeNames);
}
public void resolveClasses(WebAppContext context, Map<String, Set<String>> classMap)
{
//We have already found the classes that directly have an annotation that was in the HandlesTypes
//annotation of the ServletContainerInitializer. For each of those classes, walk the inheritance
//hierarchy to find classes that extend or implement them.
Set<String> annotatedClassNames = getAnnotatedTypeNames();
if (annotatedClassNames != null && !annotatedClassNames.isEmpty())
{
for (String name : annotatedClassNames)
{
//add the class that has the annotation
addApplicableTypeName(name);
//find and add the classes that inherit the annotation
addInheritedTypes(classMap, (Set<String>)classMap.get(name));
}
}
//Now we need to look at the HandlesTypes classes that were not annotations. We need to
//find all classes that extend or implement them.
if (getInterestedTypes() != null)
{
for (Class<?> c : getInterestedTypes())
{
if (!c.isAnnotation())
{
//find and add the classes that implement or extend the class.
//but not including the class itself
addInheritedTypes(classMap, (Set<String>)classMap.get(c.getName()));
}
}
}
}
private void addInheritedTypes(Map<String, Set<String>> classMap,Set<String> names)
{
if (names == null || names.isEmpty())
return;
for (String s : names)
{
//add the name of the class
addApplicableTypeName(s);
//walk the hierarchy and find all types that extend or implement the class
addInheritedTypes(classMap, (Set<String>)classMap.get(s));
}
}
}

View File

@ -19,6 +19,8 @@
package org.eclipse.jetty.plus.annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -136,4 +138,51 @@ public class LifeCycleCallbackCollection
for (int i=0;i<callbacks.size();i++)
((LifeCycleCallback)callbacks.get(i)).callback(o);
}
/**
* Generate a read-only view of the post-construct callbacks
* @return
*/
public Map<String, List<LifeCycleCallback>> getPostConstructCallbackMap()
{
return Collections.unmodifiableMap(postConstructCallbacksMap);
}
/**
* Generate a read-only view of the pre-destroy callbacks
* @return
*/
public Map<String, List<LifeCycleCallback>> getPreDestroyCallbackMap()
{
return Collections.unmodifiableMap(preDestroyCallbacksMap);
}
/**
* Amalgamate all post-construct callbacks and return a read only list
* @return
*/
public Collection<LifeCycleCallback> getPostConstructCallbacks()
{
List<LifeCycleCallback> list = new ArrayList<LifeCycleCallback>();
for (String s:postConstructCallbacksMap.keySet())
{
list.addAll(postConstructCallbacksMap.get(s));
}
return Collections.unmodifiableCollection(list);
}
/**
* Amalgamate all pre-destroy callbacks and return a read only list
* @return
*/
public Collection<LifeCycleCallback> getPreDestroyCallbacks()
{
List<LifeCycleCallback> list = new ArrayList<LifeCycleCallback>();
for (String s:preDestroyCallbacksMap.keySet())
{
list.addAll(preDestroyCallbacksMap.get(s));
}
return Collections.unmodifiableCollection(list);
}
}

View File

@ -109,7 +109,7 @@ public class PlusConfiguration extends AbstractConfiguration
_key = new Integer(random.nextInt());
Context context = new InitialContext();
Context compCtx = (Context)context.lookup("java:comp");
compCtx.addToEnvironment("org.eclipse.jndi.lock", _key);
compCtx.addToEnvironment("org.eclipse.jetty.jndi.lock", _key);
}
finally
{
@ -129,7 +129,7 @@ public class PlusConfiguration extends AbstractConfiguration
{
Context context = new InitialContext();
Context compCtx = (Context)context.lookup("java:comp");
compCtx.addToEnvironment("org.eclipse.jndi.unlock", _key);
compCtx.addToEnvironment("org.eclipse.jetty.jndi.unlock", _key);
}
finally
{

View File

@ -163,7 +163,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
}
/* Called to indicated that the output is already closed and the state needs to be updated to match */
/* Called to indicated that the output is already closed (write with last==true performed) and the state needs to be updated to match */
void closed()
{
loop: while(true)
@ -894,19 +894,25 @@ public class HttpOutput extends ServletOutputStream implements Runnable
// all content written, but if we have not yet signal completion, we
// need to do so
if (_complete)
if (_complete && !_completed)
{
if (!_completed)
{
_completed=true;
write(BufferUtil.EMPTY_BUFFER, _complete, this);
return Action.SCHEDULED;
}
closed();
_completed=true;
write(BufferUtil.EMPTY_BUFFER, _complete, this);
return Action.SCHEDULED;
}
return Action.SUCCEEDED;
}
@Override
protected void completed()
{
super.completed();
if (_complete)
closed();
}
}

View File

@ -70,6 +70,8 @@ public class Response implements HttpServletResponse
private static final Logger LOG = Log.getLogger(Response.class);
private static final String __COOKIE_DELIM="\",;\\ \t";
private final static String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
private final static int __MIN_BUFFER_SIZE = 1;
// Cookie building buffer. Reduce garbage for cookie using applications
private static final ThreadLocal<StringBuilder> __cookieBuilder = new ThreadLocal<StringBuilder>()
@ -1197,6 +1199,8 @@ public class Response implements HttpServletResponse
{
if (isCommitted() || getContentCount() > 0)
throw new IllegalStateException("Committed or content written");
if (size <= 0)
size = __MIN_BUFFER_SIZE;
_out.setBufferSize(size);
}

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.server;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
@ -345,6 +346,8 @@ public class Server extends HandlerWrapper implements Attributes
dumpStdErr();
mex.ifExceptionThrow();
LOG.info(String.format("Started @%dms",ManagementFactory.getRuntimeMXBean().getUptime()));
}
@Override

View File

@ -1604,6 +1604,19 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
encoding = _localeEncodingMap.get(locale.getLanguage());
return encoding;
}
/* ------------------------------------------------------------ */
/**
* Get all of the locale encodings
*
* @return a map of all the locale encodings: key is name of the locale and value is the char encoding
*/
public Map<String,String> getLocaleEncodings()
{
if (_localeEncodingMap == null)
return null;
return Collections.unmodifiableMap(_localeEncodingMap);
}
/* ------------------------------------------------------------ */
/*

View File

@ -880,107 +880,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
/* ------------------------------------------------------------ */
private SessionCookieConfig _cookieConfig =
new SessionCookieConfig()
{
@Override
public String getComment()
{
return _sessionComment;
}
@Override
public String getDomain()
{
return _sessionDomain;
}
@Override
public int getMaxAge()
{
return _maxCookieAge;
}
@Override
public String getName()
{
return _sessionCookie;
}
@Override
public String getPath()
{
return _sessionPath;
}
@Override
public boolean isHttpOnly()
{
return _httpOnly;
}
@Override
public boolean isSecure()
{
return _secureCookies;
}
@Override
public void setComment(String comment)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionComment = comment;
}
@Override
public void setDomain(String domain)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionDomain=domain;
}
@Override
public void setHttpOnly(boolean httpOnly)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_httpOnly=httpOnly;
}
@Override
public void setMaxAge(int maxAge)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_maxCookieAge=maxAge;
}
@Override
public void setName(String name)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionCookie=name;
}
@Override
public void setPath(String path)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionPath=path;
}
@Override
public void setSecure(boolean secure)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_secureCookies=secure;
}
};
new CookieConfig();
/* ------------------------------------------------------------ */
@ -1055,6 +955,112 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
}
/**
* CookieConfig
*
* Implementation of the javax.servlet.SessionCookieConfig.
*/
public final class CookieConfig implements SessionCookieConfig
{
@Override
public String getComment()
{
return _sessionComment;
}
@Override
public String getDomain()
{
return _sessionDomain;
}
@Override
public int getMaxAge()
{
return _maxCookieAge;
}
@Override
public String getName()
{
return _sessionCookie;
}
@Override
public String getPath()
{
return _sessionPath;
}
@Override
public boolean isHttpOnly()
{
return _httpOnly;
}
@Override
public boolean isSecure()
{
return _secureCookies;
}
@Override
public void setComment(String comment)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionComment = comment;
}
@Override
public void setDomain(String domain)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionDomain=domain;
}
@Override
public void setHttpOnly(boolean httpOnly)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_httpOnly=httpOnly;
}
@Override
public void setMaxAge(int maxAge)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_maxCookieAge=maxAge;
}
@Override
public void setName(String name)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionCookie=name;
}
@Override
public void setPath(String path)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionPath=path;
}
@Override
public void setSecure(boolean secure)
{
if (_context != null && _context.getContextHandler().isAvailable())
throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_secureCookies=secure;
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */

View File

@ -1411,7 +1411,7 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
String[] ids = expiredIds.toArray(new String[expiredIds.size()]);
try (Connection con = getConnection())
{
con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
con.setAutoCommit(false);
int start = 0;

View File

@ -28,6 +28,7 @@ import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
@ -36,7 +37,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Assert;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.resource.Resource;
@ -546,6 +546,23 @@ public class HttpOutputTest
assertThat(response,Matchers.not(containsString("Content-Length")));
assertThat(response,containsString("400\tThis is a big file"));
}
@Test
public void testAsyncWriteBufferLargeHEAD() throws Exception
{
final Resource big = Resource.newClassPathResource("simple/big.txt");
_handler._writeLengthIfKnown=false;
_handler._content=BufferUtil.toBuffer(big,false);
_handler._byteBuffer=BufferUtil.allocate(8192);
_handler._async=true;
int start=_handler._owp.get();
String response=_connector.getResponses("HEAD / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(_handler._owp.get()-start,Matchers.greaterThan(0));
assertThat(response,containsString("HTTP/1.1 200 OK"));
assertThat(response,Matchers.not(containsString("Content-Length")));
assertThat(response,Matchers.not(containsString("400\tThis is a big file")));
}
@Test
public void testAsyncWriteSimpleKnown() throws Exception
@ -562,9 +579,28 @@ public class HttpOutputTest
assertThat(response,containsString("Content-Length: 11"));
assertThat(response,containsString("simple text"));
}
@Test
public void testAsyncWriteSimpleKnownHEAD() throws Exception
{
final Resource big = Resource.newClassPathResource("simple/simple.txt");
_handler._async=true;
_handler._writeLengthIfKnown=true;
_handler._content=BufferUtil.toBuffer(big,false);
_handler._arrayBuffer=new byte[4000];
int start=_handler._owp.get();
String response=_connector.getResponses("HEAD / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(_handler._owp.get()-start,Matchers.equalTo(1));
assertThat(response,containsString("HTTP/1.1 200 OK"));
assertThat(response,containsString("Content-Length: 11"));
assertThat(response,Matchers.not(containsString("simple text")));
}
static class ContentHandler extends AbstractHandler
{
AtomicInteger _owp = new AtomicInteger();
boolean _writeLengthIfKnown=true;
boolean _async;
ByteBuffer _byteBuffer;
@ -609,6 +645,8 @@ public class HttpOutputTest
@Override
public void onWritePossible() throws IOException
{
_owp.incrementAndGet();
while (out.isReady())
{
Assert.assertTrue(out.isReady());
@ -666,6 +704,8 @@ public class HttpOutputTest
@Override
public void onWritePossible() throws IOException
{
_owp.incrementAndGet();
while (out.isReady())
{
Assert.assertTrue(out.isReady());
@ -693,7 +733,6 @@ public class HttpOutputTest
return;
}
while(BufferUtil.hasContent(_content))
{
BufferUtil.clearToFill(_byteBuffer);
@ -714,10 +753,7 @@ public class HttpOutputTest
_content=null;
return;
}
}
}
}

View File

@ -129,6 +129,18 @@ public class FilterMapping implements Dumpable
return (_dispatches&type)!=0;
}
/* ------------------------------------------------------------ */
public boolean appliesTo(DispatcherType t)
{
return appliesTo(dispatch(t));
}
/* ------------------------------------------------------------ */
public boolean isDefaultDispatches()
{
return _dispatches==0;
}
/* ------------------------------------------------------------ */
/**
* @return Returns the filterName.

View File

@ -242,12 +242,6 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
return (link==null)?name:link;
}
/* ------------------------------------------------------------ */
public Map<String, String> getRoleMap()
{
return _roleMap == null? NO_MAPPED_ROLES : _roleMap;
}
/* ------------------------------------------------------------ */
/**
* @return Returns the forcedPath.

View File

@ -36,6 +36,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.util.log.Log;
@ -243,6 +244,25 @@ public class GzipHandler extends HandlerWrapper
{
_minGzipSize = minGzipSize;
}
/* ------------------------------------------------------------ */
@Override
protected void doStart() throws Exception
{
if (_mimeTypes.size()==0)
{
for (String type:MimeTypes.getKnownMimeTypes())
{
if (type.startsWith("image/")||
type.startsWith("audio/")||
type.startsWith("video/"))
_mimeTypes.add(type);
_mimeTypes.add("application/compress");
_mimeTypes.add("application/zip");
_mimeTypes.add("application/gzip");
}
}
}
/* ------------------------------------------------------------ */
/**

View File

@ -188,6 +188,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
{
final String paramName = "a";
final String paramValue = "\u20AC";
final String encodedParamValue = URLEncoder.encode(paramValue, "UTF-8");
start(new AbstractHandler()
{
@Override
@ -204,7 +205,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
}
});
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort() + "/path?" + paramName + "=" + paramValue);
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort() + "/path?" + paramName + "=" + encodedParamValue);
ContentResponse response = client.newRequest(uri)
.method(HttpMethod.PUT)
.timeout(5, TimeUnit.SECONDS)

View File

@ -339,23 +339,20 @@ public class BufferUtil
* @param from Buffer to take bytes from in flush mode
* @param to Buffer to put bytes to in flush mode. The buffer is flipToFill before the put and flipToFlush after.
* @return number of bytes moved
* @deprecated use {@link #append(ByteBuffer, ByteBuffer)}
*/
public static int flipPutFlip(ByteBuffer from, ByteBuffer to)
{
int pos = flipToFill(to);
try
{
return put(from, to);
}
finally
{
flipToFlush(to, pos);
}
return append(to,from);
}
/* ------------------------------------------------------------ */
/** Append bytes to a buffer.
*
* @param to Buffer is flush mode
* @param b bytes to append
* @param off offset into byte
* @param len length to append
* @throws BufferOverflowException
*/
public static void append(ByteBuffer to, byte[] b, int off, int len) throws BufferOverflowException
{
@ -372,6 +369,8 @@ public class BufferUtil
/* ------------------------------------------------------------ */
/** Appends a byte to a buffer
* @param to Buffer is flush mode
* @param b byte to append
*/
public static void append(ByteBuffer to, byte b)
{
@ -386,9 +385,31 @@ public class BufferUtil
}
}
/* ------------------------------------------------------------ */
/** Appends a byte to a buffer
* @param to Buffer is flush mode
* @param b bytes to append
*/
public static int append(ByteBuffer to, ByteBuffer b)
{
int pos = flipToFill(to);
try
{
return put(b, to);
}
finally
{
flipToFlush(to, pos);
}
}
/* ------------------------------------------------------------ */
/**
* Like append, but does not throw {@link BufferOverflowException}
* @param to Buffer is flush mode
* @param b bytes to fill
* @param off offset into byte
* @param len length to fill
*/
public static int fill(ByteBuffer to, byte[] b, int off, int len)
{

View File

@ -719,4 +719,17 @@ public class StringUtil
return str.substring(0,maxSize);
}
public static String[] arrayFromString(String s)
{
if (s==null)
return new String[]{};
if (!s.startsWith("[") || !s.endsWith("]"))
throw new IllegalArgumentException();
if (s.length()==2)
return new String[]{};
return s.substring(1,s.length()-1).split(" *, *");
}
}

View File

@ -616,4 +616,32 @@ public class TypeUtil
}
throw new NoSuchMethodException("<init>");
}
/* ------------------------------------------------------------ */
/**
* @param o Object to test for true
* @return True if passed object is not null and is either a Boolean with value true or evaluates to a string that evaluates to true.
*/
public static boolean isTrue(Object o)
{
if (o==null)
return false;
if (o instanceof Boolean)
return ((Boolean)o).booleanValue();
return Boolean.parseBoolean(o.toString());
}
/* ------------------------------------------------------------ */
/**
* @param o Object to test for false
* @return True if passed object is not null and is either a Boolean with value false or evaluates to a string that evaluates to false.
*/
public static boolean isFalse(Object o)
{
if (o==null)
return false;
if (o instanceof Boolean)
return !((Boolean)o).booleanValue();
return "false".equalsIgnoreCase(o.toString());
}
}

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.util.component;
import java.lang.management.ManagementFactory;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
@ -27,14 +28,12 @@ import org.eclipse.jetty.util.log.Logger;
/**
* Basic implementation of the life cycle interface for components.
*
*
*/
@ManagedObject("Abstract Implementation of LifeCycle")
public abstract class AbstractLifeCycle implements LifeCycle
{
private static final Logger LOG = Log.getLogger(AbstractLifeCycle.class);
public static final String STOPPED="STOPPED";
public static final String FAILED="FAILED";
public static final String STARTING="STARTING";
@ -174,7 +173,9 @@ public abstract class AbstractLifeCycle implements LifeCycle
private void setStarted()
{
_state = __STARTED;
LOG.debug(STARTED+" {}",this);
if (LOG.isDebugEnabled())
LOG.debug(STARTED+" @{}ms {}",ManagementFactory.getRuntimeMXBean().getUptime(),this);
for (Listener listener : _listeners)
listener.lifeCycleStarted(this);
}

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.util.log;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.AccessController;
@ -178,6 +179,9 @@ public class Log
// Unable to load specified Logger implementation, default to standard logging.
initStandardLogging(e);
}
if (LOG!=null)
LOG.info(String.format("Logging initialized @%dms",ManagementFactory.getRuntimeMXBean().getUptime()));
return LOG != null;
}

View File

@ -120,6 +120,12 @@ public class Constraint implements Cloneable, Serializable
_name = name;
}
/* ------------------------------------------------------------ */
public String getName()
{
return _name;
}
/* ------------------------------------------------------------ */
public void setRoles(String[] roles)
{

View File

@ -141,14 +141,14 @@ public class BufferUtilTest
ByteBuffer from=BufferUtil.toBuffer("12345");
BufferUtil.clear(to);
assertEquals(5,BufferUtil.flipPutFlip(from,to));
assertEquals(5,BufferUtil.append(to,from));
assertTrue(BufferUtil.isEmpty(from));
assertEquals("12345",BufferUtil.toString(to));
from=BufferUtil.toBuffer("XX67890ZZ");
from.position(2);
assertEquals(5,BufferUtil.flipPutFlip(from,to));
assertEquals(5,BufferUtil.append(to,from));
assertEquals(2,from.remaining());
assertEquals("1234567890",BufferUtil.toString(to));
}
@ -183,14 +183,14 @@ public class BufferUtilTest
ByteBuffer from=BufferUtil.toBuffer("12345");
BufferUtil.clear(to);
assertEquals(5,BufferUtil.flipPutFlip(from,to));
assertEquals(5,BufferUtil.append(to,from));
assertTrue(BufferUtil.isEmpty(from));
assertEquals("12345",BufferUtil.toString(to));
from=BufferUtil.toBuffer("XX67890ZZ");
from.position(2);
assertEquals(5,BufferUtil.flipPutFlip(from,to));
assertEquals(5,BufferUtil.append(to,from));
assertEquals(2,from.remaining());
assertEquals("1234567890",BufferUtil.toString(to));
}

View File

@ -18,8 +18,8 @@
package org.eclipse.jetty.util;
import junit.framework.Assert;
import org.junit.Assert;
import org.junit.Test;
public class TypeUtilTest
@ -90,4 +90,33 @@ public class TypeUtilTest
Assert.assertEquals("123456789ABCDEF0",b.toString());
}
@Test
public void testIsTrue() throws Exception
{
Assert.assertTrue(TypeUtil.isTrue(Boolean.TRUE));
Assert.assertTrue(TypeUtil.isTrue(true));
Assert.assertTrue(TypeUtil.isTrue("true"));
Assert.assertTrue(TypeUtil.isTrue(new Object(){@Override public String toString(){return "true";}}));
Assert.assertFalse(TypeUtil.isTrue(Boolean.FALSE));
Assert.assertFalse(TypeUtil.isTrue(false));
Assert.assertFalse(TypeUtil.isTrue("false"));
Assert.assertFalse(TypeUtil.isTrue("blargle"));
Assert.assertFalse(TypeUtil.isTrue(new Object(){@Override public String toString(){return "false";}}));
}
@Test
public void testIsFalse() throws Exception
{
Assert.assertTrue(TypeUtil.isFalse(Boolean.FALSE));
Assert.assertTrue(TypeUtil.isFalse(false));
Assert.assertTrue(TypeUtil.isFalse("false"));
Assert.assertTrue(TypeUtil.isFalse(new Object(){@Override public String toString(){return "false";}}));
Assert.assertFalse(TypeUtil.isFalse(Boolean.TRUE));
Assert.assertFalse(TypeUtil.isFalse(true));
Assert.assertFalse(TypeUtil.isFalse("true"));
Assert.assertFalse(TypeUtil.isFalse("blargle"));
Assert.assertFalse(TypeUtil.isFalse(new Object(){@Override public String toString(){return "true";}}));
}
}

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.webapp;
import java.net.URL;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlParser;
@ -43,7 +44,8 @@ public abstract class Descriptor
protected void redirect(XmlParser parser, String resource, URL source)
{
if (source != null) parser.redirectEntity(resource, source);
if (source != null)
parser.redirectEntity(resource, source);
}

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.webapp;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -69,14 +70,27 @@ public class MetaData
public static class OriginInfo
{
protected String name;
protected Origin origin;
protected Descriptor descriptor;
private final String name;
private final Origin origin;
private final Descriptor descriptor;
private final Annotation annotation;
private final Class<?> annotated;
public OriginInfo (String n, Annotation a,Class<?> ac)
{
name=n;
origin=Origin.Annotation;
descriptor=null;
annotation=a;
annotated=ac;
}
public OriginInfo (String n, Descriptor d)
{
name = n;
descriptor = d;
annotation=null;
annotated=null;
if (d == null)
throw new IllegalArgumentException("No descriptor");
if (d instanceof FragmentDescriptor)
@ -89,16 +103,13 @@ public class MetaData
origin = Origin.WebXml;
}
public OriginInfo (String n)
public OriginInfo(String n)
{
name = n;
origin = Origin.Annotation;
}
public OriginInfo(String n, Origin o)
{
name = n;
origin = o;
origin = Origin.API;
annotation=null;
descriptor=null;
annotated=null;
}
public String getName()
@ -115,6 +126,15 @@ public class MetaData
{
return descriptor;
}
public String toString()
{
if (descriptor!=null)
return descriptor.toString();
if (annotation!=null)
return "@"+annotation.annotationType().getSimpleName()+" on "+annotated.getName();
return origin.toString();
}
}
public MetaData ()
@ -172,8 +192,6 @@ public class MetaData
_webXmlRoot.parse();
_metaDataComplete=_webXmlRoot.getMetaDataComplete() == MetaDataComplete.True;
if (_webXmlRoot.isOrdered())
{
if (_ordering == null)
@ -526,6 +544,14 @@ public class MetaData
return x.getOriginType();
}
public OriginInfo getOriginInfo (String name)
{
OriginInfo x = _origins.get(name);
if (x == null)
return null;
return x;
}
public Descriptor getOriginDescriptor (String name)
{
@ -541,21 +567,21 @@ public class MetaData
_origins.put(name, x);
}
public void setOrigin (String name)
public void setOrigin (String name, Annotation annotation, Class<?> annotated)
{
if (name == null)
return;
OriginInfo x = new OriginInfo (name, Origin.Annotation);
OriginInfo x = new OriginInfo (name, annotation, annotated);
_origins.put(name, x);
}
public void setOrigin(String name, Origin origin)
public void setOriginAPI(String name)
{
if (name == null)
return;
OriginInfo x = new OriginInfo (name, origin);
OriginInfo x = new OriginInfo (name);
_origins.put(name, x);
}
@ -604,4 +630,9 @@ public class MetaData
{
this.allowDuplicateFragmentNames = allowDuplicateFragmentNames;
}
public Map<String,OriginInfo> getOrigins()
{
return Collections.unmodifiableMap(_origins);
}
}

View File

@ -54,6 +54,7 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.xml.XmlParser;
import org.eclipse.jetty.xml.XmlParser.Node;
/**
* StandardDescriptorProcessor
@ -157,6 +158,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
}
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
if (LOG.isDebugEnabled())
LOG.debug("ContextParam: " + name + "=" + value);
@ -244,18 +247,17 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Mismatching init-param "+pname+"="+pvalue+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
String servlet_class = node.getString("servlet-class", false, true);
// Handle JSP
String jspServletClass=null;
//Handle the default jsp servlet instance
if (id != null && id.equals("jsp"))
{
jspServletClass = servlet_class;
try
{
Loader.loadClass(this.getClass(), servlet_class);
@ -272,7 +274,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
catch (ClassNotFoundException e)
{
LOG.info("NO JSP Support for {}, did not find {}", context.getContextPath(), servlet_class);
jspServletClass = servlet_class = "org.eclipse.jetty.servlet.NoJspServlet";
servlet_class = "org.eclipse.jetty.servlet.NoJspServlet";
}
}
@ -309,6 +311,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting servlet-class "+servlet_class+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
@ -374,10 +378,12 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting load-on-startup value in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
Iterator sRefsIter = node.iterator("security-role-ref");
Iterator<Node> sRefsIter = node.iterator("security-role-ref");
while (sRefsIter.hasNext())
{
XmlParser.Node securityRef = (XmlParser.Node) sRefsIter.next();
@ -413,6 +419,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting role-link for role-name "+roleName+" for servlet "+servlet_name+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
else
@ -457,6 +465,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting run-as role "+roleName+" for servlet "+servlet_name+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
}
@ -493,6 +503,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting async-supported="+async+" for servlet "+servlet_name+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
@ -528,6 +540,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting value of servlet enabled for servlet "+servlet_name+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
@ -585,6 +599,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting multipart-config location for servlet "+servlet_name+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
}
@ -632,6 +648,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
addServletMapping(servlet_name, node, context, descriptor);
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
@ -653,7 +671,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
//Servlet Spec 3.0
// <tracking-mode>
// this is additive across web-fragments
Iterator iter = node.iterator("tracking-mode");
Iterator<Node> iter = node.iterator("tracking-mode");
if (iter.hasNext())
{
Set<SessionTrackingMode> modes = null;
@ -679,7 +697,9 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
modes = new HashSet<SessionTrackingMode>(context.getSessionHandler().getSessionManager().getEffectiveSessionTrackingModes());
context.getMetaData().setOrigin("session.tracking-mode", descriptor);
break;
}
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
while (iter.hasNext())
@ -729,6 +749,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting cookie-config name "+name+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
@ -764,6 +786,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting cookie-config domain "+domain+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
@ -799,6 +823,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting cookie-config path "+path+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
@ -834,6 +860,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting cookie-config comment "+comment+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
@ -870,6 +898,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting cookie-config http-only "+httpOnly+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
@ -906,6 +936,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting cookie-config secure "+secure+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
@ -942,6 +974,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting cookie-config max-age "+maxAge+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
}
@ -990,6 +1024,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting mime-type "+mimeType+" for extension "+extension+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
}
@ -1038,6 +1074,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
addWelcomeFiles(context,node);
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
@ -1085,6 +1123,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting loacle-encoding mapping for locale "+locale+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
}
@ -1150,6 +1190,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting error-code or exception-type "+error+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
@ -1422,18 +1464,18 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
//remember origin so we can process ServletRegistration.Dynamic.setServletSecurityElement() correctly
context.getMetaData().setOrigin("constraint.url."+url, descriptor);
Iterator<XmlParser.Node> iter3 = collection.iterator("http-method");
Iterator<XmlParser.Node> iter4 = collection.iterator("http-method-omission");
Iterator<XmlParser.Node> methods = collection.iterator("http-method");
Iterator<XmlParser.Node> ommissions = collection.iterator("http-method-omission");
if (iter3.hasNext())
if (methods.hasNext())
{
if (iter4.hasNext())
if (ommissions.hasNext())
throw new IllegalStateException ("web-resource-collection cannot contain both http-method and http-method-omission");
//configure all the http-method elements for each url
while (iter3.hasNext())
while (methods.hasNext())
{
String method = ((XmlParser.Node) iter3.next()).toString(false, true);
String method = ((XmlParser.Node) methods.next()).toString(false, true);
ConstraintMapping mapping = new ConstraintMapping();
mapping.setMethod(method);
mapping.setPathSpec(url);
@ -1441,12 +1483,13 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
}
}
else if (iter4.hasNext())
else if (ommissions.hasNext())
{
//configure all the http-method-omission elements for each url
while (iter4.hasNext())
// TODO use the array
while (ommissions.hasNext())
{
String method = ((XmlParser.Node)iter4.next()).toString(false, true);
String method = ((XmlParser.Node)ommissions.next()).toString(false, true);
ConstraintMapping mapping = new ConstraintMapping();
mapping.setMethodOmissions(new String[]{method});
mapping.setPathSpec(url);
@ -1514,6 +1557,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting auth-method value in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
//handle realm-name merge
@ -1547,9 +1592,11 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting realm-name value in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
if (Constraint.__FORM_AUTH.equals(context.getSecurityHandler().getAuthMethod()))
if (Constraint.__FORM_AUTH.equalsIgnoreCase(context.getSecurityHandler().getAuthMethod()))
{
XmlParser.Node formConfig = node.get("form-login-config");
if (formConfig != null)
@ -1592,6 +1639,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting form-login-page value in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
//handle form-error-page
@ -1623,6 +1672,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting form-error-page value in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
else
@ -1696,6 +1747,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting filter-class for filter "+name+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
@ -1735,6 +1788,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Mismatching init-param "+pname+"="+pvalue+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
@ -1772,6 +1827,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
throw new IllegalStateException("Conflicting async-supported="+async+" for filter "+name+" in "+descriptor.getResource());
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}
}
@ -1814,6 +1871,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
addFilterMapping(filter_name, node, context, descriptor);
break;
}
default:
LOG.warn(new Throwable()); // TODO throw ISE?
}
}

View File

@ -1386,7 +1386,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
for (ConstraintMapping m:mappings)
((ConstraintAware)getSecurityHandler()).addConstraintMapping(m);
((ConstraintAware)getSecurityHandler()).checkPathsWithUncoveredHttpMethods();
getMetaData().setOrigin("constraint.url."+pathSpec, Origin.API);
getMetaData().setOriginAPI("constraint.url."+pathSpec);
break;
}
case WebXml:

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlParser;
import org.xml.sax.InputSource;
@ -63,108 +64,130 @@ public class WebDescriptor extends Descriptor
_parser = _parserSingleton;
}
public XmlParser newParser()
throws ClassNotFoundException
{
XmlParser xmlParser=new XmlParser();
//set up cache of DTDs and schemas locally
URL dtd22=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_2.dtd");
URL dtd23=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_3.dtd");
URL j2ee14xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_1_4.xsd");
URL javaee5=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_5.xsd");
URL javaee6=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_6.xsd");
URL javaee7=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_7.xsd");
URL webapp24xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_4.xsd");
URL webapp25xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_5.xsd");
URL webapp30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_3_0.xsd");
URL webcommon30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-common_3_0.xsd");
URL webfragment30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-fragment_3_0.xsd");
URL webapp31xsd=Loader.getResource(Servlet.class, "javax/servlet/resources/web-app_3_1.xsd");
URL webcommon31xsd=Loader.getResource(Servlet.class, "javax/servlet/resources/web-common_3_1.xsd");
URL webfragment31xsd=Loader.getResource(Servlet.class, "javax/servlet/resources/web-fragment_3_1.xsd");
URL schemadtd=Loader.getResource(Servlet.class,"javax/servlet/resources/XMLSchema.dtd");
URL xmlxsd=Loader.getResource(Servlet.class,"javax/servlet/resources/xml.xsd");
URL webservice11xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_web_services_client_1_1.xsd");
URL webservice12xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_web_services_client_1_2.xsd");
URL webservice13xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_web_services_client_1_3.xsd");
URL webservice14xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_web_services_client_1_4.xsd");
URL datatypesdtd=Loader.getResource(Servlet.class,"javax/servlet/resources/datatypes.dtd");
URL jsp20xsd = null;
URL jsp21xsd = null;
URL jsp22xsd = null;
URL jsp23xsd = null;
try
XmlParser xmlParser=new XmlParser()
{
//try both javax/servlet/resources and javax/servlet/jsp/resources to load
jsp20xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_0.xsd");
jsp21xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_1.xsd");
jsp22xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_2.xsd");
jsp23xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_3.xsd");
}
catch (Exception e)
{
LOG.ignore(e);
}
finally
{
if (jsp20xsd == null) jsp20xsd = Loader.getResource(Servlet.class, "javax/servlet/jsp/resources/jsp_2_0.xsd");
if (jsp21xsd == null) jsp21xsd = Loader.getResource(Servlet.class, "javax/servlet/jsp/resources/jsp_2_1.xsd");
if (jsp22xsd == null) jsp22xsd = Loader.getResource(Servlet.class, "javax/servlet/jsp/resources/jsp_2_2.xsd");
if (jsp23xsd == null) jsp23xsd = Loader.getResource(Servlet.class, "javax/servlet/jsp/resources/jsp_2_3.xsd");
}
boolean mapped=false;
@Override
protected InputSource resolveEntity(String pid, String sid)
{
if (!mapped)
{
mapResources();
mapped=true;
}
InputSource is = super.resolveEntity(pid,sid);
return is;
}
void mapResources()
{
//set up cache of DTDs and schemas locally
URL dtd22=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_2.dtd");
URL dtd23=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_3.dtd");
URL j2ee14xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_1_4.xsd");
URL javaee5=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_5.xsd");
URL javaee6=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_6.xsd");
URL javaee7=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_7.xsd");
redirect(xmlParser,"web-app_2_2.dtd",dtd22);
redirect(xmlParser,"-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN",dtd22);
redirect(xmlParser,"web.dtd",dtd23);
redirect(xmlParser,"web-app_2_3.dtd",dtd23);
redirect(xmlParser,"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN",dtd23);
redirect(xmlParser,"XMLSchema.dtd",schemadtd);
redirect(xmlParser,"http://www.w3.org/2001/XMLSchema.dtd",schemadtd);
redirect(xmlParser,"-//W3C//DTD XMLSCHEMA 200102//EN",schemadtd);
redirect(xmlParser,"jsp_2_0.xsd",jsp20xsd);
redirect(xmlParser,"http://java.sun.com/xml/ns/j2ee/jsp_2_0.xsd",jsp20xsd);
redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/jsp_2_1.xsd",jsp21xsd);
redirect(xmlParser,"jsp_2_2.xsd",jsp22xsd);
redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/jsp_2_2.xsd",jsp22xsd);
redirect(xmlParser,"jsp_2_3.xsd",jsp23xsd);
redirect(xmlParser,"http://xmlns.jcp.org/xml/ns/javaee/jsp_2_3.xsd",jsp23xsd);
redirect(xmlParser,"j2ee_1_4.xsd",j2ee14xsd);
redirect(xmlParser,"http://java.sun.com/xml/ns/j2ee/j2ee_1_4.xsd",j2ee14xsd);
redirect(xmlParser, "http://java.sun.com/xml/ns/javaee/javaee_5.xsd",javaee5);
redirect(xmlParser, "http://java.sun.com/xml/ns/javaee/javaee_6.xsd",javaee6);
redirect(xmlParser, "http://xmlns.jcp.org/xml/ns/javaee/javaee_7.xsd",javaee7);
redirect(xmlParser,"web-app_2_4.xsd",webapp24xsd);
redirect(xmlParser,"http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd",webapp24xsd);
redirect(xmlParser,"web-app_2_5.xsd",webapp25xsd);
redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd",webapp25xsd);
redirect(xmlParser,"web-app_3_0.xsd",webapp30xsd);
redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd",webapp30xsd);
redirect(xmlParser,"web-common_3_0.xsd",webcommon30xsd);
redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-common_3_0.xsd",webcommon30xsd);
redirect(xmlParser,"web-fragment_3_0.xsd",webfragment30xsd);
redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd",webfragment30xsd);
redirect(xmlParser,"web-app_3_1.xsd",webapp31xsd);
redirect(xmlParser,"http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd",webapp31xsd);
redirect(xmlParser,"web-common_3_1.xsd",webcommon30xsd);
redirect(xmlParser,"http://xmlns.jcp.org/xml/ns/javaee/web-common_3_1.xsd",webcommon31xsd);
redirect(xmlParser,"web-fragment_3_1.xsd",webfragment30xsd);
redirect(xmlParser,"http://xmlns.jcp.org/xml/ns/javaee/web-fragment_3_1.xsd",webfragment31xsd);
redirect(xmlParser,"xml.xsd",xmlxsd);
redirect(xmlParser,"http://www.w3.org/2001/xml.xsd",xmlxsd);
redirect(xmlParser,"datatypes.dtd",datatypesdtd);
redirect(xmlParser,"http://www.w3.org/2001/datatypes.dtd",datatypesdtd);
redirect(xmlParser,"j2ee_web_services_client_1_1.xsd",webservice11xsd);
redirect(xmlParser,"http://www.ibm.com/webservices/xsd/j2ee_web_services_client_1_1.xsd",webservice11xsd);
redirect(xmlParser,"javaee_web_services_client_1_2.xsd",webservice12xsd);
redirect(xmlParser,"http://www.ibm.com/webservices/xsd/javaee_web_services_client_1_2.xsd",webservice12xsd);
redirect(xmlParser,"javaee_web_services_client_1_3.xsd",webservice13xsd);
redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/javaee_web_services_client_1_3.xsd",webservice13xsd);
redirect(xmlParser,"javaee_web_services_client_1_4.xsd",webservice14xsd);
redirect(xmlParser,"http://xmlns.jcp.org/xml/ns/javaee/javaee_web_services_client_1_4.xsd",webservice14xsd);
URL webapp24xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_4.xsd");
URL webapp25xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_5.xsd");
URL webapp30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_3_0.xsd");
URL webapp31xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_3_1.xsd");
URL webcommon30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-common_3_0.xsd");
URL webcommon31xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-common_3_1.xsd");
URL webfragment30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-fragment_3_0.xsd");
URL webfragment31xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-fragment_3_1.xsd");
URL schemadtd=Loader.getResource(Servlet.class,"javax/servlet/resources/XMLSchema.dtd");
URL xmlxsd=Loader.getResource(Servlet.class,"javax/servlet/resources/xml.xsd");
URL webservice11xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_web_services_client_1_1.xsd");
URL webservice12xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_web_services_client_1_2.xsd");
URL webservice13xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_web_services_client_1_3.xsd");
URL webservice14xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_web_services_client_1_4.xsd");
URL datatypesdtd=Loader.getResource(Servlet.class,"javax/servlet/resources/datatypes.dtd");
URL jsp20xsd = null;
URL jsp21xsd = null;
URL jsp22xsd = null;
URL jsp23xsd = null;
try
{
//try both javax/servlet/resources and javax/servlet/jsp/resources to load
jsp20xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_0.xsd");
jsp21xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_1.xsd");
jsp22xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_2.xsd");
jsp23xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_3.xsd");
}
catch (Exception e)
{
LOG.ignore(e);
}
finally
{
if (jsp20xsd == null) jsp20xsd = Loader.getResource(Servlet.class, "javax/servlet/jsp/resources/jsp_2_0.xsd");
if (jsp21xsd == null) jsp21xsd = Loader.getResource(Servlet.class, "javax/servlet/jsp/resources/jsp_2_1.xsd");
if (jsp22xsd == null) jsp22xsd = Loader.getResource(Servlet.class, "javax/servlet/jsp/resources/jsp_2_2.xsd");
if (jsp23xsd == null) jsp23xsd = Loader.getResource(Servlet.class, "javax/servlet/jsp/resources/jsp_2_3.xsd");
}
redirect(this,"web-app_2_2.dtd",dtd22);
redirect(this,"-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN",dtd22);
redirect(this,"web.dtd",dtd23);
redirect(this,"web-app_2_3.dtd",dtd23);
redirect(this,"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN",dtd23);
redirect(this,"XMLSchema.dtd",schemadtd);
redirect(this,"http://www.w3.org/2001/XMLSchema.dtd",schemadtd);
redirect(this,"-//W3C//DTD XMLSCHEMA 200102//EN",schemadtd);
redirect(this,"jsp_2_0.xsd",jsp20xsd);
redirect(this,"http://java.sun.com/xml/ns/j2ee/jsp_2_0.xsd",jsp20xsd);
redirect(this,"http://java.sun.com/xml/ns/javaee/jsp_2_1.xsd",jsp21xsd);
redirect(this,"jsp_2_2.xsd",jsp22xsd);
redirect(this,"http://java.sun.com/xml/ns/javaee/jsp_2_2.xsd",jsp22xsd);
redirect(this,"jsp_2_3.xsd",jsp23xsd);
redirect(this,"http://xmlns.jcp.org/xml/ns/javaee/jsp_2_3.xsd",jsp23xsd);
redirect(this,"j2ee_1_4.xsd",j2ee14xsd);
redirect(this,"http://java.sun.com/xml/ns/j2ee/j2ee_1_4.xsd",j2ee14xsd);
redirect(this, "http://java.sun.com/xml/ns/javaee/javaee_5.xsd",javaee5);
redirect(this, "http://java.sun.com/xml/ns/javaee/javaee_6.xsd",javaee6);
redirect(this, "http://xmlns.jcp.org/xml/ns/javaee/javaee_7.xsd",javaee7);
redirect(this,"web-app_2_4.xsd",webapp24xsd);
redirect(this,"http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd",webapp24xsd);
redirect(this,"web-app_2_5.xsd",webapp25xsd);
redirect(this,"http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd",webapp25xsd);
redirect(this,"web-app_3_0.xsd",webapp30xsd);
redirect(this,"http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd",webapp30xsd);
redirect(this,"web-common_3_0.xsd",webcommon30xsd);
redirect(this,"http://java.sun.com/xml/ns/javaee/web-common_3_0.xsd",webcommon30xsd);
redirect(this,"web-fragment_3_0.xsd",webfragment30xsd);
redirect(this,"http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd",webfragment30xsd);
redirect(this,"web-app_3_1.xsd",webapp31xsd);
redirect(this,"http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd",webapp31xsd);
redirect(this,"web-common_3_1.xsd",webcommon30xsd);
redirect(this,"http://xmlns.jcp.org/xml/ns/javaee/web-common_3_1.xsd",webcommon31xsd);
redirect(this,"web-fragment_3_1.xsd",webfragment30xsd);
redirect(this,"http://xmlns.jcp.org/xml/ns/javaee/web-fragment_3_1.xsd",webfragment31xsd);
redirect(this,"xml.xsd",xmlxsd);
redirect(this,"http://www.w3.org/2001/xml.xsd",xmlxsd);
redirect(this,"datatypes.dtd",datatypesdtd);
redirect(this,"http://www.w3.org/2001/datatypes.dtd",datatypesdtd);
redirect(this,"j2ee_web_services_client_1_1.xsd",webservice11xsd);
redirect(this,"http://www.ibm.com/webservices/xsd/j2ee_web_services_client_1_1.xsd",webservice11xsd);
redirect(this,"javaee_web_services_client_1_2.xsd",webservice12xsd);
redirect(this,"http://www.ibm.com/webservices/xsd/javaee_web_services_client_1_2.xsd",webservice12xsd);
redirect(this,"javaee_web_services_client_1_3.xsd",webservice13xsd);
redirect(this,"http://java.sun.com/xml/ns/javaee/javaee_web_services_client_1_3.xsd",webservice13xsd);
redirect(this,"javaee_web_services_client_1_4.xsd",webservice14xsd);
redirect(this,"http://xmlns.jcp.org/xml/ns/javaee/javaee_web_services_client_1_4.xsd",webservice14xsd);
}
};
return xmlParser;
}

View File

@ -21,7 +21,6 @@ package org.eclipse.jetty.websocket.jsr356;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.Future;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.RemoteEndpoint;
@ -30,6 +29,7 @@ import javax.websocket.SendHandler;
import org.eclipse.jetty.util.BufferUtil;
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.common.WebSocketRemoteEndpoint;
import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
import org.eclipse.jetty.websocket.common.message.MessageOutputStream;
@ -80,24 +80,31 @@ public abstract class AbstractJsrRemote implements RemoteEndpoint
@Override
public void flushBatch() throws IOException
{
// TODO Auto-generated method stub
jettyRemote.flush();
}
@Override
public boolean getBatchingAllowed()
{
// TODO Auto-generated method stub
return false;
return jettyRemote.getBatchMode() == BatchMode.ON;
}
@Override
public void setBatchingAllowed(boolean allowed) throws IOException
{
if (jettyRemote.getBatchMode() == BatchMode.ON && !allowed)
jettyRemote.flush();
jettyRemote.setBatchMode(allowed ? BatchMode.ON : BatchMode.OFF);
}
@SuppressWarnings(
{ "rawtypes", "unchecked" })
{"rawtypes", "unchecked"})
public Future<Void> sendObjectViaFuture(Object data)
{
assertMessageNotNull(data);
if (LOG.isDebugEnabled())
{
LOG.debug("sendObject({})",data);
LOG.debug("sendObject({})", data);
}
Encoder encoder = encoders.getEncoderFor(data.getClass());
@ -108,15 +115,15 @@ public abstract class AbstractJsrRemote implements RemoteEndpoint
if (encoder instanceof Encoder.Text)
{
Encoder.Text etxt = (Encoder.Text)encoder;
Encoder.Text text = (Encoder.Text)encoder;
try
{
String msg = etxt.encode(data);
String msg = text.encode(data);
return jettyRemote.sendStringByFuture(msg);
}
catch (EncodeException e)
{
return new EncodeFailedFuture(data,etxt,Encoder.Text.class,e);
return new EncodeFailedFuture(data, text, Encoder.Text.class, e);
}
}
else if (encoder instanceof Encoder.TextStream)
@ -126,12 +133,12 @@ public abstract class AbstractJsrRemote implements RemoteEndpoint
try (MessageWriter writer = new MessageWriter(session))
{
writer.setCallback(callback);
etxt.encode(data,writer);
etxt.encode(data, writer);
return callback;
}
catch (EncodeException | IOException e)
{
return new EncodeFailedFuture(data,etxt,Encoder.Text.class,e);
return new EncodeFailedFuture(data, etxt, Encoder.Text.class, e);
}
}
else if (encoder instanceof Encoder.Binary)
@ -144,7 +151,7 @@ public abstract class AbstractJsrRemote implements RemoteEndpoint
}
catch (EncodeException e)
{
return new EncodeFailedFuture(data,ebin,Encoder.Binary.class,e);
return new EncodeFailedFuture(data, ebin, Encoder.Binary.class, e);
}
}
else if (encoder instanceof Encoder.BinaryStream)
@ -154,12 +161,12 @@ public abstract class AbstractJsrRemote implements RemoteEndpoint
try (MessageOutputStream out = new MessageOutputStream(session))
{
out.setCallback(callback);
ebin.encode(data,out);
ebin.encode(data, out);
return callback;
}
catch (EncodeException | IOException e)
{
return new EncodeFailedFuture(data,ebin,Encoder.Binary.class,e);
return new EncodeFailedFuture(data, ebin, Encoder.Binary.class, e);
}
}
@ -171,7 +178,7 @@ public abstract class AbstractJsrRemote implements RemoteEndpoint
{
if (LOG.isDebugEnabled())
{
LOG.debug("sendPing({})",BufferUtil.toDetailString(data));
LOG.debug("sendPing({})", BufferUtil.toDetailString(data));
}
jettyRemote.sendPing(data);
}
@ -181,14 +188,8 @@ public abstract class AbstractJsrRemote implements RemoteEndpoint
{
if (LOG.isDebugEnabled())
{
LOG.debug("sendPong({})",BufferUtil.toDetailString(data));
LOG.debug("sendPong({})", BufferUtil.toDetailString(data));
}
jettyRemote.sendPong(data);
}
@Override
public void setBatchingAllowed(boolean allowed) throws IOException
{
// TODO Auto-generated method stub
}
}

View File

@ -99,7 +99,7 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont
client = new WebSocketClient(executor);
client.setEventDriverFactory(new JsrEventDriverFactory(client.getPolicy()));
client.setSessionFactory(new JsrSessionFactory(this,this));
client.setSessionFactory(new JsrSessionFactory(this,this,client));
addBean(client);
ShutdownThread.register(this);

View File

@ -29,7 +29,6 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.Extension;
@ -41,6 +40,7 @@ import javax.websocket.WebSocketContainer;
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.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.common.LogicalConnection;
import org.eclipse.jetty.websocket.common.SessionListener;
@ -73,9 +73,9 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess
private JsrAsyncRemote asyncRemote;
private JsrBasicRemote basicRemote;
public JsrSession(URI requestURI, EventDriver websocket, LogicalConnection connection, ClientContainer container, String id, SessionListener[] sessionListeners)
public JsrSession(URI requestURI, EventDriver websocket, LogicalConnection connection, ClientContainer container, String id, SessionListener... sessionListeners)
{
super(requestURI,websocket,connection,sessionListeners);
super(requestURI, websocket, connection, sessionListeners);
if (!(websocket instanceof AbstractJsrEventDriver))
{
throw new IllegalArgumentException("Cannot use, not a JSR WebSocket: " + websocket);
@ -90,13 +90,12 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess
this.messageHandlerFactory = new MessageHandlerFactory();
this.wrappers = new MessageHandlerWrapper[MessageType.values().length];
this.messageHandlerSet = new HashSet<>();
}
@Override
public void addMessageHandler(MessageHandler handler) throws IllegalStateException
{
Objects.requireNonNull(handler,"MessageHandler cannot be null");
Objects.requireNonNull(handler, "MessageHandler cannot be null");
synchronized (wrappers)
{
@ -374,4 +373,11 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess
messageHandlerSet.add(wrapper.getHandler());
}
}
@Override
public BatchMode getBatchMode()
{
// JSR 356 specification mandates default batch mode to be off.
return BatchMode.OFF;
}
}

View File

@ -23,7 +23,6 @@ import java.io.InputStream;
import java.io.Reader;
import java.nio.ByteBuffer;
import java.util.Map;
import javax.websocket.CloseReason;
import javax.websocket.DecodeException;
@ -103,7 +102,7 @@ public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver implements E
if (activeMessage == null)
{
LOG.debug("Binary Message InputStream");
final MessageInputStream stream = new MessageInputStream(session.getConnection());
final MessageInputStream stream = new MessageInputStream();
activeMessage = stream;
// Always dispatch streaming read to another thread.
@ -311,7 +310,7 @@ public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver implements E
{
LOG.debug("Text Message Writer");
final MessageReader stream = new MessageReader(new MessageInputStream(session.getConnection()));
final MessageReader stream = new MessageReader(new MessageInputStream());
activeMessage = stream;
// Always dispatch streaming read to another thread.

View File

@ -23,7 +23,6 @@ import java.io.InputStream;
import java.io.Reader;
import java.nio.ByteBuffer;
import java.util.Map;
import javax.websocket.CloseReason;
import javax.websocket.Endpoint;
import javax.websocket.MessageHandler;
@ -86,7 +85,7 @@ public class JsrEndpointEventDriver extends AbstractJsrEventDriver implements Ev
}
else if (wrapper.wantsStreams())
{
final MessageInputStream stream = new MessageInputStream(session.getConnection());
final MessageInputStream stream = new MessageInputStream();
activeMessage = stream;
dispatch(new Runnable()
{
@ -181,7 +180,7 @@ public class JsrEndpointEventDriver extends AbstractJsrEventDriver implements Ev
}
else if (wrapper.wantsStreams())
{
final MessageReader stream = new MessageReader(new MessageInputStream(session.getConnection()));
final MessageReader stream = new MessageReader(new MessageInputStream());
activeMessage = stream;
dispatch(new Runnable()

View File

@ -18,9 +18,14 @@
package org.eclipse.jetty.websocket.jsr356;
import java.io.IOException;
import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.util.BufferUtil;
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.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
/**
@ -33,7 +38,17 @@ public class JettyEchoSocket extends WebSocketAdapter
@Override
public void onWebSocketBinary(byte[] payload, int offset, int len)
{
getRemote().sendBytes(BufferUtil.toBuffer(payload,offset,len),null);
try
{
RemoteEndpoint remote = getRemote();
remote.sendBytes(BufferUtil.toBuffer(payload, offset, len), null);
if (remote.getBatchMode() == BatchMode.ON)
remote.flush();
}
catch (IOException x)
{
throw new RuntimeIOException(x);
}
}
@Override
@ -45,6 +60,16 @@ public class JettyEchoSocket extends WebSocketAdapter
@Override
public void onWebSocketText(String message)
{
getRemote().sendString(message,null);
try
{
RemoteEndpoint remote = getRemote();
remote.sendString(message, null);
if (remote.getBatchMode() == BatchMode.ON)
remote.flush();
}
catch (IOException x)
{
throw new RuntimeIOException(x);
}
}
}

View File

@ -18,17 +18,13 @@
package org.eclipse.jetty.websocket.jsr356;
import static org.hamcrest.Matchers.instanceOf;
import java.net.URI;
import java.nio.ByteBuffer;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.DeploymentException;
import javax.websocket.MessageHandler;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.common.SessionListener;
import org.eclipse.jetty.websocket.common.events.EventDriver;
import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
import org.eclipse.jetty.websocket.jsr356.client.SimpleEndpointMetadata;
@ -44,6 +40,8 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.Matchers.instanceOf;
public class JsrSessionTest
{
private ClientContainer container;
@ -65,7 +63,7 @@ public class JsrSessionTest
EventDriver driver = new JsrEndpointEventDriver(policy,ei);
DummyConnection connection = new DummyConnection();
session = new JsrSession(requestURI,driver,connection,container,id,new SessionListener[0]);
session = new JsrSession(requestURI,driver,connection,container,id);
}
@Test

View File

@ -22,6 +22,7 @@ import java.net.InetSocketAddress;
import java.util.concurrent.Executor;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.SuspendToken;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WriteCallback;
@ -123,7 +124,7 @@ public class DummyConnection implements LogicalConnection
}
@Override
public void outgoingFrame(Frame frame, WriteCallback callback)
public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
{
}

View File

@ -33,6 +33,7 @@ import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
@ -45,16 +46,6 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
public static final String ENABLE_KEY = "org.eclipse.jetty.websocket.jsr356";
private static final Logger LOG = Log.getLogger(WebSocketServerContainerInitializer.class);
public static boolean isJSR356EnabledOnContext(ServletContext context)
{
Object enable = context.getAttribute(ENABLE_KEY);
if (enable instanceof Boolean)
{
return ((Boolean)enable).booleanValue();
}
return true;
}
public static ServerContainer configureContext(ServletContextHandler context)
{
@ -77,9 +68,22 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
@Override
public void onStartup(Set<Class<?>> c, ServletContext context) throws ServletException
{
if (!isJSR356EnabledOnContext(context))
Object enable = context.getAttribute(ENABLE_KEY);
// Disable if explicitly disabled
if (TypeUtil.isFalse(enable))
{
LOG.info("JSR-356 support disabled via attribute on context {} - {}",context.getContextPath(),context);
if (c.isEmpty())
LOG.debug("JSR-356 support disabled via attribute on context {} - {}",context.getContextPath(),context);
else
LOG.warn("JSR-356 support disabled via attribute on context {} - {}",context.getContextPath(),context);
return;
}
// Disabled if not explicitly enabled and there are no discovered annotations or interfaces
if (!TypeUtil.isTrue(enable) && c.isEmpty())
{
LOG.debug("No JSR-356 annotations or interfaces discovered. JSR-356 support disabled",context.getContextPath(),context);
return;
}
@ -87,12 +91,12 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
if (handler == null)
{
throw new ServletException("Not running on Jetty, JSR support disabled");
throw new ServletException("Not running on Jetty, JSR-356 support disabled");
}
if (!(handler instanceof ServletContextHandler))
{
throw new ServletException("Not running in Jetty ServletContextHandler, JSR support disabled");
throw new ServletException("Not running in Jetty ServletContextHandler, JSR-356 support disabled");
}
ServletContextHandler jettyContext = (ServletContextHandler)handler;
@ -126,7 +130,7 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
LOG.debug("Found ServerApplicationConfig: {}",clazz);
try
{
ServerApplicationConfig config = (ServerApplicationConfig)clazz.newInstance();
ServerApplicationConfig config = clazz.newInstance();
Set<ServerEndpointConfig> seconfigs = config.getEndpointConfigs(discoveredExtendedEndpoints);
if (seconfigs != null)

View File

@ -0,0 +1,177 @@
//
// ========================================================================
// 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.jsr356.server;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.websocket.ClientEndpoint;
import javax.websocket.ContainerProvider;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class BinaryStreamTest
{
private static final String PATH = "/echo";
private Server server;
private ServerConnector connector;
private WebSocketContainer wsClient;
@Before
public void prepare() throws Exception
{
server = new Server();
connector = new ServerConnector(server);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler(server, "/", true, false);
ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
ServerEndpointConfig config = ServerEndpointConfig.Builder.create(ServerBinaryStreamer.class, PATH).build();
container.addEndpoint(config);
server.start();
wsClient = ContainerProvider.getWebSocketContainer();
server.addBean(wsClient, true);
}
@After
public void dispose() throws Exception
{
server.stop();
}
@Test
public void testEchoWithMediumMessage() throws Exception
{
testEcho(1024);
}
@Test
public void testLargestMessage() throws Exception
{
testEcho(wsClient.getDefaultMaxBinaryMessageBufferSize());
}
private void testEcho(int size) throws Exception
{
byte[] data = randomBytes(size);
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + PATH);
ClientBinaryStreamer client = new ClientBinaryStreamer();
Session session = wsClient.connectToServer(client, uri);
try (OutputStream output = session.getBasicRemote().getSendStream())
{
output.write(data);
}
Assert.assertTrue(client.await(5, TimeUnit.SECONDS));
Assert.assertArrayEquals(data, client.getEcho());
}
@Test
public void testMoreThanLargestMessageOneByteAtATime() throws Exception
{
int size = wsClient.getDefaultMaxBinaryMessageBufferSize() + 16;
byte[] data = randomBytes(size);
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + PATH);
ClientBinaryStreamer client = new ClientBinaryStreamer();
Session session = wsClient.connectToServer(client, uri);
try (OutputStream output = session.getBasicRemote().getSendStream())
{
for (int i = 0; i < size; ++i)
output.write(data[i]);
}
Assert.assertTrue(client.await(5, TimeUnit.SECONDS));
Assert.assertArrayEquals(data, client.getEcho());
}
private byte[] randomBytes(int size)
{
byte[] data = new byte[size];
new Random().nextBytes(data);
return data;
}
@ClientEndpoint
public static class ClientBinaryStreamer
{
private final CountDownLatch latch = new CountDownLatch(1);
private final ByteArrayOutputStream output = new ByteArrayOutputStream();
@OnMessage
public void echoed(InputStream input) throws IOException
{
while (true)
{
int read = input.read();
if (read < 0)
break;
output.write(read);
}
latch.countDown();
}
public byte[] getEcho()
{
return output.toByteArray();
}
public boolean await(long timeout, TimeUnit unit) throws InterruptedException
{
return latch.await(timeout, unit);
}
}
@ServerEndpoint(PATH)
public static class ServerBinaryStreamer
{
@OnMessage
public void echo(Session session, InputStream input) throws IOException
{
byte[] buffer = new byte[128];
try (OutputStream output = session.getBasicRemote().getSendStream())
{
int read;
while ((read = input.read(buffer)) >= 0)
output.write(buffer, 0, read);
}
}
}
}

View File

@ -24,6 +24,7 @@ import java.util.concurrent.Executor;
import org.eclipse.jetty.io.ByteBufferPool;
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.SuspendToken;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WriteCallback;
@ -126,7 +127,7 @@ public class DummyConnection implements LogicalConnection
}
@Override
public void outgoingFrame(Frame frame, WriteCallback callback)
public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
{
callback.writeSuccess();
}

View File

@ -0,0 +1,171 @@
//
// ========================================================================
// 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.jsr356.server;
import java.net.URI;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.ContainerProvider;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.Extension;
import javax.websocket.MessageHandler;
import javax.websocket.SendHandler;
import javax.websocket.SendResult;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
import org.eclipse.jetty.websocket.client.io.WebSocketClientConnection;
import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
import org.eclipse.jetty.websocket.common.extensions.compress.DeflateFrameExtension;
import org.eclipse.jetty.websocket.jsr356.JsrExtension;
import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpoint;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class ExtensionStackProcessingTest
{
private Server server;
private ServerConnector connector;
private WebSocketContainer client;
@Before
public void prepare() throws Exception
{
server = new Server();
connector = new ServerConnector(server);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler(server, "/", true, false);
ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
ServerEndpointConfig config = ServerEndpointConfig.Builder.create(BasicEchoEndpoint.class, "/").build();
container.addEndpoint(config);
server.start();
client = ContainerProvider.getWebSocketContainer();
server.addBean(client, true);
}
@After
public void dispose() throws Exception
{
server.stop();
}
@Test
public void testDeflateFrameExtension() throws Exception
{
ClientEndpointConfig config = ClientEndpointConfig.Builder.create()
.extensions(Arrays.<Extension>asList(new JsrExtension("deflate-frame")))
.build();
final String content = "deflate_me";
final CountDownLatch messageLatch = new CountDownLatch(1);
URI uri = URI.create("ws://localhost:" + connector.getLocalPort());
Session session = client.connectToServer(new EndpointAdapter()
{
@Override
public void onMessage(String message)
{
Assert.assertEquals(content, message);
messageLatch.countDown();
}
}, config, uri);
// Make sure everything is wired properly.
OutgoingFrames firstOut = ((JsrSession)session).getOutgoingHandler();
Assert.assertTrue(firstOut instanceof ExtensionStack);
ExtensionStack extensionStack = (ExtensionStack)firstOut;
Assert.assertTrue(extensionStack.isRunning());
OutgoingFrames secondOut = extensionStack.getNextOutgoing();
Assert.assertTrue(secondOut instanceof DeflateFrameExtension);
DeflateFrameExtension deflateExtension = (DeflateFrameExtension)secondOut;
Assert.assertTrue(deflateExtension.isRunning());
OutgoingFrames thirdOut = deflateExtension.getNextOutgoing();
Assert.assertTrue(thirdOut instanceof WebSocketClientConnection);
final CountDownLatch latch = new CountDownLatch(1);
session.getAsyncRemote().sendText(content, new SendHandler()
{
@Override
public void onResult(SendResult result)
{
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(messageLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testPerMessageDeflateExtension() throws Exception
{
ClientEndpointConfig config = ClientEndpointConfig.Builder.create()
.extensions(Arrays.<Extension>asList(new JsrExtension("permessage-deflate")))
.build();
final String content = "deflate_me";
final CountDownLatch messageLatch = new CountDownLatch(1);
URI uri = URI.create("ws://localhost:" + connector.getLocalPort());
Session session = client.connectToServer(new EndpointAdapter()
{
@Override
public void onMessage(String message)
{
Assert.assertEquals(content, message);
messageLatch.countDown();
}
}, config, uri);
final CountDownLatch latch = new CountDownLatch(1);
session.getAsyncRemote().sendText(content, new SendHandler()
{
@Override
public void onResult(SendResult result)
{
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(messageLatch.await(5, TimeUnit.SECONDS));
}
private static abstract class EndpointAdapter extends Endpoint implements MessageHandler.Whole<String>
{
@Override
public void onOpen(Session session, EndpointConfig config)
{
session.addMessageHandler(this);
}
}
}

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.websocket.jsr356.server;
import java.io.IOException;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@ -25,6 +26,7 @@ import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.toolchain.test.EventQueue;
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.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
@ -74,10 +76,10 @@ public class JettyEchoSocket
}
@OnWebSocketMessage
public void onMessage(String msg)
public void onMessage(String msg) throws IOException
{
incomingMessages.add(msg);
remote.sendString(msg,null);
sendMessage(msg);
}
@OnWebSocketConnect
@ -88,8 +90,10 @@ public class JettyEchoSocket
this.remote = session.getRemote();
}
public void sendMessage(String msg)
public void sendMessage(String msg) throws IOException
{
remote.sendStringByFuture(msg);
if (remote.getBatchMode() == BatchMode.ON)
remote.flush();
}
}

View File

@ -0,0 +1,181 @@
//
// ========================================================================
// 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.jsr356.server;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.ContainerProvider;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpoint;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class JsrBatchModeTest
{
private Server server;
private ServerConnector connector;
private WebSocketContainer client;
@Before
public void prepare() throws Exception
{
server = new Server();
connector = new ServerConnector(server);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler(server, "/", true, false);
ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
ServerEndpointConfig config = ServerEndpointConfig.Builder.create(BasicEchoEndpoint.class, "/").build();
container.addEndpoint(config);
server.start();
client = ContainerProvider.getWebSocketContainer();
server.addBean(client, true);
}
@After
public void dispose() throws Exception
{
server.stop();
}
@Test
public void testBatchModeOn() throws Exception
{
ClientEndpointConfig config = ClientEndpointConfig.Builder.create().build();
URI uri = URI.create("ws://localhost:" + connector.getLocalPort());
final CountDownLatch latch = new CountDownLatch(1);
EndpointAdapter endpoint = new EndpointAdapter()
{
@Override
public void onMessage(String message)
{
latch.countDown();
}
};
try (Session session = client.connectToServer(endpoint, config, uri))
{
RemoteEndpoint.Async remote = session.getAsyncRemote();
remote.setBatchingAllowed(true);
Future<Void> future = remote.sendText("batch_mode_on");
// The write is aggregated and therefore completes immediately.
future.get(1, TimeUnit.MICROSECONDS);
// Did not flush explicitly, so the message should not be back yet.
Assert.assertFalse(latch.await(1, TimeUnit.SECONDS));
// Explicitly flush.
remote.flushBatch();
// Wait for the echo.
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}
@Test
public void testBatchModeOff() throws Exception
{
ClientEndpointConfig config = ClientEndpointConfig.Builder.create().build();
URI uri = URI.create("ws://localhost:" + connector.getLocalPort());
final CountDownLatch latch = new CountDownLatch(1);
EndpointAdapter endpoint = new EndpointAdapter()
{
@Override
public void onMessage(String message)
{
latch.countDown();
}
};
try (Session session = client.connectToServer(endpoint, config, uri))
{
RemoteEndpoint.Async remote = session.getAsyncRemote();
remote.setBatchingAllowed(false);
Future<Void> future = remote.sendText("batch_mode_off");
// The write is immediate.
future.get(1, TimeUnit.SECONDS);
// Wait for the echo.
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}
@Test
public void testBatchModeAuto() throws Exception
{
ClientEndpointConfig config = ClientEndpointConfig.Builder.create().build();
URI uri = URI.create("ws://localhost:" + connector.getLocalPort());
final CountDownLatch latch = new CountDownLatch(1);
EndpointAdapter endpoint = new EndpointAdapter()
{
@Override
public void onMessage(String message)
{
latch.countDown();
}
};
try (Session session = client.connectToServer(endpoint, config, uri))
{
RemoteEndpoint.Async remote = session.getAsyncRemote();
Future<Void> future = remote.sendText("batch_mode_auto");
// The write is immediate, as per the specification.
future.get(1, TimeUnit.SECONDS);
// Wait for the echo.
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}
private static abstract class EndpointAdapter extends Endpoint implements MessageHandler.Whole<String>
{
@Override
public void onOpen(Session session, EndpointConfig config)
{
session.addMessageHandler(this);
}
}
}

View File

@ -0,0 +1,130 @@
//
// ========================================================================
// 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.jsr356.server;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.websocket.ContainerProvider;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpoint;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class MemoryUsageTest
{
private Server server;
private ServerConnector connector;
private WebSocketContainer client;
@Before
public void prepare() throws Exception
{
server = new Server();
connector = new ServerConnector(server);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler(server, "/", true, false);
ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
ServerEndpointConfig config = ServerEndpointConfig.Builder.create(BasicEchoEndpoint.class, "/").build();
container.addEndpoint(config);
server.start();
client = ContainerProvider.getWebSocketContainer();
server.addBean(client, true);
}
@After
public void dispose() throws Exception
{
server.stop();
}
@Test
public void testMemoryUsage() throws Exception
{
int sessionCount = 1000;
Session[] sessions = new Session[sessionCount];
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
System.gc();
MemoryUsage heapBefore = memoryMXBean.getHeapMemoryUsage();
MemoryUsage nonHeapBefore = memoryMXBean.getNonHeapMemoryUsage();
URI uri = URI.create("ws://localhost:" + connector.getLocalPort());
final CountDownLatch latch = new CountDownLatch(sessionCount);
for (int i = 0; i < sessionCount; ++i)
{
sessions[i] = client.connectToServer(new EndpointAdapter()
{
@Override
public void onMessage(String message)
{
latch.countDown();
}
}, uri);
}
for (int i = 0; i < sessionCount; ++i)
{
sessions[i].getBasicRemote().sendText("OK");
}
latch.await(5 * sessionCount, TimeUnit.MILLISECONDS);
System.gc();
MemoryUsage heapAfter = memoryMXBean.getHeapMemoryUsage();
MemoryUsage nonHeapAfter = memoryMXBean.getNonHeapMemoryUsage();
long heapUsed = heapAfter.getUsed() - heapBefore.getUsed();
long nonHeapUsed = nonHeapAfter.getUsed() - nonHeapBefore.getUsed();
// System.out.println("heapUsed = " + heapUsed);
// System.out.println("nonHeapUsed = " + nonHeapUsed);
// new CountDownLatch(1).await();
// Assume no more than 25 KiB per session pair (client and server).
long expected = 25 * 1024 * sessionCount;
Assert.assertTrue(heapUsed < expected);
}
private static abstract class EndpointAdapter extends Endpoint implements MessageHandler.Whole<String>
{
@Override
public void onOpen(Session session, EndpointConfig config)
{
session.addMessageHandler(this);
}
}
}

View File

@ -18,17 +18,13 @@
package org.eclipse.jetty.websocket.jsr356.server;
import static org.hamcrest.Matchers.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.common.SessionListener;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.events.EventDriver;
import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
@ -45,6 +41,9 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
public class OnPartialTest
{
@Rule
@ -80,7 +79,7 @@ public class OnPartialTest
DummyConnection connection = new DummyConnection();
ClientContainer container = new ClientContainer();
@SuppressWarnings("resource")
JsrSession session = new JsrSession(requestURI,driver,connection,container,id,new SessionListener[0]);
JsrSession session = new JsrSession(requestURI,driver,connection,container,id);
session.setPolicy(policy);
session.open();
return driver;

View File

@ -0,0 +1,179 @@
//
// ========================================================================
// 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.jsr356.server;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.websocket.ClientEndpoint;
import javax.websocket.ContainerProvider;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class TextStreamTest
{
private static final String PATH = "/echo";
private static final String CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private Server server;
private ServerConnector connector;
private WebSocketContainer wsClient;
@Before
public void prepare() throws Exception
{
server = new Server();
connector = new ServerConnector(server);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler(server, "/", true, false);
ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
ServerEndpointConfig config = ServerEndpointConfig.Builder.create(ServerTextStreamer.class, PATH).build();
container.addEndpoint(config);
server.start();
wsClient = ContainerProvider.getWebSocketContainer();
server.addBean(wsClient, true);
}
@After
public void dispose() throws Exception
{
server.stop();
}
@Test
public void testEchoWithMediumMessage() throws Exception
{
testEcho(1024);
}
@Test
public void testLargestMessage() throws Exception
{
testEcho(wsClient.getDefaultMaxBinaryMessageBufferSize());
}
private void testEcho(int size) throws Exception
{
char[] data = randomChars(size);
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + PATH);
ClientTextStreamer client = new ClientTextStreamer();
Session session = wsClient.connectToServer(client, uri);
try (Writer output = session.getBasicRemote().getSendWriter())
{
output.write(data);
}
Assert.assertTrue(client.await(5, TimeUnit.SECONDS));
Assert.assertArrayEquals(data, client.getEcho());
}
@Test
public void testMoreThanLargestMessageOneByteAtATime() throws Exception
{
int size = wsClient.getDefaultMaxBinaryMessageBufferSize() + 16;
char[] data = randomChars(size);
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + PATH);
ClientTextStreamer client = new ClientTextStreamer();
Session session = wsClient.connectToServer(client, uri);
try (Writer output = session.getBasicRemote().getSendWriter())
{
for (int i = 0; i < size; ++i)
output.write(data[i]);
}
Assert.assertTrue(client.await(5, TimeUnit.SECONDS));
Assert.assertArrayEquals(data, client.getEcho());
}
private char[] randomChars(int size)
{
char[] data = new char[size];
Random random = new Random();
for (int i = 0; i < data.length; ++i)
data[i] = CHARS.charAt(random.nextInt(CHARS.length()));
return data;
}
@ClientEndpoint
public static class ClientTextStreamer
{
private final CountDownLatch latch = new CountDownLatch(1);
private final StringBuilder output = new StringBuilder();
@OnMessage
public void echoed(Reader input) throws IOException
{
while (true)
{
int read = input.read();
if (read < 0)
break;
output.append((char)read);
}
latch.countDown();
}
public char[] getEcho()
{
return output.toString().toCharArray();
}
public boolean await(long timeout, TimeUnit unit) throws InterruptedException
{
return latch.await(timeout, unit);
}
}
@ServerEndpoint(PATH)
public static class ServerTextStreamer
{
@OnMessage
public void echo(Session session, Reader input) throws IOException
{
char[] buffer = new char[128];
try (Writer output = session.getBasicRemote().getSendWriter())
{
int read;
while ((read = input.read(buffer)) >= 0)
output.write(buffer, 0, read);
}
}
}
}

View File

@ -0,0 +1,50 @@
//
// ========================================================================
// 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.api;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
/**
* The possible batch modes when invoking {@link OutgoingFrames#outgoingFrame(Frame, WriteCallback, BatchMode)}.
*/
public enum BatchMode
{
/**
* Implementers are free to decide whether to send or not frames
* to the network layer.
*/
AUTO,
/**
* Implementers must batch frames.
*/
ON,
/**
* Implementers must send frames to the network layer.
*/
OFF;
public static BatchMode max(BatchMode one, BatchMode two)
{
// Return the BatchMode that has the higher priority, where AUTO < ON < OFF.
return one.ordinal() < two.ordinal() ? two : one;
}
}

View File

@ -121,4 +121,17 @@ public interface RemoteEndpoint
* callback to notify of success or failure of the write operation
*/
void sendString(String text, WriteCallback callback);
/**
* @return the batch mode with which messages are sent.
* @see #flush()
*/
BatchMode getBatchMode();
/**
* Flushes messages that may have been batched by the implementation.
* @throws IOException if the flush fails
* @see #getBatchMode()
*/
void flush() throws IOException;
}

View File

@ -34,16 +34,16 @@ import org.eclipse.jetty.websocket.api.util.QuoteUtil;
public class UpgradeRequest
{
private URI requestURI;
private List<String> subProtocols = new ArrayList<>();
private List<ExtensionConfig> extensions = new ArrayList<>();
private List<HttpCookie> cookies = new ArrayList<>();
private List<String> subProtocols = new ArrayList<>(1);
private List<ExtensionConfig> extensions = new ArrayList<>(1);
private List<HttpCookie> cookies = new ArrayList<>(1);
private Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private Map<String, List<String>> parameters = new HashMap<>();
private Map<String, List<String>> parameters = new HashMap<>(1);
private Object session;
private String httpVersion;
private String method;
private String host;
private boolean secure = false;
private boolean secure;
protected UpgradeRequest()
{
@ -57,16 +57,12 @@ public class UpgradeRequest
public UpgradeRequest(URI requestURI)
{
this();
setRequestURI(requestURI);
}
public void addExtensions(ExtensionConfig... configs)
{
for (ExtensionConfig config : configs)
{
extensions.add(config);
}
Collections.addAll(extensions, configs);
}
public void addExtensions(String... configs)
@ -357,10 +353,7 @@ public class UpgradeRequest
*/
public void setSubProtocols(String... protocols)
{
this.subProtocols.clear();
for (String protocol : protocols)
{
this.subProtocols.add(protocol);
}
subProtocols.clear();
Collections.addAll(subProtocols, protocols);
}
}

View File

@ -68,6 +68,11 @@ public interface Frame
return (opcode == TEXT.getOpCode()) | (opcode == BINARY.getOpCode());
}
public boolean isContinuation()
{
return opcode == CONTINUATION.getOpCode();
}
@Override
public String toString()
{

View File

@ -25,5 +25,14 @@ public interface IncomingFrames
{
public void incomingError(Throwable t);
/**
* Process the incoming frame.
* <p>
* Note: if you need to hang onto any information from the frame, be sure
* to copy it, as the information contained in the Frame will be released
* and/or reused by the implementation.
*
* @param frame the frame to process
*/
public void incomingFrame(Frame frame);
}

View File

@ -18,24 +18,27 @@
package org.eclipse.jetty.websocket.api.extensions;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.WriteCallback;
/**
* Interface for dealing with frames outgoing to the network (eventually)
* Interface for dealing with frames outgoing to (eventually) the network layer.
*/
public interface OutgoingFrames
{
/**
* A frame, and optional callback, intended for the network.
* <p>
* Note: the frame can undergo many transformations in the various layers and extensions present in the implementation.
* <p>
* If you are implementing a mutation, you are obliged to handle the incoming WriteCallback appropriately.
*
* @param frame
* the frame to eventually write to the network.
* @param callback
* the optional callback to use for success/failure of the network write operation. Can be null.
* A frame, and optional callback, intended for the network layer.
* <p/>
* Note: the frame can undergo many transformations in the various
* layers and extensions present in the implementation.
* <p/>
* If you are implementing a mutation, you are obliged to handle
* the incoming WriteCallback appropriately.
*
* @param frame the frame to eventually write to the network layer.
* @param callback the callback to notify when the frame is written.
* @param batchMode the batch mode requested by the sender.
*/
void outgoingFrame(Frame frame, WriteCallback callback);
void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode);
}

View File

@ -23,11 +23,10 @@ import java.net.CookieStore;
import java.net.SocketAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
@ -75,7 +74,6 @@ public class WebSocketClient extends ContainerLifeCycle implements SessionListen
private boolean daemon = false;
private EventDriverFactory eventDriverFactory;
private SessionFactory sessionFactory;
private Set<WebSocketSession> openSessions = new CopyOnWriteArraySet<>();
private ByteBufferPool bufferPool;
private Executor executor;
private Scheduler scheduler;
@ -374,7 +372,7 @@ public class WebSocketClient extends ContainerLifeCycle implements SessionListen
public Set<WebSocketSession> getOpenSessions()
{
return Collections.unmodifiableSet(this.openSessions);
return new HashSet<>(getBeans(WebSocketSession.class));
}
public WebSocketPolicy getPolicy()
@ -472,15 +470,14 @@ public class WebSocketClient extends ContainerLifeCycle implements SessionListen
@Override
public void onSessionClosed(WebSocketSession session)
{
LOG.info("Session Closed: {}",session);
this.openSessions.remove(session);
LOG.debug("Session Closed: {}",session);
removeBean(session);
}
@Override
public void onSessionOpened(WebSocketSession session)
{
LOG.info("Session Opened: {}",session);
this.openSessions.add(session);
LOG.debug("Session Opened: {}",session);
}
public void setAsyncWriteTimeout(long ms)
@ -566,4 +563,11 @@ public class WebSocketClient extends ContainerLifeCycle implements SessionListen
{
this.sessionFactory = sessionFactory;
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
dumpThis(out);
dump(out, indent, getOpenSessions());
}
}

View File

@ -259,6 +259,9 @@ public class UpgradeConnection extends AbstractConnection
session.setOutgoingHandler(extensionStack);
extensionStack.setNextOutgoing(connection);
session.addBean(extensionStack);
connectPromise.getClient().addBean(session);
// Now swap out the connection
endp.setConnection(connection);
connection.onOpen();

View File

@ -26,7 +26,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.ProtocolException;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.Frame;
@ -96,26 +96,16 @@ public class WebSocketClientConnection extends AbstractWebSocketConnection
}
/**
* Overrride to set masker
* Override to set the masker.
*/
@Override
public void outgoingFrame(Frame frame, WriteCallback callback)
public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
{
if (frame instanceof WebSocketFrame)
{
if (masker == null)
{
ProtocolException ex = new ProtocolException("Must set a Masker");
LOG.warn(ex);
if (callback != null)
{
callback.writeFailed(ex);
}
return;
}
masker.setMask((WebSocketFrame)frame);
}
super.outgoingFrame(frame,callback);
super.outgoingFrame(frame,callback, batchMode);
}
@Override

View File

@ -18,14 +18,13 @@
package org.eclipse.jetty.websocket.client;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
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.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
@ -79,10 +78,13 @@ public class ClientWriteThread extends Thread
TimeUnit.MILLISECONDS.sleep(slowness);
}
}
if (remote.getBatchMode() == BatchMode.ON)
remote.flush();
// block on write of last message
lastMessage.get(2,TimeUnit.MINUTES); // block on write
if (lastMessage != null)
lastMessage.get(2,TimeUnit.MINUTES); // block on write
}
catch (InterruptedException | ExecutionException | TimeoutException e)
catch (Exception e)
{
LOG.warn(e);
}

View File

@ -18,8 +18,6 @@
package org.eclipse.jetty.websocket.client;
import static org.hamcrest.Matchers.*;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Queue;
@ -37,6 +35,8 @@ import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
import org.junit.Assert;
import static org.hamcrest.Matchers.is;
public class ServerReadThread extends Thread
{
private static final int BUFFER_SIZE = 8192;
@ -44,13 +44,13 @@ public class ServerReadThread extends Thread
private final ServerConnection conn;
private boolean active = true;
private int slowness = -1; // disabled is default
private AtomicInteger frameCount = new AtomicInteger();
private CountDownLatch expectedMessageCount;
private final AtomicInteger frameCount = new AtomicInteger();
private final CountDownLatch expectedMessageCount;
public ServerReadThread(ServerConnection conn)
public ServerReadThread(ServerConnection conn, int expectedMessages)
{
this.conn = conn;
this.expectedMessageCount = new CountDownLatch(1);
this.expectedMessageCount = new CountDownLatch(expectedMessages);
}
public void cancel()
@ -75,14 +75,12 @@ public class ServerReadThread extends Thread
ByteBuffer buf = bufferPool.acquire(BUFFER_SIZE,false);
BufferUtil.clearToFill(buf);
int len = 0;
try
{
while (active)
{
BufferUtil.clearToFill(buf);
len = conn.read(buf);
int len = conn.read(buf);
if (len > 0)
{
@ -108,7 +106,7 @@ public class ServerReadThread extends Thread
}
if (slowness > 0)
{
TimeUnit.MILLISECONDS.sleep(slowness);
TimeUnit.MILLISECONDS.sleep(getSlowness());
}
}
}
@ -122,11 +120,6 @@ public class ServerReadThread extends Thread
}
}
public void setExpectedMessageCount(int expectedMessageCount)
{
this.expectedMessageCount = new CountDownLatch(expectedMessageCount);
}
public void setSlowness(int slowness)
{
this.slowness = slowness;

View File

@ -18,13 +18,13 @@
package org.eclipse.jetty.websocket.client;
import static org.hamcrest.Matchers.*;
import java.net.URI;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.test.BlockheadServer;
@ -34,6 +34,9 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
public class SessionTest
{
private BlockheadServer server;
@ -81,7 +84,10 @@ public class SessionTest
Assert.assertThat("client.connectionManager.sessions.size",client.getConnectionManager().getSessions().size(),is(1));
cliSock.getSession().getRemote().sendStringByFuture("Hello World!");
RemoteEndpoint remote = cliSock.getSession().getRemote();
remote.sendStringByFuture("Hello World!");
if (remote.getBatchMode() == BatchMode.ON)
remote.flush();
srvSock.echoMessage(1,TimeUnit.MILLISECONDS,500);
// wait for response from server
cliSock.waitForMessage(500,TimeUnit.MILLISECONDS);

View File

@ -18,8 +18,6 @@
package org.eclipse.jetty.websocket.client;
import static org.hamcrest.Matchers.is;
import java.net.URI;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@ -36,6 +34,8 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
public class SlowClientTest
{
@Rule
@ -79,24 +79,23 @@ public class SlowClientTest
client.getPolicy().setIdleTimeout(60000);
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(tsocket,wsUri);
Future<Session> future = client.connect(tsocket, wsUri);
ServerConnection sconnection = server.accept();
sconnection.setSoTimeout(60000);
sconnection.upgrade();
// Confirm connected
future.get(500,TimeUnit.MILLISECONDS);
tsocket.waitForConnected(500,TimeUnit.MILLISECONDS);
future.get(500, TimeUnit.MILLISECONDS);
tsocket.waitForConnected(500, TimeUnit.MILLISECONDS);
int messageCount = 10;
// Setup server read thread
ServerReadThread reader = new ServerReadThread(sconnection);
reader.setExpectedMessageCount(Integer.MAX_VALUE); // keep reading till I tell you to stop
ServerReadThread reader = new ServerReadThread(sconnection, messageCount);
reader.start();
// Have client write slowly.
int messageCount = 1000;
ClientWriteThread writer = new ClientWriteThread(tsocket.getSession());
writer.setMessageCount(messageCount);
writer.setMessage("Hello");
@ -104,13 +103,15 @@ public class SlowClientTest
writer.start();
writer.join();
reader.waitForExpectedMessageCount(1, TimeUnit.MINUTES);
// Verify receive
Assert.assertThat("Frame Receive Count",reader.getFrameCount(),is(messageCount));
Assert.assertThat("Frame Receive Count", reader.getFrameCount(), is(messageCount));
// Close
tsocket.getSession().close(StatusCode.NORMAL,"Done");
tsocket.getSession().close(StatusCode.NORMAL, "Done");
Assert.assertTrue("Client Socket Closed",tsocket.closeLatch.await(3,TimeUnit.MINUTES));
Assert.assertTrue("Client Socket Closed", tsocket.closeLatch.await(3, TimeUnit.MINUTES));
tsocket.assertCloseCode(StatusCode.NORMAL);
reader.cancel(); // stop reading

View File

@ -18,8 +18,6 @@
package org.eclipse.jetty.websocket.client;
import static org.hamcrest.Matchers.is;
import java.net.URI;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@ -37,6 +35,8 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
public class SlowServerTest
{
@Rule
@ -91,11 +91,10 @@ public class SlowServerTest
future.get(500,TimeUnit.MILLISECONDS);
tsocket.waitForConnected(500,TimeUnit.MILLISECONDS);
int messageCount = 10; // TODO: increase to 1000
int messageCount = 10;
// Setup slow server read thread
ServerReadThread reader = new ServerReadThread(sconnection);
reader.setExpectedMessageCount(messageCount);
ServerReadThread reader = new ServerReadThread(sconnection, messageCount);
reader.setSlowness(100); // slow it down
reader.start();

View File

@ -18,11 +18,6 @@
package org.eclipse.jetty.websocket.client;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.Arrays;
@ -33,6 +28,8 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.common.frames.TextFrame;
@ -45,6 +42,11 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
@RunWith(AdvancedRunner.class)
public class WebSocketClientTest
{
@ -118,7 +120,10 @@ public class WebSocketClientTest
Assert.assertThat("client.connectionManager.sessions.size",client.getConnectionManager().getSessions().size(),is(1));
cliSock.getSession().getRemote().sendStringByFuture("Hello World!");
RemoteEndpoint remote = cliSock.getSession().getRemote();
remote.sendStringByFuture("Hello World!");
if (remote.getBatchMode() == BatchMode.ON)
remote.flush();
srvSock.echoMessage(1,TimeUnit.MILLISECONDS,500);
// wait for response from server
cliSock.waitForMessage(500,TimeUnit.MILLISECONDS);

View File

@ -58,7 +58,7 @@ public class Generator
/**
* The overhead (maximum) for a framing header. Assuming a maximum sized payload with masking key.
*/
public static final int OVERHEAD = 28;
public static final int MAX_HEADER_LENGTH = 28;
private final WebSocketBehavior behavior;
private final ByteBufferPool bufferPool;
@ -193,12 +193,18 @@ public class Generator
public ByteBuffer generateHeaderBytes(Frame frame)
{
ByteBuffer buffer = bufferPool.acquire(MAX_HEADER_LENGTH,true);
generateHeaderBytes(frame,buffer);
return buffer;
}
public void generateHeaderBytes(Frame frame, ByteBuffer buffer)
{
int p=BufferUtil.flipToFill(buffer);
// we need a framing header
assertFrameValid(frame);
ByteBuffer buffer = bufferPool.acquire(OVERHEAD,true);
BufferUtil.clearToFill(buffer);
/*
* start the generation process
*/
@ -284,7 +290,9 @@ public class Generator
{
byte[] mask = frame.getMask();
buffer.put(mask);
int maskInt = ByteBuffer.wrap(mask).getInt();
int maskInt = 0;
for (byte maskByte : mask)
maskInt = (maskInt << 8) + (maskByte & 0xFF);
// perform data masking here
ByteBuffer payload = frame.getPayload();
@ -311,8 +319,7 @@ public class Generator
}
}
BufferUtil.flipToFlush(buffer,0);
return buffer;
BufferUtil.flipToFlush(buffer,p);
}
/**

View File

@ -97,10 +97,8 @@ public class Parser
private void assertSanePayloadLength(long len)
{
if (LOG.isDebugEnabled())
{
LOG.debug("Payload Length: {} - {}",len,this);
}
// Since we use ByteBuffer so often, having lengths over Integer.MAX_VALUE is really impossible.
if (len > Integer.MAX_VALUE)
{
@ -184,9 +182,7 @@ public class Parser
protected void notifyFrame(final Frame f)
{
if (LOG.isDebugEnabled())
{
LOG.debug("{} Notify {}",policy.getBehavior(),getIncomingFramesHandler());
}
if (policy.getBehavior() == WebSocketBehavior.SERVER)
{
@ -243,7 +239,7 @@ public class Parser
incomingFramesHandler.incomingError(e);
}
public synchronized void parse(ByteBuffer buffer)
public void parse(ByteBuffer buffer)
{
if (buffer.remaining() <= 0)
{
@ -256,7 +252,8 @@ public class Parser
// parse through all the frames in the buffer
while (parseFrame(buffer))
{
LOG.debug("{} Parsed Frame: {}",policy.getBehavior(),frame);
if (LOG.isDebugEnabled())
LOG.debug("{} Parsed Frame: {}",policy.getBehavior(),frame);
notifyFrame(frame);
if (frame.isDataFrame())
{
@ -301,7 +298,8 @@ public class Parser
*/
private boolean parseFrame(ByteBuffer buffer)
{
LOG.debug("{} Parsing {} bytes",policy.getBehavior(),buffer.remaining());
if (LOG.isDebugEnabled())
LOG.debug("{} Parsing {} bytes",policy.getBehavior(),buffer.remaining());
while (buffer.hasRemaining())
{
switch (state)
@ -320,14 +318,12 @@ public class Parser
}
if (LOG.isDebugEnabled())
{
LOG.debug("OpCode {}, fin={} rsv={}{}{}",
OpCode.name(opcode),
fin,
(isRsv1InUse()?'1':'.'),
(isRsv2InUse()?'1':'.'),
(isRsv3InUse()?'1':'.'));
}
// base framing flags
switch(opcode)
@ -419,9 +415,7 @@ public class Parser
else
{
if (LOG.isDebugEnabled())
{
LOG.debug("OpCode {}, fin={} rsv=000",OpCode.name(opcode),fin);
}
}
state = State.PAYLOAD_LEN;
@ -598,9 +592,7 @@ public class Parser
buffer.position(buffer.position() + window.remaining());
if (LOG.isDebugEnabled())
{
LOG.debug("Window: {}",BufferUtil.toDetailString(window));
}
maskProcessor.process(window);

View File

@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.util.BufferUtil;
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.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
@ -37,6 +38,7 @@ import org.eclipse.jetty.websocket.common.frames.DataFrame;
import org.eclipse.jetty.websocket.common.frames.PingFrame;
import org.eclipse.jetty.websocket.common.frames.PongFrame;
import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.common.io.FrameFlusher;
import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
/**
@ -44,23 +46,22 @@ import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
*/
public class WebSocketRemoteEndpoint implements RemoteEndpoint
{
/** Message Type*/
private enum MsgType
private enum MsgType
{
BLOCKING,
ASYNC,
STREAMING,
PARTIAL_TEXT,
PARTIAL_BINARY
};
}
private static final WriteCallback NOOP_CALLBACK = new WriteCallback()
{
@Override
public void writeSuccess()
{
}
@Override
public void writeFailed(Throwable x)
{
@ -68,21 +69,25 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
};
private static final Logger LOG = Log.getLogger(WebSocketRemoteEndpoint.class);
public final LogicalConnection connection;
public final OutgoingFrames outgoing;
/** JSR-356 blocking send behaviour message and Type sanity to support partial send properly */
private final static int ASYNC_MASK = 0x0000FFFF;
private final static int BLOCK_MASK = 0x00010000;
private final static int STREAM_MASK = 0x00020000;
private final static int PARTIAL_TEXT_MASK= 0x00040000;
private final static int PARTIAL_BINARY_MASK= 0x00080000;
private final static int ASYNC_MASK = 0x0000FFFF;
private final static int BLOCK_MASK = 0x00010000;
private final static int STREAM_MASK = 0x00020000;
private final static int PARTIAL_TEXT_MASK = 0x00040000;
private final static int PARTIAL_BINARY_MASK = 0x00080000;
private final LogicalConnection connection;
private final OutgoingFrames outgoing;
private final AtomicInteger msgState = new AtomicInteger();
private final BlockingWriteCallback blocker = new BlockingWriteCallback();
private volatile BatchMode batchMode;
public WebSocketRemoteEndpoint(LogicalConnection connection, OutgoingFrames outgoing)
{
this(connection, outgoing, BatchMode.AUTO);
}
public WebSocketRemoteEndpoint(LogicalConnection connection, OutgoingFrames outgoing, BatchMode batchMode)
{
if (connection == null)
{
@ -90,11 +95,12 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
}
this.connection = connection;
this.outgoing = outgoing;
this.batchMode = batchMode;
}
private void blockingWrite(WebSocketFrame frame) throws IOException
{
uncheckedSendFrame(frame,blocker);
uncheckedSendFrame(frame, blocker);
blocker.block();
}
@ -106,107 +112,107 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
// Blocking -> Pending!! ; Async -> STREAMING ; Partial -> Pending!! ; Stream -> STREAMING
// Blocking -> Pending!! ; Async -> Pending!! ; Partial -> PARTIAL_TEXT ; Stream -> Pending!!
// Blocking -> Pending!! ; Async -> Pending!! ; Partial -> PARTIAL_BIN ; Stream -> Pending!!
while(true)
while (true)
{
int state = msgState.get();
switch (type)
{
case BLOCKING:
if ((state&(PARTIAL_BINARY_MASK+PARTIAL_TEXT_MASK))!=0)
throw new IllegalStateException(String.format("Partial message pending %x for %s",state,type));
if ((state&BLOCK_MASK)!=0)
throw new IllegalStateException(String.format("Blocking message pending %x for %s",state,type));
if (msgState.compareAndSet(state,state|BLOCK_MASK))
return state==0;
if ((state & (PARTIAL_BINARY_MASK + PARTIAL_TEXT_MASK)) != 0)
throw new IllegalStateException(String.format("Partial message pending %x for %s", state, type));
if ((state & BLOCK_MASK) != 0)
throw new IllegalStateException(String.format("Blocking message pending %x for %s", state, type));
if (msgState.compareAndSet(state, state | BLOCK_MASK))
return state == 0;
break;
case ASYNC:
if ((state&(PARTIAL_BINARY_MASK+PARTIAL_TEXT_MASK))!=0)
throw new IllegalStateException(String.format("Partial message pending %x for %s",state,type));
if ((state&ASYNC_MASK)==ASYNC_MASK)
throw new IllegalStateException(String.format("Too many async sends: %x",state));
if (msgState.compareAndSet(state,state+1))
return state==0;
if ((state & (PARTIAL_BINARY_MASK + PARTIAL_TEXT_MASK)) != 0)
throw new IllegalStateException(String.format("Partial message pending %x for %s", state, type));
if ((state & ASYNC_MASK) == ASYNC_MASK)
throw new IllegalStateException(String.format("Too many async sends: %x", state));
if (msgState.compareAndSet(state, state + 1))
return state == 0;
break;
case STREAMING:
if ((state&(PARTIAL_BINARY_MASK+PARTIAL_TEXT_MASK))!=0)
throw new IllegalStateException(String.format("Partial message pending %x for %s",state,type));
if ((state&STREAM_MASK)!=0)
throw new IllegalStateException(String.format("Already streaming %x for %s",state,type));
if (msgState.compareAndSet(state,state|STREAM_MASK))
return state==0;
if ((state & (PARTIAL_BINARY_MASK + PARTIAL_TEXT_MASK)) != 0)
throw new IllegalStateException(String.format("Partial message pending %x for %s", state, type));
if ((state & STREAM_MASK) != 0)
throw new IllegalStateException(String.format("Already streaming %x for %s", state, type));
if (msgState.compareAndSet(state, state | STREAM_MASK))
return state == 0;
break;
case PARTIAL_BINARY:
if (state==PARTIAL_BINARY_MASK)
if (state == PARTIAL_BINARY_MASK)
return false;
if (state==0)
if (state == 0)
{
if (msgState.compareAndSet(0,state|PARTIAL_BINARY_MASK))
if (msgState.compareAndSet(0, state | PARTIAL_BINARY_MASK))
return true;
}
throw new IllegalStateException(String.format("Cannot send %s in state %x",type,state));
throw new IllegalStateException(String.format("Cannot send %s in state %x", type, state));
case PARTIAL_TEXT:
if (state==PARTIAL_TEXT_MASK)
if (state == PARTIAL_TEXT_MASK)
return false;
if (state==0)
if (state == 0)
{
if (msgState.compareAndSet(0,state|PARTIAL_TEXT_MASK))
if (msgState.compareAndSet(0, state | PARTIAL_TEXT_MASK))
return true;
}
throw new IllegalStateException(String.format("Cannot send %s in state %x",type,state));
throw new IllegalStateException(String.format("Cannot send %s in state %x", type, state));
}
}
}
private void unlockMsg(MsgType type)
{
while(true)
while (true)
{
int state = msgState.get();
switch (type)
{
case BLOCKING:
if ((state&BLOCK_MASK)==0)
throw new IllegalStateException(String.format("Not Blocking in state %x",state));
if (msgState.compareAndSet(state,state&~BLOCK_MASK))
if ((state & BLOCK_MASK) == 0)
throw new IllegalStateException(String.format("Not Blocking in state %x", state));
if (msgState.compareAndSet(state, state & ~BLOCK_MASK))
return;
break;
case ASYNC:
if ((state&ASYNC_MASK)==0)
throw new IllegalStateException(String.format("Not Async in %x",state));
if (msgState.compareAndSet(state,state-1))
if ((state & ASYNC_MASK) == 0)
throw new IllegalStateException(String.format("Not Async in %x", state));
if (msgState.compareAndSet(state, state - 1))
return;
break;
case STREAMING:
if ((state&STREAM_MASK)==0)
throw new IllegalStateException(String.format("Not Streaming in state %x",state));
if (msgState.compareAndSet(state,state&~STREAM_MASK))
if ((state & STREAM_MASK) == 0)
throw new IllegalStateException(String.format("Not Streaming in state %x", state));
if (msgState.compareAndSet(state, state & ~STREAM_MASK))
return;
break;
case PARTIAL_BINARY:
if (msgState.compareAndSet(PARTIAL_BINARY_MASK,0))
if (msgState.compareAndSet(PARTIAL_BINARY_MASK, 0))
return;
throw new IllegalStateException(String.format("Not Partial Binary in state %x",state));
throw new IllegalStateException(String.format("Not Partial Binary in state %x", state));
case PARTIAL_TEXT:
if (msgState.compareAndSet(PARTIAL_TEXT_MASK,0))
if (msgState.compareAndSet(PARTIAL_TEXT_MASK, 0))
return;
throw new IllegalStateException(String.format("Not Partial Text in state %x",state));
throw new IllegalStateException(String.format("Not Partial Text in state %x", state));
}
}
}
public InetSocketAddress getInetSocketAddress()
{
return connection.getRemoteAddress();
@ -214,15 +220,14 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
/**
* Internal
*
* @param frame
* the frame to write
*
* @param frame the frame to write
* @return the future for the network write of the frame
*/
private Future<Void> sendAsyncFrame(WebSocketFrame frame)
{
FutureWriteCallback future = new FutureWriteCallback();
uncheckedSendFrame(frame,future);
uncheckedSendFrame(frame, future);
return future;
}
@ -238,7 +243,7 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
connection.getIOState().assertOutputOpen();
if (LOG.isDebugEnabled())
{
LOG.debug("sendBytes with {}",BufferUtil.toDetailString(data));
LOG.debug("sendBytes with {}", BufferUtil.toDetailString(data));
}
blockingWrite(new BinaryFrame().setPayload(data));
}
@ -256,7 +261,7 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
{
if (LOG.isDebugEnabled())
{
LOG.debug("sendBytesByFuture with {}",BufferUtil.toDetailString(data));
LOG.debug("sendBytesByFuture with {}", BufferUtil.toDetailString(data));
}
return sendAsyncFrame(new BinaryFrame().setPayload(data));
}
@ -274,9 +279,9 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
{
if (LOG.isDebugEnabled())
{
LOG.debug("sendBytes({}, {})",BufferUtil.toDetailString(data),callback);
LOG.debug("sendBytes({}, {})", BufferUtil.toDetailString(data), callback);
}
uncheckedSendFrame(new BinaryFrame().setPayload(data),callback==null?NOOP_CALLBACK:callback);
uncheckedSendFrame(new BinaryFrame().setPayload(data), callback == null ? NOOP_CALLBACK : callback);
}
finally
{
@ -284,17 +289,15 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
}
}
/* ------------------------------------------------------------ */
/** unchecked send
* @param frame
* @param callback
*/
public void uncheckedSendFrame(WebSocketFrame frame, WriteCallback callback)
{
try
{
BatchMode batchMode = BatchMode.OFF;
if (frame.isDataFrame())
batchMode = getBatchMode();
connection.getIOState().assertOutputOpen();
outgoing.outgoingFrame(frame,callback);
outgoing.outgoingFrame(frame, callback, batchMode);
}
catch (IOException e)
{
@ -305,14 +308,14 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
@Override
public void sendPartialBytes(ByteBuffer fragment, boolean isLast) throws IOException
{
boolean first=lockMsg(MsgType.PARTIAL_BINARY);
boolean first = lockMsg(MsgType.PARTIAL_BINARY);
try
{
if (LOG.isDebugEnabled())
{
LOG.debug("sendPartialBytes({}, {})",BufferUtil.toDetailString(fragment),isLast);
LOG.debug("sendPartialBytes({}, {})", BufferUtil.toDetailString(fragment), isLast);
}
DataFrame frame = first?new BinaryFrame():new ContinuationFrame();
DataFrame frame = first ? new BinaryFrame() : new ContinuationFrame();
frame.setPayload(fragment);
frame.setFin(isLast);
blockingWrite(frame);
@ -327,15 +330,15 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
@Override
public void sendPartialString(String fragment, boolean isLast) throws IOException
{
boolean first=lockMsg(MsgType.PARTIAL_TEXT);
boolean first = lockMsg(MsgType.PARTIAL_TEXT);
try
{
if (LOG.isDebugEnabled())
{
LOG.debug("sendPartialString({}, {})",fragment,isLast);
LOG.debug("sendPartialString({}, {})", fragment, isLast);
}
DataFrame frame = first?new TextFrame():new ContinuationFrame();
frame.setPayload(BufferUtil.toBuffer(fragment,StandardCharsets.UTF_8));
DataFrame frame = first ? new TextFrame() : new ContinuationFrame();
frame.setPayload(BufferUtil.toBuffer(fragment, StandardCharsets.UTF_8));
frame.setFin(isLast);
blockingWrite(frame);
}
@ -351,7 +354,7 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
{
if (LOG.isDebugEnabled())
{
LOG.debug("sendPing with {}",BufferUtil.toDetailString(applicationData));
LOG.debug("sendPing with {}", BufferUtil.toDetailString(applicationData));
}
sendAsyncFrame(new PingFrame().setPayload(applicationData));
}
@ -361,7 +364,7 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
{
if (LOG.isDebugEnabled())
{
LOG.debug("sendPong with {}",BufferUtil.toDetailString(applicationData));
LOG.debug("sendPong with {}", BufferUtil.toDetailString(applicationData));
}
sendAsyncFrame(new PongFrame().setPayload(applicationData));
}
@ -375,7 +378,7 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
WebSocketFrame frame = new TextFrame().setPayload(text);
if (LOG.isDebugEnabled())
{
LOG.debug("sendString with {}",BufferUtil.toDetailString(frame.getPayload()));
LOG.debug("sendString with {}", BufferUtil.toDetailString(frame.getPayload()));
}
blockingWrite(frame);
}
@ -394,9 +397,9 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
TextFrame frame = new TextFrame().setPayload(text);
if (LOG.isDebugEnabled())
{
LOG.debug("sendStringByFuture with {}",BufferUtil.toDetailString(frame.getPayload()));
LOG.debug("sendStringByFuture with {}", BufferUtil.toDetailString(frame.getPayload()));
}
return sendAsyncFrame(frame);
return sendAsyncFrame(frame);
}
finally
{
@ -413,13 +416,47 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint
TextFrame frame = new TextFrame().setPayload(text);
if (LOG.isDebugEnabled())
{
LOG.debug("sendString({},{})",BufferUtil.toDetailString(frame.getPayload()),callback);
LOG.debug("sendString({},{})", BufferUtil.toDetailString(frame.getPayload()), callback);
}
uncheckedSendFrame(frame,callback==null?NOOP_CALLBACK:callback);
uncheckedSendFrame(frame, callback == null ? NOOP_CALLBACK : callback);
}
finally
{
unlockMsg(MsgType.ASYNC);
}
}
@Override
public BatchMode getBatchMode()
{
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.
public void setBatchMode(BatchMode batchMode)
{
this.batchMode = batchMode;
}
public void flush() throws IOException
{
lockMsg(MsgType.ASYNC);
try
{
uncheckedSendFrame(FrameFlusher.FLUSH_FRAME, blocker);
blocker.block();
}
finally
{
unlockMsg(MsgType.ASYNC);
}
}
@Override
public String toString()
{
return String.format("%s@%x[batching=%b]", getClass().getSimpleName(), hashCode(), getBatchMode());
}
}

View File

@ -33,6 +33,7 @@ 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;
@ -57,8 +58,8 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
private final URI requestURI;
private final EventDriver websocket;
private final LogicalConnection connection;
private final Executor executor;
private final SessionListener[] sessionListeners;
private final Executor executor;
private ExtensionFactory extensionFactory;
private String protocolVersion;
private Map<String, String[]> parameterMap = new HashMap<>();
@ -69,7 +70,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
private UpgradeRequest upgradeRequest;
private UpgradeResponse upgradeResponse;
public WebSocketSession(URI requestURI, EventDriver websocket, LogicalConnection connection, SessionListener[] sessionListeners)
public WebSocketSession(URI requestURI, EventDriver websocket, LogicalConnection connection, SessionListener... sessionListeners)
{
if (requestURI == null)
{
@ -83,27 +84,26 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
this.executor = connection.getExecutor();
this.outgoingHandler = connection;
this.incomingHandler = websocket;
this.connection.getIOState().addListener(this);
}
@Override
public void close()
{
this.close(StatusCode.NORMAL,null);
this.close(StatusCode.NORMAL, null);
}
@Override
public void close(CloseStatus closeStatus)
{
this.close(closeStatus.getCode(),closeStatus.getPhrase());
this.close(closeStatus.getCode(), closeStatus.getPhrase());
}
@Override
public void close(int statusCode, String reason)
{
connection.close(statusCode,reason);
notifyClose(statusCode,reason);
connection.close(statusCode, reason);
notifyClose(statusCode, reason);
}
/**
@ -115,7 +115,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
connection.disconnect();
// notify of harsh disconnect
notifyClose(StatusCode.NO_CLOSE,"Harsh disconnect");
notifyClose(StatusCode.NO_CLOSE, "Harsh disconnect");
}
public void dispatch(Runnable runnable)
@ -126,25 +126,25 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
@Override
public void dump(Appendable out, String indent) throws IOException
{
super.dump(out,indent);
dumpThis(out);
out.append(indent).append(" +- incomingHandler : ");
if (incomingHandler instanceof Dumpable)
{
((Dumpable)incomingHandler).dump(out,indent + " ");
((Dumpable)incomingHandler).dump(out, indent + " ");
}
else
{
out.append(incomingHandler.toString()).append('\n');
out.append(incomingHandler.toString()).append(System.lineSeparator());
}
out.append(indent).append(" +- outgoingHandler : ");
if (outgoingHandler instanceof Dumpable)
{
((Dumpable)outgoingHandler).dump(out,indent + " ");
((Dumpable)outgoingHandler).dump(out, indent + " ");
}
else
{
out.append(outgoingHandler.toString()).append('\n');
out.append(outgoingHandler.toString()).append(System.lineSeparator());
}
}
@ -273,7 +273,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
{
final int prime = 31;
int result = 1;
result = (prime * result) + ((connection == null)?0:connection.hashCode());
result = (prime * result) + ((connection == null) ? 0 : connection.hashCode());
return result;
}
@ -328,14 +328,14 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
public void notifyClose(int statusCode, String reason)
{
websocket.onClose(new CloseInfo(statusCode,reason));
websocket.onClose(new CloseInfo(statusCode, reason));
}
public void notifyError(Throwable cause)
{
incomingError(cause);
}
@SuppressWarnings("incomplete-switch")
@Override
public void onConnectionStateChange(ConnectionState state)
@ -363,9 +363,9 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
if (ioState.wasAbnormalClose())
{
CloseInfo close = ioState.getCloseInfo();
LOG.debug("Detected abnormal close: {}",close);
LOG.debug("Detected abnormal close: {}", close);
// notify local endpoint
notifyClose(close.getStatusCode(),close.getReason());
notifyClose(close.getStatusCode(), close.getReason());
}
break;
case OPEN:
@ -400,7 +400,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
connection.getIOState().onConnected();
// Connect remote
remote = new WebSocketRemoteEndpoint(connection,outgoingHandler);
remote = new WebSocketRemoteEndpoint(connection, outgoingHandler, getBatchMode());
// Open WebSocket
websocket.openSession(this);
@ -410,7 +410,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
if (LOG.isDebugEnabled())
{
LOG.debug("open -> {}",dump());
LOG.debug("open -> {}", dump());
}
}
@ -450,11 +450,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
List<String> values = entry.getValue();
if (values != null)
{
this.parameterMap.put(entry.getKey(),values.toArray(new String[values.size()]));
this.parameterMap.put(entry.getKey(), values.toArray(new String[values.size()]));
}
else
{
this.parameterMap.put(entry.getKey(),new String[0]);
this.parameterMap.put(entry.getKey(), new String[0]);
}
}
}
@ -468,7 +468,15 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
@Override
public SuspendToken suspend()
{
return connection;
return connection.suspend();
}
/**
* @return the default (initial) value for the batching mode.
*/
public BatchMode getBatchMode()
{
return BatchMode.AUTO;
}
@Override

View File

@ -79,7 +79,7 @@ public class JettyAnnotatedEventDriver extends AbstractEventDriver
{
if (events.onBinary.isStreaming())
{
activeMessage = new MessageInputStream(session.getConnection());
activeMessage = new MessageInputStream();
final MessageAppender msg = activeMessage;
dispatch(new Runnable()
{
@ -181,7 +181,7 @@ public class JettyAnnotatedEventDriver extends AbstractEventDriver
{
if (events.onText.isStreaming())
{
activeMessage = new MessageReader(new MessageInputStream(session.getConnection()));
activeMessage = new MessageReader(new MessageInputStream());
final MessageAppender msg = activeMessage;
dispatch(new Runnable()
{

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
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.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.Extension;
@ -162,10 +163,10 @@ public abstract class AbstractExtension extends ContainerLifeCycle implements Ex
this.nextIncoming.incomingFrame(frame);
}
protected void nextOutgoingFrame(Frame frame, WriteCallback callback)
protected void nextOutgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
{
log.debug("nextOutgoingFrame({})",frame);
this.nextOutgoing.outgoingFrame(frame,callback);
this.nextOutgoing.outgoingFrame(frame,callback, batchMode);
}
public void setBufferPool(ByteBufferPool bufferPool)

View File

@ -22,12 +22,16 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Queue;
import org.eclipse.jetty.util.ConcurrentArrayQueue;
import org.eclipse.jetty.util.IteratingCallback;
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.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.Extension;
@ -47,6 +51,8 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
{
private static final Logger LOG = Log.getLogger(ExtensionStack.class);
private final Queue<FrameEntry> entries = new ConcurrentArrayQueue<>();
private final IteratingCallback flusher = new Flusher();
private final ExtensionFactory factory;
private List<Extension> extensions;
private IncomingFrames nextIncoming;
@ -76,20 +82,20 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
// Wire up Extensions
if ((extensions != null) && (extensions.size() > 0))
{
ListIterator<Extension> eiter = extensions.listIterator();
ListIterator<Extension> exts = extensions.listIterator();
// Connect outgoings
while (eiter.hasNext())
while (exts.hasNext())
{
Extension ext = eiter.next();
Extension ext = exts.next();
ext.setNextOutgoingFrames(nextOutgoing);
nextOutgoing = ext;
}
// Connect incomings
while (eiter.hasPrevious())
while (exts.hasPrevious())
{
Extension ext = eiter.previous();
Extension ext = exts.previous();
ext.setNextIncomingFrames(nextIncoming);
nextIncoming = ext;
}
@ -104,13 +110,13 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
IncomingFrames websocket = getLastIncoming();
OutgoingFrames network = getLastOutgoing();
out.append(indent).append(" +- Stack\n");
out.append(indent).append(" +- Network : ").append(network.toString()).append('\n');
out.append(indent).append(" +- Stack").append(System.lineSeparator());
out.append(indent).append(" +- Network : ").append(network.toString()).append(System.lineSeparator());
for (Extension ext : extensions)
{
out.append(indent).append(" +- Extension: ").append(ext.toString()).append('\n');
out.append(indent).append(" +- Extension: ").append(ext.toString()).append(System.lineSeparator());
}
out.append(indent).append(" +- Websocket: ").append(websocket.toString()).append('\n');
out.append(indent).append(" +- Websocket: ").append(websocket.toString()).append(System.lineSeparator());
}
@ManagedAttribute(name = "Extension List", readonly = true)
@ -247,6 +253,8 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
// Add Extension
extensions.add(ext);
addBean(ext);
LOG.debug("Adding Extension: {}",config);
// Record RSV Claims
@ -263,14 +271,15 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
rsvClaims[2] = ext.getName();
}
}
addBean(extensions);
}
@Override
public void outgoingFrame(Frame frame, WriteCallback callback)
public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
{
nextOutgoing.outgoingFrame(frame,callback);
FrameEntry entry = new FrameEntry(frame, callback, batchMode);
LOG.debug("Queuing {}", entry);
entries.offer(entry);
flusher.iterate();
}
public void setNextIncoming(IncomingFrames nextIncoming)
@ -299,7 +308,8 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
{
StringBuilder s = new StringBuilder();
s.append("ExtensionStack[");
s.append("extensions=");
s.append("queueSize=").append(entries.size());
s.append(",extensions=");
if (extensions == null)
{
s.append("<null>");
@ -331,4 +341,93 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
s.append("]");
return s.toString();
}
private static class FrameEntry
{
private final Frame frame;
private final WriteCallback callback;
private final BatchMode batchMode;
private FrameEntry(Frame frame, WriteCallback callback, BatchMode batchMode)
{
this.frame = frame;
this.callback = callback;
this.batchMode = batchMode;
}
@Override
public String toString()
{
return frame.toString();
}
}
private class Flusher extends IteratingCallback implements WriteCallback
{
private FrameEntry current;
@Override
protected Action process() throws Exception
{
current = entries.poll();
LOG.debug("Processing {}", current);
if (current == null)
return Action.IDLE;
nextOutgoing.outgoingFrame(current.frame, this, current.batchMode);
return Action.SCHEDULED;
}
@Override
protected void completed()
{
// This IteratingCallback never completes.
}
@Override
public void writeSuccess()
{
// Notify first then call succeeded(), otherwise
// write callbacks may be invoked out of order.
notifyCallbackSuccess(current.callback);
succeeded();
}
@Override
public void writeFailed(Throwable x)
{
// Notify first, the call succeeded() to drain the queue.
// We don't want to call failed(x) because that will put
// this flusher into a final state that cannot be exited,
// and the failure of a frame may not mean that the whole
// connection is now invalid.
notifyCallbackFailure(current.callback, x);
succeeded();
}
private void notifyCallbackSuccess(WriteCallback callback)
{
try
{
if (callback != null)
callback.writeSuccess();
}
catch (Throwable x)
{
LOG.debug("Exception while notifying success of callback " + callback, x);
}
}
private void notifyCallbackFailure(WriteCallback callback, Throwable failure)
{
try
{
if (callback != null)
callback.writeFailed(failure);
}
catch (Throwable x)
{
LOG.debug("Exception while notifying failure of callback " + callback, x);
}
}
}
}

View File

@ -22,43 +22,27 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.websocket.api.MessageTooLargeException;
public class ByteAccumulator
{
private static class Buf
{
public Buf(byte[] buffer, int offset, int length)
{
this.buffer = buffer;
this.offset = offset;
this.length = length;
}
byte[] buffer;
int offset;
int length;
}
private final List<Chunk> chunks = new ArrayList<>();
private final int maxSize;
private int length = 0;
private List<Buf> buffers;
public ByteAccumulator(int maxOverallBufferSize)
{
this.maxSize = maxOverallBufferSize;
this.buffers = new ArrayList<>();
}
public void addBuffer(byte buf[], int offset, int length)
public void addChunk(byte buf[], int offset, int length)
{
if (this.length + length > maxSize)
{
throw new MessageTooLargeException("Frame is too large");
}
buffers.add(new Buf(buf,offset,length));
chunks.add(new Chunk(buf, offset, length));
this.length += length;
}
@ -67,17 +51,29 @@ public class ByteAccumulator
return length;
}
public ByteBuffer getByteBuffer(ByteBufferPool pool)
public void transferTo(ByteBuffer buffer)
{
ByteBuffer ret = pool.acquire(length,false);
BufferUtil.clearToFill(ret);
for (Buf buf : buffers)
if (buffer.remaining() < length)
throw new IllegalArgumentException();
int position = buffer.position();
for (Chunk chunk : chunks)
{
ret.put(buf.buffer, buf.offset, buf.length);
buffer.put(chunk.buffer, chunk.offset, chunk.length);
}
BufferUtil.flipToFlush(buffer, position);
}
BufferUtil.flipToFlush(ret,0);
return ret;
private static class Chunk
{
private final byte[] buffer;
private final int offset;
private final int length;
private Chunk(byte[] buffer, int offset, int length)
{
this.buffer = buffer;
this.offset = offset;
this.length = length;
}
}
}

View File

@ -0,0 +1,345 @@
//
// ========================================================================
// 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.extensions.compress;
import java.nio.ByteBuffer;
import java.util.Queue;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import java.util.zip.ZipException;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.ConcurrentArrayQueue;
import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.BadPayloadException;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
import org.eclipse.jetty.websocket.common.frames.DataFrame;
public abstract class CompressExtension extends AbstractExtension
{
protected static final byte[] TAIL_BYTES = new byte[]{0x00, 0x00, (byte)0xFF, (byte)0xFF};
private static final Logger LOG = Log.getLogger(CompressExtension.class);
private final Queue<FrameEntry> entries = new ConcurrentArrayQueue<>();
private final IteratingCallback flusher = new Flusher();
private final Deflater compressor;
private final Inflater decompressor;
protected CompressExtension()
{
compressor = new Deflater(Deflater.BEST_COMPRESSION, true);
decompressor = new Inflater(true);
}
public Deflater getDeflater()
{
return compressor;
}
public Inflater getInflater()
{
return decompressor;
}
/**
* Indicates use of RSV1 flag for indicating deflation is in use.
*/
@Override
public boolean isRsv1User()
{
return true;
}
protected void forwardIncoming(Frame frame, ByteAccumulator accumulator)
{
DataFrame newFrame = new DataFrame(frame);
// Unset RSV1 since it's not compressed anymore.
newFrame.setRsv1(false);
ByteBuffer buffer = getBufferPool().acquire(accumulator.getLength(), false);
try
{
BufferUtil.flipToFill(buffer);
accumulator.transferTo(buffer);
newFrame.setPayload(buffer);
nextIncomingFrame(newFrame);
}
finally
{
getBufferPool().release(buffer);
}
}
protected ByteAccumulator decompress(byte[] input)
{
// Since we don't track text vs binary vs continuation state, just grab whatever is the greater value.
int maxSize = Math.max(getPolicy().getMaxTextMessageSize(), getPolicy().getMaxBinaryMessageBufferSize());
ByteAccumulator accumulator = new ByteAccumulator(maxSize);
decompressor.setInput(input, 0, input.length);
LOG.debug("Decompressing {} bytes", input.length);
try
{
// It is allowed to send DEFLATE blocks with BFINAL=1.
// For such blocks, getRemaining() will be > 0 but finished()
// will be true, so we need to check for both.
// When BFINAL=0, finished() will always be false and we only
// check the remaining bytes.
while (decompressor.getRemaining() > 0 && !decompressor.finished())
{
byte[] output = new byte[Math.min(input.length * 2, 32 * 1024)];
int decompressed = decompressor.inflate(output);
if (decompressed == 0)
{
if (decompressor.needsInput())
{
throw new BadPayloadException("Unable to inflate frame, not enough input on frame");
}
if (decompressor.needsDictionary())
{
throw new BadPayloadException("Unable to inflate frame, frame erroneously says it needs a dictionary");
}
}
else
{
accumulator.addChunk(output, 0, decompressed);
}
}
LOG.debug("Decompressed {}->{} bytes", input.length, accumulator.getLength());
return accumulator;
}
catch (DataFormatException x)
{
throw new BadPayloadException(x);
}
}
@Override
public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
{
// We use a queue and an IteratingCallback to handle concurrency.
// We must compress and write atomically, otherwise the compression
// context on the other end gets confused.
if (flusher.isFailed())
{
notifyCallbackFailure(callback, new ZipException());
return;
}
FrameEntry entry = new FrameEntry(frame, callback, batchMode);
LOG.debug("Queuing {}", entry);
entries.offer(entry);
flusher.iterate();
}
protected void notifyCallbackSuccess(WriteCallback callback)
{
try
{
if (callback != null)
callback.writeSuccess();
}
catch (Throwable x)
{
LOG.debug("Exception while notifying success of callback " + callback, x);
}
}
protected void notifyCallbackFailure(WriteCallback callback, Throwable failure)
{
try
{
if (callback != null)
callback.writeFailed(failure);
}
catch (Throwable x)
{
LOG.debug("Exception while notifying failure of callback " + callback, x);
}
}
@Override
public String toString()
{
return getClass().getSimpleName();
}
private static class FrameEntry
{
private final Frame frame;
private final WriteCallback callback;
private final BatchMode batchMode;
private FrameEntry(Frame frame, WriteCallback callback, BatchMode batchMode)
{
this.frame = frame;
this.callback = callback;
this.batchMode = batchMode;
}
@Override
public String toString()
{
return frame.toString();
}
}
private class Flusher extends IteratingCallback implements WriteCallback
{
private FrameEntry current;
private ByteBuffer payload;
private boolean finished = true;
@Override
protected Action process() throws Exception
{
if (finished)
{
current = entries.poll();
LOG.debug("Processing {}", current);
if (current == null)
return Action.IDLE;
deflate(current);
}
else
{
compress(current, false);
}
return Action.SCHEDULED;
}
private void deflate(FrameEntry entry)
{
Frame frame = entry.frame;
BatchMode batchMode = entry.batchMode;
if (OpCode.isControlFrame(frame.getOpCode()) || !frame.hasPayload())
{
nextOutgoingFrame(frame, this, batchMode);
return;
}
compress(entry, true);
}
private void compress(FrameEntry entry, boolean first)
{
// Get a chunk of the payload to avoid to blow
// the heap if the payload is a huge mapped file.
Frame frame = entry.frame;
ByteBuffer data = frame.getPayload();
int remaining = data.remaining();
int inputLength = Math.min(remaining, 32 * 1024);
LOG.debug("Compressing {}: {} bytes in {} bytes chunk", entry, remaining, inputLength);
// Avoid to copy the bytes if the ByteBuffer
// is backed by an array.
int inputOffset;
byte[] input;
if (data.hasArray())
{
input = data.array();
int position = data.position();
inputOffset = position + data.arrayOffset();
data.position(position + inputLength);
}
else
{
input = new byte[inputLength];
inputOffset = 0;
data.get(input, 0, inputLength);
}
finished = inputLength == remaining;
compressor.setInput(input, inputOffset, inputLength);
// Use an additional space in case the content is not compressible.
byte[] output = new byte[inputLength + 64];
int outputOffset = 0;
int outputLength = 0;
while (true)
{
int space = output.length - outputOffset;
int compressed = compressor.deflate(output, outputOffset, space, Deflater.SYNC_FLUSH);
outputLength += compressed;
if (compressed < space)
{
// Everything was compressed.
break;
}
else
{
// The compressed output is bigger than the uncompressed input.
byte[] newOutput = new byte[output.length * 2];
System.arraycopy(output, 0, newOutput, 0, output.length);
outputOffset += output.length;
output = newOutput;
}
}
// Skip the last tail bytes bytes generated by SYNC_FLUSH.
payload = ByteBuffer.wrap(output, 0, outputLength - TAIL_BYTES.length);
LOG.debug("Compressed {}: {}->{} chunk bytes", entry, inputLength, outputLength);
boolean continuation = frame.getType().isContinuation() || !first;
DataFrame chunk = new DataFrame(frame, continuation);
chunk.setRsv1(true);
chunk.setPayload(payload);
boolean fin = frame.isFin() && finished;
chunk.setFin(fin);
nextOutgoingFrame(chunk, this, entry.batchMode);
}
@Override
protected void completed()
{
// This IteratingCallback never completes.
}
@Override
public void writeSuccess()
{
if (finished)
notifyCallbackSuccess(current.callback);
succeeded();
}
@Override
public void writeFailed(Throwable x)
{
notifyCallbackFailure(current.callback, x);
// If something went wrong, very likely the compression context
// will be invalid, so we need to fail this IteratingCallback.
failed(x);
// Now no more frames can be queued, fail those in the queue.
FrameEntry entry;
while ((entry = entries.poll()) != null)
notifyCallbackFailure(entry.callback, x);
}
}
}

View File

@ -19,39 +19,17 @@
package org.eclipse.jetty.websocket.common.extensions.compress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.BadPayloadException;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
import org.eclipse.jetty.websocket.common.frames.DataFrame;
/**
* Implementation of the <a href="https://tools.ietf.org/id/draft-tyoshino-hybi-websocket-perframe-deflate-05.txt">deflate-frame</a> extension seen out in the
* wild.
* Implementation of the
* <a href="https://tools.ietf.org/id/draft-tyoshino-hybi-websocket-perframe-deflate.txt">deflate-frame</a>
* extension seen out in the wild.
*/
public class DeflateFrameExtension extends AbstractExtension
public class DeflateFrameExtension extends CompressExtension
{
private static final boolean BFINAL_HACK = Boolean.parseBoolean(System.getProperty("jetty.websocket.bfinal.hack","true"));
private static final Logger LOG = Log.getLogger(DeflateFrameExtension.class);
private static final int OVERHEAD = 64;
/** Tail Bytes per Spec */
private static final byte[] TAIL = new byte[] { 0x00, 0x00, (byte)0xFF, (byte)0xFF };
private int bufferSize = 64 * 1024;
private Deflater compressor;
private Inflater decompressor;
@Override
public String getName()
{
@ -61,194 +39,22 @@ public class DeflateFrameExtension extends AbstractExtension
@Override
public void incomingFrame(Frame frame)
{
if (OpCode.isControlFrame(frame.getOpCode()) || !frame.isRsv1())
// Incoming frames are always non concurrent because
// they are read and parsed with a single thread, and
// therefore there is no need for synchronization.
if (OpCode.isControlFrame(frame.getOpCode()) || !frame.isRsv1() || !frame.hasPayload())
{
// Cannot modify incoming control frames or ones with RSV1 set.
nextIncomingFrame(frame);
return;
}
if (!frame.hasPayload())
{
// no payload? nothing to do.
nextIncomingFrame(frame);
return;
}
// Prime the decompressor
ByteBuffer payload = frame.getPayload();
int inlen = payload.remaining();
byte compressed[] = new byte[inlen + TAIL.length];
payload.get(compressed,0,inlen);
System.arraycopy(TAIL,0,compressed,inlen,TAIL.length);
int remaining = payload.remaining();
byte[] input = new byte[remaining + TAIL_BYTES.length];
payload.get(input, 0, remaining);
System.arraycopy(TAIL_BYTES, 0, input, remaining, TAIL_BYTES.length);
// Since we don't track text vs binary vs continuation state, just grab whatever is the greater value.
int maxSize = Math.max(getPolicy().getMaxTextMessageSize(),getPolicy().getMaxBinaryMessageBufferSize());
ByteAccumulator accumulator = new ByteAccumulator(maxSize);
DataFrame out = new DataFrame(frame);
out.setRsv1(false); // Unset RSV1
synchronized (decompressor)
{
decompressor.setInput(compressed,0,compressed.length);
// Perform decompression
while (decompressor.getRemaining() > 0 && !decompressor.finished())
{
byte outbuf[] = new byte[Math.min(inlen * 2,bufferSize)];
try
{
int len = decompressor.inflate(outbuf);
if (len == 0)
{
if (decompressor.needsInput())
{
throw new BadPayloadException("Unable to inflate frame, not enough input on frame");
}
if (decompressor.needsDictionary())
{
throw new BadPayloadException("Unable to inflate frame, frame erroneously says it needs a dictionary");
}
}
if (len > 0)
{
accumulator.addBuffer(outbuf,0,len);
}
}
catch (DataFormatException e)
{
LOG.warn(e);
throw new BadPayloadException(e);
}
}
}
// Forward on the frame
out.setPayload(accumulator.getByteBuffer(getBufferPool()));
nextIncomingFrame(out);
}
/**
* Indicates use of RSV1 flag for indicating deflation is in use.
* <p>
* Also known as the "COMP" framing header bit
*/
@Override
public boolean isRsv1User()
{
return true;
}
@Override
public void outgoingFrame(Frame frame, WriteCallback callback)
{
if (OpCode.isControlFrame(frame.getOpCode()))
{
// skip, cannot compress control frames.
nextOutgoingFrame(frame,callback);
return;
}
if (!frame.hasPayload())
{
// pass through, nothing to do
nextOutgoingFrame(frame,callback);
return;
}
if (LOG.isDebugEnabled())
{
LOG.debug("outgoingFrame({}, {}) - {}",OpCode.name(frame.getOpCode()),callback != null?callback.getClass().getSimpleName():"<null>",
BufferUtil.toDetailString(frame.getPayload()));
}
// Prime the compressor
byte uncompressed[] = BufferUtil.toArray(frame.getPayload());
List<DataFrame> dframes = new ArrayList<>();
synchronized (compressor)
{
// Perform the compression
if (!compressor.finished())
{
compressor.setInput(uncompressed,0,uncompressed.length);
byte compressed[] = new byte[uncompressed.length + OVERHEAD];
while (!compressor.needsInput())
{
int len = compressor.deflate(compressed,0,compressed.length,Deflater.SYNC_FLUSH);
ByteBuffer outbuf = getBufferPool().acquire(len,true);
BufferUtil.clearToFill(outbuf);
if (len > 0)
{
outbuf.put(compressed,0,len - 4);
}
BufferUtil.flipToFlush(outbuf,0);
if (len > 0 && BFINAL_HACK)
{
/*
* Per the spec, it says that BFINAL 1 or 0 are allowed.
*
* However, Java always uses BFINAL 1, whereas the browsers Chromium and Safari fail to decompress when it encounters BFINAL 1.
*
* This hack will always set BFINAL 0
*/
byte b0 = outbuf.get(0);
if ((b0 & 1) != 0) // if BFINAL 1
{
outbuf.put(0,(b0 ^= 1)); // flip bit to BFINAL 0
}
}
DataFrame out = new DataFrame(frame);
out.setRsv1(true);
out.setBufferPool(getBufferPool());
out.setPayload(outbuf);
if (!compressor.needsInput())
{
// this is fragmented
out.setFin(false);
}
dframes.add(out);
}
}
}
// notify outside of synchronize
for (DataFrame df : dframes)
{
if (df.isFin())
{
nextOutgoingFrame(df,callback);
}
else
{
// non final frames have no callback
nextOutgoingFrame(df,null);
}
}
}
@Override
public void setConfig(ExtensionConfig config)
{
super.setConfig(config);
boolean nowrap = true;
compressor = new Deflater(Deflater.BEST_COMPRESSION,nowrap);
compressor.setStrategy(Deflater.DEFAULT_STRATEGY);
decompressor = new Inflater(nowrap);
}
@Override
public String toString()
{
return this.getClass().getSimpleName() + "[]";
forwardIncoming(frame, decompress(input));
}
}

View File

@ -19,49 +19,29 @@
package org.eclipse.jetty.websocket.common.extensions.compress;
import java.nio.ByteBuffer;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.BadPayloadException;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
import org.eclipse.jetty.websocket.common.frames.DataFrame;
/**
* Per Message Deflate Compression extension for WebSocket.
* <p>
* <p/>
* Attempts to follow <a href="https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-12">draft-ietf-hybi-permessage-compression-12</a>
*/
public class PerMessageDeflateExtension extends AbstractExtension
public class PerMessageDeflateExtension extends CompressExtension
{
private static final boolean BFINAL_HACK = Boolean.parseBoolean(System.getProperty("jetty.websocket.bfinal.hack","true"));
private static final Logger LOG = Log.getLogger(PerMessageDeflateExtension.class);
private static final int OVERHEAD = 64;
/** Tail Bytes per Spec */
private static final byte[] TAIL = new byte[]
{ 0x00, 0x00, (byte)0xFF, (byte)0xFF };
private ExtensionConfig configRequested;
private ExtensionConfig configNegotiated;
private Deflater compressor;
private Inflater decompressor;
private boolean incomingCompressed = false;
private boolean outgoingCompressed = false;
/**
* Context Takeover Control.
* <p>
* If true, the same LZ77 window is used between messages. Can be overridden with extension parameters.
*/
private boolean incomingContextTakeover = true;
private boolean outgoingContextTakeover = true;
private boolean incomingCompressed;
@Override
public String getName()
@ -70,203 +50,35 @@ public class PerMessageDeflateExtension extends AbstractExtension
}
@Override
public synchronized void incomingFrame(Frame frame)
public void incomingFrame(Frame frame)
{
switch (frame.getOpCode())
{
case OpCode.BINARY: // fall-thru
case OpCode.TEXT:
incomingCompressed = frame.isRsv1();
break;
case OpCode.CONTINUATION:
if (!incomingCompressed)
{
nextIncomingFrame(frame);
}
break;
default:
// All others are assumed to be control frames
nextIncomingFrame(frame);
return;
}
// Incoming frames are always non concurrent because
// they are read and parsed with a single thread, and
// therefore there is no need for synchronization.
if (!incomingCompressed || !frame.hasPayload())
// This extension requires the RSV1 bit set only in the first frame.
// Subsequent continuation frames don't have RSV1 set, but are compressed.
if (frame.getType().isData())
incomingCompressed = frame.isRsv1();
if (OpCode.isControlFrame(frame.getOpCode()) || !frame.hasPayload() || !incomingCompressed)
{
// nothing to do with this frame
nextIncomingFrame(frame);
return;
}
// Prime the decompressor
boolean appendTail = frame.isFin();
ByteBuffer payload = frame.getPayload();
int inlen = payload.remaining();
byte compressed[] = null;
int remaining = payload.remaining();
byte[] input = new byte[remaining + (appendTail ? TAIL_BYTES.length : 0)];
payload.get(input, 0, remaining);
if (appendTail)
System.arraycopy(TAIL_BYTES, 0, input, remaining, TAIL_BYTES.length);
forwardIncoming(frame, decompress(input));
if (frame.isFin())
{
compressed = new byte[inlen + TAIL.length];
payload.get(compressed,0,inlen);
System.arraycopy(TAIL,0,compressed,inlen,TAIL.length);
incomingCompressed = false;
}
else
{
compressed = new byte[inlen];
payload.get(compressed,0,inlen);
}
decompressor.setInput(compressed,0,compressed.length);
// Since we don't track text vs binary vs continuation state, just grab whatever is the greater value.
int maxSize = Math.max(getPolicy().getMaxTextMessageSize(),getPolicy().getMaxBinaryMessageBufferSize());
ByteAccumulator accumulator = new ByteAccumulator(maxSize);
DataFrame out = new DataFrame(frame);
out.setRsv1(false); // Unset RSV1
// Perform decompression
while (decompressor.getRemaining() > 0 && !decompressor.finished())
{
byte outbuf[] = new byte[inlen];
try
{
int len = decompressor.inflate(outbuf);
if (len == 0)
{
if (decompressor.needsInput())
{
throw new BadPayloadException("Unable to inflate frame, not enough input on frame");
}
if (decompressor.needsDictionary())
{
throw new BadPayloadException("Unable to inflate frame, frame erroneously says it needs a dictionary");
}
}
if (len > 0)
{
accumulator.addBuffer(outbuf,0,len);
}
}
catch (DataFormatException e)
{
LOG.warn(e);
throw new BadPayloadException(e);
}
}
// Forward on the frame
out.setPayload(accumulator.getByteBuffer(getBufferPool()));
nextIncomingFrame(out);
}
/**
* Indicates use of RSV1 flag for indicating deflation is in use.
*/
@Override
public boolean isRsv1User()
{
return true;
}
@Override
public synchronized void outgoingFrame(Frame frame, WriteCallback callback)
{
if (OpCode.isControlFrame(frame.getOpCode()))
{
// skip, cannot compress control frames.
nextOutgoingFrame(frame,callback);
return;
}
if (!frame.hasPayload())
{
// pass through, nothing to do
nextOutgoingFrame(frame,callback);
return;
}
if (LOG.isDebugEnabled())
{
LOG.debug("outgoingFrame({}, {}) - {}",OpCode.name(frame.getOpCode()),callback != null?callback.getClass().getSimpleName():"<null>",
BufferUtil.toDetailString(frame.getPayload()));
}
// Prime the compressor
byte uncompressed[] = BufferUtil.toArray(frame.getPayload());
// Perform the compression
if (!compressor.finished())
{
compressor.setInput(uncompressed,0,uncompressed.length);
byte compressed[] = new byte[uncompressed.length + OVERHEAD];
while (!compressor.needsInput())
{
int len = compressor.deflate(compressed,0,compressed.length,Deflater.SYNC_FLUSH);
ByteBuffer outbuf = getBufferPool().acquire(len,true);
BufferUtil.clearToFill(outbuf);
if (len > 0)
{
if (len > 4)
{
// Test for the 4 tail octets (0x00 0x00 0xff 0xff)
int idx = len - 4;
boolean found = true;
for (int n = 0; n < TAIL.length; n++)
{
if (compressed[idx + n] != TAIL[n])
{
found = false;
break;
}
}
if (found)
{
len = len - 4;
}
}
outbuf.put(compressed,0,len);
}
BufferUtil.flipToFlush(outbuf,0);
if (len > 0 && BFINAL_HACK)
{
/*
* Per the spec, it says that BFINAL 1 or 0 are allowed.
*
* However, Java always uses BFINAL 1, whereas the browsers Chromium and Safari fail to decompress when it encounters BFINAL 1.
*
* This hack will always set BFINAL 0
*/
byte b0 = outbuf.get(0);
if ((b0 & 1) != 0) // if BFINAL 1
{
outbuf.put(0,(b0 ^= 1)); // flip bit to BFINAL 0
}
}
DataFrame out = new DataFrame(frame,outgoingCompressed);
out.setRsv1(true);
out.setBufferPool(getBufferPool());
out.setPayload(outbuf);
if (!compressor.needsInput())
{
// this is fragmented
out.setFin(false);
nextOutgoingFrame(out,null); // non final frames have no callback
}
else
{
// pass through the callback
nextOutgoingFrame(out,callback);
}
outgoingCompressed = !out.isFin();
}
}
}
@Override
@ -275,22 +87,20 @@ public class PerMessageDeflateExtension extends AbstractExtension
if (frame.isFin() && !incomingContextTakeover)
{
LOG.debug("Incoming Context Reset");
decompressor.reset();
getInflater().reset();
}
super.nextIncomingFrame(frame);
}
@Override
protected void nextOutgoingFrame(Frame frame, WriteCallback callback)
protected void nextOutgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
{
if (frame.isFin() && !outgoingContextTakeover)
{
LOG.debug("Outgoing Context Reset");
compressor.reset();
getDeflater().reset();
}
super.nextOutgoingFrame(frame,callback);
super.nextOutgoingFrame(frame, callback, batchMode);
}
@Override
@ -299,23 +109,20 @@ public class PerMessageDeflateExtension extends AbstractExtension
configRequested = new ExtensionConfig(config);
configNegotiated = new ExtensionConfig(config.getName());
boolean nowrap = true;
compressor = new Deflater(Deflater.BEST_COMPRESSION,nowrap);
compressor.setStrategy(Deflater.DEFAULT_STRATEGY);
decompressor = new Inflater(nowrap);
for (String key : config.getParameterKeys())
{
key = key.trim();
switch (key)
{
case "client_max_window_bits": // fallthru
case "client_max_window_bits":
case "server_max_window_bits":
{
// Not supported by Jetty
// Don't negotiate these parameters
break;
}
case "client_no_context_takeover":
{
configNegotiated.setParameter("client_no_context_takeover");
switch (getPolicy().getBehavior())
{
@ -327,7 +134,9 @@ public class PerMessageDeflateExtension extends AbstractExtension
break;
}
break;
}
case "server_no_context_takeover":
{
configNegotiated.setParameter("server_no_context_takeover");
switch (getPolicy().getBehavior())
{
@ -339,6 +148,11 @@ public class PerMessageDeflateExtension extends AbstractExtension
break;
}
break;
}
default:
{
throw new IllegalArgumentException();
}
}
}
@ -348,11 +162,9 @@ public class PerMessageDeflateExtension extends AbstractExtension
@Override
public String toString()
{
StringBuilder str = new StringBuilder();
str.append(this.getClass().getSimpleName());
str.append("[requested=").append(configRequested.getParameterizedName());
str.append(",negotiated=").append(configNegotiated.getParameterizedName());
str.append(']');
return str.toString();
return String.format("%s[requested=%s,negotiated=%s]",
getClass().getSimpleName(),
configRequested.getParameterizedName(),
configNegotiated.getParameterizedName());
}
}

View File

@ -29,10 +29,4 @@ public class XWebkitDeflateFrameExtension extends DeflateFrameExtension
{
return "x-webkit-deflate-frame";
}
@Override
public String toString()
{
return this.getClass().getSimpleName() + "[]";
}
}

View File

@ -20,7 +20,13 @@ package org.eclipse.jetty.websocket.common.extensions.fragment;
import java.nio.ByteBuffer;
import java.util.Queue;
import org.eclipse.jetty.util.ConcurrentArrayQueue;
import org.eclipse.jetty.util.IteratingCallback;
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.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.Frame;
@ -33,85 +39,167 @@ import org.eclipse.jetty.websocket.common.frames.DataFrame;
*/
public class FragmentExtension extends AbstractExtension
{
private int maxLength = -1;
private static final Logger LOG = Log.getLogger(FragmentExtension.class);
private final Queue<FrameEntry> entries = new ConcurrentArrayQueue<>();
private final IteratingCallback flusher = new Flusher();
private int maxLength;
@Override
public String getName()
{
return "fragment";
}
@Override
public void incomingError(Throwable e)
{
// Pass thru
nextIncomingError(e);
}
@Override
public void incomingFrame(Frame frame)
{
// Pass thru
nextIncomingFrame(frame);
}
@Override
public void outgoingFrame(Frame frame, WriteCallback callback)
public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
{
if (OpCode.isControlFrame(frame.getOpCode()))
ByteBuffer payload = frame.getPayload();
int length = payload != null ? payload.remaining() : 0;
if (OpCode.isControlFrame(frame.getOpCode()) || maxLength <= 0 || length <= maxLength)
{
// Cannot fragment Control Frames
nextOutgoingFrame(frame,callback);
nextOutgoingFrame(frame, callback, batchMode);
return;
}
int length = frame.getPayloadLength();
ByteBuffer payload = frame.getPayload().slice();
int originalLimit = payload.limit();
int currentPosition = payload.position();
if (maxLength <= 0)
{
// output original frame
nextOutgoingFrame(frame,callback);
return;
}
boolean continuation = false;
// break apart payload based on maxLength rules
while (length > maxLength)
{
DataFrame frag = new DataFrame(frame,continuation);
frag.setFin(false); // always false here
payload.position(currentPosition);
payload.limit(Math.min(payload.position() + maxLength,originalLimit));
frag.setPayload(payload);
// no callback for beginning and middle parts
nextOutgoingFrame(frag,null);
length -= maxLength;
continuation = true;
currentPosition = payload.limit();
}
// write remaining
DataFrame frag = new DataFrame(frame,continuation);
frag.setFin(frame.isFin()); // use original fin
payload.position(currentPosition);
payload.limit(originalLimit);
frag.setPayload(payload);
nextOutgoingFrame(frag,callback);
FrameEntry entry = new FrameEntry(frame, callback, batchMode);
LOG.debug("Queuing {}", entry);
entries.offer(entry);
flusher.iterate();
}
@Override
public void setConfig(ExtensionConfig config)
{
super.setConfig(config);
maxLength = config.getParameter("maxLength", -1);
}
maxLength = config.getParameter("maxLength",maxLength);
private static class FrameEntry
{
private final Frame frame;
private final WriteCallback callback;
private final BatchMode batchMode;
private FrameEntry(Frame frame, WriteCallback callback, BatchMode batchMode)
{
this.frame = frame;
this.callback = callback;
this.batchMode = batchMode;
}
@Override
public String toString()
{
return frame.toString();
}
}
private class Flusher extends IteratingCallback implements WriteCallback
{
private FrameEntry current;
private boolean finished = true;
@Override
protected Action process() throws Exception
{
if (finished)
{
current = entries.poll();
LOG.debug("Processing {}", current);
if (current == null)
return Action.IDLE;
fragment(current, true);
}
else
{
fragment(current, false);
}
return Action.SCHEDULED;
}
private void fragment(FrameEntry entry, boolean first)
{
Frame frame = entry.frame;
ByteBuffer payload = frame.getPayload();
int remaining = payload.remaining();
int length = Math.min(remaining, maxLength);
finished = length == remaining;
boolean continuation = frame.getType().isContinuation() || !first;
DataFrame fragment = new DataFrame(frame, continuation);
boolean fin = frame.isFin() && finished;
fragment.setFin(fin);
int limit = payload.limit();
int newLimit = payload.position() + length;
payload.limit(newLimit);
ByteBuffer payloadFragment = payload.slice();
payload.limit(limit);
fragment.setPayload(payloadFragment);
LOG.debug("Fragmented {}->{}", frame, fragment);
payload.position(newLimit);
nextOutgoingFrame(fragment, this, entry.batchMode);
}
@Override
protected void completed()
{
// This IteratingCallback never completes.
}
@Override
public void writeSuccess()
{
// Notify first then call succeeded(), otherwise
// write callbacks may be invoked out of order.
notifyCallbackSuccess(current.callback);
succeeded();
}
@Override
public void writeFailed(Throwable x)
{
// Notify first, the call succeeded() to drain the queue.
// We don't want to call failed(x) because that will put
// this flusher into a final state that cannot be exited,
// and the failure of a frame may not mean that the whole
// connection is now invalid.
notifyCallbackFailure(current.callback, x);
succeeded();
}
private void notifyCallbackSuccess(WriteCallback callback)
{
try
{
if (callback != null)
callback.writeSuccess();
}
catch (Throwable x)
{
LOG.debug("Exception while notifying success of callback " + callback, x);
}
}
private void notifyCallbackFailure(WriteCallback callback, Throwable failure)
{
try
{
if (callback != null)
callback.writeFailed(failure);
}
catch (Throwable x)
{
LOG.debug("Exception while notifying failure of callback " + callback, x);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More