前言
每种编程语言里最常用的库恐怕是Http请求库了,如python里的requests包,nodejs里的request模块。
在Java世界里,也是百花齐放,山头林立。常用的有:
- HttpURLConnection: 最早的JDK提供的类
- Java 11提供的HttpClient
- Apache HttpComponents项目中的HTTPClient
- Square提供的OkHttpClient
- Spring 自带的WebClient
本文着重介绍JDK 11的HttpClient。
Apache HttpComponents
该组件提供了两个核心类:
- HttpCore: 更底层的传输处理类
- HttpClient:基于HttpCore实现的HTTP-compliant 处理类
JDK 11 HTTP Client使用举例
JDK 11开始新增了HttpClient这个组件,方便我们发送同步或异步的http请求。
Post同步的json数据
public void invokePost() {
try {
String requestBody = prepareRequest();
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest
.newBuilder()
.uri(URI.create("https://reqbin.com/echo/post/json"))
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.header("Accept", "application/json")
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
private String prepareRequest() throws JsonProcessingException {
var values = new HashMap<String, String>() {
{
put("Id", "12345");
put("Customer", "Roger Moose");
put("Quantity", "3");
put("Price","167.35");
}
};
var objectMapper = new ObjectMapper();
String requestBody = objectMapper.writeValueAsString(values);
return requestBody;
}
发送异步请求
用于同时发送大量的Http请求的场景,例如爬虫:
public void invoke() throws URISyntaxException {
HttpClient client = HttpClient.newBuilder()
.version(Version.HTTP_2)
.followRedirects(Redirect.NORMAL)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI(URLConstants.URL))
.GET()
.header(URLConstants.API_KEY_NAME, URLConstants.API_KEY_VALUE)
.timeout(Duration.ofSeconds(10))
.build();
client.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
}
会话保持
//使用默认的CookieManager,并且接受所有第三方Cookie
cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(5000))
.cookieHandler(cookieManager)//注意在此步骤送送入
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
爬虫示例:HttpClient 并发编程
HttpClient uses executor java.util.concurrent.Executors.newCachedThreadPool().
private static final ExecutorService executorService = Executors.newFixedThreadPool(5);
private static final HttpClient httpClient = HttpClient.newBuilder()
.executor(executorService)
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10))
.build();
public static void main(String[] args) throws Exception {
List<URI> targets = Arrays.asList(
new URI("https://httpbin.org/get?name=mkyong1"),
new URI("https://httpbin.org/get?name=mkyong2"),
new URI("https://httpbin.org/get?name=mkyong3"));
List<CompletableFuture<String>> result = targets.stream()
.map(url -> httpClient.sendAsync(
HttpRequest.newBuilder(url)
.GET()
.setHeader("User-Agent", "Java 11 HttpClient Bot")
.build(),
HttpResponse.BodyHandlers.ofString())
.thenApply(response -> response.body()))
.collect(Collectors.toList());
for (CompletableFuture<String> future : result) {
System.out.println(future.get());
}
}
发送multipart/form-data数据
遗憾的是,JDK 11 HttpClient对这类数据支持不太友好。
private val httpClient: HttpClient = HttpClient.newHttpClient()
fun main() {
val file = File(
LocalTest::class.java.classLoader.getResource("my_pic_from_resources.jpg").file
)
val token = "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
val data: MutableMap<String, Any> = LinkedHashMap()
data["chat_id"] = "123456789"
data["photo"] = file
val boundary: String = BigInteger(35, Random()).toString()
val request = HttpRequest.newBuilder()
.uri(URI.create("https://api.telegram.org/bot$token/sendPhoto"))
.postMultipartFormData(boundary, data)
.build()
val response = httpClient.send(request, HttpResponse.BodyHandlers.ofString())
println(response)
}
fun HttpRequest.Builder.postMultipartFormData(boundary: String, data: Map<String, Any>): HttpRequest.Builder {
val byteArrays = ArrayList<ByteArray>()
val separator = "--$boundary\r\nContent-Disposition: form-data; name=".toByteArray(StandardCharsets.UTF_8)
for (entry in data.entries) {
byteArrays.add(separator)
when(entry.value) {
is File -> {
val file = entry.value as File
val path = Path.of(file.toURI())
val mimeType = Files.probeContentType(path)
byteArrays.add("\"${entry.key}\"; filename=\"${path.fileName}\"\r\nContent-Type: $mimeType\r\n\r\n".toByteArray(StandardCharsets.UTF_8))
byteArrays.add(Files.readAllBytes(path))
byteArrays.add("\r\n".toByteArray(StandardCharsets.UTF_8))
}
else -> byteArrays.add("\"${entry.key}\"\r\n\r\n${entry.value}\r\n".toByteArray(StandardCharsets.UTF_8))
}
}
byteArrays.add("--$boundary--".toByteArray(StandardCharsets.UTF_8))
this.header("Content-Type", "multipart/form-data;boundary=$boundary")
.POST(HttpRequest.BodyPublishers.ofByteArrays(byteArrays))
return this
}
或者,借用Apache Http组件的部分类:
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.channels.Channels;
import java.nio.channels.Pipe;
import java.nio.charset.StandardCharsets;
import org.apache.http.HttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.StringBody;
public class MultipartRequest {
public static void main(String[] args) throws Exception {
/**
* Create a Multipart request body with MultipartEntityBuilder.
*/
HttpEntity httpEntity = MultipartEntityBuilder.create()
// FORM
.addPart("name",
new StringBody("<Spring Cloud>",
ContentType.create("application/x-www-form-urlencoded", StandardCharsets.UTF_8)))
// JSON
.addPart("info",
new StringBody("{\"site\": \"https://www.springcloud.io\"}", ContentType.APPLICATION_JSON))
// FILE
.addBinaryBody("logo", new File("C:\\Users\\KevinBlandy\\Desktop\\logo.png"), ContentType.IMAGE_PNG,
"logo.png")
.build();
/**
* Use pipeline streams to write the encoded data directly to the network
* instead of caching it in memory. Because Multipart request bodies contain
* files, they can cause memory overflows if cached in memory.
*/
Pipe pipe = Pipe.open();
// Pipeline streams must be used in a multi-threaded environment. Using one
// thread for simultaneous reads and writes can lead to deadlocks.
new Thread(() -> {
try (OutputStream outputStream = Channels.newOutputStream(pipe.sink())) {
// Write the encoded data to the pipeline.
httpEntity.writeTo(outputStream);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder(new URI("http://localhost/upload"))
// The Content-Type header is important, don't forget to set it.
.header("Content-Type", httpEntity.getContentType().getValue())
// Reads data from a pipeline stream.
.POST(BodyPublishers.ofInputStream(() -> Channels.newInputStream(pipe.source()))).build();
HttpResponse<String> responseBody = httpClient.send(request, BodyHandlers.ofString(StandardCharsets.UTF_8));
System.out.println(responseBody.body());
}
}
如果不想引入上面的Apache Http组件,则自己写一个类MultipartFormDataBodyPublisher也行。然后如下使用:
var body = new MultipartFormData()
.add(StringPart("name", "Hello,")
.add(StringPart("value", "World!")
.addFile("f", Path.of("index.html"), "text/html")
.addFile("cpuinfo", Path.of("/proc/cpuinfo"), "text/html");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("http://localhost:8080/"))
.header("Content-Type", body.contentType())
.POST(body)
.build();
var response = client.send(request, BodyHandlers.ofLines());
response.body().forEach(line -> System.out.println(line));
其它HTTP Client包装库
methanol
A Lightweight HTTP extensions for Java
cVurl
cVurl is an open-source wrapper for the Java HTTP client. It is written in Java 11 and can be used with any JDK 11.0.2 or newer.
public void cVurl() {
CVurl cVurl = new CVurl();
//POST
Result result = cVurl.post("https://api.imgflip.com/caption_image")
.queryParams(Map.of(
"template_id", "112126428",
"username", "test-user",
"password", "123test321",
"text0", "text0",
"text1", "text1"
))
.asObject(Result.class);
System.out.println("CVurl POST: " + result);
}
它支持Compression、Multipart、Form data这些Java 11 HttpClient不具备的特性。文章来源:https://www.toymoban.com/news/detail-706802.html
Avaje-HTTP
- Fluid API for building URLs and payload
- JSON marshaling using Avaje Jsonb/Jackson/Gson
- Light Feign-style interfaces via annotation processing.
- Request/Response Interception
- Authorization via Basic Auth or OAuth Bearer Tokens
- Async and sync API
个人建议
在实际项目中,设计符合自身项目需求的HTTP client接口,并基于JDK 11 HTTP client实现,独立于任何上述库。文章来源地址https://www.toymoban.com/news/detail-706802.html
参考链接
- comparison-of-java-http-clients
- A closer look at the Java 11 HTTP Client
- cvurl
- methanol
- avaje-http
- java.net.HttpClient multipart/form-data BodyPublisher
到了这里,关于Java 11 HTTP Client库的使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!