Merge branch 'master' into release-9
This commit is contained in:
commit
7982a38ea6
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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)}
|
||||
*/
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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"))
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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>
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
|
||||
--extra-start-dir=${start.basedir}/../../extra-start-dirs/logging
|
||||
--module=server,http,jmx
|
||||
|
||||
jetty.port=9090
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
--include-jetty-dir=${start.basedir}/../../extra-jetty-dirs/logging
|
||||
--module=server,http,jmx
|
||||
|
||||
jetty.port=9090
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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++)
|
||||
{
|
||||
|
|
|
@ -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(""));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)));
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -81,7 +81,7 @@ public class SessionSocket
|
|||
delim = true;
|
||||
}
|
||||
valueStr.append(']');
|
||||
System.err.println("valueStr = " + valueStr);
|
||||
LOG.debug("valueStr = {}", valueStr);
|
||||
sendString(valueStr.toString());
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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)));
|
||||
}
|
||||
|
||||
}
|
|
@ -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 |
|
@ -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>
|
|
@ -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; }
|
Loading…
Reference in New Issue