Merge branch 'master' into release-9

This commit is contained in:
Jesse McConnell 2014-05-15 07:34:09 -05:00
commit 7982a38ea6
83 changed files with 2341 additions and 660 deletions

View File

@ -82,7 +82,7 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
public void onComplete(Result result)
{
HttpRequest request = (HttpRequest)result.getRequest();
ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getEncoding());
ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding());
if (result.isFailed())
{
Throwable failure = result.getFailure();

View File

@ -192,6 +192,11 @@ public class ConnectionPool implements Closeable, Dumpable
return idleConnections.contains(connection);
}
public boolean isEmpty()
{
return connectionCount.get() == 0;
}
public void close()
{
for (Connection connection : idleConnections)

View File

@ -88,7 +88,7 @@ public class ContinueProtocolHandler implements ProtocolHandler
// or it does and wants to refuse the request content,
// or we got some other HTTP status code like a redirect.
List<Response.ResponseListener> listeners = exchange.getResponseListeners();
HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getEncoding());
HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getMediaType(), getEncoding());
notifier.forwardSuccess(listeners, contentResponse);
exchange.proceed(new HttpRequestException("Expectation failed", exchange.getRequest()));
break;
@ -108,7 +108,7 @@ public class ContinueProtocolHandler implements ProtocolHandler
HttpExchange exchange = conversation.getExchanges().peekLast();
assert exchange.getResponse() == response;
List<Response.ResponseListener> listeners = exchange.getResponseListeners();
HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getEncoding());
HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getMediaType(), getEncoding());
notifier.forwardFailureComplete(listeners, exchange.getRequest(), exchange.getRequestFailure(), contentResponse, failure);
}

View File

@ -49,12 +49,14 @@ import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.client.util.FormContentProvider;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.SocketAddressResolver;
@ -133,6 +135,7 @@ public class HttpClient extends ContainerLifeCycle
private volatile boolean dispatchIO = true;
private volatile boolean strictEventOrdering = false;
private volatile HttpField encodingField;
private volatile boolean removeIdleDestinations = false;
/**
* Creates a {@link HttpClient} instance that can perform requests to non-TLS destinations only
@ -324,6 +327,30 @@ public class HttpClient extends ContainerLifeCycle
return newRequest(uri).send();
}
/**
* Performs a POST request to the specified URI with the given form parameters.
*
* @param uri the URI to POST
* @param fields the fields composing the form name/value pairs
* @return the {@link ContentResponse} for the request
*/
public ContentResponse FORM(String uri, Fields fields) throws InterruptedException, ExecutionException, TimeoutException
{
return FORM(URI.create(uri), fields);
}
/**
* Performs a POST request to the specified URI with the given form parameters.
*
* @param uri the URI to POST
* @param fields the fields composing the form name/value pairs
* @return the {@link ContentResponse} for the request
*/
public ContentResponse FORM(URI uri, Fields fields) throws InterruptedException, ExecutionException, TimeoutException
{
return POST(uri).content(new FormContentProvider(fields)).send();
}
/**
* Creates a POST request to the specified URI.
*
@ -464,6 +491,11 @@ public class HttpClient extends ContainerLifeCycle
return destination;
}
protected boolean removeDestination(HttpDestination destination)
{
return destinations.remove(destination.getOrigin()) != null;
}
/**
* @return the list of destinations known to this {@link HttpClient}.
*/
@ -825,6 +857,7 @@ public class HttpClient extends ContainerLifeCycle
/**
* @return whether request events must be strictly ordered
* @see #setStrictEventOrdering(boolean)
*/
public boolean isStrictEventOrdering()
{
@ -832,32 +865,62 @@ public class HttpClient extends ContainerLifeCycle
}
/**
* Whether request events must be strictly ordered.
* Whether request/response events must be strictly ordered with respect to connection usage.
* <p />
* {@link org.eclipse.jetty.client.api.Response.CompleteListener}s may send a second request.
* If the second request is for the same destination, there is an inherent race
* condition for the use of the connection: the first request may still be associated with the
* connection, so the second request cannot use that connection and is forced to open another one.
* From the point of view of connection usage, the connection can be reused just before the
* "complete" event notified to {@link org.eclipse.jetty.client.api.Response.CompleteListener}s
* (but after the "success" event).
* <p />
* From the point of view of connection usage, the connection is reusable just before the "complete"
* event, so it would be possible to reuse that connection from {@link org.eclipse.jetty.client.api.Response.CompleteListener}s;
* but in this case the second request's events will fire before the "complete" events of the first
* request.
* When a request/response exchange is completing, the destination may have another request
* queued to be sent to the server.
* If the connection for that destination is reused for the second request before the "complete"
* event of the first exchange, it may happen that the "begin" event of the second request
* happens before the "complete" event of the first exchange.
* <p />
* This setting enforces strict event ordering so that a "begin" event of a second request can never
* fire before the "complete" event of a first request, but at the expense of an increased usage
* of connections.
* Enforcing strict ordering of events so that a "begin" event of a request can never happen
* before the "complete" event of the previous exchange comes with the cost of increased
* connection usage.
* In case of HTTP redirects and strict event ordering, for example, the redirect request will
* be forced to open a new connection because it is typically sent from the complete listener
* when the connection cannot yet be reused.
* When strict event ordering is not enforced, the redirect request will reuse the already
* open connection making the system more efficient.
* <p />
* When not enforced, a "begin" event of a second request may happen before the "complete" event of
* a first request and allow for better usage of connections.
* The default value for this property is {@code false}.
*
* @param strictEventOrdering whether request events must be strictly ordered
* @param strictEventOrdering whether request/response events must be strictly ordered
*/
public void setStrictEventOrdering(boolean strictEventOrdering)
{
this.strictEventOrdering = strictEventOrdering;
}
/**
* @return whether destinations that have no connections should be removed
* @see #setRemoveIdleDestinations(boolean)
*/
public boolean isRemoveIdleDestinations()
{
return removeIdleDestinations;
}
/**
* Whether destinations that have no connections (nor active nor idle) should be removed.
* <p />
* Applications typically make request to a limited number of destinations so keeping
* destinations around is not a problem for the memory or the GC.
* However, for applications that hit millions of different destinations (e.g. a spider
* bot) it would be useful to be able to remove the old destinations that won't be visited
* anymore and leave space for new destinations.
*
* @param removeIdleDestinations whether destinations that have no connections should be removed
* @see org.eclipse.jetty.client.ConnectionPool
*/
public void setRemoveIdleDestinations(boolean removeIdleDestinations)
{
this.removeIdleDestinations = removeIdleDestinations;
}
/**
* @return the forward proxy configuration
*/
@ -973,7 +1036,28 @@ public class HttpClient extends ContainerLifeCycle
@Override
public Iterator<ContentDecoder.Factory> iterator()
{
return set.iterator();
final Iterator<ContentDecoder.Factory> iterator = set.iterator();
return new Iterator<ContentDecoder.Factory>()
{
@Override
public boolean hasNext()
{
return iterator.hasNext();
}
@Override
public ContentDecoder.Factory next()
{
return iterator.next();
}
@Override
public void remove()
{
iterator.remove();
invalidate();
}
};
}
@Override
@ -988,7 +1072,7 @@ public class HttpClient extends ContainerLifeCycle
return set.toArray(a);
}
protected void invalidate()
private void invalidate()
{
if (set.isEmpty())
{

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.client;
import java.net.CookieStore;
import java.net.HttpCookie;
import java.net.URI;
import java.util.ArrayList;
@ -110,6 +111,15 @@ public abstract class HttpConnection implements Connection
// Add content headers
if (content != null)
{
if (content instanceof ContentProvider.Typed)
{
if (!headers.containsKey(HttpHeader.CONTENT_TYPE.asString()))
{
String contentType = ((ContentProvider.Typed)content).getContentType();
if (contentType != null)
headers.put(HttpHeader.CONTENT_TYPE, contentType);
}
}
long contentLength = content.getLength();
if (contentLength >= 0)
{
@ -124,19 +134,14 @@ public abstract class HttpConnection implements Connection
}
// Cookies
List<HttpCookie> cookies = getHttpClient().getCookieStore().get(request.getURI());
StringBuilder cookieString = null;
for (int i = 0; i < cookies.size(); ++i)
CookieStore cookieStore = getHttpClient().getCookieStore();
if (cookieStore != null)
{
if (cookieString == null)
cookieString = new StringBuilder();
if (i > 0)
cookieString.append("; ");
HttpCookie cookie = cookies.get(i);
cookieString.append(cookie.getName()).append("=").append(cookie.getValue());
StringBuilder cookies = convertCookies(cookieStore.get(request.getURI()), null);
cookies = convertCookies(request.getCookies(), cookies);
if (cookies != null)
request.header(HttpHeader.COOKIE.asString(), cookies.toString());
}
if (cookieString != null)
request.header(HttpHeader.COOKIE.asString(), cookieString.toString());
// Authorization
URI authenticationURI = proxy != null ? proxy.getURI() : request.getURI();
@ -148,6 +153,20 @@ public abstract class HttpConnection implements Connection
}
}
private StringBuilder convertCookies(List<HttpCookie> cookies, StringBuilder builder)
{
for (int i = 0; i < cookies.size(); ++i)
{
if (builder == null)
builder = new StringBuilder();
if (builder.length() > 0)
builder.append("; ");
HttpCookie cookie = cookies.get(i);
builder.append(cookie.getName()).append("=").append(cookie.getValue());
}
return builder;
}
@Override
public String toString()
{

View File

@ -33,12 +33,14 @@ public class HttpContentResponse implements ContentResponse
{
private final Response response;
private final byte[] content;
private final String mediaType;
private final String encoding;
public HttpContentResponse(Response response, byte[] content, String encoding)
public HttpContentResponse(Response response, byte[] content, String mediaType, String encoding)
{
this.response = response;
this.content = content;
this.mediaType = mediaType;
this.encoding = encoding;
}
@ -48,13 +50,6 @@ public class HttpContentResponse implements ContentResponse
return response.getRequest();
}
@Override
@Deprecated
public long getConversationID()
{
return getRequest().getConversationID();
}
@Override
public <T extends ResponseListener> List<T> getListeners(Class<T> listenerClass)
{
@ -91,6 +86,18 @@ public class HttpContentResponse implements ContentResponse
return response.abort(cause);
}
@Override
public String getMediaType()
{
return mediaType;
}
@Override
public String getEncoding()
{
return encoding;
}
@Override
public byte[] getContent()
{

View File

@ -118,7 +118,7 @@ public class HttpRedirector
{
resultRef.set(new Result(result.getRequest(),
result.getRequestFailure(),
new HttpContentResponse(result.getResponse(), getContent(), getEncoding()),
new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding()),
result.getResponseFailure()));
latch.countDown();
}
@ -271,7 +271,7 @@ public class HttpRedirector
}
}
private Request redirect(final Request request, Response response, Response.CompleteListener listener, URI location, String method)
private Request redirect(Request request, Response response, Response.CompleteListener listener, URI location, String method)
{
HttpRequest httpRequest = (HttpRequest)request;
HttpConversation conversation = httpRequest.getConversation();
@ -281,9 +281,20 @@ public class HttpRedirector
if (redirects < client.getMaxRedirects())
{
++redirects;
if (conversation != null)
conversation.setAttribute(ATTRIBUTE, redirects);
conversation.setAttribute(ATTRIBUTE, redirects);
return sendRedirect(httpRequest, response, listener, location, method);
}
else
{
fail(request, response, new HttpResponseException("Max redirects exceeded " + redirects, response));
return null;
}
}
private Request sendRedirect(final HttpRequest httpRequest, Response response, Response.CompleteListener listener, URI location, String method)
{
try
{
Request redirect = client.copyRequest(httpRequest, location);
// Use given method
@ -294,7 +305,7 @@ public class HttpRedirector
@Override
public void onBegin(Request redirect)
{
Throwable cause = request.getAbortCause();
Throwable cause = httpRequest.getAbortCause();
if (cause != null)
redirect.abort(cause);
}
@ -303,9 +314,9 @@ public class HttpRedirector
redirect.send(listener);
return redirect;
}
else
catch (Throwable x)
{
fail(request, response, new HttpResponseException("Max redirects exceeded " + redirects, response));
fail(httpRequest, response, x);
return null;
}
}

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.client;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpCookie;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
@ -27,6 +28,7 @@ import java.nio.ByteBuffer;
import java.nio.charset.UnsupportedCharsetException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@ -57,8 +59,6 @@ public class HttpRequest implements Request
{
private final HttpFields headers = new HttpFields();
private final Fields params = new Fields(true);
private final Map<String, Object> attributes = new HashMap<>();
private final List<RequestListener> requestListeners = new ArrayList<>();
private final List<Response.ResponseListener> responseListeners = new ArrayList<>();
private final AtomicReference<Throwable> aborted = new AtomicReference<>();
private final HttpClient client;
@ -75,6 +75,9 @@ public class HttpRequest implements Request
private long timeout;
private ContentProvider content;
private boolean followRedirects;
private List<HttpCookie> cookies;
private Map<String, Object> attributes;
private List<RequestListener> requestListeners;
protected HttpRequest(HttpClient client, HttpConversation conversation, URI uri)
{
@ -98,12 +101,6 @@ public class HttpRequest implements Request
return conversation;
}
@Override
public long getConversationID()
{
return getConversation().getID();
}
@Override
public String getScheme()
{
@ -218,7 +215,7 @@ public class HttpRequest implements Request
{
// If we have an existing query string, preserve it and append the new parameter.
if (query != null)
query += "&" + name + "=" + urlEncode(value);
query += "&" + urlEncode(name) + "=" + urlEncode(value);
else
query = buildQuery();
uri = null;
@ -245,6 +242,21 @@ public class HttpRequest implements Request
return this;
}
@Override
public Request accept(String... accepts)
{
StringBuilder result = new StringBuilder();
for (String accept : accepts)
{
if (result.length() > 0)
result.append(", ");
result.append(accept);
}
if (result.length() > 0)
headers.put(HttpHeader.ACCEPT, result.toString());
return this;
}
@Override
public Request header(String name, String value)
{
@ -265,9 +277,26 @@ public class HttpRequest implements Request
return this;
}
@Override
public List<HttpCookie> getCookies()
{
return cookies != null ? cookies : Collections.<HttpCookie>emptyList();
}
@Override
public Request cookie(HttpCookie cookie)
{
if (cookies == null)
cookies = new ArrayList<>();
cookies.add(cookie);
return this;
}
@Override
public Request attribute(String name, Object value)
{
if (attributes == null)
attributes = new HashMap<>(4);
attributes.put(name, value);
return this;
}
@ -275,7 +304,7 @@ public class HttpRequest implements Request
@Override
public Map<String, Object> getAttributes()
{
return attributes;
return attributes != null ? attributes : Collections.<String, Object>emptyMap();
}
@Override
@ -290,8 +319,8 @@ public class HttpRequest implements Request
{
// This method is invoked often in a request/response conversation,
// so we avoid allocation if there is no need to filter.
if (type == null)
return (List<T>)requestListeners;
if (type == null || requestListeners == null)
return requestListeners != null ? (List<T>)requestListeners : Collections.<T>emptyList();
ArrayList<T> result = new ArrayList<>();
for (RequestListener listener : requestListeners)
@ -303,14 +332,13 @@ public class HttpRequest implements Request
@Override
public Request listener(Request.Listener listener)
{
this.requestListeners.add(listener);
return this;
return requestListener(listener);
}
@Override
public Request onRequestQueued(final QueuedListener listener)
{
this.requestListeners.add(new QueuedListener()
return requestListener(new QueuedListener()
{
@Override
public void onQueued(Request request)
@ -318,13 +346,12 @@ public class HttpRequest implements Request
listener.onQueued(request);
}
});
return this;
}
@Override
public Request onRequestBegin(final BeginListener listener)
{
this.requestListeners.add(new BeginListener()
return requestListener(new BeginListener()
{
@Override
public void onBegin(Request request)
@ -332,13 +359,12 @@ public class HttpRequest implements Request
listener.onBegin(request);
}
});
return this;
}
@Override
public Request onRequestHeaders(final HeadersListener listener)
{
this.requestListeners.add(new HeadersListener()
return requestListener(new HeadersListener()
{
@Override
public void onHeaders(Request request)
@ -346,13 +372,12 @@ public class HttpRequest implements Request
listener.onHeaders(request);
}
});
return this;
}
@Override
public Request onRequestCommit(final CommitListener listener)
{
this.requestListeners.add(new CommitListener()
return requestListener(new CommitListener()
{
@Override
public void onCommit(Request request)
@ -360,13 +385,12 @@ public class HttpRequest implements Request
listener.onCommit(request);
}
});
return this;
}
@Override
public Request onRequestContent(final ContentListener listener)
{
this.requestListeners.add(new ContentListener()
return requestListener(new ContentListener()
{
@Override
public void onContent(Request request, ByteBuffer content)
@ -374,13 +398,12 @@ public class HttpRequest implements Request
listener.onContent(request, content);
}
});
return this;
}
@Override
public Request onRequestSuccess(final SuccessListener listener)
{
this.requestListeners.add(new SuccessListener()
return requestListener(new SuccessListener()
{
@Override
public void onSuccess(Request request)
@ -388,13 +411,12 @@ public class HttpRequest implements Request
listener.onSuccess(request);
}
});
return this;
}
@Override
public Request onRequestFailure(final FailureListener listener)
{
this.requestListeners.add(new FailureListener()
return requestListener(new FailureListener()
{
@Override
public void onFailure(Request request, Throwable failure)
@ -402,6 +424,13 @@ public class HttpRequest implements Request
listener.onFailure(request, failure);
}
});
}
private Request requestListener(RequestListener listener)
{
if (requestListeners == null)
requestListeners = new ArrayList<>();
requestListeners.add(listener);
return this;
}
@ -555,9 +584,7 @@ public class HttpRequest implements Request
@Override
public Request file(Path file, String contentType) throws IOException
{
if (contentType != null)
header(HttpHeader.CONTENT_TYPE, contentType);
return content(new PathContentProvider(file));
return content(new PathContentProvider(contentType, file));
}
@Override
@ -656,7 +683,7 @@ public class HttpRequest implements Request
private String buildQuery()
{
StringBuilder result = new StringBuilder();
for (Iterator<Fields.Field> iterator = params.iterator(); iterator.hasNext();)
for (Iterator<Fields.Field> iterator = params.iterator(); iterator.hasNext(); )
{
Fields.Field field = iterator.next();
List<String> values = field.getValues();
@ -698,7 +725,7 @@ public class HttpRequest implements Request
String[] parts = nameValue.split("=");
if (parts.length > 0)
{
String name = parts[0];
String name = urlDecode(parts[0]);
if (name.trim().length() == 0)
continue;
param(name, parts.length < 2 ? "" : urlDecode(parts[1]), true);

View File

@ -87,13 +87,6 @@ public class HttpResponse implements Response
return headers;
}
@Override
@Deprecated
public long getConversationID()
{
return request.getConversationID();
}
@Override
public <T extends ResponseListener> List<T> getListeners(Class<T> type)
{

View File

@ -146,7 +146,11 @@ public abstract class MultiplexHttpDestination<C extends Connection> extends Htt
{
ConnectState current = connect.get();
if (connect.compareAndSet(current, ConnectState.DISCONNECTED))
{
if (getHttpClient().isRemoveIdleDestinations())
getHttpClient().removeDestination(this);
break;
}
}
}

View File

@ -168,11 +168,24 @@ public abstract class PoolingHttpDestination<C extends Connection> extends HttpD
super.close(oldConnection);
connectionPool.remove(oldConnection);
// We need to execute queued requests even if this connection failed.
// We may create a connection that is not needed, but it will eventually
// idle timeout, so no worries
if (!getHttpExchanges().isEmpty())
if (getHttpExchanges().isEmpty())
{
if (getHttpClient().isRemoveIdleDestinations() && connectionPool.isEmpty())
{
// There is a race condition between this thread removing the destination
// and another thread queueing a request to this same destination.
// If this destination is removed, but the request queued, a new connection
// will be opened, the exchange will be executed and eventually the connection
// will idle timeout and be closed. Meanwhile a new destination will be created
// in HttpClient and will be used for other requests.
getHttpClient().removeDestination(this);
}
}
else
{
// We need to execute queued requests even if this connection failed.
// We may create a connection that is not needed, but it will eventually
// idle timeout, so no worries.
C newConnection = acquire();
if (newConnection != null)
process(newConnection, false);

View File

@ -28,17 +28,15 @@ import org.eclipse.jetty.client.util.PathContentProvider;
/**
* {@link ContentProvider} provides a source of request content.
* <p />
* <p/>
* Implementations should return an {@link Iterator} over the request content.
* If the request content comes from a source that needs to be closed (for
* example, an {@link InputStream}), then the iterator implementation class
* must implement {@link Closeable} and will be closed when the request is
* completed (either successfully or failed).
* <p />
* <p/>
* Applications should rely on utility classes such as {@link ByteBufferContentProvider}
* or {@link PathContentProvider}.
* <p />
*
*/
public interface ContentProvider extends Iterable<ByteBuffer>
{
@ -46,4 +44,17 @@ public interface ContentProvider extends Iterable<ByteBuffer>
* @return the content length, if known, or -1 if the content length is unknown
*/
long getLength();
/**
* An extension of {@link ContentProvider} that provides a content type string
* to be used as a {@code Content-Type} HTTP header in requests.
*/
public interface Typed extends ContentProvider
{
/**
* @return the content type string such as "application/octet-stream" or
* "application/json;charset=UTF8", or null if no content type must be set
*/
public String getContentType();
}
}

View File

@ -23,6 +23,16 @@ package org.eclipse.jetty.client.api;
*/
public interface ContentResponse extends Response
{
/**
* @return the media type of the content, such as "text/html" or "application/octet-stream"
*/
String getMediaType();
/**
* @return the encoding of the content, such as "UTF-8"
*/
String getEncoding();
/**
* @return the response content
*/

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.client.api;
import java.io.IOException;
import java.net.HttpCookie;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
@ -49,13 +50,6 @@ import org.eclipse.jetty.util.Fields;
*/
public interface Request
{
/**
* @return the conversation id
* @deprecated do not use this method anymore
*/
@Deprecated
long getConversationID();
/**
* @return the scheme of this request, such as "http" or "https"
*/
@ -170,6 +164,17 @@ public interface Request
*/
Request header(HttpHeader header, String value);
/**
* @return the cookies associated with this request
*/
List<HttpCookie> getCookies();
/**
* @param cookie a cookie for this request
* @return this request object
*/
Request cookie(HttpCookie cookie);
/**
* @param name the name of the attribute
* @param value the value of the attribute
@ -225,11 +230,18 @@ public interface Request
String getAgent();
/**
* @param agent the user agent for this request
* @param agent the user agent for this request (corresponds to the {@code User-Agent} header)
* @return this request object
*/
Request agent(String agent);
/**
* @param accepts the media types that are acceptable in the response, such as
* "text/plain;q=0.5" or "text/html" (corresponds to the {@code Accept} header)
* @return this request object
*/
Request accept(String... accepts);
/**
* @return the idle timeout for this request, in milliseconds
*/

View File

@ -45,13 +45,6 @@ public interface Response
*/
Request getRequest();
/**
* @return the conversation id
* @deprecated do not use this method anymore
*/
@Deprecated
long getConversationID();
/**
* @return the response listener passed to {@link Request#send(CompleteListener)}
*/

View File

@ -23,6 +23,7 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpContent;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpRequestException;
import org.eclipse.jetty.client.HttpSender;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request;
@ -107,6 +108,12 @@ public class HttpSenderOverHTTP extends HttpSender
endPoint.write(new ByteBufferRecyclerCallback(callback, bufferPool, toRecycle), toWrite);
return;
}
case DONE:
{
// The headers have already been generated, perhaps by a concurrent abort.
callback.failed(new HttpRequestException("Could not generate headers", request));
return;
}
default:
{
throw new IllegalStateException(result.toString());

View File

@ -0,0 +1,37 @@
//
// ========================================================================
// 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.client.util;
import org.eclipse.jetty.client.api.ContentProvider;
public abstract class AbstractTypedContentProvider implements ContentProvider.Typed
{
private final String contentType;
protected AbstractTypedContentProvider(String contentType)
{
this.contentType = contentType;
}
@Override
public String getContentType()
{
return contentType;
}
}

View File

@ -20,12 +20,9 @@ package org.eclipse.jetty.client.util;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Arrays;
import java.util.Locale;
import org.eclipse.jetty.client.api.Response;
@ -45,6 +42,7 @@ public abstract class BufferingResponseListener extends Listener.Adapter
{
private final int maxLength;
private volatile ByteBuffer buffer;
private volatile String mediaType;
private volatile String encoding;
/**
@ -62,14 +60,14 @@ public abstract class BufferingResponseListener extends Listener.Adapter
*/
public BufferingResponseListener(int maxLength)
{
this.maxLength=maxLength;
this.maxLength = maxLength;
}
@Override
public void onHeaders(Response response)
{
super.onHeaders(response);
HttpFields headers = response.getHeaders();
long length = headers.getLongField(HttpHeader.CONTENT_LENGTH.asString());
if (length > maxLength)
@ -77,47 +75,58 @@ public abstract class BufferingResponseListener extends Listener.Adapter
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
return;
}
buffer=BufferUtil.allocate((length > 0)?(int)length:1024);
buffer = BufferUtil.allocate(length > 0 ? (int)length : 1024);
String contentType = headers.get(HttpHeader.CONTENT_TYPE);
if (contentType != null)
{
String media = contentType;
String charset = "charset=";
int index = contentType.toLowerCase(Locale.ENGLISH).indexOf(charset);
if (index > 0)
{
media = contentType.substring(0, index);
String encoding = contentType.substring(index + charset.length());
// Sometimes charsets arrive with an ending semicolon
index = encoding.indexOf(';');
if (index > 0)
encoding = encoding.substring(0, index);
int semicolon = encoding.indexOf(';');
if (semicolon > 0)
encoding = encoding.substring(0, semicolon).trim();
this.encoding = encoding;
}
int semicolon = media.indexOf(';');
if (semicolon > 0)
media = media.substring(0, semicolon).trim();
this.mediaType = media;
}
}
@Override
public void onContent(Response response, ByteBuffer content)
{
{
int length = content.remaining();
if (length>BufferUtil.space(buffer))
if (length > BufferUtil.space(buffer))
{
int requiredCapacity = buffer==null?0:buffer.capacity()+length;
if (requiredCapacity>maxLength)
int requiredCapacity = buffer == null ? 0 : buffer.capacity() + length;
if (requiredCapacity > maxLength)
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
int newCapacity = Math.min(Integer.highestOneBit(requiredCapacity) << 1, maxLength);
buffer = BufferUtil.ensureCapacity(buffer,newCapacity);
}
int newCapacity = Math.min(Integer.highestOneBit(requiredCapacity) << 1, maxLength);
buffer = BufferUtil.ensureCapacity(buffer, newCapacity);
}
BufferUtil.append(buffer, content);
}
@Override
public abstract void onComplete(Result result);
public String getMediaType()
{
return mediaType;
}
public String getEncoding()
{
return encoding;
@ -129,14 +138,14 @@ public abstract class BufferingResponseListener extends Listener.Adapter
*/
public byte[] getContent()
{
if (buffer==null)
if (buffer == null)
return new byte[0];
return BufferUtil.toArray(buffer);
}
/**
* @return the content as a string, using the "Content-Type" header to detect the encoding
* or defaulting to UTF-8 if the encoding could not be detected.
* or defaulting to UTF-8 if the encoding could not be detected.
* @see #getContentAsString(String)
*/
public String getContentAsString()
@ -154,7 +163,7 @@ public abstract class BufferingResponseListener extends Listener.Adapter
*/
public String getContentAsString(String encoding)
{
if (buffer==null)
if (buffer == null)
return null;
return BufferUtil.toString(buffer, Charset.forName(encoding));
}
@ -166,18 +175,17 @@ public abstract class BufferingResponseListener extends Listener.Adapter
*/
public String getContentAsString(Charset encoding)
{
if (buffer==null)
if (buffer == null)
return null;
return BufferUtil.toString(buffer, encoding);
}
/* ------------------------------------------------------------ */
/**
* @return Content as InputStream
*/
public InputStream getContentAsInputStream()
{
if (buffer==null)
if (buffer == null)
return new ByteArrayInputStream(new byte[]{});
return new ByteArrayInputStream(buffer.array(), buffer.arrayOffset(), buffer.remaining());
}

View File

@ -31,13 +31,19 @@ import org.eclipse.jetty.client.api.ContentProvider;
* and each invocation of the {@link #iterator()} method returns a {@link ByteBuffer#slice() slice}
* of the original {@link ByteBuffer}.
*/
public class ByteBufferContentProvider implements ContentProvider
public class ByteBufferContentProvider extends AbstractTypedContentProvider
{
private final ByteBuffer[] buffers;
private final int length;
public ByteBufferContentProvider(ByteBuffer... buffers)
{
this("application/octet-stream", buffers);
}
public ByteBufferContentProvider(String contentType, ByteBuffer... buffers)
{
super(contentType);
this.buffers = buffers;
int length = 0;
for (ByteBuffer buffer : buffers)

View File

@ -27,13 +27,19 @@ import org.eclipse.jetty.client.api.ContentProvider;
/**
* A {@link ContentProvider} for byte arrays.
*/
public class BytesContentProvider implements ContentProvider
public class BytesContentProvider extends AbstractTypedContentProvider
{
private final byte[][] bytes;
private final long length;
public BytesContentProvider(byte[]... bytes)
{
this("application/octet-stream", bytes);
}
public BytesContentProvider(String contentType, byte[]... bytes)
{
super(contentType);
this.bytes = bytes;
long length = 0;
for (byte[] buffer : bytes)

View File

@ -0,0 +1,78 @@
//
// ========================================================================
// 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.client.util;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.util.Fields;
/**
* A {@link ContentProvider} for form uploads with the
* "application/x-www-form-urlencoded" content type.
*/
public class FormContentProvider extends StringContentProvider
{
public FormContentProvider(Fields fields)
{
this(fields, StandardCharsets.UTF_8);
}
public FormContentProvider(Fields fields, Charset charset)
{
super("application/x-www-form-urlencoded", convert(fields, charset), charset);
}
public static String convert(Fields fields)
{
return convert(fields, StandardCharsets.UTF_8);
}
public static String convert(Fields fields, Charset charset)
{
// Assume 32 chars between name and value.
StringBuilder builder = new StringBuilder(fields.getSize() * 32);
for (Fields.Field field : fields)
{
for (String value : field.getValues())
{
if (builder.length() > 0)
builder.append("&");
builder.append(encode(field.getName(), charset)).append("=").append(encode(value, charset));
}
}
return builder.toString();
}
private static String encode(String value, Charset charset)
{
try
{
return URLEncoder.encode(value, charset.name());
}
catch (UnsupportedEncodingException x)
{
throw new UnsupportedCharsetException(charset.name());
}
}
}

View File

@ -70,7 +70,7 @@ public class FutureResponseListener extends BufferingResponseListener implements
@Override
public void onComplete(Result result)
{
response = new HttpContentResponse(result.getResponse(), getContent(), getEncoding());
response = new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding());
failure = result.getFailure();
latch.countDown();
}

View File

@ -40,7 +40,7 @@ import org.eclipse.jetty.util.log.Logger;
* It is possible to specify, at the constructor, a buffer size used to read content from the
* stream, by default 4096 bytes.
*/
public class PathContentProvider implements ContentProvider
public class PathContentProvider extends AbstractTypedContentProvider
{
private static final Logger LOG = Log.getLogger(PathContentProvider.class);
@ -55,6 +55,17 @@ public class PathContentProvider implements ContentProvider
public PathContentProvider(Path filePath, int bufferSize) throws IOException
{
this("application/octet-stream", filePath, bufferSize);
}
public PathContentProvider(String contentType, Path filePath) throws IOException
{
this(contentType, filePath, 4096);
}
public PathContentProvider(String contentType, Path filePath, int bufferSize) throws IOException
{
super(contentType);
if (!Files.isRegularFile(filePath))
throw new NoSuchFileException(filePath.toString());
if (!Files.isReadable(filePath))

View File

@ -43,6 +43,11 @@ public class StringContentProvider extends BytesContentProvider
public StringContentProvider(String content, Charset charset)
{
super(content.getBytes(charset));
this("text/plain;charset=" + charset.name(), content, charset);
}
public StringContentProvider(String contentType, String content, Charset charset)
{
super(contentType, content.getBytes(charset));
}
}

View File

@ -0,0 +1,124 @@
//
// ========================================================================
// 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.client;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.junit.Test;
public class ContentResponseTest extends AbstractHttpClientServerTest
{
public ContentResponseTest(SslContextFactory sslContextFactory)
{
super(sslContextFactory);
}
@Test
public void testResponseWithoutContentType() throws Exception
{
final byte[] content = new byte[1024];
new Random().nextBytes(content);
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.getOutputStream().write(content);
}
});
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(200, response.getStatus());
Assert.assertArrayEquals(content, response.getContent());
Assert.assertNull(response.getMediaType());
Assert.assertNull(response.getEncoding());
}
@Test
public void testResponseWithMediaType() throws Exception
{
final String content = "The quick brown fox jumped over the lazy dog";
final String mediaType = "text/plain";
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setHeader(HttpHeader.CONTENT_TYPE.asString(), mediaType);
response.getOutputStream().write(content.getBytes("UTF-8"));
}
});
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(content, response.getContentAsString());
Assert.assertEquals(mediaType, response.getMediaType());
Assert.assertNull(response.getEncoding());
}
@Test
public void testResponseWithContentType() throws Exception
{
final String content = "The quick brown fox jumped over the lazy dog";
final String mediaType = "text/plain";
final String encoding = "UTF-8";
final String contentType = mediaType + "; charset=" + encoding;
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setHeader(HttpHeader.CONTENT_TYPE.asString(), contentType);
response.getOutputStream().write(content.getBytes("UTF-8"));
}
});
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(content, response.getContentAsString());
Assert.assertEquals(mediaType, response.getMediaType());
Assert.assertEquals(encoding, response.getEncoding());
}
}

View File

@ -26,7 +26,6 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -244,6 +243,38 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
}
@Test
public void testRedirectWithWrongScheme() throws Exception
{
dispose();
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setStatus(303);
response.setHeader("Location", "ssh://localhost/path");
}
});
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/path")
.timeout(5, TimeUnit.SECONDS)
.send(new Response.CompleteListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertTrue(result.isFailed());
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
@Ignore
public void testRedirectFailed() throws Exception

View File

@ -22,7 +22,7 @@ import java.io.IOException;
import java.net.HttpCookie;
import java.net.URI;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
@ -83,12 +83,13 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Cookie[] cookies = request.getCookies();
Assert.assertNotNull(cookies);
Assert.assertEquals(1, cookies.length);
Cookie cookie = cookies[0];
Assert.assertEquals(name, cookie.getName());
Assert.assertEquals(value, cookie.getValue());
baseRequest.setHandled(true);
}
});
@ -122,4 +123,32 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(client.getCookieStore().getCookies().isEmpty());
}
@Test
public void test_PerRequestCookieIsSent() throws Exception
{
final String name = "foo";
final String value = "bar";
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Cookie[] cookies = request.getCookies();
Assert.assertNotNull(cookies);
Assert.assertEquals(1, cookies.length);
Cookie cookie = cookies[0];
Assert.assertEquals(name, cookie.getName());
Assert.assertEquals(value, cookie.getValue());
}
});
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.cookie(new HttpCookie(name, value))
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(200, response.getStatus());
}
}

View File

@ -27,9 +27,12 @@ import org.eclipse.jetty.client.EmptyServerHandler;
import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.hamcrest.Matchers;
@ -226,4 +229,34 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testDestinationIsRemoved() throws Exception
{
String host = "localhost";
int port = connector.getLocalPort();
Destination destinationBefore = client.getDestination(scheme, host, port);
ContentResponse response = client.newRequest(host, port)
.scheme(scheme)
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
.send();
Assert.assertEquals(200, response.getStatus());
Destination destinationAfter = client.getDestination(scheme, host, port);
Assert.assertSame(destinationBefore, destinationAfter);
client.setRemoveIdleDestinations(true);
response = client.newRequest(host, port)
.scheme(scheme)
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
.send();
Assert.assertEquals(200, response.getStatus());
destinationAfter = client.getDestination(scheme, host, port);
Assert.assertNotSame(destinationBefore, destinationAfter);
}
}

View File

@ -0,0 +1,141 @@
//
// ========================================================================
// 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.client.util;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.AbstractHttpClientServerTest;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
public class TypedContentProviderTest extends AbstractHttpClientServerTest
{
public TypedContentProviderTest(SslContextFactory sslContextFactory)
{
super(sslContextFactory);
}
@Test
public void testFormContentProvider() throws Exception
{
final String name1 = "a";
final String value1 = "1";
final String name2 = "b";
final String value2 = "2";
final String value3 = "\u20AC";
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Assert.assertEquals("POST", request.getMethod());
Assert.assertEquals(MimeTypes.Type.FORM_ENCODED.asString(), request.getContentType());
Assert.assertEquals(value1, request.getParameter(name1));
String[] values = request.getParameterValues(name2);
Assert.assertNotNull(values);
Assert.assertEquals(2, values.length);
Assert.assertThat(values, Matchers.arrayContainingInAnyOrder(value2, value3));
}
});
Fields fields = new Fields();
fields.put(name1, value1);
fields.add(name2, value2);
fields.add(name2, value3);
ContentResponse response = client.FORM(scheme + "://localhost:" + connector.getLocalPort(), fields);
Assert.assertEquals(200, response.getStatus());
}
@Test
public void testFormContentProviderWithDifferentContentType() throws Exception
{
final String name1 = "a";
final String value1 = "1";
final String name2 = "b";
final String value2 = "2";
Fields fields = new Fields();
fields.put(name1, value1);
fields.add(name2, value2);
final String content = FormContentProvider.convert(fields);
final String contentType = "text/plain;charset=UTF-8";
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Assert.assertEquals("POST", request.getMethod());
Assert.assertEquals(contentType, request.getContentType());
Assert.assertEquals(content, IO.toString(request.getInputStream()));
}
});
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.method(HttpMethod.POST)
.content(new FormContentProvider(fields))
.header(HttpHeader.CONTENT_TYPE, contentType)
.send();
Assert.assertEquals(200, response.getStatus());
}
@Test
public void testTypedContentProviderWithNoContentType() throws Exception
{
final String content = "data";
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Assert.assertEquals("GET", request.getMethod());
Assert.assertNull(request.getContentType());
Assert.assertEquals(content, IO.toString(request.getInputStream()));
}
});
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.content(new StringContentProvider(null, content, StandardCharsets.UTF_8))
.send();
Assert.assertEquals(200, response.getStatus());
}
}

View File

@ -331,7 +331,7 @@
<!-- ==================================================================== -->
<!-- Default MIME mappings -->
<!-- The default MIME mappings are provided by the mime.properties -->
<!-- resource in the org.eclipse.jetty.server.jar file. Additional or modified -->
<!-- resource in the jetty-http.jar file. Additional or modified -->
<!-- mappings may be specified here -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- UNCOMMENT TO ACTIVATE

View File

@ -739,7 +739,7 @@ public class ProxyServletTest
protected void onResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
{
byte[] content = temp.remove(request.getRequestURI()).toByteArray();
ContentResponse cached = new HttpContentResponse(proxyResponse, content, null);
ContentResponse cached = new HttpContentResponse(proxyResponse, content, null, null);
cache.put(request.getRequestURI(), cached);
super.onResponseSuccess(request, response, proxyResponse);
}

View File

@ -22,7 +22,6 @@ import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Locale;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
@ -360,7 +359,7 @@ public class FormAuthenticator extends LoginAuthenticator
{
LOG.debug("auth rePOST {}->{}",authentication,j_uri);
Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
base_request.setParameters(j_post);
base_request.setContentParameters(j_post);
}
session.removeAttribute(__J_URI);
session.removeAttribute(__J_METHOD);
@ -395,8 +394,9 @@ public class FormAuthenticator extends LoginAuthenticator
if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod()))
{
Request base_request = (req instanceof Request)?(Request)req:HttpChannel.getCurrentHttpChannel().getRequest();
base_request.extractParameters();
session.setAttribute(__J_POST, new MultiMap<String>(base_request.getParameters()));
MultiMap<String> formParameters = new MultiMap<>();
base_request.extractFormParameters(formParameters);
session.setAttribute(__J_POST, formParameters);
}
}
}

View File

@ -22,7 +22,6 @@ import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
@ -34,13 +33,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.UrlEncoded;
/* ------------------------------------------------------------ */
/** Servlet RequestDispatcher.
*
*
*/
public class Dispatcher implements RequestDispatcher
{
/** Dispatch include attribute names */
@ -49,71 +42,41 @@ public class Dispatcher implements RequestDispatcher
/** Dispatch include attribute names */
public final static String __FORWARD_PREFIX="javax.servlet.forward.";
/** JSP attributes */
public final static String __JSP_FILE="org.apache.catalina.jsp_file";
/* ------------------------------------------------------------ */
private final ContextHandler _contextHandler;
private final String _uri;
private final String _path;
private final String _dQuery;
private final String _query;
private final String _named;
/* ------------------------------------------------------------ */
/**
* @param contextHandler
* @param uri
* @param pathInContext
* @param query
*/
public Dispatcher(ContextHandler contextHandler, String uri, String pathInContext, String query)
{
_contextHandler=contextHandler;
_uri=uri;
_path=pathInContext;
_dQuery=query;
_query=query;
_named=null;
}
/* ------------------------------------------------------------ */
/** Constructor.
* @param contextHandler
* @param name
*/
public Dispatcher(ContextHandler contextHandler,String name)
throws IllegalStateException
public Dispatcher(ContextHandler contextHandler, String name) throws IllegalStateException
{
_contextHandler=contextHandler;
_named=name;
_uri=null;
_path=null;
_dQuery=null;
_query=null;
}
/* ------------------------------------------------------------ */
/*
* @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
*/
@Override
public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException
{
forward(request, response, DispatcherType.FORWARD);
}
/* ------------------------------------------------------------ */
/*
* @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
*/
public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException
{
forward(request, response, DispatcherType.ERROR);
}
/* ------------------------------------------------------------ */
/*
* @see javax.servlet.RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
*/
@Override
public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException
{
@ -126,70 +89,48 @@ public class Dispatcher implements RequestDispatcher
final DispatcherType old_type = baseRequest.getDispatcherType();
final Attributes old_attr=baseRequest.getAttributes();
MultiMap<String> old_params=baseRequest.getParameters();
final MultiMap<String> old_query_params=baseRequest.getQueryParameters();
try
{
baseRequest.setDispatcherType(DispatcherType.INCLUDE);
baseRequest.getResponse().include();
if (_named!=null)
{
_contextHandler.handle(_named,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
}
else
{
String query=_dQuery;
if (query!=null)
{
// force parameter extraction
if (old_params==null)
{
baseRequest.extractParameters();
old_params=baseRequest.getParameters();
}
MultiMap<String> parameters=new MultiMap<>();
UrlEncoded.decodeTo(query,parameters,baseRequest.getCharacterEncoding(),-1);
if(old_params != null) {
// Merge parameters.
parameters.addAllValues(old_params);
}
baseRequest.setParameters(parameters);
}
IncludeAttributes attr = new IncludeAttributes(old_attr);
attr._requestURI=_uri;
attr._contextPath=_contextHandler.getContextPath();
attr._servletPath=null; // set by ServletHandler
attr._pathInfo=_path;
attr._query=query;
attr._query=_query;
if (_query!=null)
baseRequest.mergeQueryParameters(_query, false);
baseRequest.setAttributes(attr);
_contextHandler.handle(_path,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
_contextHandler.handle(_path, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
}
}
finally
{
baseRequest.setAttributes(old_attr);
baseRequest.getResponse().included();
baseRequest.setParameters(old_params);
baseRequest.setQueryParameters(old_query_params);
baseRequest.resetParameters();
baseRequest.setDispatcherType(old_type);
}
}
/* ------------------------------------------------------------ */
/*
* @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
*/
protected void forward(ServletRequest request, ServletResponse response, DispatcherType dispatch) throws ServletException, IOException
{
Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
Response base_response=baseRequest.getResponse();
base_response.resetForForward();
if (!(request instanceof HttpServletRequest))
request = new ServletRequestHttpWrapper(request);
if (!(response instanceof HttpServletResponse))
@ -201,9 +142,9 @@ public class Dispatcher implements RequestDispatcher
final String old_servlet_path=baseRequest.getServletPath();
final String old_path_info=baseRequest.getPathInfo();
final String old_query=baseRequest.getQueryString();
final MultiMap<String> old_query_params=baseRequest.getQueryParameters();
final Attributes old_attr=baseRequest.getAttributes();
final DispatcherType old_type=baseRequest.getDispatcherType();
MultiMap<String> old_params=baseRequest.getParameters();
try
{
@ -211,24 +152,11 @@ public class Dispatcher implements RequestDispatcher
baseRequest.setDispatcherType(dispatch);
if (_named!=null)
_contextHandler.handle(_named,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
{
_contextHandler.handle(_named, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
}
else
{
// process any query string from the dispatch URL
String query=_dQuery;
if (query!=null)
{
// force parameter extraction
if (old_params==null)
{
baseRequest.extractParameters();
old_params=baseRequest.getParameters();
}
baseRequest.mergeQueryString(query);
}
ForwardAttributes attr = new ForwardAttributes(old_attr);
//If we have already been forwarded previously, then keep using the established
@ -256,9 +184,11 @@ public class Dispatcher implements RequestDispatcher
baseRequest.setContextPath(_contextHandler.getContextPath());
baseRequest.setServletPath(null);
baseRequest.setPathInfo(_uri);
if (_query!=null)
baseRequest.mergeQueryParameters(_query, true);
baseRequest.setAttributes(attr);
_contextHandler.handle(_path,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
_contextHandler.handle(_path, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
if (!baseRequest.getHttpChannelState().isAsync())
commitResponse(response,baseRequest);
@ -271,15 +201,14 @@ public class Dispatcher implements RequestDispatcher
baseRequest.setContextPath(old_context_path);
baseRequest.setServletPath(old_servlet_path);
baseRequest.setPathInfo(old_path_info);
baseRequest.setAttributes(old_attr);
baseRequest.setParameters(old_params);
baseRequest.setQueryString(old_query);
baseRequest.setQueryParameters(old_query_params);
baseRequest.resetParameters();
baseRequest.setAttributes(old_attr);
baseRequest.setDispatcherType(old_type);
}
}
/* ------------------------------------------------------------ */
private void commitResponse(ServletResponse response, Request baseRequest) throws IOException
{
if (baseRequest.getResponse().isWriting())
@ -306,10 +235,6 @@ public class Dispatcher implements RequestDispatcher
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private class ForwardAttributes implements Attributes
{
final Attributes _attr;
@ -349,7 +274,6 @@ public class Dispatcher implements RequestDispatcher
return _attr.getAttribute(key);
}
/* ------------------------------------------------------------ */
@Override
public Enumeration<String> getAttributeNames()
{
@ -381,7 +305,6 @@ public class Dispatcher implements RequestDispatcher
return Collections.enumeration(set);
}
/* ------------------------------------------------------------ */
@Override
public void setAttribute(String key, Object value)
{
@ -409,21 +332,18 @@ public class Dispatcher implements RequestDispatcher
_attr.setAttribute(key,value);
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return "FORWARD+"+_attr.toString();
}
/* ------------------------------------------------------------ */
@Override
public void clearAttributes()
{
throw new IllegalStateException();
}
/* ------------------------------------------------------------ */
@Override
public void removeAttribute(String name)
{
@ -431,7 +351,6 @@ public class Dispatcher implements RequestDispatcher
}
}
/* ------------------------------------------------------------ */
private class IncludeAttributes implements Attributes
{
final Attributes _attr;
@ -447,9 +366,6 @@ public class Dispatcher implements RequestDispatcher
_attr=attributes;
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
@Override
public Object getAttribute(String key)
{
@ -468,7 +384,6 @@ public class Dispatcher implements RequestDispatcher
return _attr.getAttribute(key);
}
/* ------------------------------------------------------------ */
@Override
public Enumeration<String> getAttributeNames()
{
@ -499,7 +414,6 @@ public class Dispatcher implements RequestDispatcher
return Collections.enumeration(set);
}
/* ------------------------------------------------------------ */
@Override
public void setAttribute(String key, Object value)
{
@ -521,21 +435,18 @@ public class Dispatcher implements RequestDispatcher
_attr.setAttribute(key,value);
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return "INCLUDE+"+_attr.toString();
}
/* ------------------------------------------------------------ */
@Override
public void clearAttributes()
{
throw new IllegalStateException();
}
/* ------------------------------------------------------------ */
@Override
public void removeAttribute(String name)
{

View File

@ -40,7 +40,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncListener;
import javax.servlet.DispatcherType;
@ -177,7 +176,6 @@ public class Request implements HttpServletRequest
private boolean _requestedSessionIdFromCookie = false;
private volatile Attributes _attributes;
private Authentication _authentication;
private MultiMap<String> _baseParameters;
private String _characterEncoding;
private ContextHandler.Context _context;
private String _contextPath;
@ -186,6 +184,8 @@ public class Request implements HttpServletRequest
private int _inputState = __NONE;
private HttpMethod _httpMethod;
private String _httpMethodString;
private MultiMap<String> _queryParameters;
private MultiMap<String> _contentParameters;
private MultiMap<String> _parameters;
private String _pathInfo;
private int _port;
@ -237,148 +237,43 @@ public class Request implements HttpServletRequest
throw new IllegalArgumentException(listener.getClass().toString());
}
/* ------------------------------------------------------------ */
/**
* Extract Parameters from query string and/or form _content.
*/
public void extractParameters()
{
if (_baseParameters == null)
_baseParameters = new MultiMap<>();
if (_paramsExtracted)
{
if (_parameters == null)
_parameters = _baseParameters;
return;
}
_paramsExtracted = true;
try
// Extract query string parameters; these may be replaced by a forward()
// and may have already been extracted by mergeQueryParameters().
if (_queryParameters == null)
_queryParameters = extractQueryParameters();
// Extract content parameters; these cannot be replaced by a forward()
// once extracted and may have already been extracted by getParts() or
// by a processing happening after a form-based authentication.
if (_contentParameters == null)
_contentParameters = extractContentParameters();
_parameters = restoreParameters();
}
private MultiMap<String> extractQueryParameters()
{
MultiMap<String> result = new MultiMap<>();
if (_uri != null && _uri.hasQuery())
{
// Handle query string
if (_uri != null && _uri.hasQuery())
if (_queryEncoding == null)
{
if (_queryEncoding == null)
_uri.decodeQueryTo(_baseParameters);
else
{
try
{
_uri.decodeQueryTo(_baseParameters,_queryEncoding);
}
catch (UnsupportedEncodingException e)
{
if (LOG.isDebugEnabled())
LOG.warn(e);
else
LOG.warn(e.toString());
}
}
_uri.decodeQueryTo(result);
}
// handle any _content.
String encoding = getCharacterEncoding();
String content_type = getContentType();
if (content_type != null && content_type.length() > 0)
{
content_type = HttpFields.valueParameters(content_type,null);
if (MimeTypes.Type.FORM_ENCODED.is(content_type) && _inputState == __NONE &&
(HttpMethod.POST.is(getMethod()) || HttpMethod.PUT.is(getMethod())))
{
int content_length = getContentLength();
if (content_length != 0)
{
try
{
int maxFormContentSize = -1;
int maxFormKeys = -1;
if (_context != null)
{
maxFormContentSize = _context.getContextHandler().getMaxFormContentSize();
maxFormKeys = _context.getContextHandler().getMaxFormKeys();
}
if (maxFormContentSize < 0)
{
Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
if (obj == null)
maxFormContentSize = 200000;
else if (obj instanceof Number)
{
Number size = (Number)obj;
maxFormContentSize = size.intValue();
}
else if (obj instanceof String)
{
maxFormContentSize = Integer.valueOf((String)obj);
}
}
if (maxFormKeys < 0)
{
Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys");
if (obj == null)
maxFormKeys = 1000;
else if (obj instanceof Number)
{
Number keys = (Number)obj;
maxFormKeys = keys.intValue();
}
else if (obj instanceof String)
{
maxFormKeys = Integer.valueOf((String)obj);
}
}
if (content_length > maxFormContentSize && maxFormContentSize > 0)
{
throw new IllegalStateException("Form too large " + content_length + ">" + maxFormContentSize);
}
InputStream in = getInputStream();
if (_input.isAsync())
throw new IllegalStateException("Cannot extract parameters with async IO");
// Add form params to query params
UrlEncoded.decodeTo(in,_baseParameters,encoding,content_length < 0?maxFormContentSize:-1,maxFormKeys);
}
catch (IOException e)
{
if (LOG.isDebugEnabled())
LOG.warn(e);
else
LOG.warn(e.toString());
}
}
}
}
if (_parameters == null)
_parameters = _baseParameters;
else if (_parameters != _baseParameters)
{
// Merge parameters (needed if parameters extracted after a forward).
_parameters.addAllValues(_baseParameters);
}
if (content_type != null && content_type.length()>0 && content_type.startsWith("multipart/form-data") && getAttribute(__MULTIPART_CONFIG_ELEMENT)!=null)
else
{
try
{
getParts();
_uri.decodeQueryTo(result, _queryEncoding);
}
catch (IOException e)
{
if (LOG.isDebugEnabled())
LOG.warn(e);
else
LOG.warn(e.toString());
}
catch (ServletException e)
catch (UnsupportedEncodingException e)
{
if (LOG.isDebugEnabled())
LOG.warn(e);
@ -387,11 +282,114 @@ public class Request implements HttpServletRequest
}
}
}
finally
return result;
}
private MultiMap<String> extractContentParameters()
{
MultiMap<String> result = new MultiMap<>();
String contentType = getContentType();
if (contentType != null && !contentType.isEmpty())
{
// ensure params always set (even if empty) after extraction
if (_parameters == null)
_parameters = _baseParameters;
contentType = HttpFields.valueParameters(contentType, null);
int contentLength = getContentLength();
if (contentLength != 0)
{
if (MimeTypes.Type.FORM_ENCODED.is(contentType) && _inputState == __NONE &&
(HttpMethod.POST.is(getMethod()) || HttpMethod.PUT.is(getMethod())))
{
extractFormParameters(result);
}
else if (contentType.startsWith("multipart/form-data") &&
getAttribute(__MULTIPART_CONFIG_ELEMENT) != null &&
_multiPartInputStream == null)
{
extractMultipartParameters(result);
}
}
}
return result;
}
public void extractFormParameters(MultiMap<String> params)
{
try
{
int maxFormContentSize = -1;
int maxFormKeys = -1;
if (_context != null)
{
maxFormContentSize = _context.getContextHandler().getMaxFormContentSize();
maxFormKeys = _context.getContextHandler().getMaxFormKeys();
}
if (maxFormContentSize < 0)
{
Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
if (obj == null)
maxFormContentSize = 200000;
else if (obj instanceof Number)
{
Number size = (Number)obj;
maxFormContentSize = size.intValue();
}
else if (obj instanceof String)
{
maxFormContentSize = Integer.valueOf((String)obj);
}
}
if (maxFormKeys < 0)
{
Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys");
if (obj == null)
maxFormKeys = 1000;
else if (obj instanceof Number)
{
Number keys = (Number)obj;
maxFormKeys = keys.intValue();
}
else if (obj instanceof String)
{
maxFormKeys = Integer.valueOf((String)obj);
}
}
int contentLength = getContentLength();
if (contentLength > maxFormContentSize && maxFormContentSize > 0)
{
throw new IllegalStateException("Form too large: " + contentLength + " > " + maxFormContentSize);
}
InputStream in = getInputStream();
if (_input.isAsync())
throw new IllegalStateException("Cannot extract parameters with async IO");
UrlEncoded.decodeTo(in,params,getCharacterEncoding(),contentLength<0?maxFormContentSize:-1,maxFormKeys);
}
catch (IOException e)
{
if (LOG.isDebugEnabled())
LOG.warn(e);
else
LOG.warn(e.toString());
}
}
private void extractMultipartParameters(MultiMap<String> result)
{
try
{
getParts(result);
}
catch (IOException | ServletException e)
{
if (LOG.isDebugEnabled())
LOG.warn(e);
else
LOG.warn(e.toString());
}
}
@ -827,6 +825,8 @@ public class Request implements HttpServletRequest
{
if (!_paramsExtracted)
extractParameters();
if (_parameters == null)
_parameters = restoreParameters();
return _parameters.getValue(name,0);
}
@ -839,7 +839,8 @@ public class Request implements HttpServletRequest
{
if (!_paramsExtracted)
extractParameters();
if (_parameters == null)
_parameters = restoreParameters();
return Collections.unmodifiableMap(_parameters.toStringArrayMap());
}
@ -852,18 +853,11 @@ public class Request implements HttpServletRequest
{
if (!_paramsExtracted)
extractParameters();
if (_parameters == null)
_parameters = restoreParameters();
return Collections.enumeration(_parameters.keySet());
}
/* ------------------------------------------------------------ */
/**
* @return Returns the parameters.
*/
public MultiMap<String> getParameters()
{
return _parameters;
}
/* ------------------------------------------------------------ */
/*
* @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
@ -873,12 +867,44 @@ public class Request implements HttpServletRequest
{
if (!_paramsExtracted)
extractParameters();
if (_parameters == null)
_parameters = restoreParameters();
List<String> vals = _parameters.getValues(name);
if (vals == null)
return null;
return vals.toArray(new String[vals.size()]);
}
private MultiMap<String> restoreParameters()
{
MultiMap<String> result = new MultiMap<>();
if (_queryParameters == null)
_queryParameters = extractQueryParameters();
result.addAllValues(_queryParameters);
result.addAllValues(_contentParameters);
return result;
}
public MultiMap<String> getQueryParameters()
{
return _queryParameters;
}
public void setQueryParameters(MultiMap<String> queryParameters)
{
_queryParameters = queryParameters;
}
public void setContentParameters(MultiMap<String> contentParameters)
{
_contentParameters = contentParameters;
}
public void resetParameters()
{
_parameters = null;
}
/* ------------------------------------------------------------ */
/*
* @see javax.servlet.http.HttpServletRequest#getPathInfo()
@ -1370,6 +1396,9 @@ public class Request implements HttpServletRequest
if (!create)
return null;
if (getResponse().isCommitted())
throw new IllegalStateException("Response is committed");
if (_sessionManager == null)
throw new IllegalStateException("No SessionManager");
@ -1620,8 +1649,8 @@ public class Request implements HttpServletRequest
_servletPath = null;
_timeStamp = 0;
_uri = null;
if (_baseParameters != null)
_baseParameters.clear();
_queryParameters = null;
_contentParameters = null;
_parameters = null;
_paramsExtracted = false;
_inputState = __NONE;
@ -1858,18 +1887,6 @@ public class Request implements HttpServletRequest
return HttpMethod.HEAD==_httpMethod;
}
/* ------------------------------------------------------------ */
/**
* @param parameters
* The parameters to set.
*/
public void setParameters(MultiMap<String> parameters)
{
_parameters = (parameters == null)?_baseParameters:parameters;
if (_paramsExtracted && _parameters == null)
throw new IllegalStateException();
}
/* ------------------------------------------------------------ */
/**
* @param pathInfo
@ -2103,10 +2120,14 @@ public class Request implements HttpServletRequest
{
if (getContentType() == null || !getContentType().startsWith("multipart/form-data"))
throw new ServletException("Content-Type != multipart/form-data");
return getParts(null);
}
private Collection<Part> getParts(MultiMap<String> params) throws IOException, ServletException
{
if (_multiPartInputStream == null)
_multiPartInputStream = (MultiPartInputStreamParser)getAttribute(__MULTIPART_INPUT_STREAM);
if (_multiPartInputStream == null)
{
MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT);
@ -2127,20 +2148,20 @@ public class Request implements HttpServletRequest
MultiPartInputStreamParser.MultiPart mp = (MultiPartInputStreamParser.MultiPart)p;
if (mp.getContentDispositionFilename() == null)
{
//Servlet Spec 3.0 pg 23, parts without filenames must be put into init params
// Servlet Spec 3.0 pg 23, parts without filename must be put into params.
String charset = null;
if (mp.getContentType() != null)
charset = MimeTypes.getCharsetFromContentType(mp.getContentType());
//get the bytes regardless of being in memory or in temp file
try (InputStream is = mp.getInputStream())
{
if (os == null)
os = new ByteArrayOutputStream();
IO.copy(is, os);
String content=new String(os.toByteArray(),charset==null?StandardCharsets.UTF_8:Charset.forName(charset));
getParameter(""); //cause params to be evaluated
getParameters().add(mp.getName(), content);
if (_contentParameters == null)
_contentParameters = params == null ? new MultiMap<String>() : params;
_contentParameters.add(mp.getName(), content);
}
os.reset();
}
@ -2175,72 +2196,48 @@ public class Request implements HttpServletRequest
_authentication=Authentication.UNAUTHENTICATED;
}
/* ------------------------------------------------------------ */
/**
* Merge in a new query string. The query string is merged with the existing parameters and {@link #setParameters(MultiMap)} and
* {@link #setQueryString(String)} are called with the result. The merge is according to the rules of the servlet dispatch forward method.
*
* @param query
* The query string to merge into the request.
*/
public void mergeQueryString(String query)
public void mergeQueryParameters(String newQuery, boolean updateQueryString)
{
// extract parameters from dispatch query
MultiMap<String> parameters = new MultiMap<>();
UrlEncoded.decodeTo(query,parameters, UrlEncoded.ENCODING,-1); //have to assume ENCODING because we can't know otherwise
MultiMap<String> newQueryParams = new MultiMap<>();
// Have to assume ENCODING because we can't know otherwise.
UrlEncoded.decodeTo(newQuery, newQueryParams, UrlEncoded.ENCODING, -1);
boolean merge_old_query = false;
// Have we evaluated parameters
if (!_paramsExtracted)
extractParameters();
// Are there any existing parameters?
if (_parameters != null && _parameters.size() > 0)
MultiMap<String> oldQueryParams = _queryParameters;
if (oldQueryParams == null && _queryString != null)
{
// Merge parameters; new parameters of the same name take precedence.
merge_old_query = parameters.addAllValues(_parameters);
oldQueryParams = new MultiMap<>();
UrlEncoded.decodeTo(_queryString, oldQueryParams, getQueryEncoding(), -1);
}
if (_queryString != null && _queryString.length() > 0)
MultiMap<String> mergedQueryParams = newQueryParams;
if (oldQueryParams != null)
{
if (merge_old_query)
{
StringBuilder overridden_query_string = new StringBuilder();
MultiMap<String> overridden_old_query = new MultiMap<>();
UrlEncoded.decodeTo(_queryString,overridden_old_query,getQueryEncoding(),-1);//decode using any queryencoding set for the request
MultiMap<String> overridden_new_query = new MultiMap<>();
UrlEncoded.decodeTo(query,overridden_new_query,UrlEncoded.ENCODING,-1); //have to assume ENCODING as we cannot know otherwise
for(String name: overridden_old_query.keySet())
{
if (!overridden_new_query.containsKey(name))
{
List<String> values = overridden_old_query.get(name);
for(String v: values)
{
overridden_query_string.append("&").append(name).append("=").append(v);
}
}
}
query = query + overridden_query_string;
}
else
{
query = query + "&" + _queryString;
}
// Parameters values are accumulated.
mergedQueryParams = new MultiMap<>(newQueryParams);
mergedQueryParams.addAllValues(oldQueryParams);
}
setParameters(parameters);
setQueryString(query);
setQueryParameters(mergedQueryParams);
resetParameters();
if (updateQueryString)
{
// Build the new merged query string, parameters in the
// new query string hide parameters in the old query string.
StringBuilder mergedQuery = new StringBuilder(newQuery);
for (Map.Entry<String, List<String>> entry : mergedQueryParams.entrySet())
{
if (newQueryParams.containsKey(entry.getKey()))
continue;
for (String value : entry.getValue())
mergedQuery.append("&").append(entry.getKey()).append("=").append(value);
}
setQueryString(mergedQuery.toString());
}
}
/**
/**
* @see javax.servlet.http.HttpServletRequest#upgrade(java.lang.Class)
*/
@Override

View File

@ -32,7 +32,6 @@ import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -502,7 +501,7 @@ public class Server extends HandlerWrapper implements Attributes
baseRequest.setRequestURI(null);
baseRequest.setPathInfo(baseRequest.getRequestURI());
if (uri.getQuery()!=null)
baseRequest.mergeQueryString(uri.getQuery()); //we have to assume dispatch path and query are UTF8
baseRequest.mergeQueryParameters(uri.getQuery(), true); //we have to assume dispatch path and query are UTF8
}
final String target=baseRequest.getPathInfo();

View File

@ -26,6 +26,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.BufferedReader;
import java.io.File;
@ -711,6 +712,41 @@ public class RequestTest
assertTrue(responses.indexOf("read='param=wrong' param=right")>0);
}
@Test
public void testSessionAfterRedirect() throws Exception
{
Handler handler = new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException
{
baseRequest.setHandled(true);
response.sendRedirect("/foo");
try
{
request.getSession(true);
fail("Session should not be created after response committed");
}
catch (IllegalStateException e)
{
//expected
}
catch (Exception e)
{
fail("Session creation after response commit should throw IllegalStateException");
}
}
};
_server.stop();
_server.setHandler(handler);
_server.start();
String response=_connector.getResponses("GET / HTTP/1.1\n"+
"Host: myhost\n"+
"Connection: close\n"+
"\n");
}
@Test
public void testPartialInput() throws Exception

View File

@ -720,7 +720,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
{
if (!isStarted())
throw new UnavailableException("Servlet not initialized", -1);
if (_unavailable!=0 || !_initOnStartup)
if (_unavailable!=0 || (!_initOnStartup && servlet==null))
servlet=getServlet();
if (servlet==null)
throw new UnavailableException("Could not instantiate "+_class);

View File

@ -0,0 +1,518 @@
//
// ========================================================================
// 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.servlet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
public class DispatcherForwardTest
{
private Server server;
private LocalConnector connector;
private HttpServlet servlet1;
private HttpServlet servlet2;
private List<Exception> failures = new ArrayList<>();
public void prepare() throws Exception
{
server = new Server();
connector = new LocalConnector(server);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler(server, "/");
context.addServlet(new ServletHolder(servlet1), "/one");
context.addServlet(new ServletHolder(servlet2), "/two");
server.start();
}
@After
public void dispose() throws Exception
{
for (Exception failure : failures)
throw failure;
server.stop();
}
// Replacement for Assert that allows to check failures after the response has been sent.
private <S> void checkThat(S item, Matcher<S> matcher)
{
if (!matcher.matches(item))
failures.add(new Exception());
}
@Test
public void testQueryRetainedByForwardWithoutQuery() throws Exception
{
// 1. request /one?a=1
// 1. forward /two
// 2. assert query => a=1
// 1. assert query => a=1
final String query1 = "a=1";
servlet1 = new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query1, Matchers.equalTo(req.getQueryString()));
req.getRequestDispatcher("/two").forward(req, resp);
checkThat(query1, Matchers.equalTo(req.getQueryString()));
checkThat("1", Matchers.equalTo(req.getParameter("a")));
}
};
servlet2 = new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query1, Matchers.equalTo(req.getQueryString()));
checkThat("1", Matchers.equalTo(req.getParameter("a")));
}
};
prepare();
String request = "" +
"GET /one?" + query1 + " HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n" +
"\r\n";
String response = connector.getResponses(request);
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@Test
public void testQueryReplacedByForwardWithQuery() throws Exception
{
// 1. request /one?a=1
// 1. forward /two?a=2
// 2. assert query => a=2
// 1. assert query => a=1
final String query1 = "a=1&b=2";
final String query2 = "a=3";
final String query3 = "a=3&b=2";
servlet1 = new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query1, Matchers.equalTo(req.getQueryString()));
req.getRequestDispatcher("/two?" + query2).forward(req, resp);
checkThat(query1, Matchers.equalTo(req.getQueryString()));
checkThat("1", Matchers.equalTo(req.getParameter("a")));
checkThat("2", Matchers.equalTo(req.getParameter("b")));
}
};
servlet2 = new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query3, Matchers.equalTo(req.getQueryString()));
checkThat("3", Matchers.equalTo(req.getParameter("a")));
checkThat("2", Matchers.equalTo(req.getParameter("b")));
}
};
prepare();
String request = "" +
"GET /one?" + query1 + " HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n" +
"\r\n";
String response = connector.getResponses(request);
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@Test
public void testQueryMergedByForwardWithQuery() throws Exception
{
// 1. request /one?a=1
// 1. forward /two?b=2
// 2. assert query => a=1&b=2
// 1. assert query => a=1
final String query1 = "a=1";
final String query2 = "b=2";
final String query3 = "b=2&a=1";
servlet1 = new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query1, Matchers.equalTo(req.getQueryString()));
req.getRequestDispatcher("/two?" + query2).forward(req, resp);
checkThat(query1, Matchers.equalTo(req.getQueryString()));
checkThat("1", Matchers.equalTo(req.getParameter("a")));
}
};
servlet2 = new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query3, Matchers.equalTo(req.getQueryString()));
checkThat("1", Matchers.equalTo(req.getParameter("a")));
checkThat("2", Matchers.equalTo(req.getParameter("b")));
}
};
prepare();
String request = "" +
"GET /one?" + query1 + " HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n" +
"\r\n";
String response = connector.getResponses(request);
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@Test
public void testQueryAggregatesWithFormByForwardWithoutQuery() throws Exception
{
// 1. request /one?a=1 + content a=2
// 1. forward /two
// 2. assert query => a=1 + params => a=1,2
// 1. assert query => a=1 + params => a=1,2
final String query1 = "a=1";
final String form = "a=2";
servlet1 = new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query1, Matchers.equalTo(req.getQueryString()));
req.getRequestDispatcher("/two").forward(req, resp);
checkThat(query1, Matchers.equalTo(req.getQueryString()));
String[] values = req.getParameterValues("a");
checkThat(values, Matchers.notNullValue());
checkThat(2, Matchers.equalTo(values.length));
checkThat(values, Matchers.arrayContainingInAnyOrder("1", "2"));
}
};
servlet2 = new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query1, Matchers.equalTo(req.getQueryString()));
String[] values = req.getParameterValues("a");
checkThat(values, Matchers.notNullValue());
checkThat(2, Matchers.equalTo(values.length));
checkThat(values, Matchers.arrayContainingInAnyOrder("1", "2"));
}
};
prepare();
String request = "" +
"POST /one?" + query1 + " HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Content-Length: " + form.length() + "\r\n" +
"Connection: close\r\n" +
"\r\n" +
form;
String response = connector.getResponses(request);
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@Test
public void testQueryAggregatesWithFormReplacedByForwardWithQuery() throws Exception
{
// 1. request /one?a=1 + content a=2
// 1. forward /two?a=3
// 2. assert query => a=3 + params => a=3,2,1
// 1. assert query => a=1 + params => a=1,2
final String query1 = "a=1";
final String query2 = "a=3";
final String form = "a=2";
servlet1 = new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query1, Matchers.equalTo(req.getQueryString()));
req.getRequestDispatcher("/two?" + query2).forward(req, resp);
checkThat(query1, Matchers.equalTo(req.getQueryString()));
String[] values = req.getParameterValues("a");
checkThat(values, Matchers.notNullValue());
checkThat(2, Matchers.equalTo(values.length));
checkThat(values, Matchers.arrayContainingInAnyOrder("1", "2"));
}
};
servlet2 = new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query2, Matchers.equalTo(req.getQueryString()));
String[] values = req.getParameterValues("a");
checkThat(values, Matchers.notNullValue());
checkThat(3, Matchers.equalTo(values.length));
checkThat(values, Matchers.arrayContainingInAnyOrder("3", "2", "1"));
}
};
prepare();
String request = "" +
"POST /one?" + query1 + " HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Content-Length: " + form.length() + "\r\n" +
"Connection: close\r\n" +
"\r\n" +
form;
String response = connector.getResponses(request);
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@Test
public void testQueryAggregatesWithFormMergedByForwardWithQuery() throws Exception
{
// 1. request /one?a=1 + content b=2
// 1. forward /two?c=3
// 2. assert query => a=1&c=3 + params => a=1&b=2&c=3
// 1. assert query => a=1 + params => a=1&b=2
final String query1 = "a=1";
final String query2 = "c=3";
final String query3 = "c=3&a=1";
final String form = "b=2";
servlet1 = new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query1, Matchers.equalTo(req.getQueryString()));
req.getRequestDispatcher("/two?" + query2).forward(req, resp);
checkThat(query1, Matchers.equalTo(req.getQueryString()));
checkThat("1", Matchers.equalTo(req.getParameter("a")));
checkThat("2", Matchers.equalTo(req.getParameter("b")));
checkThat(req.getParameter("c"), Matchers.nullValue());
}
};
servlet2 = new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query3, Matchers.equalTo(req.getQueryString()));
checkThat("1", Matchers.equalTo(req.getParameter("a")));
checkThat("2", Matchers.equalTo(req.getParameter("b")));
checkThat("3", Matchers.equalTo(req.getParameter("c")));
}
};
prepare();
String request = "" +
"POST /one?" + query1 + " HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Content-Length: " + form.length() + "\r\n" +
"Connection: close\r\n" +
"\r\n" +
form;
String response = connector.getResponses(request);
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@Test
public void testQueryAggregatesWithFormBeforeAndAfterForward() throws Exception
{
// 1. request /one?a=1 + content b=2
// 1. assert params => a=1&b=2
// 1. forward /two?c=3
// 2. assert query => a=1&c=3 + params => a=1&b=2&c=3
// 1. assert query => a=1 + params => a=1&b=2
final String query1 = "a=1";
final String query2 = "c=3";
final String query3 = "c=3&a=1";
final String form = "b=2";
servlet1 = new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query1, Matchers.equalTo(req.getQueryString()));
checkThat("1", Matchers.equalTo(req.getParameter("a")));
checkThat("2", Matchers.equalTo(req.getParameter("b")));
req.getRequestDispatcher("/two?" + query2).forward(req, resp);
checkThat(query1, Matchers.equalTo(req.getQueryString()));
checkThat("1", Matchers.equalTo(req.getParameter("a")));
checkThat("2", Matchers.equalTo(req.getParameter("b")));
checkThat(req.getParameter("c"), Matchers.nullValue());
}
};
servlet2 = new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query3, Matchers.equalTo(req.getQueryString()));
checkThat("1", Matchers.equalTo(req.getParameter("a")));
checkThat("2", Matchers.equalTo(req.getParameter("b")));
checkThat("3", Matchers.equalTo(req.getParameter("c")));
}
};
prepare();
String request = "" +
"POST /one?" + query1 + " HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Content-Length: " + form.length() + "\r\n" +
"Connection: close\r\n" +
"\r\n" +
form;
String response = connector.getResponses(request);
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@Test
public void testContentCanBeReadViaInputStreamAfterForwardWithoutQuery() throws Exception
{
final String query1 = "a=1";
final String form = "c=3";
servlet1 = new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query1, Matchers.equalTo(req.getQueryString()));
req.getRequestDispatcher("/two").forward(req, resp);
checkThat(query1, Matchers.equalTo(req.getQueryString()));
checkThat(req.getParameter("c"), Matchers.nullValue());
}
};
servlet2 = new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query1, Matchers.equalTo(req.getQueryString()));
ServletInputStream input = req.getInputStream();
for (int i = 0; i < form.length(); ++i)
checkThat(form.charAt(i) & 0xFFFF, Matchers.equalTo(input.read()));
}
};
prepare();
String request = "" +
"POST /one?" + query1 + " HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Content-Length: " + form.length() + "\r\n" +
"Connection: close\r\n" +
"\r\n" +
form;
String response = connector.getResponses(request);
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@Test
public void testContentCanBeReadViaInputStreamAfterForwardWithQuery() throws Exception
{
final String query1 = "a=1";
final String query2 = "b=2";
final String query3 = "b=2&a=1";
final String form = "c=3";
servlet1 = new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query1, Matchers.equalTo(req.getQueryString()));
req.getRequestDispatcher("/two?" + query2).forward(req, resp);
checkThat(query1, Matchers.equalTo(req.getQueryString()));
checkThat(req.getParameter("c"), Matchers.nullValue());
}
};
servlet2 = new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(query3, Matchers.equalTo(req.getQueryString()));
ServletInputStream input = req.getInputStream();
for (int i = 0; i < form.length(); ++i)
checkThat(form.charAt(i) & 0xFFFF, Matchers.equalTo(input.read()));
checkThat(-1, Matchers.equalTo(input.read()));
}
};
prepare();
String request = "" +
"POST /one?" + query1 + " HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Content-Length: " + form.length() + "\r\n" +
"Connection: close\r\n" +
"\r\n" +
form;
String response = connector.getResponses(request);
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
// TODO: add multipart tests
}

View File

@ -18,18 +18,11 @@
package org.eclipse.jetty.servlet;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@ -64,6 +57,12 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
public class DispatcherTest
{
private Server _server;
@ -349,7 +348,6 @@ public class DispatcherTest
dispatcher = getServletContext().getRequestDispatcher("/IncludeServlet/includepath?do=assertforwardinclude");
else if(request.getParameter("do").equals("assertincludeforward"))
dispatcher = getServletContext().getRequestDispatcher("/AssertIncludeForwardServlet/assertpath?do=end");
else if(request.getParameter("do").equals("assertforward"))
dispatcher = getServletContext().getRequestDispatcher("/AssertForwardServlet?do=end&do=the");
else if(request.getParameter("do").equals("ctx.echo"))

View File

@ -222,7 +222,7 @@ public class BaseHome
* <ol>
* <li>If provided path is an absolute reference., and exists, return that reference</li>
* <li>If exists relative to <code>${jetty.base}</code>, return that reference</li>
* <li>If exists relative to and <code>extra-start-dir</code> locations, return that reference</li>
* <li>If exists relative to and <code>include-jetty-dir</code> locations, return that reference</li>
* <li>If exists relative to <code>${jetty.home}</code>, return that reference</li>
* <li>Return standard {@link Path} reference obtained from {@link java.nio.file.FileSystem#getPath(String, String...)} (no exists check performed)</li>
* </ol>

View File

@ -120,7 +120,17 @@ public class FS
{
return filename.toLowerCase(Locale.ENGLISH).endsWith(".xml");
}
public static String toRelativePath(File baseDir, File path)
{
return baseDir.toURI().relativize(path.toURI()).toASCIIString();
}
public static boolean isPropertyFile(String filename)
{
return filename.toLowerCase(Locale.ENGLISH).endsWith(".properties");
}
public static String separators(String path)
{
StringBuilder ret = new StringBuilder();

View File

@ -137,7 +137,7 @@ public class Main
private BaseHome baseHome;
Main() throws IOException
public Main() throws IOException
{
}
@ -593,6 +593,9 @@ public class Main
// ------------------------------------------------------------
// 6) Resolve Extra XMLs
args.resolveExtraXmls(baseHome);
// 9) Resolve Property Files
args.resolvePropertyFiles(baseHome);
return args;
}

View File

@ -88,6 +88,12 @@ public class StartArgs
/** List of all xml references found directly on command line or start.ini */
private List<String> xmlRefs = new ArrayList<>();
/** List of all property references found directly on command line or start.ini */
private List<String> propertyFileRefs = new ArrayList<>();
/** List of all property files */
private List<Path> propertyFiles = new ArrayList<>();
private Props properties = new Props();
private Set<String> systemPropertyKeys = new HashSet<>();
private List<String> rawLibs = new ArrayList<>();
@ -149,6 +155,19 @@ public class StartArgs
xmls.add(xmlfile);
}
}
private void addUniquePropertyFile(String propertyFileRef, Path propertyFile) throws IOException
{
if (!FS.canReadFile(propertyFile))
{
throw new IOException("Cannot read file: " + propertyFileRef);
}
propertyFile = FS.toRealPath(propertyFile);
if (!propertyFiles.contains(propertyFile))
{
propertyFiles.add(propertyFile);
}
}
public void dumpActiveXmls(BaseHome baseHome)
{
@ -506,6 +525,11 @@ public class StartArgs
{
cmd.addRawArg(xml.toAbsolutePath().toString());
}
for (Path propertyFile : propertyFiles)
{
cmd.addRawArg(propertyFile.toAbsolutePath().toString());
}
return cmd;
}
@ -651,7 +675,7 @@ public class StartArgs
return;
}
if (arg.startsWith("--extra-start-dir="))
if (arg.startsWith("--include-jetty-dir="))
{
// valid, but handled in ConfigSources instead
return;
@ -844,6 +868,16 @@ public class StartArgs
}
return;
}
if (FS.isPropertyFile(arg))
{
// only add non-duplicates
if (!propertyFileRefs.contains(arg))
{
propertyFileRefs.add(arg);
}
return;
}
// Anything else is unrecognized
throw new UsageException(ERR_BAD_ARG,"Unrecognized argument: \"%s\" in %s",arg,source);
@ -863,6 +897,21 @@ public class StartArgs
addUniqueXmlFile(xmlRef,xmlfile);
}
}
public void resolvePropertyFiles(BaseHome baseHome) throws IOException
{
// Find and Expand property files
for (String propertyFileRef : propertyFileRefs)
{
// Straight Reference
Path propertyFile = baseHome.getPath(propertyFileRef);
if (!FS.exists(propertyFile))
{
propertyFile = baseHome.getPath("etc/" + propertyFileRef);
}
addUniquePropertyFile(propertyFileRef,propertyFile);
}
}
public void setAllModules(Modules allModules)
{

View File

@ -43,8 +43,8 @@ public interface ConfigSource
* <pre>
* -1 = the command line
* 0 = the ${jetty.base} source
* [1..n] = extra-start-dir entries from command line
* [n+1..n] = extra-start-dir entries from start.ini (or start.d/*.ini)
* [1..n] = include-jetty-dir entries from command line
* [n+1..n] = include-jetty-dir entries from start.ini (or start.d/*.ini)
* 9999999 = the ${jetty.home} source
* </pre>
*

View File

@ -51,7 +51,7 @@ public class ConfigSources implements Iterable<ConfigSource>
private LinkedList<ConfigSource> sources = new LinkedList<>();
private Props props = new Props();
private AtomicInteger xtraSourceWeight = new AtomicInteger(1);
private AtomicInteger sourceWeight = new AtomicInteger(1);
public void add(ConfigSource source) throws IOException
{
@ -66,15 +66,15 @@ public class ConfigSources implements Iterable<ConfigSource>
updateProps();
// look for --extra-start-dir entries
// look for --include-jetty-dir entries
for (String arg : source.getArgs())
{
if (arg.startsWith("--extra-start-dir"))
if (arg.startsWith("--include-jetty-dir"))
{
String ref = getValue(arg);
String dirName = props.expand(ref);
Path dir = FS.toPath(dirName);
DirConfigSource dirsource = new DirConfigSource(ref,dir,xtraSourceWeight.incrementAndGet(),true);
DirConfigSource dirsource = new DirConfigSource(ref,dir,sourceWeight.incrementAndGet(),true);
add(dirsource);
}
}

View File

@ -41,7 +41,7 @@ import org.eclipse.jetty.start.StartLog;
/**
* A Directory based {@link ConfigSource}.
* <p>
* Such as <code>${jetty.base}</code> or and <code>--extra-start-dir=[path]</code> sources.
* Such as <code>${jetty.base}</code> or and <code>--include-jetty-dir=[path]</code> sources.
*/
public class DirConfigSource implements ConfigSource
{

View File

@ -34,7 +34,7 @@ import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
public class ExtraStartTest
public class IncludeJettyDirTest
{
private static class MainResult
{
@ -97,7 +97,7 @@ public class ExtraStartTest
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1");
// Simple command line - no reference to extra-start-dirs
// Simple command line - no reference to include-jetty-dirs
MainResult result = runMain(base,home);
List<String> expectedSearchOrder = new ArrayList<>();
@ -127,10 +127,10 @@ public class ExtraStartTest
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1");
// Simple command line reference to extra-start-dir
// Simple command line reference to include-jetty-dir
MainResult result = runMain(base,home,
// direct reference via path
"--extra-start-dir=" + common.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath());
List<String> expectedSearchOrder = new ArrayList<>();
expectedSearchOrder.add("${jetty.base}");
@ -161,12 +161,12 @@ public class ExtraStartTest
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1");
// Simple command line reference to extra-start-dir via property (also on command line)
// Simple command line reference to include-jetty-dir via property (also on command line)
MainResult result = runMain(base,home,
// property
"my.common=" + common.getAbsolutePath(),
// reference via property
"--extra-start-dir=${my.common}");
"--include-jetty-dir=${my.common}");
List<String> expectedSearchOrder = new ArrayList<>();
expectedSearchOrder.add("${jetty.base}");
@ -203,12 +203,12 @@ public class ExtraStartTest
String dirRef = "${my.opt}" + File.separator + "common";
// Simple command line reference to extra-start-dir via property (also on command line)
// Simple command line reference to include-jetty-dir via property (also on command line)
MainResult result = runMain(base,home,
// property to 'opt' dir
"my.opt=" + opt.getAbsolutePath(),
// reference via property prefix
"--extra-start-dir=" + dirRef);
"--include-jetty-dir=" + dirRef);
List<String> expectedSearchOrder = new ArrayList<>();
expectedSearchOrder.add("${jetty.base}");
@ -245,14 +245,14 @@ public class ExtraStartTest
String dirRef = "${my.opt}" + File.separator + "${my.dir}";
// Simple command line reference to extra-start-dir via property (also on command line)
// Simple command line reference to include-jetty-dir via property (also on command line)
MainResult result = runMain(base,home,
// property to 'opt' dir
"my.opt=" + opt.getAbsolutePath(),
// property to commmon dir name
"my.dir=common",
// reference via property prefix
"--extra-start-dir=" + dirRef);
"--include-jetty-dir=" + dirRef);
List<String> expectedSearchOrder = new ArrayList<>();
expectedSearchOrder.add("${jetty.base}");
@ -282,7 +282,7 @@ public class ExtraStartTest
FS.ensureEmpty(base);
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1",//
"--extra-start-dir=" + common.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath());
MainResult result = runMain(base,home);
@ -318,8 +318,8 @@ public class ExtraStartTest
FS.ensureEmpty(base);
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1",//
"--extra-start-dir=" + common.getAbsolutePath(), //
"--extra-start-dir=" + corp.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath(), //
"--include-jetty-dir=" + corp.getAbsolutePath());
MainResult result = runMain(base,home);
@ -351,7 +351,7 @@ public class ExtraStartTest
File common = testdir.getFile("common");
FS.ensureEmpty(common);
TestEnv.makeFile(common,"start.ini", //
"--extra-start-dir=" + corp.getAbsolutePath(), //
"--include-jetty-dir=" + corp.getAbsolutePath(), //
"jetty.port=8080");
// Create base
@ -359,7 +359,7 @@ public class ExtraStartTest
FS.ensureEmpty(base);
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1",//
"--extra-start-dir=" + common.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath());
MainResult result = runMain(base,home);
@ -393,7 +393,7 @@ public class ExtraStartTest
FS.ensureEmpty(common);
TestEnv.makeFile(common,"start.ini", //
"my.corp=" + corp.getAbsolutePath(), //
"--extra-start-dir=${my.corp}", //
"--include-jetty-dir=${my.corp}", //
"jetty.port=8080");
// Create base
@ -402,7 +402,7 @@ public class ExtraStartTest
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1",//
"my.common=" + common.getAbsolutePath(), //
"--extra-start-dir=${my.common}");
"--include-jetty-dir=${my.common}");
MainResult result = runMain(base,home);
@ -442,7 +442,7 @@ public class ExtraStartTest
File common = testdir.getFile("common");
FS.ensureEmpty(common);
TestEnv.makeFile(common,"start.ini", //
"--extra-start-dir=" + corp.getAbsolutePath(), //
"--include-jetty-dir=" + corp.getAbsolutePath(), //
"jetty.port=8080");
// Create base
@ -450,11 +450,11 @@ public class ExtraStartTest
FS.ensureEmpty(base);
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1",//
"--extra-start-dir=" + common.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath());
MainResult result = runMain(base,home,
// command line provided extra-start-dir ref
"--extra-start-dir=" + devops.getAbsolutePath());
// command line provided include-jetty-dir ref
"--include-jetty-dir=" + devops.getAbsolutePath());
List<String> expectedSearchOrder = new ArrayList<>();
expectedSearchOrder.add("${jetty.base}");
@ -486,7 +486,7 @@ public class ExtraStartTest
File common = testdir.getFile("common");
FS.ensureEmpty(common);
TestEnv.makeFile(common,"start.ini", //
"--extra-start-dir=" + corp.getAbsolutePath(), //
"--include-jetty-dir=" + corp.getAbsolutePath(), //
"jetty.port=8080");
// Create base
@ -494,7 +494,7 @@ public class ExtraStartTest
FS.ensureEmpty(base);
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1",//
"--extra-start-dir=" + common.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath());
MainResult result = runMain(base,home,
// command line property should override all others
@ -530,21 +530,21 @@ public class ExtraStartTest
// standard property
"jetty.port=9090",
// INTENTIONAL BAD Reference (duplicate)
"--extra-start-dir=" + common.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath());
// Populate common
TestEnv.makeFile(common,"start.ini",
// standard property
"jetty.port=8080",
// reference to corp
"--extra-start-dir=" + corp.getAbsolutePath());
"--include-jetty-dir=" + corp.getAbsolutePath());
// Create base
File base = testdir.getFile("base");
FS.ensureEmpty(base);
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1",//
"--extra-start-dir=" + common.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath());
try
{

View File

@ -62,9 +62,9 @@ public class TestUseCases
}
@Test
public void testWithExtraStartDir_Logging() throws Exception
public void testWithIncludeJettyDir_Logging() throws Exception
{
assertUseCase("home","base.with.extra.start.dirs","assert-extra-start-dir-logging.txt");
assertUseCase("home","base.with.include.jetty.dirs","assert-include-jetty-dir-logging.txt");
}
@Test

View File

@ -118,7 +118,7 @@ public class ConfigSourcesTest
FS.ensureEmpty(base);
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1",//
"--extra-start-dir=" + common.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath());
ConfigSources sources = new ConfigSources();
@ -151,13 +151,13 @@ public class ConfigSourcesTest
ConfigSources sources = new ConfigSources();
// Simple command line reference to extra-start-dir via property (also on command line)
// Simple command line reference to include-jetty-dir via property (also on command line)
String[] cmdLine = new String[] {
// property
"my.common=" + common.getAbsolutePath(),
// reference via property
"--extra-start-dir=${my.common}" };
"--include-jetty-dir=${my.common}" };
sources.add(new CommandLineConfigSource(cmdLine));
sources.add(new JettyHomeConfigSource(home.toPath()));
@ -198,12 +198,12 @@ public class ConfigSourcesTest
ConfigSources sources = new ConfigSources();
// Simple command line reference to extra-start-dir via property (also on command line)
// Simple command line reference to include-jetty-dir via property (also on command line)
String[] cmdLine = new String[] {
// property to 'opt' dir
"my.opt=" + opt.getAbsolutePath(),
// reference via property prefix
"--extra-start-dir=" + dirRef };
"--include-jetty-dir=" + dirRef };
sources.add(new CommandLineConfigSource(cmdLine));
sources.add(new JettyHomeConfigSource(home.toPath()));
@ -244,7 +244,7 @@ public class ConfigSourcesTest
ConfigSources sources = new ConfigSources();
// Simple command line reference to extra-start-dir via property (also on command line)
// Simple command line reference to include-jetty-dir via property (also on command line)
String[] cmdLine = new String[] {
// property to 'opt' dir
@ -252,7 +252,7 @@ public class ConfigSourcesTest
// property to commmon dir name
"my.dir=common",
// reference via property prefix
"--extra-start-dir=" + dirRef };
"--include-jetty-dir=" + dirRef };
sources.add(new CommandLineConfigSource(cmdLine));
sources.add(new JettyHomeConfigSource(home.toPath()));
@ -284,7 +284,7 @@ public class ConfigSourcesTest
FS.ensureEmpty(base);
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1",//
"--extra-start-dir=" + common.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath());
ConfigSources sources = new ConfigSources();
@ -323,8 +323,8 @@ public class ConfigSourcesTest
FS.ensureEmpty(base);
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1",//
"--extra-start-dir=" + common.getAbsolutePath(), //
"--extra-start-dir=" + corp.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath(), //
"--include-jetty-dir=" + corp.getAbsolutePath());
ConfigSources sources = new ConfigSources();
@ -362,7 +362,7 @@ public class ConfigSourcesTest
File common = testdir.getFile("common");
FS.ensureEmpty(common);
TestEnv.makeFile(common,"start.ini", //
"--extra-start-dir=" + corp.getAbsolutePath(), //
"--include-jetty-dir=" + corp.getAbsolutePath(), //
"jetty.port=8080");
// Create base
@ -370,7 +370,7 @@ public class ConfigSourcesTest
FS.ensureEmpty(base);
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1",//
"--extra-start-dir=" + common.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath());
ConfigSources sources = new ConfigSources();
@ -409,7 +409,7 @@ public class ConfigSourcesTest
FS.ensureEmpty(common);
TestEnv.makeFile(common,"start.ini", //
"my.corp=" + corp.getAbsolutePath(), //
"--extra-start-dir=${my.corp}", //
"--include-jetty-dir=${my.corp}", //
"jetty.port=8080");
// Create base
@ -418,7 +418,7 @@ public class ConfigSourcesTest
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1",//
"my.common="+common.getAbsolutePath(), //
"--extra-start-dir=${my.common}");
"--include-jetty-dir=${my.common}");
ConfigSources sources = new ConfigSources();
@ -464,7 +464,7 @@ public class ConfigSourcesTest
File common = testdir.getFile("common");
FS.ensureEmpty(common);
TestEnv.makeFile(common,"start.ini", //
"--extra-start-dir=" + corp.getAbsolutePath(), //
"--include-jetty-dir=" + corp.getAbsolutePath(), //
"jetty.port=8080");
// Create base
@ -472,13 +472,13 @@ public class ConfigSourcesTest
FS.ensureEmpty(base);
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1",//
"--extra-start-dir=" + common.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath());
ConfigSources sources = new ConfigSources();
String cmdLine[] = new String[]{
// command line provided extra-start-dir ref
"--extra-start-dir=" + devops.getAbsolutePath()};
// command line provided include-jetty-dir ref
"--include-jetty-dir=" + devops.getAbsolutePath()};
sources.add(new CommandLineConfigSource(cmdLine));
sources.add(new JettyHomeConfigSource(home.toPath()));
sources.add(new JettyBaseConfigSource(base.toPath()));
@ -514,7 +514,7 @@ public class ConfigSourcesTest
File common = testdir.getFile("common");
FS.ensureEmpty(common);
TestEnv.makeFile(common,"start.ini", //
"--extra-start-dir=" + corp.getAbsolutePath(), //
"--include-jetty-dir=" + corp.getAbsolutePath(), //
"jetty.port=8080");
// Create base
@ -522,7 +522,7 @@ public class ConfigSourcesTest
FS.ensureEmpty(base);
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1",//
"--extra-start-dir=" + common.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath());
ConfigSources sources = new ConfigSources();
@ -564,21 +564,21 @@ public class ConfigSourcesTest
// standard property
"jetty.port=9090",
// INTENTIONAL BAD Reference (duplicate)
"--extra-start-dir=" + common.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath());
// Populate common
TestEnv.makeFile(common,"start.ini",
// standard property
"jetty.port=8080",
// reference to corp
"--extra-start-dir=" + corp.getAbsolutePath());
"--include-jetty-dir=" + corp.getAbsolutePath());
// Create base
File base = testdir.getFile("base");
FS.ensureEmpty(base);
TestEnv.makeFile(base,"start.ini", //
"jetty.host=127.0.0.1",//
"--extra-start-dir=" + common.getAbsolutePath());
"--include-jetty-dir=" + common.getAbsolutePath());
ConfigSources sources = new ConfigSources();

View File

@ -1,5 +0,0 @@
--extra-start-dir=${start.basedir}/../../extra-start-dirs/logging
--module=server,http,jmx
jetty.port=9090

View File

@ -0,0 +1,5 @@
--include-jetty-dir=${start.basedir}/../../extra-jetty-dirs/logging
--module=server,http,jmx
jetty.port=9090

View File

@ -27,6 +27,7 @@ import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
@ -90,24 +91,18 @@ public class Log
* configuration of the Log class in situations where access to the System.properties are
* either too late or just impossible.
*/
URL testProps = Loader.getResource(Log.class,"jetty-logging.properties");
if (testProps != null)
loadProperties("jetty-logging.properties",__props);
/*
* Next see if an OS specific jetty-logging.properties object exists in the classpath.
* This really for setting up test specific logging behavior based on OS.
*/
String osName = System.getProperty("os.name");
// NOTE: cannot use jetty-util's StringUtil as that initializes logging itself.
if (osName != null && osName.length() > 0)
{
InputStream in = null;
try
{
in = testProps.openStream();
__props.load(in);
}
catch (IOException e)
{
System.err.println("Unable to load " + testProps);
e.printStackTrace(System.err);
}
finally
{
safeCloseInputStream(in);
}
osName = osName.toLowerCase(Locale.ENGLISH).replace(' ','-');
loadProperties("jetty-logging-" + osName + ".properties",__props);
}
/* Now load the System.properties as-is into the __props, these values will override
@ -119,9 +114,11 @@ public class Log
{
String key = systemKeyEnum.nextElement();
String val = System.getProperty(key);
//protect against application code insertion of non-String values (returned as null)
// protect against application code insertion of non-String values (returned as null)
if (val != null)
{
__props.setProperty(key,val);
}
}
/* Now use the configuration properties to configure the Log statics
@ -132,17 +129,30 @@ public class Log
}
});
}
private static void safeCloseInputStream(InputStream in)
private static void loadProperties(String resourceName, Properties props)
{
try
URL testProps = Loader.getResource(Log.class,resourceName);
if (testProps != null)
{
if (in != null)
in.close();
}
catch (IOException e)
{
LOG.ignore(e);
try (InputStream in = testProps.openStream())
{
Properties p = new Properties();
p.load(in);
for (Object key : p.keySet())
{
Object value = p.get(key);
if (value != null)
{
props.put(key,value);
}
}
}
catch (IOException e)
{
System.err.println("[WARN] Error loading logging config: " + testProps);
e.printStackTrace(System.err);
}
}
}
@ -184,7 +194,7 @@ public class Log
Class<?> log_class;
if(e != null && __ignored)
{
e.printStackTrace();
e.printStackTrace(System.err);
}
if (LOG == null)

View File

@ -110,11 +110,11 @@ public class WebSocketPolicy
}
}
private void assertPositive(String name, long size)
private void assertGreaterThan(String name, long size, long minSize)
{
if (size < 1)
if (size < minSize)
{
throw new IllegalArgumentException(String.format("%s [%d] must be a postive value larger than 0",name,size));
throw new IllegalArgumentException(String.format("%s [%d] must be a greater than or equal to " + minSize,name,size));
}
}
@ -260,7 +260,7 @@ public class WebSocketPolicy
*/
public void setIdleTimeout(long ms)
{
assertPositive("IdleTimeout",ms);
assertGreaterThan("IdleTimeout",ms,0);
this.idleTimeout = ms;
}
@ -272,7 +272,7 @@ public class WebSocketPolicy
*/
public void setInputBufferSize(int size)
{
assertPositive("InputBufferSize",size);
assertGreaterThan("InputBufferSize",size,1);
assertLessThan("InputBufferSize",size,"MaxTextMessageBufferSize",maxTextMessageBufferSize);
assertLessThan("InputBufferSize",size,"MaxBinaryMessageBufferSize",maxBinaryMessageBufferSize);
@ -289,7 +289,7 @@ public class WebSocketPolicy
*/
public void setMaxBinaryMessageBufferSize(int size)
{
assertPositive("MaxBinaryMessageBufferSize",size);
assertGreaterThan("MaxBinaryMessageBufferSize",size,1);
this.maxBinaryMessageBufferSize = size;
}
@ -304,7 +304,7 @@ public class WebSocketPolicy
*/
public void setMaxBinaryMessageSize(int size)
{
assertPositive("MaxBinaryMessageSize",size);
assertGreaterThan("MaxBinaryMessageSize",size,1);
this.maxBinaryMessageSize = size;
}
@ -319,7 +319,7 @@ public class WebSocketPolicy
*/
public void setMaxTextMessageBufferSize(int size)
{
assertPositive("MaxTextMessageBufferSize",size);
assertGreaterThan("MaxTextMessageBufferSize",size,1);
this.maxTextMessageBufferSize = size;
}
@ -334,7 +334,7 @@ public class WebSocketPolicy
*/
public void setMaxTextMessageSize(int size)
{
assertPositive("MaxTextMessageSize",size);
assertGreaterThan("MaxTextMessageSize",size,1);
this.maxTextMessageSize = size;
}

View File

@ -345,8 +345,16 @@ public class QuoteUtil
*/
public static void quoteIfNeeded(StringBuilder buf, String str, String delim)
{
if (str == null)
{
return;
}
// check for delimiters in input string
int len = str.length();
if (len == 0)
{
return;
}
int ch;
for (int i = 0; i < len; i++)
{

View File

@ -18,7 +18,8 @@
package org.eclipse.jetty.websocket.api.util;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Iterator;
import java.util.NoSuchElementException;
@ -136,4 +137,20 @@ public class QuoteUtilTest
Iterator<String> iter = QuoteUtil.splitAt("Key = Value","=");
assertSplitAt(iter,"Key","Value");
}
@Test
public void testQuoteIfNeeded()
{
StringBuilder buf = new StringBuilder();
QuoteUtil.quoteIfNeeded(buf, "key",",");
assertThat("key",buf.toString(),is("key"));
}
@Test
public void testQuoteIfNeeded_null()
{
StringBuilder buf = new StringBuilder();
QuoteUtil.quoteIfNeeded(buf, null,";=");
assertThat("<null>",buf.toString(),is(""));
}
}

View File

@ -433,7 +433,7 @@ public class ClientCloseTest
// client close event on ws-endpoint
clientSocket.assertReceivedCloseEvent(timeout,
anyOf(is(StatusCode.SHUTDOWN),is(StatusCode.ABNORMAL)),
anyOf(containsString("Timeout"),containsString("Write")));
anyOf(containsString("Timeout"),containsString("timeout"),containsString("Write")));
}
@Test

View File

@ -88,7 +88,7 @@ public class SessionTest
remote.sendStringByFuture("Hello World!");
if (remote.getBatchMode() == BatchMode.ON)
remote.flush();
srvSock.echoMessage(1,TimeUnit.MILLISECONDS,500);
srvSock.echoMessage(1,500,TimeUnit.MILLISECONDS);
// wait for response from server
cliSock.waitForMessage(500,TimeUnit.MILLISECONDS);

View File

@ -124,7 +124,7 @@ public class WebSocketClientTest
remote.sendStringByFuture("Hello World!");
if (remote.getBatchMode() == BatchMode.ON)
remote.flush();
srvSock.echoMessage(1,TimeUnit.MILLISECONDS,500);
srvSock.echoMessage(1,500,TimeUnit.MILLISECONDS);
// wait for response from server
cliSock.waitForMessage(500,TimeUnit.MILLISECONDS);
@ -320,13 +320,13 @@ public class WebSocketClientTest
String msg = StringUtil.toUTF8String(buf,0,buf.length);
wsocket.getSession().getRemote().sendStringByFuture(msg);
ssocket.echoMessage(1,TimeUnit.MILLISECONDS,500);
ssocket.echoMessage(1,2,TimeUnit.SECONDS);
// wait for response from server
wsocket.waitForMessage(500,TimeUnit.MILLISECONDS);
wsocket.waitForMessage(1,TimeUnit.SECONDS);
wsocket.assertMessage(msg);
Assert.assertTrue(wsocket.dataLatch.await(1000,TimeUnit.SECONDS));
Assert.assertTrue(wsocket.dataLatch.await(2,TimeUnit.SECONDS));
}
finally
{

View File

@ -42,18 +42,39 @@ public abstract class CompressExtension extends AbstractExtension
{
protected static final byte[] TAIL_BYTES = new byte[]{0x00, 0x00, (byte)0xFF, (byte)0xFF};
private static final Logger LOG = Log.getLogger(CompressExtension.class);
/** Never drop tail bytes 0000FFFF, from any frame type */
protected static final int TAIL_DROP_NEVER = 0;
/** Always drop tail bytes 0000FFFF, from all frame types */
protected static final int TAIL_DROP_ALWAYS = 1;
/** Only drop tail bytes 0000FFFF, from fin==true frames */
protected static final int TAIL_DROP_FIN_ONLY = 2;
/** Always set RSV flag, on all frame types */
protected static final int RSV_USE_ALWAYS = 0;
/**
* Only set RSV flag on first frame in multi-frame messages.
* <p>
* Note: this automatically means no-continuation frames have
* the RSV bit set
*/
protected static final int RSV_USE_ONLY_FIRST = 1;
private final Queue<FrameEntry> entries = new ConcurrentArrayQueue<>();
private final IteratingCallback flusher = new Flusher();
private final Deflater compressor;
private final Inflater decompressor;
private int tailDrop = TAIL_DROP_NEVER;
private int rsvUse = RSV_USE_ALWAYS;
protected CompressExtension()
{
compressor = new Deflater(Deflater.BEST_COMPRESSION, true);
decompressor = new Inflater(true);
tailDrop = getTailDropMode();
rsvUse = getRsvUseMode();
}
public Deflater getDeflater()
{
return compressor;
@ -72,6 +93,20 @@ public abstract class CompressExtension extends AbstractExtension
{
return true;
}
/**
* Return the mode of operation for dropping (or keeping) tail bytes in frames generated by compress (outgoing)
*
* @return either {@link #TAIL_DROP_ALWAYS}, {@link #TAIL_DROP_FIN_ONLY}, or {@link #TAIL_DROP_NEVER}
*/
abstract int getTailDropMode();
/**
* Return the mode of operation for RSV flag use in frames generate by compress (outgoing)
*
* @return either {@link #RSV_USE_ALWAYS} or {@link #RSV_USE_ONLY_FIRST}
*/
abstract int getRsvUseMode();
protected void forwardIncoming(Frame frame, ByteAccumulator accumulator)
{
@ -301,15 +336,31 @@ public abstract class CompressExtension extends AbstractExtension
}
}
// Skip the last tail bytes bytes generated by SYNC_FLUSH.
payload = ByteBuffer.wrap(output, 0, outputLength - TAIL_BYTES.length);
LOG.debug("Compressed {}: {}->{} chunk bytes", entry, inputLength, outputLength);
boolean fin = frame.isFin() && finished;
// Handle tail bytes generated by SYNC_FLUSH.
if(tailDrop == TAIL_DROP_ALWAYS) {
payload = ByteBuffer.wrap(output, 0, outputLength - TAIL_BYTES.length);
} else if(tailDrop == TAIL_DROP_FIN_ONLY) {
payload = ByteBuffer.wrap(output, 0, outputLength - (fin?TAIL_BYTES.length:0));
} else {
// always include
payload = ByteBuffer.wrap(output, 0, outputLength);
}
if (LOG.isDebugEnabled())
{
LOG.debug("Compressed {}: {}->{} chunk bytes",entry,inputLength,outputLength);
}
boolean continuation = frame.getType().isContinuation() || !first;
DataFrame chunk = new DataFrame(frame, continuation);
chunk.setRsv1(true);
if(rsvUse == RSV_USE_ONLY_FIRST) {
chunk.setRsv1(!continuation);
} else {
// always set
chunk.setRsv1(true);
}
chunk.setPayload(payload);
boolean fin = frame.isFin() && finished;
chunk.setFin(fin);
nextOutgoingFrame(chunk, this, entry.batchMode);

View File

@ -35,6 +35,18 @@ public class DeflateFrameExtension extends CompressExtension
{
return "deflate-frame";
}
@Override
int getRsvUseMode()
{
return RSV_USE_ALWAYS;
}
@Override
int getTailDropMode()
{
return TAIL_DROP_ALWAYS;
}
@Override
public void incomingFrame(Frame frame)

View File

@ -102,13 +102,25 @@ public class PerMessageDeflateExtension extends CompressExtension
}
super.nextOutgoingFrame(frame, callback, batchMode);
}
@Override
int getRsvUseMode()
{
return RSV_USE_ONLY_FIRST;
}
@Override
int getTailDropMode()
{
return TAIL_DROP_FIN_ONLY;
}
@Override
public void setConfig(final ExtensionConfig config)
{
configRequested = new ExtensionConfig(config);
configNegotiated = new ExtensionConfig(config.getName());
for (String key : config.getParameterKeys())
{
key = key.trim();

View File

@ -188,6 +188,13 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
return countOnFillableEvents.get();
}
}
private static enum ReadMode
{
PARSE,
DISCARD,
EOF
}
private static final Logger LOG = Log.getLogger(AbstractWebSocketConnection.class);
@ -206,6 +213,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
private WebSocketSession session;
private List<ExtensionConfig> extensions;
private boolean isFilling;
private ReadMode readMode = ReadMode.PARSE;
private IOState ioState;
private Stats stats = new Stats();
@ -441,18 +449,24 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
LOG.debug("{} onFillable()",policy.getBehavior());
stats.countOnFillableEvents.incrementAndGet();
ByteBuffer buffer = bufferPool.acquire(getInputBufferSize(),true);
boolean readMore = false;
try
{
isFilling = true;
readMore = (read(buffer) != -1);
if(readMode == ReadMode.PARSE)
{
readMode = readParse(buffer);
} else
{
readMode = readDiscard(buffer);
}
}
finally
{
bufferPool.release(buffer);
}
if (readMore && (suspendToken.get() == false))
if ((readMode != ReadMode.EOF) && (suspendToken.get() == false))
{
fillInterested();
}
@ -462,6 +476,8 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
}
}
@Override
protected void onFillInterestedFailed(Throwable cause)
{
@ -521,7 +537,45 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
flusher.enqueue(frame,callback,batchMode);
}
private int read(ByteBuffer buffer)
private ReadMode readDiscard(ByteBuffer buffer)
{
EndPoint endPoint = getEndPoint();
try
{
while (true)
{
int filled = endPoint.fill(buffer);
if (filled == 0)
{
return ReadMode.DISCARD;
}
else if (filled < 0)
{
LOG.debug("read - EOF Reached (remote: {})",getRemoteAddress());
return ReadMode.EOF;
}
else
{
if (LOG.isDebugEnabled())
{
LOG.debug("Discarded {} bytes - {}",filled,BufferUtil.toDetailString(buffer));
}
}
}
}
catch (IOException e)
{
LOG.ignore(e);
return ReadMode.EOF;
}
catch (Throwable t)
{
LOG.ignore(t);
return ReadMode.DISCARD;
}
}
private ReadMode readParse(ByteBuffer buffer)
{
EndPoint endPoint = getEndPoint();
try
@ -531,13 +585,13 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
int filled = endPoint.fill(buffer);
if (filled == 0)
{
return 0;
return ReadMode.PARSE;
}
else if (filled < 0)
{
LOG.debug("read - EOF Reached (remote: {})",getRemoteAddress());
ioState.onReadFailure(new EOFException("Remote Read EOF"));
return -1;
return ReadMode.EOF;
}
else
{
@ -553,19 +607,20 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
{
LOG.warn(e);
close(StatusCode.PROTOCOL,e.getMessage());
return -1;
return ReadMode.DISCARD;
}
catch (CloseException e)
{
LOG.debug(e);
close(e.getStatusCode(),e.getMessage());
return -1;
return ReadMode.DISCARD;
}
catch (Throwable t)
{
LOG.warn(t);
close(StatusCode.ABNORMAL,t.getMessage());
return -1;
// TODO: should probably only switch to discard if a non-ws-endpoint error
return ReadMode.DISCARD;
}
}

View File

@ -29,6 +29,7 @@ import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.ArrayQueue;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -46,11 +47,17 @@ public class FrameFlusher
{
private class Flusher extends IteratingCallback
{
private final List<FrameEntry> entries = new ArrayList<>(maxGather);
private final List<ByteBuffer> buffers = new ArrayList<>((maxGather * 2) + 1);
private final List<FrameEntry> entries;
private final List<ByteBuffer> buffers;
private ByteBuffer aggregate;
private BatchMode batchMode;
public Flusher(int maxGather)
{
entries = new ArrayList<>(maxGather);
buffers = new ArrayList<>((maxGather * 2) + 1);
}
private Action batch()
{
if (aggregate == null)
@ -289,7 +296,7 @@ public class FrameFlusher
private final int maxGather;
private final Object lock = new Object();
private final ArrayQueue<FrameEntry> queue = new ArrayQueue<>(16,16,lock);
private final Flusher flusher = new Flusher();
private final Flusher flusher;
private final AtomicBoolean closed = new AtomicBoolean();
private volatile Throwable failure;
@ -300,6 +307,7 @@ public class FrameFlusher
this.bufferSize = bufferSize;
this.generator = Objects.requireNonNull(generator);
this.maxGather = maxGather;
this.flusher = new Flusher(maxGather);
}
public void close()

View File

@ -152,10 +152,10 @@ public class BlockheadServer
}
}
public void echoMessage(int expectedFrames, TimeUnit timeoutUnit, int timeoutDuration) throws IOException, TimeoutException
public void echoMessage(int expectedFrames, int timeoutDuration, TimeUnit timeoutUnit) throws IOException, TimeoutException
{
LOG.debug("Echo Frames [expecting {}]",expectedFrames);
IncomingFramesCapture cap = readFrames(expectedFrames,timeoutUnit,timeoutDuration);
IncomingFramesCapture cap = readFrames(expectedFrames,timeoutDuration,timeoutUnit);
// now echo them back.
for (Frame frame : cap.getFrames())
{
@ -329,15 +329,6 @@ public class BlockheadServer
return len;
}
/**
* @deprecated use {@link #readFrames(int, int, TimeUnit)} for correct parameter order
*/
@Deprecated
public IncomingFramesCapture readFrames(int expectedCount, TimeUnit timeoutUnit, int timeoutDuration) throws IOException, TimeoutException
{
return readFrames(expectedCount,timeoutDuration,timeoutUnit);
}
public IncomingFramesCapture readFrames(int expectedCount, int timeoutDuration, TimeUnit timeoutUnit) throws IOException, TimeoutException
{
LOG.debug("Read: waiting for {} frame(s) from client",expectedCount);

View File

@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.EventQueue;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
@ -42,10 +43,14 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
public class AnnotatedMaxMessageSizeTest
{
@Rule
public TestTracker tracker = new TestTracker();
private static Server server;
private static ServerConnector connector;
private static URI serverUri;
@ -110,7 +115,7 @@ public class AnnotatedMaxMessageSizeTest
}
}
@Test(timeout=4000)
@Test(timeout=8000)
public void testEchoTooBig() throws IOException, Exception
{
BlockheadClient client = new BlockheadClient(serverUri);
@ -122,7 +127,8 @@ public class AnnotatedMaxMessageSizeTest
client.expectUpgradeResponse();
// Generate text frame
byte buf[] = new byte[90*1024]; // buffer bigger than maxMessageSize
int size = 120 * 1024;
byte buf[] = new byte[size]; // buffer bigger than maxMessageSize
Arrays.fill(buf,(byte)'x');
client.write(new TextFrame().setPayload(ByteBuffer.wrap(buf)));

View File

@ -23,6 +23,7 @@ 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.common.extensions.compress.PerMessageDeflateExtension;
import org.eclipse.jetty.websocket.server.WebSocketHandler;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
@ -83,21 +84,15 @@ public class BrowserDebugTool implements WebSocketCreator
String ua = req.getHeader("User-Agent");
String rexts = req.getHeader("Sec-WebSocket-Extensions");
LOG.debug("User-Agent: {}", ua);
LOG.debug("Sec-WebSocket-Extensions (Request) : {}", rexts);
LOG.debug("User-Agent: {}",ua);
LOG.debug("Sec-WebSocket-Extensions (Request) : {}",rexts);
return new BrowserSocket(ua,rexts);
}
public void start() throws Exception
public int getPort()
{
server.start();
LOG.info("Server available on port {}", getPort());
}
public void stop() throws Exception
{
server.stop();
return connector.getLocalPort();
}
public void prepare(int port)
@ -116,6 +111,7 @@ public class BrowserDebugTool implements WebSocketCreator
// factory.getExtensionFactory().unregister("deflate-frame");
// factory.getExtensionFactory().unregister("permessage-deflate");
factory.getExtensionFactory().register("permessage-deflate",PerMessageDeflateExtension.class);
// factory.getExtensionFactory().unregister("x-webkit-deflate-frame");
// Setup the desired Socket to use for all incoming upgrade requests
@ -123,6 +119,9 @@ public class BrowserDebugTool implements WebSocketCreator
// Set the timeout
factory.getPolicy().setIdleTimeout(30000);
// Set top end message size
factory.getPolicy().setMaxTextMessageSize(15 * 1024 * 1024);
}
};
@ -138,8 +137,14 @@ public class BrowserDebugTool implements WebSocketCreator
LOG.info("{} setup on port {}",this.getClass().getName(),port);
}
public int getPort()
public void start() throws Exception
{
return connector.getLocalPort();
server.start();
LOG.info("Server available on port {}",getPort());
}
public void stop() throws Exception
{
server.stop();
}
}

View File

@ -109,7 +109,15 @@ public class BrowserSocket
@OnWebSocketMessage
public void onTextMessage(String message)
{
LOG.info("onTextMessage({})",message);
if (message.length() > 300)
{
int len = message.length();
LOG.info("onTextMessage({} ... {}) size:{}",message.substring(0,15),message.substring(len - 15,len).replaceAll("[\r\n]*",""),len);
}
else
{
LOG.info("onTextMessage({})",message);
}
int idx = message.indexOf(':');
if (idx > 0)

View File

@ -81,7 +81,7 @@ public class SessionSocket
delim = true;
}
valueStr.append(']');
System.err.println("valueStr = " + valueStr);
LOG.debug("valueStr = {}", valueStr);
sendString(valueStr.toString());
return;
}

View File

@ -17,6 +17,10 @@
<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="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"/>
<input id="send10m" class="button" type="submit" name="send10m" value="send10m" disabled="disabled"/>
</div>
<script type="text/javascript">
$("connect").onclick = function(event) { wstool.connect(); return false; }
@ -32,6 +36,21 @@
+ " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/chat/demo\",\"data\":{\"user\":\"ch\",\"membership\":\"join\",\"chat\":\"ch"
+ " has joined\"},\"id\":\"4\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+ " 12 Sep 2013 19:42:30 GMT\"}]"); return false; }
$("send10k").onclick = function(event) {wstool.write(randomString( 10*1024)); return false;}
$("send100k").onclick = function(event) {wstool.write(randomString( 100*1024)); return false;}
$("send1000k").onclick = function(event) {wstool.write(randomString(1000*1024)); return false;}
$("send10m").onclick = function(event) {wstool.write(randomString( 10*1024*1024)); return false;}
function randomString(len, charSet) {
charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789{} ":;<>,.()[]';
var randomString = '';
var charLen = charSet.length;
for (var i = 0; i < len; i++) {
var randomPoz = Math.floor(Math.random() * charLen);
randomString += charSet.substring(randomPoz,randomPoz+1);
}
return randomString;
}
</script>
</body>
</html>

View File

@ -59,12 +59,20 @@ var wstool = {
},
infoc : function(message) {
wstool._out("client", "[c] " + message);
if(message.length > 300) {
wstool._out("client", "[c] [big message: " + message.length + " characters]");
} else {
wstool._out("client", "[c] " + message);
}
},
infos : function(message) {
this._scount++;
wstool._out("server", "[s" + this._scount + "] " + message);
if(message.length > 300) {
wstool._out("server", "[s" + this._scount + "] [big message: " + message.length + " characters]");
} else {
wstool._out("server", "[s" + this._scount + "] " + message);
}
},
setState : function(enabled) {
@ -77,6 +85,10 @@ var wstool = {
$('hello').disabled = !enabled;
$('there').disabled = !enabled;
$('json').disabled = !enabled;
$('send10k').disabled = !enabled;
$('send100k').disabled = !enabled;
$('send1000k').disabled = !enabled;
$('send10m').disabled = !enabled;
},
_onopen : function() {

View File

@ -36,19 +36,12 @@
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-Description>Websocket Servlet Interface</Bundle-Description>
<Dynamic-Import-Package>org.eclipse.jetty.websocket.server.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}"</Dynamic-Import-Package>
<Bundle-Classpath />
<_nouses>true</_nouses>
<DynamicImport-Package>org.eclipse.jetty.websocket.server.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}"</DynamicImport-Package>
</instructions>
</configuration>
</plugin>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-webapps-parent</artifactId>
<version>9.2.0-SNAPSHOT</version>
</parent>
<name>Jetty Tests :: Webapps :: Dispatch Webapp</name>
<artifactId>test-dispatch-webapp</artifactId>
<packaging>war</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${project.version}</version>
<configuration>
<scanIntervalSeconds>1</scanIntervalSeconds>
<useTestClasspath>true</useTestClasspath>
<webAppConfig>
<war>src/main/webapp</war>
<descriptor>src/main/webapp/WEB-INF/web.xml</descriptor>
<contextPath>/test-dispatch</contextPath>
<containerIncludeJarPattern>.*/javax.servlet-[^/]*\.jar$</containerIncludeJarPattern>
<configurationDiscovered>true</configurationDiscovered>
</webAppConfig>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,123 @@
//
// ========================================================================
// 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 com.acme;
import static javax.servlet.RequestDispatcher.FORWARD_PATH_INFO;
import static javax.servlet.RequestDispatcher.FORWARD_QUERY_STRING;
import static javax.servlet.RequestDispatcher.FORWARD_SERVLET_PATH;
import static javax.servlet.RequestDispatcher.INCLUDE_PATH_INFO;
import static javax.servlet.RequestDispatcher.INCLUDE_QUERY_STRING;
import static javax.servlet.RequestDispatcher.INCLUDE_REQUEST_URI;
import static javax.servlet.RequestDispatcher.INCLUDE_SERVLET_PATH;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DispatchServlet extends HttpServlet
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
doGet(req, resp);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
Integer depth = (Integer)request.getAttribute("depth");
if (depth==null)
depth=1;
else
depth=depth+1;
request.setAttribute("depth", depth);
String path=request.getServletPath();
String info=request.getPathInfo();
String query=request.getQueryString();
boolean include=request.getAttribute(INCLUDE_REQUEST_URI)!=null;
String tpath = include?(String)request.getAttribute(INCLUDE_SERVLET_PATH):path;
String tinfo = include?(String)request.getAttribute(INCLUDE_PATH_INFO):info;
if ("/forward".equals(tpath))
{
getServletContext().getRequestDispatcher(tinfo+"?depth="+depth+"&p"+depth+"="+"ABCDEFGHI".charAt(depth)).forward(request, response);
}
else if ("/include".equals(tpath))
{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<h1>Dispatch Depth="+depth+"</h1><pre>");
out.printf(" %30s%30s%30s%n","REQUEST","FORWARD","INCLUDE");
out.printf("servletPath:%30s%30s%30s%n",path,request.getAttribute(FORWARD_SERVLET_PATH),request.getAttribute(INCLUDE_SERVLET_PATH));
out.printf(" pathInfo:%30s%30s%30s%n",info,request.getAttribute(FORWARD_PATH_INFO),request.getAttribute(INCLUDE_PATH_INFO));
out.printf(" query:%30s%30s%30s%n",query,request.getAttribute(FORWARD_QUERY_STRING),request.getAttribute(INCLUDE_QUERY_STRING));
out.println();
printParameters(out, request.getParameterMap());
out.println("</pre>");
out.println("<hr/>");
getServletContext().getRequestDispatcher(tinfo+"?depth="+depth+"&p"+depth+"="+"BCDEFGHI".charAt(depth)).include(request, response);
out.println("<hr/>");
out.println("<h1>Dispatch Depth="+depth+"</h1><pre>");
out.printf(" %30s%30s%30s%n","REQUEST","FORWARD","INCLUDE");
out.printf("servletPath:%30s%30s%30s%n",path,request.getAttribute(FORWARD_SERVLET_PATH),request.getAttribute(INCLUDE_SERVLET_PATH));
out.printf(" pathInfo:%30s%30s%30s%n",info,request.getAttribute(FORWARD_PATH_INFO),request.getAttribute(INCLUDE_PATH_INFO));
out.printf(" query:%30s%30s%30s%n",query,request.getAttribute(FORWARD_QUERY_STRING),request.getAttribute(INCLUDE_QUERY_STRING));
out.println();
printParameters(out, request.getParameterMap());
out.println("</pre>");
}
else
{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<h1>Dispatch Depth="+depth+"</h1><pre>");
out.printf(" %30s%30s%30s%n","REQUEST","FORWARD","INCLUDE");
out.printf("servletPath:%30s%30s%30s%n",path,request.getAttribute(FORWARD_SERVLET_PATH),request.getAttribute(INCLUDE_SERVLET_PATH));
out.printf(" pathInfo:%30s%30s%30s%n",info,request.getAttribute(FORWARD_PATH_INFO),request.getAttribute(INCLUDE_PATH_INFO));
out.printf(" query:%30s%30s%30s%n",query,request.getAttribute(FORWARD_QUERY_STRING),request.getAttribute(INCLUDE_QUERY_STRING));
out.println();
printParameters(out, request.getParameterMap());
out.println("</pre>");
}
}
private static void printParameters(PrintWriter out, Map<String,String[]> params)
{
List<String> names = new ArrayList<>(params.keySet());
Collections.sort(names);
for (String name: names)
out.printf("%10s : %s%n", name,Arrays.asList(params.get(name)));
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
metadata-complete="false"
version="3.1">
<display-name>Test Dispatch WebApp</display-name>
<servlet>
<servlet-name>Dispatch</servlet-name>
<servlet-class>com.acme.DispatchServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Dispatch</servlet-name>
<url-pattern>/forward/*</url-pattern>
<url-pattern>/include/*</url-pattern>
<url-pattern>/info/*</url-pattern>
</servlet-mapping>
</web-app>

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,48 @@
<HTML>
<HEAD>
<TITLE>Test Dispatch WebApp</TITLE>
<META http-equiv="Pragma" content="no-cache">
<META http-equiv="Cache-Control" content="no-cache,no-store">
<link rel="stylesheet" type="text/css" href="stylesheet.css"/>
</HEAD>
<BODY >
<A HREF="http://www.eclipse.org/jetty"><IMG SRC="images/jetty_banner.gif"></A>
<h1>Request Dispatching Query Tests</h1>
<p><ul>
<li><a href="forward/info?depth=0&p0=A">forward/info?depth=0&p0=A</a></li>
<li><a href="include/info?depth=0&p0=A">include/info?depth=0&p0=A</a></li>
<li><a href="forward/forward/info?depth=0&p0=A">forward/forward/info?depth=0&p0=A</a></li>
<li><a href="forward/include/info?depth=0&p0=A">forward/include/info?depth=0&p0=A</a></li>
<li><a href="include/forward/info?depth=0&p0=A">include/forward/info?depth=0&p0=A</a></li>
<li><a href="include/include/info?depth=0&p0=A">include/include/info?depth=0&p0=A</a></li>
</ul>
</p>
<h1>Request Dispatching Form POST Tests</h1>
<p><ul>
<li><form action="forward/info" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="forward/info"></form></li>
<li><form action="include/info" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="include/info"></form></li>
<li><form action="forward/forward/info" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="forward/forward/info"></form></li>
<li><form action="forward/include/info" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="forward/include/info"></form></li>
<li><form action="include/forward/info" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="include/forward/info"></form></li>
<li><form action="include/include/info" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="include/include/info"></form></li>
</ul>
</p>
<h1>Request Dispatching Query Form POST Tests</h1>
<p><ul>
<li><form action="forward/info?depth=-1&p0=X&q=0" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="forward/info"></form></li>
<li><form action="include/info?depth=-1&p0=X&q=0" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="include/info"></form></li>
<li><form action="forward/forward/info?depth=-1&p0=X&q=0" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="forward/forward/info"></form></li>
<li><form action="forward/include/info?depth=-1&p0=X&q=0" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="forward/include/info"></form></li>
<li><form action="include/forward/info?depth=-1&p0=X&q=0" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="include/forward/info"></form></li>
<li><form action="include/include/info?depth=-1&p0=X&q=0" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="include/include/info"></form></li>
</ul>
</p>
<center>
<hr/>
<a href="http://www.eclipse.org/jetty"><img style="border:0" src="images/small_powered_by.gif"/></a>
</center>
</BODY>
</HTML>

View File

@ -0,0 +1,7 @@
body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em; margin-top:2em;}
h3 {font-size:100%; letter-spacing: 0.1em;}
span.pass { color: green; }
span.fail { color:red; }