使用Spring的RestTemplate发送GET请求,并支持传递Request body参数

目录


最近在使用Spring Boot实现微服务,都是使用RESTful风格的Api接口,服务间使用RestTemplate来进行HTTP通信,遇到这样一个需求:开发一个查询请求Api,参数使用JSON格式的字符串来提交。

请求格式

希望的请求格式如下:

GET /pointCard/ HTTP/1.1
Host: localhost:8100
Content-Type: application/json;charset=UTF-8
Content-Length: 114

{"iColumns":7,"iDisplayLength":10,"iDisplayStart":0,"iSortingCols":0,"sColumns":"","sEcho":1,"subjectId":"11227"}

RESTful下,这样的设计是合理的,GET请求表示从服务器获取资源,但需要将查询参数以JSON格式来提交。但是,这违背了传统的GET请求的规范,我们都知道,GET请求只能将请求参数拼接URI后边,而不能单独传递request body参数,除非你改用POST

代码实现

我们先来编一个上述请求的API,然后进行测试。

1、编写一个API:

@GetMapping(value = "/")
public Response getById(@RequestBody @Valid PointCardQuery query) throws Exception {
    Assert.notNull(query,"查询条件不能为空!");
    ……
    return Response.success(pointCardPurePager, "积分卡获取成功!");
}

上边的代码片段处于一个Restcontroller,要求使用GET方法,并且使用了@RequestBody注解来获取request body参数。

2、我们使用RestTemplate来测试一下:

@Test
public void testGetWithBody() {
    RestTemplate restTemplate = new RestTemplate();
    String p = "{\"iColumns\":7,\"iDisplayLength\":10,\"iDisplayStart\":0,\"iSortingCols\":0,\"sColumns\":\"\",\"sEcho\":1,\"subjectId\":\"11227\"}";
    String url = "http://localhost:8100/pointCard/";
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
    HttpEntity<String> httpEntity = new HttpEntity<>(p, headers);
    ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
    String body = responseEntity.getBody();
    System.out.println(body);
    System.out.println(responseEntity.getStatusCode());
    System.out.println(responseEntity.getStatusCodeValue());
    System.out.println(responseEntity);
}

运行测试代码,发现请求直接400错误:

org.springframework.web.client.HttpClientErrorException: 400 null

	at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:85)
	at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:708)
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:661)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:621)
	at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:539)
	at cn.bookingsmart.sp.api.PointCardApiTest1.test1(PointCardApiTest1.java:81)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

Why?问题出在服务端还是测试代码呢?

查阅了大量资料,大部分都是说GET请求不能传递Request body,对于RESTful而言,这显然是不合理的。

记得原来开发ElasticSearch的时候,很多API都是这样的形式:

GET /_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "title":          "quick brown fox" }},
        { "match": { "title.original": "quick brown fox" }},
        { "match": { "title.shingles": "quick brown fox" }}
      ]
    }
  }
}

当时还是用了curl工具来测试其API,何不试试?

3、使用curl测试:

 ~> curl -XGET -k "http://localhost:8100/pointCard/" \
    --include \
    -H "Accept: application/json" \
    -H "Content-Type: application/json" \
    --data '{"iColumns":7,"iDisplayLength":10,"iDisplayStart":0,"iSortingCols":0,"sColumns":"","sEcho":1,"subjectId":"11227"}'

出人意料,curl可以正常工作。看来,问题出在测试代码的RestTemplate上。

解决方案

继续查询资料,一篇Meik Kaufmann的文章解决了我的问题,地址在文末,他遇到的问题跟我所遇到的非常相似,非常感谢!


前一篇:Spring Boot参数验证(下)——Bean Validation在Web中的应用

belonk

轻轻地我走了,正如我轻轻地来,我挥一挥衣袖,不带走一片云彩