Merge branch 'master' into release-9
This commit is contained in:
commit
6734a318c3
|
@ -85,8 +85,7 @@ import org.eclipse.jetty.util.Callback;
|
|||
*/
|
||||
public class DeferredContentProvider implements AsyncContentProvider, Closeable
|
||||
{
|
||||
private static final Callback EMPTY_CALLBACK = new Callback.Adapter();
|
||||
private static final AsyncChunk CLOSE = new AsyncChunk(BufferUtil.EMPTY_BUFFER, EMPTY_CALLBACK);
|
||||
private static final AsyncChunk CLOSE = new AsyncChunk(BufferUtil.EMPTY_BUFFER, Callback.Adapter.INSTANCE);
|
||||
|
||||
private final Object lock = this;
|
||||
private final Queue<AsyncChunk> chunks = new ArrayQueue<>(4, 64, lock);
|
||||
|
@ -130,7 +129,7 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable
|
|||
*/
|
||||
public boolean offer(ByteBuffer buffer)
|
||||
{
|
||||
return offer(buffer, EMPTY_CALLBACK);
|
||||
return offer(buffer, Callback.Adapter.INSTANCE);
|
||||
}
|
||||
|
||||
public boolean offer(ByteBuffer buffer, Callback callback)
|
||||
|
|
|
@ -126,7 +126,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
|||
{
|
||||
try
|
||||
{
|
||||
if (!parse(buffer))
|
||||
if (parse(buffer))
|
||||
return false;
|
||||
|
||||
int read = endPoint.fill(buffer);
|
||||
|
@ -134,7 +134,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
|||
LOG.debug("Read {} bytes from {}", read, endPoint);
|
||||
if (read > 0)
|
||||
{
|
||||
if (!parse(buffer))
|
||||
if (parse(buffer))
|
||||
return false;
|
||||
}
|
||||
else if (read == 0)
|
||||
|
@ -159,7 +159,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
|||
|
||||
private boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
return !parser.parse(buffer);
|
||||
return parser.parse(buffer);
|
||||
}
|
||||
|
||||
private void shutdown()
|
||||
|
@ -199,9 +199,11 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
|||
// from an onFailure() handler or by blocking code waiting for completion.
|
||||
getHttpDestination().close(this);
|
||||
getEndPoint().shutdownOutput();
|
||||
LOG.debug("{} oshut", this);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} oshut", this);
|
||||
getEndPoint().close();
|
||||
LOG.debug("{} closed", this);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} closed", this);
|
||||
|
||||
abort(failure);
|
||||
}
|
||||
|
@ -347,7 +349,8 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
|||
@Override
|
||||
public void resume()
|
||||
{
|
||||
LOG.debug("Content consumed asynchronously, resuming processing");
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Content consumed asynchronously, resuming processing");
|
||||
process();
|
||||
}
|
||||
|
||||
|
@ -357,11 +360,14 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
|||
close(x);
|
||||
}
|
||||
};
|
||||
channel.content(buffer, callback);
|
||||
if (!channel.content(buffer, callback))
|
||||
return true;
|
||||
return callback.tryComplete();
|
||||
}
|
||||
else
|
||||
{
|
||||
noChannel(request);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STD_ERR:
|
||||
|
@ -409,7 +415,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
|||
|
||||
private void noChannel(int request)
|
||||
{
|
||||
// TODO: what here ?
|
||||
LOG.debug("Channel not found for request {}", request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ public class HttpSenderOverFCGI extends HttpSender
|
|||
int id = getHttpChannel().getRequest();
|
||||
boolean hasContent = content.hasContent();
|
||||
Generator.Result headersResult = generator.generateRequestHeaders(id, fcgiHeaders,
|
||||
hasContent ? callback : new Callback.Adapter());
|
||||
hasContent ? callback : Callback.Adapter.INSTANCE);
|
||||
if (hasContent)
|
||||
{
|
||||
getHttpChannel().flush(headersResult);
|
||||
|
|
|
@ -37,9 +37,17 @@ public class ServerGenerator extends Generator
|
|||
private static final byte[] COLON = new byte[]{':', ' '};
|
||||
private static final byte[] EOL = new byte[]{'\r', '\n'};
|
||||
|
||||
private final boolean sendStatus200;
|
||||
|
||||
public ServerGenerator(ByteBufferPool byteBufferPool)
|
||||
{
|
||||
this(byteBufferPool, true);
|
||||
}
|
||||
|
||||
public ServerGenerator(ByteBufferPool byteBufferPool, boolean sendStatus200)
|
||||
{
|
||||
super(byteBufferPool);
|
||||
this.sendStatus200 = sendStatus200;
|
||||
}
|
||||
|
||||
public Result generateResponseHeaders(int request, int code, String reason, HttpFields fields, Callback callback)
|
||||
|
@ -50,14 +58,17 @@ public class ServerGenerator extends Generator
|
|||
List<byte[]> bytes = new ArrayList<>(fields.size() * 2);
|
||||
int length = 0;
|
||||
|
||||
// Special 'Status' header
|
||||
bytes.add(STATUS);
|
||||
length += STATUS.length + COLON.length;
|
||||
if (reason == null)
|
||||
reason = HttpStatus.getMessage(code);
|
||||
byte[] responseBytes = (code + " " + reason).getBytes(utf8);
|
||||
bytes.add(responseBytes);
|
||||
length += responseBytes.length + EOL.length;
|
||||
if (code != 200 || sendStatus200)
|
||||
{
|
||||
// Special 'Status' header
|
||||
bytes.add(STATUS);
|
||||
length += STATUS.length + COLON.length;
|
||||
if (reason == null)
|
||||
reason = HttpStatus.getMessage(code);
|
||||
byte[] responseBytes = (code + " " + reason).getBytes(utf8);
|
||||
bytes.add(responseBytes);
|
||||
length += responseBytes.length + EOL.length;
|
||||
}
|
||||
|
||||
// Other headers
|
||||
for (HttpField field : fields)
|
||||
|
|
|
@ -29,6 +29,10 @@ public abstract class Parser
|
|||
private State state = State.HEADER;
|
||||
private int padding;
|
||||
|
||||
/**
|
||||
* @param buffer the bytes to parse
|
||||
* @return true if the caller should stop parsing, false if the caller should continue parsing
|
||||
*/
|
||||
public boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
while (true)
|
||||
|
@ -53,9 +57,16 @@ public abstract class Parser
|
|||
{
|
||||
ContentParser.Result result = contentParser.parse(buffer);
|
||||
if (result == ContentParser.Result.PENDING)
|
||||
{
|
||||
// Not enough data, signal to read/parse more.
|
||||
return false;
|
||||
else if (result == ContentParser.Result.ASYNC)
|
||||
}
|
||||
if (result == ContentParser.Result.ASYNC)
|
||||
{
|
||||
// The content will be processed asynchronously, signal to stop
|
||||
// parsing; the async operation will eventually resume parsing.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
padding = headerParser.getPaddingLength();
|
||||
state = State.PADDING;
|
||||
|
@ -99,6 +110,13 @@ public abstract class Parser
|
|||
|
||||
public void onHeaders(int request);
|
||||
|
||||
/**
|
||||
* @param request the request id
|
||||
* @param stream the stream type
|
||||
* @param buffer the content bytes
|
||||
* @return true to signal to the parser to stop parsing, false to continue parsing
|
||||
* @see Parser#parse(java.nio.ByteBuffer)
|
||||
*/
|
||||
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer);
|
||||
|
||||
public void onEnd(int request);
|
||||
|
|
|
@ -98,7 +98,7 @@ public class ResponseContentParser extends StreamContentParser
|
|||
{
|
||||
case HEADERS:
|
||||
{
|
||||
if (httpParser.parseHeaders(buffer))
|
||||
if (httpParser.parseNext(buffer))
|
||||
state = State.CONTENT_MODE;
|
||||
remaining = buffer.remaining();
|
||||
break;
|
||||
|
@ -124,7 +124,8 @@ public class ResponseContentParser extends StreamContentParser
|
|||
}
|
||||
case HTTP_CONTENT:
|
||||
{
|
||||
httpParser.parseContent(buffer);
|
||||
if (httpParser.parseNext(buffer))
|
||||
return true;
|
||||
remaining = buffer.remaining();
|
||||
break;
|
||||
}
|
||||
|
@ -165,21 +166,22 @@ public class ResponseContentParser extends StreamContentParser
|
|||
|
||||
// Need to set the response status so the
|
||||
// HttpParser can handle the content properly.
|
||||
String[] parts = httpField.getValue().split(" ");
|
||||
int code = Integer.parseInt(parts[0]);
|
||||
String value = httpField.getValue();
|
||||
String[] parts = value.split(" ");
|
||||
String status = parts[0];
|
||||
int code = Integer.parseInt(status);
|
||||
httpParser.setResponseStatus(code);
|
||||
String reason = parts.length > 1 ? parts[1] : HttpStatus.getMessage(code);
|
||||
String reason = parts.length > 1 ? value.substring(status.length()) : HttpStatus.getMessage(code);
|
||||
|
||||
notifyBegin(code, reason);
|
||||
notifyBegin(code, reason.trim());
|
||||
notifyHeaders(fields);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fields.add(httpField);
|
||||
if (seenResponseCode)
|
||||
notifyHeader(httpField);
|
||||
else
|
||||
fields.add(httpField);
|
||||
}
|
||||
}
|
||||
catch (Throwable x)
|
||||
|
@ -251,8 +253,7 @@ public class ResponseContentParser extends StreamContentParser
|
|||
@Override
|
||||
public boolean content(ByteBuffer buffer)
|
||||
{
|
||||
notifyContent(buffer);
|
||||
return false;
|
||||
return notifyContent(buffer);
|
||||
}
|
||||
|
||||
private boolean notifyContent(ByteBuffer buffer)
|
||||
|
@ -302,21 +303,10 @@ public class ResponseContentParser extends StreamContentParser
|
|||
public void reset()
|
||||
{
|
||||
super.reset();
|
||||
setResponseStatus(200);
|
||||
setState(State.HEADER);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean parseHeaders(ByteBuffer buffer)
|
||||
{
|
||||
return super.parseHeaders(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean parseContent(ByteBuffer buffer)
|
||||
{
|
||||
return super.parseContent(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setResponseStatus(int status)
|
||||
{
|
||||
|
|
|
@ -40,9 +40,9 @@ public class HttpTransportOverFCGI implements HttpTransport
|
|||
private volatile boolean shutdown;
|
||||
private volatile boolean aborted;
|
||||
|
||||
public HttpTransportOverFCGI(ByteBufferPool byteBufferPool, Flusher flusher, int request)
|
||||
public HttpTransportOverFCGI(ByteBufferPool byteBufferPool, Flusher flusher, int request, boolean sendStatus200)
|
||||
{
|
||||
this.generator = new ServerGenerator(byteBufferPool);
|
||||
this.generator = new ServerGenerator(byteBufferPool, sendStatus200);
|
||||
this.flusher = flusher;
|
||||
this.request = request;
|
||||
}
|
||||
|
@ -57,23 +57,20 @@ public class HttpTransportOverFCGI implements HttpTransport
|
|||
{
|
||||
if (lastContent)
|
||||
{
|
||||
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
||||
info.getHttpFields(), new Callback.Adapter());
|
||||
Generator.Result contentResult = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, aborted, callback);
|
||||
Generator.Result headersResult = generateResponseHeaders(info, Callback.Adapter.INSTANCE);
|
||||
Generator.Result contentResult = generateResponseContent(BufferUtil.EMPTY_BUFFER, true, callback);
|
||||
flusher.flush(headersResult, contentResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
||||
info.getHttpFields(), callback);
|
||||
Generator.Result headersResult = generateResponseHeaders(info, callback);
|
||||
flusher.flush(headersResult);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
||||
info.getHttpFields(), new Callback.Adapter());
|
||||
Generator.Result contentResult = generator.generateResponseContent(request, content, lastContent, aborted, callback);
|
||||
Generator.Result headersResult = generateResponseHeaders(info, Callback.Adapter.INSTANCE);
|
||||
Generator.Result contentResult = generateResponseContent(content, lastContent, callback);
|
||||
flusher.flush(headersResult, contentResult);
|
||||
}
|
||||
|
||||
|
@ -88,7 +85,7 @@ public class HttpTransportOverFCGI implements HttpTransport
|
|||
{
|
||||
if (lastContent)
|
||||
{
|
||||
Generator.Result result = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, aborted, callback);
|
||||
Generator.Result result = generateResponseContent(BufferUtil.EMPTY_BUFFER, true, callback);
|
||||
flusher.flush(result);
|
||||
}
|
||||
else
|
||||
|
@ -99,7 +96,7 @@ public class HttpTransportOverFCGI implements HttpTransport
|
|||
}
|
||||
else
|
||||
{
|
||||
Generator.Result result = generator.generateResponseContent(request, content, lastContent, aborted, callback);
|
||||
Generator.Result result = generateResponseContent(content, lastContent, callback);
|
||||
flusher.flush(result);
|
||||
}
|
||||
|
||||
|
@ -107,6 +104,16 @@ public class HttpTransportOverFCGI implements HttpTransport
|
|||
flusher.shutdown();
|
||||
}
|
||||
|
||||
protected Generator.Result generateResponseHeaders(HttpGenerator.ResponseInfo info, Callback callback)
|
||||
{
|
||||
return generator.generateResponseHeaders(request, info.getStatus(), info.getReason(), info.getHttpFields(), callback);
|
||||
}
|
||||
|
||||
protected Generator.Result generateResponseContent(ByteBuffer buffer, boolean lastContent, Callback callback)
|
||||
{
|
||||
return generator.generateResponseContent(request, buffer, lastContent, aborted, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abort()
|
||||
{
|
||||
|
|
|
@ -41,16 +41,18 @@ public class ServerFCGIConnection extends AbstractConnection
|
|||
|
||||
private final ConcurrentMap<Integer, HttpChannelOverFCGI> channels = new ConcurrentHashMap<>();
|
||||
private final Connector connector;
|
||||
private final boolean sendStatus200;
|
||||
private final Flusher flusher;
|
||||
private final HttpConfiguration configuration;
|
||||
private final ServerParser parser;
|
||||
|
||||
public ServerFCGIConnection(Connector connector, EndPoint endPoint, HttpConfiguration configuration)
|
||||
public ServerFCGIConnection(Connector connector, EndPoint endPoint, HttpConfiguration configuration, boolean sendStatus200)
|
||||
{
|
||||
super(endPoint, connector.getExecutor());
|
||||
this.connector = connector;
|
||||
this.flusher = new Flusher(endPoint);
|
||||
this.configuration = configuration;
|
||||
this.sendStatus200 = sendStatus200;
|
||||
this.parser = new ServerParser(new ServerListener());
|
||||
}
|
||||
|
||||
|
@ -119,7 +121,8 @@ public class ServerFCGIConnection extends AbstractConnection
|
|||
{
|
||||
// TODO: handle flags
|
||||
HttpChannelOverFCGI channel = new HttpChannelOverFCGI(connector, configuration, getEndPoint(),
|
||||
new HttpTransportOverFCGI(connector.getByteBufferPool(), flusher, request), new ByteBufferQueuedHttpInput());
|
||||
new HttpTransportOverFCGI(connector.getByteBufferPool(), flusher, request, sendStatus200),
|
||||
new ByteBufferQueuedHttpInput());
|
||||
HttpChannelOverFCGI existing = channels.putIfAbsent(request, channel);
|
||||
if (existing != null)
|
||||
throw new IllegalStateException();
|
||||
|
|
|
@ -27,16 +27,23 @@ import org.eclipse.jetty.server.HttpConfiguration;
|
|||
public class ServerFCGIConnectionFactory extends AbstractConnectionFactory
|
||||
{
|
||||
private final HttpConfiguration configuration;
|
||||
private final boolean sendStatus200;
|
||||
|
||||
public ServerFCGIConnectionFactory(HttpConfiguration configuration)
|
||||
{
|
||||
this(configuration, true);
|
||||
}
|
||||
|
||||
public ServerFCGIConnectionFactory(HttpConfiguration configuration, boolean sendStatus200)
|
||||
{
|
||||
super("fcgi/1.0");
|
||||
this.configuration = configuration;
|
||||
this.sendStatus200 = sendStatus200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||
{
|
||||
return new ServerFCGIConnection(connector, endPoint, configuration);
|
||||
return new ServerFCGIConnection(connector, endPoint, configuration, sendStatus200);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,11 @@ package org.eclipse.jetty.fcgi.server.proxy;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -28,30 +32,49 @@ import javax.servlet.http.HttpServletResponse;
|
|||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.util.FutureResponseListener;
|
||||
import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class FastCGIProxyServletTest
|
||||
{
|
||||
@Parameterized.Parameters
|
||||
public static Collection<Object[]> parameters()
|
||||
{
|
||||
return Arrays.asList(new Object[]{true}, new Object[]{false});
|
||||
}
|
||||
|
||||
private final boolean sendStatus200;
|
||||
private Server server;
|
||||
private ServerConnector httpConnector;
|
||||
private ServerConnector fcgiConnector;
|
||||
private HttpClient client;
|
||||
|
||||
public FastCGIProxyServletTest(boolean sendStatus200)
|
||||
{
|
||||
this.sendStatus200 = sendStatus200;
|
||||
}
|
||||
|
||||
public void prepare(HttpServlet servlet) throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
httpConnector = new ServerConnector(server);
|
||||
server.addConnector(httpConnector);
|
||||
|
||||
fcgiConnector = new ServerConnector(server, new ServerFCGIConnectionFactory(new HttpConfiguration()));
|
||||
fcgiConnector = new ServerConnector(server, new ServerFCGIConnectionFactory(new HttpConfiguration(), sendStatus200));
|
||||
server.addConnector(fcgiConnector);
|
||||
|
||||
final String contextPath = "/";
|
||||
|
@ -89,7 +112,24 @@ public class FastCGIProxyServletTest
|
|||
@Test
|
||||
public void testGETWithSmallResponseContent() throws Exception
|
||||
{
|
||||
final byte[] data = new byte[1024];
|
||||
testGETWithResponseContent(1024, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGETWithLargeResponseContent() throws Exception
|
||||
{
|
||||
testGETWithResponseContent(16 * 1024 * 1024, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGETWithLargeResponseContentWithSlowClient() throws Exception
|
||||
{
|
||||
testGETWithResponseContent(16 * 1024 * 1024, 1);
|
||||
}
|
||||
|
||||
private void testGETWithResponseContent(int length, final long delay) throws Exception
|
||||
{
|
||||
final byte[] data = new byte[length];
|
||||
new Random().nextBytes(data);
|
||||
|
||||
final String path = "/foo/index.php";
|
||||
|
@ -99,13 +139,35 @@ public class FastCGIProxyServletTest
|
|||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
Assert.assertTrue(req.getRequestURI().endsWith(path));
|
||||
resp.setContentLength(data.length);
|
||||
resp.getOutputStream().write(data);
|
||||
}
|
||||
});
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", httpConnector.getLocalPort())
|
||||
.path(path)
|
||||
.send();
|
||||
Request request = client.newRequest("localhost", httpConnector.getLocalPort())
|
||||
.onResponseContentAsync(new Response.AsyncContentListener()
|
||||
{
|
||||
@Override
|
||||
public void onContent(Response response, ByteBuffer content, Callback callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (delay > 0)
|
||||
TimeUnit.MILLISECONDS.sleep(delay);
|
||||
callback.succeeded();
|
||||
}
|
||||
catch (InterruptedException x)
|
||||
{
|
||||
callback.failed(x);
|
||||
}
|
||||
}
|
||||
})
|
||||
.path(path);
|
||||
FutureResponseListener listener = new FutureResponseListener(request, length);
|
||||
request.send(listener);
|
||||
|
||||
ContentResponse response = listener.get(30, TimeUnit.SECONDS);
|
||||
|
||||
Assert.assertEquals(200, response.getStatus());
|
||||
Assert.assertArrayEquals(data, response.getContent());
|
||||
}
|
||||
|
|
|
@ -236,8 +236,7 @@ public class JettyWebAppContext extends WebAppContext
|
|||
|
||||
public void doStart () throws Exception
|
||||
{
|
||||
//Set up the pattern that tells us where the jars are that need scanning for
|
||||
//stuff like taglibs so we can tell jasper about it (see TagLibConfiguration)
|
||||
//Set up the pattern that tells us where the jars are that need scanning
|
||||
|
||||
//Allow user to set up pattern for names of jars from the container classpath
|
||||
//that will be scanned - note that by default NO jars are scanned
|
||||
|
|
|
@ -1,161 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.osgi.boot.jsp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
|
||||
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory;
|
||||
import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper;
|
||||
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.webapp.TagLibConfiguration;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
import org.osgi.framework.Bundle;
|
||||
import org.osgi.framework.BundleReference;
|
||||
import org.osgi.service.packageadmin.PackageAdmin;
|
||||
import org.osgi.util.tracker.ServiceTracker;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Replacement for {@link TagLibConfiguration} for the OSGi integration.
|
||||
* </p>
|
||||
* <p>
|
||||
* In the case of a WAB, tlds can be located in OSGi bundles that are
|
||||
* dependencies of the WAB. It is expected that each WAB lists the
|
||||
* symbolic-names of the bundles that contain tld files. The list is defined as
|
||||
* the value of the header 'Require-TldBundle'
|
||||
* </p>
|
||||
* <p>
|
||||
* Discussions about this are logged in
|
||||
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=306971
|
||||
* </p>
|
||||
*/
|
||||
public class TagLibOSGiConfiguration extends TagLibConfiguration
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(TagLibOSGiConfiguration.class);
|
||||
|
||||
private ServiceTracker packageAdminServiceTracker = null;
|
||||
|
||||
/**
|
||||
* Override the preConfigure; locates the bundles that contain tld files
|
||||
* according to the value of the manifest header Require-TldBundle.
|
||||
* <p>
|
||||
* Set or add to the property TldProcessor.TLDResources the list of located
|
||||
* jars so that the super class will scan those.
|
||||
* </p>
|
||||
*/
|
||||
public void preConfigure(WebAppContext context) throws Exception
|
||||
{
|
||||
String requireTldBundle = (String) context.getAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE);
|
||||
if (requireTldBundle != null)
|
||||
{
|
||||
Collection<Resource> resources = getRequireTldBundleAsJettyResources(context, requireTldBundle);
|
||||
if (resources != null && !resources.isEmpty())
|
||||
{
|
||||
Collection<Resource> previouslySet = (Collection<Resource>) context.getAttribute(TagLibConfiguration.TLD_RESOURCES);
|
||||
if (previouslySet != null)
|
||||
{
|
||||
resources.addAll(previouslySet);
|
||||
}
|
||||
context.setAttribute(TagLibConfiguration.TLD_RESOURCES, resources);
|
||||
}
|
||||
}
|
||||
super.preConfigure(context);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param requireTldBundle The comma separated list of bundles' symbolic
|
||||
* names that contain tld for this osgi webapp.
|
||||
* @return The collection of jars or folders that match those bundles.
|
||||
*/
|
||||
private Collection<Resource> getRequireTldBundleAsJettyResources(WebAppContext context, String requireTldBundle)
|
||||
{
|
||||
Bundle bundle = (Bundle) context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE);
|
||||
PackageAdmin packAdmin = getBundleAdmin();
|
||||
String[] symbNames = requireTldBundle.split(", ");
|
||||
Collection<Resource> tlds = new LinkedHashSet<Resource>();
|
||||
for (String symbName : symbNames)
|
||||
{
|
||||
Bundle[] bs = packAdmin.getBundles(symbName, null);
|
||||
if (bs == null || bs.length == 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Unable to locate the bundle '" + symbName
|
||||
+ "' specified in the "
|
||||
+ OSGiWebappConstants.REQUIRE_TLD_BUNDLE
|
||||
+ " of the manifest of "
|
||||
+ bundle.getSymbolicName());
|
||||
}
|
||||
// take the first one as it is the most recent version?
|
||||
Enumeration<URL> en = bs[0].findEntries("META-INF", "*.tld", false);
|
||||
boolean atLeastOneTldFound = false;
|
||||
while (en.hasMoreElements())
|
||||
{
|
||||
atLeastOneTldFound = true;
|
||||
URL oriUrl = en.nextElement();
|
||||
try
|
||||
{
|
||||
URL url = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(oriUrl);
|
||||
Resource tldResource;
|
||||
tldResource = Resource.newResource(url);
|
||||
tlds.add(tldResource);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IllegalArgumentException("Unable to locate the " + "tld resource in '"
|
||||
+ oriUrl.toString()
|
||||
+ "' in the bundle '"
|
||||
+ bs[0].getSymbolicName()
|
||||
+ "' while registering the "
|
||||
+ OSGiWebappConstants.REQUIRE_TLD_BUNDLE
|
||||
+ " of the manifest of "
|
||||
+ bundle.getSymbolicName(), e);
|
||||
}
|
||||
}
|
||||
if (!atLeastOneTldFound)
|
||||
{
|
||||
LOG.warn("No '/META-INF/*.tld' resources were found " + " in the bundle '"
|
||||
+ bs[0].getSymbolicName()
|
||||
+ "' while registering the "
|
||||
+ OSGiWebappConstants.REQUIRE_TLD_BUNDLE
|
||||
+ " of the manifest of "
|
||||
+ bundle.getSymbolicName());
|
||||
}
|
||||
}
|
||||
return tlds;
|
||||
}
|
||||
|
||||
private PackageAdmin getBundleAdmin()
|
||||
{
|
||||
if (packageAdminServiceTracker == null)
|
||||
{
|
||||
Bundle bootBundle = ((BundleReference) OSGiWebappConstants.class.getClassLoader()).getBundle();
|
||||
packageAdminServiceTracker = new ServiceTracker(bootBundle.getBundleContext(), PackageAdmin.class.getName(), null);
|
||||
packageAdminServiceTracker.open();
|
||||
}
|
||||
return (PackageAdmin) packageAdminServiceTracker.getService();
|
||||
}
|
||||
|
||||
}
|
|
@ -330,8 +330,6 @@ public abstract class AbstractWebAppProvider extends AbstractLifeCycle implement
|
|||
// the webapp first
|
||||
applyMetaInfContextXml(rootResource);
|
||||
|
||||
// pass the value of the require tld bundle so that the TagLibOSGiConfiguration
|
||||
// can pick it up.
|
||||
_webApp.setAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, requireTldBundles);
|
||||
|
||||
//Set up some attributes
|
||||
|
|
|
@ -63,7 +63,7 @@ public class OSGiWebInfConfiguration extends WebInfConfiguration
|
|||
* </ol>
|
||||
*
|
||||
* We also allow individual bundles to specify particular bundles that might include TLDs via the Require-Tlds
|
||||
* MANIFEST.MF header. This is processed in the TagLibOSGiConfiguration class.
|
||||
* MANIFEST.MF header.
|
||||
*
|
||||
* @see org.eclipse.jetty.webapp.WebInfConfiguration#preConfigure(org.eclipse.jetty.webapp.WebAppContext)
|
||||
*/
|
||||
|
|
|
@ -51,7 +51,6 @@ public class OverlayServer
|
|||
org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(),
|
||||
org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(),
|
||||
org.eclipse.jetty.webapp.JettyWebXmlConfiguration.class.getCanonicalName(),
|
||||
org.eclipse.jetty.webapp.TagLibConfiguration.class.getCanonicalName()
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -46,10 +46,15 @@ public class AsyncProxyServlet extends ProxyServlet
|
|||
{
|
||||
ServletInputStream input = request.getInputStream();
|
||||
DeferredContentProvider provider = new DeferredContentProvider();
|
||||
input.setReadListener(new StreamReader(proxyRequest, request, provider));
|
||||
input.setReadListener(newReadListener(proxyRequest, request, provider));
|
||||
return provider;
|
||||
}
|
||||
|
||||
protected ReadListener newReadListener(Request proxyRequest, HttpServletRequest request, DeferredContentProvider provider)
|
||||
{
|
||||
return new StreamReader(proxyRequest, request, provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback)
|
||||
{
|
||||
|
@ -59,7 +64,7 @@ public class AsyncProxyServlet extends ProxyServlet
|
|||
StreamWriter writeListener = (StreamWriter)request.getAttribute(WRITE_LISTENER_ATTRIBUTE);
|
||||
if (writeListener == null)
|
||||
{
|
||||
writeListener = new StreamWriter(request, proxyResponse);
|
||||
writeListener = newWriteListener(request, proxyResponse);
|
||||
request.setAttribute(WRITE_LISTENER_ATTRIBUTE, writeListener);
|
||||
|
||||
// Set the data to write before calling setWriteListener(), because
|
||||
|
@ -82,7 +87,12 @@ public class AsyncProxyServlet extends ProxyServlet
|
|||
onResponseFailure(request, response, proxyResponse, x);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected StreamWriter newWriteListener(HttpServletRequest request, Response proxyResponse)
|
||||
{
|
||||
return new StreamWriter(request, proxyResponse);
|
||||
}
|
||||
|
||||
public static class Transparent extends AsyncProxyServlet
|
||||
{
|
||||
private final TransparentDelegate delegate = new TransparentDelegate(this);
|
||||
|
@ -101,14 +111,14 @@ public class AsyncProxyServlet extends ProxyServlet
|
|||
}
|
||||
}
|
||||
|
||||
private class StreamReader implements ReadListener, Callback
|
||||
protected class StreamReader implements ReadListener, Callback
|
||||
{
|
||||
private final byte[] buffer = new byte[getHttpClient().getRequestBufferSize()];
|
||||
private final Request proxyRequest;
|
||||
private final HttpServletRequest request;
|
||||
private final DeferredContentProvider provider;
|
||||
|
||||
public StreamReader(Request proxyRequest, HttpServletRequest request, DeferredContentProvider provider)
|
||||
protected StreamReader(Request proxyRequest, HttpServletRequest request, DeferredContentProvider provider)
|
||||
{
|
||||
this.proxyRequest = proxyRequest;
|
||||
this.request = request;
|
||||
|
@ -131,7 +141,7 @@ public class AsyncProxyServlet extends ProxyServlet
|
|||
if (read > 0)
|
||||
{
|
||||
_log.debug("{} proxying content to upstream: {} bytes", requestId, read);
|
||||
provider.offer(ByteBuffer.wrap(buffer, 0, read), this);
|
||||
onRequestContent(proxyRequest, request, provider, buffer, 0, read, this);
|
||||
// Do not call isReady() so that we can apply backpressure.
|
||||
break;
|
||||
}
|
||||
|
@ -140,6 +150,11 @@ public class AsyncProxyServlet extends ProxyServlet
|
|||
_log.debug("{} asynchronous read pending on {}", requestId, input);
|
||||
}
|
||||
|
||||
protected void onRequestContent(Request proxyRequest, HttpServletRequest request, DeferredContentProvider provider, byte[] buffer, int offset, int length, Callback callback)
|
||||
{
|
||||
provider.offer(ByteBuffer.wrap(buffer, offset, length), callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllDataRead() throws IOException
|
||||
{
|
||||
|
@ -174,7 +189,7 @@ public class AsyncProxyServlet extends ProxyServlet
|
|||
}
|
||||
}
|
||||
|
||||
private class StreamWriter implements WriteListener
|
||||
protected class StreamWriter implements WriteListener
|
||||
{
|
||||
private final HttpServletRequest request;
|
||||
private final Response proxyResponse;
|
||||
|
@ -184,14 +199,14 @@ public class AsyncProxyServlet extends ProxyServlet
|
|||
private int length;
|
||||
private Callback callback;
|
||||
|
||||
private StreamWriter(HttpServletRequest request, Response proxyResponse)
|
||||
protected StreamWriter(HttpServletRequest request, Response proxyResponse)
|
||||
{
|
||||
this.request = request;
|
||||
this.proxyResponse = proxyResponse;
|
||||
this.state = WriteState.IDLE;
|
||||
}
|
||||
|
||||
private void data(byte[] bytes, int offset, int length, Callback callback)
|
||||
protected void data(byte[] bytes, int offset, int length, Callback callback)
|
||||
{
|
||||
if (state != WriteState.IDLE)
|
||||
throw new WritePendingException();
|
||||
|
@ -235,7 +250,7 @@ public class AsyncProxyServlet extends ProxyServlet
|
|||
}
|
||||
}
|
||||
|
||||
private void complete()
|
||||
protected void complete()
|
||||
{
|
||||
buffer = null;
|
||||
offset = 0;
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package org.eclipse.jetty.proxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
|
@ -477,32 +478,17 @@ public class ProxyServlet extends HttpServlet
|
|||
proxyRequest.getHeaders().toString().trim());
|
||||
}
|
||||
|
||||
proxyRequest.send(new ProxyResponseListener(request, response));
|
||||
proxyRequest.send(newProxyResponseListener(request, response));
|
||||
}
|
||||
|
||||
protected ContentProvider proxyRequestContent(final Request proxyRequest, final HttpServletRequest request) throws IOException
|
||||
{
|
||||
return new InputStreamContentProvider(request.getInputStream())
|
||||
{
|
||||
@Override
|
||||
public long getLength()
|
||||
{
|
||||
return request.getContentLength();
|
||||
}
|
||||
return new ProxyInputStreamContentProvider(proxyRequest, request, request.getInputStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuffer onRead(byte[] buffer, int offset, int length)
|
||||
{
|
||||
_log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length);
|
||||
return super.onRead(buffer, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReadFailure(Throwable failure)
|
||||
{
|
||||
onClientRequestFailure(proxyRequest, request, failure);
|
||||
}
|
||||
};
|
||||
protected Response.Listener newProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
return new ProxyResponseListener(request, response);
|
||||
}
|
||||
|
||||
protected void onClientRequestFailure(Request proxyRequest, HttpServletRequest request, Throwable failure)
|
||||
|
@ -716,12 +702,12 @@ public class ProxyServlet extends HttpServlet
|
|||
}
|
||||
}
|
||||
|
||||
private class ProxyResponseListener extends Response.Listener.Adapter
|
||||
protected class ProxyResponseListener extends Response.Listener.Adapter
|
||||
{
|
||||
private final HttpServletRequest request;
|
||||
private final HttpServletResponse response;
|
||||
|
||||
public ProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
|
||||
protected ProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
|
@ -811,4 +797,41 @@ public class ProxyServlet extends HttpServlet
|
|||
_log.debug("{} proxying complete", getRequestId(request));
|
||||
}
|
||||
}
|
||||
|
||||
protected class ProxyInputStreamContentProvider extends InputStreamContentProvider
|
||||
{
|
||||
private final Request proxyRequest;
|
||||
private final HttpServletRequest request;
|
||||
|
||||
protected ProxyInputStreamContentProvider(Request proxyRequest, HttpServletRequest request, InputStream input)
|
||||
{
|
||||
super(input);
|
||||
this.proxyRequest = proxyRequest;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLength()
|
||||
{
|
||||
return request.getContentLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuffer onRead(byte[] buffer, int offset, int length)
|
||||
{
|
||||
_log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length);
|
||||
return onRequestContent(proxyRequest, request, buffer, offset, length);
|
||||
}
|
||||
|
||||
protected ByteBuffer onRequestContent(Request proxyRequest, final HttpServletRequest request, byte[] buffer, int offset, int length)
|
||||
{
|
||||
return super.onRead(buffer, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReadFailure(Throwable failure)
|
||||
{
|
||||
onClientRequestFailure(proxyRequest, request, failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,6 +109,18 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
|
|||
if (_invalid)
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------- */
|
||||
/** Check to see if session has expired as at the time given.
|
||||
* @param time
|
||||
* @return
|
||||
*/
|
||||
protected boolean checkExpiry(long time)
|
||||
{
|
||||
if (_maxIdleMs>0 && _lastAccessed>0 && _lastAccessed + _maxIdleMs < time)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------- */
|
||||
@Override
|
||||
|
@ -317,7 +329,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
|
|||
_lastAccessed=_accessed;
|
||||
_accessed=time;
|
||||
|
||||
if (_maxIdleMs>0 && _lastAccessed>0 && _lastAccessed + _maxIdleMs < time)
|
||||
if (checkExpiry(time))
|
||||
{
|
||||
invalidate();
|
||||
return false;
|
||||
|
|
|
@ -380,7 +380,7 @@ public class SPDYClient
|
|||
private void closeConnections()
|
||||
{
|
||||
for (Session session : sessions)
|
||||
session.goAway(new GoAwayInfo(), new Callback.Adapter());
|
||||
session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE);
|
||||
sessions.clear();
|
||||
}
|
||||
|
||||
|
|
|
@ -167,7 +167,7 @@ public class SPDYConnection extends AbstractConnection implements Controller, Id
|
|||
protected void goAway(ISession session)
|
||||
{
|
||||
if (session != null)
|
||||
session.goAway(new GoAwayInfo(), new Callback.Adapter());
|
||||
session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE);
|
||||
}
|
||||
|
||||
private void shutdown(ISession session)
|
||||
|
|
|
@ -84,7 +84,7 @@ public class SPDYv3FlowControlStrategy implements FlowControlStrategy
|
|||
if (dataInfo.consumed() == length && !stream.isClosed() && length > 0)
|
||||
{
|
||||
WindowUpdateFrame windowUpdateFrame = new WindowUpdateFrame(session.getVersion(), stream.getId(), length);
|
||||
session.control(stream, windowUpdateFrame, 0, TimeUnit.MILLISECONDS, new Callback.Adapter());
|
||||
session.control(stream, windowUpdateFrame, 0, TimeUnit.MILLISECONDS, Callback.Adapter.INSTANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -431,7 +431,7 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
|
|||
{
|
||||
RstInfo rstInfo = new RstInfo(streamId, StreamStatus.INVALID_STREAM);
|
||||
LOG.debug("Unknown stream {}", rstInfo);
|
||||
rst(rstInfo, new Callback.Adapter());
|
||||
rst(rstInfo, Callback.Adapter.INSTANCE);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -471,7 +471,7 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
|
|||
public void onStreamException(StreamException x)
|
||||
{
|
||||
notifyOnFailure(listener, x); // TODO: notify StreamFrameListener if exists?
|
||||
rst(new RstInfo(x.getStreamId(), x.getStreamStatus()), new Callback.Adapter());
|
||||
rst(new RstInfo(x.getStreamId(), x.getStreamStatus()), Callback.Adapter.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -479,7 +479,7 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
|
|||
{
|
||||
Throwable cause = x.getCause();
|
||||
notifyOnFailure(listener, cause == null ? x : cause);
|
||||
goAway(x.getSessionStatus(), 0, TimeUnit.SECONDS, new Callback.Adapter());
|
||||
goAway(x.getSessionStatus(), 0, TimeUnit.SECONDS, Callback.Adapter.INSTANCE);
|
||||
}
|
||||
|
||||
private void onSyn(final SynStreamFrame frame)
|
||||
|
@ -570,7 +570,7 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
|
|||
}
|
||||
RstInfo rstInfo = new RstInfo(streamId, StreamStatus.PROTOCOL_ERROR);
|
||||
LOG.debug("Duplicate stream, {}", rstInfo);
|
||||
rst(rstInfo, new Callback.Adapter()); // We don't care (too much) if the reset fails.
|
||||
rst(rstInfo, Callback.Adapter.INSTANCE); // We don't care (too much) if the reset fails.
|
||||
return null;
|
||||
}
|
||||
else
|
||||
|
@ -653,7 +653,7 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
|
|||
{
|
||||
RstInfo rstInfo = new RstInfo(streamId, StreamStatus.INVALID_STREAM);
|
||||
LOG.debug("Unknown stream {}", rstInfo);
|
||||
rst(rstInfo, new Callback.Adapter());
|
||||
rst(rstInfo, Callback.Adapter.INSTANCE);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -712,7 +712,7 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
|
|||
}
|
||||
else
|
||||
{
|
||||
control(null, frame, 0, TimeUnit.MILLISECONDS, new Callback.Adapter());
|
||||
control(null, frame, 0, TimeUnit.MILLISECONDS, Callback.Adapter.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -736,7 +736,7 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
|
|||
{
|
||||
RstInfo rstInfo = new RstInfo(streamId, StreamStatus.INVALID_STREAM);
|
||||
LOG.debug("Unknown stream, {}", rstInfo);
|
||||
rst(rstInfo, new Callback.Adapter());
|
||||
rst(rstInfo, Callback.Adapter.INSTANCE);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1238,7 +1238,7 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
|
|||
{
|
||||
private CloseFrameBytes()
|
||||
{
|
||||
super(null, new Callback.Adapter());
|
||||
super(null, Callback.Adapter.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -272,7 +272,7 @@ public class StandardStream extends IdleTimeout implements IStream
|
|||
if (!canReceive())
|
||||
{
|
||||
LOG.debug("Protocol error receiving {}, resetting", dataInfo);
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new Adapter());
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), Callback.Adapter.INSTANCE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -547,7 +547,7 @@ public class StandardStream extends IdleTimeout implements IStream
|
|||
|
||||
private StreamCallback()
|
||||
{
|
||||
this(new Adapter());
|
||||
this(Callback.Adapter.INSTANCE);
|
||||
}
|
||||
|
||||
private StreamCallback(Callback callback)
|
||||
|
|
|
@ -63,7 +63,7 @@ public class HttpConnectionOverSPDY extends HttpConnection
|
|||
// First close then abort, to be sure that the connection cannot be reused
|
||||
// from an onFailure() handler or by blocking code waiting for completion.
|
||||
getHttpDestination().close(this);
|
||||
session.goAway(new GoAwayInfo(), new Callback.Adapter());
|
||||
session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE);
|
||||
abort(new AsynchronousCloseException());
|
||||
}
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ public class HttpReceiverOverSPDY extends HttpReceiver implements StreamFrameLis
|
|||
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
|
||||
{
|
||||
// SPDY push not supported
|
||||
getHttpChannel().getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), new Callback.Adapter());
|
||||
getHttpChannel().getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), Callback.Adapter.INSTANCE);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
[name]
|
||||
npn-boot
|
||||
protonego-boot
|
||||
|
||||
[files]
|
||||
http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.7.v20140316/npn-boot-1.1.7.v20140316.jar:lib/npn/npn-boot-1.1.7.v20140316.jar
|
||||
|
||||
[ini-template]
|
||||
--exec
|
||||
[exec]
|
||||
-Xbootclasspath/p:lib/npn/npn-boot-1.1.7.v20140316.jar
|
|
@ -154,7 +154,7 @@ public class ProxyEngineSelector extends ServerSessionFrameListener.Adapter
|
|||
private void rst(Stream stream)
|
||||
{
|
||||
RstInfo rstInfo = new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM);
|
||||
stream.getSession().rst(rstInfo, new Callback.Adapter());
|
||||
stream.getSession().rst(rstInfo, Callback.Adapter.INSTANCE);
|
||||
}
|
||||
|
||||
public static class ProxyServerInfo
|
||||
|
|
|
@ -201,7 +201,7 @@ public class ProxyHTTPSPDYConnection extends HttpConnection implements HttpParse
|
|||
{
|
||||
HttpGenerator.ResponseInfo info = new HttpGenerator.ResponseInfo(HttpVersion.fromString(headers.get
|
||||
("version").getValue()), null, 0, 502, "SPDY reset received from upstream server", false);
|
||||
send(info, null, true, new Callback.Adapter());
|
||||
send(info, null, true, Callback.Adapter.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -189,7 +189,7 @@ public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener
|
|||
Session existing = serverSessions.putIfAbsent(host, session);
|
||||
if (existing != null)
|
||||
{
|
||||
session.goAway(new GoAwayInfo(), new Callback.Adapter());
|
||||
session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE);
|
||||
session = existing;
|
||||
}
|
||||
}
|
||||
|
@ -222,7 +222,7 @@ public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener
|
|||
private void rst(Stream stream)
|
||||
{
|
||||
RstInfo rstInfo = new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM);
|
||||
stream.getSession().rst(rstInfo, new Callback.Adapter());
|
||||
stream.getSession().rst(rstInfo, Callback.Adapter.INSTANCE);
|
||||
}
|
||||
|
||||
private class ProxyPushStreamFrameListener implements StreamFrameListener
|
||||
|
@ -581,7 +581,7 @@ public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener
|
|||
{
|
||||
Session clientSession = clientStream.getSession();
|
||||
RstInfo clientRstInfo = new RstInfo(clientStream.getId(), serverRstInfo.getStreamStatus());
|
||||
clientSession.rst(clientRstInfo, new Callback.Adapter());
|
||||
clientSession.rst(clientRstInfo, Callback.Adapter.INSTANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -182,7 +182,7 @@ public class SPDYServerConnectionFactory extends AbstractConnectionFactory
|
|||
void closeSessions()
|
||||
{
|
||||
for (Session session : sessions)
|
||||
session.goAway(new GoAwayInfo(), new Callback.Adapter());
|
||||
session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE);
|
||||
sessions.clear();
|
||||
}
|
||||
|
||||
|
|
|
@ -287,6 +287,7 @@ public class BaseHome
|
|||
PathFinder finder = new PathFinder();
|
||||
finder.setFileMatcher(matcher);
|
||||
finder.setBase(dir);
|
||||
finder.setIncludeDirsInResults(true);
|
||||
Files.walkFileTree(dir,SEARCH_VISIT_OPTIONS,searchDepth,finder);
|
||||
hits.addAll(finder.getHits());
|
||||
Collections.sort(hits,new NaturalSort.Paths());
|
||||
|
|
|
@ -73,9 +73,18 @@ public class PathMatchers
|
|||
return new File(test).toPath();
|
||||
}
|
||||
|
||||
public static PathMatcher getMatcher(String pattern)
|
||||
public static PathMatcher getMatcher(final String rawpattern)
|
||||
{
|
||||
FileSystem fs = FileSystems.getDefault();
|
||||
|
||||
String pattern = rawpattern;
|
||||
|
||||
// Strip trailing slash (if present)
|
||||
int lastchar = pattern.charAt(pattern.length() - 1);
|
||||
if (lastchar == '/' || lastchar == '\\')
|
||||
{
|
||||
pattern = pattern.substring(0,pattern.length() - 1);
|
||||
}
|
||||
|
||||
// If using FileSystem.getPathMatcher() with "glob:" or "regex:"
|
||||
// use FileSystem default pattern behavior
|
||||
|
|
|
@ -82,7 +82,7 @@ public class ModulesTest
|
|||
|
||||
String expected[] = { "jmx", "client", "stats", "spdy", "deploy", "debug", "security", "npn", "ext", "websocket", "rewrite", "ipaccess", "xinetd",
|
||||
"proxy", "webapp", "jndi", "lowresources", "https", "plus", "requestlog", "jsp", "monitor", "xml", "servlet", "jaas", "http", "base", "server",
|
||||
"annotations" };
|
||||
"annotations", "resources", "loggging" };
|
||||
|
||||
Assert.assertThat("Module count: " + moduleNames,moduleNames.size(),is(expected.length));
|
||||
}
|
||||
|
|
|
@ -61,6 +61,12 @@ public class TestUseCases
|
|||
assertUseCase("home","base.jmx","assert-jmx.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithLogging() throws Exception
|
||||
{
|
||||
assertUseCase("home","base.logging","assert-logging.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithIncludeJettyDir_Logging() throws Exception
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# The XMLs we expect (order is important)
|
||||
XML|${jetty.home}/etc/jetty-jmx.xml
|
||||
XML|${maven-test-resources}/extra-jetty-dirs/logging/etc/jetty-logging.xml
|
||||
XML|${jetty.home}/etc/jetty.xml
|
||||
XML|${jetty.home}/etc/jetty-http.xml
|
||||
|
||||
|
@ -13,6 +14,11 @@ LIB|${jetty.home}/lib/jetty-server-TEST.jar
|
|||
LIB|${jetty.home}/lib/jetty-util-TEST.jar
|
||||
LIB|${jetty.home}/lib/jetty-xml-TEST.jar
|
||||
LIB|${jetty.home}/lib/servlet-api-3.1.jar
|
||||
LIB|${jetty.home}/resources
|
||||
LIB|${maven-test-resources}/extra-jetty-dirs/logging/lib/logging/logback.jar
|
||||
|
||||
# The Properties we expect (order is irrelevant)
|
||||
PROP|jetty.port=9090
|
||||
|
||||
# Files
|
||||
FILE|logs/
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# The XMLs we expect (order is important)
|
||||
XML|${jetty.home}/etc/jetty-logging.xml
|
||||
XML|${jetty.home}/etc/jetty.xml
|
||||
XML|${jetty.home}/etc/jetty-http.xml
|
||||
|
||||
# The LIBs we expect (order is irrelevant)
|
||||
LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
|
||||
LIB|${jetty.home}/lib/jetty-http-TEST.jar
|
||||
LIB|${jetty.home}/lib/jetty-io-TEST.jar
|
||||
LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
|
||||
LIB|${jetty.home}/lib/jetty-server-TEST.jar
|
||||
LIB|${jetty.home}/lib/jetty-util-TEST.jar
|
||||
LIB|${jetty.home}/lib/jetty-xml-TEST.jar
|
||||
LIB|${jetty.home}/lib/servlet-api-3.1.jar
|
||||
LIB|${jetty.base}/lib/logging/slf4j-api.jar
|
||||
LIB|${jetty.base}/lib/logging/jul-to-slf4j.jar
|
||||
LIB|${jetty.base}/lib/logging/logback-core.jar
|
||||
LIB|${jetty.base}/lib/logging/logback-classic.jar
|
||||
LIB|${jetty.base}/resources
|
||||
|
||||
# The Properties we expect (order is irrelevant)
|
||||
PROP|jetty.port=9090
|
||||
|
||||
# Other File References
|
||||
FILE|logs/
|
||||
FILE|resources/
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
--module=server
|
||||
--module=http
|
||||
--module=logging
|
||||
--module=resources
|
||||
|
||||
jetty.port=9090
|
|
@ -0,0 +1,31 @@
|
|||
#
|
||||
# Jetty std err/out logging
|
||||
#
|
||||
|
||||
[xml]
|
||||
etc/jetty-logging.xml
|
||||
|
||||
[files]
|
||||
logs/
|
||||
|
||||
[lib]
|
||||
lib/logging/**.jar
|
||||
resources/
|
||||
|
||||
[ini-template]
|
||||
## Logging Configuration
|
||||
# Configure jetty logging for default internal behavior STDERR output
|
||||
# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||
|
||||
# Configure jetty logging for slf4j
|
||||
# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog
|
||||
|
||||
# Configure jetty logging for java.util.logging
|
||||
# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.JavaUtilLog
|
||||
|
||||
# STDERR / STDOUT Logging
|
||||
# Number of days to retain logs
|
||||
# jetty.log.retain=90
|
||||
# Directory for logging output
|
||||
# Either a path relative to ${jetty.base} or an absolute path
|
||||
# jetty.logs=logs
|
|
@ -0,0 +1,10 @@
|
|||
#
|
||||
# Module to add resources directory to classpath
|
||||
#
|
||||
|
||||
[lib]
|
||||
resources/
|
||||
|
||||
[files]
|
||||
resources/
|
||||
|
|
@ -60,6 +60,12 @@ public interface Callback
|
|||
*/
|
||||
public static class Adapter implements Callback
|
||||
{
|
||||
/**
|
||||
* Instance of Adapter that can be used when the callback methods need an empty
|
||||
* implementation without incurring in the cost of allocating a new Adapter object.
|
||||
*/
|
||||
public static final Adapter INSTANCE = new Adapter();
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
|
|
|
@ -57,6 +57,7 @@ import java.util.ListIterator;
|
|||
*
|
||||
* @see java.util.List
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class LazyList
|
||||
implements Cloneable, Serializable
|
||||
{
|
||||
|
@ -259,6 +260,38 @@ public class LazyList
|
|||
|
||||
return (List<E>)Collections.singletonList(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple utility method to test if List has at least 1 entry.
|
||||
*
|
||||
* @param list
|
||||
* a LazyList, {@link List} or {@link Object}
|
||||
* @return true if not-null and is not empty
|
||||
*/
|
||||
public static boolean hasEntry(Object list)
|
||||
{
|
||||
if (list == null)
|
||||
return false;
|
||||
if (list instanceof List)
|
||||
return !((List<?>)list).isEmpty();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple utility method to test if List is empty
|
||||
*
|
||||
* @param list
|
||||
* a LazyList, {@link List} or {@link Object}
|
||||
* @return true if null or is empty
|
||||
*/
|
||||
public static boolean isEmpty(Object list)
|
||||
{
|
||||
if (list == null)
|
||||
return true;
|
||||
if (list instanceof List)
|
||||
return ((List<?>)list).isEmpty();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
|
|
@ -1,532 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.webapp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.EventListener;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.Servlet;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
|
||||
import org.eclipse.jetty.util.Loader;
|
||||
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;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** TagLibConfiguration.
|
||||
*
|
||||
* The class searches for TLD descriptors found in web.xml, in WEB-INF/*.tld files of the web app
|
||||
* or *.tld files within jars found in WEB-INF/lib of the webapp. Any listeners defined in these
|
||||
* tld's are added to the context.
|
||||
*
|
||||
* <bile>This is total rubbish special case for JSPs! If there was a general use-case for web app
|
||||
* frameworks to register listeners directly, then a generic mechanism could have been added to the servlet
|
||||
* spec. Instead some special purpose JSP support is required that breaks all sorts of encapsulation rules as
|
||||
* the servlet container must go searching for and then parsing the descriptors for one particular framework.
|
||||
* It only appears to be used by JSF, which is being developed by the same developer who implemented this
|
||||
* feature in the first place!
|
||||
* </bile>
|
||||
*
|
||||
*
|
||||
* Note- this has been superceded by the new TldScanner in jasper which uses ServletContainerInitializer to
|
||||
* find all the listeners in tag libs and register them.
|
||||
*/
|
||||
@Deprecated
|
||||
public class TagLibConfiguration extends AbstractConfiguration
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(TagLibConfiguration.class);
|
||||
|
||||
public static final String TLD_RESOURCES = "org.eclipse.jetty.tlds";
|
||||
|
||||
|
||||
/**
|
||||
* TagLibListener
|
||||
*
|
||||
* A listener that does the job of finding .tld files that contain
|
||||
* (other) listeners that need to be called by the servlet container.
|
||||
*
|
||||
* This implementation is necessitated by the fact that it is only
|
||||
* after all the Configuration classes have run that we will
|
||||
* parse web.xml/fragments etc and thus find tlds mentioned therein.
|
||||
*
|
||||
* Note: TagLibConfiguration is not used in jetty-8 as jasper (JSP engine)
|
||||
* uses the new TldScanner class - a ServletContainerInitializer from
|
||||
* Servlet Spec 3 - to find all listeners in taglibs and register them
|
||||
* with the servlet container.
|
||||
*/
|
||||
public class TagLibListener implements ServletContextListener {
|
||||
private List<EventListener> _tldListeners;
|
||||
private WebAppContext _context;
|
||||
|
||||
public TagLibListener (WebAppContext context) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public void contextDestroyed(ServletContextEvent sce)
|
||||
{
|
||||
if (_tldListeners == null)
|
||||
return;
|
||||
|
||||
for (int i=_tldListeners.size()-1; i>=0; i--) {
|
||||
EventListener l = _tldListeners.get(i);
|
||||
if (l instanceof ServletContextListener) {
|
||||
((ServletContextListener)l).contextDestroyed(sce);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void contextInitialized(ServletContextEvent sce)
|
||||
{
|
||||
try
|
||||
{
|
||||
//For jasper 2.1:
|
||||
//Get the system classpath tlds and tell jasper about them, if jasper is on the classpath
|
||||
try
|
||||
{
|
||||
|
||||
ClassLoader loader = _context.getClassLoader();
|
||||
if (loader == null || loader.getParent() == null)
|
||||
loader = getClass().getClassLoader();
|
||||
else
|
||||
loader = loader.getParent();
|
||||
//Choose a class that should be present if tlds are in use
|
||||
Class<?> clazz = loader.loadClass("org.apache.jasper.compiler.TagFileProcessor");
|
||||
assert clazz!=null;
|
||||
Collection<Resource> tld_resources = (Collection<Resource>)_context.getAttribute(TLD_RESOURCES);
|
||||
|
||||
Map<URI, List<String>> tldMap = new HashMap<URI, List<String>>();
|
||||
|
||||
if (tld_resources != null)
|
||||
{
|
||||
//get the jar file names of the files
|
||||
for (Resource r:tld_resources)
|
||||
{
|
||||
Resource jarResource = extractJarResource(r);
|
||||
//jasper is happy with an empty list of tlds
|
||||
if (!tldMap.containsKey(jarResource.getURI()))
|
||||
tldMap.put(jarResource.getURI(), null);
|
||||
|
||||
}
|
||||
//set the magic context attribute that tells jasper about the system tlds
|
||||
sce.getServletContext().setAttribute("com.sun.appserv.tld.map", tldMap);
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
}
|
||||
|
||||
//find the tld files and parse them to get out their
|
||||
//listeners
|
||||
Set<Resource> tlds = findTldResources();
|
||||
List<TldDescriptor> descriptors = parseTlds(tlds);
|
||||
processTlds(descriptors);
|
||||
|
||||
if (_tldListeners == null)
|
||||
return;
|
||||
|
||||
//call the listeners that are ServletContextListeners, put the
|
||||
//rest into the context's list of listeners to call at the appropriate
|
||||
//moment
|
||||
for (EventListener l:_tldListeners) {
|
||||
if (l instanceof ServletContextListener) {
|
||||
((ServletContextListener)l).contextInitialized(sce);
|
||||
} else {
|
||||
_context.addEventListener(l);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOG.warn(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private Resource extractJarResource (Resource r)
|
||||
{
|
||||
if (r == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
String url = r.getURI().toURL().toString();
|
||||
int idx = url.lastIndexOf("!/");
|
||||
if (idx >= 0)
|
||||
url = url.substring(0, idx);
|
||||
if (url.startsWith("jar:"))
|
||||
url = url.substring(4);
|
||||
return Resource.newResource(url);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.warn(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all the locations that can harbour tld files that may contain
|
||||
* a listener which the web container is supposed to instantiate and
|
||||
* call.
|
||||
*
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private Set<Resource> findTldResources () throws IOException {
|
||||
|
||||
Set<Resource> tlds = new HashSet<Resource>();
|
||||
|
||||
// Find tld's from web.xml
|
||||
// When web.xml was processed, it should have created aliases for all TLDs. So search resources aliases
|
||||
// for aliases ending in tld
|
||||
if (_context.getResourceAliases()!=null &&
|
||||
_context.getBaseResource()!=null &&
|
||||
_context.getBaseResource().exists())
|
||||
{
|
||||
Iterator<String> iter=_context.getResourceAliases().values().iterator();
|
||||
while(iter.hasNext())
|
||||
{
|
||||
String location = iter.next();
|
||||
if (location!=null && location.toLowerCase(Locale.ENGLISH).endsWith(".tld"))
|
||||
{
|
||||
if (!location.startsWith("/"))
|
||||
location="/WEB-INF/"+location;
|
||||
Resource l=_context.getBaseResource().addPath(location);
|
||||
tlds.add(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look for any tlds in WEB-INF directly.
|
||||
Resource web_inf = _context.getWebInf();
|
||||
if (web_inf!=null)
|
||||
{
|
||||
String[] contents = web_inf.list();
|
||||
for (int i=0;contents!=null && i<contents.length;i++)
|
||||
{
|
||||
if (contents[i]!=null && contents[i].toLowerCase(Locale.ENGLISH).endsWith(".tld"))
|
||||
{
|
||||
Resource l=web_inf.addPath(contents[i]);
|
||||
tlds.add(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Look for tlds in common location of WEB-INF/tlds
|
||||
if (web_inf != null) {
|
||||
Resource web_inf_tlds = _context.getWebInf().addPath("/tlds/");
|
||||
if (web_inf_tlds.exists() && web_inf_tlds.isDirectory()) {
|
||||
String[] contents = web_inf_tlds.list();
|
||||
for (int i=0;contents!=null && i<contents.length;i++)
|
||||
{
|
||||
if (contents[i]!=null && contents[i].toLowerCase(Locale.ENGLISH).endsWith(".tld"))
|
||||
{
|
||||
Resource l=web_inf_tlds.addPath(contents[i]);
|
||||
tlds.add(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add in tlds found in META-INF of jars. The jars that will be scanned are controlled by
|
||||
// the patterns defined in the context attributes: org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern,
|
||||
// and org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<Resource> tld_resources=(Collection<Resource>)_context.getAttribute(TLD_RESOURCES);
|
||||
if (tld_resources!=null)
|
||||
tlds.addAll(tld_resources);
|
||||
|
||||
return tlds;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse xml into in-memory tree
|
||||
* @param tlds
|
||||
* @return
|
||||
*/
|
||||
private List<TldDescriptor> parseTlds (Set<Resource> tlds) {
|
||||
List<TldDescriptor> descriptors = new ArrayList<TldDescriptor>();
|
||||
|
||||
Resource tld = null;
|
||||
Iterator<Resource> iter = tlds.iterator();
|
||||
while (iter.hasNext())
|
||||
{
|
||||
try
|
||||
{
|
||||
tld = iter.next();
|
||||
if (LOG.isDebugEnabled()) LOG.debug("TLD="+tld);
|
||||
|
||||
TldDescriptor d = new TldDescriptor(tld);
|
||||
d.parse();
|
||||
descriptors.add(d);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Unable to parse TLD: " + tld,e);
|
||||
}
|
||||
}
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create listeners from the parsed tld trees
|
||||
* @param descriptors
|
||||
* @throws Exception
|
||||
*/
|
||||
private void processTlds (List<TldDescriptor> descriptors) throws Exception {
|
||||
|
||||
TldProcessor processor = new TldProcessor();
|
||||
for (TldDescriptor d:descriptors)
|
||||
processor.process(_context, d);
|
||||
|
||||
_tldListeners = new ArrayList<EventListener>(processor.getListeners());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* TldDescriptor
|
||||
*
|
||||
*
|
||||
*/
|
||||
public static class TldDescriptor extends Descriptor
|
||||
{
|
||||
protected static XmlParser __parserSingleton;
|
||||
|
||||
public TldDescriptor(Resource xml)
|
||||
{
|
||||
super(xml);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ensureParser() throws ClassNotFoundException
|
||||
{
|
||||
if (__parserSingleton == null)
|
||||
__parserSingleton = newParser();
|
||||
_parser = __parserSingleton;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlParser newParser() throws ClassNotFoundException
|
||||
{
|
||||
// Create a TLD parser
|
||||
XmlParser parser = new XmlParser(false);
|
||||
|
||||
URL taglib11=null;
|
||||
URL taglib12=null;
|
||||
URL taglib20=null;
|
||||
URL taglib21=null;
|
||||
|
||||
try
|
||||
{
|
||||
Class<?> jsp_page = Loader.loadClass(WebXmlConfiguration.class,"javax.servlet.jsp.JspPage");
|
||||
taglib11=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd");
|
||||
taglib12=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd");
|
||||
taglib20=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd");
|
||||
taglib21=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd");
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(taglib11==null)
|
||||
taglib11=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd");
|
||||
if(taglib12==null)
|
||||
taglib12=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd");
|
||||
if(taglib20==null)
|
||||
taglib20=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd");
|
||||
if(taglib21==null)
|
||||
taglib21=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd");
|
||||
}
|
||||
|
||||
|
||||
if(taglib11!=null)
|
||||
{
|
||||
redirect(parser, "web-jsptaglib_1_1.dtd",taglib11);
|
||||
redirect(parser, "web-jsptaglibrary_1_1.dtd",taglib11);
|
||||
}
|
||||
if(taglib12!=null)
|
||||
{
|
||||
redirect(parser, "web-jsptaglib_1_2.dtd",taglib12);
|
||||
redirect(parser, "web-jsptaglibrary_1_2.dtd",taglib12);
|
||||
}
|
||||
if(taglib20!=null)
|
||||
{
|
||||
redirect(parser, "web-jsptaglib_2_0.xsd",taglib20);
|
||||
redirect(parser, "web-jsptaglibrary_2_0.xsd",taglib20);
|
||||
}
|
||||
if(taglib21!=null)
|
||||
{
|
||||
redirect(parser, "web-jsptaglib_2_1.xsd",taglib21);
|
||||
redirect(parser, "web-jsptaglibrary_2_1.xsd",taglib21);
|
||||
}
|
||||
|
||||
parser.setXpath("/taglib/listener/listener-class");
|
||||
return parser;
|
||||
}
|
||||
|
||||
public void parse ()
|
||||
throws Exception
|
||||
{
|
||||
ensureParser();
|
||||
try
|
||||
{
|
||||
//xerces on apple appears to sometimes close the zip file instead
|
||||
//of the inputstream, so try opening the input stream, but if
|
||||
//that doesn't work, fallback to opening a new url
|
||||
_root = _parser.parse(_xml.getInputStream());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_root = _parser.parse(_xml.getURL().toString());
|
||||
}
|
||||
|
||||
if (_root==null)
|
||||
{
|
||||
LOG.warn("No TLD root in {}",_xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TldProcessor
|
||||
*
|
||||
* Process TldDescriptors representing tag libs to find listeners.
|
||||
*/
|
||||
public class TldProcessor extends IterativeDescriptorProcessor
|
||||
{
|
||||
public static final String TAGLIB_PROCESSOR = "org.eclipse.jetty.tagLibProcessor";
|
||||
XmlParser _parser;
|
||||
List<XmlParser.Node> _roots = new ArrayList<XmlParser.Node>();
|
||||
List<EventListener> _listeners;
|
||||
|
||||
|
||||
public TldProcessor ()
|
||||
throws Exception
|
||||
{
|
||||
_listeners = new ArrayList<EventListener>();
|
||||
registerVisitor("listener", this.getClass().getDeclaredMethod("visitListener", __signature));
|
||||
}
|
||||
|
||||
|
||||
public void visitListener (WebAppContext context, Descriptor descriptor, XmlParser.Node node)
|
||||
{
|
||||
String className=node.getString("listener-class",false,true);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("listener="+className);
|
||||
|
||||
try
|
||||
{
|
||||
Class<?> listenerClass = context.loadClass(className);
|
||||
EventListener l = (EventListener)listenerClass.newInstance();
|
||||
_listeners.add(l);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Could not instantiate listener "+className+": "+e);
|
||||
LOG.debug(e);
|
||||
}
|
||||
catch(Error e)
|
||||
{
|
||||
LOG.warn("Could not instantiate listener "+className+": "+e);
|
||||
LOG.debug(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(WebAppContext context, Descriptor descriptor)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(WebAppContext context, Descriptor descriptor)
|
||||
{
|
||||
}
|
||||
|
||||
public List<EventListener> getListeners() {
|
||||
return _listeners;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void preConfigure(WebAppContext context) throws Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> jsp_page = Loader.loadClass(WebXmlConfiguration.class,"javax.servlet.jsp.JspPage");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//no jsp available, don't parse TLDs
|
||||
return;
|
||||
}
|
||||
|
||||
TagLibListener tagLibListener = new TagLibListener(context);
|
||||
context.addEventListener(tagLibListener);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void configure (WebAppContext context) throws Exception
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postConfigure(WebAppContext context) throws Exception
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void deconfigure(WebAppContext context) throws Exception
|
||||
{
|
||||
}
|
||||
}
|
|
@ -34,6 +34,7 @@ import javax.websocket.ClientEndpoint;
|
|||
import javax.websocket.ClientEndpointConfig;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.WebSocketContainer;
|
||||
|
@ -199,7 +200,7 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont
|
|||
return client;
|
||||
}
|
||||
|
||||
public EndpointMetadata getClientEndpointMetadata(Class<?> endpoint)
|
||||
public EndpointMetadata getClientEndpointMetadata(Class<?> endpoint, EndpointConfig config)
|
||||
{
|
||||
EndpointMetadata metadata = null;
|
||||
|
||||
|
@ -226,7 +227,7 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont
|
|||
// extends Endpoint
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Endpoint> eendpoint = (Class<? extends Endpoint>)endpoint;
|
||||
metadata = new SimpleEndpointMetadata(eendpoint);
|
||||
metadata = new SimpleEndpointMetadata(eendpoint,config);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -313,7 +314,7 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont
|
|||
|
||||
public EndpointInstance newClientEndpointInstance(Object endpoint, ClientEndpointConfig config)
|
||||
{
|
||||
EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass());
|
||||
EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass(),config);
|
||||
ClientEndpointConfig cec = config;
|
||||
if (config == null)
|
||||
{
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package org.eclipse.jetty.websocket.jsr356.client;
|
||||
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.EndpointConfig;
|
||||
|
||||
import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet;
|
||||
import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadataSet;
|
||||
|
@ -34,10 +35,21 @@ public class SimpleEndpointMetadata implements EndpointMetadata
|
|||
private EncoderMetadataSet encoders;
|
||||
|
||||
public SimpleEndpointMetadata(Class<? extends Endpoint> endpointClass)
|
||||
{
|
||||
this(endpointClass, null);
|
||||
}
|
||||
|
||||
public SimpleEndpointMetadata(Class<? extends Endpoint> endpointClass, EndpointConfig config)
|
||||
{
|
||||
this.endpointClass = endpointClass;
|
||||
this.decoders = new DecoderMetadataSet();
|
||||
this.encoders = new EncoderMetadataSet();
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
this.decoders.addAll(config.getDecoders());
|
||||
this.encoders.addAll(config.getEncoders());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,316 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.websocket.ClientEndpointConfig;
|
||||
import javax.websocket.ContainerProvider;
|
||||
import javax.websocket.EncodeException;
|
||||
import javax.websocket.Encoder;
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.MessageHandler;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.WebSocketContainer;
|
||||
|
||||
import org.eclipse.jetty.toolchain.test.EventQueue;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadServer;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
public class EncoderTest
|
||||
{
|
||||
private static class EchoServer implements Runnable
|
||||
{
|
||||
private Thread thread;
|
||||
private BlockheadServer server;
|
||||
private ServerConnection sconnection;
|
||||
private CountDownLatch connectLatch = new CountDownLatch(1);
|
||||
|
||||
public EchoServer(BlockheadServer server)
|
||||
{
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
sconnection = server.accept();
|
||||
sconnection.setSoTimeout(60000);
|
||||
sconnection.upgrade();
|
||||
sconnection.startEcho();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
public void start()
|
||||
{
|
||||
this.thread = new Thread(this,"EchoServer");
|
||||
this.thread.start();
|
||||
}
|
||||
|
||||
public void stop()
|
||||
{
|
||||
if (this.sconnection != null)
|
||||
{
|
||||
this.sconnection.stopEcho();
|
||||
try
|
||||
{
|
||||
this.sconnection.close();
|
||||
}
|
||||
catch (IOException ignore)
|
||||
{
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Quotes
|
||||
{
|
||||
private String author;
|
||||
private List<String> quotes = new ArrayList<>();
|
||||
|
||||
public void addQuote(String quote)
|
||||
{
|
||||
quotes.add(quote);
|
||||
}
|
||||
|
||||
public String getAuthor()
|
||||
{
|
||||
return author;
|
||||
}
|
||||
|
||||
public List<String> getQuotes()
|
||||
{
|
||||
return quotes;
|
||||
}
|
||||
|
||||
public void setAuthor(String author)
|
||||
{
|
||||
this.author = author;
|
||||
}
|
||||
}
|
||||
|
||||
public static class QuotesEncoder implements Encoder.Text<Quotes>
|
||||
{
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(Quotes q) throws EncodeException
|
||||
{
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("Author: ").append(q.getAuthor());
|
||||
buf.append(System.lineSeparator());
|
||||
for (String quote : q.quotes)
|
||||
{
|
||||
buf.append("Quote: ").append(quote);
|
||||
buf.append(System.lineSeparator());
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(EndpointConfig config)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static class QuotesSocket extends Endpoint implements MessageHandler.Whole<String>
|
||||
{
|
||||
private Session session;
|
||||
private EventQueue<String> messageQueue = new EventQueue<>();
|
||||
|
||||
@Override
|
||||
public void onMessage(String message)
|
||||
{
|
||||
messageQueue.add(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(Session session, EndpointConfig config)
|
||||
{
|
||||
this.session = session;
|
||||
this.session.addMessageHandler(this);
|
||||
}
|
||||
|
||||
public void write(Quotes quotes) throws IOException, EncodeException
|
||||
{
|
||||
LOG.debug("Writing Quotes: {}",quotes);
|
||||
this.session.getBasicRemote().sendObject(quotes);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger LOG = Log.getLogger(EncoderTest.class);
|
||||
|
||||
@Rule
|
||||
public TestTracker tt = new TestTracker();
|
||||
private BlockheadServer server;
|
||||
|
||||
private WebSocketContainer client;
|
||||
|
||||
private void assertReceivedQuotes(String result, Quotes quotes)
|
||||
{
|
||||
Assert.assertThat("Quote Author",result,containsString("Author: " + quotes.getAuthor()));
|
||||
for (String quote : quotes.quotes)
|
||||
{
|
||||
Assert.assertThat("Quote",result,containsString("Quote: " + quote));
|
||||
}
|
||||
}
|
||||
|
||||
private Quotes getQuotes(String filename) throws IOException
|
||||
{
|
||||
Quotes quotes = new Quotes();
|
||||
|
||||
// read file
|
||||
File qfile = MavenTestingUtils.getTestResourceFile(filename);
|
||||
try (FileReader reader = new FileReader(qfile); BufferedReader buf = new BufferedReader(reader))
|
||||
{
|
||||
String line;
|
||||
while ((line = buf.readLine()) != null)
|
||||
{
|
||||
switch (line.charAt(0))
|
||||
{
|
||||
case 'a':
|
||||
quotes.setAuthor(line.substring(2));
|
||||
break;
|
||||
case 'q':
|
||||
quotes.addQuote(line.substring(2));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return quotes;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void initClient()
|
||||
{
|
||||
client = ContainerProvider.getWebSocketContainer();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void startServer() throws Exception
|
||||
{
|
||||
server = new BlockheadServer();
|
||||
server.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void stopServer() throws Exception
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleQuotes() throws Exception
|
||||
{
|
||||
EchoServer eserver = new EchoServer(server);
|
||||
try
|
||||
{
|
||||
eserver.start();
|
||||
|
||||
QuotesSocket quoter = new QuotesSocket();
|
||||
|
||||
ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
|
||||
List<Class<? extends Encoder>> encoders = new ArrayList<>();
|
||||
encoders.add(QuotesEncoder.class);
|
||||
builder.encoders(encoders);
|
||||
ClientEndpointConfig cec = builder.build();
|
||||
client.connectToServer(quoter,cec,server.getWsUri());
|
||||
|
||||
Quotes ben = getQuotes("quotes-ben.txt");
|
||||
quoter.write(ben);
|
||||
|
||||
quoter.messageQueue.awaitEventCount(1,1000,TimeUnit.MILLISECONDS);
|
||||
|
||||
String result = quoter.messageQueue.poll();
|
||||
assertReceivedQuotes(result,ben);
|
||||
}
|
||||
finally
|
||||
{
|
||||
eserver.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoQuotes() throws Exception
|
||||
{
|
||||
EchoServer eserver = new EchoServer(server);
|
||||
try
|
||||
{
|
||||
eserver.start();
|
||||
|
||||
QuotesSocket quoter = new QuotesSocket();
|
||||
ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
|
||||
List<Class<? extends Encoder>> encoders = new ArrayList<>();
|
||||
encoders.add(QuotesEncoder.class);
|
||||
builder.encoders(encoders);
|
||||
ClientEndpointConfig cec = builder.build();
|
||||
client.connectToServer(quoter,cec,server.getWsUri());
|
||||
|
||||
Quotes ben = getQuotes("quotes-ben.txt");
|
||||
Quotes twain = getQuotes("quotes-twain.txt");
|
||||
quoter.write(ben);
|
||||
quoter.write(twain);
|
||||
|
||||
quoter.messageQueue.awaitEventCount(2,1000,TimeUnit.MILLISECONDS);
|
||||
|
||||
String result = quoter.messageQueue.poll();
|
||||
assertReceivedQuotes(result,ben);
|
||||
result = quoter.messageQueue.poll();
|
||||
assertReceivedQuotes(result,twain);
|
||||
}
|
||||
finally
|
||||
{
|
||||
eserver.stop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -57,7 +57,7 @@ public class ServerContainer extends ClientContainer implements javax.websocket.
|
|||
|
||||
public EndpointInstance newClientEndpointInstance(Object endpoint, ServerEndpointConfig config, String path)
|
||||
{
|
||||
EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass());
|
||||
EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass(),config);
|
||||
ServerEndpointConfig cec = config;
|
||||
if (config == null)
|
||||
{
|
||||
|
|
|
@ -29,13 +29,8 @@ public class SimpleServerEndpointMetadata extends SimpleEndpointMetadata impleme
|
|||
|
||||
public SimpleServerEndpointMetadata(Class<? extends Endpoint> endpointClass, ServerEndpointConfig config)
|
||||
{
|
||||
super(endpointClass);
|
||||
super(endpointClass,config);
|
||||
this.config = config;
|
||||
if (this.config != null)
|
||||
{
|
||||
getDecoders().addAll(config.getDecoders());
|
||||
getEncoders().addAll(config.getEncoders());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -259,9 +259,12 @@ public class UpgradeRequest
|
|||
public void setCookies(List<HttpCookie> cookies)
|
||||
{
|
||||
this.cookies.clear();
|
||||
this.cookies.addAll(cookies);
|
||||
if (cookies != null && !cookies.isEmpty())
|
||||
{
|
||||
this.cookies.addAll(cookies);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setExtensions(List<ExtensionConfig> configs)
|
||||
{
|
||||
this.extensions.clear();
|
||||
|
|
|
@ -31,6 +31,7 @@ import java.util.TreeSet;
|
|||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import org.eclipse.jetty.util.B64Code;
|
||||
import org.eclipse.jetty.util.LazyList;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
|
@ -210,7 +211,19 @@ public class ClientUpgradeRequest extends UpgradeRequest
|
|||
return;
|
||||
}
|
||||
|
||||
setCookies(cookieStore.get(getRequestURI()));
|
||||
List<HttpCookie> existing = getCookies();
|
||||
List<HttpCookie> extra = cookieStore.get(getRequestURI());
|
||||
|
||||
List<HttpCookie> cookies = new ArrayList<>();
|
||||
if (LazyList.hasEntry(existing))
|
||||
{
|
||||
cookies.addAll(existing);
|
||||
}
|
||||
if (LazyList.hasEntry(extra))
|
||||
{
|
||||
cookies.addAll(extra);
|
||||
}
|
||||
setCookies(cookies);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -36,7 +36,6 @@ import org.eclipse.jetty.io.SelectorManager;
|
|||
import org.eclipse.jetty.util.HttpCookieStore;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.client;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.net.CookieManager;
|
||||
import java.net.HttpCookie;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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.Session;
|
||||
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
||||
import org.eclipse.jetty.websocket.api.util.QuoteUtil;
|
||||
import org.eclipse.jetty.websocket.common.frames.TextFrame;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadServer;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CookieTest
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(CookieTest.class);
|
||||
|
||||
public static class CookieTrackingSocket extends WebSocketAdapter
|
||||
{
|
||||
public EventQueue<String> messageQueue = new EventQueue<>();
|
||||
public EventQueue<Throwable> errorQueue = new EventQueue<>();
|
||||
|
||||
@Override
|
||||
public void onWebSocketText(String message)
|
||||
{
|
||||
messageQueue.add(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketError(Throwable cause)
|
||||
{
|
||||
errorQueue.add(cause);
|
||||
}
|
||||
}
|
||||
|
||||
private WebSocketClient client;
|
||||
private BlockheadServer server;
|
||||
|
||||
@Before
|
||||
public void startClient() throws Exception
|
||||
{
|
||||
client = new WebSocketClient();
|
||||
client.start();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void startServer() throws Exception
|
||||
{
|
||||
server = new BlockheadServer();
|
||||
server.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void stopClient() throws Exception
|
||||
{
|
||||
if (client.isRunning())
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void stopServer() throws Exception
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testViaCookieManager() throws Exception
|
||||
{
|
||||
// Setup client
|
||||
CookieManager cookieMgr = new CookieManager();
|
||||
client.setCookieStore(cookieMgr.getCookieStore());
|
||||
HttpCookie cookie = new HttpCookie("hello","world");
|
||||
cookie.setPath("/");
|
||||
cookie.setMaxAge(100000);
|
||||
cookieMgr.getCookieStore().add(server.getWsUri(),cookie);
|
||||
|
||||
// Client connects
|
||||
CookieTrackingSocket clientSocket = new CookieTrackingSocket();
|
||||
Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
|
||||
|
||||
// Server accepts connect
|
||||
ServerConnection serverConn = server.accept();
|
||||
|
||||
// client confirms upgrade and receipt of frame
|
||||
String serverCookies = confirmClientUpgradeAndCookies(clientSocket,clientConnectFuture,serverConn);
|
||||
|
||||
Assert.assertThat("Cookies seen at server side",serverCookies,containsString("hello=\"world\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testViaServletUpgradeRequest() throws Exception
|
||||
{
|
||||
// Setup client
|
||||
HttpCookie cookie = new HttpCookie("hello","world");
|
||||
cookie.setPath("/");
|
||||
cookie.setMaxAge(100000);
|
||||
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
request.setCookies(Collections.singletonList(cookie));
|
||||
|
||||
// Client connects
|
||||
CookieTrackingSocket clientSocket = new CookieTrackingSocket();
|
||||
Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri(),request);
|
||||
|
||||
// Server accepts connect
|
||||
ServerConnection serverConn = server.accept();
|
||||
|
||||
// client confirms upgrade and receipt of frame
|
||||
String serverCookies = confirmClientUpgradeAndCookies(clientSocket,clientConnectFuture,serverConn);
|
||||
|
||||
Assert.assertThat("Cookies seen at server side",serverCookies,containsString("hello=\"world\""));
|
||||
}
|
||||
|
||||
private String confirmClientUpgradeAndCookies(CookieTrackingSocket clientSocket, Future<Session> clientConnectFuture, ServerConnection serverConn)
|
||||
throws Exception
|
||||
{
|
||||
// Server upgrades
|
||||
List<String> upgradeRequestLines = serverConn.upgrade();
|
||||
List<String> upgradeRequestCookies = serverConn.regexFind(upgradeRequestLines,"^Cookie: (.*)$");
|
||||
|
||||
// Server responds with cookies it knows about
|
||||
TextFrame serverCookieFrame = new TextFrame();
|
||||
serverCookieFrame.setFin(true);
|
||||
serverCookieFrame.setPayload(QuoteUtil.join(upgradeRequestCookies,","));
|
||||
serverConn.write(serverCookieFrame);
|
||||
|
||||
// Server closes connection
|
||||
serverConn.close(StatusCode.NORMAL);
|
||||
|
||||
// Confirm client connect on future
|
||||
clientConnectFuture.get(500,TimeUnit.MILLISECONDS);
|
||||
|
||||
// Wait for client receipt of cookie frame via client websocket
|
||||
clientSocket.messageQueue.awaitEventCount(1,2,TimeUnit.SECONDS);
|
||||
|
||||
String cookies = clientSocket.messageQueue.poll();
|
||||
LOG.debug("Cookies seen at server: {}",cookies);
|
||||
return cookies;
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ org.eclipse.jetty.LEVEL=WARN
|
|||
# org.eclipse.jetty.io.FillInterest.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.io.AbstractConnection.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.websocket.LEVEL=WARN
|
||||
# org.eclipse.jetty.websocket.LEVEL=DEBUG
|
||||
org.eclipse.jetty.websocket.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.websocket.client.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.websocket.common.io.IOState.LEVEL=DEBUG
|
||||
|
|
|
@ -63,8 +63,9 @@ public class Generator
|
|||
private final WebSocketBehavior behavior;
|
||||
private final ByteBufferPool bufferPool;
|
||||
private final boolean validating;
|
||||
private final boolean readOnly;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Are any flags in use
|
||||
* <p>
|
||||
*
|
||||
|
@ -74,7 +75,7 @@ public class Generator
|
|||
* 0001_0000 (0x10) = rsv3
|
||||
* </pre>
|
||||
*/
|
||||
private byte flagsInUse=0x00;
|
||||
private byte flagsInUse = 0x00;
|
||||
|
||||
/**
|
||||
* Construct Generator with provided policy and bufferPool
|
||||
|
@ -86,7 +87,7 @@ public class Generator
|
|||
*/
|
||||
public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool)
|
||||
{
|
||||
this(policy,bufferPool,true);
|
||||
this(policy,bufferPool,true,false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -100,10 +101,28 @@ public class Generator
|
|||
* true to enable RFC frame validation
|
||||
*/
|
||||
public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool, boolean validating)
|
||||
{
|
||||
this(policy,bufferPool,validating,false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct Generator with provided policy and bufferPool
|
||||
*
|
||||
* @param policy
|
||||
* the policy to use
|
||||
* @param bufferPool
|
||||
* the buffer pool to use
|
||||
* @param validating
|
||||
* true to enable RFC frame validation
|
||||
* @param readOnly
|
||||
* true if generator is to treat frames as read-only and not modify them. Useful for debugging purposes, but not generally for runtime use.
|
||||
*/
|
||||
public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool, boolean validating, boolean readOnly)
|
||||
{
|
||||
this.behavior = policy.getBehavior();
|
||||
this.bufferPool = bufferPool;
|
||||
this.validating = validating;
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
public void assertFrameValid(Frame frame)
|
||||
|
@ -200,22 +219,22 @@ public class Generator
|
|||
|
||||
public void generateHeaderBytes(Frame frame, ByteBuffer buffer)
|
||||
{
|
||||
int p=BufferUtil.flipToFill(buffer);
|
||||
|
||||
int p = BufferUtil.flipToFill(buffer);
|
||||
|
||||
// we need a framing header
|
||||
assertFrameValid(frame);
|
||||
|
||||
|
||||
/*
|
||||
* start the generation process
|
||||
*/
|
||||
byte b = 0x00;
|
||||
|
||||
|
||||
// Setup fin thru opcode
|
||||
if (frame.isFin())
|
||||
{
|
||||
b |= 0x80; // 1000_0000
|
||||
}
|
||||
|
||||
|
||||
// Set the flags
|
||||
if (frame.isRsv1())
|
||||
{
|
||||
|
@ -229,7 +248,7 @@ public class Generator
|
|||
{
|
||||
b |= 0x10; // 0001_0000
|
||||
}
|
||||
|
||||
|
||||
// NOTE: using .getOpCode() here, not .getType().getOpCode() for testing reasons
|
||||
byte opcode = frame.getOpCode();
|
||||
|
||||
|
@ -269,7 +288,7 @@ public class Generator
|
|||
/*
|
||||
* if payload is greater that 126 we have a 7 + 16 bit length
|
||||
*/
|
||||
else if (payloadLength >= 0x7E )
|
||||
else if (payloadLength >= 0x7E)
|
||||
{
|
||||
b |= 0x7E;
|
||||
buffer.put(b); // indicate 2 byte length
|
||||
|
@ -286,7 +305,7 @@ public class Generator
|
|||
}
|
||||
|
||||
// masking key
|
||||
if (frame.isMasked())
|
||||
if (frame.isMasked() && !readOnly)
|
||||
{
|
||||
byte[] mask = frame.getMask();
|
||||
buffer.put(mask);
|
||||
|
@ -306,12 +325,12 @@ public class Generator
|
|||
{
|
||||
if (remaining >= 4)
|
||||
{
|
||||
payload.putInt(start, payload.getInt(start) ^ maskInt);
|
||||
payload.putInt(start,payload.getInt(start) ^ maskInt);
|
||||
start += 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
payload.put(start, (byte)(payload.get(start) ^ mask[maskOffset & 3]));
|
||||
payload.put(start,(byte)(payload.get(start) ^ mask[maskOffset & 3]));
|
||||
++start;
|
||||
++maskOffset;
|
||||
}
|
||||
|
@ -335,7 +354,14 @@ public class Generator
|
|||
buf.put(generateHeaderBytes(frame));
|
||||
if (frame.hasPayload())
|
||||
{
|
||||
buf.put(frame.getPayload());
|
||||
if (readOnly)
|
||||
{
|
||||
buf.put(frame.getPayload().slice());
|
||||
}
|
||||
else
|
||||
{
|
||||
buf.put(frame.getPayload());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -344,19 +370,30 @@ public class Generator
|
|||
return bufferPool;
|
||||
}
|
||||
|
||||
|
||||
public void setRsv1InUse(boolean rsv1InUse)
|
||||
{
|
||||
if (readOnly)
|
||||
{
|
||||
throw new RuntimeException("Not allowed to modify read-only frame");
|
||||
}
|
||||
flagsInUse = (byte)((flagsInUse & 0xBF) | (rsv1InUse?0x40:0x00));
|
||||
}
|
||||
|
||||
public void setRsv2InUse(boolean rsv2InUse)
|
||||
{
|
||||
if (readOnly)
|
||||
{
|
||||
throw new RuntimeException("Not allowed to modify read-only frame");
|
||||
}
|
||||
flagsInUse = (byte)((flagsInUse & 0xDF) | (rsv2InUse?0x20:0x00));
|
||||
}
|
||||
|
||||
public void setRsv3InUse(boolean rsv3InUse)
|
||||
{
|
||||
if (readOnly)
|
||||
{
|
||||
throw new RuntimeException("Not allowed to modify read-only frame");
|
||||
}
|
||||
flagsInUse = (byte)((flagsInUse & 0xEF) | (rsv3InUse?0x10:0x00));
|
||||
}
|
||||
|
||||
|
|
|
@ -176,6 +176,11 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
|
|||
|
||||
for (Extension ext : extensions)
|
||||
{
|
||||
if (ext.getName().charAt(0) == '@')
|
||||
{
|
||||
// special, internal-only extensions, not present on negotiation level
|
||||
continue;
|
||||
}
|
||||
ret.add(ext.getConfig());
|
||||
}
|
||||
return ret;
|
||||
|
@ -276,8 +281,8 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
|
|||
@Override
|
||||
public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
|
||||
{
|
||||
FrameEntry entry = new FrameEntry(frame, callback, batchMode);
|
||||
LOG.debug("Queuing {}", entry);
|
||||
FrameEntry entry = new FrameEntry(frame,callback,batchMode);
|
||||
LOG.debug("Queuing {}",entry);
|
||||
entries.offer(entry);
|
||||
flusher.iterate();
|
||||
}
|
||||
|
@ -403,7 +408,7 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
|
|||
// 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);
|
||||
notifyCallbackFailure(current.callback,x);
|
||||
succeeded();
|
||||
}
|
||||
|
||||
|
@ -416,7 +421,7 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
|
|||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.debug("Exception while notifying success of callback " + callback, x);
|
||||
LOG.debug("Exception while notifying success of callback " + callback,x);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -429,7 +434,7 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
|
|||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.debug("Exception while notifying failure of callback " + callback, x);
|
||||
LOG.debug("Exception while notifying failure of callback " + callback,x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.api.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.Generator;
|
||||
|
||||
public class FrameDebugExtension extends AbstractExtension
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(FrameDebugExtension.class);
|
||||
|
||||
private static final int BUFSIZE = 32768;
|
||||
private Generator generator;
|
||||
private Path outputDir;
|
||||
private String prefix = "frame";
|
||||
private AtomicLong incomingId = new AtomicLong(0);
|
||||
private AtomicLong outgoingId = new AtomicLong(0);
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return "@frame-debug";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incomingFrame(Frame frame)
|
||||
{
|
||||
saveFrame(frame,false);
|
||||
nextIncomingFrame(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
|
||||
{
|
||||
saveFrame(frame,true);
|
||||
nextOutgoingFrame(frame,callback,batchMode);
|
||||
}
|
||||
|
||||
private void saveFrame(Frame frame, boolean outgoing)
|
||||
{
|
||||
if (outputDir == null || generator == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder filename = new StringBuilder();
|
||||
filename.append(prefix);
|
||||
if (outgoing)
|
||||
{
|
||||
filename.append(String.format("-outgoing-%05d",outgoingId.getAndIncrement()));
|
||||
}
|
||||
else
|
||||
{
|
||||
filename.append(String.format("-incoming-%05d",incomingId.getAndIncrement()));
|
||||
}
|
||||
filename.append(".dat");
|
||||
|
||||
Path outputFile = outputDir.resolve(filename.toString());
|
||||
ByteBuffer buf = getBufferPool().acquire(BUFSIZE,false);
|
||||
try (SeekableByteChannel channel = Files.newByteChannel(outputFile,StandardOpenOption.CREATE,StandardOpenOption.WRITE))
|
||||
{
|
||||
generator.generateHeaderBytes(frame,buf);
|
||||
channel.write(buf);
|
||||
if (frame.hasPayload())
|
||||
{
|
||||
channel.write(frame.getPayload().slice());
|
||||
}
|
||||
LOG.debug("Saved raw frame: {}",outputFile.toString());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.warn("Unable to save frame: " + filename.toString(),e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
getBufferPool().release(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfig(ExtensionConfig config)
|
||||
{
|
||||
super.setConfig(config);
|
||||
|
||||
String cfgOutputDir = config.getParameter("output-dir",null);
|
||||
if (StringUtil.isNotBlank(cfgOutputDir))
|
||||
{
|
||||
Path path = new File(cfgOutputDir).toPath();
|
||||
if (Files.isDirectory(path) && Files.exists(path) && Files.isWritable(path))
|
||||
{
|
||||
this.outputDir = path;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Unable to configure {}: not a valid output directory",path.toAbsolutePath().toString());
|
||||
}
|
||||
}
|
||||
|
||||
String cfgPrefix = config.getParameter("prefix","frame");
|
||||
if (StringUtil.isNotBlank(cfgPrefix))
|
||||
{
|
||||
this.prefix = cfgPrefix;
|
||||
}
|
||||
|
||||
if (this.outputDir != null)
|
||||
{
|
||||
// create a non-validating, read-only generator
|
||||
this.generator = new Generator(getPolicy(),getBufferPool(),false,true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -246,6 +246,7 @@ public abstract class CompressExtension extends AbstractExtension
|
|||
|
||||
private class Flusher extends IteratingCallback implements WriteCallback
|
||||
{
|
||||
private static final int INPUT_BUFSIZE = 32 * 1024;
|
||||
private FrameEntry current;
|
||||
private ByteBuffer payload;
|
||||
private boolean finished = true;
|
||||
|
@ -288,7 +289,7 @@ public abstract class CompressExtension extends AbstractExtension
|
|||
Frame frame = entry.frame;
|
||||
ByteBuffer data = frame.getPayload();
|
||||
int remaining = data.remaining();
|
||||
int inputLength = Math.min(remaining, 32 * 1024);
|
||||
int inputLength = Math.min(remaining, INPUT_BUFSIZE);
|
||||
LOG.debug("Compressing {}: {} bytes in {} bytes chunk", entry, remaining, inputLength);
|
||||
|
||||
// Avoid to copy the bytes if the ByteBuffer
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.common.test;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -52,9 +54,9 @@ import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
|||
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.api.extensions.Frame.Type;
|
||||
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
|
||||
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
|
||||
import org.eclipse.jetty.websocket.api.extensions.Frame.Type;
|
||||
import org.eclipse.jetty.websocket.common.AcceptHash;
|
||||
import org.eclipse.jetty.websocket.common.CloseInfo;
|
||||
import org.eclipse.jetty.websocket.common.Generator;
|
||||
|
@ -66,9 +68,6 @@ import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory;
|
|||
import org.eclipse.jetty.websocket.common.frames.CloseFrame;
|
||||
import org.junit.Assert;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
|
||||
/**
|
||||
* A overly simplistic websocket server used during testing.
|
||||
* <p>
|
||||
|
@ -234,7 +233,7 @@ public class BlockheadServer
|
|||
{
|
||||
try
|
||||
{
|
||||
write(WebSocketFrame.copy(frame));
|
||||
write(WebSocketFrame.copy(frame).setMasked(false));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
@ -280,20 +279,14 @@ public class BlockheadServer
|
|||
public List<ExtensionConfig> parseExtensions(List<String> requestLines)
|
||||
{
|
||||
List<ExtensionConfig> extensionConfigs = new ArrayList<>();
|
||||
|
||||
List<String> hits = regexFind(requestLines, "^Sec-WebSocket-Extensions: (.*)$");
|
||||
|
||||
Pattern patExts = Pattern.compile("^Sec-WebSocket-Extensions: (.*)$",Pattern.CASE_INSENSITIVE);
|
||||
|
||||
Matcher mat;
|
||||
for (String line : requestLines)
|
||||
for (String econf : hits)
|
||||
{
|
||||
mat = patExts.matcher(line);
|
||||
if (mat.matches())
|
||||
{
|
||||
// found extensions
|
||||
String econf = mat.group(1);
|
||||
ExtensionConfig config = ExtensionConfig.parse(econf);
|
||||
extensionConfigs.add(config);
|
||||
}
|
||||
// found extensions
|
||||
ExtensionConfig config = ExtensionConfig.parse(econf);
|
||||
extensionConfigs.add(config);
|
||||
}
|
||||
|
||||
return extensionConfigs;
|
||||
|
@ -301,20 +294,15 @@ public class BlockheadServer
|
|||
|
||||
public String parseWebSocketKey(List<String> requestLines)
|
||||
{
|
||||
String key = null;
|
||||
|
||||
Pattern patKey = Pattern.compile("^Sec-WebSocket-Key: (.*)$",Pattern.CASE_INSENSITIVE);
|
||||
|
||||
Matcher mat;
|
||||
for (String line : requestLines)
|
||||
List<String> hits = regexFind(requestLines,"^Sec-WebSocket-Key: (.*)$");
|
||||
if (hits.size() <= 0)
|
||||
{
|
||||
mat = patKey.matcher(line);
|
||||
if (mat.matches())
|
||||
{
|
||||
key = mat.group(1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Assert.assertThat("Number of Sec-WebSocket-Key headers", hits.size(), is(1));
|
||||
|
||||
String key = hits.get(0);
|
||||
return key;
|
||||
}
|
||||
|
||||
|
@ -415,6 +403,32 @@ public class BlockheadServer
|
|||
return lines;
|
||||
}
|
||||
|
||||
public List<String> regexFind(List<String> lines, String pattern)
|
||||
{
|
||||
List<String> hits = new ArrayList<>();
|
||||
|
||||
Pattern patKey = Pattern.compile(pattern,Pattern.CASE_INSENSITIVE);
|
||||
|
||||
Matcher mat;
|
||||
for (String line : lines)
|
||||
{
|
||||
mat = patKey.matcher(line);
|
||||
if (mat.matches())
|
||||
{
|
||||
if (mat.groupCount() >= 1)
|
||||
{
|
||||
hits.add(mat.group(1));
|
||||
}
|
||||
else
|
||||
{
|
||||
hits.add(mat.group(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hits;
|
||||
}
|
||||
|
||||
public void respond(String rawstr) throws IOException
|
||||
{
|
||||
LOG.debug("respond(){}{}","\n",rawstr);
|
||||
|
@ -486,7 +500,7 @@ public class BlockheadServer
|
|||
echoing.set(false);
|
||||
}
|
||||
|
||||
public void upgrade() throws IOException
|
||||
public List<String> upgrade() throws IOException
|
||||
{
|
||||
List<String> requestLines = readRequestLines();
|
||||
List<ExtensionConfig> extensionConfigs = parseExtensions(requestLines);
|
||||
|
@ -559,6 +573,7 @@ public class BlockheadServer
|
|||
// Write Response
|
||||
LOG.debug("Response: {}",resp.toString());
|
||||
write(resp.toString().getBytes());
|
||||
return requestLines;
|
||||
}
|
||||
|
||||
private void write(byte[] bytes) throws IOException
|
||||
|
|
|
@ -18,11 +18,16 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.server.browser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.handler.ResourceHandler;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.common.extensions.FrameDebugExtension;
|
||||
import org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension;
|
||||
import org.eclipse.jetty.websocket.server.WebSocketHandler;
|
||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
|
||||
|
@ -85,8 +90,27 @@ public class BrowserDebugTool implements WebSocketCreator
|
|||
String ua = req.getHeader("User-Agent");
|
||||
String rexts = req.getHeader("Sec-WebSocket-Extensions");
|
||||
|
||||
// manually negotiate extensions
|
||||
List<ExtensionConfig> negotiated = new ArrayList<>();
|
||||
// adding frame debug
|
||||
negotiated.add(new ExtensionConfig("@frame-debug; output-dir=target"));
|
||||
for (ExtensionConfig config : req.getExtensions())
|
||||
{
|
||||
if (config.getName().equals("permessage-deflate"))
|
||||
{
|
||||
// what we are interested in here
|
||||
negotiated.add(config);
|
||||
continue;
|
||||
}
|
||||
// skip all others
|
||||
}
|
||||
|
||||
resp.setExtensions(negotiated);
|
||||
|
||||
LOG.debug("User-Agent: {}",ua);
|
||||
LOG.debug("Sec-WebSocket-Extensions (Request) : {}",rexts);
|
||||
|
||||
req.getExtensions();
|
||||
return new BrowserSocket(ua,rexts);
|
||||
}
|
||||
|
||||
|
@ -114,6 +138,9 @@ public class BrowserDebugTool implements WebSocketCreator
|
|||
factory.getExtensionFactory().register("permessage-deflate",PerMessageDeflateExtension.class);
|
||||
// factory.getExtensionFactory().unregister("x-webkit-deflate-frame");
|
||||
|
||||
// Registering Frame Debug
|
||||
factory.getExtensionFactory().register("@frame-debug",FrameDebugExtension.class);
|
||||
|
||||
// Setup the desired Socket to use for all incoming upgrade requests
|
||||
factory.setCreator(BrowserDebugTool.this);
|
||||
|
||||
|
|
|
@ -18,12 +18,17 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.server.browser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
|
||||
import org.eclipse.jetty.util.IO;
|
||||
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;
|
||||
|
@ -119,6 +124,38 @@ public class BrowserSocket
|
|||
LOG.info("onTextMessage({})",message);
|
||||
}
|
||||
|
||||
// Is multi-line?
|
||||
if (message.contains("\n"))
|
||||
{
|
||||
// echo back exactly
|
||||
writeMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Is resource lookup?
|
||||
if (message.charAt(0) == '@')
|
||||
{
|
||||
String name = message.substring(1);
|
||||
URL url = Loader.getResource(BrowserSocket.class,name);
|
||||
if (url == null)
|
||||
{
|
||||
writeMessage("Unable to find resource: " + name);
|
||||
return;
|
||||
}
|
||||
try (InputStream in = url.openStream())
|
||||
{
|
||||
String data = IO.toString(in);
|
||||
writeMessage(data);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
writeMessage("Unable to read resource: " + name);
|
||||
LOG.warn("Unable to read resource: " + name,e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Is parameterized?
|
||||
int idx = message.indexOf(':');
|
||||
if (idx > 0)
|
||||
{
|
||||
|
@ -194,12 +231,11 @@ public class BrowserSocket
|
|||
writeMessage("key[%s] val[%s]",key,val);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not parameterized, echo it back
|
||||
writeMessage(message);
|
||||
}
|
||||
|
||||
// Not parameterized, echo it back as-is
|
||||
writeMessage(message);
|
||||
}
|
||||
|
||||
private void writeManyAsync(int size, int count)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<input id="hello" class="button" type="submit" name="hello" value="hello" disabled="disabled"/>
|
||||
<input id="there" class="button" type="submit" name="there" value="there" disabled="disabled"/>
|
||||
<input id="json" class="button" type="submit" name="json" value="json" disabled="disabled"/>
|
||||
<input id="twain" class="button" type="submit" name="twain" value="twain" disabled="disabled"/>
|
||||
<input id="send10k" class="button" type="submit" name="send10k" value="send10k" disabled="disabled"/>
|
||||
<input id="send100k" class="button" type="submit" name="send100k" value="send100k" disabled="disabled"/>
|
||||
<input id="send1000k" class="button" type="submit" name="send1000k" value="send1000k" disabled="disabled"/>
|
||||
|
@ -31,6 +32,7 @@
|
|||
$("manythreads").onclick = function(event) {wstool.write("manythreads:20,25,60"); return false; }
|
||||
$("hello").onclick = function(event) {wstool.write("Hello"); return false; }
|
||||
$("there").onclick = function(event) {wstool.write("There"); return false; }
|
||||
$("twain").onclick = function(event) {wstool.write("@twain.txt"); return false; }
|
||||
$("json").onclick = function(event) {wstool.write("[{\"channel\":\"/meta/subscribe\",\"subscription\":\"/chat/demo\",\"id\":\"2\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
|
||||
+ " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/meta/subscribe\",\"subscription\":\"/members/demo\",\"id\":\"3\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
|
||||
+ " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/chat/demo\",\"data\":{\"user\":\"ch\",\"membership\":\"join\",\"chat\":\"ch"
|
||||
|
|
|
@ -85,6 +85,7 @@ var wstool = {
|
|||
$('hello').disabled = !enabled;
|
||||
$('there').disabled = !enabled;
|
||||
$('json').disabled = !enabled;
|
||||
$('twain').disabled = !enabled;
|
||||
$('send10k').disabled = !enabled;
|
||||
$('send100k').disabled = !enabled;
|
||||
$('send1000k').disabled = !enabled;
|
||||
|
|
Loading…
Reference in New Issue