diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpCookieParser.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpCookieParser.java
deleted file mode 100644
index 0651fe2636c..00000000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpCookieParser.java
+++ /dev/null
@@ -1,192 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2012 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.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.TimeZone;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.http.HttpCookie;
-
-public class HttpCookieParser
-{
- private static final String[] DATE_PATTERNS = new String[]
- {
- "EEE',' dd-MMM-yyyy HH:mm:ss 'GMT'",
- "EEE',' dd MMM yyyy HH:mm:ss 'GMT'",
- "EEE MMM dd yyyy HH:mm:ss 'GMT'Z"
- };
-
- private HttpCookieParser()
- {
- }
-
- public static List parseCookies(String headerValue)
- {
- if (headerValue.toLowerCase(Locale.ENGLISH).contains("expires="))
- {
- HttpCookie cookie = parseCookie(headerValue, 0);
- if (cookie != null)
- return Collections.singletonList(cookie);
- else
- return Collections.emptyList();
- }
- else
- {
- List result = new ArrayList<>();
- List cookieStrings = splitCookies(headerValue);
- for (String cookieString : cookieStrings)
- {
- HttpCookie cookie = parseCookie(cookieString, 1);
- if (cookie != null)
- result.add(cookie);
- }
- return result;
- }
- }
-
- private static List splitCookies(String headerValue)
- {
- // The comma is the separator, but only if it's outside double quotes
- List result = new ArrayList<>();
- int start = 0;
- int quotes = 0;
- for (int i = 0; i < headerValue.length(); ++i)
- {
- char c = headerValue.charAt(i);
- if (c == ',' && quotes % 2 == 0)
- {
- result.add(headerValue.substring(start, i));
- start = i + 1;
- }
- else if (c == '"')
- {
- ++quotes;
- }
- }
- result.add(headerValue.substring(start));
- return result;
- }
-
- private static HttpCookie parseCookie(String cookieString, int version)
- {
- String[] cookieParts = cookieString.split(";");
-
- String nameValue = cookieParts[0];
- int equal = nameValue.indexOf('=');
- if (equal < 1)
- return null;
-
- String name = nameValue.substring(0, equal).trim();
- String value = nameValue.substring(equal + 1);
- String domain = null;
- String path = "/";
- long maxAge = -1;
- boolean httpOnly = false;
- boolean secure = false;
- String comment = null;
- for (int i = 1; i < cookieParts.length; ++i)
- {
- try
- {
- String[] attributeParts = cookieParts[i].split("=", 2);
- String attributeName = attributeParts[0].trim().toLowerCase(Locale.ENGLISH);
- String attributeValue = attributeParts.length < 2 ? "" : attributeParts[1].trim();
- switch (attributeName)
- {
- case "domain":
- {
- domain = attributeValue;
- break;
- }
- case "path":
- {
- path = attributeValue;
- break;
- }
- case "max-age":
- {
- maxAge = Long.parseLong(attributeValue);
- break;
- }
- case "expires":
- {
- maxAge = parseDate(attributeValue);
- break;
- }
- case "secure":
- {
- secure = true;
- break;
- }
- case "httponly":
- {
- httpOnly = true;
- }
- case "comment":
- {
- comment = attributeValue;
- break;
- }
- case "version":
- {
- version = Integer.parseInt(attributeValue);
- break;
- }
- default:
- {
- // Ignore
- break;
- }
- }
- }
- catch (NumberFormatException x)
- {
- // Ignore
- }
- }
-
- return new HttpCookie(name, value, domain, path, maxAge, httpOnly, secure, comment, version);
- }
-
- private static long parseDate(String attributeValue)
- {
- for (String pattern : DATE_PATTERNS)
- {
- try
- {
- SimpleDateFormat dateFormat = new SimpleDateFormat(pattern, Locale.US);
- dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
- long result = TimeUnit.MILLISECONDS.toSeconds(dateFormat.parse(attributeValue).getTime() - System.currentTimeMillis());
- if (result < 0)
- return 0;
- }
- catch (ParseException x)
- {
- // Ignore and continue
- }
- }
- return 0;
- }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
index 1f5b9f1abb1..941f9a7357f 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
@@ -193,21 +193,27 @@ public class HttpReceiver implements HttpParser.ResponseHandler
// The exchange may be null if it failed concurrently
if (exchange != null)
{
- exchange.getResponse().getHeaders().add(field);
- HttpHeader fieldHeader = field.getHeader();
- if (fieldHeader != null)
+ HttpConversation conversation = exchange.getConversation();
+ HttpResponse response = exchange.getResponse();
+ boolean process = responseNotifier.notifyHeader(conversation.getResponseListeners(), response, field);
+ if (process)
{
- switch (fieldHeader)
+ response.getHeaders().add(field);
+ HttpHeader fieldHeader = field.getHeader();
+ if (fieldHeader != null)
{
- case SET_COOKIE:
- case SET_COOKIE2:
+ switch (fieldHeader)
{
- storeCookie(exchange.getRequest().getURI(), field);
- break;
- }
- default:
- {
- break;
+ case SET_COOKIE:
+ case SET_COOKIE2:
+ {
+ storeCookie(exchange.getRequest().getURI(), field);
+ break;
+ }
+ default:
+ {
+ break;
+ }
}
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
index 8ed4147d294..0656abbc639 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
@@ -300,6 +300,13 @@ public class HttpRequest implements Request
return this;
}
+ @Override
+ public Request onResponseHeader(Response.HeaderListener listener)
+ {
+ this.responseListeners.add(listener);
+ return this;
+ }
+
@Override
public Request onResponseHeaders(Response.HeadersListener listener)
{
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
index a1b64c6d2ee..a332bbf683c 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
@@ -19,12 +19,14 @@
package org.eclipse.jetty.client;
import java.nio.ByteBuffer;
+import java.util.Iterator;
import java.util.List;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -57,6 +59,28 @@ public class ResponseNotifier
}
}
+ public boolean notifyHeader(List listeners, Response response, HttpField field)
+ {
+ boolean result = true;
+ for (Response.ResponseListener listener : listeners)
+ if (listener instanceof Response.HeaderListener)
+ result &= notifyHeader((Response.HeaderListener)listener, response, field);
+ return result;
+ }
+
+ private boolean notifyHeader(Response.HeaderListener listener, Response response, HttpField field)
+ {
+ try
+ {
+ return listener.onHeader(response, field);
+ }
+ catch (Exception x)
+ {
+ LOG.info("Exception while notifying listener " + listener, x);
+ return false;
+ }
+ }
+
public void notifyHeaders(List listeners, Response response)
{
for (Response.ResponseListener listener : listeners)
@@ -156,6 +180,12 @@ public class ResponseNotifier
public void forwardSuccess(List listeners, Response response)
{
notifyBegin(listeners, response);
+ for (Iterator iterator = response.getHeaders().iterator(); iterator.hasNext();)
+ {
+ HttpField field = iterator.next();
+ if (!notifyHeader(listeners, response, field))
+ iterator.remove();
+ }
notifyHeaders(listeners, response);
if (response instanceof ContentResponse)
notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()));
@@ -173,6 +203,12 @@ public class ResponseNotifier
public void forwardFailure(List listeners, Response response, Throwable failure)
{
notifyBegin(listeners, response);
+ for (Iterator iterator = response.getHeaders().iterator(); iterator.hasNext();)
+ {
+ HttpField field = iterator.next();
+ if (!notifyHeader(listeners, response, field))
+ iterator.remove();
+ }
notifyHeaders(listeners, response);
if (response instanceof ContentResponse)
notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()));
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
index 1a386bf2c7d..f52c00fdb5b 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
@@ -26,6 +26,7 @@ import java.util.Map;
import java.util.concurrent.Future;
import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.util.InputStreamResponseListener;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
@@ -36,7 +37,7 @@ import org.eclipse.jetty.util.Fields;
* various attributes such as the path, the headers, the content, etc.
* You can create {@link Request} objects via {@link HttpClient#newRequest(String)} and
* you can send them using either {@link #send()} for a blocking semantic, or
- * {@link #send(Response.Listener)} for an asynchronous semantic.
+ * {@link #send(Response.CompleteListener)} for an asynchronous semantic.
*
* @see Response
*/
@@ -260,6 +261,12 @@ public interface Request
*/
Request onResponseBegin(Response.BeginListener listener);
+ /**
+ * @param listener a listener for response header event
+ * @return this request object
+ */
+ Request onResponseHeader(Response.HeaderListener listener);
+
/**
* @param listener a listener for response headers event
* @return this request object
@@ -291,7 +298,7 @@ public interface Request
* This method should be used when a simple blocking semantic is needed, and when it is known
* that the response content can be buffered without exceeding memory constraints.
* For example, this method is not appropriate to download big files from a server; consider using
- * {@link #send(Response.Listener)} instead, passing your own {@link Response.Listener} or a utility
+ * {@link #send(Response.CompleteListener)} instead, passing your own {@link Response.Listener} or a utility
* listener such as {@link InputStreamResponseListener}.
*
* The future will return when {@link Response.Listener#onComplete(Result)} is invoked.
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java
index e28e95628f3..c9ab661f5ed 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java
@@ -23,6 +23,7 @@ import java.util.EventListener;
import java.util.List;
import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpVersion;
@@ -99,6 +100,22 @@ public interface Response
public void onBegin(Response response);
}
+ /**
+ * Listener for a response header event.
+ */
+ public interface HeaderListener extends ResponseListener
+ {
+ /**
+ * Callback method invoked when a response header has been received,
+ * returning whether the header should be processed or not.
+ *
+ * @param response the response containing the response line data and the headers so far
+ * @param field the header received
+ * @return true to process the header, false to skip processing of the header
+ */
+ public boolean onHeader(Response response, HttpField field);
+ }
+
/**
* Listener for the response headers event.
*/
@@ -180,7 +197,7 @@ public interface Response
/**
* Listener for all response events.
*/
- public interface Listener extends BeginListener, HeadersListener, ContentListener, SuccessListener, FailureListener, CompleteListener
+ public interface Listener extends BeginListener, HeaderListener, HeadersListener, ContentListener, SuccessListener, FailureListener, CompleteListener
{
/**
* An empty implementation of {@link Listener}
@@ -192,6 +209,12 @@ public interface Response
{
}
+ @Override
+ public boolean onHeader(Response response, HttpField field)
+ {
+ return true;
+ }
+
@Override
public void onHeaders(Response response)
{
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/TimedResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/TimedResponseListener.java
index bae72759296..3a18bf46381 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/TimedResponseListener.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/TimedResponseListener.java
@@ -27,6 +27,7 @@ import org.eclipse.jetty.client.Schedulable;
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.HttpField;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Scheduler;
@@ -89,6 +90,14 @@ public class TimedResponseListener implements Response.Listener, Schedulable, Ru
((Response.BeginListener)delegate).onBegin(response);
}
+ @Override
+ public boolean onHeader(Response response, HttpField field)
+ {
+ if (delegate instanceof Response.HeaderListener)
+ return ((Response.HeaderListener)delegate).onHeader(response, field);
+ return true;
+ }
+
@Override
public void onHeaders(Response response)
{
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
index d560e69b74d..a8af2d05345 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
@@ -48,6 +48,7 @@ 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.client.util.BytesContentProvider;
+import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
@@ -638,4 +639,36 @@ public class HttpClientTest extends AbstractHttpClientServerTest
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
}
+
+ @Test
+ public void testHeaderProcessing() throws Exception
+ {
+ final String headerName = "X-Header-Test";
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ response.setHeader(headerName, "X");
+ }
+ });
+
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .onResponseHeader(new Response.HeaderListener()
+ {
+ @Override
+ public boolean onHeader(Response response, HttpField field)
+ {
+ return !field.getName().equals(headerName);
+ }
+ })
+ .send()
+ .get(5, TimeUnit.SECONDS);
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ Assert.assertFalse(response.getHeaders().containsKey(headerName));
+ }
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieParserTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieParserTest.java
deleted file mode 100644
index 744fd437451..00000000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieParserTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2012 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.util.List;
-
-import org.eclipse.jetty.http.HttpCookie;
-import org.eclipse.jetty.toolchain.test.TestTracker;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-
-public class HttpCookieParserTest
-{
- @Rule
- public final TestTracker tracker = new TestTracker();
-
- @Test
- public void testParseCookie1() throws Exception
- {
- String value = "wd=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/; domain=.domain.com; httponly; secure";
- List cookies = HttpCookieParser.parseCookies(value);
- Assert.assertEquals(1, cookies.size());
- HttpCookie cookie = cookies.get(0);
- Assert.assertEquals("wd", cookie.getName());
- Assert.assertEquals("deleted", cookie.getValue());
- Assert.assertEquals(".domain.com", cookie.getDomain());
- Assert.assertEquals("/", cookie.getPath());
- Assert.assertTrue(cookie.isHttpOnly());
- Assert.assertTrue(cookie.isSecure());
- Assert.assertTrue(cookie.isExpired(System.nanoTime()));
- }
-
- @Test
- public void testParseCookie2() throws Exception
- {
- String value = "wd=deleted; max-Age=0; path=/; domain=.domain.com; httponly; version=3";
- List cookies = HttpCookieParser.parseCookies(value);
- Assert.assertEquals(1, cookies.size());
- HttpCookie cookie = cookies.get(0);
- Assert.assertEquals("wd", cookie.getName());
- Assert.assertEquals("deleted", cookie.getValue());
- Assert.assertEquals(".domain.com", cookie.getDomain());
- Assert.assertEquals("/", cookie.getPath());
- Assert.assertTrue(cookie.isHttpOnly());
- Assert.assertEquals(3, cookie.getVersion());
- Assert.assertTrue(cookie.isExpired(System.nanoTime()));
- }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java
index ec97ac48528..69c2e1326fb 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java
@@ -34,6 +34,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -75,6 +76,35 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
+ @Test
+ public void testAbortOnHeader() throws Exception
+ {
+ start(new EmptyServerHandler());
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .onResponseHeader(new Response.HeaderListener()
+ {
+ @Override
+ public boolean onHeader(Response response, HttpField field)
+ {
+ response.abort(new Exception());
+ return true;
+ }
+ })
+ .send(new Response.CompleteListener()
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ Assert.assertTrue(result.isFailed());
+ latch.countDown();
+ }
+ });
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+ }
+
@Test
public void testAbortOnHeaders() throws Exception
{