blob: 2d1e87ac5c3892908ef164117eff6cef26f5499a [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2016 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());
}
}