HTTP/1.1和HTTP/2最主要的区别是客户端和服务器之间传输数据的方式。HTTP/1.1依赖于请求和响应周期。HTTP/2允许服务器向客户端”push”数据,它可以向客户端发送比客户端请求更多的数据。这使得它可以优先处理发送对于首先加载网页至关重要的数据。
从Java9提供了新的API来处理HTTP调用,JDK中新的HTTP客户端(HttpClient)在Java9中还属于孵化器模块,经历了JEP110,到Java11 的JEP321,在Java11版本正式”转正”。
在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
HttpClient HttpClient是一个抽象类,在HttpClient中仅定义获取配置的抽象方法,构建HttpClient可以通过下面两个方法
httpClient()
newBuilder()
public abstract class HttpClient { protected HttpClient () {} public static HttpClient newHttpClient () { return newBuilder().build(); } public static Builder newBuilder () { return new HttpClientBuilderImpl (); } }
通过源码可以看到HttpClient其实是通过HttpClientBuilderImpl构建的
public class HttpClientBuilderImpl implements HttpClient .Builder { CookieHandler cookieHandler; Duration connectTimeout; HttpClient.Redirect followRedirects; ProxySelector proxy; Authenticator authenticator; HttpClient.Version version; Executor executor; SSLContext sslContext; SSLParameters sslParams; int priority = -1 ; @Override public HttpClient build () { return HttpClientImpl.create(this ); } }
HttpClientBuilderImpl属性配置完成后调用build()方法构建HttpClient,可以看到HttpClient的实现类其实是HttpClientImpl
public class HttpClientBuilderImpl implements HttpClient .Builder { CookieHandler cookieHandler; Duration connectTimeout; HttpClient.Redirect followRedirects; ProxySelector proxy; Authenticator authenticator; HttpClient.Version version; Executor executor; SSLContext sslContext; SSLParameters sslParams; int priority = -1 ; @Override public HttpClient build () { return HttpClientImpl.create(this ); } }
HttpClientImpl源码简单分析,我们截取一些关键代码
构建HttpClientImpl构造器里面的逻辑还是很复杂的,在构造犯法中,HttpClientImpl默认会创建核心线程数为0的缓存线程池 同步发送请求方法底层其实用的是异步请求方法 异步发送请求使用构造函数中生成的代理线程池发送Http请求
final class HttpClientImpl extends HttpClient implements Trackable { private HttpClientImpl (HttpClientBuilderImpl builder, SingleFacadeFactory facadeFactory) { 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) { Objects.requireNonNull(userRequest); Objects.requireNonNull(responseHandler); AccessControlContext acc = null ; if (System.getSecurityManager() != null ) acc = AccessController.getContext(); 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); CompletableFuture<HttpResponse<T>> res = mex.responseAsync(executor).whenComplete((b,t) -> unreference()); if (exchangeExecutor != null ) { res = res.whenCompleteAsync((r, t) -> { }, ASYNC_POOL); } return res; } catch (Throwable t) { unreference(); debugCompleted("ClientImpl (async)" , start, userRequest); throw t; } } }
HttpRequest
与HttpClient类似,HttpRequest也是一个抽象类,它不能通过构造方法直接创建,需要通过newBuilder()生成HttpRequest.Builder
public abstract class HttpRequest { protected HttpRequest () {} public static HttpRequest.Builder newBuilder (URI uri) { return new HttpRequestBuilderImpl (uri); } public static HttpRequest.Builder newBuilder () { return new HttpRequestBuilderImpl (); } }
HttpRequest.Builder是一个接口,从源码可以看出它的实现类是HttpRequestBuilderImpl
public class HttpRequestBuilderImpl implements HttpRequest .Builder { private HttpHeadersBuilder headersBuilder; private URI uri; private String method; private boolean expectContinue; private BodyPublisher bodyPublisher; private volatile Optional<HttpClient.Version> version; private Duration duration; @Override public HttpRequest build () { if (uri == null ) throw new IllegalStateException ("uri is null" ); assert method != null ; return new ImmutableHttpRequest (this ); } }
通过HttpRequestBuilderImpl的build()方法构建的HttpRequest的实现类是ImmutableHttpRequest,ImmutableHttpRequest中的参数与HttpRequestBuilderImpl相同
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请求结果的接口,我们不需要手动去创建它,HttpResponse在HttpClient发送请求,HttpResponse在接收到响应状态代码和标头时可用,通常在完全接收到响应主体之后可用。在完全接收到响应主体之前,HttpResponse是否可用取决于发送HttpRequest时提供的BodyHandler
public interface HttpResponse <T> { public int statusCode () ; public HttpRequest request () ; public Optional<HttpResponse<T>> previousResponse () ; public HttpHeaders headers () ; public T body () ; public Optional<SSLSession> sslSession () ; public URI uri () ; public HttpClient.Version version () ; public interface ResponseInfo { public int statusCode () ; public HttpHeaders headers () ; public HttpClient.Version version () ; } @FunctionalInterface public interface BodyHandler <T> { public BodySubscriber<T> apply (ResponseInfo responseInfo) ; } public static class BodyHandlers { private BodyHandlers () { } public static BodyHandler<String> ofString (Charset charset) { Objects.requireNonNull(charset); return (responseInfo) -> BodySubscribers.ofString(charset); } } 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); } } public interface BodySubscriber <T> extends Flow .Subscriber<List<ByteBuffer>> { public CompletionStage<T> getBody () ; } 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请求只需要遵循下面4个步骤
通过HttpClient.newHttpClient()创建HttpClient
通过HttpRequest.newBuilder()创建HttpRequest
调用HttpClient发送请求方法发送Http请求
处理返回结果
同步
同步Http请求使用send()方法
HttpClient client = HttpClient.newHttpClient();HttpRequest request = HttpRequest.newBuilder() .GET() .uri(new URI ("http://localhost:8080/linshifu/name?id=1" )) .build(); System.out.println(LocalDateTime.now()+"===> 开始发送发送请求" ); HttpResponse<String> response = client.send(request, (responseInfo -> HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8) )); System.out.println(LocalDateTime.now()+"===> 结束发送请求,请求结果:\n" +response.body());
HttpClient client = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_2) .connectTimeout(Duration.ofSeconds(5 )) .followRedirects(HttpClient.Redirect.ALWAYS) .build(); HttpRequest getRequest = HttpRequest.newBuilder() .GET() .uri(URI.create("http://www.flydean.com" )) .header("User-Agent" , "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36" ) .build(); HttpRequest.BodyPublisher requestBody = HttpRequest.BodyPublishers .ofString("{ 我是body }" ); HttpRequest postRequest = HttpRequest.newBuilder() .POST(requestBody) .uri(URI.create("http://www.flydean.com" )) .build(); HttpResponse<String> response = client.send( getRequest, HttpResponse.BodyHandlers.ofString()); String respnseBody = response.body(); log.info(respnseBody);
异步
异步发送Http请求使用的是sendAsync()方法,返回CompletableFuture<ResponseBody<T>>
HttpClient client = HttpClient.newHttpClient();HttpRequest request = HttpRequest.newBuilder() .GET() .uri(new URI ("http://localhost:8080/linshifu/name?id=1" )) .build(); System.out.println(LocalDateTime.now()+"===> 开始发送发送请求" ); CompletableFuture<HttpResponse<String>> asyncResult = client.sendAsync(request, (responseInfo -> HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8) )); asyncResult.thenAccept(response -> { System.out.println(LocalDateTime.now() + "===> 结束发送请求,请求结果:\n" + response.body()); }); asyncResult.get();
public void useAsyncHttp () { HttpClient client = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_2) .connectTimeout(Duration.ofSeconds(5 )) .followRedirects(HttpClient.Redirect.ALWAYS) .build(); CompletableFuture<Void> completableFuture=checkUri(client,URI.create("http://www.flydean.com" )); completableFuture.join(); } private CompletableFuture<Void> checkUri (HttpClient httpClient, URI uri) { HttpRequest request = HttpRequest.newBuilder() .GET() .uri(uri) .header("User-Agent" , "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36" ) .build(); return httpClient.sendAsync(request,HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::statusCode) .thenApply(statusCode -> statusCode == 200 ) .exceptionally(ex -> false ) .thenAccept(valid -> { if (valid) { log.info("uri {} is valid" ,uri); } else { log.info("uri {} is not valid" , uri); } }); }
上面的例子中我们获取了HTTP请求,然后调用了CompletableFuture的thenApply和thenAccept对结果进行过滤和处理。 CompletableFuture是Future和CompletionStage的混合体。Future大家都很熟悉了,可以通过get方法获取异步执行的结果。而CompletionStage代表的是一个异步计算的stage,不同的stage可以互相依赖,通过then***方法来组成一个级联操作。和Stream的操作有点像,和ES6中的promise的then一样,使用CompletionStage可以避免回调地狱。CompletionStage可以将异步回调转换成级联操作。 thenApply的参数是一个Function,thenAccept是一个Consumer。 最后,我们需要调用completableFuture.join()来保证completableFuture的异步操作执行完成。 当然调用completableFuture.get()方法也是可以的