From 38dfaba3b86e5e7d0c933754014921a5e3c50708 Mon Sep 17 00:00:00 2001 From: Zengyi Wang Date: Mon, 15 Jul 2024 21:42:17 +0800 Subject: [PATCH 1/5] update cache SPI and resource serialization --- .../vertx/httpproxy/cache/CacheOptions.java | 4 +- .../io/vertx/httpproxy/impl/CacheImpl.java | 35 ++- .../vertx/httpproxy/impl/CachingFilter.java | 108 ++++---- .../io/vertx/httpproxy/impl/Resource.java | 51 ---- .../io/vertx/httpproxy/impl/ReverseProxy.java | 2 +- .../io/vertx/httpproxy/spi/cache/Cache.java | 32 ++- .../vertx/httpproxy/spi/cache/Resource.java | 253 ++++++++++++++++++ .../tests/parsing/ResourceParseTest.java | 92 +++++++ 8 files changed, 463 insertions(+), 114 deletions(-) delete mode 100644 src/main/java/io/vertx/httpproxy/impl/Resource.java create mode 100644 src/main/java/io/vertx/httpproxy/spi/cache/Resource.java create mode 100644 src/test/java/io/vertx/tests/parsing/ResourceParseTest.java diff --git a/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java b/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java index 4a5e9a7..7ee7898 100644 --- a/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java +++ b/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java @@ -45,8 +45,8 @@ public CacheOptions setMaxSize(int maxSize) { return this; } - public Cache newCache() { - return new CacheImpl<>(this); + public Cache newCache() { + return new CacheImpl(this); } @Override diff --git a/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java b/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java index 4614707..2fce671 100644 --- a/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java +++ b/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java @@ -1,23 +1,52 @@ package io.vertx.httpproxy.impl; +import io.vertx.core.Future; import io.vertx.httpproxy.cache.CacheOptions; import io.vertx.httpproxy.spi.cache.Cache; +import io.vertx.httpproxy.spi.cache.Resource; +import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.Map; /** * Simplistic implementation. */ -public class CacheImpl extends LinkedHashMap implements Cache { +public class CacheImpl implements Cache { private final int maxSize; + private final Map data; + private final LinkedList records; public CacheImpl(CacheOptions options) { this.maxSize = options.getMaxSize(); + this.data = new HashMap<>(); + this.records = new LinkedList<>(); } - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > maxSize; + + @Override + public Future put(String key, Resource value) { + while (records.size() >= maxSize) { + String toRemove = records.removeLast(); + data.remove(toRemove); + } + + data.put(key, value); + records.addFirst(key); + return Future.succeededFuture(); + } + + @Override + public Future get(String key) { + return Future.succeededFuture(data.get(key)); + } + + @Override + public Future remove(String key) { + records.remove(key); + data.remove(key); + return Future.succeededFuture(); } } diff --git a/src/main/java/io/vertx/httpproxy/impl/CachingFilter.java b/src/main/java/io/vertx/httpproxy/impl/CachingFilter.java index 5d1ed52..f5bb07d 100644 --- a/src/main/java/io/vertx/httpproxy/impl/CachingFilter.java +++ b/src/main/java/io/vertx/httpproxy/impl/CachingFilter.java @@ -10,31 +10,23 @@ import io.vertx.httpproxy.ProxyRequest; import io.vertx.httpproxy.ProxyResponse; import io.vertx.httpproxy.spi.cache.Cache; +import io.vertx.httpproxy.spi.cache.Resource; import java.time.Instant; import java.util.function.BiFunction; +import java.util.function.Predicate; class CachingFilter implements ProxyInterceptor { - private static final BiFunction CACHE_GET_AND_VALIDATE = (key, resource) -> { - long now = System.currentTimeMillis(); - long val = resource.timestamp + resource.maxAge; - return val < now ? null : resource; - }; + private final Cache cache; - private final Cache cache; - - public CachingFilter(Cache cache) { + public CachingFilter(Cache cache) { this.cache = cache; } @Override public Future handleProxyRequest(ProxyContext context) { - Future future = tryHandleProxyRequestFromCache(context); - if (future != null) { - return future; - } - return context.sendRequest(); + return tryHandleProxyRequestFromCache(context); } @Override @@ -66,23 +58,24 @@ private Future sendAndTryCacheProxyResponse(ProxyContext context) { System.currentTimeMillis(), response.maxAge()); Body body = response.getBody(); - response.setBody(Body.body(new BufferingReadStream(body.stream(), res.content), body.length())); + response.setBody(Body.body(new BufferingReadStream(body.stream(), res.getContent()), body.length())); Future fut = context.sendResponse(); fut.onSuccess(v -> { cache.put(absoluteUri, res); }); return fut; + } else if (request.getMethod() != HttpMethod.HEAD) { + return context.sendResponse(); } else { - if (request.getMethod() == HttpMethod.HEAD) { - Resource resource = cache.get(request.absoluteURI()); + return cache.get(request.absoluteURI()).compose(resource -> { if (resource != null) { if (!revalidateResource(response, resource)) { // Invalidate cache cache.remove(request.absoluteURI()); } } - } - return context.sendResponse(); + return context.sendResponse(); + }); } } else { return context.sendResponse(); @@ -90,8 +83,8 @@ private Future sendAndTryCacheProxyResponse(ProxyContext context) { } private static boolean revalidateResource(ProxyResponse response, Resource resource) { - if (resource.etag != null && response.etag() != null) { - return resource.etag.equals(response.etag()); + if (resource.getEtag() != null && response.etag() != null) { + return resource.getEtag().equals(response.etag()); } return true; } @@ -102,49 +95,54 @@ private Future tryHandleProxyRequestFromCache(ProxyContext contex HttpServerRequest response = proxyRequest.proxiedRequest(); - Resource resource; HttpMethod method = response.method(); - if (method == HttpMethod.GET || method == HttpMethod.HEAD) { - String cacheKey = proxyRequest.absoluteURI(); - resource = cache.computeIfPresent(cacheKey, CACHE_GET_AND_VALIDATE); + if (method != HttpMethod.GET && method != HttpMethod.HEAD) { + return context.sendRequest(); + } + + String cacheKey = proxyRequest.absoluteURI(); + return cache.get(cacheKey).compose(resource -> { if (resource == null) { - return null; + return context.sendRequest(); + } + + long now = System.currentTimeMillis(); + long val = resource.getTimestamp() + resource.getMaxAge(); + if (val < now) { + return cache.remove(cacheKey).compose(v -> context.sendRequest()); } - } else { - return null; - } - String cacheControlHeader = response.getHeader(HttpHeaders.CACHE_CONTROL); - if (cacheControlHeader != null) { - CacheControl cacheControl = new CacheControl().parse(cacheControlHeader); - if (cacheControl.maxAge() >= 0) { - long now = System.currentTimeMillis(); - long currentAge = now - resource.timestamp; - if (currentAge > cacheControl.maxAge() * 1000) { - String etag = resource.headers.get(HttpHeaders.ETAG); - if (etag != null) { - proxyRequest.headers().set(HttpHeaders.IF_NONE_MATCH, resource.etag); - context.set("cached_resource", resource); - return context.sendRequest(); - } else { - return null; + String cacheControlHeader = response.getHeader(HttpHeaders.CACHE_CONTROL); + if (cacheControlHeader != null) { + CacheControl cacheControl = new CacheControl().parse(cacheControlHeader); + if (cacheControl.maxAge() >= 0) { + long currentAge = now - resource.getTimestamp(); + if (currentAge > cacheControl.maxAge() * 1000) { + String etag = resource.getHeaders().get(HttpHeaders.ETAG); + if (etag != null) { + proxyRequest.headers().set(HttpHeaders.IF_NONE_MATCH, resource.getEtag()); + context.set("cached_resource", resource); + return context.sendRequest(); + } else { + return context.sendRequest(); + } } } } - } - // - String ifModifiedSinceHeader = response.getHeader(HttpHeaders.IF_MODIFIED_SINCE); - if ((response.method() == HttpMethod.GET || response.method() == HttpMethod.HEAD) && ifModifiedSinceHeader != null && resource.lastModified != null) { - Instant ifModifiedSince = ParseUtils.parseHeaderDate(ifModifiedSinceHeader); - if (!ifModifiedSince.isAfter(resource.lastModified)) { - response.response().setStatusCode(304).end(); - return Future.succeededFuture(); + // + String ifModifiedSinceHeader = response.getHeader(HttpHeaders.IF_MODIFIED_SINCE); + if ((response.method() == HttpMethod.GET || response.method() == HttpMethod.HEAD) && ifModifiedSinceHeader != null && resource.getLastModified() != null) { + Instant ifModifiedSince = ParseUtils.parseHeaderDate(ifModifiedSinceHeader); + if (!ifModifiedSince.isAfter(resource.getLastModified())) { + return Future.succeededFuture(proxyRequest.release().response().setStatusCode(304)); + } } - } - proxyRequest.release(); - ProxyResponse proxyResponse = proxyRequest.response(); - resource.init(proxyResponse); - return Future.succeededFuture(proxyResponse); + proxyRequest.release(); + ProxyResponse proxyResponse = proxyRequest.response(); + resource.init(proxyResponse); + return Future.succeededFuture(proxyResponse); + }); + } } diff --git a/src/main/java/io/vertx/httpproxy/impl/Resource.java b/src/main/java/io/vertx/httpproxy/impl/Resource.java deleted file mode 100644 index 2b7768b..0000000 --- a/src/main/java/io/vertx/httpproxy/impl/Resource.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2011-2020 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ -package io.vertx.httpproxy.impl; - -import io.vertx.core.MultiMap; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.HttpHeaders; -import io.vertx.httpproxy.Body; -import io.vertx.httpproxy.ProxyResponse; - -import java.time.Instant; - -class Resource { - - final String absoluteUri; - final int statusCode; - final String statusMessage; - final MultiMap headers; - final long timestamp; - final long maxAge; - final Instant lastModified; - final String etag; - final Buffer content = Buffer.buffer(); - - Resource(String absoluteUri, int statusCode, String statusMessage, MultiMap headers, long timestamp, long maxAge) { - String lastModifiedHeader = headers.get(HttpHeaders.LAST_MODIFIED); - this.absoluteUri = absoluteUri; - this.statusCode = statusCode; - this.statusMessage = statusMessage; - this.headers = headers; - this.timestamp = timestamp; - this.maxAge = maxAge; - this.lastModified = lastModifiedHeader != null ? ParseUtils.parseHeaderDate(lastModifiedHeader) : null; - this.etag = headers.get(HttpHeaders.ETAG); - } - - void init(ProxyResponse proxyResponse) { - proxyResponse.setStatusCode(200); - proxyResponse.setStatusMessage(statusMessage); - proxyResponse.headers().addAll(headers); - proxyResponse.setBody(Body.body(content)); - } -} diff --git a/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java b/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java index 22ca1cd..ad04f26 100644 --- a/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java +++ b/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java @@ -35,7 +35,7 @@ public class ReverseProxy implements HttpProxy { public ReverseProxy(ProxyOptions options, HttpClient client) { CacheOptions cacheOptions = options.getCacheOptions(); if (cacheOptions != null) { - Cache cache = cacheOptions.newCache(); + Cache cache = cacheOptions.newCache(); addInterceptor(new CachingFilter(cache)); } this.client = client; diff --git a/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java b/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java index c930719..88bf55c 100644 --- a/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java +++ b/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java @@ -1,9 +1,37 @@ package io.vertx.httpproxy.spi.cache; -import java.util.Map; +import io.vertx.core.Future; + /** * Cache SPI. */ -public interface Cache extends Map { +public interface Cache { + + /** + * Being called when the cache attempts to add a new cache item. + * + * @param key the URI of the resource + * @param value the cached response + * @return a succeed void future + */ + Future put(String key, Resource value); + + /** + * Being called when the cache attempts to fetch a cache item. + * + * @param key the URI of the resource + * @return the cached response, null if not exist + */ + Future get(String key); + + /** + * Being called when the cache attempts to delete a cache item, + * typically caused by invalidating an existing item. Do nothing + * if not exist. + * + * @param key the URI of the resource + * @return a succeed void future + */ + Future remove(String key); } diff --git a/src/main/java/io/vertx/httpproxy/spi/cache/Resource.java b/src/main/java/io/vertx/httpproxy/spi/cache/Resource.java new file mode 100644 index 0000000..67cc595 --- /dev/null +++ b/src/main/java/io/vertx/httpproxy/spi/cache/Resource.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2011-2020 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.httpproxy.spi.cache; + +import io.vertx.core.MultiMap; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.shareddata.ClusterSerializable; +import io.vertx.httpproxy.Body; +import io.vertx.httpproxy.ProxyResponse; +import io.vertx.httpproxy.impl.ParseUtils; + +import java.time.Instant; + +/** + * The cached object. + */ +public class Resource implements ClusterSerializable { + + private static final String UTF_8 = "utf-8"; + + private String absoluteUri; + private int statusCode; + private String statusMessage; + private MultiMap headers; + private long timestamp; + private long maxAge; + private Instant lastModified; + private String etag; + private Buffer content = Buffer.buffer(); + + // For serialization purposes, do not remove. + public Resource() { + } + + public Resource(String absoluteUri, int statusCode, String statusMessage, MultiMap headers, long timestamp, long maxAge) { + String lastModifiedHeader = headers.get(HttpHeaders.LAST_MODIFIED); + this.absoluteUri = absoluteUri; + this.statusCode = statusCode; + this.statusMessage = statusMessage; + this.headers = headers; + this.timestamp = timestamp; + this.maxAge = maxAge; + this.lastModified = lastModifiedHeader != null ? ParseUtils.parseHeaderDate(lastModifiedHeader) : null; + this.etag = headers.get(HttpHeaders.ETAG); + } + + public void init(ProxyResponse proxyResponse) { + proxyResponse.setStatusCode(200); + proxyResponse.setStatusMessage(statusMessage); + proxyResponse.headers().addAll(headers); + proxyResponse.setBody(Body.body(content)); + } + + private static class Cursor { + int i; + } + + @Override + public void writeToBuffer(Buffer buffer) { + appendString(buffer, absoluteUri); + appendInt(buffer, statusCode); + appendString(buffer, statusMessage); + appendMultiMap(buffer, headers); + appendLong(buffer, timestamp); + appendLong(buffer, maxAge); + appendInstant(buffer, lastModified); + appendString(buffer, etag); + appendBuffer(buffer, content); + } + + @Override + public int readFromBuffer(int pos, Buffer buffer) { + Cursor cursor = new Cursor(); + cursor.i = pos; + + setAbsoluteUri(readString(buffer, cursor)); + setStatusCode(readInt(buffer, cursor)); + setStatusMessage(readString(buffer, cursor)); + setHeaders(readMultiMap(buffer, cursor)); + setTimestamp(readLong(buffer, cursor)); + setMaxAge(readLong(buffer, cursor)); + setLastModified(readInstant(buffer, cursor)); + setEtag(readString(buffer, cursor)); + setContent(readBuffer(buffer, cursor)); + return cursor.i; + } + + private static void appendIsNull(Buffer buffer, Object object) { + buffer.appendByte((byte) (object == null ? 1 : 0)); + } + + private static boolean readIsNull(Buffer buffer, Cursor cursor) { + cursor.i += 1; + return buffer.getByte(cursor.i - 1) == (byte) 1; + } + + private static void appendInt(Buffer buffer, int num) { + buffer.appendInt(num); + } + + private static int readInt(Buffer buffer, Cursor cursor) { + cursor.i += 4; + return buffer.getInt(cursor.i - 4); + } + + private static void appendLong(Buffer buffer, long num) { + buffer.appendLong(num); + } + + private static long readLong(Buffer buffer, Cursor cursor) { + cursor.i += 8; + return buffer.getLong(cursor.i - 8); + } + + private static void appendInstant(Buffer buffer, Instant instant) { + appendIsNull(buffer, instant); + if (instant != null) appendLong(buffer, instant.toEpochMilli()); + } + + private static Instant readInstant(Buffer buffer, Cursor cursor) { + if (readIsNull(buffer, cursor)) return null; + return Instant.ofEpochMilli(readLong(buffer, cursor)); + } + + private static void appendBuffer(Buffer buffer, Buffer toAppend) { + appendIsNull(buffer, toAppend); + if (toAppend == null) return; + byte[] bytes = toAppend.getBytes(); + buffer.appendInt(bytes.length).appendBytes(bytes); + } + + private static Buffer readBuffer(Buffer buffer, Cursor cursor) { + if (readIsNull(buffer, cursor)) return null; + int len = buffer.getInt(cursor.i); + cursor.i += 4; + byte[] bytes = buffer.getBytes(cursor.i, cursor.i + len); + cursor.i += len; + return Buffer.buffer(bytes); + } + + private static void appendString(Buffer buffer, String string) { + appendBuffer(buffer, string == null ? null : Buffer.buffer(string, UTF_8)); + } + + private static String readString(Buffer buffer, Cursor cursor) { + Buffer result = readBuffer(buffer, cursor); + if (result == null) return null; + return result.toString(UTF_8); + } + + private static void appendMultiMap(Buffer buffer, MultiMap multiMap) { + appendIsNull(buffer, multiMap); + if (multiMap == null) return; + buffer.appendInt(multiMap.size()); + multiMap.forEach((key, value) -> { + appendString(buffer, key); + appendString(buffer, value); + }); + } + + private static MultiMap readMultiMap(Buffer buffer, Cursor cursor) { + if (readIsNull(buffer, cursor)) return null; + MultiMap multiMap = MultiMap.caseInsensitiveMultiMap(); + int size = buffer.getInt(cursor.i); + cursor.i += 4; + for (int i = 0; i < size; i++) { + multiMap.add(readString(buffer, cursor), readString(buffer, cursor)); + } + return multiMap; + } + + + public String getAbsoluteUri() { + return absoluteUri; + } + + public int getStatusCode() { + return statusCode; + } + + public String getStatusMessage() { + return statusMessage; + } + + public MultiMap getHeaders() { + return headers; + } + + public long getTimestamp() { + return timestamp; + } + + public long getMaxAge() { + return maxAge; + } + + public Instant getLastModified() { + return lastModified; + } + + public String getEtag() { + return etag; + } + + public Buffer getContent() { + return content; + } + + public void setAbsoluteUri(String absoluteUri) { + this.absoluteUri = absoluteUri; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + public void setStatusMessage(String statusMessage) { + this.statusMessage = statusMessage; + } + + public void setHeaders(MultiMap headers) { + this.headers = headers; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public void setMaxAge(long maxAge) { + this.maxAge = maxAge; + } + + public void setLastModified(Instant lastModified) { + this.lastModified = lastModified; + } + + public void setEtag(String etag) { + this.etag = etag; + } + + public void setContent(Buffer content) { + this.content = content; + } +} diff --git a/src/test/java/io/vertx/tests/parsing/ResourceParseTest.java b/src/test/java/io/vertx/tests/parsing/ResourceParseTest.java new file mode 100644 index 0000000..1621724 --- /dev/null +++ b/src/test/java/io/vertx/tests/parsing/ResourceParseTest.java @@ -0,0 +1,92 @@ +package io.vertx.tests.parsing; + +import io.vertx.core.MultiMap; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpHeaders; +import io.vertx.httpproxy.spi.cache.Resource; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Objects; + +public class ResourceParseTest { + + public static boolean weakEquals(Resource r1, Resource r2) { + boolean same = r1.getStatusCode() == r2.getStatusCode() + && r1.getTimestamp() == r2.getTimestamp() + && r1.getMaxAge() == r2.getMaxAge() + && Objects.equals(r1.getAbsoluteUri(), r2.getAbsoluteUri()) + && Objects.equals(r1.getStatusMessage(), r2.getStatusMessage()) + && Objects.equals(r1.getLastModified(), r2.getLastModified()) + && Objects.equals(r1.getEtag(), r2.getEtag()); + if (!same) return false; + + if (r1.getHeaders() == null ^ r2.getHeaders() == null) return false; + if (r1.getHeaders() != null && r2.getHeaders() != null) { + if (r1.getHeaders().size() != r2.getHeaders().size()) return false; + } + + if (r1.getContent() == null ^ r2.getContent() == null) return false; + if (r1.getContent() != null && r2.getContent() != null) { + if (r1.getContent().length() != r2.getContent().length()) return false; + } + + return true; + } + + @Test + public void testRegular() { + Resource resource = new Resource( + "http://www.example.com", + 200, + "OK", + MultiMap.caseInsensitiveMultiMap() + .add(HttpHeaders.LAST_MODIFIED, "Fri, 12 Jul 2024 12:34:56 GMT") + .add(HttpHeaders.ETAG, "etag0"), + System.currentTimeMillis(), + 3600 + ); + resource.getContent().appendInt(2048); + + Buffer buffer = Buffer.buffer(); + resource.writeToBuffer(buffer); + Resource recovered = new Resource(); + recovered.readFromBuffer(0, buffer); + + Assert.assertTrue(weakEquals(resource, recovered)); + } + + @Test + public void testEmpty() { + Resource resource = new Resource( + "http://www.example.com", + 200, + "OK", + MultiMap.caseInsensitiveMultiMap(), + System.currentTimeMillis(), + 3600 + ); + + Buffer buffer = Buffer.buffer(new byte[]{1, 1, 1, 1}); + resource.writeToBuffer(buffer); + Resource recovered = new Resource(); + recovered.readFromBuffer(4, buffer); + + Assert.assertTrue(weakEquals(resource, recovered)); + } + + @Test + public void testNulls() { + Resource resource = new Resource(); + + Buffer buffer = Buffer.buffer(); + resource.writeToBuffer(buffer); + Resource recovered = new Resource(); + recovered.readFromBuffer(0, buffer); + + Assert.assertTrue(weakEquals(resource, recovered)); + } + + +} From 74e787957b22f7919bd9d72a3ae5e6b2528a02ad Mon Sep 17 00:00:00 2001 From: Zengyi Wang Date: Mon, 5 Aug 2024 20:38:57 +0800 Subject: [PATCH 2/5] fix batch 2 --- .../java/io/vertx/httpproxy/HttpProxy.java | 1 + .../vertx/httpproxy/cache/CacheOptions.java | 84 ++++++++++++- .../io/vertx/httpproxy/impl/CacheImpl.java | 24 ++-- .../io/vertx/httpproxy/impl/ReverseProxy.java | 20 +++- .../io/vertx/httpproxy/spi/cache/Cache.java | 16 ++- .../vertx/httpproxy/spi/cache/Resource.java | 6 +- .../vertx/tests/cache/spi/CacheSpiBase.java | 111 ++++++++++++++++++ .../vertx/tests/cache/spi/LocalCacheTest.java | 16 +++ 8 files changed, 256 insertions(+), 22 deletions(-) create mode 100644 src/test/java/io/vertx/tests/cache/spi/CacheSpiBase.java create mode 100644 src/test/java/io/vertx/tests/cache/spi/LocalCacheTest.java diff --git a/src/main/java/io/vertx/httpproxy/HttpProxy.java b/src/main/java/io/vertx/httpproxy/HttpProxy.java index 9137dda..22689b7 100644 --- a/src/main/java/io/vertx/httpproxy/HttpProxy.java +++ b/src/main/java/io/vertx/httpproxy/HttpProxy.java @@ -15,6 +15,7 @@ import io.vertx.codegen.annotations.VertxGen; import io.vertx.core.Future; import io.vertx.core.Handler; +import io.vertx.core.Vertx; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpServerRequest; diff --git a/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java b/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java index 7ee7898..0a62cf3 100644 --- a/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java +++ b/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java @@ -3,8 +3,8 @@ import io.vertx.codegen.annotations.DataObject; import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.json.JsonObject; -import io.vertx.httpproxy.impl.CacheImpl; -import io.vertx.httpproxy.spi.cache.Cache; + +import java.util.Objects; /** * Cache options. @@ -13,19 +13,53 @@ @JsonGen(publicConverter = false) public class CacheOptions { + /** + * Default max size of the cache = 1000 + */ public static final int DEFAULT_MAX_SIZE = 1000; + /** + * Actual name of anonymous shared cache = {@code __vertx.DEFAULT} + */ + public static final String DEFAULT_NAME = "__vertx.DEFAULT"; + + /** + * Default shared cache = {@code false} + */ + public static final boolean DEFAULT_SHARED = false; + private int maxSize = DEFAULT_MAX_SIZE; + private String name = DEFAULT_NAME; + private boolean shared = DEFAULT_SHARED; + /** + * Default constructor + */ public CacheOptions() { } + /** + * Copy constructor + * + * @param other the options to copy + */ + public CacheOptions(CacheOptions other) { + this.maxSize = other.getMaxSize(); + this.name = other.getName(); + this.shared = other.getShared(); + } + + /** + * Constructor to create an options from JSON + * + * @param json the JSON + */ public CacheOptions(JsonObject json) { CacheOptionsConverter.fromJson(json, this); } /** - * @return the max number of entries the cache can hold + * @return the max number of entries the cache can hold. */ public int getMaxSize() { return maxSize; @@ -45,8 +79,43 @@ public CacheOptions setMaxSize(int maxSize) { return this; } - public Cache newCache() { - return new CacheImpl(this); + /** + * @return the cache name used for sharing + */ + public String getName() { + return this.name; + } + + /** + * Set the cache name, used when the cache is shared, otherwise ignored. + * @param name the new name + * @return a reference to this, so the API can be used fluently + */ + public CacheOptions setName(String name) { + Objects.requireNonNull(name, "Client name cannot be null"); + this.name = name; + return this; + } + + /** + * @return whether the cache is shared + */ + public boolean getShared() { + return shared; + } + + /** + * Set to {@code true} to share the cache. + * + *

There can be multiple shared caches distinguished by {@link #getName()}, when no specific + * name is set, the {@link #DEFAULT_NAME} is used. + * + * @param shared {@code true} to use a shared client + * @return a reference to this, so the API can be used fluently + */ + public CacheOptions setShared(boolean shared) { + this.shared = shared; + return this; } @Override @@ -54,6 +123,11 @@ public String toString() { return toJson().toString(); } + /** + * Convert to JSON + * + * @return the JSON + */ public JsonObject toJson() { JsonObject json = new JsonObject(); CacheOptionsConverter.toJson(this, json); diff --git a/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java b/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java index 2fce671..88b99ce 100644 --- a/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java +++ b/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java @@ -1,6 +1,7 @@ package io.vertx.httpproxy.impl; import io.vertx.core.Future; +import io.vertx.core.Vertx; import io.vertx.httpproxy.cache.CacheOptions; import io.vertx.httpproxy.spi.cache.Cache; import io.vertx.httpproxy.spi.cache.Resource; @@ -16,25 +17,22 @@ public class CacheImpl implements Cache { private final int maxSize; - private final Map data; - private final LinkedList records; + private final LinkedHashMap data; public CacheImpl(CacheOptions options) { this.maxSize = options.getMaxSize(); - this.data = new HashMap<>(); - this.records = new LinkedList<>(); + this.data = new LinkedHashMap<>() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > maxSize; + } + }; } @Override public Future put(String key, Resource value) { - while (records.size() >= maxSize) { - String toRemove = records.removeLast(); - data.remove(toRemove); - } - data.put(key, value); - records.addFirst(key); return Future.succeededFuture(); } @@ -45,8 +43,12 @@ public Future get(String key) { @Override public Future remove(String key) { - records.remove(key); data.remove(key); return Future.succeededFuture(); } + + @Override + public Future close() { + return Future.succeededFuture(); + } } diff --git a/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java b/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java index ad04f26..444b6e8 100644 --- a/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java +++ b/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java @@ -12,7 +12,11 @@ import io.vertx.core.Future; import io.vertx.core.buffer.Buffer; +import io.vertx.core.Vertx; import io.vertx.core.http.*; +import io.vertx.core.internal.CloseFuture; +import io.vertx.core.internal.VertxInternal; +import io.vertx.core.internal.http.*; import io.vertx.core.internal.logging.Logger; import io.vertx.core.internal.logging.LoggerFactory; import io.vertx.core.net.NetSocket; @@ -35,13 +39,27 @@ public class ReverseProxy implements HttpProxy { public ReverseProxy(ProxyOptions options, HttpClient client) { CacheOptions cacheOptions = options.getCacheOptions(); if (cacheOptions != null) { - Cache cache = cacheOptions.newCache(); + Cache cache = newCache(cacheOptions, ((HttpClientInternal) client).vertx()); addInterceptor(new CachingFilter(cache)); } this.client = client; this.supportWebSocket = options.getSupportWebSocket(); } + public Cache newCache(CacheOptions options, Vertx vertx) { + if (options.getShared()) { + CloseFuture closeFuture = new CloseFuture(); + return ((VertxInternal) vertx).createSharedResource("__vertx.shared.proxyCache", options.getName(), closeFuture, (cf_) -> { + Cache cache = new CacheImpl(options); + cf_.add(completion -> { + cache.close().onComplete(completion); + }); + return cache; + }); + } + return new CacheImpl(options); + } + @Override public HttpProxy originRequestProvider(BiFunction> provider) { selector = provider; diff --git a/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java b/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java index 88bf55c..d269753 100644 --- a/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java +++ b/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java @@ -1,6 +1,7 @@ package io.vertx.httpproxy.spi.cache; import io.vertx.core.Future; +import io.vertx.core.dns.SrvRecord; /** @@ -9,7 +10,9 @@ public interface Cache { /** - * Being called when the cache attempts to add a new cache item. + * Being called when the proxy attempts to add a new cache item. + * The cache can only store up to maxSize of the latest items based + * on CacheOptions. * * @param key the URI of the resource * @param value the cached response @@ -18,7 +21,7 @@ public interface Cache { Future put(String key, Resource value); /** - * Being called when the cache attempts to fetch a cache item. + * Being called when the proxy attempts to fetch a cache item. * * @param key the URI of the resource * @return the cached response, null if not exist @@ -26,7 +29,7 @@ public interface Cache { Future get(String key); /** - * Being called when the cache attempts to delete a cache item, + * Being called when the proxy attempts to delete a cache item, * typically caused by invalidating an existing item. Do nothing * if not exist. * @@ -34,4 +37,11 @@ public interface Cache { * @return a succeed void future */ Future remove(String key); + + /** + * Being called when need to close the cache. + * + * @return a succeed void future + */ + Future close(); } diff --git a/src/main/java/io/vertx/httpproxy/spi/cache/Resource.java b/src/main/java/io/vertx/httpproxy/spi/cache/Resource.java index 67cc595..d3b8dd6 100644 --- a/src/main/java/io/vertx/httpproxy/spi/cache/Resource.java +++ b/src/main/java/io/vertx/httpproxy/spi/cache/Resource.java @@ -18,6 +18,8 @@ import io.vertx.httpproxy.ProxyResponse; import io.vertx.httpproxy.impl.ParseUtils; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.time.Instant; /** @@ -25,7 +27,7 @@ */ public class Resource implements ClusterSerializable { - private static final String UTF_8 = "utf-8"; + private static final Charset UTF_8 = StandardCharsets.UTF_8; private String absoluteUri; private int statusCode; @@ -148,7 +150,7 @@ private static Buffer readBuffer(Buffer buffer, Cursor cursor) { } private static void appendString(Buffer buffer, String string) { - appendBuffer(buffer, string == null ? null : Buffer.buffer(string, UTF_8)); + appendBuffer(buffer, string == null ? null : Buffer.buffer(string.getBytes(UTF_8))); } private static String readString(Buffer buffer, Cursor cursor) { diff --git a/src/test/java/io/vertx/tests/cache/spi/CacheSpiBase.java b/src/test/java/io/vertx/tests/cache/spi/CacheSpiBase.java new file mode 100644 index 0000000..caee5fc --- /dev/null +++ b/src/test/java/io/vertx/tests/cache/spi/CacheSpiBase.java @@ -0,0 +1,111 @@ +package io.vertx.tests.cache.spi; + +import io.vertx.core.MultiMap; +import io.vertx.core.Vertx; +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; +import io.vertx.httpproxy.cache.CacheOptions; +import io.vertx.httpproxy.spi.cache.Cache; +import io.vertx.httpproxy.spi.cache.Resource; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.time.Instant; + +@RunWith(VertxUnitRunner.class) +public abstract class CacheSpiBase { + + protected Cache cache; + protected CacheOptions cacheOptions; + protected Vertx vertx; + private Instant now; + + private final String URL1 = "http://k1.exmaple.com"; + private final String URL2 = "http://k2.exmaple.com"; + private final String URL3 = "http://k3.exmaple.com"; + + @Before + public void setUp() { + vertx = Vertx.vertx(); + now = Instant.now(); + cacheOptions = new CacheOptions().setMaxSize(2); + } + + @After + public void tearDown(TestContext context) { + cache.close().onComplete(context.asyncAssertSuccess( + v -> vertx.close().onComplete(context.asyncAssertSuccess()) + )); + } + + private Resource generateResource(String absoluteURI, long maxAge) { + return new Resource( + absoluteURI, + 200, + "OK", + MultiMap.caseInsensitiveMultiMap(), + now.toEpochMilli(), + maxAge + ); + } + + @Test + public void testAddAndGet(TestContext ctx) { + Async latch = ctx.async(); + cache.put(URL1, generateResource(URL1, 100L)).compose(v -> { + return cache.get(URL1); + }).onComplete(ctx.asyncAssertSuccess(res1 -> { + ctx.assertNotNull(res1); + ctx.assertEquals(res1.getMaxAge(), 100L); + })).compose(v -> cache.get(URL2)) + .onComplete(ctx.asyncAssertSuccess(res2 -> { + ctx.assertNull(res2); + })).compose(v -> cache.put(URL1, generateResource(URL1, 200L))) + .compose(v -> cache.get(URL1)).onComplete(ctx.asyncAssertSuccess(res1 -> { + ctx.assertNotNull(res1); + ctx.assertEquals(res1.getMaxAge(), 200L); + latch.complete(); + })); + } + + @Test + public void testRemove(TestContext ctx) { + Async latch = ctx.async(); + cache.put(URL1, generateResource(URL1, 100L)) + .compose(v -> cache.put(URL2, generateResource(URL2, 200L))) + .compose(v -> cache.remove(URL1)) + .onSuccess(v -> { + cache.get(URL1).onSuccess(resp1 -> { + cache.get(URL2).onComplete(ctx.asyncAssertSuccess(resp2 -> { + ctx.assertNull(resp1); + ctx.assertEquals(resp2.getMaxAge(), 200L); + })).compose(x -> cache.remove(URL1)).onComplete(ctx.asyncAssertSuccess(r -> latch.complete())); + }); + }); + } + + @Test + public void testMaxSize(TestContext ctx) { + Async latch = ctx.async(); + cache.put(URL1, generateResource(URL1, 100L)) + .compose(v -> cache.put(URL2, generateResource(URL2, 200L))) + .compose(v -> cache.put(URL3, generateResource(URL3, 300L))) + .onSuccess(v -> { + cache.get(URL1).onSuccess(resp1 -> { + cache.get(URL2).onSuccess(resp2 -> { + cache.get(URL3).onSuccess(resp3 -> { + int cnt = 0; + if (resp1 != null) cnt++; + if (resp2 != null) cnt++; + if (resp3 != null) cnt++; + ctx.assertEquals(cnt, 2); + latch.complete(); + }); + }); + }); + }); + } +} diff --git a/src/test/java/io/vertx/tests/cache/spi/LocalCacheTest.java b/src/test/java/io/vertx/tests/cache/spi/LocalCacheTest.java new file mode 100644 index 0000000..e4e0d10 --- /dev/null +++ b/src/test/java/io/vertx/tests/cache/spi/LocalCacheTest.java @@ -0,0 +1,16 @@ +package io.vertx.tests.cache.spi; + +import io.vertx.core.http.HttpClientOptions; +import io.vertx.ext.unit.TestContext; +import io.vertx.httpproxy.cache.CacheOptions; +import io.vertx.httpproxy.impl.CacheImpl; + +public class LocalCacheTest extends CacheSpiBase { + + @Override + public void setUp() { + super.setUp(); + cache = new CacheImpl(cacheOptions); + } + +} From 3b9c859ed967e5ecc6a86b20b29db9c7e3f703b8 Mon Sep 17 00:00:00 2001 From: Zengyi Wang Date: Thu, 8 Aug 2024 16:29:08 +0800 Subject: [PATCH 3/5] fix batch 3 --- src/main/java/io/vertx/httpproxy/impl/CacheImpl.java | 11 ++++------- .../java/io/vertx/httpproxy/impl/CachingFilter.java | 4 +--- .../spi/{CacheSpiBase.java => CacheSpiTestBase.java} | 2 +- .../java/io/vertx/tests/cache/spi/LocalCacheTest.java | 5 +---- 4 files changed, 7 insertions(+), 15 deletions(-) rename src/test/java/io/vertx/tests/cache/spi/{CacheSpiBase.java => CacheSpiTestBase.java} (98%) diff --git a/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java b/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java index 88b99ce..a22b821 100644 --- a/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java +++ b/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java @@ -6,10 +6,7 @@ import io.vertx.httpproxy.spi.cache.Cache; import io.vertx.httpproxy.spi.cache.Resource; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.Map; +import java.util.*; /** * Simplistic implementation. @@ -17,16 +14,16 @@ public class CacheImpl implements Cache { private final int maxSize; - private final LinkedHashMap data; + private final Map data; public CacheImpl(CacheOptions options) { this.maxSize = options.getMaxSize(); - this.data = new LinkedHashMap<>() { + this.data = Collections.synchronizedMap(new LinkedHashMap<>() { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > maxSize; } - }; + }); } diff --git a/src/main/java/io/vertx/httpproxy/impl/CachingFilter.java b/src/main/java/io/vertx/httpproxy/impl/CachingFilter.java index f5bb07d..edec8d5 100644 --- a/src/main/java/io/vertx/httpproxy/impl/CachingFilter.java +++ b/src/main/java/io/vertx/httpproxy/impl/CachingFilter.java @@ -122,10 +122,8 @@ private Future tryHandleProxyRequestFromCache(ProxyContext contex if (etag != null) { proxyRequest.headers().set(HttpHeaders.IF_NONE_MATCH, resource.getEtag()); context.set("cached_resource", resource); - return context.sendRequest(); - } else { - return context.sendRequest(); } + return context.sendRequest(); } } } diff --git a/src/test/java/io/vertx/tests/cache/spi/CacheSpiBase.java b/src/test/java/io/vertx/tests/cache/spi/CacheSpiTestBase.java similarity index 98% rename from src/test/java/io/vertx/tests/cache/spi/CacheSpiBase.java rename to src/test/java/io/vertx/tests/cache/spi/CacheSpiTestBase.java index caee5fc..5f03cbc 100644 --- a/src/test/java/io/vertx/tests/cache/spi/CacheSpiBase.java +++ b/src/test/java/io/vertx/tests/cache/spi/CacheSpiTestBase.java @@ -16,7 +16,7 @@ import java.time.Instant; @RunWith(VertxUnitRunner.class) -public abstract class CacheSpiBase { +public abstract class CacheSpiTestBase { protected Cache cache; protected CacheOptions cacheOptions; diff --git a/src/test/java/io/vertx/tests/cache/spi/LocalCacheTest.java b/src/test/java/io/vertx/tests/cache/spi/LocalCacheTest.java index e4e0d10..18bd475 100644 --- a/src/test/java/io/vertx/tests/cache/spi/LocalCacheTest.java +++ b/src/test/java/io/vertx/tests/cache/spi/LocalCacheTest.java @@ -1,11 +1,8 @@ package io.vertx.tests.cache.spi; -import io.vertx.core.http.HttpClientOptions; -import io.vertx.ext.unit.TestContext; -import io.vertx.httpproxy.cache.CacheOptions; import io.vertx.httpproxy.impl.CacheImpl; -public class LocalCacheTest extends CacheSpiBase { +public class LocalCacheTest extends CacheSpiTestBase { @Override public void setUp() { From 5e089c35a9a897f034246a1480fff1a9c3554d91 Mon Sep 17 00:00:00 2001 From: Zengyi Wang Date: Wed, 14 Aug 2024 20:56:05 +0800 Subject: [PATCH 4/5] fix batch 4 --- .../java/io/vertx/httpproxy/impl/CachingFilter.java | 11 +++++++++-- src/main/java/io/vertx/httpproxy/spi/cache/Cache.java | 2 +- .../java/io/vertx/httpproxy/spi/cache/Resource.java | 7 ------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/vertx/httpproxy/impl/CachingFilter.java b/src/main/java/io/vertx/httpproxy/impl/CachingFilter.java index edec8d5..c871476 100644 --- a/src/main/java/io/vertx/httpproxy/impl/CachingFilter.java +++ b/src/main/java/io/vertx/httpproxy/impl/CachingFilter.java @@ -42,7 +42,7 @@ private Future sendAndTryCacheProxyResponse(ProxyContext context) { if (cached != null && response.getStatusCode() == 304) { // Warning: this relies on the fact that HttpServerRequest will not send a body for HEAD response.release(); - cached.init(response); + fillResponseFromResource(response, cached); return context.sendResponse(); } @@ -138,9 +138,16 @@ private Future tryHandleProxyRequestFromCache(ProxyContext contex } proxyRequest.release(); ProxyResponse proxyResponse = proxyRequest.response(); - resource.init(proxyResponse); + fillResponseFromResource(proxyResponse, resource); return Future.succeededFuture(proxyResponse); }); } + + public void fillResponseFromResource(ProxyResponse proxyResponse, Resource resource) { + proxyResponse.setStatusCode(200); + proxyResponse.setStatusMessage(resource.getStatusMessage()); + proxyResponse.headers().addAll(resource.getHeaders()); + proxyResponse.setBody(Body.body(resource.getContent())); + } } diff --git a/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java b/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java index d269753..3531a07 100644 --- a/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java +++ b/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java @@ -24,7 +24,7 @@ public interface Cache { * Being called when the proxy attempts to fetch a cache item. * * @param key the URI of the resource - * @return the cached response, null if not exist + * @return the cached response, null if not exist, should all wrap with future */ Future get(String key); diff --git a/src/main/java/io/vertx/httpproxy/spi/cache/Resource.java b/src/main/java/io/vertx/httpproxy/spi/cache/Resource.java index d3b8dd6..8921f53 100644 --- a/src/main/java/io/vertx/httpproxy/spi/cache/Resource.java +++ b/src/main/java/io/vertx/httpproxy/spi/cache/Resource.java @@ -55,13 +55,6 @@ public Resource(String absoluteUri, int statusCode, String statusMessage, MultiM this.etag = headers.get(HttpHeaders.ETAG); } - public void init(ProxyResponse proxyResponse) { - proxyResponse.setStatusCode(200); - proxyResponse.setStatusMessage(statusMessage); - proxyResponse.headers().addAll(headers); - proxyResponse.setBody(Body.body(content)); - } - private static class Cursor { int i; } From 341e28625142189f5f5f1c498c97b2022d19ebc5 Mon Sep 17 00:00:00 2001 From: Zengyi Wang Date: Sun, 22 Sep 2024 15:15:55 +0200 Subject: [PATCH 5/5] fix batch 5 --- .../httpproxy/cache/CacheOptionsConverter.java | 14 ++++++++++++++ .../java/io/vertx/httpproxy/HttpProxy.java | 1 - .../java/io/vertx/httpproxy/ProxyContext.java | 4 ++-- .../io/vertx/httpproxy/ProxyInterceptor.java | 1 + .../java/io/vertx/httpproxy/ProxyRequest.java | 6 +++--- .../io/vertx/httpproxy/cache/CacheOptions.java | 8 ++++---- .../io/vertx/httpproxy/impl/CacheImpl.java | 4 ---- .../io/vertx/httpproxy/impl/ReverseProxy.java | 6 +----- .../io/vertx/httpproxy/spi/cache/Cache.java | 9 ++------- .../tests/cache/spi/CacheSpiTestBase.java | 4 +--- .../vertx/tests/parsing/ResourceParseTest.java | 18 ++++++++++++------ 11 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/main/generated/io/vertx/httpproxy/cache/CacheOptionsConverter.java b/src/main/generated/io/vertx/httpproxy/cache/CacheOptionsConverter.java index dd565b9..490d54b 100644 --- a/src/main/generated/io/vertx/httpproxy/cache/CacheOptionsConverter.java +++ b/src/main/generated/io/vertx/httpproxy/cache/CacheOptionsConverter.java @@ -23,6 +23,16 @@ static void fromJson(Iterable> json, CacheOp obj.setMaxSize(((Number)member.getValue()).intValue()); } break; + case "name": + if (member.getValue() instanceof String) { + obj.setName((String)member.getValue()); + } + break; + case "shared": + if (member.getValue() instanceof Boolean) { + obj.setShared((Boolean)member.getValue()); + } + break; } } } @@ -33,5 +43,9 @@ static void toJson(CacheOptions obj, JsonObject json) { static void toJson(CacheOptions obj, java.util.Map json) { json.put("maxSize", obj.getMaxSize()); + if (obj.getName() != null) { + json.put("name", obj.getName()); + } + json.put("shared", obj.getShared()); } } diff --git a/src/main/java/io/vertx/httpproxy/HttpProxy.java b/src/main/java/io/vertx/httpproxy/HttpProxy.java index 22689b7..9137dda 100644 --- a/src/main/java/io/vertx/httpproxy/HttpProxy.java +++ b/src/main/java/io/vertx/httpproxy/HttpProxy.java @@ -15,7 +15,6 @@ import io.vertx.codegen.annotations.VertxGen; import io.vertx.core.Future; import io.vertx.core.Handler; -import io.vertx.core.Vertx; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpServerRequest; diff --git a/src/main/java/io/vertx/httpproxy/ProxyContext.java b/src/main/java/io/vertx/httpproxy/ProxyContext.java index a080fc7..6cd40f5 100644 --- a/src/main/java/io/vertx/httpproxy/ProxyContext.java +++ b/src/main/java/io/vertx/httpproxy/ProxyContext.java @@ -35,7 +35,7 @@ public interface ProxyContext { boolean isWebSocket(); /** - * Attach a payload to the context + * Attach a payload to the context. * * @param name the payload name * @param value any payload value @@ -43,7 +43,7 @@ public interface ProxyContext { void set(String name, Object value); /** - * Get a payload attached to this context + * Get a payload attached to this context. * * @param name the payload name * @param type the expected payload type diff --git a/src/main/java/io/vertx/httpproxy/ProxyInterceptor.java b/src/main/java/io/vertx/httpproxy/ProxyInterceptor.java index 65ec34a..252f320 100644 --- a/src/main/java/io/vertx/httpproxy/ProxyInterceptor.java +++ b/src/main/java/io/vertx/httpproxy/ProxyInterceptor.java @@ -32,6 +32,7 @@ default Future handleProxyResponse(ProxyContext context) { /** * Used to set whether to apply the interceptor to the WebSocket * handshake packet. The default value is false. + * * @return the boolean value */ default boolean allowApplyToWebSocket() { diff --git a/src/main/java/io/vertx/httpproxy/ProxyRequest.java b/src/main/java/io/vertx/httpproxy/ProxyRequest.java index 5f7bda9..30d96d5 100644 --- a/src/main/java/io/vertx/httpproxy/ProxyRequest.java +++ b/src/main/java/io/vertx/httpproxy/ProxyRequest.java @@ -101,7 +101,7 @@ static ProxyRequest reverseProxy(HttpServerRequest proxiedRequest) { ProxyRequest setBody(Body body); /** - * Set the request authority + * Set the request authority. * *

    *
  • for HTTP/1 the {@literal Host} header
  • @@ -128,7 +128,7 @@ static ProxyRequest reverseProxy(HttpServerRequest proxiedRequest) { MultiMap headers(); /** - * Put an HTTP header + * Put an HTTP header. * * @param name The header name * @param value The header value @@ -157,7 +157,7 @@ default Future proxy(HttpClientRequest request) { Future send(HttpClientRequest request); /** - * Release the proxy request and its associated resources + * Release the proxy request and its associated resources. * *

    The HTTP server request is resumed, no HTTP server response is sent. * diff --git a/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java b/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java index 0a62cf3..2661825 100644 --- a/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java +++ b/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java @@ -33,13 +33,13 @@ public class CacheOptions { private boolean shared = DEFAULT_SHARED; /** - * Default constructor + * Default constructor. */ public CacheOptions() { } /** - * Copy constructor + * Copy constructor. * * @param other the options to copy */ @@ -50,7 +50,7 @@ public CacheOptions(CacheOptions other) { } /** - * Constructor to create an options from JSON + * Constructor to create an options from JSON. * * @param json the JSON */ @@ -124,7 +124,7 @@ public String toString() { } /** - * Convert to JSON + * Convert to JSON. * * @return the JSON */ diff --git a/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java b/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java index a22b821..359f493 100644 --- a/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java +++ b/src/main/java/io/vertx/httpproxy/impl/CacheImpl.java @@ -44,8 +44,4 @@ public Future remove(String key) { return Future.succeededFuture(); } - @Override - public Future close() { - return Future.succeededFuture(); - } } diff --git a/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java b/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java index 444b6e8..975f026 100644 --- a/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java +++ b/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java @@ -50,11 +50,7 @@ public Cache newCache(CacheOptions options, Vertx vertx) { if (options.getShared()) { CloseFuture closeFuture = new CloseFuture(); return ((VertxInternal) vertx).createSharedResource("__vertx.shared.proxyCache", options.getName(), closeFuture, (cf_) -> { - Cache cache = new CacheImpl(options); - cf_.add(completion -> { - cache.close().onComplete(completion); - }); - return cache; + return new CacheImpl(options); }); } return new CacheImpl(options); diff --git a/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java b/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java index 3531a07..e36bb89 100644 --- a/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java +++ b/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java @@ -1,5 +1,6 @@ package io.vertx.httpproxy.spi.cache; +import io.vertx.codegen.annotations.Unstable; import io.vertx.core.Future; import io.vertx.core.dns.SrvRecord; @@ -7,6 +8,7 @@ /** * Cache SPI. */ +@Unstable public interface Cache { /** @@ -37,11 +39,4 @@ public interface Cache { * @return a succeed void future */ Future remove(String key); - - /** - * Being called when need to close the cache. - * - * @return a succeed void future - */ - Future close(); } diff --git a/src/test/java/io/vertx/tests/cache/spi/CacheSpiTestBase.java b/src/test/java/io/vertx/tests/cache/spi/CacheSpiTestBase.java index 5f03cbc..0f68ff7 100644 --- a/src/test/java/io/vertx/tests/cache/spi/CacheSpiTestBase.java +++ b/src/test/java/io/vertx/tests/cache/spi/CacheSpiTestBase.java @@ -36,9 +36,7 @@ public void setUp() { @After public void tearDown(TestContext context) { - cache.close().onComplete(context.asyncAssertSuccess( - v -> vertx.close().onComplete(context.asyncAssertSuccess()) - )); + vertx.close().onComplete(context.asyncAssertSuccess()); } private Resource generateResource(String absoluteURI, long maxAge) { diff --git a/src/test/java/io/vertx/tests/parsing/ResourceParseTest.java b/src/test/java/io/vertx/tests/parsing/ResourceParseTest.java index 1621724..933c3e7 100644 --- a/src/test/java/io/vertx/tests/parsing/ResourceParseTest.java +++ b/src/test/java/io/vertx/tests/parsing/ResourceParseTest.java @@ -8,11 +8,12 @@ import org.junit.Test; import java.util.Arrays; +import java.util.Map; import java.util.Objects; public class ResourceParseTest { - public static boolean weakEquals(Resource r1, Resource r2) { + public static boolean resourceEquals(Resource r1, Resource r2) { boolean same = r1.getStatusCode() == r2.getStatusCode() && r1.getTimestamp() == r2.getTimestamp() && r1.getMaxAge() == r2.getMaxAge() @@ -24,12 +25,17 @@ public static boolean weakEquals(Resource r1, Resource r2) { if (r1.getHeaders() == null ^ r2.getHeaders() == null) return false; if (r1.getHeaders() != null && r2.getHeaders() != null) { - if (r1.getHeaders().size() != r2.getHeaders().size()) return false; + MultiMap h1 = r1.getHeaders(); + MultiMap h2 = r2.getHeaders(); + if (h1.size() != h2.size()) return false; + for (Map.Entry e : h1.entries()) { + if (!Objects.equals(e.getValue(), h2.get(e.getKey()))) return false; + } } if (r1.getContent() == null ^ r2.getContent() == null) return false; if (r1.getContent() != null && r2.getContent() != null) { - if (r1.getContent().length() != r2.getContent().length()) return false; + if (!Arrays.equals(r1.getContent().getBytes(), r2.getContent().getBytes())) return false; } return true; @@ -54,7 +60,7 @@ public void testRegular() { Resource recovered = new Resource(); recovered.readFromBuffer(0, buffer); - Assert.assertTrue(weakEquals(resource, recovered)); + Assert.assertTrue(resourceEquals(resource, recovered)); } @Test @@ -73,7 +79,7 @@ public void testEmpty() { Resource recovered = new Resource(); recovered.readFromBuffer(4, buffer); - Assert.assertTrue(weakEquals(resource, recovered)); + Assert.assertTrue(resourceEquals(resource, recovered)); } @Test @@ -85,7 +91,7 @@ public void testNulls() { Resource recovered = new Resource(); recovered.readFromBuffer(0, buffer); - Assert.assertTrue(weakEquals(resource, recovered)); + Assert.assertTrue(resourceEquals(resource, recovered)); }