Java中5中发送HTTP请求的的方式

发送HTTP请求是现代编程中的核心功能,也是我们学习一种编程语言时最先想做的事。在Java中,有多种方式实现此功能,比如JDK或第三方库,这篇文章将会介绍我所知的Java中HTTP客户端,如果你使用过其他的,那很好,请告诉我。这篇文章将会涉及:

JDK中:
  • HttpURLConnection
  • HttpClient
流行的第三方库:
  • ApacheHttpClient
  • OkHttp
  • Retrofit

我将会使用 NASA APIs 中每天一图Astronomy Picture of the Day做为示例,所有代码都是基于Java11, 并且在GitHub 上。

JDK中API发送HTTP请求

JDK自从1.1就有一个发送HTTP请求的客户端,Java11中新增了一个客户端。如果项目中不想引入第三方Jar包,使用JDK中自带API是个好的选择。

Java 1.1 HttpURLConnection

首先,请记住,类名首字母要大写。让我们闭上眼睛,回到1997,泰坦尼克在票房上大卖并激起了人们上千种表情,Spice Girls专辑也大卖,但年度最大新闻却是Java1.1中加入HttpURLConnection。下面试是使用HttpURLConnection发送Get请求获取每日一图的代码片段:

// Create a neat value object to hold the URL
URL url = new URL("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY");

// Open a connection(?) on the URL(??) and cast the response(???)
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

// Now it's "open", we can set the request method, headers etc.
connection.setRequestProperty("accept", "application/json");

// This line makes the request
InputStream responseStream = connection.getInputStream();

// Manually converting the response body InputStream to APOD using Jackson
ObjectMapper mapper = new ObjectMapper();
APOD apod = mapper.readValue(responseStream, APOD.class);

// Finally we have the response
System.out.println(apod.title);

[GitHub上完整代码]

代码中似乎有很多冗余,并且有些代码顺序也让人困惑,比如为什么是在打开URL链接后设置Header。如果使用Post发送更复杂的请求或设置超时时间等,也有这些功能,但却非常不直观。

那什么时候使用HttpURLConnection呢?如果你支持老版本Java, 并且不能依赖第三方库可使用HttpURLConnection。尽管只有少部分开发者会遇到这种情况,但老的代码库中可能存在HttpURLConnection,现代化的方式,请向下看。

Java 11 HttpClient

自HttpURLConnection加入JDK二十多年后,《黑豹》已上市,JDK中加入了一种新的HTTP客户端java.net.http.HttpClient。这个API更合乎逻辑,并且可以处理HTTP/2、Websockets,同时不仅能发送同步请求,也能借助CompletableFuture发送异步请求。

当发送一个HTTP请求时,大多数时候我们想要读取响应体,没有提供方便实现此功能的库无法吸引我。HttpClient 可使用BodyHandler将响应体转换成自定义类,系统中内置了一些BodyHandler,比如string、byte[]适用字节流,stream那行分割响应,等等。你也可以自定义BodyHandler,比如系统中并没有解析JSON的BodyHandler,参照Java Docs示例,基于Jackson,我实现了一个(here) 。它返回一个APOD class的Supplier,可调用get()获取结果。

同步请求:

// create a client
var client = HttpClient.newHttpClient();

// create a request
var request = HttpRequest.newBuilder(
       URI.create("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY"))
   .header("accept", "application/json")
   .build();

// use the client to send the request
var response = client.send(request, new JsonBodyHandler<>(APOD.class));

// the response:
System.out.println(response.body().get().title);

异步请求时构造client和request的方式相同,只需用sendAsync替换send即可。

// use the client to send the request
var responseFuture = client.sendAsync(request, new JsonBodyHandler<>(APOD.class));

// We can do other things here while the request is in-flight

// This blocks until the request is complete
var response = responseFuture.get();

// the response:
System.out.println(response.body().get().title);

GitHub上完整代码]

第三方HTTP客户端库

如果JDK内置的功能无法满足你的需求,不用担心,还有很多第三方库可使用。

Apache HttpClient

Apache Software FoundationHTTP客户端 存在好长时间了,它被广泛使用,很多更高层次的库使用了它。它的历史有点令人迷惑,老的Commons HttpClient 停止开发,新版HttpClient属于HttpComponents 项目,版本5.0于2020年初发布,并且支持HTTP/2, 既能发送同步请求,也能发送异步请求。

总体来开此API相对底层,需用户自己实现很多功能。下面代码调用NASA API, 似乎很好用,但是我忽略了很多在生产环境中所需的错误处理,同样,我也要使用Jackson 来解析JSON响应体。也许,你还需配置一个日志框架避免告警输出到stdout, 尽管这不是个大问题,但同样使我厌烦。不管怎样,下面是代码:

ObjectMapper mapper = new ObjectMapper();

try (CloseableHttpClient client = HttpClients.createDefault()) {

   HttpGet request = new HttpGet("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY");

   APOD response = client.execute(request, httpResponse ->
       mapper.readValue(httpResponse.getEntity().getContent(), APOD.class));

   System.out.println(response.title);
}

GitHub上完整代码

对应同步异步 请求,Apache提供了更多示例。

OkHttp

OkHttpSquare 开发的HTTP客户端,它有许多有用的功能,比如自动处理GZIP,缓存响应,发生网络故障时重试或降级至其他机器,同样它也支持HTTP/2和WebSocket, 他的API很简洁,依然没有内置解析JSON响应体的功能,所以我添加了用Jackson解析JSON的代码:

ObjectMapper mapper = new ObjectMapper();

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
   .url("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY")
   .build(); // defaults to GET

Response response = client.newCall(request).execute();

APOD apod = mapper.readValue(response.body().byteStream(), APOD.class);

System.out.println(apod.title);

GitHub上完整代码]

这很好, 但构建在OkHttp之上的Retrofit更强大。

Retrofit

Retrofit 是Square构建在OkHttp之上的另外一个库。除了拥有OkHttp本身的功能外,它添加了一种方式可构建抽象了HTTP细节并拥有友好API的Java类。

首先,我们创建一个接口声明我们想调用APOD API的方法,在这个方法上使用注解定义HTTP请求:

public interface APODClient {
   @GET("/planetary/apod")
   @Headers("accept: application/json")
   CompletableFuture<APOD> getApod(@Query("api_key") String apiKey);
}

返回类型CompletableFuture表明这个方法是个异步请求,Square提供更多 adapters或者也可以自定义。我很喜欢这种方式,使用这样的接口在测试时可方便mock客户端。

声明接口后,可使用Retrofit创建一个实现类发送请求,可更换基础URL方便集成测试。生成具体实现类,代码如下:
Retrofit retrofit = new Retrofit.Builder()
   .baseUrl("https://api.nasa.gov")
   .addConverterFactory(JacksonConverterFactory.create())
   .build();

APODClient apodClient = retrofit.create(APODClient.class);

CompletableFuture<APOD> response = apodClient.getApod("DEMO_KEY");

// do other stuff here while the request is in-flight

APOD apod = response.get();

System.out.println(apod.title);

GitHub上完整代码]

API鉴权

如果有多个接口都需增加一个API key, 这可通过在OkHttpClient基础上增加HttpInterceptor来实现,自定义客户端也可基于Retrofit.Builder。下面是自定义客户端代码:

private OkHttpClient clientWithApiKey(String apiKey) {
   return new OkHttpClient.Builder()
           .addInterceptor(chain -> {
               Request originalRequest = chain.request();
               HttpUrl newUrl = originalRequest.url().newBuilder()
                   .addQueryParameter("api_key", apiKey).build();
               Request request = originalRequest.newBuilder().url(newUrl).build();
               return chain.proceed(request);
           }).build();
}

GitHub上完整代码]

除了简单场景,我喜欢这种API,通过依赖注入生成类来进行远程API调用,并且可通过Retrofit生成自定义OkHttp客户端,这种方式很好。

Java中其他HTTP客户端

自从在Twitter 上发表此篇文章后,我很高兴地看到人们讨论他们使用的HTTP客户端。如果上面那些并不是你想要的,可看看下面这些建议:

  • REST Assured - 用于测试REST 服务的一个HTTP客户端,流式风格构建请求, 并且可设置响应断言.
  • cvurl - 包装Java 11 HttpClient,可避免特殊场景下构建复杂请求.
  • Feign - 类似Retrofit, Feign可基于注解接口生成类. Feign可灵活发送请求和读取响应,度量性能,重试等等。
  • Spring RestTemplate (同步) and WebClient (异步) 客户端 - 如果项目中使用了Spring,使用它能很好整合到系统中. Baeldung 有一篇比较它们的文章.
  • MicroProfile Rest Client - 另外一个从注解接口生成类的客户端,它很有趣因为可使用相同接口生成服务端,并保证客户端和服务端兼容。如果同时构建客户端和服务端,可以选择它。

Java中有很多HTTP客户端库可供选择 - 简单场景推荐使用内置的 java.net.http.HttpClient. 复杂场景或在大型应用中想使用Java类抽象HTTP API, 可考虑Retrofit 或Feign. 也可魔改, 我很期待看到你的创新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值