【Java技术指南】「Unirest编程专题」一起认识一下一个“灰常”优秀的Http工具,让Http开发变得如此简单(高级篇)

822 阅读9分钟

承接上文

承接《【Java技术指南】「Unirest编程专题」一起认识一下一个“灰常”优秀的Http工具,让Http开发变得如此简单》这篇文章,让我们大概是知道如何使用Unirest开发我们的功能以及一些业务需求,那么针对于一些较为复杂的场景就需要单独进行处理了,那么接下来我们会进行相关的高级特性和特殊场景进行案例分析和说明。


JSON Patch操作请求

什么是JSON Patch?

JSON Patch是一种用于描述JSON文档更改的格式。它可以用来避免在只有部分更改时发送整个文档。当与HTTP补丁方法结合使用时,它允许以符合标准的方式对HTTP API进行部分更新。

JSON补丁在IETF的RFC 6902中指定。

简单的案例

原始数据信息

{
  "baz": "qux",
  "foo": "bar"
}

Patch请求操作

[
  { "op": "replace", "path": "/baz", "value": "boo" },
  { "op": "add", "path": "/hello", "value": ["world"] },
  { "op": "remove", "path": "/foo" }
]

结果信息

{
  "baz": "boo",
  "hello": ["world"]
}

JSON Patch Bodies

Unirest完全支持JSON Patch请求(RFC-6902根据规范参见: json-patch),json-patch的默认内容类型是:application/json-patch+json

Unirest.jsonPatch("http://localhost")
            .add("/fruits/-", "Apple")
            .remove("/bugs")
            .replace("/lastname", "Flintstone")
            .test("/firstname", "Fred")
            .move("/old/location", "/new/location")
            .copy("/original/location", "/new/location")
            .asJson();

将发送包含正文的请求

[
     {"op":"add","path":"/fruits/-","value":"Apple"},
     {"op":"remove","path":"/bugs"},
     {"op":"replace","path":"/lastname","value":"Flintstone"},
     {"op":"test","path":"/firstname","value":"Fred"},
     {"op":"move","path":"/new/location","from":"/old/location"},
     {"op":"copy","path":"/new/location","from":"/original/location"}
 ]

分页请求操作Requests

有时业务服务提供分页请求。目前如何做到这一点并没有标准化,但Unirest实现了了一种机制,可以跟踪页面,直到所有页面都被使用完。

必须提供两个函数来提取下一页,第一种方法是以您想要的格式获取HttpResponse,另一种方法是从响应中提取下一个链接,结果是HttpResponse的PagedList。分页列表有一些处理结果的便捷方法。在这里,我们得到一个分页的Dog列表。

PagedList<Doggos> result =  Unirest.get("https://somewhere/dogs")
                .asPaged(
                        r -> r.asObject(Doggos.class),
                        r -> r.getHeaders().getFirst("nextPage")
);

配置客户端证书

如果需要使用自定义客户端证书来调用服务,可以使用自定义密钥库提供最统一的服务。您可以将密钥库对象或路径传递到有效的PKCS#12密钥库文件。

Unirest.config().clientCertificateStore("/path/mykeystore.p12", "password1!");
Unirest.get("https://some.custom.secured.place.com").asString();
  • /path/mykeystore.p12:本地的证书地址
  • password1! :密码操作

配置代理服务器

有时,您需要通过代理进行隧道操作。Unirest可以配置为执行此操作。请注意,不能针对每个请求配置经过身份验证的代理,除非您希望将其构建到URL本身中。

使用身份验证进行配置

Unirest.config().proxy("proxy.com", 7777, "username", "password1!");
// 不配置用户名/密码
Unirest.config().proxy("proxy.com", 7777);

或者在请求中传递它。这将覆盖在配置中完成的任何代理,目前,只有未经身份验证的代理才能工作。

Unirest.get(MockServer.GET)
                    .proxy("proxy.com", 7777)
                    .asString();

对象或JSON分析中的错误

有时从Web服务获得的结果不会映射到您期望的结果中。当asObject或asJson请求发生这种情况时,结果正文将为空,但响应对象将包含允许您获取错误和原始正文以进行检查的ParsingException。

UnirestParsingException ex = response.getParsingError().get();

获取错误信息的描述

将原始正文作为字符串

ex.getOriginalBody(); 

解析异常信息

ex.getMessage(); 

原始的解析异常本身

ex.getCause(); 

映射错误对象

有时使用rest API,服务会返回一个可以解析的错误对象。您可以选择将其映射到POJO中,如下所示

HttpResponse<Book> book = Unirest.get("http://localhost/books/{id}")
                                     .asObject(Book.class);

如果没有错误,则为空

Error er = book.mapError(Error.class);

也可以在ifFailure方法中利用这一点进行处理返回结果

Unirest.get("http://localhost/books/{id}")
           .asObject(Book.class)
           .ifFailure(Error.class, r -> {
                    Error e = r.getBody();
});

在没有对象映射器的情况下将一种主体类型映射到另一种主体类型,如果不想提供完整的对象映射器实现,可以使用一个简单的函数来映射响应。

int body = Unirest.get("http://httpbin/count")
                      .asString()
                      .mapBody(Integer::valueOf);

错误处理

HttpResponse对象有几个处理程序方法,可以链接起来处理成功和失败:

如果响应是200系列响应并且任何正文处理(如json或Object)成功,则将调用

  • ifSuccess(Consumer<HttpResponse>Response);
  • IfFailure(如果状态为400+或者Body处理失败,则调用Consumer响应。

将它们放在一起可能如下所示:

Unirest.get("http://somewhere")
                .asJson()
                .ifSuccess(response -> todo )
                .ifFailure(response -> {
                    log.error("Oh No! Status" + response.getStatus());
                    response.getParsingError().ifPresent(e -> {
                        log.error("Parsing Exception: ", e);
                        log.error("Original body: " + e.getOriginalBody());
                    });
                });

缓存

Unirest提供了一种简单的即时消息内存响应缓存机制,具有几个条目过期选项。这可以通过默认的过期选项来启用,或者消费者可以提供由他们选择的缓存支持的定制缓存。建议在高负载系统中,消费者使用专用的缓存实现(如EHCache或Guava)来支持缓存。

简单的cache

Unirest.config().cacheResponses(true);
     //These 1st response will be cached in this case:
     Unirest.get("https://somwhere").asString();
     Unirest.get("https://somwhere").asString();

高级特性

您可以使用构建器自定义逐出规则:

Unirest.config().cacheResponses(builder()
               .depth(5)              
               .maxAge(5, TimeUnit.MINUTES));
  • Depth是缓存的最大条目数
  • 最长期限是条目将保留多长时间。

定制化Caches

您还可以通过实现缓存接口来提供自定义缓存,例如我们使用了:

public static void main(String[] args){
       Unirest.config().cacheResponses(Cache.builder().backingCache(new GuavaCache()));
    }
    public static class GuavaCache implements Cache {
            com.google.common.cache.Cache<Key, HttpResponse> regular = CacheBuilder.newBuilder().build();
            com.google.common.cache.Cache<Key, CompletableFuture> async = CacheBuilder.newBuilder().build();
            @Override
            public <T> HttpResponse get(Key key, Supplier<HttpResponse<T>> fetcher) {
                try {
                    return regular.get(key, fetcher::get);
                } catch (ExecutionException e) {
                    throw new RuntimeException(e);
                }
            }
    
            @Override
            public <T> CompletableFuture getAsync(Key key, Supplier<CompletableFuture<HttpResponse<T>>> fetcher) {
                try {
                    return async.get(key, fetcher::get);
                } catch (ExecutionException e) {
                    throw new RuntimeException(e);
                }
            }
        }

网络参数配置

Unirest之前的版本在几个不同的地方进行了配置。有时是在Unirest上完成的,有时是按选项完成的,有时是在其他地方完成的。所有配置现在都是通过Unirest.config()完成的

Unirest.config()
           .socketTimeout(500)
           .connectTimeout(1000)
           .concurrency(10, 5)
           .proxy(new Proxy("https://proxy"))
           .setDefaultHeader("Accept", "application/json")
           .followRedirects(false)
           .enableCookieManagement(false)
           .addInterceptor(new MyCustomInterceptor());

理想情况下,更改Unirest的配置应该只做一次,或者很少做。Unirest本身和ApacheHttpAsyncClient都产生了几个后台线程。一旦Unirest被激活,在没有明确关机或重置的情况下,就不能更改创建客户端所涉及的配置选项。

配置介绍

  • socketTimeout(int):设置所有请求的套接字超时时间(以毫秒为单位),默认为60000
  • connectTimeout(int) :设置所有请求的连接超时(以毫秒为单位),默认为10000
  • concurrency(int, int) :设置并发速率;最大总数、每路由最大值,200, 20。
  • proxy(proxy):设置用于协商代理服务器的代理对象。可以包括身份验证凭据。
  • setDefaultHeader(String, String) :设置默认请求头。将覆盖(如果存在)
  • setDefaultHeader(String, Supplier) :按Suplier接口设置默认。非常适合为微服务体系结构设置跟踪令牌。将覆盖(如果存在)
  • setDefaultBasicAuth(String, String) :添加默认的基本身份验证标头
  • followRedirects(boolean) :切换以下重定向,true
  • enableCookieManagement(boolean):切换接受和存储Cookie,true
  • automaticRetries(boolean) :超时的自动重试(最多4次)
  • defaultBaseUrl(String value) :设置用于所有尚未包含方案的请求的默认基URL
  • errorHandler(Consumer<HttpResponse<?>> consumer) :设置全局错误处理程序,任何状态>400或解析错误都将调用该处理程序
  • interceptor(Interceptor value) :设置将在每个请求之前和之后调用的全局拦截器处理程序。

全局拦截器Interceptor

您可以为您的配置设置全局拦截器。这是在每个请求之前和之后调用的。这对于记录或注入公共属性很有用。

package kong.unirest;

/**
 * Each configuration of Unirest has an interceptor.
 * This has three stages:
 *      * onRequest allows you to modify of view the request before it it sent
 *      * onResponse allows you to view the response. This includes both successful and failed but valid responses
 *      * onFail is called if a total connection or network  failure occurred.
 */
public interface Interceptor {
    /**
     * Called just before a request. This can be used to view or modify the request.
     * this could be used for things like
     *      - Logging the request
     *      - Injecting tracer headers
     *      - Record metrics
     * The default implementation does nothing at all
     * @param request the request
     * @param config the current configuration
     */
    default void onRequest(HttpRequest<?> request, Config config) {
    }

    /**
     * Called just after the request. This can be used to view the response,
     * Perhaps for logging purposes or just because you're curious.
     *  @param response the response
     *  @param request a summary of the request
     *  @param config the current configuration
     */
    default void onResponse(HttpResponse<?> response, HttpRequestSummary request, Config config) {
    }

    /**
     * Called in the case of a total failure.
     * This would be where Unirest was completely unable to make a request at all for reasons like:
     *      - DNS errors
     *      - Connection failure
     *      - Connection or Socket timeout
     *      - SSL/TLS errors
     *
     * The default implimentation simply wraps the exception in a UnirestException and throws it.
     * It is possible to return a different response object from the original if you really
     * didn't want to every throw exceptions. Keep in mind that this is a lie
     *
     * Nevertheless, you could return something like a kong.unirest.FailedResponse
     * 
     * @param e the exception
     * @param request the original request
     * @param config the current config
     * @return a alternative response.
     */
    default HttpResponse<?> onFail(Exception e, HttpRequestSummary request, Config config) throws UnirestException {
        throw new UnirestException(e);
    }
}

实现对应的方法,操作我们想要处理的数据即可。

定制化Http客户端

Unirest在底层利用了Apachehttp客户端,这并不被认为是一个永久性的要求,Unirest的未来版本可能会用其他东西取代Apache.。

您可以设置自己的定制ApacheHttpClient和HttpAsyncClient。请注意,Unirest设置(如超时或拦截器)不适用于自定义客户端。

Unirest.config()
            .httpClient(ApacheClient.builder(myClient))
            .asyncClient(ApacheAsyncClient.builder(myAsyncClient));

您还可以重写Unirest的Apache请求配置实现

Unirest.config()
            .httpClient(ApacheClient.builder(client)
                .withRequestConfig((c,r) -> RequestConfig.custom().build()
                );

多种配置

像往常一样,Unirest维护一个主单一实例。有时,您可能需要为不同的系统配置不同的配置。出于测试目的,您可能还需要一个实例,而不是静态上下文。

这将返回Unirest.Get(“http://somewhere/”)“使用的相同实例。

UnirestInstance unirest = Unirest.primaryInstance();
// 可以像静态上下文一样配置和使用
unirest.config().connectTimeout(5000);
String result = unirest.get("http://foo").asString().getBody();

您还可以获得一个全新的实例

UnirestInstance unirest = Unirest.spawnInstance();

注意:如果您获得了一个新的uniest实例,那么您需要负责在JVM关闭时将其关闭。它无法被Unirest.ShutDown()跟踪或关闭;

整合Jackson/Gson

Unirest基于流行的JSON库(Jackson和Gson)提供了几种不同的ObjectMapper。

<!-- https://mvnrepository.com/artifact/com.konghq/unirest-objectmapper-jackson -->
<dependency>
    <groupId>com.konghq</groupId>
    <artifactId>unirest-objectmapper-jackson</artifactId>
    <version>3.11.09</version>
</dependency>


<!-- https://mvnrepository.com/artifact/com.konghq/unirest-object-mappers-gson -->
<dependency>
    <groupId>com.konghq</groupId>
    <artifactId>unirest-object-mappers-gson</artifactId>
    <version>3.11.09</version>
</dependency>

如果您有其他需要,可以通过实现ObjectMapper接口来提供您自己的对象映射器。它只有几种方法:

关闭组件和连接

Unirest启动一个后台事件循环,您的Java应用程序将无法退出,直到您通过调用以下命令手动关闭所有线程:

Unirest.shutdown();

关闭后,再次使用Unirest将重新初始化。

参考资料: