Merge remote-tracking branch 'origin/jetty-9.4.x'

This commit is contained in:
Greg Wilkins 2016-11-18 11:19:40 +11:00
commit bf3ea9ae44
18 changed files with 635 additions and 292 deletions

View File

@ -1,6 +1,7 @@
# GPG Release Key Fingerprints
Jan Bartel AED5 EE6C 45D0 FE8D 5D1B 164F 27DE D4BF 6216 DB
Simone Bordet 8B09 6546 B1A8 F026 56B1 5D3B 1677 D141 BCF3 58
Joakim Erdfelt <joakim@erdfelt.com> BFBB 21C2 46D7 7768 3628 7A48 A04E 0C74 ABB3 5FEA
Joakim Erdfelt <joakim@apache.org> B59B 67FD 7904 9843 67F9 3180 0818 D9D6 8FB6 7BAC
Jesse McConnell 2A68 4B57 436A 81FA 8706 B53C 61C3 351A 438A 3B7D
Jan Bartel <janb@mortbay.com> AED5 EE6C 45D0 FE8D 5D1B 164F 27DE D4BF 6216 DB8F
Jesse McConnell <jesse.mcconnell@gmail.com> 2A68 4B57 436A 81FA 8706 B53C 61C3 351A 438A 3B7D
Joakim Erdfelt <joakim.erdfelt@gmail.com> 5989 BAF7 6217 B843 D66B E55B 2D0E 1FB8 FE4B 68B4
Joakim Erdfelt <joakim@apache.org> B59B 67FD 7904 9843 67F9 3180 0818 D9D6 8FB6 7BAC
Joakim Erdfelt <joakim@erdfelt.com> BFBB 21C2 46D7 7768 3628 7A48 A04E 0C74 ABB3 5FEA
Simone Bordet <simone.bordet@gmail.com> 8B09 6546 B1A8 F026 56B1 5D3B 1677 D141 BCF3 584D

View File

@ -42,6 +42,7 @@ public class DumpServlet extends HttpServlet
out.println("<h1>DumpServlet</h1>");
out.println("<pre>");
out.println("requestURI=" + request.getRequestURI());
out.println("requestURL=" + request.getRequestURL().toString());
out.println("contextPath=" + request.getContextPath());
out.println("servletPath=" + request.getServletPath());
out.println("pathInfo=" + request.getPathInfo());

View File

@ -0,0 +1,84 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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.embedded;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@SuppressWarnings("serial")
public class HelloSessionServlet extends HttpServlet
{
public HelloSessionServlet() {}
@Override
protected void doGet( HttpServletRequest request,
HttpServletResponse response ) throws ServletException,
IOException
{
response.setContentType("text/html");
response.setStatus(HttpServletResponse.SC_OK);
response.addHeader("Cache-Control","no-cache");
HttpSession session = request.getSession();
String message;
String link;
String greeting = request.getParameter("greeting");
if (greeting != null)
{
session.setAttribute("greeting", greeting);
message = "New greeting '" + greeting + "' set in session.";
link = "Click <a href=\"/\">here</a> to use the new greeting from the session.";
}
else
{
greeting = (String)session.getAttribute("greeting");
if (greeting != null)
{
message = "Greeting '" + greeting + "' set from session.";
}
else
{
greeting = "Hello";
message = "Greeting '" + greeting + "' is default.";
}
link = "Click <a href=\"/?greeting=Hola\">here</a> to set a new greeting.";
}
PrintWriter out = response.getWriter();
out.println("<h1>" + greeting + " from HelloSessionServlet</h1>");
out.println("<p>" + message + "</p>");
out.println("<pre>");
out.println("session.getId() = " +session.getId());
out.println("session.isNew() = " +session.isNew());
out.println("</pre>");
out.println("<p>" + link + "</p>");
}
}

View File

@ -0,0 +1,63 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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.embedded;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.session.DefaultSessionCache;
import org.eclipse.jetty.server.session.NullSessionDataStore;
import org.eclipse.jetty.server.session.SessionCache;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
public class OneServletContextWithSession
{
public static void main(String[] args) throws Exception
{
Server server = new Server(8080);
// Create a ServletContext, with a session handler enabled.
ServletContextHandler context = new ServletContextHandler(
ServletContextHandler.SESSIONS);
context.setContextPath("/");
context.setResourceBase(System.getProperty("java.io.tmpdir"));
server.setHandler(context);
// Access the SessionHandler from the context.
SessionHandler sessions = context.getSessionHandler();
// Explicitly set Session Cache and null Datastore.
// This is normally done by default,
// but is done explicitly here for demonstration.
// If more than one context is to be deployed, it is
// simpler to use SessionCacheFactory and/or
// SessionDataStoreFactory instances set as beans on
// the server.
SessionCache cache = new DefaultSessionCache(sessions);
cache.setSessionDataStore(new NullSessionDataStore());
sessions.setSessionCache(cache);
// Servlet to read/set the greeting stored in the session.
// Can be accessed using http://localhost:8080/hello
context.addServlet(HelloSessionServlet.class, "/");
server.start();
server.join();
}
}

View File

@ -39,10 +39,6 @@
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<!--
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.servlet</artifactId>
-->
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
@ -83,5 +79,6 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</dependencies>
</project>

View File

@ -222,13 +222,14 @@ public abstract class AbstractNCSARequestLog extends AbstractLifeCycle implement
}
/**
* @param request request object
* @param b StringBuilder to write to
* @throws IOException if unable to append extended log
* @deprecated override {@link #logExtended(StringBuilder, Request, Response)} instead
* Writes extended request and response information to the output stream.
*
* @param b StringBuilder to write to
* @param request request object
* @param response response object
* @throws IOException if unable to log the extended information
*/
@Deprecated
protected void logExtended(Request request, StringBuilder b) throws IOException
protected void logExtended(StringBuilder b, Request request, Response response) throws IOException
{
String referer = request.getHeader(HttpHeader.REFERER.toString());
if (referer == null)
@ -251,19 +252,6 @@ public abstract class AbstractNCSARequestLog extends AbstractLifeCycle implement
}
}
/**
* Writes extended request and response information to the output stream.
*
* @param b StringBuilder to write to
* @param request request object
* @param response response object
* @throws IOException if unable to log the extended information
*/
protected void logExtended(StringBuilder b, Request request, Response response) throws IOException
{
logExtended(request, b);
}
/**
* Set request paths that will not be logged.
*

View File

@ -1681,7 +1681,9 @@ public class Request implements HttpServletRequest
{
_metaData=request;
HttpURI uri = request.getURI();
_originalUri = uri.toString();
_originalUri = uri.isAbsolute() && request.getHttpVersion()!=HttpVersion.HTTP_2
? uri.toString()
: uri.getPathQuery();
if (uri.getScheme()==null)
uri.setScheme("http");
@ -1817,7 +1819,6 @@ public class Request implements HttpServletRequest
_requestAttributeListeners.remove(listener);
}
/* ------------------------------------------------------------ */
public void setAsyncSupported(boolean supported,String source)
{

View File

@ -91,6 +91,51 @@ public class RequestLogTest
assertThat(log,containsString("GET http://host:80/foo?data=1 HTTP/1.0\" 200 "));
}
@Test
public void testHTTP10Host() throws Exception
{
_connector.getResponse(
"GET /foo?name=value HTTP/1.0\n"+
"Host: servername\n"+
"\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
assertThat(log,containsString("GET /foo?name=value"));
assertThat(log,containsString(" 200 "));
}
@Test
public void testHTTP11() throws Exception
{
_connector.getResponse(
"GET /foo?name=value HTTP/1.1\n"+
"Host: servername\n"+
"\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
assertThat(log,containsString("GET /foo?name=value"));
assertThat(log,containsString(" 200 "));
}
@Test
public void testAbsolute() throws Exception
{
_connector.getResponse(
"GET http://hostname:8888/foo?name=value HTTP/1.1\n"+
"Host: servername\n"+
"\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
assertThat(log,containsString("GET http://hostname:8888/foo?name=value"));
assertThat(log,containsString(" 200 "));
}
@Test
public void testQuery() throws Exception
{
_connector.getResponse("GET /foo?name=value HTTP/1.0\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
assertThat(log,containsString("GET /foo?name=value"));
assertThat(log,containsString(" 200 "));
}
@Test
public void testSmallData() throws Exception
{

View File

@ -24,22 +24,25 @@ import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.Timer;
import java.util.TimerTask;
/**
* RolloverFileOutputStream
*
* RolloverFileOutputStream.
*
* <p>
* This output stream puts content in a file that is rolled over every 24 hours.
* The filename must include the string "yyyy_mm_dd", which is replaced with the
* actual date when creating and rolling over the file.
*
* </p>
* <p>
* Old files are retained for a number of days before being deleted.
* </p>
*/
public class RolloverFileOutputStream extends FilterOutputStream
{
@ -51,6 +54,7 @@ public class RolloverFileOutputStream extends FilterOutputStream
final static int ROLLOVER_FILE_RETAIN_DAYS = 31;
private RollTask _rollTask;
private ZonedDateTime midnight;
private SimpleDateFormat _fileBackupFormat;
private SimpleDateFormat _fileDateFormat;
@ -172,19 +176,21 @@ public class RolloverFileOutputStream extends FilterOutputStream
_rollTask=new RollTask();
Calendar now = Calendar.getInstance();
now.setTimeZone(zone);
GregorianCalendar midnight =
new GregorianCalendar(now.get(Calendar.YEAR),
now.get(Calendar.MONTH),
now.get(Calendar.DAY_OF_MONTH),
23,0);
midnight.setTimeZone(zone);
midnight.add(Calendar.HOUR,1);
__rollover.scheduleAtFixedRate(_rollTask,midnight.getTime(),1000L*60*60*24);
midnight = ZonedDateTime.now().toLocalDate().atStartOfDay(zone.toZoneId());
scheduleNextRollover();
}
}
private void scheduleNextRollover()
{
// Increment to next day.
// Using Calendar.add(DAY, 1) takes in account Daylights Savings
// differences, and still maintains the "midnight" settings for
// Hour, Minute, Second, Milliseconds
midnight = midnight.toLocalDate().plus(1, ChronoUnit.DAYS).atStartOfDay(midnight.getZone());
__rollover.schedule(_rollTask,midnight.toInstant().toEpochMilli());
}
/* ------------------------------------------------------------ */
public String getFilename()
@ -254,7 +260,9 @@ public class RolloverFileOutputStream extends FilterOutputStream
{
if (_retainDays>0)
{
long now = System.currentTimeMillis();
ZonedDateTime now = ZonedDateTime.now(this.midnight.getZone());
now.minus(_retainDays, ChronoUnit.DAYS);
long expired = now.toInstant().toEpochMilli();
File file= new File(_filename);
File dir = new File(file.getParent());
@ -272,9 +280,10 @@ public class RolloverFileOutputStream extends FilterOutputStream
if(fn.startsWith(prefix)&&fn.indexOf(suffix,prefix.length())>=0)
{
File f = new File(dir,fn);
long date = f.lastModified();
if ( ((now-date)/(1000*60*60*24))>_retainDays)
f.delete();
if(f.lastModified() < expired)
{
f.delete();
}
}
}
}
@ -297,8 +306,6 @@ public class RolloverFileOutputStream extends FilterOutputStream
}
/* ------------------------------------------------------------ */
/**
*/
@Override
public void close()
throws IOException
@ -316,8 +323,6 @@ public class RolloverFileOutputStream extends FilterOutputStream
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private class RollTask extends TimerTask
{
@ -327,13 +332,13 @@ public class RolloverFileOutputStream extends FilterOutputStream
try
{
RolloverFileOutputStream.this.setFile();
RolloverFileOutputStream.this.scheduleNextRollover();
RolloverFileOutputStream.this.removeOldFiles();
}
catch(IOException e)
{
// Cannot log this exception to a LOG, as RolloverFOS can be used by logging
e.printStackTrace();
e.printStackTrace(System.err);
}
}
}

View File

@ -33,13 +33,18 @@ import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.DecoratedObjectFactory;
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.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
import org.eclipse.jetty.websocket.server.DefaultMappedWebSocketCreator;
import org.eclipse.jetty.websocket.server.MappedWebSocketCreator;
import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter;
@HandlesTypes(
@ -47,13 +52,11 @@ import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter;
public class WebSocketServerContainerInitializer implements ServletContainerInitializer
{
public static final String ENABLE_KEY = "org.eclipse.jetty.websocket.jsr356";
public static final String ADD_DYNAMIC_FILTER_KEY = "org.eclipse.jetty.websocket.jsr356.addDynamicFilter";
private static final Logger LOG = Log.getLogger(WebSocketServerContainerInitializer.class);
/**
* DestroyListener
*
*
*/
public static class ContextDestroyListener implements ServletContextListener
{
@ -80,107 +83,97 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
}
}
/**
* Test a ServletContext for {@code init-param} or {@code attribute} at {@code keyName} for
* true or false setting that determines if the specified feature is enabled (or not).
*
* @param context the context to search
* @param keyName the key name
* @param defValue the default value, if the value is not specified in the context
* @return the value for the feature key
*/
public static boolean isEnabledViaContext(ServletContext context, String keyName, boolean defValue)
{
// Try context parameters first
String cp = context.getInitParameter(keyName);
if(cp != null)
{
if (TypeUtil.isTrue(cp))
{
return true;
}
if (TypeUtil.isFalse(cp))
{
return false;
}
return defValue;
}
// Next, try attribute on context
Object enable = context.getAttribute(ENABLE_KEY);
if(enable != null)
{
if (TypeUtil.isTrue(enable))
{
return true;
}
if (TypeUtil.isFalse(enable))
{
return false;
}
}
return defValue;
}
/**
* Jetty Native approach.
* <p>
* Note: this will add the Upgrade filter to the existing list, with no regard for order. It will just be tacked onto the end of the list.
*
* @param context the servlet context handler
* @return the created websocket server container
* @throws ServletException if unable to create the websocket server container
* Embedded Jetty approach for non-bytecode scanning.
*/
public static ServerContainer configureContext(ServletContextHandler context) throws ServletException
{
// Create Filter
WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context);
// Create Basic components
WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
ByteBufferPool bufferPool = new MappedByteBufferPool();
MappedWebSocketCreator creator = new DefaultMappedWebSocketCreator();
WebSocketServerFactory factory = new WebSocketServerFactory(policy, bufferPool);
// Create the Jetty ServerContainer implementation
ServerContainer jettyContainer = new ServerContainer(filter,filter.getFactory(),context.getServer().getThreadPool());
context.addBean(jettyContainer, true);
ServerContainer jettyContainer = new ServerContainer(creator,factory,context.getServer().getThreadPool());
context.addBean(jettyContainer);
context.setAttribute(WebSocketUpgradeFilter.CREATOR_KEY, creator);
context.setAttribute(WebSocketUpgradeFilter.FACTORY_KEY, factory);
// Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
return jettyContainer;
}
/**
* Servlet 3.1 approach.
* <p>
* This will use Servlet 3.1 techniques on the {@link ServletContext} to add a filter at the start of the filter chain.
*
* @param context the servlet context
* @param jettyContext the jetty servlet context handler
* @return the created websocket server container
* @throws ServletException if unable to create the websocket server container
*/
public static ServerContainer configureContext(ServletContext context, ServletContextHandler jettyContext) throws ServletException
{
// Create Filter
WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context);
// Create the Jetty ServerContainer implementation
ServerContainer jettyContainer = new ServerContainer(filter,filter.getFactory(),jettyContext.getServer().getThreadPool());
jettyContext.addBean(jettyContainer, true);
// Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
if(isEnabledViaContext(context.getServletContext(), ADD_DYNAMIC_FILTER_KEY, true))
{
WebSocketUpgradeFilter.configureContext(context);
}
return jettyContainer;
}
private boolean isEnabled(Set<Class<?>> c, ServletContext context)
/**
* @deprecated use {@link #configureContext(ServletContextHandler)} instead
*/
@Deprecated
public static ServerContainer configureContext(ServletContext context, ServletContextHandler jettyContext) throws ServletException
{
// Try context parameters first
String cp = context.getInitParameter(ENABLE_KEY);
if(TypeUtil.isTrue(cp))
{
// forced on
return true;
}
if(TypeUtil.isFalse(cp))
{
// forced off
LOG.warn("JSR-356 support disabled via parameter on context {} - {}",context.getContextPath(),context);
return false;
}
// Next, try attribute on context
Object enable = context.getAttribute(ENABLE_KEY);
if(TypeUtil.isTrue(enable))
{
// forced on
return true;
}
if (TypeUtil.isFalse(enable))
{
// forced off
LOG.warn("JSR-356 support disabled via attribute on context {} - {}",context.getContextPath(),context);
return false;
}
// if not forced on or off, determine behavior based on annotations.
if (c.isEmpty())
{
if (LOG.isDebugEnabled())
{
LOG.debug("No JSR-356 annotations or interfaces discovered. JSR-356 support disabled",context.getContextPath(),context);
}
return false;
}
return true;
return configureContext(jettyContext);
}
@Override
public void onStartup(Set<Class<?>> c, ServletContext context) throws ServletException
{
if(!isEnabled(c,context))
if(!isEnabledViaContext(context, ENABLE_KEY, true))
{
return;
}
@ -203,26 +196,26 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
try
{
Thread.currentThread().setContextClassLoader(context.getClassLoader());
// Create the Jetty ServerContainer implementation
ServerContainer jettyContainer = configureContext(context,jettyContext);
context.addListener(new ContextDestroyListener()); //make sure context is cleaned up when the context stops
// Establish the DecoratedObjectFactory thread local
// for various ServiceLoader initiated components to use.
DecoratedObjectFactory instantiator = (DecoratedObjectFactory)context.getAttribute(DecoratedObjectFactory.ATTR);
if (instantiator == null)
ServerContainer jettyContainer = configureContext(jettyContext);
context.addListener(new ContextDestroyListener()); // make sure context is cleaned up when the context stops
if (c.isEmpty())
{
LOG.info("Using WebSocket local DecoratedObjectFactory - none found in ServletContext");
instantiator = new DecoratedObjectFactory();
if (LOG.isDebugEnabled())
{
LOG.debug("No JSR-356 annotations or interfaces discovered");
}
return;
}
if (LOG.isDebugEnabled())
{
LOG.debug("Found {} classes",c.size());
}
// Now process the incoming classes
Set<Class<? extends Endpoint>> discoveredExtendedEndpoints = new HashSet<>();
Set<Class<?>> discoveredAnnotatedEndpoints = new HashSet<>();
@ -312,9 +305,7 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
throw new ServletException(e);
}
}
}
finally
{
} finally {
Thread.currentThread().setContextClassLoader(old);
}
}

View File

@ -0,0 +1,97 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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 static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import java.net.URI;
import java.util.Queue;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoSocket;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
/**
* Testing the use of an alternate {@link org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter}
* defined in the WEB-INF/web.xml
*/
public class AltFilterTest
{
@Rule
public TestingDir testdir = new TestingDir();
@Rule
public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
@Test
public void testEcho() throws Exception
{
WSServer wsb = new WSServer(testdir,"app");
wsb.copyWebInf("alt-filter-web.xml");
// the endpoint (extends javax.websocket.Endpoint)
wsb.copyClass(BasicEchoSocket.class);
try
{
wsb.start();
URI uri = wsb.getServerBaseURI();
WebAppContext webapp = wsb.createWebAppContext();
wsb.deployWebapp(webapp);
FilterHolder filterWebXml = webapp.getServletHandler().getFilter("wsuf-test");
assertThat("Filter[wsuf-test]", filterWebXml, notNullValue());
FilterHolder filterSCI = webapp.getServletHandler().getFilter("Jetty_WebSocketUpgradeFilter");
assertThat("Filter[Jetty_WebSocketUpgradeFilter]", filterSCI, nullValue());
WebSocketClient client = new WebSocketClient(bufferPool);
try
{
client.start();
JettyEchoSocket clientEcho = new JettyEchoSocket();
Future<Session> future = client.connect(clientEcho,uri.resolve("echo"));
// wait for connect
future.get(1,TimeUnit.SECONDS);
clientEcho.sendMessage("Hello Echo");
Queue<String> msgs = clientEcho.awaitMessages(1);
Assert.assertEquals("Expected message","Hello Echo",msgs.poll());
}
finally
{
client.stop();
}
}
finally
{
wsb.stop();
}
}
}

View File

@ -58,21 +58,18 @@ public class ExtensionStackProcessingTest
{
private Server server;
private ServerConnector connector;
private ExtensionFactory serverExtensionFactory;
private WebSocketContainer client;
private ServletContextHandler servletContextHandler;
@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);
WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)context.getAttribute(WebSocketUpgradeFilter.class.getName());
serverExtensionFactory = filter.getFactory().getExtensionFactory();
servletContextHandler = new ServletContextHandler(server, "/", true, false);
ServerContainer container = WebSocketServerContainerInitializer.configureContext(servletContextHandler);
ServerEndpointConfig config = ServerEndpointConfig.Builder.create(BasicEchoEndpoint.class, "/").build();
container.addEndpoint(config);
@ -88,11 +85,18 @@ public class ExtensionStackProcessingTest
{
server.stop();
}
private void assumeDeflateFrameAvailable()
{
WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)servletContextHandler.getAttribute(WebSocketUpgradeFilter.class.getName());
ExtensionFactory serverExtensionFactory = filter.getFactory().getExtensionFactory();
Assume.assumeTrue("Server has permessage-deflate extension registered",serverExtensionFactory.isAvailable("permessage-deflate"));
}
@Test
public void testDeflateFrameExtension() throws Exception
{
Assume.assumeTrue("Server has deflate-frame extension registered",serverExtensionFactory.isAvailable("deflate-frame"));
assumeDeflateFrameAvailable();
ClientEndpointConfig config = ClientEndpointConfig.Builder.create()
.extensions(Arrays.<Extension>asList(new JsrExtension("deflate-frame")))
@ -140,7 +144,7 @@ public class ExtensionStackProcessingTest
@Test
public void testPerMessageDeflateExtension() throws Exception
{
Assume.assumeTrue("Server has permessage-deflate extension registered",serverExtensionFactory.isAvailable("permessage-deflate"));
assumeDeflateFrameAvailable();
ClientEndpointConfig config = ClientEndpointConfig.Builder.create()
.extensions(Arrays.<Extension>asList(new JsrExtension("permessage-deflate")))

View File

@ -142,6 +142,11 @@ public class WSServer
{
return serverUri;
}
public Server getServer()
{
return server;
}
public File getWebAppDir()
{

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<context-param>
<param-name>org.eclipse.jetty.websocket.jsr356.addDynamicFilter</param-name>
<param-value>false</param-value>
</context-param>
<filter>
<filter-name>wsuf-test</filter-name>
<filter-class>org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>wsuf-test</filter-name>
<url-pattern>/echo/*</url-pattern>
</filter-mapping>
</web-app>

View File

@ -221,9 +221,13 @@ public abstract class AbstractEventDriver extends AbstractLifeCycle implements I
public void openSession(WebSocketSession session)
{
if (LOG.isDebugEnabled())
LOG.debug("openSession({})",session);
{
LOG.debug("openSession({})", session);
LOG.debug("objectFactory={}", session.getContainerScope().getObjectFactory());
}
this.session = session;
this.session.getContainerScope().getObjectFactory().decorate(this.websocket);
try
{
this.onConnect();

View File

@ -0,0 +1,40 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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.server;
import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
public class DefaultMappedWebSocketCreator implements MappedWebSocketCreator
{
private final PathMappings<WebSocketCreator> mappings = new PathMappings<>();
@Override
public void addMapping(PathSpec spec, WebSocketCreator creator)
{
this.mappings.put(spec, creator);
}
@Override
public PathMappings<WebSocketCreator> getMappings()
{
return this.mappings;
}
}

View File

@ -25,7 +25,6 @@ import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
@ -40,6 +39,7 @@ import org.eclipse.jetty.http.pathmap.RegexPathSpec;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
@ -59,11 +59,11 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter
{
public static final String CONTEXT_ATTRIBUTE_KEY = "contextAttributeKey";
private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class);
public static WebSocketUpgradeFilter configureContext(ServletContextHandler context) throws ServletException
{
// Prevent double configure
WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)context.getAttribute(WebSocketUpgradeFilter.class.getName());
WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter) context.getAttribute(WebSocketUpgradeFilter.class.getName());
if (filter != null)
{
return filter;
@ -72,77 +72,71 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter
// Dynamically add filter
filter = new WebSocketUpgradeFilter();
filter.setToAttribute(context, WebSocketUpgradeFilter.class.getName());
String name = "Jetty_WebSocketUpgradeFilter";
String pathSpec = "/*";
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
FilterHolder fholder = new FilterHolder(filter);
fholder.setName(name);
fholder.setAsyncSupported(true);
fholder.setInitParameter(CONTEXT_ATTRIBUTE_KEY,WebSocketUpgradeFilter.class.getName());
context.addFilter(fholder,pathSpec,dispatcherTypes);
fholder.setInitParameter(CONTEXT_ATTRIBUTE_KEY, WebSocketUpgradeFilter.class.getName());
context.addFilter(fholder, pathSpec, dispatcherTypes);
if (LOG.isDebugEnabled())
{
LOG.debug("Adding [{}] {} mapped to {} to {}",name,filter,pathSpec,context);
}
return filter;
}
public static WebSocketUpgradeFilter configureContext(ServletContext context) throws ServletException
{
// Prevent double configure
WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)context.getAttribute(WebSocketUpgradeFilter.class.getName());
if (filter != null)
{
return filter;
LOG.debug("Adding [{}] {} mapped to {} to {}", name, filter, pathSpec, context);
}
// Dynamically add filter
filter = new WebSocketUpgradeFilter();
filter.setToAttribute(context, WebSocketUpgradeFilter.class.getName());
String name = "Jetty_Dynamic_WebSocketUpgradeFilter";
String pathSpec = "/*";
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
boolean isMatchAfter = false;
String urlPatterns[] = { pathSpec };
FilterRegistration.Dynamic dyn = context.addFilter(name,filter);
dyn.setAsyncSupported(true);
dyn.setInitParameter(CONTEXT_ATTRIBUTE_KEY,WebSocketUpgradeFilter.class.getName());
dyn.addMappingForUrlPatterns(dispatcherTypes,isMatchAfter,urlPatterns);
if (LOG.isDebugEnabled())
{
LOG.debug("Adding [{}] {} mapped to {} to {}",name,filter,pathSpec,context);
}
return filter;
}
private final WebSocketServerFactory factory;
private final PathMappings<WebSocketCreator> pathmap = new PathMappings<>();
/**
* @deprecated use {@link #configureContext(ServletContextHandler)} instead
*/
@Deprecated
public static WebSocketUpgradeFilter configureContext(ServletContext context) throws ServletException
{
ContextHandler handler = ContextHandler.getContextHandler(context);
if (handler == null)
{
throw new ServletException("Not running on Jetty, WebSocket support unavailable");
}
if (!(handler instanceof ServletContextHandler))
{
throw new ServletException("Not running in Jetty ServletContextHandler, WebSocket support via " + WebSocketUpgradeFilter.class.getName() + " unavailable");
}
return configureContext((ServletContextHandler) handler);
}
public static final String CREATOR_KEY = "org.eclipse.jetty.websocket.server.creator";
public static final String FACTORY_KEY = "org.eclipse.jetty.websocket.server.factory";
private final WebSocketPolicy policy;
private final ByteBufferPool bufferPool;
private WebSocketServerFactory factory;
private MappedWebSocketCreator mappedWebSocketCreator;
private String fname;
private boolean alreadySetToAttribute = false;
public WebSocketUpgradeFilter()
{
this(WebSocketPolicy.newServerPolicy(),new MappedByteBufferPool());
this(WebSocketPolicy.newServerPolicy(), new MappedByteBufferPool());
}
public WebSocketUpgradeFilter(WebSocketPolicy policy, ByteBufferPool bufferPool)
{
factory = new WebSocketServerFactory(policy,bufferPool);
addBean(factory,true);
this.policy = policy;
this.bufferPool = bufferPool;
}
@Override
public void addMapping(PathSpec spec, WebSocketCreator creator)
{
pathmap.put(spec,creator);
mappedWebSocketCreator.addMapping(spec, creator);
}
/**
@ -169,10 +163,9 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter
public void destroy()
{
factory.cleanup();
pathmap.reset();
super.destroy();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
@ -180,10 +173,10 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter
{
// no factory, cannot operate
LOG.debug("WebSocketUpgradeFilter is not operational - no WebSocketServletFactory configured");
chain.doFilter(request,response);
chain.doFilter(request, response);
return;
}
try
{
HttpServletRequest httpreq = (HttpServletRequest) request;
@ -195,7 +188,7 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter
chain.doFilter(request, response);
return;
}
// Since this is a filter, we need to be smart about determining the target path
String contextPath = httpreq.getContextPath();
String target = httpreq.getRequestURI();
@ -203,37 +196,37 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter
{
target = target.substring(contextPath.length());
}
MappedResource<WebSocketCreator> resource = pathmap.getMatch(target);
MappedResource<WebSocketCreator> resource = mappedWebSocketCreator.getMappings().getMatch(target);
if (resource == null)
{
if (LOG.isDebugEnabled())
{
LOG.debug("WebSocket Upgrade on {} has no associated endpoint", target);
LOG.debug("PathMappings: {}", pathmap.dump());
LOG.debug("PathMappings: {}", mappedWebSocketCreator.getMappings().dump());
}
// no match.
chain.doFilter(request, response);
return;
}
if(LOG.isDebugEnabled())
if (LOG.isDebugEnabled())
{
LOG.debug("WebSocket Upgrade detected on {} for endpoint {}", target, resource);
}
WebSocketCreator creator = resource.getResource();
// Store PathSpec resource mapping as request attribute
httpreq.setAttribute(PathSpec.class.getName(), resource.getPathSpec());
// We have an upgrade request
if (factory.acceptWebSocket(creator, httpreq, httpresp))
{
// We have a socket instance created
return;
}
// If we reach this point, it means we had an incoming request to upgrade
// but it was either not a proper websocket upgrade, or it was possibly rejected
// due to incoming request constraints (controlled by WebSocketCreator)
@ -251,71 +244,83 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter
LOG.debug("Not a HttpServletRequest, skipping WebSocketUpgradeFilter");
}
}
// not an Upgrade request
chain.doFilter(request,response);
chain.doFilter(request, response);
}
@Override
public String dump()
{
return ContainerLifeCycle.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
out.append(indent).append(" +- pathmap=").append(pathmap.toString()).append("\n");
pathmap.dump(out,indent + " ");
out.append(indent).append(" +- pathmap=").append(mappedWebSocketCreator.toString()).append("\n");
mappedWebSocketCreator.getMappings().dump(out, indent + " ");
}
public WebSocketServerFactory getFactory()
{
return factory;
}
@ManagedAttribute(value = "mappings", readonly = true)
@Override
public PathMappings<WebSocketCreator> getMappings()
{
return pathmap;
return mappedWebSocketCreator.getMappings();
}
@Override
public void init(FilterConfig config) throws ServletException
{
fname = config.getFilterName();
try
{
ServletContext ctx = config.getServletContext();
factory.init(ctx);
WebSocketPolicy policy = factory.getPolicy();
mappedWebSocketCreator = (MappedWebSocketCreator) config.getServletContext().getAttribute(CREATOR_KEY);
if (mappedWebSocketCreator == null)
{
mappedWebSocketCreator = new DefaultMappedWebSocketCreator();
}
factory = (WebSocketServerFactory) config.getServletContext().getAttribute(FACTORY_KEY);
if (factory == null)
{
factory = new WebSocketServerFactory(policy, bufferPool);
}
factory.init(config.getServletContext());
addBean(factory, true);
// TODO: Policy isn't from attributes
String max = config.getInitParameter("maxIdleTime");
if (max != null)
{
policy.setIdleTimeout(Long.parseLong(max));
factory.getPolicy().setIdleTimeout(Long.parseLong(max));
}
max = config.getInitParameter("maxTextMessageSize");
if (max != null)
{
policy.setMaxTextMessageSize(Integer.parseInt(max));
factory.getPolicy().setMaxTextMessageSize(Integer.parseInt(max));
}
max = config.getInitParameter("maxBinaryMessageSize");
if (max != null)
{
policy.setMaxBinaryMessageSize(Integer.parseInt(max));
factory.getPolicy().setMaxBinaryMessageSize(Integer.parseInt(max));
}
max = config.getInitParameter("inputBufferSize");
if (max != null)
{
policy.setInputBufferSize(Integer.parseInt(max));
factory.getPolicy().setInputBufferSize(Integer.parseInt(max));
}
String key = config.getInitParameter(CONTEXT_ATTRIBUTE_KEY);
if (key == null)
{
@ -323,7 +328,7 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter
key = WebSocketUpgradeFilter.class.getName();
}
setToAttribute(ctx, key);
setToAttribute(config.getServletContext(), key);
factory.start();
}
@ -335,45 +340,45 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter
private void setToAttribute(ServletContextHandler context, String key) throws ServletException
{
if(alreadySetToAttribute)
if (alreadySetToAttribute)
{
return;
}
if (context.getAttribute(key) != null)
{
throw new ServletException(WebSocketUpgradeFilter.class.getName() +
throw new ServletException(WebSocketUpgradeFilter.class.getName() +
" is defined twice for the same context attribute key '" + key
+ "'. Make sure you have different init-param '" +
+ "'. Make sure you have different init-param '" +
CONTEXT_ATTRIBUTE_KEY + "' values set");
}
context.setAttribute(key,this);
context.setAttribute(key, this);
alreadySetToAttribute = true;
}
public void setToAttribute(ServletContext context, String key) throws ServletException
{
if(alreadySetToAttribute)
if (alreadySetToAttribute)
{
return;
}
if (context.getAttribute(key) != null)
{
throw new ServletException(WebSocketUpgradeFilter.class.getName() +
throw new ServletException(WebSocketUpgradeFilter.class.getName() +
" is defined twice for the same context attribute key '" + key
+ "'. Make sure you have different init-param '" +
+ "'. Make sure you have different init-param '" +
CONTEXT_ATTRIBUTE_KEY + "' values set");
}
context.setAttribute(key,this);
context.setAttribute(key, this);
alreadySetToAttribute = true;
}
@Override
public String toString()
{
return String.format("%s[factory=%s,pathmap=%s]",this.getClass().getSimpleName(),factory,pathmap);
return String.format("%s[factory=%s,creator=%s]", this.getClass().getSimpleName(), factory, mappedWebSocketCreator);
}
}

View File

@ -33,6 +33,7 @@ import java.net.URL;
import java.net.UnknownHostException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@ -1429,13 +1430,12 @@ public class XmlConfiguration
*/
public static void main(final String... args) throws Exception
{
final AtomicReference<Throwable> exception = new AtomicReference<>();
AccessController.doPrivileged(new PrivilegedAction<Object>()
try
{
public Object run()
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>()
{
try
@Override
public Void run() throws Exception
{
Properties properties = null;
@ -1509,26 +1509,15 @@ public class XmlConfiguration
lc.start();
}
}
}
catch (Exception e)
{
LOG.debug(Log.EXCEPTION,e);
exception.set(e);
}
return null;
}
});
Throwable th = exception.get();
if (th != null)
return null;
}
});
}
catch (Error|Exception e)
{
if (th instanceof RuntimeException)
throw (RuntimeException)th;
else if (th instanceof Exception)
throw (Exception)th;
else if (th instanceof Error)
throw (Error)th;
throw new Error(th);
LOG.warn(e);
throw e;
}
}
}