1. 程式人生 > 其它 >【JavaWeb】HttpClient

【JavaWeb】HttpClient

需要的依賴:

        <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
</dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>

參考視訊:

https://www.bilibili.com/video/BV1W54y1s7BZ

1、原生JDK實現的網路請求

這個在狂神的爬蟲上面有看到過原生的方式

當時還不明白這個技術其實就是後臺的Ajax

    @Test
    public void quickStart() throws IOException {
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        try {

            // 原生JDK API 傳送請求
String urlString = "https://www.baidu.com/"; URL url = new URL(urlString); URLConnection urlConnection = url.openConnection(); HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection; httpURLConnection.setRequestMethod("GET"); httpURLConnection.setRequestProperty("aaa", "123"); inputStream = httpURLConnection.getInputStream(); inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); bufferedReader = new BufferedReader(inputStreamReader); String line = null; while (null != ( line = bufferedReader.readLine())) { System.out.println(line); } } catch (Exception e) { e.printStackTrace(); } finally { bufferedReader.close(); inputStreamReader.close(); inputStream.close(); } }

2、使用ApacheHttpClient傳送請求:

    @Test
    public void useHttpClient() throws IOException {
        // 使用 HttpClient Get 無參請求實現

        CloseableHttpClient closeableHttpClient = null;
        CloseableHttpResponse closeableHttpResponse = null;
        HttpEntity httpEntity = null;
        try {
            // 建立一個可關閉的Http客戶端物件
            closeableHttpClient = HttpClients.createDefault();

            // 要請求的地址
            String urlString = "https://www.baidu.com/";

            // GET引數需要進行URL編碼處理
            String param1 = "orderId=21343000123324";
            String param2 = "orderRemark=大三大四1王企鵝1 哇多久啊是巴西 &%……¥%";
            param1 = URLEncoder.encode(param1, StandardCharsets.UTF_8.name());
            param2 = URLEncoder.encode(param2, StandardCharsets.UTF_8.name());

            // 根據地址建立一個Http請求物件 這裡使用的是GET請求物件
            // HttpGet httpGet = new HttpGet(urlString);
            HttpGet httpGet = new HttpGet(urlString + "?" + param1 + "&" + param2);

            // 瀏覽器偽裝資訊
            httpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Edg/94.0.992.38");

            // 防盜鏈設定 https://www.jianshu.com/p/0a1338db6cab
            httpGet.addHeader("Referer", "https://ntp.msn.cn/");

            // 客戶端物件帶著請求物件 執行請求傳送, 返回響應物件
            closeableHttpResponse = closeableHttpClient.execute(httpGet);

            // 可以從響應物件獲取對應的響應資訊
            StatusLine statusLine = closeableHttpResponse.getStatusLine();
            System.out.println(statusLine); // HTTP/1.1 200 OK

            // 響應不成功狀態直接結束後續邏輯
            if (HttpStatus.SC_OK != statusLine.getStatusCode()) return;

            ProtocolVersion protocolVersion = statusLine.getProtocolVersion(); // HTTP/1.1
            int major = protocolVersion.getMajor(); // 1 主版本協議號
            int minor = protocolVersion.getMinor(); // 1 附屬小版本協議號
            String protocol = protocolVersion.getProtocol(); // HTTP

            int statusCode = statusLine.getStatusCode(); // 200
            String reasonPhrase = statusLine.getReasonPhrase(); // OK

            Header[] allHeaders = closeableHttpResponse.getAllHeaders();
            for (Header header : allHeaders) {
                System.out.println("Response Header -> " + header.getName()  + " : " + header.getValue());
            }
            
            // 從響應物件中獲取響應實體物件
            httpEntity = closeableHttpResponse.getEntity();

            Header contentType = httpEntity.getContentType();
            String contentTypeName = contentType.getName(); // Content-Type
            String contentTypeValue = contentType.getValue(); // Content-Type: text/html;charset=utf-8

            // 這個響應頭不常見
            // Header contentEncoding = httpEntity.getContentEncoding(); // null
            // String contentEncodingName = contentEncoding.getName();
            // String contentEncodingValue = contentEncoding.getValue();

            // 使用實體工具類轉換成字元結果
            String httpEntityResult = EntityUtils.toString(httpEntity, StandardCharsets.UTF_8);
            // System.out.println(httpEntityResult);

        } catch (Exception exception) {
            exception.printStackTrace();
        } finally {
            // 最後呼叫此方法確保資源釋放
            EntityUtils.consume(httpEntity);
            closeableHttpResponse.close();
            closeableHttpClient.close();
        }
    }

3、下載圖片資源:

    @Test
    public void downloadWebPicture() throws Exception {

        // 請求傳送
        CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
        String resource = "https://img.zcool.cn/community/01088b5a052431a801204a0e253198.jpg@1280w_1l_2o_100sh.jpg";
        HttpGet httpGet = new HttpGet(resource);
        CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpGet);
        HttpEntity httpEntity = closeableHttpResponse.getEntity();

        // 型別解析
        Header contentType = httpEntity.getContentType();
        String contentTypeValue = contentType.getValue(); // image/jpeg
        String fileTypeSuffix = contentTypeValue.split("/")[1]; // jpeg

        // 檔案下載
        byte[] bytes = EntityUtils.toByteArray(httpEntity);
        String localPath = "C:\\Users\\Administrator\\Desktop\\test." + fileTypeSuffix;
        OutputStream outputStream = new FileOutputStream(localPath);
        outputStream.write(bytes);

        // 資源釋放
        outputStream.close();
        EntityUtils.consume(httpEntity);
        closeableHttpResponse.close();
        closeableHttpClient.close();
    }

4、配置代理主機:

    @Test
    public void proxySettings() throws Exception {
        
        CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
        String target = "https://www.baidu.com/";
        HttpGet httpGet = new HttpGet(target);

        // 代理主機的資訊 http://www.66ip.cn/
        String ip = "176.121.1.81";
        int port = 8181;
        HttpHost httpHost = new HttpHost(ip, port);

        // 建立請求配置物件
        RequestConfig requestConfig = RequestConfig
                .custom()
                .setProxy(httpHost) // 設定代理主機的資訊
                .build();

        // 設定請求配置
        httpGet.setConfig(requestConfig);

        CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpGet);
        HttpEntity httpEntity = closeableHttpResponse.getEntity();

        Header[] allHeaders = closeableHttpResponse.getAllHeaders();
        for (Header header : allHeaders) {
            System.out.println("Response Header -> " + header.getName()  + " : " + header.getValue());
        }

        String httpEntityResult = EntityUtils.toString(httpEntity, StandardCharsets.UTF_8);
        System.out.println(httpEntityResult);

        // 資源釋放
        EntityUtils.consume(httpEntity);
        closeableHttpResponse.close();
        closeableHttpClient.close();
    }

5、設定超時相關的配置:

    @Test
    public void proxySettings() throws Exception {
        // 請求傳送
        CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
        String target = "https://www.baidu.com/";
        HttpGet httpGet = new HttpGet(target);

        // 代理主機的資訊 http://www.66ip.cn/
        String ip = "176.121.1.81";
        int port = 8181;
        HttpHost httpHost = new HttpHost(ip, port);

        // 建立請求配置物件
        RequestConfig requestConfig = RequestConfig
                .custom()
                .setProxy(httpHost)
                .setConnectTimeout(5000) // 設定連線超時的上限 TCP3次握手的時限
                .setSocketTimeout(3000) // 設定讀取超時上限  從請求的網址中獲取響應資料的間隔時限(因為並不是一次請求就完成了載入)
                .setConnectionRequestTimeout(3000) // 從HttpClient連線池中獲取connection物件的時限
                .build();

        // 設定請求配置
        httpGet.setConfig(requestConfig);

        CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpGet);
        HttpEntity httpEntity = closeableHttpResponse.getEntity();

        Header[] allHeaders = closeableHttpResponse.getAllHeaders();
        for (Header header : allHeaders) {
            System.out.println("Response Header -> " + header.getName()  + " : " + header.getValue());
        }

        String httpEntityResult = EntityUtils.toString(httpEntity, StandardCharsets.UTF_8);
        System.out.println(httpEntityResult);

        // 資源釋放
        EntityUtils.consume(httpEntity);
        closeableHttpResponse.close();
        closeableHttpClient.close();
    }

6、MIME-TYPE 郵件擴充套件型別 與POST請求

mime-type 就是具體檔案型別的前面的所屬規範型別

在Tomcat裡面配置的web.xml資訊就可以看到所有的規範型別了

E:\apache-tomcat-8.5.70\conf\web.xml

片段:

    <mime-mapping>
        <extension>zirz</extension>
        <mime-type>application/vnd.zul</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>zmm</extension>
        <mime-type>application/vnd.handheld-entertainment+xml</mime-type>
    </mime-mapping>

常見Content-type:

# 一般html表單提交 傳送的型別
application/x-www-form-urlencoded

# html上傳檔案規範的型別
multipart/form-data

# 目前主流規範的型別
application/json

POST表單型別提交案例:

1、客戶端請求程式碼

    @Test
    public void postWithFormType() throws Exception {
        CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
        String target = "http://localhost:8080/mvc-framework/test/testMethod2";

        // 首先建立POST請求物件
        HttpPost httpPost = new HttpPost(target);


        // 設定表單需要提交的引數
        List<NameValuePair> nvpList = new ArrayList<>();
        nvpList.add(new BasicNameValuePair("username", "張三"));
        nvpList.add(new BasicNameValuePair("password", "w123e21"));

        // 表單型別實體物件 裝填引數資訊 application/x-www-form-urlencoded
        UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(nvpList, Consts.UTF_8);

// 設定表單型別實體物件
        httpPost.setEntity(urlEncodedFormEntity);

        // 客戶端執行請求傳送
        CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpPost);
        HttpEntity httpEntity = closeableHttpResponse.getEntity();
        System.out.println(EntityUtils.toString(httpEntity, StandardCharsets.UTF_8));

        EntityUtils.consume(httpEntity);
        closeableHttpResponse.close();
        closeableHttpClient.close();
    }

2、伺服器處理程式碼:

    /**
     *
     *  POST請求測試
     *  http://localhost:8080/mvc-framework/test/testMethod2
     * @param request
     * @param response
     */
    // @RequestMethod(MethodType.POST)
    @RequestMapping(value = "/testMethod2", methodType = MethodType.POST)
    public void testMethod2(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("testMethod2");

        ServletUtil.printAllParamByRequestMap(request);

        Map<String, Object> requestAttribute = (Map<String, Object>) request.getAttribute(ServletConstants.POST_PARAM_KEY);

        for (String s : requestAttribute.keySet()) {
            System.out.println("postParam " + s + ": " + requestAttribute.get(s));
        }
    }

服務這裡沒有對請求做出響應,就是列印看看沒有收到引數資訊:

doPost detected
doGet detected
testMethod2
username: [張三]
password: [w123e21]
postParam password: w123e21
postParam username: 張三

做了兩次列印的原因是第一個列印是直接呼叫ServletAPI實現:

基本的POST表單請求Servlet有做識別處理

    public static void printAllParamByRequestMap(HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        for (String s : parameterMap.keySet()) {
            System.out.println(s + ": " + Arrays.toString(parameterMap.get(s)));
        }
    }

第二次列印是從輸入流中獲取識別的

    /**
     * 從POST請求中獲取引數
     * @param request
     * @return
     * @throws Exception
     */
    public static Map<String, Object> getPostParam(HttpServletRequest request) throws Exception {
        // 返回引數
        Map<String, Object> params = new HashMap<>();

        // 獲取內容格式
        String contentType = request.getContentType();

        if (null == contentType || "".equals(contentType)) throw new ServletException("沒有設定請求頭項Content-Type!!!");
        else contentType = contentType.split(";")[0];

        // form表單格式  表單形式可以從 ParameterMap中獲取
        if (ServletConstants.CONTENT_TYPE_VALUE_URL_ENCODED2.equalsIgnoreCase(contentType)) {
            // 獲取引數
            Map<String, String[]> parameterMap = request.getParameterMap();
            if (parameterMap != null) {
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                    params.put(entry.getKey(), entry.getValue()[0]);
                }
            }
        }

        // json格式 json格式需要從request的輸入流中解析獲取
        if (ServletConstants.CONTENT_TYPE_VALUE_JSON2.equalsIgnoreCase(contentType)) {
            // 使用 commons-io中 IOUtils 類快速獲取輸入流內容
            String paramJson = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
            Map parseObject = JSON.parseObject(paramJson, Map.class);
            params.putAll(parseObject);
        }

        return params ;
    }

也可以不使用UrlEncodedFormEntity ,使用請求頭設定即可

這個意思在視訊裡說錯了,應該是這個Entity已經在Header這麼處理了

        // 配置Http請求頭
        httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");

因為表單的引數還是需要放到Entity裡面傳送過去的

這裡特意看了下Entity的實現結構:

搜尋Entity相關的時候發現這個部落格寫的也很好,涉及到Cookie相關的操作

https://www.cnblogs.com/licl11092/p/9075677.html

POST + JSON型別案例:

SpringMVC接受Post JSON資料時要帶上 @RequestBody給方法

普通Get引數則是@RequestParam

類註解為@RestController就可以不註解@RequestBody方法

在這裡我使用的是封裝的一套MVC,Servlet對JSON引數是不支援的,只能從輸入流自行獲取

這裡JSON引數採用的是StringEntity實現儲存,看了原始碼發現預設是text/plain型別,也允許構造器自行設定型別

    @Test
    public void postWithFormJson() throws Exception {
        CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
        java.lang.String target = "http://localhost:8080/mvc-framework/test/testMethod2";

        // 首先建立POST請求物件
        HttpPost httpPost = new HttpPost(target);

        // 設定需要提交的JSON引數 這裡做簡單案例,就不去下載Fastjson來轉換了,自己手寫一個
        String jsonParam = "{ \"username\": \"張三\", \"password\": \"w123e21\" }";

// 表單型別實體物件 裝填引數資訊 application/x-www-form-urlencoded
        StringEntity jsonEntity = new StringEntity(jsonParam , Consts.UTF_8);

        // 配置Http請求頭
        // httpPost.addHeader("Content-Type", "application/json; charset=UTF-8");
        jsonEntity.setContentType(new BasicHeader("Content-Type", "application/json; charset=UTF-8"));
        // StringEntity 設定編碼
        jsonEntity.setContentEncoding(Consts.UTF_8.name());

        // 設定JSON實體物件
        httpPost.setEntity(jsonEntity);

        // 客戶端執行請求傳送
        CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpPost);
        HttpEntity httpEntity = closeableHttpResponse.getEntity();
        System.out.println(EntityUtils.toString(httpEntity, StandardCharsets.UTF_8));

        EntityUtils.consume(httpEntity);
        closeableHttpResponse.close();
        closeableHttpClient.close();
    }

POST + 檔案型別案例: