Redesign of HTTP cache resource APIs
This commit is contained in:
parent
e2a464084c
commit
73c67f221d
|
@ -26,7 +26,7 @@
|
|||
*/
|
||||
package org.apache.hc.client5.http.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
|
@ -36,25 +36,40 @@ import java.io.Serializable;
|
|||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
public interface Resource extends Serializable {
|
||||
public abstract class Resource implements Serializable {
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} from which the response
|
||||
* body can be read.
|
||||
* @throws IOException
|
||||
* Returns resource content as a {@link InputStream}.
|
||||
*
|
||||
* @throws ResourceIOException
|
||||
*/
|
||||
InputStream getInputStream() throws IOException;
|
||||
public InputStream getInputStream() throws ResourceIOException {
|
||||
return new ByteArrayInputStream(get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns resource content as a byte array.
|
||||
* <p>
|
||||
* Please note for memory efficiency some resource implementations
|
||||
* may return a reference to the underlying byte array. The returned
|
||||
* value should be treated as immutable.
|
||||
*
|
||||
* @throws ResourceIOException
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public abstract byte[] get() throws ResourceIOException;
|
||||
|
||||
/**
|
||||
* Returns the length in bytes of the response body.
|
||||
*/
|
||||
long length();
|
||||
public abstract long length();
|
||||
|
||||
/**
|
||||
* Indicates the system no longer needs to keep this
|
||||
* response body and any system resources associated with
|
||||
* it may be reclaimed.
|
||||
*/
|
||||
void dispose();
|
||||
public abstract void dispose();
|
||||
|
||||
}
|
||||
|
|
|
@ -26,9 +26,6 @@
|
|||
*/
|
||||
package org.apache.hc.client5.http.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Generates {@link Resource} instances for handling cached
|
||||
* HTTP response bodies.
|
||||
|
@ -39,20 +36,25 @@ public interface ResourceFactory {
|
|||
|
||||
/**
|
||||
* Creates a {@link Resource} from a given response body.
|
||||
* @param requestId a unique identifier for this particular
|
||||
* response body
|
||||
* @param instream the original {@link InputStream}
|
||||
* containing the response body of the origin HTTP response.
|
||||
* @param limit maximum number of bytes to consume of the
|
||||
* response body; if this limit is reached before the
|
||||
* response body is fully consumed, mark the limit has
|
||||
* having been reached and return a {@code Resource}
|
||||
* containing the data read to that point.
|
||||
* @param requestId a unique identifier for this particular response body.
|
||||
* @param content byte array that represents the origin HTTP response body.
|
||||
* @return a {@code Resource} containing however much of
|
||||
* the response body was successfully read.
|
||||
* @throws IOException
|
||||
* @throws ResourceIOException
|
||||
*/
|
||||
Resource generate(String requestId, InputStream instream, InputLimit limit) throws IOException;
|
||||
Resource generate(String requestId, byte[] content) throws ResourceIOException;
|
||||
|
||||
/**
|
||||
* Creates a {@link Resource} from a given response body.
|
||||
* @param requestId a unique identifier for this particular response body.
|
||||
* @param content byte array that represents the origin HTTP response body.
|
||||
* @param off the start offset in the array.
|
||||
* @param len the number of bytes to read from the array.
|
||||
* @return a {@code Resource} containing however much of
|
||||
* the response body was successfully read.
|
||||
* @throws ResourceIOException
|
||||
*/
|
||||
Resource generate(String requestId, byte[] content, int off, int len) throws ResourceIOException;
|
||||
|
||||
/**
|
||||
* Clones an existing {@link Resource}.
|
||||
|
@ -60,8 +62,8 @@ public interface ResourceFactory {
|
|||
* with the cloned response body.
|
||||
* @param resource the original response body to clone.
|
||||
* @return the {@code Resource} copy
|
||||
* @throws IOException
|
||||
* @throws ResourceIOException
|
||||
*/
|
||||
Resource copy(String requestId, Resource resource) throws IOException;
|
||||
Resource copy(String requestId, Resource resource) throws ResourceIOException;
|
||||
|
||||
}
|
||||
|
|
|
@ -26,48 +26,20 @@
|
|||
*/
|
||||
package org.apache.hc.client5.http.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Used to limiting the size of an incoming response body of
|
||||
* unknown size that is optimistically being read in anticipation
|
||||
* of caching it.
|
||||
* @since 4.1
|
||||
* Signals a generic resource I/O error.
|
||||
*/
|
||||
public class InputLimit {
|
||||
public class ResourceIOException extends IOException {
|
||||
|
||||
private final long value;
|
||||
private boolean reached;
|
||||
|
||||
/**
|
||||
* Create a limit for how many bytes of a response body to
|
||||
* read.
|
||||
* @param value maximum length in bytes
|
||||
*/
|
||||
public InputLimit(final long value) {
|
||||
public ResourceIOException(final String message) {
|
||||
super();
|
||||
this.value = value;
|
||||
this.reached = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current maximum limit that was set on
|
||||
* creation.
|
||||
*/
|
||||
public long getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to report that the limit has been reached.
|
||||
*/
|
||||
public void reached() {
|
||||
this.reached = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the input limit has been reached.
|
||||
*/
|
||||
public boolean isReached() {
|
||||
return this.reached;
|
||||
public ResourceIOException(final String message, final Throwable cause) {
|
||||
super(message);
|
||||
initCause(cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -27,6 +27,7 @@
|
|||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
@ -44,6 +45,7 @@ import org.apache.hc.client5.http.cache.Resource;
|
|||
import org.apache.hc.client5.http.cache.ResourceFactory;
|
||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpEntity;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
|
@ -51,6 +53,7 @@ import org.apache.hc.core5.http.HttpResponse;
|
|||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
|
||||
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
|
||||
import org.apache.hc.core5.util.ByteArrayBuffer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
@ -288,41 +291,38 @@ class BasicHttpCache implements HttpCache {
|
|||
final ClassicHttpResponse originResponse,
|
||||
final Date requestSent,
|
||||
final Date responseReceived) throws IOException {
|
||||
|
||||
boolean closeOriginResponse = true;
|
||||
final SizeLimitedResponseReader responseReader = getResponseReader(request, originResponse);
|
||||
try {
|
||||
responseReader.readResponse();
|
||||
|
||||
if (responseReader.isLimitReached()) {
|
||||
closeOriginResponse = false;
|
||||
return responseReader.getReconstructedResponse();
|
||||
}
|
||||
|
||||
final Resource resource = responseReader.getResource();
|
||||
if (isIncompleteResponse(originResponse, resource)) {
|
||||
return generateIncompleteResponseError(originResponse, resource);
|
||||
}
|
||||
|
||||
final HttpCacheEntry entry = new HttpCacheEntry(
|
||||
requestSent,
|
||||
responseReceived,
|
||||
originResponse.getCode(),
|
||||
originResponse.getAllHeaders(),
|
||||
resource);
|
||||
storeInCache(host, request, entry);
|
||||
return responseGenerator.generateResponse(request, entry);
|
||||
} finally {
|
||||
if (closeOriginResponse) {
|
||||
originResponse.close();
|
||||
final Resource resource;
|
||||
final HttpEntity entity = originResponse.getEntity();
|
||||
if (entity != null) {
|
||||
final ByteArrayBuffer buf = new ByteArrayBuffer(1024);
|
||||
final InputStream instream = entity.getContent();
|
||||
final byte[] tmp = new byte[2048];
|
||||
long total = 0;
|
||||
int l;
|
||||
while ((l = instream.read(tmp)) != -1) {
|
||||
buf.append(tmp, 0, l);
|
||||
total += l;
|
||||
if (total > maxObjectSizeBytes) {
|
||||
originResponse.setEntity(new CombinedEntity(entity, buf));
|
||||
return originResponse;
|
||||
}
|
||||
}
|
||||
resource = resourceFactory.generate(request.getRequestUri(), buf.array(), 0, buf.length());
|
||||
} else {
|
||||
resource = null;
|
||||
}
|
||||
}
|
||||
|
||||
SizeLimitedResponseReader getResponseReader(final HttpRequest request,
|
||||
final ClassicHttpResponse backEndResponse) {
|
||||
return new SizeLimitedResponseReader(
|
||||
resourceFactory, maxObjectSizeBytes, request, backEndResponse);
|
||||
originResponse.close();
|
||||
if (isIncompleteResponse(originResponse, resource)) {
|
||||
return generateIncompleteResponseError(originResponse, resource);
|
||||
}
|
||||
final HttpCacheEntry entry = new HttpCacheEntry(
|
||||
requestSent,
|
||||
responseReceived,
|
||||
originResponse.getCode(),
|
||||
originResponse.getAllHeaders(),
|
||||
resource);
|
||||
storeInCache(host, request, entry);
|
||||
return responseGenerator.generateResponse(request, entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -298,7 +298,7 @@ public class CachingExec implements ExecChainHandler {
|
|||
final ExecChain.Scope scope,
|
||||
final ExecChain chain,
|
||||
final HttpCacheEntry entry,
|
||||
final Date now) throws HttpException {
|
||||
final Date now) throws HttpException, IOException {
|
||||
|
||||
final HttpClientContext context = scope.clientContext;
|
||||
try {
|
||||
|
|
|
@ -26,26 +26,31 @@
|
|||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.hc.client5.http.cache.Resource;
|
||||
import org.apache.hc.core5.http.io.entity.AbstractHttpEntity;
|
||||
import org.apache.hc.core5.function.Supplier;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpEntity;
|
||||
import org.apache.hc.core5.util.Args;
|
||||
import org.apache.hc.core5.util.ByteArrayBuffer;
|
||||
|
||||
class CombinedEntity extends AbstractHttpEntity {
|
||||
class CombinedEntity implements HttpEntity {
|
||||
|
||||
private final Resource resource;
|
||||
private final HttpEntity entity;
|
||||
private final InputStream combinedStream;
|
||||
|
||||
CombinedEntity(final Resource resource, final InputStream instream) throws IOException {
|
||||
CombinedEntity(final HttpEntity entity, final ByteArrayBuffer buf) throws IOException {
|
||||
super();
|
||||
this.resource = resource;
|
||||
this.entity = entity;
|
||||
this.combinedStream = new SequenceInputStream(
|
||||
new ResourceStream(resource.getInputStream()), instream);
|
||||
new ByteArrayInputStream(buf.array(), 0, buf.length()),
|
||||
entity.getContent());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,6 +58,21 @@ class CombinedEntity extends AbstractHttpEntity {
|
|||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return entity.getContentType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentEncoding() {
|
||||
return entity.getContentEncoding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChunked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRepeatable() {
|
||||
return false;
|
||||
|
@ -68,6 +88,16 @@ class CombinedEntity extends AbstractHttpEntity {
|
|||
return this.combinedStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getTrailerNames() {
|
||||
return entity.getTrailerNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<List<? extends Header>> getTrailers() {
|
||||
return entity.getTrailers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(final OutputStream outstream) throws IOException {
|
||||
Args.notNull(outstream, "Output stream");
|
||||
|
@ -82,28 +112,11 @@ class CombinedEntity extends AbstractHttpEntity {
|
|||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
dispose();
|
||||
}
|
||||
|
||||
private void dispose() {
|
||||
this.resource.dispose();
|
||||
}
|
||||
|
||||
class ResourceStream extends FilterInputStream {
|
||||
|
||||
protected ResourceStream(final InputStream in) {
|
||||
super(in);
|
||||
try {
|
||||
combinedStream.close();
|
||||
} finally {
|
||||
entity.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
super.close();
|
||||
} finally {
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,12 +28,17 @@ package org.apache.hc.client5.http.impl.cache;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.hc.client5.http.cache.Resource;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
import org.apache.hc.core5.util.Args;
|
||||
import org.apache.hc.core5.util.ByteArrayBuffer;
|
||||
|
||||
/**
|
||||
* Cache resource backed by a file.
|
||||
|
@ -41,41 +46,70 @@ import org.apache.hc.core5.annotation.ThreadingBehavior;
|
|||
* @since 4.1
|
||||
*/
|
||||
@Contract(threading = ThreadingBehavior.SAFE)
|
||||
public class FileResource implements Resource {
|
||||
public class FileResource extends Resource {
|
||||
|
||||
private static final long serialVersionUID = 4132244415919043397L;
|
||||
|
||||
private final File file;
|
||||
|
||||
private volatile boolean disposed;
|
||||
private final AtomicReference<File> fileRef;
|
||||
|
||||
public FileResource(final File file) {
|
||||
super();
|
||||
this.file = file;
|
||||
this.disposed = false;
|
||||
this.fileRef = new AtomicReference<>(Args.notNull(file, "File"));
|
||||
}
|
||||
|
||||
synchronized File getFile() {
|
||||
return this.file;
|
||||
File getFile() {
|
||||
return this.fileRef.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized InputStream getInputStream() throws IOException {
|
||||
return new FileInputStream(this.file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized long length() {
|
||||
return this.file.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void dispose() {
|
||||
if (this.disposed) {
|
||||
return;
|
||||
public byte[] get() throws ResourceIOException {
|
||||
final File file = this.fileRef.get();
|
||||
if (file == null) {
|
||||
throw new ResourceIOException("Resouce already dispoased");
|
||||
}
|
||||
try (final InputStream in = new FileInputStream(file)) {
|
||||
final ByteArrayBuffer buf = new ByteArrayBuffer(1024);
|
||||
final byte[] tmp = new byte[2048];
|
||||
int len;
|
||||
while ((len = in.read(tmp)) != -1) {
|
||||
buf.append(tmp, 0, len);
|
||||
}
|
||||
return buf.toByteArray();
|
||||
} catch (final IOException ex) {
|
||||
throw new ResourceIOException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws ResourceIOException {
|
||||
final File file = this.fileRef.get();
|
||||
if (file != null) {
|
||||
try {
|
||||
return new FileInputStream(file);
|
||||
} catch (final FileNotFoundException ex) {
|
||||
throw new ResourceIOException(ex.getMessage(), ex);
|
||||
}
|
||||
} else {
|
||||
throw new ResourceIOException("Resouce already dispoased");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
final File file = this.fileRef.get();
|
||||
if (file != null) {
|
||||
return file.length();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
final File file = this.fileRef.getAndSet(null);
|
||||
if (file != null) {
|
||||
file.delete();
|
||||
}
|
||||
this.disposed = true;
|
||||
this.file.delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,13 +29,13 @@ package org.apache.hc.client5.http.impl.cache;
|
|||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.hc.client5.http.cache.InputLimit;
|
||||
import org.apache.hc.client5.http.cache.Resource;
|
||||
import org.apache.hc.client5.http.cache.ResourceFactory;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
import org.apache.hc.core5.util.Args;
|
||||
|
||||
/**
|
||||
* Generates {@link Resource} instances whose body is stored in a temporary file.
|
||||
|
@ -73,37 +73,41 @@ public class FileResourceFactory implements ResourceFactory {
|
|||
@Override
|
||||
public Resource generate(
|
||||
final String requestId,
|
||||
final InputStream instream,
|
||||
final InputLimit limit) throws IOException {
|
||||
final byte[] content, final int off, final int len) throws ResourceIOException {
|
||||
Args.notNull(requestId, "Request id");
|
||||
final File file = generateUniqueCacheFile(requestId);
|
||||
try (FileOutputStream outstream = new FileOutputStream(file)) {
|
||||
final byte[] buf = new byte[2048];
|
||||
long total = 0;
|
||||
int l;
|
||||
while ((l = instream.read(buf)) != -1) {
|
||||
outstream.write(buf, 0, l);
|
||||
total += l;
|
||||
if (limit != null && total > limit.getValue()) {
|
||||
limit.reached();
|
||||
break;
|
||||
}
|
||||
if (content != null) {
|
||||
outstream.write(content, off, len);
|
||||
}
|
||||
} catch (final IOException ex) {
|
||||
throw new ResourceIOException(ex.getMessage(), ex);
|
||||
}
|
||||
return new FileResource(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource generate(final String requestId, final byte[] content) throws ResourceIOException {
|
||||
Args.notNull(content, "Content");
|
||||
return generate(requestId, content, 0, content.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource copy(
|
||||
final String requestId,
|
||||
final Resource resource) throws IOException {
|
||||
final Resource resource) throws ResourceIOException {
|
||||
final File file = generateUniqueCacheFile(requestId);
|
||||
|
||||
if (resource instanceof FileResource) {
|
||||
final File src = ((FileResource) resource).getFile();
|
||||
IOUtils.copyFile(src, file);
|
||||
} else {
|
||||
final FileOutputStream out = new FileOutputStream(file);
|
||||
IOUtils.copyAndClose(resource.getInputStream(), out);
|
||||
try {
|
||||
if (resource instanceof FileResource) {
|
||||
final File src = ((FileResource) resource).getFile();
|
||||
IOUtils.copyFile(src, file);
|
||||
} else {
|
||||
final FileOutputStream out = new FileOutputStream(file);
|
||||
IOUtils.copyAndClose(resource.getInputStream(), out);
|
||||
}
|
||||
} catch (final IOException ex) {
|
||||
throw new ResourceIOException(ex.getMessage(), ex);
|
||||
}
|
||||
return new FileResource(file);
|
||||
}
|
||||
|
|
|
@ -26,10 +26,10 @@
|
|||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.hc.client5.http.cache.Resource;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
|
||||
|
@ -39,33 +39,40 @@ import org.apache.hc.core5.annotation.ThreadingBehavior;
|
|||
* @since 4.1
|
||||
*/
|
||||
@Contract(threading = ThreadingBehavior.IMMUTABLE)
|
||||
public class HeapResource implements Resource {
|
||||
public class HeapResource extends Resource {
|
||||
|
||||
private static final long serialVersionUID = -2078599905620463394L;
|
||||
|
||||
private final byte[] b;
|
||||
private final AtomicReference<byte[]> arrayRef;
|
||||
|
||||
public HeapResource(final byte[] b) {
|
||||
super();
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
byte[] getByteArray() {
|
||||
return this.b;
|
||||
this.arrayRef = new AtomicReference<>(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(this.b);
|
||||
public byte[] get() throws ResourceIOException {
|
||||
final byte[] byteArray = this.arrayRef.get();
|
||||
if (byteArray != null) {
|
||||
return byteArray;
|
||||
} else {
|
||||
throw new ResourceIOException("Resouce already dispoased");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return this.b.length;
|
||||
final byte[] byteArray = this.arrayRef.get();
|
||||
if (byteArray != null) {
|
||||
return byteArray.length;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
this.arrayRef.set(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,15 +26,12 @@
|
|||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.hc.client5.http.cache.InputLimit;
|
||||
import org.apache.hc.client5.http.cache.Resource;
|
||||
import org.apache.hc.client5.http.cache.ResourceFactory;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
import org.apache.hc.core5.util.Args;
|
||||
|
||||
/**
|
||||
* Generates {@link Resource} instances stored entirely in heap.
|
||||
|
@ -47,40 +44,23 @@ public class HeapResourceFactory implements ResourceFactory {
|
|||
@Override
|
||||
public Resource generate(
|
||||
final String requestId,
|
||||
final InputStream instream,
|
||||
final InputLimit limit) throws IOException {
|
||||
final ByteArrayOutputStream outstream = new ByteArrayOutputStream();
|
||||
final byte[] buf = new byte[2048];
|
||||
long total = 0;
|
||||
int l;
|
||||
while ((l = instream.read(buf)) != -1) {
|
||||
outstream.write(buf, 0, l);
|
||||
total += l;
|
||||
if (limit != null && total > limit.getValue()) {
|
||||
limit.reached();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return createResource(outstream.toByteArray());
|
||||
final byte[] content, final int off, final int len) throws ResourceIOException {
|
||||
final byte[] copy = new byte[len];
|
||||
System.arraycopy(content, off, copy, 0, len);
|
||||
return new HeapResource(copy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource generate(final String requestId, final byte[] content) {
|
||||
return new HeapResource(content != null ? content.clone() : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource copy(
|
||||
final String requestId,
|
||||
final Resource resource) throws IOException {
|
||||
final byte[] body;
|
||||
if (resource instanceof HeapResource) {
|
||||
body = ((HeapResource) resource).getByteArray();
|
||||
} else {
|
||||
final ByteArrayOutputStream outstream = new ByteArrayOutputStream();
|
||||
IOUtils.copyAndClose(resource.getInputStream(), outstream);
|
||||
body = outstream.toByteArray();
|
||||
}
|
||||
return createResource(body);
|
||||
}
|
||||
|
||||
Resource createResource(final byte[] buf) {
|
||||
return new HeapResource(buf);
|
||||
final Resource resource) throws ResourceIOException {
|
||||
Args.notNull(resource, "Resource");
|
||||
return new HeapResource(resource.get());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,146 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.hc.client5.http.cache.InputLimit;
|
||||
import org.apache.hc.client5.http.cache.Resource;
|
||||
import org.apache.hc.client5.http.cache.ResourceFactory;
|
||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.HttpEntity;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
class SizeLimitedResponseReader {
|
||||
|
||||
private final ResourceFactory resourceFactory;
|
||||
private final long maxResponseSizeBytes;
|
||||
private final HttpRequest request;
|
||||
private final ClassicHttpResponse response;
|
||||
|
||||
private InputStream instream;
|
||||
private InputLimit limit;
|
||||
private Resource resource;
|
||||
private boolean consumed;
|
||||
|
||||
/**
|
||||
* Create an {@link ClassicHttpResponse} that is limited in size, this allows for checking
|
||||
* the size of objects that will be stored in the cache.
|
||||
*/
|
||||
public SizeLimitedResponseReader(
|
||||
final ResourceFactory resourceFactory,
|
||||
final long maxResponseSizeBytes,
|
||||
final HttpRequest request,
|
||||
final ClassicHttpResponse response) {
|
||||
super();
|
||||
this.resourceFactory = resourceFactory;
|
||||
this.maxResponseSizeBytes = maxResponseSizeBytes;
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
protected void readResponse() throws IOException {
|
||||
if (!consumed) {
|
||||
doConsume();
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureNotConsumed() {
|
||||
if (consumed) {
|
||||
throw new IllegalStateException("Response has already been consumed");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureConsumed() {
|
||||
if (!consumed) {
|
||||
throw new IllegalStateException("Response has not been consumed");
|
||||
}
|
||||
}
|
||||
|
||||
private void doConsume() throws IOException {
|
||||
ensureNotConsumed();
|
||||
consumed = true;
|
||||
|
||||
limit = new InputLimit(maxResponseSizeBytes);
|
||||
|
||||
final HttpEntity entity = response.getEntity();
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
final String uri = request.getRequestUri();
|
||||
instream = entity.getContent();
|
||||
try {
|
||||
resource = resourceFactory.generate(uri, instream, limit);
|
||||
} finally {
|
||||
if (!limit.isReached()) {
|
||||
instream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isLimitReached() {
|
||||
ensureConsumed();
|
||||
return limit.isReached();
|
||||
}
|
||||
|
||||
Resource getResource() {
|
||||
ensureConsumed();
|
||||
return resource;
|
||||
}
|
||||
|
||||
ClassicHttpResponse getReconstructedResponse() throws IOException {
|
||||
ensureConsumed();
|
||||
final ClassicHttpResponse reconstructed = new BasicClassicHttpResponse(response.getCode()) {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
super.close();
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
reconstructed.setHeaders(response.getAllHeaders());
|
||||
|
||||
final CombinedEntity combinedEntity = new CombinedEntity(resource, instream);
|
||||
final HttpEntity entity = response.getEntity();
|
||||
if (entity != null) {
|
||||
combinedEntity.setContentType(entity.getContentType());
|
||||
combinedEntity.setContentEncoding(entity.getContentEncoding());
|
||||
combinedEntity.setChunked(entity.isChunked());
|
||||
}
|
||||
reconstructed.setEntity(combinedEntity);
|
||||
return reconstructed;
|
||||
}
|
||||
|
||||
}
|
|
@ -35,7 +35,6 @@ import static org.junit.Assert.assertTrue;
|
|||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
@ -359,7 +358,7 @@ public class TestBasicHttpCache {
|
|||
|
||||
final ClassicHttpResponse result = impl.cacheAndReturnResponse(host, request, originResponse, requestSent, responseReceived);
|
||||
assertEquals(0, backing.map.size());
|
||||
assertTrue(HttpTestUtils.semanticallyTransparent(originResponse, result));
|
||||
assertSame(originResponse, result);
|
||||
}
|
||||
|
||||
|
||||
|
@ -521,53 +520,10 @@ public class TestBasicHttpCache {
|
|||
assertTrue(inputStream.wasClosed());
|
||||
}
|
||||
|
||||
static class DisposableResource implements Resource {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final byte[] b;
|
||||
private boolean dispoased;
|
||||
|
||||
public DisposableResource(final byte[] b) {
|
||||
super();
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
byte[] getByteArray() {
|
||||
return this.b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
if (dispoased) {
|
||||
throw new IOException("Already dispoased");
|
||||
}
|
||||
return new ByteArrayInputStream(this.b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return this.b.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
this.dispoased = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntryUpdate() throws Exception {
|
||||
|
||||
final HeapResourceFactory rf = new HeapResourceFactory() {
|
||||
|
||||
@Override
|
||||
Resource createResource(final byte[] buf) {
|
||||
return new DisposableResource(buf);
|
||||
}
|
||||
|
||||
};
|
||||
final HeapResourceFactory rf = new HeapResourceFactory();
|
||||
|
||||
impl = new BasicHttpCache(rf, backing, CacheConfig.DEFAULT);
|
||||
|
||||
|
|
|
@ -32,8 +32,9 @@ import static org.mockito.Mockito.when;
|
|||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import org.apache.hc.client5.http.cache.Resource;
|
||||
import org.apache.hc.core5.http.HttpEntity;
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
||||
import org.apache.hc.core5.util.ByteArrayBuffer;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -41,21 +42,25 @@ public class TestCombinedEntity {
|
|||
|
||||
@Test
|
||||
public void testCombinedEntityBasics() throws Exception {
|
||||
final Resource resource = mock(Resource.class);
|
||||
when(resource.getInputStream()).thenReturn(
|
||||
new ByteArrayInputStream(new byte[] { 1, 2, 3, 4, 5 }));
|
||||
final HttpEntity httpEntity = mock(HttpEntity.class);
|
||||
when(httpEntity.getContent()).thenReturn(
|
||||
new ByteArrayInputStream(new byte[] { 6, 7, 8, 9, 10 }));
|
||||
|
||||
final ByteArrayInputStream instream = new ByteArrayInputStream(new byte[] { 6, 7, 8, 9, 10 });
|
||||
final CombinedEntity entity = new CombinedEntity(resource, instream);
|
||||
final ByteArrayBuffer buf = new ByteArrayBuffer(1024);
|
||||
final byte[] tmp = new byte[] { 1, 2, 3, 4, 5 };
|
||||
buf.append(tmp, 0, tmp.length);
|
||||
final CombinedEntity entity = new CombinedEntity(httpEntity, buf);
|
||||
Assert.assertEquals(-1, entity.getContentLength());
|
||||
Assert.assertFalse(entity.isRepeatable());
|
||||
Assert.assertTrue(entity.isStreaming());
|
||||
|
||||
final byte[] result = EntityUtils.toByteArray(entity);
|
||||
Assert.assertArrayEquals(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, result);
|
||||
Assert.assertArrayEquals(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, EntityUtils.toByteArray(entity));
|
||||
|
||||
verify(resource).getInputStream();
|
||||
verify(resource).dispose();
|
||||
verify(httpEntity).getContent();
|
||||
|
||||
entity.close();
|
||||
|
||||
verify(httpEntity).close();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,196 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.HttpEntity;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
||||
import org.apache.hc.core5.http.io.entity.StringEntity;
|
||||
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestSizeLimitedResponseReader {
|
||||
|
||||
private static final long MAX_SIZE = 4;
|
||||
|
||||
private HttpRequest request;
|
||||
private SizeLimitedResponseReader impl;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
request = new HttpGet("http://foo.example.com/bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLargeResponseIsTooLarge() throws Exception {
|
||||
final byte[] buf = new byte[] { 1, 2, 3, 4, 5 };
|
||||
final ClassicHttpResponse response = make200Response(buf);
|
||||
|
||||
impl = new SizeLimitedResponseReader(new HeapResourceFactory(), MAX_SIZE, request, response);
|
||||
|
||||
impl.readResponse();
|
||||
final boolean tooLarge = impl.isLimitReached();
|
||||
final ClassicHttpResponse result = impl.getReconstructedResponse();
|
||||
final byte[] body = EntityUtils.toByteArray(result.getEntity());
|
||||
|
||||
Assert.assertTrue(tooLarge);
|
||||
Assert.assertArrayEquals(buf, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExactSizeResponseIsNotTooLarge() throws Exception {
|
||||
final byte[] buf = new byte[] { 1, 2, 3, 4 };
|
||||
final ClassicHttpResponse response = make200Response(buf);
|
||||
|
||||
impl = new SizeLimitedResponseReader(new HeapResourceFactory(), MAX_SIZE, request, response);
|
||||
|
||||
impl.readResponse();
|
||||
final boolean tooLarge = impl.isLimitReached();
|
||||
final ClassicHttpResponse reconstructed = impl.getReconstructedResponse();
|
||||
final byte[] result = EntityUtils.toByteArray(reconstructed.getEntity());
|
||||
|
||||
Assert.assertFalse(tooLarge);
|
||||
Assert.assertArrayEquals(buf, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallResponseIsNotTooLarge() throws Exception {
|
||||
final byte[] buf = new byte[] { 1, 2, 3 };
|
||||
final ClassicHttpResponse response = make200Response(buf);
|
||||
|
||||
impl = new SizeLimitedResponseReader(new HeapResourceFactory(), MAX_SIZE, request, response);
|
||||
|
||||
impl.readResponse();
|
||||
final boolean tooLarge = impl.isLimitReached();
|
||||
final ClassicHttpResponse reconstructed = impl.getReconstructedResponse();
|
||||
final byte[] result = EntityUtils.toByteArray(reconstructed.getEntity());
|
||||
|
||||
Assert.assertFalse(tooLarge);
|
||||
Assert.assertArrayEquals(buf, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseWithNoEntityIsNotTooLarge() throws Exception {
|
||||
final ClassicHttpResponse response = make200Response();
|
||||
|
||||
impl = new SizeLimitedResponseReader(new HeapResourceFactory(), MAX_SIZE, request, response);
|
||||
|
||||
impl.readResponse();
|
||||
final boolean tooLarge = impl.isLimitReached();
|
||||
|
||||
Assert.assertFalse(tooLarge);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTooLargeEntityHasOriginalContentTypes() throws Exception {
|
||||
final ClassicHttpResponse response = make200Response();
|
||||
final StringEntity entity = new StringEntity("large entity content");
|
||||
response.setEntity(entity);
|
||||
|
||||
impl = new SizeLimitedResponseReader(new HeapResourceFactory(), MAX_SIZE, request, response);
|
||||
|
||||
impl.readResponse();
|
||||
final boolean tooLarge = impl.isLimitReached();
|
||||
final ClassicHttpResponse result = impl.getReconstructedResponse();
|
||||
final HttpEntity reconstructedEntity = result.getEntity();
|
||||
Assert.assertEquals(entity.getContentEncoding(), reconstructedEntity.getContentEncoding());
|
||||
Assert.assertEquals(entity.getContentType(), reconstructedEntity.getContentType());
|
||||
|
||||
final String content = EntityUtils.toString(reconstructedEntity);
|
||||
|
||||
Assert.assertTrue(tooLarge);
|
||||
Assert.assertEquals("large entity content", content);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTooLargeResponseCombinedClosed() throws Exception {
|
||||
final AtomicBoolean closed = new AtomicBoolean(false);
|
||||
final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK") {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
closed.set(true);
|
||||
}
|
||||
};
|
||||
final StringEntity entity = new StringEntity("large entity content");
|
||||
response.setEntity(entity);
|
||||
|
||||
impl = new SizeLimitedResponseReader(new HeapResourceFactory(), MAX_SIZE, request, response);
|
||||
|
||||
impl.readResponse();
|
||||
final boolean tooLarge = impl.isLimitReached();
|
||||
try (ClassicHttpResponse result = impl.getReconstructedResponse()) {
|
||||
final HttpEntity reconstructedEntity = result.getEntity();
|
||||
Assert.assertEquals(entity.getContentEncoding(), reconstructedEntity.getContentEncoding());
|
||||
Assert.assertEquals(entity.getContentType(), reconstructedEntity.getContentType());
|
||||
|
||||
Assert.assertFalse(closed.get());
|
||||
final String content = EntityUtils.toString(reconstructedEntity);
|
||||
|
||||
Assert.assertTrue(tooLarge);
|
||||
Assert.assertEquals("large entity content", content);
|
||||
}
|
||||
Assert.assertTrue(closed.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseCopiesAllOriginalHeaders() throws Exception {
|
||||
final byte[] buf = new byte[] { 1, 2, 3 };
|
||||
final ClassicHttpResponse response = make200Response(buf);
|
||||
response.setHeader("Content-Encoding", "gzip");
|
||||
|
||||
impl = new SizeLimitedResponseReader(new HeapResourceFactory(), MAX_SIZE, request, response);
|
||||
|
||||
impl.readResponse();
|
||||
final boolean tooLarge = impl.isLimitReached();
|
||||
final ClassicHttpResponse reconstructed = impl.getReconstructedResponse();
|
||||
final byte[] result = EntityUtils.toByteArray(reconstructed.getEntity());
|
||||
|
||||
Assert.assertFalse(tooLarge);
|
||||
Assert.assertArrayEquals(buf, result);
|
||||
Assert.assertEquals("gzip", reconstructed.getFirstHeader("Content-Encoding").getValue());
|
||||
}
|
||||
|
||||
private ClassicHttpResponse make200Response() {
|
||||
return new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
|
||||
}
|
||||
|
||||
private ClassicHttpResponse make200Response(final byte[] buf) {
|
||||
final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
|
||||
response.setEntity(new ByteArrayEntity(buf));
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue