From b915a3ab33d99b5d1498ac74f2e7f710984f4510 Mon Sep 17 00:00:00 2001 From: Arturo Bernal Date: Sat, 25 Mar 2023 23:31:34 +0100 Subject: [PATCH] Improve HttpByteArrayCacheEntrySerializer class by adding new methods and enhancing performance. This commit adds several new constant values to the HttpByteArrayCacheEntrySerializer class to manage cache entry headers. The HttpByteArrayCacheEntrySerializer class was also updated with a new constructor to set the buffer size and a new method to serialize HTTP cache storage entry objects. Additionally, the code was refactored to enhance performance and remove unnecessary variables. --- .../hc/client5/http/cache/HttpCacheEntry.java | 14 +- .../AbstractBinaryAsyncCacheStorage.java | 2 +- .../cache/AbstractBinaryCacheStorage.java | 2 +- .../cache/ByteArrayCacheEntrySerializer.java | 3 + .../HttpByteArrayCacheEntrySerializer.java | 522 +++++++++++------- .../ehcache/EhcacheHttpCacheStorage.java | 4 +- .../MemcachedHttpAsyncCacheStorage.java | 6 +- .../memcached/MemcachedHttpCacheStorage.java | 8 +- .../http/impl/cache/HttpTestUtils.java | 3 +- ...tAbstractSerializingAsyncCacheStorage.java | 2 +- .../TestAbstractSerializingCacheStorage.java | 2 +- ...TestHttpByteArrayCacheEntrySerializer.java | 96 ++-- 12 files changed, 392 insertions(+), 272 deletions(-) diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java index 11432ac17..e84e4e176 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java @@ -365,7 +365,7 @@ public class HttpCacheEntry implements MessageHeaders, Serializable { * the "parent" entry to hold this index of the other variants. */ public Map getVariantMap() { - return Collections.unmodifiableMap(variantMap); + return variantMap != null ? Collections.unmodifiableMap(variantMap) : Collections.emptyMap(); } /** @@ -388,8 +388,14 @@ public class HttpCacheEntry implements MessageHeaders, Serializable { */ @Override public String toString() { - return "[request date=" + this.requestDate + "; response date=" + this.responseDate - + "; status=" + this.status + "]"; + return "HttpCacheEntry{" + + "requestDate=" + requestDate + + ", responseDate=" + responseDate + + ", status=" + status + + ", responseHeaders=" + responseHeaders + + ", resource=" + resource + + ", variantMap=" + variantMap + + ", date=" + date + + '}'; } - } diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractBinaryAsyncCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractBinaryAsyncCacheStorage.java index b1361cabb..550b05b4a 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractBinaryAsyncCacheStorage.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractBinaryAsyncCacheStorage.java @@ -40,7 +40,7 @@ public abstract class AbstractBinaryAsyncCacheStorage extends AbstractSeria } public AbstractBinaryAsyncCacheStorage(final int maxUpdateRetries) { - super(maxUpdateRetries, ByteArrayCacheEntrySerializer.INSTANCE); + super(maxUpdateRetries, HttpByteArrayCacheEntrySerializer.INSTANCE); } } diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractBinaryCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractBinaryCacheStorage.java index e959216dd..7193a96d2 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractBinaryCacheStorage.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractBinaryCacheStorage.java @@ -40,7 +40,7 @@ public abstract class AbstractBinaryCacheStorage extends AbstractSerializin } public AbstractBinaryCacheStorage(final int maxUpdateRetries) { - super(maxUpdateRetries, ByteArrayCacheEntrySerializer.INSTANCE); + super(maxUpdateRetries, HttpByteArrayCacheEntrySerializer.INSTANCE); } } diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ByteArrayCacheEntrySerializer.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ByteArrayCacheEntrySerializer.java index 1bebc95a0..fa981aa03 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ByteArrayCacheEntrySerializer.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ByteArrayCacheEntrySerializer.java @@ -51,7 +51,10 @@ import org.apache.hc.core5.annotation.ThreadingBehavior; * @see java.io.Serializable * * @since 4.1 + * @deprecated This class is deprecated and will be removed in a future release. Please use {@link HttpByteArrayCacheEntrySerializer} for improved performance. + * @see HttpByteArrayCacheEntrySerializer */ +@Deprecated @Contract(threading = ThreadingBehavior.STATELESS) public final class ByteArrayCacheEntrySerializer implements HttpCacheEntrySerializer { diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpByteArrayCacheEntrySerializer.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpByteArrayCacheEntrySerializer.java index 9d81ae1a6..90e372e3d 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpByteArrayCacheEntrySerializer.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpByteArrayCacheEntrySerializer.java @@ -32,8 +32,10 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.time.Instant; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; @@ -42,14 +44,16 @@ import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer; import org.apache.hc.client5.http.cache.HttpCacheStorageEntry; import org.apache.hc.client5.http.cache.Resource; import org.apache.hc.client5.http.cache.ResourceIOException; -import org.apache.hc.core5.annotation.Experimental; -import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.annotation.Contract; +import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; +import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.impl.io.AbstractMessageParser; import org.apache.hc.core5.http.impl.io.AbstractMessageWriter; import org.apache.hc.core5.http.impl.io.DefaultHttpResponseParser; @@ -57,21 +61,51 @@ import org.apache.hc.core5.http.impl.io.SessionInputBufferImpl; import org.apache.hc.core5.http.impl.io.SessionOutputBufferImpl; import org.apache.hc.core5.http.io.SessionInputBuffer; import org.apache.hc.core5.http.io.SessionOutputBuffer; +import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.message.BasicLineFormatter; +import org.apache.hc.core5.http.message.HeaderGroup; +import org.apache.hc.core5.http.message.LazyLineParser; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.util.CharArrayBuffer; import org.apache.hc.core5.util.TimeValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * Cache serializer and deserializer that uses an HTTP-like format. + * Serializes and deserializes byte arrays into HTTP cache entries using a default buffer size of 8192 bytes. + * The cache entries contain an HTTP response generated from the byte array data, which can be used to generate + * HTTP responses for cache hits. + *

+ * This implementation uses the Apache HttpComponents library to perform the serialization and deserialization. + *

+ * To serialize a byte array into an HTTP cache entry, use the {@link #serialize(HttpCacheStorageEntry)} method. To deserialize an HTTP cache + * entry into a byte array, use the {@link #deserialize(byte[])} method. + *

+ * This class implements the {@link HttpCacheEntrySerializer} interface, which defines the contract for HTTP cache + * entry serialization and deserialization. It also includes a default buffer size of 8192 bytes, which can be + * overridden by specifying a different buffer size in the constructor. + *

+ * Note that this implementation only supports HTTP responses and does not support HTTP requests or any other types of + * HTTP messages. * - * Existing libraries for reading and writing HTTP are used, and metadata is encoded into HTTP - * pseudo-headers for storage. + * @since 5.3 */ -@Experimental +@Contract(threading = ThreadingBehavior.STATELESS) public class HttpByteArrayCacheEntrySerializer implements HttpCacheEntrySerializer { - public static final HttpByteArrayCacheEntrySerializer INSTANCE = new HttpByteArrayCacheEntrySerializer(); + + private static final Logger LOG = LoggerFactory.getLogger(HttpByteArrayCacheEntrySerializer.class); + + /** + * The default buffer size used for I/O operations, set to 8192 bytes. + */ + private static final int DEFAULT_BUFFER_SIZE = 8192; + + /** + * Singleton instance of this class. + */ + public static final HttpByteArrayCacheEntrySerializer INSTANCE = new HttpByteArrayCacheEntrySerializer(DEFAULT_BUFFER_SIZE); + private static final String SC_CACHE_ENTRY_PREFIX = "hc-"; @@ -81,14 +115,58 @@ public class HttpByteArrayCacheEntrySerializer implements HttpCacheEntrySerializ private static final String SC_HEADER_NAME_NO_CONTENT = SC_CACHE_ENTRY_PREFIX + "no-content"; private static final String SC_HEADER_NAME_VARIANT_MAP_KEY = SC_CACHE_ENTRY_PREFIX + "varmap-key"; private static final String SC_HEADER_NAME_VARIANT_MAP_VALUE = SC_CACHE_ENTRY_PREFIX + "varmap-val"; - private static final String SC_CACHE_ENTRY_PRESERVE_PREFIX = SC_CACHE_ENTRY_PREFIX + "esc-"; - private static final int BUFFER_SIZE = 8192; - public HttpByteArrayCacheEntrySerializer() { + /** + * The generator used to generate cached HTTP responses. + */ + private final CachedHttpResponseGenerator cachedHttpResponseGenerator; + + /** + * The size of the buffer used for reading/writing data. + */ + private final int bufferSize; + + /** + * The parser used for reading SimpleHttpResponse instances from the network. + */ + private final AbstractMessageParser responseParser = new SimpleHttpResponseParser(); + + /** + * The writer used for writing SimpleHttpResponse instances to the network. + */ + private final AbstractMessageWriter responseWriter = new SimpleHttpResponseWriter(); + + /** + * Constructs a HttpByteArrayCacheEntrySerializer with the specified buffer size. + * + * @param bufferSize the buffer size to use for serialization and deserialization. + */ + public HttpByteArrayCacheEntrySerializer( + final int bufferSize) { + this.bufferSize = bufferSize > 0 ? bufferSize : DEFAULT_BUFFER_SIZE; + this.cachedHttpResponseGenerator = new CachedHttpResponseGenerator(NoAgeCacheValidityPolicy.INSTANCE); } + /** + * Constructs a new instance of {@code HttpByteArrayCacheEntrySerializer} with a default buffer size. + * + * @see #DEFAULT_BUFFER_SIZE + */ + public HttpByteArrayCacheEntrySerializer() { + this(DEFAULT_BUFFER_SIZE); + } + + /** + * Serializes an HttpCacheStorageEntry object into a byte array using an HTTP-like format. + *

+ * The metadata is encoded into HTTP pseudo-headers for storage. + * + * @param httpCacheEntry the HttpCacheStorageEntry to serialize. + * @return the byte array containing the serialized HttpCacheStorageEntry. + * @throws ResourceIOException if there is an error during serialization. + */ @Override public byte[] serialize(final HttpCacheStorageEntry httpCacheEntry) throws ResourceIOException { if (httpCacheEntry.getKey() == null) { @@ -100,67 +178,138 @@ public class HttpByteArrayCacheEntrySerializer implements HttpCacheEntrySerializ // Use request method from httpCacheEntry, but as far as I can tell it will only ever return "GET". final HttpRequest httpRequest = new BasicHttpRequest(httpCacheEntry.getContent().getRequestMethod(), "/"); - final CacheValidityPolicy cacheValidityPolicy = new NoAgeCacheValidityPolicy(); - final CachedHttpResponseGenerator cachedHttpResponseGenerator = new CachedHttpResponseGenerator(cacheValidityPolicy); - final SimpleHttpResponse httpResponse = cachedHttpResponseGenerator.generateResponse(httpRequest, httpCacheEntry.getContent()); - - try(final ByteArrayOutputStream out = new ByteArrayOutputStream()) { + final int size = httpResponse.getHeaders().length + (httpResponse.getBodyBytes() != null ? httpResponse.getBodyBytes().length : 0); + try (ByteArrayOutputStream out = new ByteArrayOutputStream(size)) { escapeHeaders(httpResponse); addMetadataPseudoHeaders(httpResponse, httpCacheEntry); final byte[] bodyBytes = httpResponse.getBodyBytes(); - final int resourceLength; + int resourceLength = 0; if (bodyBytes == null) { // This means no content, for example a 204 response httpResponse.addHeader(SC_HEADER_NAME_NO_CONTENT, Boolean.TRUE.toString()); - resourceLength = 0; } else { resourceLength = bodyBytes.length; } - // Use the default, ASCII-only encoder for HTTP protocol and header values. - // It's the only thing that's widely used, and it's not worth it to support anything else. - final SessionOutputBufferImpl outputBuffer = new SessionOutputBufferImpl(BUFFER_SIZE); - final AbstractMessageWriter httpResponseWriter = makeHttpResponseWriter(outputBuffer); - httpResponseWriter.write(httpResponse, outputBuffer, out); + final SessionOutputBuffer outputBuffer = new SessionOutputBufferImpl(bufferSize); + + responseWriter.write(httpResponse, outputBuffer, out); outputBuffer.flush(out); final byte[] headerBytes = out.toByteArray(); - final byte[] bytes = new byte[headerBytes.length + resourceLength]; - System.arraycopy(headerBytes, 0, bytes, 0, headerBytes.length); + final ByteBuffer buffer = ByteBuffer.allocate(headerBytes.length + resourceLength); + buffer.put(headerBytes, 0, headerBytes.length); if (resourceLength > 0) { - System.arraycopy(bodyBytes, 0, bytes, headerBytes.length, resourceLength); + buffer.put(bodyBytes, 0, resourceLength); } - return bytes; - } catch(final IOException|HttpException e) { + + if (LOG.isDebugEnabled()) { + LOG.debug("Serialized cache entry with key {} and {} bytes", httpCacheEntry.getKey(), size); + } + + return buffer.array(); + + } catch (final IOException | HttpException e) { throw new ResourceIOException("Exception while serializing cache entry", e); } } + /** + * Deserializes a byte array representation of an HTTP cache storage entry into an instance of + * {@link HttpCacheStorageEntry}. + * + * @param serializedObject the byte array representation of the HTTP cache storage entry + * @return the deserialized HTTP cache storage entry + * @throws ResourceIOException if an error occurs during deserialization + */ @Override public HttpCacheStorageEntry deserialize(final byte[] serializedObject) throws ResourceIOException { - try (final InputStream in = makeByteArrayInputStream(serializedObject); + if (serializedObject == null || serializedObject.length == 0) { + throw new ResourceIOException("Serialized object is null or empty"); + } + try (final InputStream in = new ByteArrayInputStream(serializedObject); final ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(serializedObject.length) // this is bigger than necessary but will save us from reallocating ) { - final SessionInputBufferImpl inputBuffer = new SessionInputBufferImpl(BUFFER_SIZE); - final AbstractMessageParser responseParser = makeHttpResponseParser(); - final ClassicHttpResponse response = responseParser.parse(inputBuffer, in); + final SessionInputBufferImpl inputBuffer = new SessionInputBufferImpl(bufferSize); + final SimpleHttpResponse response = responseParser.parse(inputBuffer, in); - // Extract metadata pseudo-headers - final String storageKey = getCachePseudoHeaderAndRemove(response, SC_HEADER_NAME_STORAGE_KEY); - final Instant requestDate = getCachePseudoHeaderDateAndRemove(response, SC_HEADER_NAME_REQUEST_DATE); - final Instant responseDate = getCachePseudoHeaderDateAndRemove(response, SC_HEADER_NAME_RESPONSE_DATE); - final boolean noBody = getCachePseudoHeaderBooleanAndRemove(response, SC_HEADER_NAME_NO_CONTENT); - final Map variantMap = getVariantMapPseudoHeadersAndRemove(response); - unescapeHeaders(response); + if (LOG.isDebugEnabled()) { + LOG.debug("Deserializing cache entry with headers: {}", response.getHeaders()); + } + + final Map variantMap = new HashMap<>(); + + String lastKey = null; + String storageKey = null; + Instant requestDate = null; + Instant responseDate= null; + boolean noBody = false; + + final HeaderGroup headerGroup = new HeaderGroup(); + + for (final Iterator

it = response.headerIterator(); it.hasNext(); ) { + final Header header = it.next(); + + if (header != null) { + final String headerName = header.getName(); + final String value = header.getValue(); + + if (headerName.equals(SC_HEADER_NAME_VARIANT_MAP_KEY)) { + lastKey = header.getValue(); + continue; + } else if (headerName.equals(SC_HEADER_NAME_VARIANT_MAP_VALUE)) { + if (lastKey == null) { + throw new ResourceIOException("Found mismatched variant map key/value headers"); + } + variantMap.put(lastKey, value); + lastKey = null; + } + + if (headerName.equalsIgnoreCase(SC_HEADER_NAME_STORAGE_KEY)) { + storageKey = value; + continue; + } + + if (headerName.equalsIgnoreCase(SC_HEADER_NAME_REQUEST_DATE)) { + requestDate = parseCachePseudoHeaderDate(value); + continue; + } + + if (headerName.equalsIgnoreCase(SC_HEADER_NAME_RESPONSE_DATE)) { + responseDate = parseCachePseudoHeaderDate(value); + continue; + } + + if (headerName.equalsIgnoreCase(SC_HEADER_NAME_NO_CONTENT)) { + noBody = Boolean.parseBoolean(value); + continue; + } + + if (headerName.startsWith(SC_CACHE_ENTRY_PRESERVE_PREFIX)) { + headerGroup.addHeader(new BasicHeader(header.getName().substring(SC_CACHE_ENTRY_PRESERVE_PREFIX.length()), header.getValue())); + continue; + } + headerGroup.addHeader(header); + } + } + + headerNotNull(storageKey,SC_HEADER_NAME_STORAGE_KEY); + headerNotNull(requestDate, SC_HEADER_NAME_REQUEST_DATE); + headerNotNull(responseDate, SC_HEADER_NAME_RESPONSE_DATE); + + + if (lastKey != null) { + throw new ResourceIOException("Found mismatched variant map key/value headers"); + } + + response.setHeaders(headerGroup.getHeaders()); + + Resource resource = null; + if (!noBody) { - final Resource resource; - if (noBody) { - // This means no content, for example a 204 response - resource = null; - } else { copyBytes(inputBuffer, in, bytesOut); resource = new HeapResource(bytesOut.toByteArray()); } @@ -174,79 +323,37 @@ public class HttpByteArrayCacheEntrySerializer implements HttpCacheEntrySerializ variantMap ); + + if (LOG.isDebugEnabled()) { + LOG.debug("Returning deserialized cache entry with storage key '{}'" , httpCacheEntry); + } + + return new HttpCacheStorageEntry(storageKey, httpCacheEntry); - } catch (final IOException|HttpException e) { + } catch (final IOException | HttpException e) { throw new ResourceIOException("Error deserializing cache entry", e); } } - /** - * Helper method to make a new HTTP response writer. - *

- * Useful to override for testing. - * - * @param outputBuffer Output buffer to write to - * @return HTTP response writer to write to - */ - protected AbstractMessageWriter makeHttpResponseWriter(final SessionOutputBuffer outputBuffer) { - return new SimpleHttpResponseWriter(); - } - - /** - * Helper method to make a new ByteArrayInputStream. - *

- * Useful to override for testing. - * - * @param bytes Bytes to read from the stream - * @return Stream to read the bytes from - */ - protected InputStream makeByteArrayInputStream(final byte[] bytes) { - return new ByteArrayInputStream(bytes); - } - - /** - * Helper method to make a new HTTP Response parser. - *

- * Useful to override for testing. - * - * @return HTTP response parser - */ - protected AbstractMessageParser makeHttpResponseParser() { - return new DefaultHttpResponseParser(); - } - /** * Modify the given response to escape any header names that start with the prefix we use for our own pseudo-headers, * prefixing them with an escape sequence we can use to recover them later. * * @param httpResponse HTTP response object to escape headers in - * @see #unescapeHeaders(HttpResponse) for the corresponding un-escaper. */ - private static void escapeHeaders(final HttpResponse httpResponse) { - final Header[] headers = httpResponse.getHeaders(); - for (final Header header : headers) { - if (header.getName().startsWith(SC_CACHE_ENTRY_PREFIX)) { - httpResponse.removeHeader(header); - httpResponse.addHeader(SC_CACHE_ENTRY_PRESERVE_PREFIX + header.getName(), header.getValue()); + private void escapeHeaders(final HttpResponse httpResponse) { + final HeaderGroup headerGroup = new HeaderGroup(); + for (final Iterator

it = httpResponse.headerIterator(); it.hasNext(); ) { + final Header header = it.next(); + if (header != null && header.getName().startsWith(SC_CACHE_ENTRY_PREFIX)) { + headerGroup.addHeader(new BasicHeader(SC_CACHE_ENTRY_PRESERVE_PREFIX + header.getName(), header.getValue())); + } else if (header != null) { + headerGroup.addHeader(header); } } + httpResponse.setHeaders(headerGroup.getHeaders()); } - /** - * Modify the given response to remove escaping from any header names we escaped before saving. - * - * @param httpResponse HTTP response object to un-escape headers in - * @see #unescapeHeaders(HttpResponse) for the corresponding escaper - */ - private void unescapeHeaders(final HttpResponse httpResponse) { - final Header[] headers = httpResponse.getHeaders(); - for (final Header header : headers) { - if (header.getName().startsWith(SC_CACHE_ENTRY_PRESERVE_PREFIX)) { - httpResponse.removeHeader(header); - httpResponse.addHeader(header.getName().substring(SC_CACHE_ENTRY_PRESERVE_PREFIX.length()), header.getValue()); - } - } - } /** * Modify the given response to add our own cache metadata as pseudo-headers. @@ -254,125 +361,51 @@ public class HttpByteArrayCacheEntrySerializer implements HttpCacheEntrySerializ * @param httpResponse HTTP response object to add pseudo-headers to */ private void addMetadataPseudoHeaders(final HttpResponse httpResponse, final HttpCacheStorageEntry httpCacheEntry) { - httpResponse.addHeader(SC_HEADER_NAME_STORAGE_KEY, httpCacheEntry.getKey()); - httpResponse.addHeader(SC_HEADER_NAME_RESPONSE_DATE, Long.toString(httpCacheEntry.getContent().getResponseInstant().toEpochMilli())); - httpResponse.addHeader(SC_HEADER_NAME_REQUEST_DATE, Long.toString(httpCacheEntry.getContent().getRequestInstant().toEpochMilli())); + final HeaderGroup headerGroup = new HeaderGroup(); + headerGroup.setHeaders(httpResponse.getHeaders()); + headerGroup.addHeader(new BasicHeader(SC_HEADER_NAME_STORAGE_KEY, httpCacheEntry.getKey())); + headerGroup.addHeader(new BasicHeader(SC_HEADER_NAME_RESPONSE_DATE, Long.toString(httpCacheEntry.getContent().getResponseInstant().toEpochMilli()))); + headerGroup.addHeader(new BasicHeader(SC_HEADER_NAME_REQUEST_DATE, Long.toString(httpCacheEntry.getContent().getRequestInstant().toEpochMilli()))); // Encode these so map entries are stored in a pair of headers, one for key and one for value. // Header keys look like: {Accept-Encoding=gzip} // And header values like: {Accept-Encoding=gzip}https://example.com:1234/foo for (final Map.Entry entry : httpCacheEntry.getContent().getVariantMap().entrySet()) { // Headers are ordered - httpResponse.addHeader(SC_HEADER_NAME_VARIANT_MAP_KEY, entry.getKey()); - httpResponse.addHeader(SC_HEADER_NAME_VARIANT_MAP_VALUE, entry.getValue()); + headerGroup.addHeader(new BasicHeader(SC_HEADER_NAME_VARIANT_MAP_KEY, entry.getKey())); + headerGroup.addHeader(new BasicHeader(SC_HEADER_NAME_VARIANT_MAP_VALUE, entry.getValue())); } + + httpResponse.setHeaders(headerGroup.getHeaders()); } - /** - * Get the string value for a single metadata pseudo-header, and remove it from the response object. - * - * @param response Response object to get and remove the pseudo-header from - * @param name Name of metadata pseudo-header - * @return Value for metadata pseudo-header - * @throws ResourceIOException if the given pseudo-header is not found - */ - private static String getCachePseudoHeaderAndRemove(final HttpResponse response, final String name) throws ResourceIOException { - final String headerValue = getOptionalCachePseudoHeaderAndRemove(response, name); - if (headerValue == null) { - throw new ResourceIOException("Expected cache header '" + name + "' not found"); - } - return headerValue; - } /** - * Get the string value for a single metadata pseudo-header if it exists, and remove it from the response object. + * Parses the date value for a single metadata pseudo-header based on the given header name, and returns it as an Instant. * - * @param response Response object to get and remove the pseudo-header from - * @param name Name of metadata pseudo-header - * @return Value for metadata pseudo-header, or null if it does not exist - */ - private static String getOptionalCachePseudoHeaderAndRemove(final HttpResponse response, final String name) { - final Header header = response.getFirstHeader(name); - if (header == null) { - return null; - } - response.removeHeader(header); - return header.getValue(); - } - - /** - * Get the date value for a single metadata pseudo-header, and remove it from the response object. - * - * @param response Response object to get and remove the pseudo-header from - * @param name Name of metadata pseudo-header - * @return Value for metadata pseudo-header + * @param name Name of the metadata pseudo-header to parse + * @return Instant value for the metadata pseudo-header * @throws ResourceIOException if the given pseudo-header is not found, or contains invalid data */ - private static Instant getCachePseudoHeaderDateAndRemove(final HttpResponse response, final String name) throws ResourceIOException{ - final String value = getCachePseudoHeaderAndRemove(response, name); - response.removeHeaders(name); + private Instant parseCachePseudoHeaderDate(final String name) throws ResourceIOException { try { - final long timestamp = Long.parseLong(value); - return Instant.ofEpochMilli(timestamp); + return Instant.ofEpochMilli(Long.parseLong(name)); } catch (final NumberFormatException e) { throw new ResourceIOException("Invalid value for header '" + name + "'", e); } } - /** - * Get the boolean value for a single metadata pseudo-header, and remove it from the response object. - * - * @param response Response object to get and remove the pseudo-header from - * @param name Name of metadata pseudo-header - * @return Value for metadata pseudo-header - */ - private static boolean getCachePseudoHeaderBooleanAndRemove(final ClassicHttpResponse response, final String name) { - // parseBoolean does not throw any exceptions, so no try/catch required. - return Boolean.parseBoolean(getOptionalCachePseudoHeaderAndRemove(response, name)); - } - - /** - * Get the variant map metadata pseudo-header, and remove it from the response object. - * - * @param response Response object to get and remove the pseudo-header from - * @return Extracted variant map - * @throws ResourceIOException if the given pseudo-header is not found, or contains invalid data - */ - private static Map getVariantMapPseudoHeadersAndRemove(final HttpResponse response) throws ResourceIOException { - final Header[] headers = response.getHeaders(); - final Map variantMap = new HashMap<>(0); - String lastKey = null; - for (final Header header : headers) { - if (header.getName().equals(SC_HEADER_NAME_VARIANT_MAP_KEY)) { - lastKey = header.getValue(); - response.removeHeader(header); - } else if (header.getName().equals(SC_HEADER_NAME_VARIANT_MAP_VALUE)) { - if (lastKey == null) { - throw new ResourceIOException("Found mismatched variant map key/value headers"); - } - variantMap.put(lastKey, header.getValue()); - lastKey = null; - response.removeHeader(header); - } - } - - if (lastKey != null) { - throw new ResourceIOException("Found mismatched variant map key/value headers"); - } - - return variantMap; - } /** * Copy bytes from the given source buffer and input stream to the given output stream until end-of-file is reached. * * @param srcBuf Buffered input source - * @param src Unbuffered input source - * @param dest Output destination + * @param src Unbuffered input source + * @param dest Output destination * @throws IOException if an I/O error occurs */ - private static void copyBytes(final SessionInputBuffer srcBuf, final InputStream src, final OutputStream dest) throws IOException { - final byte[] buf = new byte[BUFFER_SIZE]; + private void copyBytes(final SessionInputBuffer srcBuf, final InputStream src, final OutputStream dest) throws IOException { + final byte[] buf = new byte[bufferSize]; int lastBytesRead; while ((lastBytesRead = srcBuf.read(buf, src)) != -1) { dest.write(buf, 0, lastBytesRead); @@ -380,17 +413,38 @@ public class HttpByteArrayCacheEntrySerializer implements HttpCacheEntrySerializ } /** - * Writer for SimpleHttpResponse. + * Validates that a given cache header is not null, throwing a {@link ResourceIOException} if it is. * - * Copied from DefaultHttpResponseWriter, but wrapping a SimpleHttpResponse instead of a ClassicHttpResponse + * @param obj the cache header object to validate + * @param headerName the name of the cache header being validated, used in the exception message if it is null + * @return the validated cache header object + * @throws ResourceIOException if the cache header object is null + */ + private T headerNotNull(final T obj, final String headerName) throws ResourceIOException { + if (obj == null) { + throw new ResourceIOException("Expected cache header '" + headerName + "' not found"); + } + return obj; + } + + /** + * This class extends AbstractMessageWriter and provides the ability to write a SimpleHttpResponse message. */ - // Seems like the DefaultHttpResponseWriter should be able to do this, but it doesn't seem to be able to private static class SimpleHttpResponseWriter extends AbstractMessageWriter { + /** + * Constructs a SimpleHttpResponseWriter object with the BasicLineFormatter instance. + */ public SimpleHttpResponseWriter() { super(BasicLineFormatter.INSTANCE); } + /** + * Writes the head line of the given SimpleHttpResponse message to the given CharArrayBuffer. + * + * @param message the SimpleHttpResponse message to write + * @param lineBuf the CharArrayBuffer to write the head line to + */ @Override protected void writeHeadLine( final SimpleHttpResponse message, final CharArrayBuffer lineBuf) { @@ -402,16 +456,90 @@ public class HttpByteArrayCacheEntrySerializer implements HttpCacheEntrySerializ } } + /** + * This class extends AbstractMessageParser and provides the ability to parse a SimpleHttpResponse message. + */ + private static class SimpleHttpResponseParser extends AbstractMessageParser { + + /** + * Constructs a SimpleHttpResponseParser object with the LazyLineParser instance and Http1Config.DEFAULT. + */ + public SimpleHttpResponseParser() { + super(LazyLineParser.INSTANCE, Http1Config.DEFAULT); + } + + /** + * Creates a SimpleHttpResponse object from the given CharArrayBuffer. + * + * @param buffer the CharArrayBuffer to parse the SimpleHttpResponse from + * @return the SimpleHttpResponse object created from the buffer + * @throws HttpException if the buffer cannot be parsed + */ + @Override + protected SimpleHttpResponse createMessage(final CharArrayBuffer buffer) throws HttpException { + final StatusLine statusline = LazyLineParser.INSTANCE.parseStatusLine(buffer); + final SimpleHttpResponse response = new SimpleHttpResponse(statusline.getStatusCode(), statusline.getReasonPhrase()); + response.setVersion(statusline.getProtocolVersion()); + return response; + } + } + /** * Cache validity policy that always returns an age of {@link TimeValue#ZERO_MILLISECONDS}. - * + *

* This prevents the Age header from being written to the cache (it does not make sense to cache it), * and is the only thing the policy is used for in this case. */ private static class NoAgeCacheValidityPolicy extends CacheValidityPolicy { + + public static final NoAgeCacheValidityPolicy INSTANCE = new NoAgeCacheValidityPolicy(); + @Override public TimeValue getCurrentAge(final HttpCacheEntry entry, final Instant now) { return TimeValue.ZERO_MILLISECONDS; } } + + + /** + * Helper method to make a new ByteArrayInputStream. + *

+ * Useful to override for testing. + * + * @param bytes Bytes to read from the stream + * @return Stream to read the bytes from + * @deprecated not need it anymore. + */ + @Deprecated + protected InputStream makeByteArrayInputStream(final byte[] bytes) { + return new ByteArrayInputStream(bytes); + } + + /** + * Helper method to make a new HTTP Response parser. + *

+ * Useful to override for testing. + * + * @return HTTP response parser + * @deprecated not need it anymore. + */ + @Deprecated + protected AbstractMessageParser makeHttpResponseParser() { + return new DefaultHttpResponseParser(); + } + + /** + * Helper method to make a new HTTP response writer. + *

+ * Useful to override for testing. + * + * @param outputBuffer Output buffer to write to + * @return HTTP response writer to write to + * @deprecated not need it anymore. + */ + @Deprecated + protected AbstractMessageWriter makeHttpResponseWriter(final SessionOutputBuffer outputBuffer) { + return new SimpleHttpResponseWriter(); + } + } diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ehcache/EhcacheHttpCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ehcache/EhcacheHttpCacheStorage.java index 1053bdba2..0481a6892 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ehcache/EhcacheHttpCacheStorage.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ehcache/EhcacheHttpCacheStorage.java @@ -34,8 +34,8 @@ import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer; import org.apache.hc.client5.http.cache.HttpCacheStorageEntry; import org.apache.hc.client5.http.cache.ResourceIOException; import org.apache.hc.client5.http.impl.cache.AbstractSerializingCacheStorage; -import org.apache.hc.client5.http.impl.cache.ByteArrayCacheEntrySerializer; import org.apache.hc.client5.http.impl.cache.CacheConfig; +import org.apache.hc.client5.http.impl.cache.HttpByteArrayCacheEntrySerializer; import org.apache.hc.client5.http.impl.cache.NoopCacheEntrySerializer; import org.apache.hc.core5.util.Args; import org.ehcache.Cache; @@ -76,7 +76,7 @@ public class EhcacheHttpCacheStorage extends AbstractSerializingCacheStorage< */ public static EhcacheHttpCacheStorage createSerializedCache( final Cache cache, final CacheConfig config) { - return new EhcacheHttpCacheStorage<>(cache, config, ByteArrayCacheEntrySerializer.INSTANCE); + return new EhcacheHttpCacheStorage<>(cache, config, HttpByteArrayCacheEntrySerializer.INSTANCE); } private final Cache cache; diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpAsyncCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpAsyncCacheStorage.java index b49d63fcf..16dfe9d24 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpAsyncCacheStorage.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpAsyncCacheStorage.java @@ -37,8 +37,8 @@ import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer; import org.apache.hc.client5.http.cache.ResourceIOException; import org.apache.hc.client5.http.impl.Operations; import org.apache.hc.client5.http.impl.cache.AbstractBinaryAsyncCacheStorage; -import org.apache.hc.client5.http.impl.cache.ByteArrayCacheEntrySerializer; import org.apache.hc.client5.http.impl.cache.CacheConfig; +import org.apache.hc.client5.http.impl.cache.HttpByteArrayCacheEntrySerializer; import org.apache.hc.core5.concurrent.Cancellable; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.util.Args; @@ -111,7 +111,7 @@ public class MemcachedHttpAsyncCacheStorage extends AbstractBinaryAsyncCacheStor * @param cache client to use for communicating with memcached */ public MemcachedHttpAsyncCacheStorage(final MemcachedClient cache) { - this(cache, CacheConfig.DEFAULT, ByteArrayCacheEntrySerializer.INSTANCE, SHA256KeyHashingScheme.INSTANCE); + this(cache, CacheConfig.DEFAULT, HttpByteArrayCacheEntrySerializer.INSTANCE, SHA256KeyHashingScheme.INSTANCE); } /** @@ -130,7 +130,7 @@ public class MemcachedHttpAsyncCacheStorage extends AbstractBinaryAsyncCacheStor final HttpCacheEntrySerializer serializer, final KeyHashingScheme keyHashingScheme) { super((config != null ? config : CacheConfig.DEFAULT).getMaxUpdateRetries(), - serializer != null ? serializer : ByteArrayCacheEntrySerializer.INSTANCE); + serializer != null ? serializer : HttpByteArrayCacheEntrySerializer.INSTANCE); this.client = Args.notNull(client, "Memcached client"); this.keyHashingScheme = keyHashingScheme; } diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpCacheStorage.java index fe66d2cc0..952ed4a9d 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpCacheStorage.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpCacheStorage.java @@ -35,8 +35,8 @@ import java.util.Map; import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer; import org.apache.hc.client5.http.cache.ResourceIOException; import org.apache.hc.client5.http.impl.cache.AbstractBinaryCacheStorage; -import org.apache.hc.client5.http.impl.cache.ByteArrayCacheEntrySerializer; import org.apache.hc.client5.http.impl.cache.CacheConfig; +import org.apache.hc.client5.http.impl.cache.HttpByteArrayCacheEntrySerializer; import org.apache.hc.core5.util.Args; import net.spy.memcached.CASResponse; @@ -106,7 +106,7 @@ public class MemcachedHttpCacheStorage extends AbstractBinaryCacheStoragememcached */ public MemcachedHttpCacheStorage(final MemcachedClient cache) { - this(cache, CacheConfig.DEFAULT, ByteArrayCacheEntrySerializer.INSTANCE, SHA256KeyHashingScheme.INSTANCE); + this(cache, CacheConfig.DEFAULT, HttpByteArrayCacheEntrySerializer.INSTANCE, SHA256KeyHashingScheme.INSTANCE); } /** @@ -118,7 +118,7 @@ public class MemcachedHttpCacheStorage extends AbstractBinaryCacheStorage serializer, final KeyHashingScheme keyHashingScheme) { super((config != null ? config : CacheConfig.DEFAULT).getMaxUpdateRetries(), - serializer != null ? serializer : ByteArrayCacheEntrySerializer.INSTANCE); + serializer != null ? serializer : HttpByteArrayCacheEntrySerializer.INSTANCE); this.client = Args.notNull(client, "Memcached client"); this.keyHashingScheme = keyHashingScheme; } diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpTestUtils.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpTestUtils.java index 4bbcc04f7..b63d8e050 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpTestUtils.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpTestUtils.java @@ -247,7 +247,8 @@ public class HttpTestUtils { public static Header[] getStockHeaders(final Instant when) { return new Header[] { new BasicHeader("Date", DateUtils.formatStandardDate(when)), - new BasicHeader("Server", "MockServer/1.0") + new BasicHeader("Server", "MockServer/1.0"), + new BasicHeader("Content-Length", "128") }; } diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingAsyncCacheStorage.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingAsyncCacheStorage.java index 1cc4da2b5..aefbe66ea 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingAsyncCacheStorage.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingAsyncCacheStorage.java @@ -68,7 +68,7 @@ public class TestAbstractSerializingAsyncCacheStorage { private AbstractBinaryAsyncCacheStorage impl; public static byte[] serialize(final String key, final HttpCacheEntry value) throws ResourceIOException { - return ByteArrayCacheEntrySerializer.INSTANCE.serialize(new HttpCacheStorageEntry(key, value)); + return HttpByteArrayCacheEntrySerializer.INSTANCE.serialize(new HttpCacheStorageEntry(key, value)); } @BeforeEach diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingCacheStorage.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingCacheStorage.java index d0a9e0347..618c7f183 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingCacheStorage.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAbstractSerializingCacheStorage.java @@ -54,7 +54,7 @@ import org.mockito.stubbing.Answer; public class TestAbstractSerializingCacheStorage { public static byte[] serialize(final String key, final HttpCacheEntry value) throws ResourceIOException { - return ByteArrayCacheEntrySerializer.INSTANCE.serialize(new HttpCacheStorageEntry(key, value)); + return HttpByteArrayCacheEntrySerializer.INSTANCE.serialize(new HttpCacheStorageEntry(key, value)); } private AbstractBinaryCacheStorage impl; diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestHttpByteArrayCacheEntrySerializer.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestHttpByteArrayCacheEntrySerializer.java index 19f3cb508..341e75297 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestHttpByteArrayCacheEntrySerializer.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestHttpByteArrayCacheEntrySerializer.java @@ -34,9 +34,10 @@ import static org.apache.hc.client5.http.impl.cache.HttpByteArrayCacheEntrySeria import static org.apache.hc.client5.http.impl.cache.HttpByteArrayCacheEntrySerializerTestUtils.testWithCache; import static org.apache.hc.client5.http.impl.cache.HttpByteArrayCacheEntrySerializerTestUtils.verifyHttpCacheEntryFromTestFile; -import java.io.IOException; -import java.io.InputStream; + + import java.io.OutputStream; +import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; @@ -44,12 +45,9 @@ import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer; import org.apache.hc.client5.http.cache.HttpCacheStorageEntry; import org.apache.hc.client5.http.cache.ResourceIOException; -import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; -import org.apache.hc.core5.http.impl.io.AbstractMessageParser; import org.apache.hc.core5.http.impl.io.AbstractMessageWriter; -import org.apache.hc.core5.http.io.SessionInputBuffer; import org.apache.hc.core5.http.io.SessionOutputBuffer; import org.apache.hc.core5.http.message.BasicHeader; import org.junit.jupiter.api.Assertions; @@ -72,14 +70,14 @@ public class TestHttpByteArrayCacheEntrySerializer { private static final String TEST_CONTENT_FILE_NAME = "ApacheLogo.png"; - private HttpCacheEntrySerializer serializer; + private HttpCacheEntrySerializer httpCacheEntrySerializer; // Manually set this to true to re-generate all of the serialized files private final boolean reserializeFiles = false; @BeforeEach public void before() { - serializer = HttpByteArrayCacheEntrySerializer.INSTANCE; + httpCacheEntrySerializer = HttpByteArrayCacheEntrySerializer.INSTANCE; } /** @@ -92,7 +90,7 @@ public class TestHttpByteArrayCacheEntrySerializer { // This file is a JPEG not a cache entry, so should fail to deserialize final byte[] bytes = readTestFileBytes(TEST_CONTENT_FILE_NAME); Assertions.assertThrows(ResourceIOException.class, () -> - httpCacheStorageEntryFromBytes(serializer, bytes)); + httpCacheStorageEntryFromBytes(httpCacheEntrySerializer, bytes)); } /** @@ -105,7 +103,7 @@ public class TestHttpByteArrayCacheEntrySerializer { // This file hand-edited to be missing a necessary header final byte[] bytes = readTestFileBytes(MISSING_HEADER_TEST_SERIALIZED_NAME); Assertions.assertThrows(ResourceIOException.class, () -> - httpCacheStorageEntryFromBytes(serializer, bytes)); + httpCacheStorageEntryFromBytes(httpCacheEntrySerializer, bytes)); } /** @@ -118,7 +116,7 @@ public class TestHttpByteArrayCacheEntrySerializer { // This file hand-edited to have an invalid header final byte[] bytes = readTestFileBytes(INVALID_HEADER_TEST_SERIALIZED_NAME); Assertions.assertThrows(ResourceIOException.class, () -> - httpCacheStorageEntryFromBytes(serializer, bytes)); + httpCacheStorageEntryFromBytes(httpCacheEntrySerializer, bytes)); } /** @@ -131,7 +129,7 @@ public class TestHttpByteArrayCacheEntrySerializer { // This file hand-edited to be missing a VariantCache key final byte[] bytes = readTestFileBytes(VARIANTMAP_MISSING_KEY_TEST_SERIALIZED_NAME); Assertions.assertThrows(ResourceIOException.class, () -> - httpCacheStorageEntryFromBytes(serializer, bytes)); + httpCacheStorageEntryFromBytes(httpCacheEntrySerializer, bytes)); } /** @@ -144,36 +142,21 @@ public class TestHttpByteArrayCacheEntrySerializer { // This file hand-edited to be missing a VariantCache value final byte[] bytes = readTestFileBytes(VARIANTMAP_MISSING_VALUE_TEST_SERIALIZED_NAME); Assertions.assertThrows(ResourceIOException.class, () -> - httpCacheStorageEntryFromBytes(serializer, bytes)); + httpCacheStorageEntryFromBytes(httpCacheEntrySerializer, bytes)); } /** - * Test an IOException being thrown while deserializing. + * Test an ResourceIOException being thrown while deserializing. * - * @throws Exception is expected */ @Test - public void testDeserializeWithIOException() throws Exception { - final AbstractMessageParser throwyParser = Mockito.mock(AbstractMessageParser.class); - Mockito. - doThrow(new IOException("Test Exception")). - when(throwyParser). - parse(Mockito.any(SessionInputBuffer.class), Mockito.any(InputStream.class)); - - final HttpByteArrayCacheEntrySerializer testSerializer = new HttpByteArrayCacheEntrySerializer() { - @Override - protected AbstractMessageParser makeHttpResponseParser() { - return throwyParser; - } - }; - Assertions.assertThrows(ResourceIOException.class, () -> - testSerializer.deserialize(new byte[0])); + public void testDeserializeWithResourceIOException() { + Assertions.assertThrows(ResourceIOException.class, () -> + httpCacheEntrySerializer.deserialize(new byte[0])); } - ////////////// Using new Constructor with Instant ////////////// - /** * Serialize and deserialize a simple object with a tiny body. * @@ -184,7 +167,7 @@ public class TestHttpByteArrayCacheEntrySerializer { final HttpCacheStorageEntryTestTemplate cacheObjectValues = HttpCacheStorageEntryTestTemplate.makeDefault(); final HttpCacheStorageEntry testEntry = cacheObjectValues.toEntry(); - testWithCache(serializer, testEntry); + testWithCache(httpCacheEntrySerializer, testEntry); } /** @@ -198,7 +181,7 @@ public class TestHttpByteArrayCacheEntrySerializer { cacheObjectValues.resource = new FileResource(makeTestFileObject(TEST_CONTENT_FILE_NAME)); final HttpCacheStorageEntry testEntry = cacheObjectValues.toEntry(); - testWithCache(serializer, testEntry); + testWithCache(httpCacheEntrySerializer, testEntry); } /** @@ -212,7 +195,7 @@ public class TestHttpByteArrayCacheEntrySerializer { cacheObjectValues.responseHeaders = new Header[0]; final HttpCacheStorageEntry testEntry = cacheObjectValues.toEntry(); - testWithCache(serializer, testEntry); + testWithCache(httpCacheEntrySerializer, testEntry); } /** @@ -226,7 +209,7 @@ public class TestHttpByteArrayCacheEntrySerializer { cacheObjectValues.resource = new HeapResource(new byte[0]); final HttpCacheStorageEntry testEntry = cacheObjectValues.toEntry(); - testWithCache(serializer, testEntry); + testWithCache(httpCacheEntrySerializer, testEntry); } /** @@ -242,7 +225,7 @@ public class TestHttpByteArrayCacheEntrySerializer { final HttpCacheStorageEntry testEntry = cacheObjectValues.toEntry(); - testWithCache(serializer, testEntry); + testWithCache(httpCacheEntrySerializer, testEntry); } /** @@ -259,7 +242,7 @@ public class TestHttpByteArrayCacheEntrySerializer { cacheObjectValues.variantMap = variantMap; final HttpCacheStorageEntry testEntry = cacheObjectValues.toEntry(); - testWithCache(serializer, testEntry); + testWithCache(httpCacheEntrySerializer, testEntry); } /** @@ -280,7 +263,7 @@ public class TestHttpByteArrayCacheEntrySerializer { }; final HttpCacheStorageEntry testEntry = cacheObjectValues.toEntry(); - testWithCache(serializer, testEntry); + testWithCache(httpCacheEntrySerializer, testEntry); } /** @@ -294,7 +277,7 @@ public class TestHttpByteArrayCacheEntrySerializer { final HttpCacheStorageEntry testEntry = cacheObjectValues.toEntry(); Assertions.assertThrows(IllegalStateException.class, () -> - serializer.serialize(testEntry)); + httpCacheEntrySerializer.serialize(testEntry)); } /** @@ -309,7 +292,7 @@ public class TestHttpByteArrayCacheEntrySerializer { final HttpCacheStorageEntryTestTemplate cacheObjectValues = HttpCacheStorageEntryTestTemplate.makeDefault(); final HttpCacheStorageEntry testEntry = cacheObjectValues.toEntry(); - verifyHttpCacheEntryFromTestFile(serializer, testEntry, SIMPLE_OBJECT_SERIALIZED_NAME, reserializeFiles); + verifyHttpCacheEntryFromTestFile(httpCacheEntrySerializer, testEntry, SIMPLE_OBJECT_SERIALIZED_NAME, reserializeFiles); } /** @@ -325,7 +308,7 @@ public class TestHttpByteArrayCacheEntrySerializer { cacheObjectValues.resource = new FileResource(makeTestFileObject(TEST_CONTENT_FILE_NAME)); final HttpCacheStorageEntry testEntry = cacheObjectValues.toEntry(); - verifyHttpCacheEntryFromTestFile(serializer, testEntry, FILE_TEST_SERIALIZED_NAME, reserializeFiles); + verifyHttpCacheEntryFromTestFile(httpCacheEntrySerializer, testEntry, FILE_TEST_SERIALIZED_NAME, reserializeFiles); } /** @@ -342,7 +325,7 @@ public class TestHttpByteArrayCacheEntrySerializer { cacheObjectValues.variantMap = variantMap; final HttpCacheStorageEntry testEntry = cacheObjectValues.toEntry(); - verifyHttpCacheEntryFromTestFile(serializer, testEntry, VARIANTMAP_TEST_SERIALIZED_NAME, reserializeFiles); + verifyHttpCacheEntryFromTestFile(httpCacheEntrySerializer, testEntry, VARIANTMAP_TEST_SERIALIZED_NAME, reserializeFiles); } /** @@ -363,7 +346,7 @@ public class TestHttpByteArrayCacheEntrySerializer { }; final HttpCacheStorageEntry testEntry = cacheObjectValues.toEntry(); - verifyHttpCacheEntryFromTestFile(serializer, testEntry, ESCAPED_HEADER_TEST_SERIALIZED_NAME, reserializeFiles); + verifyHttpCacheEntryFromTestFile(httpCacheEntrySerializer, testEntry, ESCAPED_HEADER_TEST_SERIALIZED_NAME, reserializeFiles); } /** @@ -379,7 +362,7 @@ public class TestHttpByteArrayCacheEntrySerializer { final HttpCacheStorageEntry testEntry = cacheObjectValues.toEntry(); - verifyHttpCacheEntryFromTestFile(serializer, testEntry, NO_BODY_TEST_SERIALIZED_NAME, reserializeFiles); + verifyHttpCacheEntryFromTestFile(httpCacheEntrySerializer, testEntry, NO_BODY_TEST_SERIALIZED_NAME, reserializeFiles); } /** @@ -390,23 +373,22 @@ public class TestHttpByteArrayCacheEntrySerializer { @Test public void testSerializeWithHTTPException() throws Exception { final AbstractMessageWriter throwyHttpWriter = Mockito.mock(AbstractMessageWriter.class); - Mockito. - doThrow(new HttpException("Test Exception")). - when(throwyHttpWriter). - write(Mockito.any(SimpleHttpResponse.class), Mockito.any(SessionOutputBuffer.class), Mockito.any(OutputStream.class)); + Mockito.doThrow(new HttpException("Test Exception")) + .when(throwyHttpWriter) + .write(Mockito.any(SimpleHttpResponse.class), Mockito.any(SessionOutputBuffer.class), Mockito.any(OutputStream.class)); final HttpCacheStorageEntryTestTemplate cacheObjectValues = HttpCacheStorageEntryTestTemplate.makeDefault(); final HttpCacheStorageEntry testEntry = cacheObjectValues.toEntry(); - final HttpByteArrayCacheEntrySerializer testSerializer = new HttpByteArrayCacheEntrySerializer() { - @Override - protected AbstractMessageWriter makeHttpResponseWriter(final SessionOutputBuffer outputBuffer) { - return throwyHttpWriter; - } - }; - Assertions.assertThrows(ResourceIOException.class, () -> - testSerializer.serialize(testEntry)); + final Field field = httpCacheEntrySerializer.getClass().getDeclaredField("responseWriter"); + field.setAccessible(true); + final AbstractMessageWriter originalWriter = (AbstractMessageWriter) field.get(httpCacheEntrySerializer); + try { + field.set(httpCacheEntrySerializer, throwyHttpWriter); + Assertions.assertThrows(ResourceIOException.class, () -> httpCacheEntrySerializer.serialize(testEntry)); + } finally { + field.set(httpCacheEntrySerializer, originalWriter); + } } - }