解决HTTP GET方法调用带有body问题

1.背景描述

         上游服务提供的方法非常比较奇特,查询接口,定义的GET方法,参数通过request body传递的,在使用Feign Client封装GET方法调用时,会遇到一个报错,“405 Method Not Allowed”。通过查询,知道这个错误原因是HTTP调用方法错误,比如:定义的API是GET方法,通过POST方法(非GET方法)调用,就会返回这个错误。

          @RequestLine("GET /api/user/get/")

          Object getUser(@HeaderMap Map headers, UserRequest request);

2.原因分析

        奇怪代码明明写得是使用GET方法啊,进一步查资料,得知原因是Feign client框架本身有一个坑:Feign client框架,默认情况下使用的是HttpURLConnection完成实际的http请求调用,但是HttpURLConnection本身不支持GET方法调用时带有body,带有body的调用方法,只能是POST方法。

// sun.net.www.protocol.http.HttpURLConnection

private synchronized OutputStream getOutputStream0() throws IOException {

try {

if(!this.doOutput) {

throw new ProtocolException("cannot write to a URLConnection if doOutput=false - call setDoOutput(true)");

} else {

if(this.method.equals("GET")) {

this.method = "POST";

}

// ........

}

}

}

HTTP GET方法调用,到底支不支持带有body呢,HTTP协议是支持的,没有禁止,但是呢,不建议这么做,不是一个良好的习惯,因为有些浏览器

啥的可能不支持,这个时候,你写的方法就尴尬了。

Stackoverflow解释如下:

     In other words, any HTTP request message is allowed to contain a message body, and thus must parse messages with that in mind. Server semantics for GET, however,

are restricted such that a body, if any, has no semantic meaning to the request. The requirements on parsing are separate from the requirements on method semantics.

So, yes, you can send a body with GET, and no, it is never useful to do so.

This is part of the layered design of HTTP/1.1 that will become clear again once the spec is partitioned (work in progress).

3.解决方法

方案一:

         网上可以很容易搜索到这个解决方法,相关博客非常多,直接copy的情况,太严重了。但是实际验证,没有生效,具体原因待排查。

         1.yml配置文件中,加入feign的配置项:feign.httpclient.enabled: true

         2.增加如下maven依赖。

            <dependency>

                <groupId>org.apache.httpcomponents</groupId>                   

                <artifactId>httpclient</artifactId>                   

                <version>4.5.3</version>             

            </dependency>

            <dependency>

                <groupId>com.netflix.feign</groupId>

                <artifactId>feign-httpclient</artifactId>

                <version>8.17.0</version>

            </dependency>

方案原理:HttpURLConnection不支持GET方法带有body的调用,ApacheHttpClient支持GET方法带有body的调用。这个配置,就是将feign client默认使用的HTTP调用方式,从HttpURLConnection切换到ApacheHttpClient方式。

方法不生效原因:

1.可能HTTP调用方式没有切换成功,也就是配置没有生效。(确定是这个原因,因为我使用的Feign方式:Feign.builder()默认生成的就是HttpURLConnection方式的http请求调用。相关源码如下:

  // feign.Feign.Builder

private Client client = new Client.Default(null, null);

// feign.Client.Default

final HttpURLConnection connection = (HttpURLConnection) new URL(request.url()).openConnection();

因此相关配置修改是不生效的,需要重新生成一个client才行,比如:ApacheHttpClient

  2.可能ApacheHttpClient本身也不支持GET方法带有body的请求。(因为直接使用ApacheHttpClient,发现没有支持GET方法带有body的调用方式)

补充:(待验证,证明)

         feign分别尝试了Java原生URLConnection,OkHttp,ApacheHttpClient三种方式:

             1.URLConnection 报405错误,说明http方法不对,但是feign配置是GET方法,查feign的日志也是用的GET方法。后来发现原因是URLConnection在的

原因:对于有request body的GET方法,自动改为POST方法了。

             2.OkHttp 直接报错:method GET must not have a request body.

             3.ApacheHttpClient完美支持。

方案二:

        使用AsyncHttpClient,因为AsyncHttpClient支持GET方法带有Body的调用。

        网上也可以很容易搜索到这个解决方法,感觉都是复制粘贴的,没有经过验证和实证,内容完全一样,但是都缺少最关键的信息,没有给出需要引用的jar包,怎么使用测试呢?而需要引用的jar包还不好找到,实在是大坑。

         1.引入maven依赖

              <dependency>

                    <groupId>org.asynchttpclient</groupId>

                    <artifactId>async-http-client</artifactId>

                    <version>2.2.0</version>

             </dependency>

         2.解决方法demo

         public static String get(String url, String bodyData, Map<String, String> headers) throws Exception {

                // 构建请求

                BoundRequestBuilder requestBuilder = asyncHttpClient.prepareGet(url).setBody(bodyData);

                headers.forEach(requestBuilder::addHeader);

                List<Response> list = new ArrayList<>();

                requestBuilder.execute()

                        .toCompletableFuture()

                        .thenAccept(list::add)

                        .join();

                if (list.isEmpty()) {

                    return null;

                }

                Response response = list.get(0);

                if (response.getStatusCode() != 200) {

                    return null;

                }

                return response.getResponseBody();

         }

备注1:

         1.方法可以返回map,增加:new ObjectMapper().readValue(response.getResponseBody(), Map.class);

         2.方法本身必须返回json 对象的string才行,不能是非json对象的string,否则解析异常。

备注2:

          1.没有body的get方法,去掉.setBody(bodyData)即可。

          2.没有header的get方法调用,去掉headers.forEach(requestBuilder::addHeader);即可。

4.总结

         网上资源很多、很丰富,各种问题解决方案很多,但是也存在很多缺陷,不去验证、实践,根本不知道里面有问题,因此不要随便copy别人的博客,往往copy的博客本身就存在潜在的问题,copy之前,请试验一下,证明方法是正确的,减少给需要同学的误导。

5.参考资料

1.https://yanbin.blog/why-http-get-cannot-sent-data-with-reuqest-body/http://www.programmersought.com/article/752346261/

2.https://blog.csdn.net/f641385712/article/details/82431502

3.https://segmentfault.com/q/1010000011958034

4.https://stackoverflow.com/questions/978061/http-get-with-request-body

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352