HttpClient 简介
java.net.http.HttpClient 是 jdk11 中正式启用的一个 http 工具类其实早在 jdk9 的时候就已经存在了,只是处于孵化期),官方寓意为想要取代 HttpURLConnection 和 Apache HttpClient 等比较古老的开发工具。
新增的 HttpClient 截止到目前2019年3月)为止其实网络资料还比较少,笔者只是根据一些博文和官方 Demo 自己摸索了一下,做了下总结。
由于是 jdk11 中才正式使用的工具类,距离开发者还很遥远,所以对于源码笔者暂不打算深挖,浅浅的理解怎么使用就行
一、HttpClient在 Apache HttpClient 中,一般会创建一个 HttpClient 对象来作为门面。java.net.http.HttpClient 的逻辑也差不多,只是创建方式更加时髦了:
//创建 builder HttpClient.Builder builder = HttpClient.newBuilder); //链式调用 HttpClient client = builder //http 协议版本 1.1 或者 2 .versionHttpClient.Version.HTTP_2) //.versionHttpClient.Version.HTTP_1_1) //连接超时时间,单位为毫秒 .connectTimeoutDuration.ofMillis5000)) //.connectTimeoutDuration.ofMinutes1)) //连接完成之后的转发策略 .followRedirectsHttpClient.Redirect.NEVER) //.followRedirectsHttpClient.Redirect.ALWAYS) //指定线程池 .executorExecutors.newFixedThreadPool5)) //认证,默认情况下 Authenticator.getDefault) 是 null 值,会报错 //.authenticatorAuthenticator.getDefault)) //代理地址 //.proxyProxySelector.ofnew InetSocketAddress"http://www.baidu.com", 8080))) //缓存,默认情况下 CookieHandler.getDefault) 是 null 值,会报错 //.cookieHandlerCookieHandler.getDefault)) //创建完成 .build);
在 builder) 方法中,最终会调用到 HttpClientImpl 的构造器,完成 HttpClient 的创建工作:
//HttpClientImpl.class private HttpClientImplHttpClientBuilderImpl builder, SingleFacadeFactory facadeFactory) { //CLIENT_IDS 是 AtomicLong 类型的变量,使用 incrementAndGet) 方法实现自增长的 id id = CLIENT_IDS.incrementAndGet); //记录下存有 id 的字符串 dbgTag = "HttpClientImpl" + id + ")"; //ssl 认证 if builder.sslContext == null) { try { sslContext = SSLContext.getDefault); } catch NoSuchAlgorithmException ex) { throw new InternalErrorex); } } else { sslContext = builder.sslContext; } //线程池,没有的话就默认创建一个 Executor ex = builder.executor; if ex == null) { ex = Executors.newCachedThreadPoolnew DefaultThreadFactoryid)); isDefaultExecutor = true; } else { isDefaultExecutor = false; } delegatingExecutor = new DelegatingExecutorthis::isSelectorThread, ex); facadeRef = new WeakReference<>facadeFactory.createFacadethis)); //处理 http 2 的 client 类 client2 = new Http2ClientImplthis);‘ //缓存操作 cookieHandler = builder.cookieHandler; //超时时间 connectTimeout = builder.connectTimeout; //转发策略,默认为 NEVER followRedirects = builder.followRedirects == null ? Redirect.NEVER : builder.followRedirects; //代理设置 this.userProxySelector = Optional.ofNullablebuilder.proxy); this.proxySelector = userProxySelector .orElseGetHttpClientImpl::getDefaultProxySelector); if debug.on)) debug.log"proxySelector is %s user-supplied=%s)", this.proxySelector, userProxySelector.isPresent)); //认证设置 authenticator = builder.authenticator; //设置 http 协议版本 if builder.version == null) { version = HttpClient.Version.HTTP_2; } else { version = builder.version; } if builder.sslParams == null) { sslParams = getDefaultParamssslContext); } else { sslParams = builder.sslParams; } //连接线程池 connections = new ConnectionPoolid); connections.start); timeouts = new TreeSet<>); //SelectorManager 本质上是 Thread 类的封装 //selmgr 会开启一条线程,HttpClient 的主要逻辑运行在此线程中 //所以说 HttpClient 是非阻塞的,因为并不跑在主线程中 try { selmgr = new SelectorManagerthis); } catch IOException e) { throw new InternalErrore); } //设置为守护线程 selmgr.setDaemontrue); filters = new FilterFactory); initFilters); assert facadeRef.get) != null; }
主要是一些储存操作,大致理解即可,不细究。
二、HttpRequest
HttpRequest 是发起请求的主体配置:
//创建 builder HttpRequest.Builder reBuilder = HttpRequest.newBuilder); //链式调用 HttpRequest request = reBuilder //存入消息头 //消息头是保存在一张 TreeMap 里的 .header"Content-Type", "application/json") //http 协议版本 .versionHttpClient.Version.HTTP_2) //url 地址 .uriURI.create"http://openjdk.java.net/")) //超时时间 .timeoutDuration.ofMillis5009)) //发起一个 post 消息,需要存入一个消息体 .POSTHttpRequest.BodyPublishers.ofString"hello")) //发起一个 get 消息,get 不需要消息体 //.GET) //method...) 方法是 POST...) 和 GET...) 方法的底层,效果一样 //.method"POST",HttpRequest.BodyPublishers.ofString"hello")) //创建完成 .build);
三、发送
发起请求:
HttpResponse<String> response = client.sendrequest, HttpResponse.BodyHandlers.ofString));
这是同步式的发起请求方式,先来看一下它的实现:
public <T> HttpResponse<T> sendHttpRequest req, BodyHandler<T> responseHandler) throws IOException, InterruptedException{ CompletableFuture<HttpResponse<T>> cf = null; try { //调用 sendAsync...) 方法异步地完成主逻辑,并获取 Future cf = sendAsyncreq, responseHandler, null, null); return cf.get); //这之后的所有代码都是在进行异常捕捉,所以可以忽略 } catch InterruptedException ie) { if cf != null ) cf.canceltrue); throw ie; } catch ExecutionException e) { final Throwable throwable = e.getCause); final String msg = throwable.getMessage); if throwable instanceof IllegalArgumentException) { throw new IllegalArgumentExceptionmsg, throwable); } else if throwable instanceof SecurityException) { throw new SecurityExceptionmsg, throwable); } else if throwable instanceof HttpConnectTimeoutException) { HttpConnectTimeoutException hcte = new HttpConnectTimeoutExceptionmsg); hcte.initCausethrowable); throw hcte; } else if throwable instanceof HttpTimeoutException) { throw new HttpTimeoutExceptionmsg); } else if throwable instanceof ConnectException) { ConnectException ce = new ConnectExceptionmsg); ce.initCausethrowable); throw ce; } else if throwable instanceof IOException) { throw new IOExceptionmsg, throwable); } else { throw new IOExceptionmsg, throwable); } } }
本质上是使用了异步实现方法 sendAsync…)。
在 Demo 中也可以直接使用:
//返回的是 future,然后通过 future 来获取结果 CompletableFuture<String> future = client.sendAsyncrequest, HttpResponse.BodyHandlers.ofString)) .thenApplyHttpResponse::body); //阻塞线程,从 future 中获取结果 String body = future.get);