【Java11新特性】-全新的HTTP 客户端

前言

**HTTP(Hypertext Transfer Protocol)**协议是用于从服务器传输超文本到本地浏览器的传送协议。目前正在被广泛使用的是1999年发布的HTTP/1.0,直到2015年HTTP/2才成为标准。

image-20231003101304581

HTTP/1.1和HTTP/2最主要的区别是客户端和服务器之间传输数据的方式。HTTP/1.1依赖于请求和响应周期。HTTP/2允许服务器向客户端"push"数据,它可以向客户端发送比客户端请求更多的数据。这使得它可以优先处理发送对于首先加载网页至关重要的数据。

从Java9提供了新的API来处理HTTP调用,JDK中新的HTTP客户端(HttpClient)在Java9中还属于孵化器模块,经历了JEP110,到Java11 的JEP321,在Java11版本正式"转正"。

官方JEP

JDK9:JEP 110: HTTP/2 Client (Incubator)

JDK11:JEP 321: HTTP Client

在Java9之前,Java提供的HTTP客户端是HttpURLConnection

  • 它是基于URLConnectionAPI设计的,URLConnection设计之初本意是可以支持多种协议,例如ftp,gopher等等,但是目前这些协议都已经失效了,
  • HttpURLConnection的API是早于HTTP/1.1发布前就设计了,它的API太过抽象。
  • HttpURLConnection也是基于阻塞模式(blocking mode)开发的,要求每个就请求/响应都有一个单独的线程
  • 继续维护下去变得十分困难。

开发JDK的大叔因此设计了全新的HTTP客户端用于替换HttpURLConnection。新的Http客户端不仅支持HTTP/1.1,还提供HTTP/2以及WebSocket的支持。在性能方面也有了大幅提升,按照JEP中的描述,新的HTTP客户端有着不输于Apache HttpClient库以及Netty和Jetty的性能。

API实战

Http Client的主要类有下面三个,

  • java.net.http.HttpClient
  • java.net.http.HttpRequest
  • java.net.http.HttpResponse

在jdk9中,HTTP Client客户端API都可以从jdk.incubator.httpclient模块中获取,在JDK11中迁移到java.net.http

HttpClient介绍

HttpClient是一个抽象类,在HttpClient中仅定义获取配置的抽象方法,构建HttpClient可以通过下面两个方法

  • httpClient()
  • newBuilder()

通过源码可以看到HttpClient其实是通过HttpClientBuilderImpl构建的

// java.net.http
// 抽象类,不能直接new
public abstract class HttpClient {
  	// protected
  	protected HttpClient() {}
  	// 通过newHttpClient直接构建Http Client
    public static HttpClient newHttpClient() {
        return newBuilder().build();
    }
  	// 通过Builder的方式构建HTTP Client
    public static Builder newBuilder() {
        return new HttpClientBuilderImpl();
    }
}

HttpClientBuilderImpl属性如下所示,属性配置完成后调用build()方法构建HttpClient,可以看到HttpClient的实现类其实是HttpClientImpl

// jdk.internal.net.http
public class HttpClientBuilderImpl implements HttpClient.Builder {
		// CookieHandler对象提供了一种回调机制,用于将HTTP状态管理策略实现连接到HTTP协议处理程序中
    CookieHandler cookieHandler;
    Duration connectTimeout;
  	// 跳转策略
    HttpClient.Redirect followRedirects;
		// 代理Selector
    ProxySelector proxy;
    Authenticator authenticator;
  	// HTTP版本枚举,1.1和2
    HttpClient.Version version;
  	// 线程池,异步调用时使用
    Executor executor;
    // ssl安全参数
    SSLContext sslContext;
    SSLParameters sslParams;
    int priority = -1;
    @Override
    public HttpClient build() {
        return HttpClientImpl.create(this);
    }
}

HttpClientImpl源码简单分析,我们截取一些关键代码

  • 构建HttpClientImpl构造器里面的逻辑还是很复杂的,在构造犯法中,HttpClientImpl默认会创建核心线程数为0的缓存鲜橙汁
  • 同步发送请求方法底层其实用的是异步请求方法
  • 异步发送请求使用构造函数中生成的代理线程池发送Http请求
// jdk.internal.net.http
final class HttpClientImpl extends HttpClient implements Trackable {
	  // HttpClient构造方法,
  	private HttpClientImpl(HttpClientBuilderImpl builder,
                           SingleFacadeFactory facadeFactory) {
      	// ...
      	// 默认给Builder中的线程池
        Executor ex = builder.executor;
        if (ex == null) {
          	// 构建缓存线程池
            ex = Executors.newCachedThreadPool(new DefaultThreadFactory(id));
            isDefaultExecutor = true;
        } else {
            isDefaultExecutor = false;
        }
      	// 代理线程池
        delegatingExecutor = new DelegatingExecutor(this::isSelectorThread, ex);
      	// ...
    }
  
  	// 同步发送请求
    @Override
    public <T> HttpResponse<T>
    send(HttpRequest req, BodyHandler<T> responseHandler)
        throws IOException, InterruptedException
    {
        CompletableFuture<HttpResponse<T>> cf = null;
        try {
            cf = sendAsync(req, responseHandler, null, null);
            return cf.get();
        } catch (InterruptedException ie) {
            if (cf != null )
                cf.cancel(true);
            throw ie;
        } catch (ExecutionException e) {
            // ...
        }
    }
  	// 异步返送请求
  	private <T> CompletableFuture<HttpResponse<T>>
    sendAsync(HttpRequest userRequest,
              BodyHandler<T> responseHandler,
              PushPromiseHandler<T> pushPromiseHandler,
              Executor exchangeExecutor/*异步推送默认给是delegatingExecutor.delegate*/)    {

        Objects.requireNonNull(userRequest);
        Objects.requireNonNull(responseHandler);

        AccessControlContext acc = null;
        if (System.getSecurityManager() != null)
            acc = AccessController.getContext();

      	// 为了保证HttpRequest的安全,会克隆HttpRequest
        HttpRequestImpl requestImpl = new HttpRequestImpl(userRequest, proxySelector);
        if (requestImpl.method().equals("CONNECT"))
            throw new IllegalArgumentException("Unsupported method CONNECT");

        long start = DEBUGELAPSED ? System.nanoTime() : 0;
        reference();
        try {
            if (debugelapsed.on())
                debugelapsed.log("ClientImpl (async) send %s", userRequest)
						// 默认使用代理线程池
            Executor executor = exchangeExecutor == null
                    ? this.delegatingExecutor : exchangeExecutor;
            MultiExchange<T> mex = new MultiExchange<>(userRequest,
                                                            requestImpl,
                                                            this,
                                                            responseHandler,
                                                            pushPromiseHandler,
                                                            acc);
          	// 异步获取HttpResponse
            CompletableFuture<HttpResponse<T>> res =
                    mex.responseAsync(executor).whenComplete((b,t) -> unreference());
						// ...
            if (exchangeExecutor != null) {
                res = res.whenCompleteAsync((r, t) -> { /* do nothing */}, ASYNC_POOL);
            }
            return res;
        } catch(Throwable t) {
            unreference();
            debugCompleted("ClientImpl (async)", start, userRequest);
            throw t;
        }
    }

}
HttpRequest介绍

HttpClient类似,HttpRequest也是一个抽象类,它不能通过构造方法直接创建,需要通过newBuilder()生成HttpRequest.Builder

// java.net.http
public abstract class HttpRequest {
  	// protected构造方法
  	protected HttpRequest() {}
  	// 带URI的newBuilder方法
		public static HttpRequest.Builder newBuilder(URI uri) {
        return new HttpRequestBuilderImpl(uri);
    }
		// 不带URI的newBuilder方法
    public static HttpRequest.Builder newBuilder() {
        return new HttpRequestBuilderImpl();
    }
}

HttpRequest.Builder是一个接口,从源码可以看出它的实现类是HttpRequestBuilderImpl

// jdk.internal.net.http
public class HttpRequestBuilderImpl implements HttpRequest.Builder {
		// HttpHeaders建造者
    private HttpHeadersBuilder headersBuilder;
  	// http URI
    private URI uri;
  	// http method名称
    private String method;
    private boolean expectContinue;
  	// BodyPublisher将高级Java对象转换为适合作为请求体发送的字节缓冲区流。BodyPublishers类提供了许多常见发布程序的实现。BodyPublisher接口扩展了Flow。Publisher<ByteBuffer>,这意味着BodyPublisher充当字节缓冲区的发布者
    private BodyPublisher bodyPublisher;
  	// http版本
    private volatile Optional<HttpClient.Version> version;
		// http超时时间
    private Duration duration;
  	// 创建HttpRequest
    @Override
    public HttpRequest build() {
        if (uri == null)
            throw new IllegalStateException("uri is null");
        assert method != null;
        return new ImmutableHttpRequest(this);
    }
}

通过HttpRequestBuilderImplbuild()方法构建的HttpRequest的实现类是ImmutableHttpRequestImmutableHttpRequest中的参数与HttpRequestBuilderImpl相同

// jdk.internal.net.http
final class ImmutableHttpRequest extends HttpRequest {
    private final String method;
    private final URI uri;
    private final HttpHeaders headers;
    private final Optional<BodyPublisher> requestPublisher;
    private final boolean expectContinue;
    private final Optional<Duration> timeout;
    private final Optional<Version> version;

    ImmutableHttpRequest(HttpRequestBuilderImpl builder) {
        this.method = Objects.requireNonNull(builder.method());
        this.uri = Objects.requireNonNull(builder.uri());
        this.headers = HttpHeaders.of(builder.headersBuilder().map(), ALLOWED_HEADERS);
        this.requestPublisher = Optional.ofNullable(builder.bodyPublisher());
        this.expectContinue = builder.expectContinue();
        this.timeout = Optional.ofNullable(builder.timeout());
        this.version = Objects.requireNonNull(builder.version());
    }
}
HttpResponse介绍

HttpResponse是HTTP请求结果的接口,我们不需要手动去创建它,HttpResponseHttpClient发送请求,HttpResponse在接收到响应状态代码和标头时可用,通常在完全接收到响应主体之后可用。在完全接收到响应主体之前,HttpResponse是否可用取决于发送HttpRequest时提供的BodyHandler

public interface HttpResponse<T> {
		// 获取HTTP statusCode
    public int statusCode();
  	// 获取HTTP Response关联的request
    public HttpRequest request();
  	// 返回前一个response
    public Optional<HttpResponse<T>> previousResponse();
		// 返回前一个HttpHeaders
    public HttpHeaders headers();
		// 返回HTTP response body
    public T body();
		// 获取sslSession
    public Optional<SSLSession> sslSession();
  	// 获取HTTP URI
     public URI uri();
		// 获取HTTP 版本
    public HttpClient.Version version();
  	// 初始化返回信息给BodyHandler,它创建的时机是已经获取到了HTTP statusCode和HTTP headers,处理Http response body前
    public interface ResponseInfo {
      	// 获取http statusCode
        public int statusCode();
				// 获取http headers
        public HttpHeaders headers();
				// 获取http版本
        public HttpClient.Version version();
    }
		
    @FunctionalInterface
    public interface BodyHandler<T> {
				// ,返回一个CompleteStage
        public BodySubscriber<T> apply(ResponseInfo responseInfo);
    }

  	// BodyHandler的静态类,提供了不同的BodyHandler实现的实现方法
    public static class BodyHandlers {

        private BodyHandlers() { }
				// ...
      	// 将
        public static BodyHandler<String> ofString(Charset charset) {
            Objects.requireNonNull(charset);
            return (responseInfo) -> BodySubscribers.ofString(charset);
        }
      	// ...
    }
		// push承诺请求handler,用于HTTP/2
    public interface PushPromiseHandler<T> {
        public void applyPushPromise(
            HttpRequest initiatingRequest,
            HttpRequest pushPromiseRequest,
            Function<HttpResponse.BodyHandler<T>,CompletableFuture<HttpResponse<T>>> acceptor
        );

        public static <T> PushPromiseHandler<T>
        of(Function<HttpRequest,BodyHandler<T>> pushPromiseHandler,
           ConcurrentMap<HttpRequest,CompletableFuture<HttpResponse<T>>> pushPromisesMap) {
            return new PushPromisesHandlerWithMap<>(pushPromiseHandler, pushPromisesMap);
        }
    }

  	// BodySubscriber用于消费response body bytes并将其转成更高级的java类型,例如String等
    public interface BodySubscriber<T>
            extends Flow.Subscriber<List<ByteBuffer>> {
        public CompletionStage<T> getBody();
    }

		// 生成BodySubscriber的静态工具方法,它提供了许多BodySubscriber的通用实现
    public static class BodySubscribers {
        private BodySubscribers() { }
				// ...
        public static BodySubscriber<String> ofString(Charset charset) {
            Objects.requireNonNull(charset);
            return new ResponseSubscribers.ByteArraySubscriber<>(
                    bytes -> new String(bytes, charset)
            );
        }
				// ...
    }
}
HTTP Client实战

使用新的Http Client发送Http请求只需要遵循下面4个步骤

  1. 通过HttpClient.newHttpClient()创建HttpClient
  2. 通过HttpRequest.newBuilder()创建HttpRequest
  3. 调用HttpClient发送请求方法发送Http请求
  4. 处理返回结果
同步例子

同步Http请求使用send()方法

// 1.创建HttpClient
HttpClient client = HttpClient.newHttpClient();
// 2.创建HttpRequest
HttpRequest request = HttpRequest.newBuilder()
        .GET()
        .uri(new URI("http://localhost:8080/linshifu/name?id=1"))
        .build();
System.out.println(LocalDateTime.now()+"===> 开始发送发送请求");
// 3.发送http请求
HttpResponse<String> response = client.send(request, (responseInfo ->
        HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8)
));
// 4.处理返回结果
System.out.println(LocalDateTime.now()+"===> 结束发送请求,请求结果:\n"
        +response.body());
异步例子

异步发送Http请求使用的是sendAsync()方法,返回CompletableFuture<ResponseBody<T>>

// 1.创建HttpClient
HttpClient client = HttpClient.newHttpClient();
// 2.创建HttpRequest
HttpRequest request = HttpRequest.newBuilder()
        .GET()
        .uri(new URI("http://localhost:8080/linshifu/name?id=1"))
        .build();
System.out.println(LocalDateTime.now()+"===> 开始发送发送请求");
// 3.sendAsync异步请求,返回CompleteFuture
CompletableFuture<HttpResponse<String>> asyncResult = client.sendAsync(request, (responseInfo ->
        HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8)
));
// 4.处理CompletableFuture结果
asyncResult.thenAccept(response -> {
    System.out.println(LocalDateTime.now() + "===> 结束发送请求,请求结果:\n"
            + response.body());
});
asyncResult.get();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值