180815-Spring之RestTemplate中級使用篇

logo

Spring之RestTemplate中級使用篇

前面一篇介紹瞭如何使用RestTemplate發起post和get請求,然而也只能知足一些基本的場景,對於一些特殊的如須要設置請求頭,添加認證信息等場景,卻沒有說起能夠怎麼作,這一篇則至關於進階版,將主要介紹java

  • get/post請求如何攜帶 header
  • post傳文件能夠怎麼玩, post提交json串怎麼處理
  • exchange方法的使用姿式

<!-- more -->git

I. 請求頭設置

首先一個問題就是爲何要設置請求頭?github

咱們經過瀏覽器正常訪問的接口,可能經過代碼直接訪問時,就會提示403spring

而這樣的緣由,較多的一個可能就是後端的請求作了限制,好比根據請求的agent,判斷是否爲爬蟲;根據referer判斷是否要返回數據等等;然後端進行校驗的條件中,每每會拿請求頭的數據,所以這也就要求咱們在使用時,主動的塞入一些請求頭信息json

1. Get請求

直接看RestTemplate提供的幾個Get請求接口,並無發現有設置請求頭的地方,是否是就代表無法設置請求頭了?後端

答案檔案是能設置了,具體的使用思路有點相似mvc中的攔截器,自定義一個攔截器,而後在你實際發起請求時,攔截並設置request的請求頭瀏覽器

注意到 RestTemplate 的父類InterceptingHttpAccessor提供了一個接收Interceptor的接口org.springframework.http.client.support.InterceptingHttpAccessor#setInterceptors,這個就是咱們所須要的關鍵點(講道理,除非事先就知道有這麼個玩意,否則真讓你去找,還不必定能找到)安全

因此第一步就是寫一個ClientHttpRequestInterceptor類,添加請求頭網絡

public class UserAgentInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
            throws IOException {
        HttpHeaders headers = request.getHeaders();
        headers.add(HttpHeaders.USER_AGENT,
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36");
        return execution.execute(request, body);
    }
}

下一步就是在建立RestTemplate對象以後,聲明解釋器並測試使用了mvc

@Test
public void testGetHeader() {
    String url = "http://localhost:8080/agent?name=一灰灰Blog";
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setInterceptors(Collections.singletonList(new UserAgentInterceptor()));
    ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
    System.out.println(response.getStatusCode() + " | " + response.getBody());
}

首先在測試以前,先搭一個服務,簡單判斷agent,不知足條件的直接403, 後端mock代碼以下

@ResponseBody
@RequestMapping(path = "agent")
public String agent(HttpServletRequest request, HttpServletResponse response,
        @RequestParam(value = "name", required = false) String name) throws IOException {
    String agent = request.getHeader(HttpHeaders.USER_AGENT);
    if (StringUtils.isEmpty(agent) || !agent.contains("WebKit")) {
        response.sendError(403, " illegal agent ");
    }
    return "welcome " + name;
}

上面執行後輸出以下,添加請求頭後正常返回

C420EE9FB481154F53D442684F8A7B9A.jpg

固然也須要對比下不設置agent的狀況了,直接拋了一個異常出來了(說明,不顯示覆蓋User-Agent時,後端接收到的agent爲: Java/1.8.0_171

CA4B422728A6868A4366F7FE65F22BE6.jpg

上面雖然只給了設置User-Agent的例子,可是其餘的請求頭,都是能夠放在自定義的Interceptor中添加進去的

2. Post請求

固然get請求使用的這種姿式,對於post而言或者對於其餘的http請求方法而言,都是通用的,而對於post請求來講,還有另一種方式,就是requset參數,能夠攜帶request headers

首先mock一個後端接口

@ResponseBody
@RequestMapping(path = "post", method = {RequestMethod.GET, RequestMethod.OPTIONS, RequestMethod.POST},
        produces = "charset/utf8")
public String post(HttpServletRequest request, HttpServletResponse response,
        @RequestParam(value = "email", required = false) String email,
        @RequestParam(value = "nick", required = false) String nick) throws IOException {
    String agent = request.getHeader(HttpHeaders.USER_AGENT);
    if (StringUtils.isEmpty(agent) || !agent.contains("WebKit")) {
        response.sendError(403, " illegal agent ");
        return null;
    }
    return "success email=" + email + "&nick=" + URLEncoder.encode(nick, "UTF-8") + "&status=success";
}

簡單的使用姿式以下

@Test
public void testPostHeader() {
    String url = "http://localhost:8080/post";
    String email = "test@hhui.top";
    String nick = "一灰灰Blog";

    MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
    params.add("email", email);
    params.add("nick", nick);

    HttpHeaders headers = new HttpHeaders();
    headers.add(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
            "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36");
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);

    RestTemplate restTemplate = new RestTemplate();
    ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
    System.out.println(response.getStatusCode() + " | " + response.getBody());
}

從上面代碼能夠看出,具體的使用姿式相比於不添加請求頭時,只是多了一個封裝

  • 具體的header信息分裝到 HttpHeaders 對象中
  • 請求參數依然封裝到 MultiValueMap
  • 而後根據請求頭 + 請求參數,構建 HttpEntity 對象,將這個做爲post的請求request參數傳入

2D203A36995BE818CECBD936F535875F.jpg

固然做爲對比,當不加入headers時,看下返回什麼鬼, 406異常,可是咱們後端定義的是403,爲何會返回406呢?

B6C5FDF670E826D8040F6B5EDBB21F30.jpg

3. exchange 方式

另外還會關注到RestTemplate還提供了一個exchange方法,這個至關於一個公共的請求模板,使用姿式和get/post沒有什麼區別,只是能夠由調用發本身來選擇具體的請求方法

使用exchange對上面的post請求進行簡單的替換以下, 基本上除了多一個參數以外沒有什麼區別了

@Test
public void testPostHeader() {
    String url = "http://localhost:8080/post";
    String email = "test@hhui.top";
    String nick = "一灰灰Blog";

    MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
    params.add("email", email);
    params.add("nick", nick);

    HttpHeaders headers = new HttpHeaders();
    headers.add(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
            "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36");
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);

    RestTemplate restTemplate = new RestTemplate();
    ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
    System.out.println(response.getStatusCode() + " | " + response.getBody());
}

那麼問題來了,爲何要有這個東西?或者說這個接口的提供能夠帶來什麼好處?

  • 當你寫一個公共的Rest工具類時,就比較方便了,底層統一,具體的方法由上層業務方選擇便可
  • get能夠經過這種方式直接添加請求頭(也就是不須要第一種case中的自定義攔截器來塞入header,顯然更加靈活)

II. Post參數提交

前面的post參數提交,其實默認採用的是 application/x-www-form-urlencoded 方式,便是咱們最多見的表單提交方式,在瀏覽器中的表現形式以下

body

此外,還有一種直接提交json串的方式,在前文 《180730-Spring之RequestBody的使用姿式小結》中有說明,具體瀏覽器中表現形式爲

json

因此接下來的問題就是,RestTemplate要怎麼處理呢?

1. json串提交

建議在看下面的內容以前,先看一下上面的那篇博文,理解下RequestBody是什麼東西

首先搭建一個後端

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Req {
    private String key;
    private Integer size;
}

@ResponseBody
@RequestMapping(value = "/body", method = {RequestMethod.POST, RequestMethod.GET, RequestMethod.OPTIONS})
public String body(@RequestBody Req req) {
    return req.toString();
}

而後使用方式,無非就是在請求頭中添加下Content-Type爲Application/json

@Test
public void testPostRequestBody() {
    String url = "http://localhost:8080/body";
    String email = "test@hhui.top";
    String nick = "一灰灰Blog";

    Map<String, String> params = new HashMap<>();
    params.put("email", email);
    params.put("nick", nick);

    HttpHeaders headers = new HttpHeaders();
    headers.add(HttpHeaders.CONTENT_TYPE, "application/json");
    HttpEntity<Map<String, String>> request = new HttpEntity<>(params, headers);

    RestTemplate restTemplate = new RestTemplate();
    ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
    System.out.println(response.getStatusCode() + " | " + response.getBody());
}

注意下post參數,是放在Map容器中,而不是以前的MultiValueMap

運行時截圖以下

D5E1ABAFBD02418EE63B38C5061685FF.jpg

2. 文件上傳

post除了傳表單數據(json串)以外,還有一個常見的就是上傳文件了,實際上使用RestTemplate來實現文件上傳,算是比較簡單的了,和前面的使用基本上也沒有什麼差異,只是將文件做爲params參數而已

首先搭建一個Controller後端服務,簡單的獲取文件內容,並返回

@ResponseBody
@PostMapping(path = "file")
public String file(MultipartHttpServletRequest request) throws IOException {
    MultipartFile file = request.getFile("file");
    if (file == null) {
        return "no file!";
    }

    BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream()));
    StringBuilder builder = new StringBuilder();
    String line = reader.readLine();
    while (line  != null) {
        builder.append(line);
        line = reader.readLine();
    }
    reader.close();
    return builder.toString();
}

而後就是實際的測試用例,將文件包裝在FileSystemResource對象中,而後塞入MultiValueMap中,注意下面的使用並無顯示添加請求頭,而這種時候,content-type 經過斷點查看實際爲 content-type = multipart/form-data;

@Test
public void testPostFile() {
    String url = "http://localhost:8080/file";
    FileSystemResource resource = new FileSystemResource(new File("/tmp/test.txt"));
    MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
    params.add("file", resource);
    params.add("fileName", "test.txt");

    RestTemplate restTemplate = new RestTemplate();
    ResponseEntity<String> response = restTemplate.postForEntity(url, params, String.class);
    System.out.println(response);
}

14D0A3473CDF5DE85CB27FBD4DB3277F.jpg

III. 小結

本篇主要介紹如何給RestTemplate發起的請求,添加請求頭,以及完成某些特定的請求,下面小結一下使用姿式

1. 設置header

兩種方式

  • 一個是設置Interceptors,在攔截器中主動添加上對應的請求頭便可,適用於爲全部的請求添加統一的請求頭的場景
    • 這種方式不只僅能用來設置請求頭,還能夠在其中作不少其餘的事情
  • 另一種方式針對 postForXXXexchange 兩種請求方式而言,一樣本身設置請求頭HttpHeader,而後將請求頭和params封裝到HttpEntity,做爲request參數提交便可

2. 特殊的請求方式

json串的提交

  • 設置請求頭的content-type爲 Applicaiton/json
  • 將請求的數據封裝到map容器內(或者直接定義一個請求參數的DTO對象也能夠)
  • 而後將header和參數封裝到 HttpEntity 中,發起請求便可

文件上傳

  • 將資源文件塞入到MultiValueMap中便可,和普通的請求方式沒有什麼區別

3. 其餘

初級篇介紹瞭如何使用RestTemplate發起簡單的GET/POST請求;

中級篇則介紹請求的過程當中添加設置請求頭,以及某些特殊的請求能夠怎麼處理

顯然還會有高級篇,除了上面的東西,咱們還須要知道些什麼呢?

  • 請求超時的設置比較實用,有必要了解下
  • 在訪問某些特殊的網站時,代理的設置也避不開
  • 請求有身份鑑權的狀況下,如何安全的攜帶本身的身份呢?
  • RestTemplate底層使用的是什麼網絡庫作的網絡訪問?能夠用其餘的進行替換麼?(答案確定是能夠,否則這個命名就標準的名存實亡了)

關於高級篇,坐等更新

IV. 其餘

0. 相關博文

1. 一灰灰Bloghttps://liuyueyi.github.io/hexblog

一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛

2. 聲明

盡信書則不如,已上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

3. 掃描關注

一灰灰blog

QrCode

知識星球

zhishi

相關文章
相關標籤/搜索