Skip to content

Commit 1497105

Browse files
committed
add tests
1 parent 5c8fd99 commit 1497105

10 files changed

+740
-83
lines changed

src/main/java/io/vertx/httpproxy/impl/CacheControl.java

+8-4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ public CacheControl parse(String header) {
4141
_private = false;
4242
proxyRevalidate = false;
4343
_public = false;
44+
maxAge = -1;
45+
maxStale = -1;
46+
minFresh = -1;
47+
sMaxage = -1;
4448

4549
String[] parts = header.split(","); // No regex
4650
for (String part : parts) {
@@ -74,10 +78,10 @@ public CacheControl parse(String header) {
7478
proxyRevalidate = true;
7579
break;
7680
default:
77-
maxAge = loadInt(part, "max-age=");
78-
maxStale = loadInt(part, "max-stale=");
79-
minFresh = loadInt(part, "min-fresh=");
80-
sMaxage = loadInt(part, "s-maxage=");
81+
maxAge = Math.max(maxAge, loadInt(part, "max-age="));
82+
maxStale = Math.max(maxStale, loadInt(part, "max-stale="));
83+
minFresh = Math.max(minFresh, loadInt(part, "min-fresh="));
84+
sMaxage = Math.max(sMaxage, loadInt(part, "s-maxage="));
8185
break;
8286
}
8387
}

src/main/java/io/vertx/httpproxy/impl/CachingFilter.java

+82-60
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,15 @@
1414
import io.vertx.httpproxy.spi.cache.Resource;
1515

1616
import java.time.Instant;
17+
import java.util.ArrayList;
1718
import java.util.List;
1819
import java.util.Map;
1920

2021
class CachingFilter implements ProxyInterceptor {
2122

23+
private static final String SKIP_CACHE_RESPONSE_HANDLING = "skip_cache_response_handling";
24+
private static final String CACHED_RESOURCE = "cached_resource";
25+
2226
private final Cache cache;
2327

2428
public CachingFilter(Cache cache) {
@@ -32,14 +36,19 @@ public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
3236

3337
@Override
3438
public Future<Void> handleProxyResponse(ProxyContext context) {
35-
return sendAndTryCacheProxyResponse(context);
39+
Boolean skip = context.get(SKIP_CACHE_RESPONSE_HANDLING, Boolean.class);
40+
if (skip != null && skip) {
41+
return context.sendResponse();
42+
} else {
43+
return sendAndTryCacheProxyResponse(context);
44+
}
3645
}
3746

3847
private Future<Void> sendAndTryCacheProxyResponse(ProxyContext context) {
3948

4049
ProxyResponse response = context.response();
4150
ProxyRequest request = response.request();
42-
Resource cached = context.get("cached_resource", Resource.class);
51+
Resource cached = context.get(CACHED_RESOURCE, Resource.class);
4352
String absoluteUri = request.absoluteURI();
4453

4554
if (cached != null && response.getStatusCode() == 304) {
@@ -65,7 +74,7 @@ private Future<Void> sendAndTryCacheProxyResponse(ProxyContext context) {
6574
canCache = false;
6675
}
6776
}
68-
if (response.headers().get(HttpHeaders.AUTHORIZATION) != null) {
77+
if (request.headers().get(HttpHeaders.AUTHORIZATION) != null) {
6978
if (
7079
responseCacheControl == null || (
7180
!responseCacheControl.isMustRevalidate()
@@ -78,6 +87,9 @@ private Future<Void> sendAndTryCacheProxyResponse(ProxyContext context) {
7887
if (requestCacheControl != null && requestCacheControl.isNoStore()) {
7988
canCache = false;
8089
}
90+
if ("*".equals(response.headers().get(HttpHeaders.VARY))) {
91+
canCache = false;
92+
}
8193
if (canCache) {
8294
if (request.getMethod() == HttpMethod.GET) {
8395
Resource res = new Resource(
@@ -114,9 +126,6 @@ private static MultiMap varyHeaders(MultiMap requestHeaders, MultiMap responseHe
114126
MultiMap result = MultiMap.caseInsensitiveMultiMap();
115127
String vary = responseHeaders.get(HttpHeaders.VARY);
116128
if (vary != null) {
117-
if (vary.trim().equals("*")) {
118-
return result.addAll(requestHeaders);
119-
}
120129
for (String toVary : vary.split(",")) {
121130
toVary = toVary.trim();
122131
String toVaryValue = requestHeaders.get(toVary);
@@ -149,62 +158,22 @@ private Future<ProxyResponse> tryHandleProxyRequestFromCache(ProxyContext contex
149158
return cache.get(cacheKey).compose(resource -> {
150159
if (resource == null || !checkVaryHeaders(proxyRequest.headers(), resource.getRequestVaryHeader())) {
151160
if (requestCacheControl != null && requestCacheControl.isOnlyIfCached()) {
161+
context.set(SKIP_CACHE_RESPONSE_HANDLING, true);
152162
return Future.succeededFuture(proxyRequest.release().response().setStatusCode(504));
153163
}
154164
return context.sendRequest();
155165
}
156166

157-
boolean validInboundCache = false;
158-
String inboundIfModifiedSince = inboundRequest.getHeader(HttpHeaders.IF_MODIFIED_SINCE);
159-
String inboundIfNoneMatch = inboundRequest.getHeader(HttpHeaders.IF_NONE_MATCH);
160-
Instant resourceLastModified = resource.getLastModified();
161-
String resourceETag = resource.getEtag();
162-
if (resource.getStatusCode() == 200) { // TODO: status code 206
163-
if (inboundIfNoneMatch != null && resourceETag != null) {
164-
String[] inboundETags = inboundIfNoneMatch.split(",");
165-
for (String inboundETag : inboundETags) {
166-
inboundETag = inboundETag.trim();
167-
if (inboundETag.equals(resourceETag)) {
168-
validInboundCache = true;
169-
break;
170-
}
171-
}
172-
} else if (inboundIfModifiedSince != null && resourceLastModified != null) {
173-
if (ParseUtils.parseHeaderDate(inboundIfModifiedSince).isAfter(resourceLastModified)) { // TODO: is it wrong???
174-
validInboundCache = true;
175-
}
176-
}
177-
}
178-
if (validInboundCache) {
179-
MultiMap infoHeaders = MultiMap.caseInsensitiveMultiMap();
180-
List<CharSequence> headersNeeded = List.of(
181-
HttpHeaders.CACHE_CONTROL,
182-
HttpHeaders.CONTENT_LOCATION,
183-
HttpHeaders.DATE,
184-
HttpHeaders.ETAG,
185-
HttpHeaders.EXPIRES,
186-
HttpHeaders.VARY
187-
);
188-
for (CharSequence header : headersNeeded) {
189-
String value = resource.getHeaders().get(header);
190-
if (value != null) infoHeaders.add(header, value);
191-
}
192-
ProxyResponse resp = proxyRequest.release().response();
193-
resp.headers().setAll(infoHeaders);
194-
resp.setStatusCode(304);
195-
return Future.succeededFuture(resp);
196-
}
197-
167+
// to check if the resource is fresh
198168
boolean needValidate = false;
199169
String resourceCacheControlHeader = resource.getHeaders().get(HttpHeaders.CACHE_CONTROL);
200170
CacheControl resourceCacheControl = resourceCacheControlHeader == null ? null : new CacheControl().parse(resourceCacheControlHeader);
201171
if (resourceCacheControl != null && resourceCacheControl.isNoCache()) needValidate = true;
202172
if (requestCacheControl != null && requestCacheControl.isNoCache()) needValidate = true;
203173
long age = Math.subtractExact(System.currentTimeMillis(), resource.getTimestamp()); // in ms
204174
long maxAge = Math.max(0, resource.getMaxAge());
205-
if (resourceCacheControl != null && (resourceCacheControl.isMustRevalidate() || resourceCacheControl.isProxyRevalidate())) {
206-
if (age > maxAge) needValidate = true;
207-
} else if (requestCacheControl != null) {
175+
boolean responseValidateOverride = resourceCacheControl != null && (resourceCacheControl.isMustRevalidate() || resourceCacheControl.isProxyRevalidate());
176+
if (!responseValidateOverride && requestCacheControl != null) {
208177
if (requestCacheControl.maxAge() != -1) {
209178
maxAge = Math.min(maxAge, SafeMathUtils.safeMultiply(requestCacheControl.maxAge(), 1000));
210179
}
@@ -213,8 +182,8 @@ private Future<ProxyResponse> tryHandleProxyRequestFromCache(ProxyContext contex
213182
} else if (requestCacheControl.maxStale() != -1) {
214183
maxAge += SafeMathUtils.safeMultiply(requestCacheControl.maxStale(), 1000);
215184
}
216-
if (age > maxAge) needValidate = true;
217185
}
186+
if (age > maxAge) needValidate = true;
218187
String etag = resource.getHeaders().get(HttpHeaders.ETAG);
219188
String lastModified = resource.getHeaders().get(HttpHeaders.LAST_MODIFIED);
220189
if (needValidate) {
@@ -224,13 +193,68 @@ private Future<ProxyResponse> tryHandleProxyRequestFromCache(ProxyContext contex
224193
if (lastModified != null) {
225194
proxyRequest.headers().set(HttpHeaders.IF_MODIFIED_SINCE, lastModified);
226195
}
227-
context.set("cached_resource", resource);
196+
context.set(CACHED_RESOURCE, resource);
228197
return context.sendRequest();
229198
} else {
230-
proxyRequest.release();
231-
ProxyResponse proxyResponse = proxyRequest.response();
232-
resource.init(proxyResponse, inboundRequest.method() == HttpMethod.GET);
233-
return Future.succeededFuture(proxyResponse);
199+
// check if the client already have valid cache using current cache
200+
boolean validInboundCache = false;
201+
Instant inboundIfModifiedSince = ParseUtils.parseHeaderDate(inboundRequest.getHeader(HttpHeaders.IF_MODIFIED_SINCE));
202+
String inboundIfNoneMatch = inboundRequest.getHeader(HttpHeaders.IF_NONE_MATCH);
203+
Instant resourceLastModified = resource.getLastModified();
204+
Instant resourceDate = ParseUtils.parseHeaderDate(resource.getHeaders().get(HttpHeaders.DATE));
205+
String resourceETag = resource.getEtag();
206+
if (resource.getStatusCode() == 200) {
207+
if (inboundIfNoneMatch != null) {
208+
if (resourceETag != null) {
209+
String[] inboundETags = inboundIfNoneMatch.split(",");
210+
for (String inboundETag : inboundETags) {
211+
inboundETag = inboundETag.trim();
212+
if (inboundETag.equals(resourceETag)) {
213+
validInboundCache = true;
214+
break;
215+
}
216+
}
217+
}
218+
} else if (inboundIfModifiedSince != null) {
219+
if (resourceLastModified != null) {
220+
if (!inboundIfModifiedSince.isBefore(resourceLastModified)) {
221+
validInboundCache = true;
222+
}
223+
} else if (resourceDate != null) {
224+
if (!inboundIfModifiedSince.isBefore(resourceDate)) {
225+
validInboundCache = true;
226+
}
227+
}
228+
229+
}
230+
}
231+
if (validInboundCache) {
232+
MultiMap infoHeaders = MultiMap.caseInsensitiveMultiMap();
233+
List<CharSequence> headersNeeded = new ArrayList<>(List.of(
234+
HttpHeaders.CACHE_CONTROL,
235+
HttpHeaders.CONTENT_LOCATION,
236+
HttpHeaders.DATE,
237+
HttpHeaders.ETAG,
238+
HttpHeaders.EXPIRES,
239+
HttpHeaders.VARY
240+
));
241+
if (inboundIfNoneMatch == null) headersNeeded.add(HttpHeaders.LAST_MODIFIED);
242+
for (CharSequence header : headersNeeded) {
243+
String value = resource.getHeaders().get(header);
244+
if (value != null) infoHeaders.add(header, value);
245+
}
246+
ProxyResponse resp = proxyRequest.release().response();
247+
resp.headers().setAll(infoHeaders);
248+
resp.setStatusCode(304);
249+
context.set(SKIP_CACHE_RESPONSE_HANDLING, true);
250+
return Future.succeededFuture(resp);
251+
} else {
252+
proxyRequest.release();
253+
ProxyResponse proxyResponse = proxyRequest.response();
254+
resource.init(proxyResponse, inboundRequest.method() == HttpMethod.GET);
255+
context.set(SKIP_CACHE_RESPONSE_HANDLING, true);
256+
return Future.succeededFuture(proxyResponse);
257+
}
234258
}
235259

236260
});
@@ -240,11 +264,9 @@ private Future<ProxyResponse> tryHandleProxyRequestFromCache(ProxyContext contex
240264

241265
private static boolean checkVaryHeaders(MultiMap requestHeaders, MultiMap varyHeaders) {
242266
for (Map.Entry<String, String> e: varyHeaders) {
243-
String fromVary = e.getValue().toLowerCase();
267+
String fromVary = e.getValue();
244268
String fromRequest = requestHeaders.get(e.getKey());
245-
if (fromRequest == null) return false;
246-
fromRequest = fromVary.toLowerCase();
247-
if (!fromRequest.equals(fromVary)) return false;
269+
if (fromRequest == null || !fromRequest.equals(fromVary)) return false;
248270
}
249271
return true;
250272
}

src/main/java/io/vertx/httpproxy/impl/ProxiedResponse.java

+17-19
Original file line numberDiff line numberDiff line change
@@ -75,25 +75,23 @@ class ProxiedResponse implements ProxyResponse {
7575
long maxAge = -1;
7676
boolean publicCacheControl = false;
7777
String cacheControlHeader = response.getHeader(HttpHeaders.CACHE_CONTROL);
78-
if (cacheControlHeader != null) {
79-
CacheControl cacheControl = new CacheControl().parse(cacheControlHeader);
80-
if (cacheControl.isPublic()) {
81-
publicCacheControl = true;
82-
if (cacheControl.sMaxage() >= 0) {
83-
maxAge = (long) cacheControl.sMaxage() * 1000;
84-
} else if (cacheControl.maxAge() >= 0) {
85-
maxAge = (long) cacheControl.maxAge() * 1000;
86-
} else {
87-
String dateHeader = response.getHeader(HttpHeaders.DATE);
88-
String expiresHeader = response.getHeader(HttpHeaders.EXPIRES);
89-
if (dateHeader != null) {
90-
if (expiresHeader != null) {
91-
maxAge = Math.max(0, ParseUtils.parseHeaderDate(expiresHeader).toEpochMilli() - ParseUtils.parseHeaderDate(dateHeader).toEpochMilli());
92-
} else if (heuristicallyCacheable(response)) {
93-
String lastModifiedHeader = response.getHeader(HttpHeaders.LAST_MODIFIED);
94-
maxAge = Math.max(0, (ParseUtils.parseHeaderDate(lastModifiedHeader).toEpochMilli() - ParseUtils.parseHeaderDate(dateHeader).toEpochMilli()) / 10);
95-
}
96-
}
78+
CacheControl cacheControl = cacheControlHeader == null ? null : new CacheControl().parse(cacheControlHeader);
79+
if (cacheControl != null && cacheControl.isPublic()) {
80+
publicCacheControl = true;
81+
}
82+
if (cacheControl != null && cacheControl.sMaxage() >= 0) {
83+
maxAge = (long) cacheControl.sMaxage() * 1000;
84+
} else if (cacheControl != null && cacheControl.maxAge() >= 0) {
85+
maxAge = (long) cacheControl.maxAge() * 1000;
86+
} else {
87+
String dateHeader = response.getHeader(HttpHeaders.DATE);
88+
String expiresHeader = response.getHeader(HttpHeaders.EXPIRES);
89+
if (dateHeader != null) {
90+
if (expiresHeader != null) {
91+
maxAge = Math.max(0, ParseUtils.parseHeaderDate(expiresHeader).toEpochMilli() - ParseUtils.parseHeaderDate(dateHeader).toEpochMilli());
92+
} else if (heuristicallyCacheable(response)) {
93+
String lastModifiedHeader = response.getHeader(HttpHeaders.LAST_MODIFIED);
94+
maxAge = Math.max(0, (ParseUtils.parseHeaderDate(lastModifiedHeader).toEpochMilli() - ParseUtils.parseHeaderDate(dateHeader).toEpochMilli()) / 10);
9795
}
9896
}
9997
}

src/main/java/module-info.java

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
module io.vertx.httpproxy {
2+
requires io.netty.codec.http;
23
requires transitive io.vertx.core;
34
requires io.vertx.core.logging;
45
requires static io.vertx.codegen.api;

0 commit comments

Comments
 (0)