Solve the problem of FeignClient sending post request exception
- 2021-10-24 23:02:58
- OfStack
FeignClient send post request exception
This question is actually very basic. But it stumped me. Record 1
Specify the message format when sending post requests
The correct way to write it is like this
@PostMapping(value = "/test/post", consumes = "application/json")
String test(@RequestBody String name);
Invalid writing
@PostMapping(value = "/test/post", produces= "application/json")
About this distinction
produces
Its function is to specify the return value type, not only can set the return value type can also set the character code of the return value;
consumes
Specify the type of submission content to process the request (Content-Type), such as application/json, text/html;
The foundation is really important ~
Analysis and Treatment of Query Parameters Missing When FeignClient Calls POST Request
This article does not introduce the knowledge points of FeignClient in detail. There are many excellent articles on the Internet that introduce the knowledge points of FeignCient. I will not repeat them here, but focus on this problem.
Query parameter loss scenario
Service Description: The service system needs to update the A resource in the user system. Since only one field information of the A resource is updated to B, it is not selected to encapsulate B through entity, but directly transfer B information through query parameters
Text description: When using FeignClient to make a remote call, if there are query parameters in the POST request and there is no request entity (body is empty), then the query parameters are lost and the service provider cannot get the value of the query parameters.
Code description: The value of B is lost, and the service provider cannot get the value of B
@FeignClient(name = "a-service", configuration = FeignConfiguration.class)
public interface ACall {
@RequestMapping(method = RequestMethod.POST, value = "/api/xxx/{A}", headers = {"Content-Type=application/json"})
void updateAToB(@PathVariable("A") final String A, @RequestParam("B") final String B) throws Exception;
}
Problem analysis
Background Using the FeignClient client Use ApacheHttpClient in feign-httpclient to make the actual request call
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>8.18.0</version>
</dependency>
Direct source code
Through reading the source code of FeignClient, it is found that the problem is not parsing the parameters, but when using ApacheHttpClient to request, it puts the query parameters into the request body. See how the source code is handled concretely below
feign. httpclient. ApacheHttpClient This is how feign-httpclient makes the actual request
@Override
public Response execute(Request request, Request.Options options) throws IOException {
HttpUriRequest httpUriRequest;
try {
httpUriRequest = toHttpUriRequest(request, options);
} catch (URISyntaxException e) {
throw new IOException("URL '" + request.url() + "' couldn't be parsed into a URI", e);
}
HttpResponse httpResponse = client.execute(httpUriRequest);
return toFeignResponse(httpResponse);
}
HttpUriRequest toHttpUriRequest(Request request, Request.Options options) throws
UnsupportedEncodingException, MalformedURLException, URISyntaxException {
RequestBuilder requestBuilder = RequestBuilder.create(request.method());
//per request timeouts
RequestConfig requestConfig = RequestConfig
.custom()
.setConnectTimeout(options.connectTimeoutMillis())
.setSocketTimeout(options.readTimeoutMillis())
.build();
requestBuilder.setConfig(requestConfig);
URI uri = new URIBuilder(request.url()).build();
requestBuilder.setUri(uri.getScheme() + "://" + uri.getAuthority() + uri.getRawPath());
//request query params
List<NameValuePair> queryParams = URLEncodedUtils.parse(uri, requestBuilder.getCharset().name());
for (NameValuePair queryParam: queryParams) {
requestBuilder.addParameter(queryParam);
}
//request headers
boolean hasAcceptHeader = false;
for (Map.Entry<String, Collection<String>> headerEntry : request.headers().entrySet()) {
String headerName = headerEntry.getKey();
if (headerName.equalsIgnoreCase(ACCEPT_HEADER_NAME)) {
hasAcceptHeader = true;
}
if (headerName.equalsIgnoreCase(Util.CONTENT_LENGTH)) {
// The 'Content-Length' header is always set by the Apache client and it
// doesn't like us to set it as well.
continue;
}
for (String headerValue : headerEntry.getValue()) {
requestBuilder.addHeader(headerName, headerValue);
}
}
//some servers choke on the default accept string, so we'll set it to anything
if (!hasAcceptHeader) {
requestBuilder.addHeader(ACCEPT_HEADER_NAME, "*/*");
}
//request body
if (request.body() != null) {
//body Is empty, then HttpEntity Empty
HttpEntity entity = null;
if (request.charset() != null) {
ContentType contentType = getContentType(request);
String content = new String(request.body(), request.charset());
entity = new StringEntity(content, contentType);
} else {
entity = new ByteArrayEntity(request.body());
}
requestBuilder.setEntity(entity);
}
// Call org.apache.http.client.methods.RequestBuilder#build Method
return requestBuilder.build();
}
org. apache. http. client. methods. RequestBuilder This class is the Builder class of HttpUriRequest. See the build method below
public HttpUriRequest build() {
final HttpRequestBase result;
URI uriNotNull = this.uri != null ? this.uri : URI.create("/");
HttpEntity entityCopy = this.entity;
if (parameters != null && !parameters.isEmpty()) {
// Here: If HttpEntity Is empty and is POST Request or for PUT When requested, this method will take out the query parameters and encapsulate them as HttpEntity
// It is here that the query parameters are discarded, to be exact, the location is converted
if (entityCopy == null && (HttpPost.METHOD_NAME.equalsIgnoreCase(method)
|| HttpPut.METHOD_NAME.equalsIgnoreCase(method))) {
entityCopy = new UrlEncodedFormEntity(parameters, charset != null ? charset : HTTP.DEF_CONTENT_CHARSET);
} else {
try {
uriNotNull = new URIBuilder(uriNotNull)
.setCharset(this.charset)
.addParameters(parameters)
.build();
} catch (final URISyntaxException ex) {
// should never happen
}
}
}
if (entityCopy == null) {
result = new InternalRequest(method);
} else {
final InternalEntityEclosingRequest request = new InternalEntityEclosingRequest(method);
request.setEntity(entityCopy);
result = request;
}
result.setProtocolVersion(this.version);
result.setURI(uriNotNull);
if (this.headergroup != null) {
result.setHeaders(this.headergroup.getAllHeaders());
}
result.setConfig(this.config);
return result;
}
Solutions
Now that you know the reason, there are many solutions. Here are some general solutions:
Use feign-okhttp to request calls, here does not column source code, interested in you can see, feign-okhttp bottom did not judge if body is empty then put the query parameters into body. Using io. github. openfeign: feign-httpclient: 9.5. 1 dependencies, the reasons for intercepting some source codes are as follows:
HttpUriRequest toHttpUriRequest(Request request, Request.Options options) throws
UnsupportedEncodingException, MalformedURLException, URISyntaxException {
RequestBuilder requestBuilder = RequestBuilder.create(request.method());
// Omit part of the code
//request body
if (request.body() != null) {
// Omit part of the code
} else {
// Here, if the null Will be stuffed into the 1 A byte Array is 0 Object of
requestBuilder.setEntity(new ByteArrayEntity(new byte[0]));
}
return requestBuilder.build();
}
Recommended dependency
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>9.5.1</version>
</dependency>
Or
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>9.5.1</version>
</dependency>
Summarize
At present, most articles on feign are recommended com. netflix. feign: feign-httpclient: 8.18. 0 and com. netflix. feign: feign-okhttp: 8.18. 0. If you happen to use com. netflix. feign: feign-httpclient: 8.18. 0, the problem of missing query parameters will occur when POST requests and body is empty.
It is recommended that you do not rely on com. netflix. feign when using feign-httpclient or feign-okhttp, but choose io. github. openfeign, because it seems that Netflix has not maintained these two components for a long time, but OpenFeign has maintained them.