JDK 8 以前的 HTTP Client 通常是使用第三方函式庫,最常使用的是 Apache HTTPComponents 及 OkHttp。在 JDK 9 以後,標準函式庫裡面也有 HTTP Client 可以使用。
程式包含三個部分:HttpRequest, HttpResponse 及 HttpClient
HttpRequst
URI 就是 request 要發送的目標網址
HTTP Method
- GET()
- POST(BodyPublisher body)
- PUT(BodyPublisher body)
- DELETE()
HTTP Protcol Version
可指定這個 http request 的版本,以往常見的是 1.1,目前是 2
Headers
設定 http request header
Timeout
設定等待 http response 的 timeout 時間
Request Body
如果是 POST, PUT, DELETE method,需要再增加 body content,對應的 content
Request Body
可使用以下這些 API 實作 body content
StringProcessor
從 String 產生 body,以 HttpRequest.BodyPublishers.ofString 產生
HttpRequest request = HttpRequest.newBuilder() .uri(new URI("https://postman-echo.com/post")) .headers("Content-Type", "text/plain;charset=UTF-8") .POST(HttpRequest.BodyPublishers.ofString("Sample request body")) .build();
IputStreamProcessor
從 InputSteam 產生 body,以 HttpRequest.BodyPublishers.ofInputStream 產生
byte[] sampleData = "Sample request body".getBytes(); HttpRequest request = HttpRequest.newBuilder() .uri(new URI("https://postman-echo.com/post")) .headers("Content-Type", "text/plain;charset=UTF-8") .POST(HttpRequest.BodyPublishers .ofInputStream(() -> new ByteArrayInputStream(sampleData))) .build();
ByteArrayProcessor
從 ByteArray 產生 Body,以 HttpRequest.BodyPublishers.ofByteArray 產生
byte[] sampleData = "Sample request body".getBytes(); HttpRequest request = HttpRequest.newBuilder() .uri(new URI("https://postman-echo.com/post")) .headers("Content-Type", "text/plain;charset=UTF-8") .POST(HttpRequest.BodyPublishers.ofByteArray(sampleData)) .build();
FileProcessor
從某個路徑的檔案產生 body,以 HttpRequest.BodyPublishers.ofFile 產生
HttpRequest request = HttpRequest.newBuilder() .uri(new URI("https://postman-echo.com/post")) .headers("Content-Type", "text/plain;charset=UTF-8") .POST(HttpRequest.BodyPublishers.ofFile( Paths.get("sample.txt"))) .build();
noBody
如果沒有 body content,可使用 HttpRequest.BodyPublishers.**noBody()
HttpRequest request = HttpRequest.newBuilder() .uri(new URI("https://postman-echo.com/post")) .POST(HttpRequest.BodyPublishers.noBody()) .build();
HttpClient
透過 HttpClient.newBuilder() 或是 HttpClient.newHttpClient() 產生 instance
透過 Handler 處理 response body
BodyHandlers.ofByteArray BodyHandlers.ofString BodyHandlers.ofFile BodyHandlers.discarding BodyHandlers.replacing BodyHandlers.ofLines BodyHandlers.fromLineSubscriber // jdk 11 以前 HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.asString()); // 新的 jdk HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
Proxy
可定義 proxy
HttpResponse<String> response = HttpClient .newBuilder() .proxy(ProxySelector.getDefault()) .build() .send(request, BodyHandlers.ofString());
Direct Policy
如果 reponse 收到 3XX 的 redirect 結果,可設定 redirect policy 直接轉址
HttpResponse<String> response = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.ALWAYS) .build() .send(request, BodyHandlers.ofString());
HTTP Authentication
HttpResponse<String> response = HttpClient.newBuilder() .authenticator(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication( "username", "password".toCharArray()); } }).build() .send(request, BodyHandlers.ofString());
Cookie
// 透過 cookieHandler(CookieHandler cookieHandler) 定義 CookieHandler // 設定不接受 cookie HttpClient.newBuilder() .cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_NONE)) .build(); // 取得 CookieStore ((CookieManager) httpClient.cookieHandler().get()).getCookieStore()
SSL Context
在 HttpClient 指定 SSL Context,忽略 ssl key 檢查
private static SSLContext disabledSSLContext() throws KeyManagementException, NoSuchAlgorithmException { SSLContext sslContext = SSLContext.getInstance("TLS"); // https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#sslcontext-algorithms sslContext.init( null, new TrustManager[] { new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] certs, String authType) { } public void checkServerTrusted(X509Certificate[] certs, String authType) { } } }, new SecureRandom() ); return sslContext; }
Sync or Async 同步或是非同步呼叫
HttpClient 有同步或非同步的發送 request 的方式
send 同步
程式會停在這邊,等待 response 或是 timeout,下一行,可直接取得 response body
HttpResponse<String> response = HttpClient.newBuilder() .build() .send(request, BodyHandlers.ofString());
sendAsync 非同步 non-blocking
CompletableFuture<HttpResponse<String>> response = HttpClient.newBuilder() .build() .sendAsync(request, HttpResponse.BodyHandlers.ofString());
可指定 Executor 限制 threads
預設是使用 java.util.concurrent.Executors.newCachedThreadPool()
ExecutorService executorService = Executors.newFixedThreadPool(2); CompletableFuture<HttpResponse<String>> response1 = HttpClient.newBuilder() .executor(executorService) .build() .sendAsync(request, HttpResponse.BodyHandlers.ofString()); CompletableFuture<HttpResponse<String>> response2 = HttpClient.newBuilder() .executor(executorService) .build() .sendAsync(request, HttpResponse.BodyHandlers.ofString());
HttpResponse
URI
HttpResponse 也有一個 uri() method,可取得 uri,因為有時候會遇到 redirect uri 的回應,因此 response 的 uri,會取得 redirect 後的網址
Response Header
取得 response header list
HttpResponse<String> response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); HttpHeaders responseHeaders = response.headers();
Http Version
server 是以哪一個 http version 回應的
response.version();
Response Body
String body = response.body();
完整 Java Code
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Paths;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class HttpClientTest {
public static void main(String[] args) throws Exception {
// 建立HttpClient實例
HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1) // http 1.1
.connectTimeout(Duration.ofSeconds(5)) // timeout after 5 seconds
.sslContext(disabledSSLContext()) // disable SSL verify
.build();
// 建立 HttpRequest請求
// HttpRequest request = getMethod();
// HttpRequest request = postNoBody();
HttpRequest request = postStringBody();
// HttpRequest request = postInputStreamBody();
// HttpRequest request = postByteArrayBody();
// HttpRequest request = postFileBody();
// 錯誤的 URI 測試 Timeout
// HttpRequest request = postStringBody_InvalidUri();
// Sync 同步呼叫
// sync(httpClient, request);
// 非同步呼叫
async(httpClient, request);
}
private static void async(HttpClient httpClient, HttpRequest request) throws InterruptedException, java.util.concurrent.ExecutionException, java.util.concurrent.TimeoutException {
// 非同步
CompletableFuture<HttpResponse<String>> response = httpClient
.sendAsync(request, HttpResponse.BodyHandlers.ofString());
String result = response
.thenApply(HttpResponse::body)
.exceptionally(t -> {
t.printStackTrace();
return "fallback";
})
.get(10, TimeUnit.SECONDS);
System.out.println(result);
}
private static void sync(HttpClient httpClient, HttpRequest request) throws java.io.IOException, InterruptedException {
// 發送請求並接收回應
HttpResponse<String> response = httpClient.send(
request, HttpResponse.BodyHandlers.ofString());
HttpClient.Version version = response.version();
System.out.println("---response version---");
System.out.println(version);
System.out.println("---response headers---");
HttpHeaders responseHeaders = response.headers();
Map<String, List<String>> responseHeadersMap = responseHeaders.map();
for (String key : responseHeadersMap.keySet()) {
System.out.println(key + ":" + responseHeadersMap.get(key));
}
// 取得回應主體內容
String body = response.body();
System.out.println("---response body---");
System.out.println(body);
}
private static HttpRequest getMethod() {
// 臺灣證券交易所0056個股日成交資訊API
String url = "https://www.twse.com.tw/exchangeReport/STOCK_DAY?response=json&date=20230531&stockNo=0056";
// 建立 HttpRequest請求 get method
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.version(HttpClient.Version.HTTP_2)
.header("cache-control", "no-cache")
.GET()
.build();
return request;
}
private static HttpRequest postNoBody() throws URISyntaxException {
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/post"))
.POST(HttpRequest.BodyPublishers.noBody())
.build();
return request;
}
private static HttpRequest postStringBody() throws URISyntaxException {
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofString("Sample request body"))
.build();
return request;
}
private static HttpRequest postInputStreamBody() throws URISyntaxException {
byte[] sampleData = "Sample request body".getBytes();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers
.ofInputStream(() -> new ByteArrayInputStream(sampleData)))
.build();
return request;
}
private static HttpRequest postByteArrayBody() throws URISyntaxException {
byte[] sampleData = "Sample request body".getBytes();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofByteArray(sampleData))
.build();
return request;
}
private static HttpRequest postFileBody() throws URISyntaxException, FileNotFoundException {
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofFile(
Paths.get("sample.txt")))
.build();
return request;
}
private static HttpRequest postStringBody_InvalidUri() throws URISyntaxException {
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://test.com/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofString("Sample request body"))
.build();
return request;
}
private static SSLContext disabledSSLContext() throws KeyManagementException, NoSuchAlgorithmException {
SSLContext sslContext = SSLContext.getInstance("TLS");
// https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#sslcontext-algorithms
sslContext.init(
null,
new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
},
new SecureRandom()
);
return sslContext;
}
}
沒有留言:
張貼留言