池化技术一般用于长连接,那么像Http这种适合连接池吗?
HttpClient 4.3以后中使用了PoolingHttpClientConnectionManager连接池来管理持有连接,同一条TCP链路上,连接是可以复用的。HttpClient通过连接池的方式进行连接持久化(所以它这个连接池其实是tcp的连接池。它里面有一个很重要的概念:Route的概念,代表一条线路。比如baidu.com是一个route,163.com是一个route…)。
连接池:可能是http请求,也可能是https请求
加入池话技术,就不用每次发起请求都新建一个连接(每次连接握手三次,效率太低)
AbstractClientHttpRequestFactoryWrapper
对其它ClientHttpRequestFactory的一个包装抽象类,它有如下两个子类实现
InterceptingClientHttpRequestFactory(重要)
Interceptor拦截的概念,还是蛮重要的。它持有的ClientHttpRequestInterceptor对于我们若想要拦截发出去的请求非常之重要(比如全链路压测中,可以使用它设置token之类的~)
// @since 3.1 public class InterceptingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper { // 持有所有的请求拦截器 private final List<ClientHttpRequestInterceptor> interceptors; public InterceptingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory, @Nullable List<ClientHttpRequestInterceptor> interceptors) { super(requestFactory); // 拦截器只允许通过构造函数设置进来,并且并没有提供get方法方法~ this.interceptors = (interceptors != null ? interceptors : Collections.emptyList()); } // 此处返回的是一个InterceptingClientHttpRequest,显然它肯定是个ClientHttpRequest嘛~ @Override protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) { return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod); } }
InterceptingClientHttpRequest的execute()方法的特点是:若存在拦截器,交给给拦截器去执行发送请求return nextInterceptor.intercept(request, body, this),否则就自己上。
ClientHttpRequestInterceptor
关于请求拦截器,Spring MVC内置了两个最基础的实现
BasicAuthorizationInterceptor:
// @since 4.3.1 但在Spring5.1.1后推荐使用BasicAuthenticationInterceptor @Deprecated public class BasicAuthorizationInterceptor implements ClientHttpRequestInterceptor { private final String username; private final String password; // 注意:username不允许包含:这个字符,但是密码是允许的 public BasicAuthorizationInterceptor(@Nullable String username, @Nullable String password) { Assert.doesNotContain(username, ":", "Username must not contain a colon"); this.username = (username != null ? username : ""); this.password = (password != null ? password : ""); } @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { // 用户名密码连接起来后,用Base64对字节码进行编码~ String token = Base64Utils.encodeToString((this.username + ":" + this.password).getBytes(StandardCharsets.UTF_8)); // 放进请求头:key为`Authorization` 然后执行请求的发送 request.getHeaders().add("Authorization", "Basic " + token); return execution.execute(request, body); } }
这个拦截器木有对body有任何改动,只是把用户名、密码帮你放进了请求头上。
需要注意的是:若你的header里已经存在了Authorization这个key,这里也不会覆盖的,这会添加哦。但并不建议你有覆盖现象~
BasicAuthenticationInterceptor:
它是用来代替上类的。它使用标准的授权头来处理,参考HttpHeaders#setBasicAuth、HttpHeaders#AUTHORIZATION
public class BasicAuthenticationInterceptor implements ClientHttpRequestInterceptor { private final String username; private final String password; // 编码,一般不用指定 @Nullable private final Charset charset; ... // 构造函数略 @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { HttpHeaders headers = request.getHeaders(); // 只有当请求里不包含`Authorization`这个key的时候,此处才会设置授权头哦 if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) { // 这个方法是@since 5.1之后才提供的~~~~~ // 若不包含此key,就设置标准的授权头(根据用户名、密码) 它内部也有这如下三步: // String credentialsString = username + ":" + password; // byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(charset)); // String encodedCredentials = new String(encodedBytes, charset); // 注意:它内部最终还是调用set(AUTHORIZATION, "Basic " + encodedCredentials);这个方法的 headers.setBasicAuth(this.username, this.password, this.charset); } return execution.execute(request, body); } }
说明:这两个请求拦截器虽是Spring提供,但默认都是没有被"装配"的,所亲需要,请手动装配~
BufferingClientHttpRequestFactory
包装其它ClientHttpRequestFactory,使得具有缓存的能力。若开启缓存功能(有开关可控),会使用BufferingClientHttpRequestWrapper包装原来的ClientHttpRequest。这样发送请求后得到的是BufferingClientHttpResponseWrapper响应。
ResponseErrorHandler
用于确定特定响应是否有错误的策略接口。
// @since 3.0 public interface ResponseErrorHandler { // response里是否有错 boolean hasError(ClientHttpResponse response) throws IOException; // 只有hasError = true时才会调用此方法 void handleError(ClientHttpResponse response) throws IOException; // @since 5.0 default void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { handleError(response); } }
继承树如下:
DefaultResponseErrorHandler
Spring
对此策略接口的默认实现,RestTemplate
默认使用的错误处理器就是它。
// @since 3.0 public class DefaultResponseErrorHandler implements ResponseErrorHandler { // 是否有错误是根据响应码来的,所以请严格遵守响应码的规范啊 // 简单的说4xx和5xx都会被认为有错,否则是无错的 参考:HttpStatus.Series @Override public boolean hasError(ClientHttpResponse response) throws IOException { int rawStatusCode = response.getRawStatusCode(); HttpStatus statusCode = HttpStatus.resolve(rawStatusCode); return (statusCode != null ? hasError(statusCode) : hasError(rawStatusCode)); } ... // 处理错误 @Override public void handleError(ClientHttpResponse response) throws IOException { HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode()); if (statusCode == null) { throw new UnknownHttpStatusCodeException(response.getRawStatusCode(), response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response)); } handleError(response, statusCode); } // protected方法,子类对它有复写 protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException { String statusText = response.getStatusText(); HttpHeaders headers = response.getHeaders(); byte[] body = getResponseBody(response); // 拿到body,把InputStream转换为字节数组 Charset charset = getCharset(response); // 注意这里的编码,是从返回的contentType里拿的~~~ // 分别针对于客户端错误、服务端错误 包装为HttpClientErrorException和HttpServerErrorException进行抛出 // 异常内包含有状态码、状态text、头、body、编码等等信息~~~~ switch (statusCode.series()) { case CLIENT_ERROR: throw HttpClientErrorException.create(statusCode, statusText, headers, body, charset); case SERVER_ERROR: throw HttpServerErrorException.create(statusCode, statusText, headers, body, charset); default: throw new UnknownHttpStatusCodeException(statusCode.value(), statusText, headers, body, charset); } } ... }
到这里就可以给大家解释一下,为何经常能看到客户端错误,然后还有状态码+一串信息了,就是因为这两个异常。
HttpClientErrorException:
public class HttpClientErrorException extends HttpStatusCodeException { ... public static HttpClientErrorException create( HttpStatus statusCode, String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { switch (statusCode) { case BAD_REQUEST: return new HttpClientErrorException.BadRequest(statusText, headers, body, charset); case UNAUTHORIZED: return new HttpClientErrorException.Unauthorized(statusText, headers, body, charset); case FORBIDDEN: return new HttpClientErrorException.Forbidden(statusText, headers, body, charset); case NOT_FOUND: return new HttpClientErrorException.NotFound(statusText, headers, body, charset); case METHOD_NOT_ALLOWED: return new HttpClientErrorException.MethodNotAllowed(statusText, headers, body, charset); case NOT_ACCEPTABLE: return new HttpClientErrorException.NotAcceptable(statusText, headers, body, charset); case CONFLICT: return new HttpClientErrorException.Conflict(statusText, headers, body, charset); case GONE: return new HttpClientErrorException.Gone(statusText, headers, body, charset); case UNSUPPORTED_MEDIA_TYPE: return new HttpClientErrorException.UnsupportedMediaType(statusText, headers, body, charset); case TOO_MANY_REQUESTS: return new HttpClientErrorException.TooManyRequests(statusText, headers, body, charset); case UNPROCESSABLE_ENTITY: return new HttpClientErrorException.UnprocessableEntity(statusText, headers, body, charset); default: return new HttpClientErrorException(statusCode, statusText, headers, body, charset); } } ... }
它针对不同的状态码HttpStatus,创建了不同的类型进行返回,方便使用者控制,这在监控上还是蛮有意义的
BadRequest、Unauthorized、Forbidden…等等都是HttpClientErrorException的子类
HttpServerErrorException代码类似,略~
ExtractingResponseErrorHandler
继承自DefaultResponseErrorHandler。在RESTful大行其道的今天,Spring5.0开始提供了此类。它将http错误响应利用HttpMessageConverter转换为对应的RestClientException
// @since 5.0 它出现得还是很晚的。继承自DefaultResponseErrorHandler // 若你的RestTemplate想使用它,请调用RestTemplate#setErrorHandler(ResponseErrorHandler)设置即可 public class ExtractingResponseErrorHandler extends DefaultResponseErrorHandler { private List<HttpMessageConverter<?>> messageConverters = Collections.emptyList(); // 对响应码做缓存 private final Map<HttpStatus, Class<? extends RestClientException>> statusMapping = new LinkedHashMap<>(); private final Map<HttpStatus.Series, Class<? extends RestClientException>> seriesMapping = new LinkedHashMap<>(); // 构造函数、set方法给上面两个Map赋值。因为我们可以自己控制哪些状态码应该报错,哪些不应该了~ // 以及可以自定义:那个状态码抛我们自定义的异常,哪一系列状态码抛我们自定义的异常,这个十分的便于我们做监控 ... // 省略构造函数和set方法。。。 // 增加缓存功能~~~ 否则在交给父类 @Override protected boolean hasError(HttpStatus statusCode) { if (this.statusMapping.containsKey(statusCode)) { return this.statusMapping.get(statusCode) != null; } else if (this.seriesMapping.containsKey(statusCode.series())) { return this.seriesMapping.get(statusCode.series()) != null; } else { return super.hasError(statusCode); } } // 这个它做的事:extract:提取 @Override public void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException { if (this.statusMapping.containsKey(statusCode)) { extract(this.statusMapping.get(statusCode), response); } else if (this.seriesMapping.containsKey(statusCode.series())) { extract(this.seriesMapping.get(statusCode.series()), response); } else { super.handleError(response, statusCode); } } private void extract(@Nullable Class<? extends RestClientException> exceptionClass, ClientHttpResponse response) throws IOException { if (exceptionClass == null) { return; } // 这里使用到了ResponseExtractor返回值提取器,从返回值里提取内容(本文是提取异常) HttpMessageConverterExtractor<? extends RestClientException> extractor = new HttpMessageConverterExtractor<>(exceptionClass, this.messageConverters); RestClientException exception = extractor.extractData(response); if (exception != null) { // 若提取到了异常信息,抛出即可 throw exception; } } }
若你想定制请求异常的处理逻辑,你也是可以自定义这个接口的实现的,当然还是建议你通过继承DefaultResponseErrorHandler来扩展~