HttpClient 中文官方教程----第5章流利的API+第6章HTTP缓存-只收录,未测试
博客专区 > 诺岚 的博客 > 博客详情
HttpClient 中文官方教程----第5章流利的API+第6章HTTP缓存-只收录,未测试
诺岚 发表于3个月前
HttpClient 中文官方教程----第5章流利的API+第6章HTTP缓存-只收录,未测试
  • 发表于 3个月前
  • 阅读 14
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 学生专属云服务套餐 10元起购>>>   

流利的API

5.1。易于使用的外观API

从4.2版本开始,HttpClient自带了一个易于使用的外观API,基于流畅界面的概念。流畅的外观API仅暴露了HttpClient的最基本功能,旨在用于不需要HttpClient的完全灵活性的简单用例。例如,流畅的外观API可以减轻用户不必处理连接管理和资源释放。

以下是通过HC流畅API执行的HTTP请求的几个示例

// Execute a GET with timeout settings and return response content as String.
Request.Get("http://somehost/")
        .connectTimeout(1000)
        .socketTimeout(1000)
        .execute().returnContent().asString();

    
// Execute a POST with the 'expect-continue' handshake, using HTTP/1.1,
// containing a request body as String and return response content as byte array.
Request.Post("http://somehost/do-stuff")
        .useExpectContinue()
        .version(HttpVersion.HTTP_1_1)
        .bodyString("Important stuff", ContentType.DEFAULT_TEXT)
        .execute().returnContent().asBytes();
// Execute a POST with a custom header through the proxy containing a request body
// as an HTML form and save the result to the file
Request.Post("http://somehost/some-form")
        .addHeader("X-Custom-header", "stuff")
        .viaProxy(new HttpHost("myproxy", 8080))
        .bodyForm(Form.form().add("username", "vip").add("password", "secret").build())
        .execute().saveContent(new File("result.dump"));

人们还可以Executor直接使用以在特定安全上下文中执行请求,从而缓存认证细节并重新使用后续请求。

Executor executor = Executor.newInstance()
        .auth(new HttpHost("somehost"), "username", "password")
        .auth(new HttpHost("myproxy", 8080), "username", "password")
        .authPreemptive(new HttpHost("myproxy", 8080));

executor.execute(Request.Get("http://somehost/"))
        .returnContent().asString();

executor.execute(Request.Post("http://somehost/do-stuff")
        .useExpectContinue()
        .bodyString("Important stuff", ContentType.DEFAULT_TEXT))
        .returnContent().asString();

5.1.1。响应处理

流畅的外观API通常减轻用户不必处理连接管理和资源释放。然而,在大多数情况下,这是以缓冲内存中的响应消息内容为代价的。强烈建议使用ResponseHandlerHTTP响应处理,以避免缓冲内存中的内容。

Document result = Request.Get("http://somehost/content")
        .execute().handleResponse(new ResponseHandler<Document>() {

    public Document handleResponse(final HttpResponse response) throws IOException {
        StatusLine statusLine = response.getStatusLine();
        HttpEntity entity = response.getEntity();
        if (statusLine.getStatusCode() >= 300) {
            throw new HttpResponseException(
                    statusLine.getStatusCode(),
                    statusLine.getReasonPhrase());
        }
        if (entity == null) {
            throw new ClientProtocolException("Response contains no content");
        }
        DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
            ContentType contentType = ContentType.getOrDefault(entity);
            if (!contentType.equals(ContentType.APPLICATION_XML)) {
                throw new ClientProtocolException("Unexpected content type:" +
                    contentType);
            }
            String charset = contentType.getCharset();
            if (charset == null) {
                charset = HTTP.DEFAULT_CONTENT_CHARSET;
            }
            return docBuilder.parse(entity.getContent(), charset);
        } catch (ParserConfigurationException ex) {
            throw new IllegalStateException(ex);
        } catch (SAXException ex) {
            throw new ClientProtocolException("Malformed XML document", ex);
        }
    }

    });

 

第6章HTTP缓存

6.1。一般概念

HttpClient Cache提供了与HttpClient一起使用的符合HTTP / 1.1的缓存层 - 与浏览器缓存的Java相当。实施遵循“责任链”设计模式,其中缓存HttpClient实现可以为默认非缓存HttpClient实现提供替代; 可以从缓存中完全满足的请求不会导致实际的源请求。使用条件GET和If-Modified-Since和/或If-None-Match请求标头,可以在可能的情况下使用原点自动验证过时高速缓存条目。

HTTP / 1.1缓存一般设计为语义透明 ; 也就是说,缓存不应该改变客户端和服务器之间的请求 - 响应交换的含义。因此,将HttpClient缓存到现有的兼容客户端 - 服务器关系中应该是安全的。虽然缓存模块是从HTTP协议的角度来看的客户端的一部分,但实现的目标是与透明缓存代理的要求兼容。

最后,缓存HttpClient包括支持由RFC 5861指定的Cache-Control扩展(stale-if-error和stale-while-revalidate)。

当缓存HttpClient执行请求时,它将通过以下流程:

  1. 检查基本符合HTTP 1.1协议的请求,并尝试更正请求。

  2. 刷新任何将被该请求无效的缓存条目。

  3. 确定当前请求是否可以从缓存中执行。如果没有,直接通过请求到源服务器并返回响应,缓存后,如果合适。

  4. 如果它是一个可缓存可执行的请求,它将尝试从缓存中读取它。如果不在缓存中,请调用源服务器并缓存响应(如果适用)。

  5. 如果缓存响应适合作为响应,则构造一个包含ByteArrayEntity的BasicHttpResponse并返回它。否则,尝试根据原始服务器重新验证缓存条目。

  6. 在无法重新验证的缓存响应的情况下,如果适用,请调用源服务器并缓存响应。

当缓存HttpClient收到响应时,它将通过以下流程:

  1. 检查协议符合性的响应

  2. 确定响应是否可缓存

  3. 如果它是可缓存的,请尝试读取配置中允许的最大大小并将其存储在缓存中。

  4. 如果缓存的响应太大,重建部分消耗的响应并直接返回,而不缓存它。

值得注意的是,缓存HttpClient本身不是HttpClient的不同实现,而是通过将自身作为附加处理组件插入到请求执行管道中来工作。

6.2。RFC-2616合规性

我们相信HttpClient的缓存是无条件地遵从RFC-2616。也就是说,无论规范是否必须,必须不应该,应该或不应该用于HTTP高速缓存,缓存层会尝试以满足这些要求的方式进行操作。这意味着缓存模块在放入时不会产生不正确的行为。

6.3。使用示例

这是一个简单的例子,说明如何设置一个基本的缓存HttpClient。根据配置,它将最多存储1000个缓存对象,每个对象的最大主体大小为8192个字节。这里选择的数字仅作为示例,并不意图作为建议。

CacheConfig cacheConfig = CacheConfig.custom()
        .setMaxCacheEntries(1000)
        .setMaxObjectSize(8192)
        .build();
RequestConfig requestConfig = RequestConfig.custom()
        .setConnectTimeout(30000)
        .setSocketTimeout(30000)
        .build();
CloseableHttpClient cachingClient = CachingHttpClients.custom()
        .setCacheConfig(cacheConfig)
        .setDefaultRequestConfig(requestConfig)
        .build();

HttpCacheContext context = HttpCacheContext.create();
HttpGet httpget = new HttpGet("http://www.mydomain.com/content/");
CloseableHttpResponse response = cachingClient.execute(httpget, context);
try {
    CacheResponseStatus responseStatus = context.getCacheResponseStatus();
    switch (responseStatus) {
        case CACHE_HIT:
            System.out.println("A response was generated from the cache with " +
                    "no requests sent upstream");
            break;
        case CACHE_MODULE_RESPONSE:
            System.out.println("The response was generated directly by the " +
                    "caching module");
            break;
        case CACHE_MISS:
            System.out.println("The response came from an upstream server");
            break;
        case VALIDATED:
            System.out.println("The response was generated from the cache " +
                    "after validating the entry with the origin server");
            break;
    }
} finally {
    response.close();
}

6.4。组态

缓存HttpClient继承了默认非缓存实现的所有配置选项和参数(包括设置选项,如超时和连接池大小)。对于特定于缓存的配置,您可以提供一个CacheConfig实例来自定义以下方面的行为:

缓存大小。如果后端存储支持这些限制,则可以指定缓存条目的最大数量以及最大可缓存的响应体大小。

公共/私人缓存。默认情况下,缓存模块认为自己是一个共享(公共)缓存,例如,不会使用“Cache-Control:private”标记的Authorization标头或响应来缓存对请求的响应。但是,如果缓存仅由一个逻辑“用户”使用(与浏览器缓存类似),那么您将要关闭共享缓存设置。

启发式缓存。根据RFC2616,即使没有明确的高速缓存控制头由源设置,缓存也可以缓存某些缓存条目。默认情况下,此行为是关闭的,但如果您使用的是不设置适当标题但仍希望缓存响应的原点,则可能需要打开此操作。您将需要启用启发式缓存,然后指定默认的新鲜生命周期和/或资源上次修改后的一小部分时间。有关启发式缓存的更多细节,请参阅HTTP / 1.1 RFC的第13.2.2和13.2.4节。

背景验证。缓存模块支持RFC5861的stale-while-revalidate伪指令,允许在后台发生某些缓存条目重新验证。您可能需要调整最小和最大数量的后台工作线程的设置,以及在回收之前可以空闲的最长时间。当没有足够的工作人员跟上需求时,您还可以控制用于重新验证的队列的大小。

6.5。存储后端

缓存HttpClient的默认实现将缓存条目和缓存响应体存储在应用程序的JVM中。虽然这提供了高性能,但由于大小的限制或缓存条目是短暂的,并且在应用程序重新启动后无法生存,因此可能不适合您的应用程序。当前版本包括使用EhCache和memcached实现存储缓存条目的支持,这些实现允许将缓存条目溢出到磁盘或将它们存储在外部进程中。

如果这些选项都不适合您的应用程序,可以通过实施HttpCacheStorage接口提供自己的存储后端,然后在构建时提供缓存HttpClient。在这种情况下,缓存条目将使用您的方案进行存储,但您将重新使用围绕HTTP / 1.1兼容性和高速缓存处理的所有逻辑。一般来说,应该可以从支持键/值存储(类似于Java Map接口)的任何东西创建HttpCacheStorage实现,并具有应用原子更新的能力。

最后,通过一些额外的努力,完全可以设置多层缓存层次结构; 例如,按照类似于虚拟内存,L1 / L2处理器缓存等的模式,将内存缓存HttpClient包装在围绕磁盘上存储高速缓存条目或在memcached中远程存储的缓存中。

第7章高级主题

第7章高级主题

7.1。定制客户端连接

在某些情况下,可能需要定制HTTP消息通过线路传输超过可能使用HTTP参数的方式,以便能够处理非标准,不符合规定的行为。例如,对于网络爬虫,可能需要强制HttpClient接受格式错误的响应头,以便挽救消息的内容。

通常,插入自定义消息解析器或自定义连接实现的过程涉及几个步骤:

  • 提供一个自定义LineParserLineFormatter接口实现。根据需要实现消息解析/格式化逻辑。

    class MyLineParser extends BasicLineParser {
    
        @Override
        public Header parseHeader(
                CharArrayBuffer buffer) throws ParseException {
            try {
                return super.parseHeader(buffer);
            } catch (ParseException ex) {
                // Suppress ParseException exception
                return new BasicHeader(buffer.toString(), null);
            }
        }
    
    }

     

  • 提供自定义HttpConnectionFactory实现。根据需要将默认请求写入器和/或响应解析器替换为自定义请求。

    HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory =
            new ManagedHttpClientConnectionFactory(
                new DefaultHttpRequestWriterFactory(),
                new DefaultHttpResponseParserFactory(
                        new MyLineParser(), new DefaultHttpResponseFactory()));

     

  • 配置HttpClient以使用自定义连接工厂。

    PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(
        connFactory);
    CloseableHttpClient httpclient = HttpClients.custom()
            .setConnectionManager(cm)
            .build();

     

7.2。状态HTTP连接

虽然HTTP规范假定会话状态信息始终以HTTP Cookie的形式嵌入到HTTP消息中,因此HTTP连接始终是无状态的,但这种假设并不总是在现实生活中成立。有些情况下,使用特定用户身份或特定安全上下文创建HTTP连接,因此无法与其他用户共享,并且只能由同一用户重复使用。这种有状态HTTP连接的示例是经过NTLM身份验证的连接和具有客户端证书身份验证的SSL连接。

7.2.1。用户令牌处理程序

HttpClient依赖UserTokenHandler接口来确定给定的执行上下文是否是用户特定的。如果上下文是用户特定的,则此处理程序返回的令牌对象将被预期唯一标识当前用户,如果上下文不包含特定于当前用户的任何资源或详细信息,则为空。用户令牌将用于确保用户特定的资源不会被其他用户共享或重用。

UserTokenHandler接口的默认实现使用Principal类的一个实例来表示HTTP连接的状态对象,如果可以从给定的执行上下文中获取该对象。DefaultUserTokenHandler将使用基于连接的身份验证方案的用户主体NTLM或启用客户端身份验证的SSL会话的身份验证方案。如果两者都不可用,将返回空令牌。

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpClientContext context = HttpClientContext.create();
HttpGet httpget = new HttpGet("http://localhost:8080/");
CloseableHttpResponse response = httpclient.execute(httpget, context);
try {
    Principal principal = context.getUserToken(Principal.class);
    System.out.println(principal);
} finally {
    response.close();
}

如果默认的用户不能满足他们的需求,用户可以提供自定义的实现:

UserTokenHandler userTokenHandler = new UserTokenHandler() {

    public Object getUserToken(HttpContext context) {
        return context.getAttribute("my-token");
    }

};
CloseableHttpClient httpclient = HttpClients.custom()
        .setUserTokenHandler(userTokenHandler)
        .build();

7.2.2。持续状态连接

请注意,只有在执行请求时将相同的状态对象绑定到执行上下文,才能重用携带状态对象的持久连接。因此,确保相同的上下文被重用以执行相同用户的后续HTTP请求,或者在请求执行之前将用户令牌绑定到上下文是非常重要的。

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpClientContext context1 = HttpClientContext.create();
HttpGet httpget1 = new HttpGet("http://localhost:8080/");
CloseableHttpResponse response1 = httpclient.execute(httpget1, context1);
try {
    HttpEntity entity1 = response1.getEntity();
} finally {
    response1.close();
}
Principal principal = context1.getUserToken(Principal.class);

HttpClientContext context2 = HttpClientContext.create();
context2.setUserToken(principal);
HttpGet httpget2 = new HttpGet("http://localhost:8080/");
CloseableHttpResponse response2 = httpclient.execute(httpget2, context2);
try {
    HttpEntity entity2 = response2.getEntity();
} finally {
    response2.close();
}

7.3。使用FutureRequestExecutionService

使用FutureRequestExecutionService,您可以调度http呼叫并将响应视为未来。当对Web服务进行多次调用时,这很有用。使用FutureRequestExecutionService的优点是您可以使用多个线程同时调度请求,设置任务超时,或者在不再需要响应时取消它们。

FutureRequestExecutionService使用HttpRequestFutureTask包装请求,这扩展了FutureTask。该类允许您取消任务,并跟踪各种指标,例如请求持续时间。

7.3.1。创建FutureRequestExecutionService

futureRequestExecutionService的构造函数接受任何现有的httpClient实例和ExecutorService实例。配置两者时,重要的是将最大连接数与要使用的线程数对齐。当线程多于连接时,由于没有可用连接,连接可能会启动超时。当连线数超过线程时,futureRequestExecutionService将不会使用它们

HttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(5).build();
ExecutorService executorService = Executors.newFixedThreadPool(5);
FutureRequestExecutionService futureRequestExecutionService =
    new FutureRequestExecutionService(httpClient, executorService);

7.3.2。计划请求

要安排一个请求,只需提供一个HttpUriRequest,HttpContext和一个ResponseHandler。由于请求由执行程序服务处理,所以ResponseHandler是必需的。

private final class OkidokiHandler implements ResponseHandler<Boolean> {
    public Boolean handleResponse(
            final HttpResponse response) throws ClientProtocolException, IOException {
        return response.getStatusLine().getStatusCode() == 200;
    }
}

HttpRequestFutureTask<Boolean> task = futureRequestExecutionService.execute(
    new HttpGet("http://www.google.com"), HttpClientContext.create(),
    new OkidokiHandler());
// blocks until the request complete and then returns true if you can connect to Google
boolean ok=task.get();

7.3.3。取消任务

计划的任务可能会被取消。如果任务尚未执行,但仅排队等待执行,则将永远不会执行。如果正在执行,并且将mayInterruptIfRunning参数设置为true,则会在请求中调用abort(); 否则响应将被忽略,但请求将被允许正常完成。对task.get()的任何后续调用都将失败并带有IllegalStateException。应该注意的是,取消任务只会释放客户端资源。请求实际上可以在服务器端正常处理。

task.cancel(true)
task.get() // throws an Exception

7.3.4。回调

而不是手动调用task.get(),您也可以使用一个FutureCallback实例,该实例在请求完成时获取回调。这与HttpAsyncClient中使用的界面相同


private final class MyCallback implements FutureCallback<Boolean> {

    public void failed(final Exception ex) {
        // do something
    }

    public void completed(final Boolean result) {
        // do something
    }

    public void cancelled() {
        // do something
    }
}

HttpRequestFutureTask<Boolean> task = futureRequestExecutionService.execute(
    new HttpGet("http://www.google.com"), HttpClientContext.create(),
    new OkidokiHandler(), new MyCallback());

7.3.5。度量

FutureRequestExecutionService通常用于进行大量Web服务调用的应用程序。为了便于监控或配置调整,FutureRequestExecutionService跟踪了几个指标。

每个HttpRequestFutureTask提供方法来获取任务被安排,启动和结束的时间。此外,请求和任务持续时间也可用。这些指标汇总在FutureRequestExecutionService中的FutureRequestExecutionMetrics实例中,可以通过FutureRequestExecutionService.metrics()访问。

task.scheduledTime() // 返回任务被调度的时间戳
task.startedTime() //返回任务启动时的时间戳
task.endedTime() // 返回执行任务时的时间戳
task.requestDuration //返回http请求
task.taskDuration //从调度时返回任务的持续时间

FutureRequestExecutionMetrics metrics = futureRequestExecutionService.metrics()
metrics.getActiveConnectionCount() //当前活动连接
metrics.getScheduledConnectionCount(); //当前计划的连接
metrics.getSuccessfulConnectionCount(); //成功请求的总数
metrics.getSuccessfulConnectionAverageDuration(); //平均请求持续时间
metrics.getFailedConnectionCount(); //失败任务
metrics.getFailedConnectionAverageDuration(); //失败任务的平均持续时间
metrics.getTaskCount(); //计划的任务总数
metrics.getRequestCount(); // /请求
metrics.getRequestAverageDuration(); // 平均请求持续时间
metrics.getTaskAverageDuration(); // 平均任务持续时间

 

共有 人打赏支持
粉丝 0
博文 60
码字总数 94479
×
诺岚
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: