java傳送http請求(jquery傳送http請求,前後端看這一篇文章夠了,很完整)

2023-07-21 18:05:44

為什麼寫這篇部落格?

1.目前很多系統使用了微服務架構,那麼各個微服務之間進行內部通訊一般採用http協定的方式,springcloud中提供了ribbon,feign,openFeign等元件。

但是這些元件底層無非就是基於java原生的程式碼傳送http請求或者使用了RestTemplate來封裝了像okHttp等這些開源元件,他們到底是如何工作的?

2.現在很多系統互動比較複雜,往往會有存取第三方api的場景,那麼使用什麼方式比較方便呢?

下面從幾個方面來聊吧:

  • java原生的傳送Http請求的方式
  • 使用apache的httpclient元件傳送http請求
  • 使用spring的RestTemplate傳送http請求
  • 使用okHttp元件傳送http請求

一、java原生的傳送Http請求的方式

1、首先我們先建立一個springboot專案提供一些常見的http介面。

這裡資料就不存資料庫了,先暫時儲存了,目的是演示。

如果這些介面編寫你比較熟悉,可以略過。

1.1 在springboot專案中,增加一個controller。

@RestController
public class MemberController {
    private static final String FILE_PATH = System.getProperty("user.dir");
    private static ConcurrentHashMap<Integer, Member> memberMap = new ConcurrentHashMap<>(16);
    private static ConcurrentHashMap<String, String> fileMap = new ConcurrentHashMap<>(16);

    static {
        Member m1 = new Member();
        m1.setId(1);
        m1.setBirthday(new Date());
        m1.setBalance(new BigDecimal("1000"));
        m1.setName("張三");
        memberMap.put(1, m1);
        m1 = new Member();
        m1.setId(2);
        m1.setBirthday(new Date());
        m1.setBalance(new BigDecimal("1000"));
        m1.setName("李四");
        memberMap.put(2, m1);
    }
}

使用memberMap來儲存提交的會員資料。

使用fileMap來儲存上傳的檔名稱資訊和絕對路徑。(因為業務開發中檔案上傳是常見的需求)

預製兩個資料。

1.2 在MemberController中增加一下幾個介面

(1)新增會員介面。(post + json方式)

@PostMapping("/member")
    public NormalResponseObject addMember(@RequestBody MemberVO memberVO) {
        if (memberMap.containsKey(memberVO.getId())) {
            return NormalResponseObject.fail("id不能重複");
        }
        memberMap.put(memberVO.getId(), Member.of(memberVO));
        return NormalResponseObject.sucess();
    }

(2)新增會員介面。(post + param方式)

    @PostMapping("/member/param")
    public NormalResponseObject addMemberUseParam(MemberVO memberVO) {
        return addMember(memberVO);
    }

(3)新增會員介面。(get + param方式)

    @GetMapping("/member/param")
    public NormalResponseObject addMemberUseParam2(MemberVO memberVO) {
        return addMember(memberVO);
    }

(4)查詢會員詳情介面。(get)

    @GetMapping("/member/{id}")
    public NormalResponseObject<MemberVO> getMember(@PathVariable("id") Integer id) {
        if (!memberMap.containsKey(id)) {
            return NormalResponseObject.fail("不存在對應會員資訊");
        }
        return NormalResponseObject.sucess(Member.toMemberVO(memberMap.get(id)));
    }

(5)刪除會員介面。(delete)

    @DeleteMapping("/member/{id}")
    public NormalResponseObject deleteMember(@PathVariable("id") Integer id) {
        memberMap.remove(id);
        return NormalResponseObject.sucess();
    }

(6)編輯會員介面。(put + param)

    @PutMapping("/member/{id}")
    public NormalResponseObject editMember(@PathVariable("id") Integer id, MemberVO memberVO) {
        if (!memberMap.containsKey(id)) {
            return NormalResponseObject.fail("不存在對應會員資訊");
        }
        memberMap.put(id, Member.of(memberVO));
        return NormalResponseObject.sucess();
    }

(7)查詢所有會員介面。(get)

    @GetMapping("/member")
    public NormalResponseObject<List<MemberVO>> getAllMember() {
        if(memberMap.size() == 0) {
            return NormalResponseObject.sucess(new ArrayList<>());
        }
        List<MemberVO> voList = memberMap.values().stream().map(Member::toMemberVO)
                .collect(Collectors.toList());
        return NormalResponseObject.sucess(voList);
    }

(8)檔案上傳介面。(post +  multipar/form-data)

    @PostMapping("/member/fileUpload")
    public NormalResponseObject uploadFile(@RequestParam("file") MultipartFile multipartFile,
                                           @RequestParam("fileName") String fileName) {
        if(multipartFile == null || multipartFile.getSize() <= 0) {
            return NormalResponseObject.fail("檔案為空");
        }
        System.out.println("上傳的檔名為:" + multipartFile.getOriginalFilename());
        System.out.println("傳入的fileName引數為:" + fileName);
        // 儲存檔案
        File file = Paths.get(FILE_PATH, fileName).toFile();
        if(!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
                return NormalResponseObject.fail("檔案操作異常");
            }
        }
        try (
                FileOutputStream fos = new FileOutputStream(file)
        ) {
            InputStream inputStream = multipartFile.getInputStream();
            byte[] buf = new byte[1024];
            int len = 0;
            while ((len = inputStream.read(buf)) > 0) {
                fos.write(buf, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return NormalResponseObject.fail("檔案操作異常");
        }
        fileMap.put(fileName, file.getAbsolutePath());
        return NormalResponseObject.sucess();
    }

(9)檔名稱列表查詢介面。(get)

    @GetMapping("/member/files")
    public NormalResponseObject<List<FileObject>> getAllFiles() {
        if(fileMap.size() == 0) {
            return NormalResponseObject.sucess(new ArrayList<>());
        }
        List<FileObject> files = new ArrayList<>();
        fileMap.forEach((key, value) -> {
            FileObject fileObject = new FileObject();
            fileObject.setFileName(key);
            fileObject.setFileAbsolutePath(value);
            files.add(fileObject);
        });
        return NormalResponseObject.sucess(files);
    }

(10)檔案下載介面。(get)

    @GetMapping("/member/file/download")
    public void doloadFile(@RequestParam("fileName") String fileName, HttpServletResponse response) {
        // 設定響應頭
        try {
            response.setHeader("Content-disposition",
                    "attachment;filename=" + URLEncoder.encode(fileName, "utf-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        response.setContentType("text/plain");
        // 輸出檔案
        if (fileMap.containsKey(fileName)) {
            String abs = fileMap.get(fileName);
            try (
                    FileInputStream fis = new FileInputStream(abs);
                    )
            {
                byte[] buf = new byte[1024];
                int len = 0;
                while ((len = fis.read(buf)) > 0) {
                    response.getOutputStream().write(buf, 0, len);
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

 

2、驗證這些介面是否正確。

為了方便,在頁面就使用jquery來驗證這些介面是否能正常工作。

下面就是 js發起這些請求的程式碼和截圖,如果你對js熟悉或者不想關注,可以跳過。

2.1  新增會員資料

首頁頁面載入,查詢所有會員資料。

js程式碼如下:

function loadAllMembers() {
    let url = "/member";
    $.ajax(url, {
        method: "get",
        success: function (result) {
            let data = result.data;
            if(data) {
                var body = $("#dataTable tbody");
                body.empty();
                for(let index in data) {
                    let trHtml = "<tr>";
                    trHtml += "<td>" + data[index].id + "</td>";
                    trHtml += "<td>" + data[index].name + "</td>";
                    trHtml += "<td>" + data[index].balance + "</td>";
                    trHtml += "<td>" + data[index].birthday + "</td>";
                    trHtml += '<td>' + '<button class="detail" trid=' + data[index].id + '>詳情</button>' + '</td>';
                    trHtml += '<td>' + '<button class="edit" trid=' + data[index].id + '>編輯</button>' + '</td>';
                    trHtml += '<td>' + '<button class="del" trid=' + data[index].id + '>刪除</button>' + '</td>';
                    trHtml += "</tr>";
                    body.append($(trHtml));
                }
            }
        }
    })
}

這個table每行的按鈕包含了詳情,編輯,刪除。

 分別使用三種方式進行新增。

(1)post+json新增

程式碼如下:

function addMember01(event) {
    event.preventDefault();
    let url = "/member";
    let member = {};
    member.id = $('#addMemberForm [name="id"]').val();
    member.name = $('#addMemberForm [name="name"]').val();
    member.balance = $('#addMemberForm [name="balance"]').val();
    member.birthday = $('#addMemberForm [name="birthday"]').val();
    $.ajax(url, {
        method: "post",
        contentType: "application/json",
        data: JSON.stringify(member),
        success: function (result) {
            if (result.statusCode == 200) {
                $('#addMemberForm [name="id"]').val("");
                $('#addMemberForm [name="name"]').val("");
                $('#addMemberForm [name="balance"]').val("");
                $('#addMemberForm [name="birthday"]').val("");
                loadAllMembers();
            } else {
                window.alert(result.message);
            }
        }
    });
}

 

截圖:

 

 

 (2)其他兩種方式新增的程式碼和截圖。

function addMember02(event) {
    event.preventDefault();
    let url = "/member/param";
    let member = {};
    member.id = $('#addMemberForm [name="id"]').val();
    member.name = $('#addMemberForm [name="name"]').val();
    member.balance = $('#addMemberForm [name="balance"]').val();
    member.birthday = $('#addMemberForm [name="birthday"]').val();
    $.ajax(url, {
        method: "get",
        data: member,
        success: function (result) {
            if (result.statusCode == 200) {
                $('#addMemberForm [name="id"]').val("");
                $('#addMemberForm [name="name"]').val("");
                $('#addMemberForm [name="balance"]').val("");
                $('#addMemberForm [name="birthday"]').val("");
                loadAllMembers();
            } else {
                window.alert(result.message);
            }
        }
    });
}

function addMember03(event) {
    event.preventDefault();
    let url = "/member/param";
    let member = {};
    member.id = $('#addMemberForm [name="id"]').val();
    member.name = $('#addMemberForm [name="name"]').val();
    member.balance = $('#addMemberForm [name="balance"]').val();
    member.birthday = $('#addMemberForm [name="birthday"]').val();
    $.ajax(url, {
        method: "post",
        data: member,
        success: function (result) {
            if (result.statusCode == 200) {
                $('#addMemberForm [name="id"]').val("");
                $('#addMemberForm [name="name"]').val("");
                $('#addMemberForm [name="balance"]').val("");
                $('#addMemberForm [name="birthday"]').val("");
                loadAllMembers();
            } else {
                window.alert(result.message);
            }
        }
    });
}

 

 

2.2  修改會員資料,查詢詳情,刪除

(1)對三個按鈕的事件委託

修改會員資料其實就是查詢回填,加修改。

由於table的行是動態生成的,所以需要對三個按鈕進行事件委託,保證新加入的按鈕事件也能得到響應。

js程式碼如下:

// 對table中的操作按鈕進行事件委託
$("#dataTable tbody").on( "click", "button", function(event) {
    let id = $(event.target).attr("trid");
    let clz = $(event.target).attr("class");
    if("detail" == clz) {
        let url = "/member/" + id;
        $.ajax(url, {
            method: "get",
            success: function (result) {
                if(result.statusCode == 200) {
                    alert(JSON.stringify(result.data));
                } else {
                    alert(result.message);
                }
            }
        });
    } else if("del" == clz){
        let url = "/member/" + id;
        $.ajax(url, {
            method: "delete",
            success: function (result) {
                if(result.statusCode == 200) {
                    loadAllMembers();
                } else {
                    alert(result.message);
                }
            }
        });
    } else {
        let url = "/member/" + id;
        $.ajax(url, {
            method: "get",
            success: function (result) {
                if(result.statusCode == 200) {
                    $('#editMemberForm [name="id"]').val(result.data.id);
                    $('#editMemberForm [name="name"]').val(result.data.name);
                    $('#editMemberForm [name="balance"]').val(result.data.balance);
                    $('#editMemberForm [name="birthday"]').val(result.data.birthday);
                    $('#showOrHidden').show();
                } else {
                    alert(result.message);
                }
            }
        });
    }
});

上述程式碼中,完成了詳情檢視,編輯回填和刪除操作。

(2)執行修改操作

回填結束後,需要呼叫修改介面進行資料修改。

js程式碼如下:

function editMember(event) {
    event.preventDefault();
    let url = "/member/";
    let member = {};
    member.id = $('#editMemberForm [name="id"]').val();
    member.name = $('#editMemberForm [name="name"]').val();
    member.balance = $('#editMemberForm [name="balance"]').val();
    member.birthday = $('#editMemberForm [name="birthday"]').val();
    url += member.id;
    $.ajax(url, {
        method: "put",
        data: member,
        success: function (result) {
            if (result.statusCode == 200) {
                $("#showOrHidden").hide();
                loadAllMembers();
            } else {
                window.alert(result.message);
            }
        }
    });
}

截圖如下:

 

 

 

2.3  檔案上傳和下載

後端在開發檔案上傳介面的時候需要引入maven依賴。

<!--檔案上傳需要該依賴-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

對於前端,需要使用對應的特定form如下:

<h2>檔案上傳的form</h2>
<div>
    <form id="fileForm" action="/member/fileUpload" enctype="multipart/form-data">
        請選擇檔案:<input type="file" name="file"><br>
        輸入後臺儲存的檔名稱:<input type="text" name="fileName">
        <input type="submit" value="上傳" onclick="uploadFile(event)">
    </form>
</div>

然後js程式碼中注意阻止預設事件就行了:

function uploadFile(event) {
    // 防止預設表單提交行為
    event.preventDefault();

    // 獲取檔案物件
    var file = $('input[type=file]')[0].files[0];
    if (!file) {
        alert('請選擇檔案!')
        return;
    }

    // 建立 FormData 物件
    var formData = new FormData();
    formData.append('file', file);
    formData.append("fileName", $('[name="fileName"]').val());

    // 傳送 AJAX 請求
    $.ajax({
        url: '/member/fileUpload',
        type: 'POST',
        data: formData,
        processData: false,
        contentType: false,
        success: function (response) {
            alert('上傳成功!');
            getAllUploadedFile();
        },
        error: function (xhr, status, error) {
            alert('上傳失敗:' + error);
        }
    });
}

 

 至於檔案下載,前端只需要提供一個超連結去呼叫後端介面就行了。

這裡需要注意的就是需要對url進行編碼操作,js程式碼如下:

function getAllUploadedFile() {
    let url = "/member/files";
    $.ajax(url, {
        method: "get",
        success: function (result){
            if(result.statusCode == 200) {
                $("#fileOlList").empty();
                for(let index in result.data) {
                    let li = '<li><a href="/member/file/download?fileName='
                        + encodeURI(result.data[index].fileName) + '">'
                        + result.data[index].fileName + '</a></li>';
                    $("#fileOlList").append($(li))
                }
            } else {
                alert(result.message);
            }
        }
    });
}

由於後端介面已經使用了對應的響應頭設定,瀏覽器就會下載檔案:

 

 

到這裡呢,既驗證了介面的正確性,同時也將前端js如何發起ajax請求存取伺服器進行了描述。

下面就是使用java來傳送http請求了。

 

3、使用java原生方式傳送http請求。

3.1  簡單描述HttpUrlConnection

 上面的截圖就是使用HttpUrlConnection進行http請求傳送的幾個重要步驟。

還是挺複雜的。。。

 

3.2 發起get請求獲取會員資料

程式碼如下:

@Test
public void test01() {
    // 1.需要通過URL來獲取UrlConnection範例
    String url = "http://localhost:8080/member";
    URL urlObject = null;
    HttpURLConnection httpURLConnection = null;
    OutputStream os = null;
    InputStream is = null;
    ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
    try {
        urlObject = new URL(url);
    } catch (MalformedURLException e) {
        e.printStackTrace();
        return;
    }
    try {
        URLConnection urlConnection = urlObject.openConnection();
        if(urlConnection instanceof HttpURLConnection) {
            httpURLConnection = (HttpURLConnection) urlConnection;
        }
    } catch (IOException e) {
        e.printStackTrace();
        return;
    }
    if(null == httpURLConnection) {
        return;
    }
    try {
        // 2.設定請求的一些引數
        httpURLConnection.setRequestMethod(HttpMethod.GET.name());
        httpURLConnection.setDoOutput(false);
        httpURLConnection.setDoInput(true);

        // 3.開啟連線
        httpURLConnection.connect();

        // 4.是否需要使用OutputStream來向請求體中設定資料
        // 暫時不需要

        // 5.從InputStream中獲取資料
        int responseCode = httpURLConnection.getResponseCode();
        if(responseCode != 200) {
            System.out.println("請求出錯了");
            return;
        }
        is = httpURLConnection.getInputStream();
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = is.read(buf)) > 0) {
            // 使用ByteArrayOutputStream來快取資料
            baos.write(buf, 0, len);
        }
        String retStr = new String(baos.toByteArray(), "utf-8");
        System.out.println(retStr);
    }catch (Exception e) {
        e.printStackTrace();
    }finally {
        // 6.關閉連線
        closeSomething(os);
        closeSomething(is);
        closeSomething(baos);
        if(null != httpURLConnection) {
            httpURLConnection.disconnect();
        }
    }
}

結果如下圖:

 

此時要弄清楚一個問題,這幾個步驟,哪一步發起的連線呢?

下面進行驗證:

(1)openConnection()

 

 (2)connect()

 說明是在connect()方法呼叫只有才向伺服器發起的tcp連線。

 

3.3 增加會員資料

其實上一步看完,我相信基本說清楚了使用HttpUrlConnection來進行http請求的過程基本說清楚了。

下面我們看看如何新增資料呢?

(1)傳送json資料

下面我就貼出部分程式碼就行了。

// 2.設定請求的一些引數
httpURLConnection.setRequestMethod(HttpMethod.POST.name());
// 設定是否需要輸出資料和接收資料
httpURLConnection.setDoOutput(true);
httpURLConnection.setDoInput(true);
httpURLConnection.setRequestProperty(HttpHeaders.CONTENT_TYPE,
        MediaType.APPLICATION_JSON_VALUE);

// 3.開啟連線
httpURLConnection.connect();

// 4.是否需要使用OutputStream來向請求體中設定資料
os = httpURLConnection.getOutputStream();
MemberVO memberVO = new MemberVO();
memberVO.setId(3);
memberVO.setBirthday("2010-11-11");
memberVO.setBalance("1000");
memberVO.setName("httpConnection新增01");
String reqBodyStr = JSON.toJSONString(memberVO);
os.write(reqBodyStr.getBytes(StandardCharsets.UTF_8));
os.flush();

// 5.從InputStream中獲取資料
int responseCode = httpURLConnection.getResponseCode();
if(responseCode != 200) {
    System.out.println("請求出錯了");
    return;
}
is = httpURLConnection.getInputStream();
byte[] buf = new byte[1024];
int len = 0;
while ((len = is.read(buf)) > 0) {
    // 使用ByteArrayOutputStream來快取資料
    baos.write(buf, 0, len);
}
String retStr = new String(baos.toByteArray(), "utf-8");
JSONObject jsonObject = JSON.parseObject(retStr);
if (jsonObject.getInteger("statusCode") != 200) {
    throw new Exception(jsonObject.getString("message"));
}

執行程式碼,可以看見新增成功了:

 通過param來新增也試試:

(2)使用get傳送param資料

// 這裡在構建url的時候使用spring中的工具類
MemberVO memberVO = new MemberVO();
memberVO.setId(4);
memberVO.setBirthday("2010-11-11");
memberVO.setBalance("1000");
memberVO.setName("httpConnection通過get+param方式新增");
UriComponents urlCom = UriComponentsBuilder.fromHttpUrl(url)
        .queryParam("name", memberVO.getName())
        .queryParam("id", memberVO.getId())
        .queryParam("birthday", memberVO.getBirthday())
        .queryParam("balance", memberVO.getBalance())
        .build().encode(StandardCharsets.UTF_8);

URL urlObject = null;
HttpURLConnection httpURLConnection = null;
OutputStream os = null;
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
try {
    urlObject = new URL(urlCom.toUriString());
} catch (MalformedURLException e) {
    e.printStackTrace();
    return;
}

資料新增成功:

 

(3)使用post傳送param資料

這裡我只貼部分程式碼吧。

// 4.是否需要使用OutputStream來向請求體中設定資料
// 這裡在構建url的時候使用spring中的工具類
MemberVO memberVO = new MemberVO();
memberVO.setId(5);
memberVO.setBirthday("2010-11-11");
memberVO.setBalance("2000");
memberVO.setName("httpConnection通過post+param方式新增");
UriComponents urlCom = UriComponentsBuilder.fromHttpUrl(url)
        .queryParam("name", memberVO.getName())
        .queryParam("id", memberVO.getId())
        .queryParam("birthday", memberVO.getBirthday())
        .queryParam("balance", memberVO.getBalance())
        .build().encode(StandardCharsets.UTF_8);
os = httpURLConnection.getOutputStream();
os.write(urlCom.getQuery().getBytes(StandardCharsets.UTF_8));
os.flush();

發現資料新增成功:

 其實程式碼寫到這裡,我詳情傳送put請求和delete請求按照這個套路寫就行了,問題不打了吧。

為啥要講這麼多原生的東西,其實目的很簡單,

第一,並不是所有系統都能像我們平常開發的業務系統一樣,引入很多開源的jar包。例如:大廠自己封裝的工具,還有物聯網裝置。

第二,我們只有知道了原生的東西,才知道如何理解和優化有些框架。

這裡有一個問題,我們在使用原生socket程式設計的時候,其實OutputStream的flush操作是將os緩衝區的內容推給網路卡,那上述程式碼中的flush有啥作用呢?

能不呼叫嗎?

驗證如下:

 程式碼執行通過並新增成功了:

其實相當於,java為我們計算了body體的長度,並設定了對應的請求頭給伺服器端。

(我理解這個操作應該是我們嘗試獲取InputStream流的時候做的。) 

 

3.4 檔案操作相關

通過上述的幾個例子,至少我們明白了,只要明白了傳送封包的包結構,那麼就完全可以使用原生的方式傳送http請求。

下面我們來看看這種multipart/form-data型別的資料怎麼傳送呢?

我先截個網上對該MIME-TYPE的描述:

深入瞭解可以看看:https://blog.csdn.net/dreamerrrrrr/article/details/111146763

下面就是java程式碼了:

    @Test
    public void uploadFileTest() {
        // 1.需要通過URL來獲取UrlConnection範例
        String url = "http://localhost:8080/member/fileUpload";

        URL urlObject = null;
        HttpURLConnection httpURLConnection = null;
        OutputStream os = null;
        InputStream is = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
        try {
            urlObject = new URL(url);
        } catch (MalformedURLException e) {
            e.printStackTrace();
            return;
        }
        try {
            URLConnection urlConnection = urlObject.openConnection();
            if(urlConnection instanceof HttpURLConnection) {
                httpURLConnection = (HttpURLConnection) urlConnection;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        if(null == httpURLConnection) {
            return;
        }
        try {
            // 2.設定請求的一些引數
            httpURLConnection.setRequestMethod(HttpMethod.POST.name());
            // 設定是否需要輸出資料和接收資料
            httpURLConnection.setDoOutput(true);
            httpURLConnection.setDoInput(true);
            // 生成boundary,理論上只要不重複就行。
            String boundary = "JAVA-HttpUrlConnection-" +
                    UUID.randomUUID().toString().replace("-", "");
            String boundaryPrefix = "--";
            httpURLConnection.setRequestProperty(HttpHeaders.CONTENT_TYPE,
                    "multipart/form-data; boundary=" + boundary);

            // 3.開啟連線
            httpURLConnection.connect();

            // 4.是否需要使用OutputStream來向請求體中設定資料
            os = httpURLConnection.getOutputStream();
            /* 設定引數 */
            // 分割符
            os.write((boundaryPrefix + boundary + "\r\n").getBytes(StandardCharsets.UTF_8));
            // 資料頭
            os.write((HttpHeaders.CONTENT_DISPOSITION + ": form-data; name=\"fileName\"\r\n")
                    .getBytes(StandardCharsets.UTF_8));
            // 空行
            os.write("\r\n".getBytes(StandardCharsets.UTF_8));
            // 引數資料
            os.write(("urlConnection上傳的檔案.txt").getBytes(StandardCharsets.UTF_8));
            // 換行
            os.write("\r\n".getBytes(StandardCharsets.UTF_8));

            /* 設定檔案 */
            // 分割符
            os.write((boundaryPrefix + boundary + "\r\n").getBytes(StandardCharsets.UTF_8));
            // 資料頭
            os.write((HttpHeaders.CONTENT_DISPOSITION + ": form-data; name=\"file\"; filename=\"temp.txt\"\r\n")
                    .getBytes(StandardCharsets.UTF_8));
            // 空行
            os.write("\r\n".getBytes(StandardCharsets.UTF_8));
            // 檔案資料
            Files.copy(Paths.get("d:", "temp.txt"), os);
            // 換行
            os.write("\r\n".getBytes(StandardCharsets.UTF_8));

            // 結尾
            os.write((boundaryPrefix + boundary + boundaryPrefix).getBytes(StandardCharsets.UTF_8));

            // 5.從InputStream中獲取資料
            int responseCode = httpURLConnection.getResponseCode();
            if(responseCode != 200) {
                System.out.println("請求出錯了");
                return;
            }
            is = httpURLConnection.getInputStream();
            byte[] buf = new byte[1024];
            int len = 0;
            while ((len = is.read(buf)) > 0) {
                // 使用ByteArrayOutputStream來快取資料
                baos.write(buf, 0, len);
            }
            String retStr = new String(baos.toByteArray(), "utf-8");
            JSONObject jsonObject = JSON.parseObject(retStr);
            if (jsonObject.getInteger("statusCode") != 200) {
                throw new Exception(jsonObject.getString("message"));
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            // 6.關閉連線
            closeSomething(os);
            closeSomething(is);
            closeSomething(baos);
            if(null != httpURLConnection) {
                httpURLConnection.disconnect();
            }
        }
    }

上述程式碼偵錯了很多遍才成功。

其實就是要理解multipart/form-data這種型別資料的報文結構。

我嘗試把內容通過檔案的方式展示出來:

 所以一個part包含:

--${boundary}\r\n

資料頭\r\n

\r\n

資料部分\r\n

最後再以--${boundary}--結尾就可以了。

當然驗證截圖如下:

 

 

二、使用apache的httpclient元件傳送http請求

 講完上述第一部分,我相信我們對http協定本身常用的MIME型別細節已經熟悉了,甚至對java原生的傳送請求的api也熟悉了。

但是畢竟太複雜了,確實不友好。

現在我們講講apache的httpclient元件傳送http請求有多絲滑。

1.引入httpclient的依賴

我們引入4.x.x版本:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.14</version>
</dependency>

這是個經典的版本。

 

2.傳送get請求

我先貼出官網給的一個簡單例子。

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://targethost/homepage");
CloseableHttpResponse response1 = httpclient.execute(httpGet);
// The underlying HTTP connection is still held by the response object
// to allow the response content to be streamed directly from the network socket.
// In order to ensure correct deallocation of system resources
// the user MUST call CloseableHttpResponse#close() from a finally clause.
// Please note that if response content is not fully consumed the underlying
// connection cannot be safely re-used and will be shut down and discarded
// by the connection manager. 
try {
    System.out.println(response1.getStatusLine());
    HttpEntity entity1 = response1.getEntity();
    // do something useful with the response body
    // and ensure it is fully consumed
    EntityUtils.consume(entity1);
} finally {
    response1.close();
}

下面我們仿照著寫一個查詢會員列表的功能

程式碼:

@Test
public void testGetMemberList() throws Exception{
    CloseableHttpClient httpclient = HttpClients.createDefault();
    HttpGet httpGet = new HttpGet("http://localhost:8080/member");
    CloseableHttpResponse response1 = httpclient.execute(httpGet);
    ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
    try {
        System.out.println(response1.getStatusLine());
        HttpEntity entity1 = response1.getEntity();

        InputStream is = entity1.getContent();
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = is.read(buf)) > 0) {
            // 使用ByteArrayOutputStream來快取資料
            baos.write(buf, 0, len);
        }
        String retStr = new String(baos.toByteArray(), "utf-8");
        JSONObject jsonObject = JSON.parseObject(retStr);
        if (jsonObject.getInteger("statusCode") != 200) {
            throw new Exception(jsonObject.getString("message"));
        }
        System.out.println(retStr);
        EntityUtils.consume(entity1);
    } finally {
        response1.close();
        baos.close();
    }
}

 

 

列印截圖如下:

 

 

3.傳送post請求

3.1 post+param請求

直接上程式碼吧

@Test
public void testPostAddMember() throws Exception {
    CloseableHttpClient httpclient = HttpClients.createDefault();
    HttpPost httpPost = new HttpPost("http://localhost:8080/member/param");
    List<NameValuePair> nvps = new ArrayList<NameValuePair>();
    nvps.add(new BasicNameValuePair("id", "10"));
    nvps.add(new BasicNameValuePair("name", "使用httpClient+Post+Param新增"));
    nvps.add(new BasicNameValuePair("birthday", "2010-11-12"));
    nvps.add(new BasicNameValuePair("balance", "9999"));
    httpPost.setEntity(new UrlEncodedFormEntity(nvps));
    CloseableHttpResponse response2 = httpclient.execute(httpPost);
    ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
    try {
        System.out.println(response2.getStatusLine());
        HttpEntity entity2 = response2.getEntity();
        InputStream is = entity2.getContent();
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = is.read(buf)) > 0) {
            // 使用ByteArrayOutputStream來快取資料
            baos.write(buf, 0, len);
        }
        String retStr = new String(baos.toByteArray(), "utf-8");
        JSONObject jsonObject = JSON.parseObject(retStr);
        if (jsonObject.getInteger("statusCode") != 200) {
            throw new Exception(jsonObject.getString("message"));
        }
        System.out.println(retStr);
        EntityUtils.consume(entity2);
    } finally {
        response2.close();
        baos.close();
    }
}

新增成功的截圖:

 

3.2 使用get + param方式請求

程式碼如下:

    @Test
    public void testGetParamAddMember() throws Exception {
        String url = "http://localhost:8080/member/param";
        MemberVO memberVO = new MemberVO();
        memberVO.setId(11);
        memberVO.setBirthday("2010-11-11");
        memberVO.setBalance("2000");
        memberVO.setName("hc通過getParam方式新增");
        UriComponents urlCom = UriComponentsBuilder.fromHttpUrl(url)
                .queryParam("name", memberVO.getName())
                .queryParam("id", memberVO.getId())
                .queryParam("birthday", memberVO.getBirthday())
                .queryParam("balance", memberVO.getBalance())
                .build().encode(StandardCharsets.UTF_8);

        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(urlCom.toUriString());
        // 下面這種方式已經試過,不行
//        BasicHttpParams httpParams = new BasicHttpParams();
//        httpParams.setParameter("id", 11);
//        httpParams.setParameter("name", "使用hcGetParam方式新增");
//        httpParams.setParameter("birthday", "2011-03-11");
//        httpParams.setParameter("balance", 99999);
//        httpGet.setParams(httpParams);
        CloseableHttpResponse response2 = httpclient.execute(httpGet);
        ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
        try {
            System.out.println(response2.getStatusLine());
            HttpEntity entity2 = response2.getEntity();
            InputStream is = entity2.getContent();
            byte[] buf = new byte[1024];
            int len = 0;
            while ((len = is.read(buf)) > 0) {
                // 使用ByteArrayOutputStream來快取資料
                baos.write(buf, 0, len);
            }
            String retStr = new String(baos.toByteArray(), "utf-8");
            JSONObject jsonObject = JSON.parseObject(retStr);
            if (jsonObject.getInteger("statusCode") != 200) {
                throw new Exception(jsonObject.getString("message"));
            }
            System.out.println(retStr);
            EntityUtils.consume(entity2);
        } finally {
            response2.close();
            baos.close();
        }
    }

 

 

 

 

3.3 通過post+json方式新增會員

@Test
public void testPostJsonAddMember() throws Exception {
    // 獲取hc範例
    CloseableHttpClient httpclient = HttpClients.createDefault();
    // 構造HttpUriRequest範例
    HttpPost httpPost = new HttpPost("http://localhost:8080/member");
    // 設定entity
    MemberVO memberVO = new MemberVO();
    memberVO.setId(12);
    memberVO.setBirthday("2010-11-11");
    memberVO.setBalance("8888");
    memberVO.setName("hc通過PostJson方式新增");
    StringEntity stringEntity = new StringEntity(JSON.toJSONString(memberVO), "utf-8");
    stringEntity.setContentType(MediaType.APPLICATION_JSON_VALUE);
    httpPost.setEntity(stringEntity);
    // 傳送請求
    CloseableHttpResponse response2 = httpclient.execute(httpPost);
    ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
    try {
        System.out.println(response2.getStatusLine());
        // 獲取響應實體
        HttpEntity entity2 = response2.getEntity();
        InputStream is = entity2.getContent();
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = is.read(buf)) > 0) {
            // 使用ByteArrayOutputStream來快取資料
            baos.write(buf, 0, len);
        }
        String retStr = new String(baos.toByteArray(), "utf-8");
        JSONObject jsonObject = JSON.parseObject(retStr);
        if (jsonObject.getInteger("statusCode") != 200) {
            throw new Exception(jsonObject.getString("message"));
        }
        System.out.println(retStr);
        EntityUtils.consume(entity2);
    } finally {
        response2.close();
        baos.close();
    }
}

驗證結果:

 

 

4.檔案上傳功能

程式碼基本是參照官網給的example來編寫的,這裡要強調,需要引入一個依賴:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpmime</artifactId>
    <version>4.5.14</version>
</dependency>

java程式碼如下:

@Test
public void hcUploadFileTest() throws Exception{
    CloseableHttpClient httpclient = HttpClients.createDefault();
    try {
        HttpPost httppost = new HttpPost("http://localhost:8080/member/fileUpload");
        FileBody file = new FileBody(Paths.get("d:", "temp.txt").toFile());
        StringBody fileName = StringBody.create("hc傳入的檔名稱.txt", ContentType.TEXT_PLAIN.getMimeType(),
                Charset.forName("utf-8"));

        HttpEntity reqEntity = MultipartEntityBuilder.create()
                .addPart("file", file)
                .addPart("fileName", fileName)
                .build();


        httppost.setEntity(reqEntity);

        System.out.println("executing request " + httppost.getRequestLine());
        CloseableHttpResponse response = httpclient.execute(httppost);
        try {
            System.out.println("----------------------------------------");
            System.out.println(response.getStatusLine());
            HttpEntity resEntity = response.getEntity();
            if (resEntity != null) {
                System.out.println("Response content length: " + resEntity.getContentLength());
            }
            EntityUtils.consume(resEntity);
        } finally {
            response.close();
        }
    } finally {
        httpclient.close();
    }
}

 

我先把其列印的請求報文紀錄檔貼出來,更加能幫助你理解multipart/form-data的報文結構:

06:21:07.936 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> POST /member/fileUpload HTTP/1.1
06:21:07.936 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Content-Length: 426
06:21:07.936 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Content-Type: multipart/form-data; boundary=RrxE9BM4vDUS-0Liy4BUeB4WldSN9gub
06:21:07.936 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Host: localhost:8080
06:21:07.936 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Connection: Keep-Alive
06:21:07.936 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.14 (Java/1.8.0_211)
06:21:07.936 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept-Encoding: gzip,deflate
06:21:07.937 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "POST /member/fileUpload HTTP/1.1[\r][\n]"
06:21:07.937 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Content-Length: 426[\r][\n]"
06:21:07.937 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Content-Type: multipart/form-data; boundary=RrxE9BM4vDUS-0Liy4BUeB4WldSN9gub[\r][\n]"
06:21:07.937 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Host: localhost:8080[\r][\n]"
06:21:07.937 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
06:21:07.937 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.14 (Java/1.8.0_211)[\r][\n]"
06:21:07.937 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
06:21:07.937 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
06:21:07.937 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "--RrxE9BM4vDUS-0Liy4BUeB4WldSN9gub[\r][\n]"
06:21:07.937 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Content-Disposition: form-data; name="file"; filename="temp.txt"[\r][\n]"
06:21:07.937 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Content-Type: application/octet-stream[\r][\n]"
06:21:07.937 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Content-Transfer-Encoding: binary[\r][\n]"
06:21:07.938 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
06:21:07.938 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "aabb[\n]"
06:21:07.938 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "bbaad[\n]"
06:21:07.938 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "cc[\n]"
06:21:07.938 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "dd[\n]"
06:21:07.938 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
06:21:07.938 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "--RrxE9BM4vDUS-0Liy4BUeB4WldSN9gub[\r][\n]"
06:21:07.938 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Content-Disposition: form-data; name="fileName"[\r][\n]"
06:21:07.938 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Content-Type: text/plain; charset=UTF-8[\r][\n]"
06:21:07.938 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Content-Transfer-Encoding: 8bit[\r][\n]"
06:21:07.938 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
06:21:07.938 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "hc[0xe4][0xbc][0xa0][0xe5][0x85][0xa5][0xe7][0x9a][0x84][0xe6][0x96][0x87][0xe4][0xbb][0xb6][0xe5][0x90][0x8d][0xe7][0xa7][0xb0].txt"
06:21:07.939 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
06:21:07.939 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "--RrxE9BM4vDUS-0Liy4BUeB4WldSN9gub--[\r][\n]"

 

看到這裡,發現httpclient傳送http請求的確簡單了很多。

 

三、使用spring的RestTemplate傳送http請求

 RestTemplate其實是spring提供的一種高階的api去發起http請求,我們可以參考官網對RestTemplate的解釋:

 意思就是說,預設情況RestTemplate使用了HttpURLConnection作為其底層的實現。

但是也可以自己切換到其他庫,只要這些庫實現了ClientHttpRequestFactory就可以。

比如:Apache HttpComponents,netty,OkHttp等。這裡說的Apache HttpComponents你可以理解成就是我們上面講到的httpclient。

1.使用預設的實現來傳送請求

1.1 傳送get+param請求增加會員資料

程式碼如下:

@Test
public void testGetUseParamAddMember() {
    String url = "http://localhost:8080/member/param";
    MemberVO memberVO = new MemberVO();
    memberVO.setId(100);
    memberVO.setBirthday("2010-11-11");
    memberVO.setBalance("9888");
    memberVO.setName("通過restTemplate的get+param方式新增");
    UriComponents urlCom = UriComponentsBuilder.fromHttpUrl(url)
            .queryParam("name", memberVO.getName())
            .queryParam("id", memberVO.getId())
            .queryParam("birthday", memberVO.getBirthday())
            .queryParam("balance", memberVO.getBalance())
            .build().encode(StandardCharsets.UTF_8);
    String retStr = restTemplate.getForObject(urlCom.toUriString(), String.class);
    if (StringUtils.isEmpty(retStr)) {
        throw new RuntimeException("");
    }
    JSONObject jsonObject = JSON.parseObject(retStr);
    if (jsonObject.getInteger("statusCode") != null
            && 200 != jsonObject.getInteger("statusCode")) {
        throw new RuntimeException("");
    }
    System.out.println(retStr);
}

 

成功截圖:

 

但是有問題,我們看看查詢到的列表:

 這條資料是有很大問題的,如果url傳送前沒有編碼的話,呼叫是不會成功的,那這是為什麼呢?

看看下面的截圖:

 得到的結論就是:如果在使用RestTemplate的api的時候,如果傳入uri物件範例,那麼其內部不會進行uri編碼操作,而如果傳入的是string,那麼

內部會自動進行一次uri編碼。而在我們程式碼裡面,傳入string之前,自己進行了一次編碼,而api內部又進行了一次編碼,所以有問題。

所以我們發現,api中,一旦傳入的是uri物件,後面的引數值就沒有了,意味著自己必須提前準備好uri。

 

1.2 get+param請求的問題解決

(1)嘗試傳入uri物件

// 構造UriComponents的時候編碼
UriComponents urlCom = UriComponentsBuilder.fromHttpUrl(url)
        .queryParam("name", memberVO.getName())
        .queryParam("id", memberVO.getId())
        .queryParam("birthday", memberVO.getBirthday())
        .queryParam("balance", memberVO.getBalance())
        .encode()
        .build();
// 傳入uri物件
String retStr = restTemplate.getForObject(urlCom.toUri(), String.class);

 

(2)傳入string但是自己首先不編碼

// 構造UriComponents的時候不進行編碼
UriComponents urlCom = UriComponentsBuilder.fromHttpUrl(url)
        .queryParam("name", memberVO.getName())
        .queryParam("id", memberVO.getId())
        .queryParam("birthday", memberVO.getBirthday())
        .queryParam("balance", memberVO.getBalance())
        .build();
// 傳入的string是未編碼的
String retStr = restTemplate.getForObject(urlCom.toUriString(), String.class);

 

 1.3 傳送get+param請求增加會員資料新寫法

首先我們要為RestTemplate設定一個UriTemplateHandler範例。

@BeforeAll
public static void init() {
    restTemplate = new RestTemplate();
    String baseUrl = "http://localhost:8080";
    DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
    factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.TEMPLATE_AND_VALUES);
    restTemplate.setUriTemplateHandler(factory);
}

該設定已經把請求地址的基礎部分設定好了,並設定了地址模板的處理模式。

其次在發起呼叫的時候加上對應的引數值就行了。

@Test
public void testGetUseParamAddMember2() {
    String uri = "/member/param?id={a}&name={b}&birthday={c}&balance={d}";
    String retStr = restTemplate.getForObject(uri, String.class,
            101, "rtGetParam使用模板方式新增", "2001-10-23", 998);
    if (StringUtils.isEmpty(retStr)) {
        throw new RuntimeException("");
    }
    JSONObject jsonObject = JSON.parseObject(retStr);
    if (jsonObject.getInteger("statusCode") != null
            && 200 != jsonObject.getInteger("statusCode")) {
        throw new RuntimeException("");
    }
    System.out.println(retStr);
}

  

程式碼執行沒問題,而且設定了這個UriTemplateHandler範例和基地址,RestTemplate同樣可以傳入絕對地址進行呼叫。

 

1.4 傳送put請求修改資料

@Test
public void testGetUseParamAddMemberPrepareUri() {
    String uri = "/member/{1}?id={2}&name={3}&birthday={4}&balance={5}";
    URI expand = restTemplate.getUriTemplateHandler().expand(uri,
            100, 100, "put+param修改之後", "2019-10-10", 999);
    restTemplate.put(expand, null);
}

修改後:

 1.5 傳送post+param請求新增會員

程式碼如下:

@Test
public void testPostUseParamAddMember() {
    // 地址資訊
    String url = "http://localhost:8080/member/param";
    // 使用post傳送資料要有請求體
    LinkedMultiValueMap<String,Object> param = new LinkedMultiValueMap<>();
    param.add("id", 102);
    param.add("name", "通過RT的post+param新增");
    param.add("birthday", "2001-03-09");
    param.add("balance", 99999);

    RequestEntity<LinkedMultiValueMap<String,Object>> requestEntity = RequestEntity.post(url)
            .body(param);
    String s = restTemplate.postForObject(url, requestEntity, String.class);
    System.out.println(s);
}

驗證成功。

 

1.6 傳送post+json新增會員資料

程式碼如下:

@Test
public void testPostJsonAddMember() {
    // 地址資訊
    String url = "http://localhost:8080/member";
    // 使用post傳送資料要有請求體
    MemberVO memberVO = new MemberVO();
    memberVO.setId(103);
    memberVO.setBirthday("2010-11-11");
    memberVO.setBalance("12356");
    memberVO.setName("rt的post+json新增");
    RequestEntity<String> requestEntity = RequestEntity.post(url)
            .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .body(JSON.toJSONString(memberVO));
    String s = restTemplate.postForObject(url, requestEntity, String.class);
    System.out.println(s);
}

驗證成功。

 

1.7 檔案上傳

程式碼如下:

@Test
public void testFileUpload() {
    // 地址資訊
    String url = "http://localhost:8080/member/fileUpload";
    // 使用multipart/form-data傳送資料
    // 1.準備好body
    MultiValueMap<String,Object> allParts = new LinkedMultiValueMap<>();
    allParts.add("fileName", "使用rt上傳的檔案.txt");
    allParts.add("file", new FileSystemResource(Paths.get("d:", "temp.txt")));
    // 2.準備request物件
    RequestEntity<MultiValueMap<String, Object>> requestEntity = RequestEntity.post(url)
            .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
            .body(allParts);
    // 3.傳送請求
    String s = restTemplate.postForObject(url, requestEntity, String.class);
    System.out.println(s);
}

 

傳送成功:

 

 

2.切換到其他實現

 2.1 設定內部使用httpclient傳送請求

設定的程式碼修改:

@BeforeAll
public static void init() {
    restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
    String baseUrl = "http://localhost:8080";
    DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
    factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.TEMPLATE_AND_VALUES);
    restTemplate.setUriTemplateHandler(factory);
}

 

2.2 設定之後呼叫會員詳情介面

獲取會員詳情程式碼 

@Test
public void testMemberDetail() {
    // 獲取會員資訊
    String uri = "/member/{id}";
    String retString = restTemplate.getForObject(uri, String.class, 100);
    // 使用TypeRefercece在反序列化的時候獲取泛型引數
    NormalResponseObject<MemberVO> resObj =
            JSON.parseObject(retString, new TypeReference<NormalResponseObject<MemberVO>>() {
    });
    System.out.println(resObj.getData());
}

 

看紀錄檔列印如下:

 看上述紀錄檔就知道httpclient生效了,如何設定okHttp,一起在下面介紹吧。

 

四、使用okHttp元件傳送http請求

 1.okHttp的概述

  這是該框架的地址:https://square.github.io/okhttp/

 

OkHttp是一個預設情況下高效的HTTP使用者端:(要求jdk1.8及以上版本

  • HTTP/2支援允許對同一主機的所有請求共用一個通訊端。
  • 連線池減少了請求延遲(如果HTTP/2不可用)。
  • 透明的GZIP縮小了下載量。
  • 響應快取完全避免了網路重複請求。

2.簡單使用

引入依賴:

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

2.1 get獲取會員資料

@Test
public void getAllMembers() {
    // 準備url
    String url = "http://localhost:8080/member";

    // 建立client物件
    OkHttpClient client = new OkHttpClient();

    // 建立請求物件
    Request request = new Request.Builder()
            .url(url)
            .build();
    // 發起請求
    try (Response response = client.newCall(request).execute()) {
        String retStr = response.body().string();
        NormalResponseObject<List<MemberVO>> allMemberRes =
                JSON.parseObject(retStr, new TypeReference<NormalResponseObject<List<MemberVO>>>() {
        });
        if(allMemberRes.getStatusCode() != 200) {
            throw new RuntimeException("");
        }
        List<MemberVO> memberList = allMemberRes.getData();
        memberList.forEach(System.out::println);
    }catch (Exception e) {
        e.printStackTrace();
    }
}

這個api看起來更加簡潔了。

 

2.2 增加會員資料

@Test
public void testPostParamAddMember() {
    // 準備url
    String url = "http://localhost:8080/member/param";

    // 建立client物件
    OkHttpClient client = new OkHttpClient();

    // 建立請求物件
    RequestBody formBody = new FormBody.Builder()
            .add("id", "1000")
            .add("name", "okHttpPostParam新增")
            .add("birthday", "1990-10-23")
            .add("balance", "99981")
            .build();
    Request request = new Request.Builder()
            .url(url)
            .post(formBody)
            .build();
    // 發起請求
    try (Response response = client.newCall(request).execute()) {
        String retStr = response.body().string();
        System.out.println(retStr);
    }catch (Exception e) {
        e.printStackTrace();
    }
}

 

 

2.3 檔案操作

程式碼如下:

@Test
public void testFileOperation() throws Exception{
    // 建立client物件
    OkHttpClient client = new OkHttpClient();

    // 構建formData的body
    RequestBody requestBody = new MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("fileName", "由okHttp上傳的檔案.txt")
            .addFormDataPart("file", "temp.txt",
                    RequestBody.create(Paths.get("d:", "temp.txt").toFile(),
                            MediaType.get("text/plain")))
            .build();
    // 構建請求物件
    Request request = new Request.Builder()
            .url("http://localhost:8080/member/fileUpload")
            .post(requestBody)
            .build();
    // 傳送
    try (Response response = client.newCall(request).execute()) {
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

        System.out.println(response.body().string());
    }
}

甚至在使用的時候都不用自己設定請求頭就搞定了。

 

3.如何在RestTemplate中使用okHttp元件

在spring類庫搜尋ClientHttpRequestFactory的實現類。

目前我本地spring版本是5.3.0,使用springboot的版本是2.5.5版本。

找到了下面的類:

 很明顯,該類是為了適配3.x的okHttp而設計的。我本地是okHttp是4.5.0,我先試試:

 呼叫成功了,debug跟蹤呼叫流程:

 

確實使用了OkHttp的功能。

 

 

到此本文就結束了,來做一個小小總結吧:

我首先是使用springboot建立了一個小專案,提供了一些我們常用的http介面定義,其中包含了get,post,put,delete和檔案相關的操作。

然後為了驗證這些介面的正確性,我寫了一個小頁面,引入Jquery來傳送ajax請求,驗證了每個介面是否正確能實現業務邏輯。

所以本文也適合前端開發的人看看,一方面是瞭解http協定常見的這些介面定義和內涵,另一方面也可以學到傳送ajax請求的簡單寫法。

其次,我分別從幾個方面寫了java語言發起http請求的程式碼:

一個是原生的基於HttpURLConnection的方式,

一個是使用apache 的Http Compoments專案中的HttpClient傳送http請求

一個是使用RestTemplate傳送http請求

最後使用okHttp傳送http請求。

其實RestTemplate只是一個殼子,具體裡面使用啥工作,取決於我們設定的RestTemplate物件。

本文沒有具體細講HttpClient和okHttp框架的設定細節。以後有機會再寫吧,總之,希望對大家有幫助吧,謝謝。