Skip to content

Commit f5d1a17

Browse files
authored
Merge branch 'master' into fix-http2-response-timeout
2 parents c294527 + aecd01a commit f5d1a17

48 files changed

Lines changed: 1155 additions & 130 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
### Version 13.14
2+
3+
* Add support for the HTTP QUERY method (RFC 10008) — safe, idempotent, and cacheable with a
4+
request body. `HttpCacheInterceptor` includes QUERY in its default cacheable set and
5+
incorporates a body hash into the cache key to reduce cross-body collisions.
6+
17
### Version 13.12
28

39
* `UrlencodedFormContentProcessor` now honors `CollectionFormat` from `@RequestLine`/`RequestTemplate` for array and

benchmark/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<jmh.version>1.37</jmh.version>
3333
<rx.netty.version>0.5.3</rx.netty.version>
3434
<rx.java.version>1.3.8</rx.java.version>
35-
<netty.version>4.2.15.Final</netty.version>
35+
<netty.version>4.2.16.Final</netty.version>
3636
<moditect.skip>true</moditect.skip>
3737
</properties>
3838

core/src/main/java/feign/BaseBuilder.java

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@
4040

4141
public abstract class BaseBuilder<B extends BaseBuilder<B, T>, T> implements Cloneable {
4242

43-
private final B thisB;
44-
4543
protected List<RequestInterceptor> requestInterceptors = new ArrayList<>();
4644
protected List<ResponseInterceptor> responseInterceptors = new ArrayList<>();
4745
protected List<MethodInterceptor> methodInterceptors = new ArrayList<>();
@@ -64,43 +62,47 @@ public abstract class BaseBuilder<B extends BaseBuilder<B, T>, T> implements Clo
6462

6563
public BaseBuilder() {
6664
super();
67-
thisB = (B) this;
65+
}
66+
67+
@SuppressWarnings("unchecked")
68+
private B thisB() {
69+
return (B) this;
6870
}
6971

7072
public B logLevel(Logger.Level logLevel) {
7173
this.logLevel = logLevel;
72-
return thisB;
74+
return thisB();
7375
}
7476

7577
public B contract(Contract contract) {
7678
this.contract = contract;
77-
return thisB;
79+
return thisB();
7880
}
7981

8082
public B retryer(Retryer retryer) {
8183
this.retryer = retryer;
82-
return thisB;
84+
return thisB();
8385
}
8486

8587
public B logger(Logger logger) {
8688
this.logger = logger;
87-
return thisB;
89+
return thisB();
8890
}
8991

9092
public B encoder(Encoder encoder) {
9193
this.encoder = encoder;
92-
return thisB;
94+
return thisB();
9395
}
9496

9597
public B decoder(Decoder decoder) {
9698
this.decoder = decoder;
97-
return thisB;
99+
return thisB();
98100
}
99101

100102
public B codec(Codec codec) {
101103
this.encoder = codec.encoder();
102104
this.decoder = codec.decoder();
103-
return thisB;
105+
return thisB();
104106
}
105107

106108
/**
@@ -115,23 +117,23 @@ public B codec(Codec codec) {
115117
*/
116118
public B doNotCloseAfterDecode() {
117119
this.closeAfterDecode = false;
118-
return thisB;
120+
return thisB();
119121
}
120122

121123
public B decodeVoid() {
122124
this.decodeVoid = true;
123-
return thisB;
125+
return thisB();
124126
}
125127

126128
public B queryMapEncoder(QueryMapEncoder queryMapEncoder) {
127129
this.queryMapEncoder = queryMapEncoder;
128-
return thisB;
130+
return thisB();
129131
}
130132

131133
/** Allows to map the response before passing it to the decoder. */
132134
public B mapAndDecode(ResponseMapper mapper, Decoder decoder) {
133135
this.decoder = new ResponseMappingDecoder(mapper, decoder);
134-
return thisB;
136+
return thisB();
135137
}
136138

137139
/**
@@ -151,7 +153,7 @@ public B mapAndDecode(ResponseMapper mapper, Decoder decoder) {
151153
*/
152154
public B dismiss404() {
153155
this.dismiss404 = true;
154-
return thisB;
156+
return thisB();
155157
}
156158

157159
/**
@@ -173,23 +175,23 @@ public B dismiss404() {
173175
@Deprecated
174176
public B decode404() {
175177
this.dismiss404 = true;
176-
return thisB;
178+
return thisB();
177179
}
178180

179181
public B errorDecoder(ErrorDecoder errorDecoder) {
180182
this.errorDecoder = errorDecoder;
181-
return thisB;
183+
return thisB();
182184
}
183185

184186
public B options(Options options) {
185187
this.options = options;
186-
return thisB;
188+
return thisB();
187189
}
188190

189191
/** Adds a single request interceptor to the builder. */
190192
public B requestInterceptor(RequestInterceptor requestInterceptor) {
191193
this.requestInterceptors.add(requestInterceptor);
192-
return thisB;
194+
return thisB();
193195
}
194196

195197
/**
@@ -201,7 +203,7 @@ public B requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) {
201203
for (RequestInterceptor requestInterceptor : requestInterceptors) {
202204
this.requestInterceptors.add(requestInterceptor);
203205
}
204-
return thisB;
206+
return thisB();
205207
}
206208

207209
/**
@@ -213,13 +215,13 @@ public B responseInterceptors(Iterable<ResponseInterceptor> responseInterceptors
213215
for (ResponseInterceptor responseInterceptor : responseInterceptors) {
214216
this.responseInterceptors.add(responseInterceptor);
215217
}
216-
return thisB;
218+
return thisB();
217219
}
218220

219221
/** Adds a single response interceptor to the builder. */
220222
public B responseInterceptor(ResponseInterceptor responseInterceptor) {
221223
this.responseInterceptors.add(responseInterceptor);
222-
return thisB;
224+
return thisB();
223225
}
224226

225227
/**
@@ -230,7 +232,7 @@ public B responseInterceptor(ResponseInterceptor responseInterceptor) {
230232
@Experimental
231233
public B methodInterceptor(MethodInterceptor methodInterceptor) {
232234
this.methodInterceptors.add(methodInterceptor);
233-
return thisB;
235+
return thisB();
234236
}
235237

236238
/** Sets the full set of method interceptors, overwriting any previously configured. */
@@ -240,33 +242,33 @@ public B methodInterceptors(Iterable<MethodInterceptor> methodInterceptors) {
240242
for (MethodInterceptor methodInterceptor : methodInterceptors) {
241243
this.methodInterceptors.add(methodInterceptor);
242244
}
243-
return thisB;
245+
return thisB();
244246
}
245247

246248
/** Allows you to override how reflective dispatch works inside of Feign. */
247249
public B invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
248250
this.invocationHandlerFactory = invocationHandlerFactory;
249-
return thisB;
251+
return thisB();
250252
}
251253

252254
public B exceptionPropagationPolicy(ExceptionPropagationPolicy propagationPolicy) {
253255
this.propagationPolicy = propagationPolicy;
254-
return thisB;
256+
return thisB();
255257
}
256258

257259
public B addCapability(Capability capability) {
258260
this.capabilities.add(capability);
259-
return thisB;
261+
return thisB();
260262
}
261263

262264
@SuppressWarnings("unchecked")
263265
B enrich() {
264266
if (capabilities.isEmpty()) {
265-
return thisB;
267+
return thisB();
266268
}
267269

268270
try {
269-
B clone = (B) thisB.clone();
271+
B clone = (B) thisB().clone();
270272

271273
getFieldsToEnrich()
272274
.forEach(
@@ -316,10 +318,21 @@ B enrich() {
316318
// enrich each response interceptor, then enrich the list as a whole
317319
ResponseInterceptor[] responseArray =
318320
clone.responseInterceptors.toArray(new ResponseInterceptor[0]);
319-
for (int i = 0; i < responseArray.length; i++) {
320-
responseArray[i] =
321+
if (responseArray.length == 0) {
322+
ResponseInterceptor defaultResponseInterceptor = (context, chain) -> chain.next(context);
323+
ResponseInterceptor enrichedResponseInterceptor =
321324
(ResponseInterceptor)
322-
Capability.enrich(responseArray[i], ResponseInterceptor.class, capabilities);
325+
Capability.enrich(
326+
defaultResponseInterceptor, ResponseInterceptor.class, capabilities);
327+
if (enrichedResponseInterceptor != defaultResponseInterceptor) {
328+
responseArray = new ResponseInterceptor[] {enrichedResponseInterceptor};
329+
}
330+
} else {
331+
for (int i = 0; i < responseArray.length; i++) {
332+
responseArray[i] =
333+
(ResponseInterceptor)
334+
Capability.enrich(responseArray[i], ResponseInterceptor.class, capabilities);
335+
}
323336
}
324337
ResponseInterceptors responseInterceptors =
325338
(ResponseInterceptors)
@@ -356,8 +369,6 @@ List<Field> getFieldsToEnrich() {
356369
.filter(field -> !field.isSynthetic())
357370
// and capabilities itself
358371
.filter(field -> !Objects.equals(field.getName(), "capabilities"))
359-
// and thisB helper field
360-
.filter(field -> !Objects.equals(field.getName(), "thisB"))
361372
// interceptor lists are enriched per-element then as a whole via custom types
362373
.filter(field -> !Objects.equals(field.getName(), "requestInterceptors"))
363374
.filter(field -> !Objects.equals(field.getName(), "responseInterceptors"))

core/src/main/java/feign/DefaultClient.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ Response convertResponse(HttpURLConnection connection, Request request) throws I
111111
}
112112

113113
Integer length = connection.getContentLength();
114-
if (length == -1) {
114+
if (length < 0) {
115+
// -1 signals unknown or above Integer.MAX_VALUE; any other negative value is a malformed
116+
// header that HttpURLConnection surfaces verbatim
115117
length = null;
116118
}
117119
InputStream stream;
@@ -122,8 +124,11 @@ Response convertResponse(HttpURLConnection connection, Request request) throws I
122124
}
123125
if (stream != null && this.isGzip(headers.get(CONTENT_ENCODING))) {
124126
stream = new GZIPInputStream(stream);
127+
// the body is now decompressed, the Content-Length described the compressed bytes
128+
length = null;
125129
} else if (stream != null && this.isDeflate(headers.get(CONTENT_ENCODING))) {
126130
stream = new InflaterInputStream(stream);
131+
length = null;
127132
}
128133
return Response.builder()
129134
.status(status)
@@ -170,7 +175,6 @@ HttpURLConnection convertAndSend(Request request, Options options) throws IOExce
170175
if (field.equals(CONTENT_LENGTH)) {
171176
if (!gzipEncodedRequest && !deflateEncodedRequest) {
172177
contentLength = Integer.valueOf(value);
173-
connection.addRequestProperty(field, value);
174178
}
175179
}
176180
// Avoid add "Accept-encoding" twice or more when "compression" option is enabled
@@ -189,7 +193,7 @@ else if (field.equals(ACCEPT_ENCODING)) {
189193

190194
byte[] body = request.body();
191195

192-
if (body != null) {
196+
if (body != null && (body.length > 0 || request.httpMethod() != Request.HttpMethod.GET)) {
193197
/*
194198
* Ignore disableRequestBuffering flag if the empty body was set, to ensure that internal
195199
* retry logic applies to such requests.

core/src/main/java/feign/Logger.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,14 @@
2121
import static feign.Util.valuesOrEmpty;
2222
import static java.util.Objects.nonNull;
2323

24+
import java.io.Closeable;
2425
import java.io.IOException;
2526
import java.io.PrintWriter;
2627
import java.io.StringWriter;
28+
import java.util.ArrayList;
29+
import java.util.List;
2730
import java.util.logging.FileHandler;
31+
import java.util.logging.Handler;
2832
import java.util.logging.LogRecord;
2933
import java.util.logging.SimpleFormatter;
3034

@@ -182,9 +186,10 @@ protected void log(String configKey, String format, Object... args) {
182186
}
183187

184188
/** Logs to the category {@link Logger} at {@link java.util.logging.Level#FINE}, if loggable. */
185-
public static class JavaLogger extends Logger {
189+
public static class JavaLogger extends Logger implements Closeable {
186190

187191
final java.util.logging.Logger logger;
192+
private final List<Handler> handlers = new ArrayList<>();
188193

189194
/**
190195
* @deprecated Use {@link #JavaLogger(String)} or {@link #JavaLogger(Class)} instead.
@@ -256,11 +261,21 @@ public String format(LogRecord record) {
256261
}
257262
});
258263
logger.addHandler(handler);
264+
handlers.add(handler);
259265
} catch (IOException e) {
260266
throw new IllegalStateException("Could not add file handler.", e);
261267
}
262268
return this;
263269
}
270+
271+
@Override
272+
public void close() {
273+
for (Handler handler : handlers) {
274+
logger.removeHandler(handler);
275+
handler.close();
276+
}
277+
handlers.clear();
278+
}
264279
}
265280

266281
public static class NoOpLogger extends Logger {

core/src/main/java/feign/Request.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ public enum HttpMethod {
4444
CONNECT,
4545
OPTIONS,
4646
TRACE,
47-
PATCH(true);
47+
PATCH(true),
48+
QUERY(true);
4849

4950
private final boolean withBody;
5051

@@ -554,7 +555,7 @@ public boolean isBinary() {
554555
}
555556

556557
public static Body create(String data) {
557-
return new Body(data.getBytes());
558+
return create(data, Util.UTF_8);
558559
}
559560

560561
public static Body create(String data, Charset charset) {

core/src/main/java/feign/RequestLine.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@
3535
*
3636
* <p>The string must begin with a valid {@linkplain feign.Request.HttpMethod HTTP method name}
3737
* (e.g. {@linkplain feign.Request.HttpMethod#GET GET}, {@linkplain feign.Request.HttpMethod#POST
38-
* POST}, {@linkplain feign.Request.HttpMethod#PUT PUT}), followed by a space and a URI template.
39-
* If only the HTTP method is specified (e.g. {@code "DELETE"}), the request will use the base URL
40-
* defined for the client.
38+
* POST}, {@linkplain feign.Request.HttpMethod#PUT PUT}, {@linkplain
39+
* feign.Request.HttpMethod#QUERY QUERY}), followed by a space and a URI template. If only the
40+
* HTTP method is specified (e.g. {@code "DELETE"}), the request will use the base URL defined for
41+
* the client.
4142
*
4243
* <p>Example:
4344
*

core/src/main/java/feign/codec/StringDecoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public Object decode(Response response, Type type) throws IOException {
3131
return null;
3232
}
3333
if (String.class.equals(type)) {
34-
return Util.toString(body.asReader(Util.UTF_8));
34+
return Util.toString(body.asReader(response.charset()));
3535
}
3636
throw new DecodeException(
3737
response.status(),

0 commit comments

Comments
 (0)