您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“如何解決FeignClient發(fā)送post請求異常的問題”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
這個(gè)問題其實(shí)很基礎(chǔ)。但是卻難倒了我。記錄一下
在發(fā)送post請求的時(shí)候要指定消息格式
@PostMapping(value = "/test/post", consumes = "application/json") String test(@RequestBody String name);
@PostMapping(value = "/test/post", produces= "application/json")
produces
:它的作用是指定返回值類型,不但可以設(shè)置返回值類型還可以設(shè)定返回值的字符編碼;
consumes
:指定處理請求的提交內(nèi)容類型(Content-Type),例如application/json, text/html;
基礎(chǔ)真的很重要啊~
本文沒有詳細(xì)介紹 FeignClient 的知識點(diǎn),網(wǎng)上有很多優(yōu)秀的文章介紹了 FeignCient 的知識點(diǎn),在這里本人就不重復(fù)了,只是專注在這個(gè)問題點(diǎn)上。
業(yè)務(wù)描述: 業(yè)務(wù)系統(tǒng)需要更新用戶系統(tǒng)中的A資源,由于只想更新A資源的一個(gè)字段信息為B,所以沒有選擇通過 entity 封裝B,而是直接通過查詢參數(shù)來傳遞B信息
文字描述:使用FeignClient來進(jìn)行遠(yuǎn)程調(diào)用時(shí),如果POST請求中有查詢參數(shù)并且沒有請求實(shí)體(body為空),那么查詢參數(shù)被丟失,服務(wù)提供者獲取不到查詢參數(shù)的值。
代碼描述:B的值被丟失,服務(wù)提供者獲取不到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; }
使用 FeignClient 客戶端
使用 feign-httpclient 中的 ApacheHttpClient 來進(jìn)行實(shí)際請求的調(diào)用
<dependency> <groupId>com.netflix.feign</groupId> <artifactId>feign-httpclient</artifactId> <version>8.18.0</version> </dependency>
通過對 FeignClient 的源碼閱讀,發(fā)現(xiàn)問題不是出在參數(shù)解析上,而是在使用 ApacheHttpClient 進(jìn)行請求時(shí),其將查詢參數(shù)放進(jìn)請求body中了,下面看源碼具體是如何處理的
feign.httpclient.ApacheHttpClient 這是 feign-httpclient 進(jìn)行實(shí)際請求的方法
@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為空,則HttpEntity為空 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); } //調(diào)用org.apache.http.client.methods.RequestBuilder#build方法 return requestBuilder.build(); }
org.apache.http.client.methods.RequestBuilder 此類是 HttpUriRequest 的Builder類,下面看build方法
public HttpUriRequest build() { final HttpRequestBase result; URI uriNotNull = this.uri != null ? this.uri : URI.create("/"); HttpEntity entityCopy = this.entity; if (parameters != null && !parameters.isEmpty()) { // 這里:如果HttpEntity為空,并且為POST請求或者為PUT請求時(shí),這個(gè)方法會(huì)將查詢參數(shù)取出來封裝成了HttpEntity // 就是在這里查詢參數(shù)被丟棄了,準(zhǔn)確的說是被轉(zhuǎn)換位置了 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; }
既然已經(jīng)知道原因了,那么解決方法就有很多種了,下面就介紹常規(guī)的解決方案:
使用 feign-okhttp 來進(jìn)行請求調(diào)用,這里就不列源碼了,感興趣大家可以去看, feign-okhttp 底層沒有判斷如果body為空則把查詢參數(shù)放入body中。
使用 io.github.openfeign:feign-httpclient:9.5.1 依賴,截取部分源碼說明原因如下:
HttpUriRequest toHttpUriRequest(Request request, Request.Options options) throws UnsupportedEncodingException, MalformedURLException, URISyntaxException { RequestBuilder requestBuilder = RequestBuilder.create(request.method()); //省略部分代碼 //request body if (request.body() != null) { //省略部分代碼 } else { // 此處,如果為null,則會(huì)塞入一個(gè)byte數(shù)組為0的對象 requestBuilder.setEntity(new ByteArrayEntity(new byte[0])); } return requestBuilder.build(); }
推薦的依賴
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> <version>9.5.1</version> </dependency>
或者
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> <version>9.5.1</version> </dependency>
目前絕大部分的介紹 feign 的文章都是推薦的 com.netflix.feign:feign-httpclient:8.18.0 和 com.netflix.feign:feign-okhttp:8.18.0 ,如果不巧你使用了 com.netflix.feign:feign-httpclient:8.18.0,那么在POST請求時(shí)并且body為空時(shí)就會(huì)發(fā)生丟失查詢參數(shù)的問題。
這里推薦大家使用 feign-httpclient 或者是 feign-okhttp的時(shí)候不要依賴 com.netflix.feign,而應(yīng)該選擇 io.github.openfeign,因?yàn)榭雌饋?Netflix 很久沒有對這兩個(gè)組件進(jìn)行維護(hù)了,而是由 OpenFeign 來進(jìn)行維護(hù)了。
“如何解決FeignClient發(fā)送post請求異常的問題”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。