diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java index d193d64cb49..f6eabb6a3b6 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java @@ -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(); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java index 28533ba310c..7174e2059fd 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java @@ -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 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 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); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java index ec7351f7290..a0f2768bb96 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java @@ -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; } @@ -84,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() { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRedirector.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRedirector.java index aa0da9f08e9..3d7f42d69ec 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRedirector.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRedirector.java @@ -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(); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentResponse.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentResponse.java index 66e55098cec..b509902ed39 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentResponse.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentResponse.java @@ -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 */ 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 75e45dcfdcc..f37988cc4bf 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 @@ -236,7 +236,7 @@ public interface Request Request agent(String agent); /** - * @param accepts the content types that are acceptable in the response, such as + * @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 */ diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java index e30c6b90155..7cc53655775 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java @@ -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()); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/FutureResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FutureResponseListener.java index b6167c6053f..679fcc05220 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/FutureResponseListener.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FutureResponseListener.java @@ -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(); } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ContentResponseTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ContentResponseTest.java new file mode 100644 index 00000000000..0f602996ff8 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ContentResponseTest.java @@ -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()); + } +} diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java index c599fd61228..5884a14f829 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java @@ -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); }