聽說你在學習:如何通過代碼請求URL

情景引入

旁白:今天興致極好,開開心心的與小白玩着“網頁小遊戲”
小白:真開心呀,好久沒有玩這些網頁遊戲了呢。
我:確實,工作一來,哪有這樣的閒情逸致。
小白:不過,你看,咱們玩的遊戲都是通過網頁鏈接請求,然後返回給咱們這麼多對應的響應內容,它這裏感覺好神奇。
我:當然,它裏面涉及到的內容還是非常多,可不是一下就能完整掌握的,你這思考問題的能力大大加強了嘛!
小白:這可不嘛,學習到老才行呢。不過,我經常聽到同事說,快給我一個接口地址,我要向你發出請求獲取信息啦,這裏面到底是怎麼實現的,一直都沒有機會去請教他們。
我:虛心求教,這是非常值得的,不要害怕,不懂就要問,這纔是應該有的態度。既然,這樣的話,那我就給你科普科普。這不,正好咱們也可以“中場休息”一會,再繼續遊戲呢。
小白:好呀好呀!學習,遊戲兩不誤,豈不是人生一大快事!
我:小白,長大了!

基礎知識

引言

當我們輸入一個URL然後回車,頁面會顯示出對應的功能信息,那麼它這個過程到底是如何的呢?其實,這裏面涉及到的內容會非常多,比如緩存,地址解析,http協議,網絡請求,OSI模型,服務器數據組裝,頁面渲染等等。這裏,就不會說得那麼詳細(PS:但是這個鏈路過程,建議各位看官可以好好的琢磨琢磨,分析分析),本文主要是針對,如果對一個已知URL發出請求並獲取到其實時的響應信息;

實際場景

我想,在實際開發中經常會碰到這樣的事情,就是:需要Http請求其他同事開發的接口,而獲取對應需要的內容,比如下載一個URL的文件或者獲取數據列表信息等等以及調用第三方公共接口信息。那麼,必不可少的就是通過代碼去發出一個http的請求。那麼,具體如何操作呢?客觀,慢慢往下看!

操作方法

現在的方法有很多,且不限於本文說到的幾種方式哦!本文主要採取循序漸進的方式,並沒有說哪一種方法就是最好的,具體問題具體分析,要以一種開放的心態看待這個問題。

HttpUrlConnection

請求步驟

PS:該方式是最原始的方式,所以值得學習其內部執行流程;

package com.hnu.scw.utils;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 上午 10:34 2020/5/24 0024
 * @ Description:請求URL的post方法工具類
 * @ Modified By:
 * @Version: $version$
 */
public class HttpPostUtils {
    /**
     * 請求URL的post請求
     * @param url  請求url地址
     * @param parameters 請求參數
     * @return 響應結果字符串
     * @throws Exception 異常信息
     */
    public static String doRequestPostMethod(String url, Map<String, Object> parameters) throws Exception {
        // 將參數轉爲String(PS:因爲如果URL需要參數的話,那麼都是採取字符串字節的形式)
        String params = convertRequestParameter(parameters);
        URL restURL = new URL(url);
        // 請求失敗的次數(PS:便於減少由於某次的網絡抖動而影響請求)
        int errorNumber = 0;
        while( errorNumber < 3 ) {
            try {
                HttpURLConnection conn = (HttpURLConnection) restURL.openConnection();
                // 設置請求超時時間(PS:可以不設置)
                conn.setConnectTimeout(5000);
                conn.setReadTimeout(5000);
                // 請求方式(PS:這裏採取POST請求,如果需要GET請求,則爲GET)
                conn.setRequestMethod("POST");
                // 設置請求的數據格式爲JSON
                conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
                conn.setRequestProperty("Connection", "Keep-Alive");
                // 是否從httpUrlConnection讀入,默認情況下是true
                conn.setDoOutput(true);
                // 是否允許用戶交互,假設爲true,則允許用戶交互的上下文中對此URL進行檢查(比如對話框驗證交互)。
                conn.setAllowUserInteraction(false);
                // 獲取輸出流(PS:這裏方式很多,這裏提供幾種)
                // 方式一
                PrintStream ps = new PrintStream(conn.getOutputStream());
                ps.print(params);
                ps.close();
                // 方式二
               /* DataOutputStream out = new DataOutputStream(conn.getOutputStream());
                out.write(params.getBytes("UTF-8"));
                out.flush();
                out.close();*/
                // 建立連接
                conn.connect();
                // 獲取響應輸入流
                BufferedReader bReader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
                String line;
                String resultStr = "";
                // 拼接響應內容
                while (null != (line = bReader.readLine())) {
                    resultStr += line;
                }
                bReader.close();
                // 返回相應結果(PS:爲字符串格式)
                return resultStr;
            } catch (Exception e) {
                // 錯誤次數+1
                errorNumber++;
            }
        }
        throw new Exception("當前網絡異常存在抖動,請稍後再試");
    }

    /**
     * 將map形式的參數轉爲字符串形式
     * @param parameters 請求參數
     * @return 請求參數字符串
     */
    private static String convertRequestParameter(Map<String, Object> parameters) throws UnsupportedEncodingException {
        StringBuilder sb = new StringBuilder();
        // 遍歷接參數內容
        for (Map.Entry<String,Object> param : parameters.entrySet()) {
            if (sb.length() != 0) {
                sb.append('&');
            }
            // 防止中文亂碼
            sb.append(URLEncoder.encode(param.getKey(), "UTF-8"));
            sb.append('=');
            sb.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8"));
        }
        return sb.toString();
    }
}

OkHttpClient

添加依賴

<dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.8.0</version>
        </dependency>

請求步驟(GET和POST方式)

package com.hnu.scw.utils;

import okhttp3.*;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 上午 10:34 2020/5/24 0024
 * @ Description:請求URL的post方法工具類
 * @ Modified By:
 * @Version: $version$
 */
public class HttpPostUtils {
    

    /**
     * 執行 POST 請求
     * @param url  請求的URL
     * @return 響應結果
     */
    public String doRequestPostByOkHttpClient(String url) {
        // ssl的認證重寫(PS:用於防止請求URL時有些系統會進行安全驗證而導致請求失敗)
        OkHttpClient okHttpClient = new OkHttpClient.Builder().hostnameVerifier(
                new HostnameVerifier() {
                    @Override
                    public boolean verify(String s, SSLSession sslSession) {
                        return true;
                    }
                }
        ).build();
        // 如果是JDK1.8以上的,那麼可以採取lambda重寫匿名函數的方式
       /* OkHttpClient okHttpClient = new OkHttpClient.Builder().hostnameVerifier(
                (s, sslSession) ->{
                    return true;
                }
        ).build();*/

        // 設置請求的參數信息
        RequestBody requestBody = new FormBody.Builder()
                .add("key1", "test")
                .add("key2", "hello")
                .build();
        // 封裝請求頭信息
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .addHeader("Cookie", "JSESSIONID=299571E0E40DA6E9962E41B87A669BBB")
                .addHeader("content-type", "application/json")
                .addHeader("cache-control", "no-cache")
                .build();
        Call call = okHttpClient.newCall(request);
        try {
            Response response = call.execute();
            // 返回相應結果
            return response.body().string();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


}

SpingRestTemplate

請求步驟(GET和POST方式)

方式一

package com.hnu.scw.utils;

import okhttp3.*;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 上午 10:34 2020/5/24 0024
 * @ Description:請求URL的post方法工具類
 * @ Modified By:
 * @Version: $version$
 */
public class HttpPostUtils {
    

    /**
     * 執行 POST 請求 通過Spring 的restTemplate方式
     * @param url 請求URL
     * @param parameter 請求參數
     * @return 響應結果字符串
     */
    public String doRequestPostByRestTemplate(String url, Object parameter) {
        RestTemplate template = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        // 設置請求的格式爲:json
        MediaType type = MediaType.parseMediaType("application/json;charset=UTF-8");
        headers.setContentType(type);
        HttpEntity entity = new HttpEntity(parameter,headers);
        // POST方式,如果要用GET,則爲getForObject即可
        String result = template.postForObject(url, entity, String.class);
        return result;
    }

    /**
     * 執行 POST 請求 通過Spring 的restTemplate方式二
     * @param url  請求URL
     * @param parameter 請求參數(PS:一般爲java實體)
     * @param backDto 相應的結果實體
     * @return 響應結果實體
     */
    public <T> T doRequestPostByRestTemplate(String url, Object parameter, Class<T> backDto) {
        RestTemplate template = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        // 設置請求的格式爲:json
        MediaType type = MediaType.parseMediaType("application/json;charset=UTF-8");
        headers.setContentType(type);
        HttpEntity entity = new HttpEntity(parameter,headers);
        // POST方式,如果要用GET,則爲getForObject即可
        String result = template.postForObject(url, entity, String.class);
        // 將json字符串轉爲實體
        return JSON.parseObject(result, backDto);
    }

}

方式二:

package com.hnu.scw.utils;

import okhttp3.*;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 上午 10:34 2020/5/24 0024
 * @ Description:請求URL的post方法工具類
 * @ Modified By:
 * @Version: $version$
 */
public class HttpPostUtils {
    

    /**
     * 執行 POST 請求 通過Spring 的restTemplate方式的方式二
     * @param url 請求URL
     * @param parameter 請求參數
     * @return 響應結果實體類型
     */
    public <T> T doRequestPostByRestTemplateEntity(String url, Object parameter, Class<T> backDto) {
        RestTemplate template = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        // 設置請求的格式爲:json
        MediaType type = MediaType.parseMediaType("application/json;charset=UTF-8");
        headers.setContentType(type);
        HttpEntity entity = new HttpEntity(parameter,headers);
        // POST方式,如果要用GET,則爲getForEntity即可
        ResponseEntity<T> tResponseEntity = template.postForEntity(url, entity, backDto);
        return tResponseEntity.getBody();
    }


}

彩蛋

如何獲取請求URL的響應流

背景:假設我們請求的這個URL,其返回的結果是一個文件流,那麼我們就需要將這個文件流輸出給前端,以實現下載文件的功能;
實現步驟:先獲取請求URL的響應結果的輸入流,然後再通過最開始前端的HTTPServletResponse請求響應流outputStream輸出即可;

HttpUrlConnect

PS:如果採取原生的請求方式,那麼就比較簡單,直接獲取其對應的響應結果的輸入流,則可以返回。

/**
     * 請求 URL 的post請求
     * @param url  請求url地址
     * @param parameters 請求參數
     * @return 響應結果流
     * @throws Exception 異常信息
     */
    public static InputStream doRequestPostMethodInputStream(String url, Map<String, Object> parameters) throws Exception {
        // 將參數轉爲String(PS:因爲如果URL需要參數的話,那麼都是採取字符串字節的形式)
        String params = convertRequestParameter(parameters);
        URL restURL = new URL(url);
        // 請求失敗的次數(PS:便於減少由於某次的網絡抖動而影響請求)
        int errorNumber = 0;
        while( errorNumber < 3 ) {
            try {
                HttpURLConnection conn = (HttpURLConnection) restURL.openConnection();
                // 設置請求超時時間(PS:可以不設置)
                conn.setConnectTimeout(5000);
                conn.setReadTimeout(5000);
                // 請求方式(PS:這裏採取POST請求,如果需要GET請求,則爲GET)
                conn.setRequestMethod("POST");
                // 設置請求的數據格式爲JSON
                conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
                conn.setRequestProperty("Connection", "Keep-Alive");
                // 是否從httpUrlConnection讀入,默認情況下是true
                conn.setDoOutput(true);
                // 是否允許用戶交互,假設爲true,則允許用戶交互的上下文中對此URL進行檢查(比如對話框驗證交互)。
                conn.setAllowUserInteraction(false);
                // 獲取輸出流
                DataOutputStream out = new DataOutputStream(conn.getOutputStream());
                out.write(params.getBytes("UTF-8"));
                out.flush();
                out.close();
                // 建立連接
                conn.connect();
                // 獲取響應輸入流
                return conn.getInputStream();
            } catch (Exception e) {
                // 錯誤次數+1
                errorNumber++;
            }
        }
        return null;
    }

 
    /**
     * 請求URL並且下載其返回的文件流內容
     * @param httpServletResponse  http響應
     * @throws Exception
     */
    public void downLoadFile(HttpServletResponse httpResponse) throws Exception {
        InputStream inputStream = doRequestPostMethodInputStream("xxx", new HashMap<>());
        // 採取緩存流的方式
        OutputStream outputStream = new BufferedOutputStream(httpResponse.getOutputStream());
        httpResponse.setContentType("application/octer-stream");
        String fileName = URLEncoder.encode("測試","UTF-8");
        httpResponse.setHeader("Content-disposition","attachment;filename=" + fileName + ";");
        byte[] buff = new byte[1024];
        int length = 0;
        while((length = inputStream.read()) != -1 ){
            outputStream.write(buff, 0, length);
        }
        outputStream.flush();
    }

Spring的RestTemplate

package com.hnu.scw.utils;

import okhttp3.*;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 上午 10:34 2020/5/24 0024
 * @ Description:請求URL的post方法工具類
 * @ Modified By:
 * @Version: $version$
 */
public class HttpPostUtils {
    /**
     * 執行 POST 請求  獲取其返回的文件流
     * @param url  請求URL
     * @param parameter 請求參數
     * @return 響應結果文件輸入流
     * @throws IOException
     */
    public static InputStream doRequestPostByRestTemplateEntity(String url, Object parameter) throws IOException {
        RestTemplate template = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        // 設置請求的格式爲:json
        MediaType type = MediaType.parseMediaType("application/json;charset=UTF-8");
        headers.setContentType(type);
        HttpEntity entity = new HttpEntity(parameter,headers);
        // POST方式,如果要用GET,則爲getForEntity即可
        ResponseEntity<Resource> responseEntity = template.postForEntity(url, entity, Resource.class);
        if(HttpStatus.OK.equals(responseEntity.getStatusCode())){
            return responseEntity.getBody().getInputStream();
        }
        return null;
    }

 /**
     * 請求URL並且下載其返回的文件流內容
     * @param httpServletResponse  http響應
     * @throws Exception
     */
    public void downLoadFile(HttpServletResponse httpResponse) throws Exception {
        InputStream inputStream = doRequestPostByRestTemplateEntity("xxx", new HashMap<>());
        // 採取緩存流的方式
        OutputStream outputStream = new BufferedOutputStream(httpResponse.getOutputStream());
        httpResponse.setContentType("application/octer-stream");
        String fileName = URLEncoder.encode("測試","UTF-8");
        httpResponse.setHeader("Content-disposition","attachment;filename=" + fileName + ";");
        byte[] buff = new byte[1024];
        int length = 0;
        while((length = inputStream.read()) != -1 ){
            outputStream.write(buff, 0, length);
        }
        outputStream.flush();
    }
}

總結

  1. 本文講述的方式各自有各自的應用場景,並不存在哪一種就是最好的形式(PS:當然不僅僅只有這些方式,比如還有apache的httpClient,CloseableHttpClient以及springCloud中常見的Fegin);
  2. 實際工作中發出http的請求是相對的多,對於其內部的執行流程和實現原理要去掌握(PS:網絡協議+socket+netty都是需要掌握);
  3. 針對常用的場景和技術要學會去總結,分析各自的利弊,而不要只會用而不明白其內部;
  4. 通過閱讀本文內容之後,建議將“輸入一個URL地址,然後回車敲擊顯示結果”,這個鏈路的整個過程都去學習和掌握,你會發現更多的美哦!;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章