串接第三方 API,使用 HttpClient HttpGet 發出 GET 請求,取得使用者資訊及列表,返回 HTTP 200 成功,接收所提供的 ErrorCode 等資訊, EP 22-2 增加 GggClient 範例 ,並透過 JUnit 5 來驗證產出結果。
Table of Contents
Toggle前言
HttpClient 是一套支援 HTTP 協議的用戶端程式庫,實現了所有 HTTP 的方法,如: GET 、 POST 、PUT 等,以及支援自動轉向與代理服務器等,提供了許多高效率的類別。
HttpClient GET
檔案目錄
./
+- build.gradle
+- src
+- main
+- java
+- org
+- ruoxue
+- spring_boot_168
+- game
+- ggg
+- client
| +- GggClient.java
+- ex
| +- GggException.java
+- model
| +- GggReponse.java
| +- GggListReponse.java
設定
Java HttpClient GET JSON
Function | Method | Path | Content-Type | Params | Description |
使用者資訊 | GET | /user | application/x-www-form-urlencoded | username | 使用者名稱 |
Reponse | {"errorCode":0,"name":"player"} | ||||
使用者列表 | GET | /users | application/x-www-form-urlencoded | page | 頁碼 |
size | 筆數 | ||||
Reponse | {"errorCode":0,"names":[player, player2]} | ||||
使用者是否存在 | GET | /exist | application/x-www-form-urlencoded | username | 使用者名稱 |
password | 密碼 | ||||
Reponse | {"errorCode": 0,"exist": false} |
實作
GggReponse.java
新增檔案,接收回應,定義 errorCode 、 token 、 name 。
package org.ruoxue.spring_boot_168.game.ggg.model;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class GggReponse {
/** 錯誤碼 */
private int errorCode;
/** 名稱 */
private String name;
/** 是否存在 */
private boolean exist;
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.JSON_STYLE);
builder.appendSuper(super.toString());
builder.append("errorCode", errorCode);
builder.append("name", name);
builder.append("exist", exist);
return builder.toString();
}
}
GggException.java
新增檔案,自定義例外,當發生錯誤時,拋出此例外,讓外層調用的服務,處理例外,如:記錄日誌。
package org.ruoxue.spring_boot_168.game.ggg.ex;
public class GggException extends RuntimeException {
private static final long serialVersionUID = 2209749235554430258L;
public GggException() {
super();
}
public GggException(String message) {
super(message);
}
public GggException(Throwable cause) {
super(cause);
}
public GggException(String message, Throwable cause) {
super(message, cause);
}
}
HttpClient HttpGet
請求參數,接在 URL 後面,如: API_URL + USER + “?username=” + username 。
Apache HttpGet
使用 URIBuilder 設定請求參數,如: setParameter(“page”, String.valueOf(page) 。
GggClient.java
新增檔案,調用第三方 API 客戶端。
package org.ruoxue.spring_boot_168.game.ggg.client;
import java.net.URI;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.ruoxue.spring_boot_168.game.ggg.ex.GggException;
import org.ruoxue.spring_boot_168.game.ggg.model.GggListReponse;
import org.ruoxue.spring_boot_168.game.ggg.model.GggReponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
public class GggClient {
/** API 網址 */
public static final String API_URL = "http://ggg.cc:10090";
/** 使用者資訊 */
public static final String USER = "/user";
/** 使用者列表 */
public static final String USERS = "/users";
/** 使用者是否存在 */
public static final String EXIST = "/exist";
@Autowired
@Qualifier("closeableHttpClient")
private CloseableHttpClient httpClient;
private static final Gson gson = new Gson();
/**
* 使用者資訊
*
* Content-Type: application/x-www-form-urlencoded
*
* @param username
* @return
* @throws Exception
*/
public GggReponse user(String username) throws Exception {
GggReponse result = null;
try {
String requestUrl = API_URL + USER + "?username=" + username;
HttpGet httpGet = new HttpGet(requestUrl);
httpGet.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
ResponseHandler<String> responseHandler = response -> {
int status = response.getStatusLine().getStatusCode();
if (status >= HttpStatus.SC_OK && status < HttpStatus.SC_MULTIPLE_CHOICES) {
HttpEntity entity = response.getEntity();
return (entity != null ? EntityUtils.toString(entity) : null);
} else {
log.error("statusCode: " + status);
log.error("statusLine: " + response.getStatusLine());
throw new ClientProtocolException("Unexpected response status: " + status);
}
};
log.info("requestUrl: " + requestUrl);
String body = httpClient.execute(httpGet, responseHandler);
if (StringUtils.isNotEmpty(body)) {
result = gson.fromJson(body, GggReponse.class);
} else {
throw new GggException("ERRORS_NOT_EXIST");
}
} catch (Exception ex) {
throw ex;
}
return result;
}
/**
* 使用者列表
*
* Content-Type: application/x-www-form-urlencoded
*
* @param page
* @param size
* @return
* @throws Exception
*/
public GggListReponse users(int page, int size) throws Exception {
GggListReponse result = null;
try {
String requestUrl = API_URL + USERS;
URI uri = new URIBuilder(requestUrl) //
.setParameter("page", String.valueOf(page)) //
.setParameter("size", String.valueOf(size)) //
.build();
HttpGet httpGet = new HttpGet(uri);
httpGet.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
ResponseHandler<String> responseHandler = response -> {
int status = response.getStatusLine().getStatusCode();
if (status >= HttpStatus.SC_OK && status < HttpStatus.SC_MULTIPLE_CHOICES) {
HttpEntity entity = response.getEntity();
return (entity != null ? EntityUtils.toString(entity) : null);
} else {
log.error("statusCode: " + status);
log.error("statusLine: " + response.getStatusLine());
throw new ClientProtocolException("Unexpected response status: " + status);
}
};
log.info("requestUrl: " + requestUrl);
String body = httpClient.execute(httpGet, responseHandler);
if (StringUtils.isNotEmpty(body)) {
result = gson.fromJson(body, GggListReponse.class);
} else {
throw new GggException("ERRORS_NOT_EXIST");
}
} catch (Exception ex) {
throw ex;
}
return result;
}
/**
* 使用者是否存在
*
* Content-Type: application/x-www-form-urlencoded
*
* @param username
* @return
* @throws Exception
*/
public GggReponse exist(String username, String password) throws Exception {
GggReponse result = null;
try {
String requestUrl = API_URL + EXIST + "?username=" + username + "&password=" + password;
HttpGet httpGet = new HttpGet(requestUrl);
httpGet.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
ResponseHandler<String> responseHandler = response -> {
int status = response.getStatusLine().getStatusCode();
if (status >= HttpStatus.SC_OK && status < HttpStatus.SC_MULTIPLE_CHOICES) {
HttpEntity entity = response.getEntity();
return (entity != null ? EntityUtils.toString(entity) : null);
} else {
log.error("statusCode: " + status);
log.error("statusLine: " + response.getStatusLine());
throw new ClientProtocolException("Unexpected response status: " + status);
}
};
log.info("requestUrl: " + requestUrl);
String body = httpClient.execute(httpGet, responseHandler);
if (StringUtils.isNotEmpty(body)) {
result = gson.fromJson(body, GggReponse.class);
} else {
throw new GggException("ERRORS_NOT_EXIST");
}
} catch (Exception ex) {
throw ex;
}
return result;
}
}
測試 JUnit 5
GggClientTest.java
新增單元測試,驗證是否符合預期 。
package org.ruoxue.spring_boot_168.game.ggg.client;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.ruoxue.spring_boot_168.Application;
import org.ruoxue.spring_boot_168.game.ggg.model.GggListReponse;
import org.ruoxue.spring_boot_168.game.ggg.model.GggReponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = Application.class)
public class GggClientTest {
@Autowired
private GggClient client;
@Test
public void client() {
System.out.println(client);
assertNotNull(client);
}
@Test
public void user() throws Exception {
GggReponse gggReponse = client.user("ruoxue");
System.out.println(gggReponse);
assertNotNull(gggReponse);
assertEquals(0, gggReponse.getErrorCode());
}
@Test
public void users() throws Exception {
GggListReponse gggListReponse = client.users(0, 10);
System.out.println(gggListReponse);
assertNotNull(gggListReponse);
assertEquals(0, gggListReponse.getErrorCode());
assertEquals(2, gggListReponse.getNames().size());
}
@Test
public void exist() throws Exception {
GggReponse gggReponse = client.exist("ruoxue","1111");
System.out.println(gggReponse);
assertNotNull(gggReponse);
assertFalse(gggReponse.isExist());
}
}
user
測試方法上點右鍵執行 Run As -> JUnit Test ,查看 console 。
2022-07-15T15:39:03.321+0800 [main] INFO GggClient#user:59 - requestUrl: http://ggg.cc:10090/user
{"errorCode":0,"name":"player"}
users
測試方法上點右鍵執行 Run As -> JUnit Test ,查看 console 。
2022-07-15T15:39:03.321+0800 [main] INFO GggClient#users:59 - requestUrl: http://ggg.cc:10090/users
{"errorCode":0,"names":[player, player2]}
exist
測試方法上點右鍵執行 Run As -> JUnit Test ,查看 console 。
{"errorCode": 0,"exist": false}
心得分享
使用 Nginx 模擬第三方 API,來協助快速開發,從連接池取得連線,建立 Apache HttpGet ,設定 Content-Type ,及增加請求參數, 發送 GET 請求,然後會返回一個 HttpResponse 物件,封裝了 Server 的回應,並且可以透過該物件取得 HTTP 狀態碼,回應內容等。