// // ======================================================================== // Copyright (c) 1995-2017 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.ByteArrayInputStream; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Locale; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Response.Listener; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.util.BufferUtil; /** * <p>Implementation of {@link Listener} that buffers the content up to a maximum length * specified to the constructors.</p> * <p>The content may be retrieved from {@link #onSuccess(Response)} or {@link #onComplete(Result)} * via {@link #getContent()} or {@link #getContentAsString()}.</p> */ public abstract class BufferingResponseListener extends Listener.Adapter { private final int maxLength; private volatile ByteBuffer buffer; private volatile String mediaType; private volatile String encoding; /** * Creates an instance with a default maximum length of 2 MiB. */ public BufferingResponseListener() { this(2 * 1024 * 1024); } /** * Creates an instance with the given maximum length * * @param maxLength the maximum length of the content */ public BufferingResponseListener(int 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) { response.abort(new IllegalArgumentException("Buffering capacity exceeded")); return; } 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 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)) { 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); } BufferUtil.append(buffer, content); } @Override public abstract void onComplete(Result result); public String getMediaType() { return mediaType; } public String getEncoding() { return encoding; } /** * @return the content as bytes * @see #getContentAsString() */ public byte[] getContent() { 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. * @see #getContentAsString(String) */ public String getContentAsString() { String encoding = this.encoding; if (encoding == null) return getContentAsString(StandardCharsets.UTF_8); return getContentAsString(encoding); } /** * @param encoding the encoding of the content bytes * @return the content as a string, with the specified encoding * @see #getContentAsString() */ public String getContentAsString(String encoding) { if (buffer == null) return null; return BufferUtil.toString(buffer, Charset.forName(encoding)); } /** * @param encoding the encoding of the content bytes * @return the content as a string, with the specified encoding * @see #getContentAsString() */ public String getContentAsString(Charset encoding) { if (buffer == null) return null; return BufferUtil.toString(buffer, encoding); } /** * @return Content as InputStream */ public InputStream getContentAsInputStream() { if (buffer == null) return new ByteArrayInputStream(new byte[]{}); return new ByteArrayInputStream(buffer.array(), buffer.arrayOffset(), buffer.remaining()); } }