package com.cycore.dbs.common.demo.hc; import java.io.IOException; import java.security.Principal; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.ParseException; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; import org.apache.http.client.UserTokenHandler; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.concurrent.FutureCallback; import org.apache.http.conn.HttpConnectionFactory; import org.apache.http.conn.ManagedHttpClientConnection; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.impl.DefaultHttpResponseFactory; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.FutureRequestExecutionMetrics; import org.apache.http.impl.client.FutureRequestExecutionService; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpRequestFutureTask; import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.impl.io.DefaultHttpRequestWriterFactory; import org.apache.http.impl.io.DefaultHttpResponseParserFactory; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicLineParser; import org.apache.http.protocol.HttpContext; import org.apache.http.util.CharArrayBuffer; /** * 自定义客户端连接:通常插入一个定制化的消息解析器或一个定制化的连接实现包括以下若干步: * 1.提供一个自定义的 LineParser/LineFormatter接口实现。实现要求的解析/格式化逻辑。 * @author xiawq * */ class MyLineParser extends BasicLineParser{ @Override public Header parseHeader(CharArrayBuffer buffer) throws ParseException { try { return super.parseHeader(buffer); } catch (Exception e) { //Suppress ParseException exception return new BasicHeader(buffer.toString(), null); } } } public class HcAdvancedTopicDemo { /** * 2.提供一个自定义的HttpConnectionFactory实现,按要求用自定义的替换请求writer和/或响应parser。 */ public void m1(){ HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory = new ManagedHttpClientConnectionFactory( new DefaultHttpRequestWriterFactory(), new DefaultHttpResponseParserFactory( new MyLineParser(), new DefaultHttpResponseFactory())); } /** * 3.配置HttpClient使用自定义的连接工厂 */ public void m2(){ HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory = new ManagedHttpClientConnectionFactory( new DefaultHttpRequestWriterFactory(), new DefaultHttpResponseParserFactory( new MyLineParser(), new DefaultHttpResponseFactory())); PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(connFactory); CloseableHttpClient client = HttpClients.custom() .setConnectionManager(cm) .build(); } /** * 状态HTTP连接:当HTTP说明假设会话状态信息常以HTTP cookie的形式被嵌入HTTP消息因此HTTP连接一直无状态, * 这种假设在真实环境中并不总是成立。当HTTP连接被一个特殊用户身份或者有一个特殊安全上下文以至于连接不能被其它 * 用户共享但连接能被同一个用户重用时就是这样的例子。 * 像这样状态HTTP连接的例子是NLTM认证连接和客户端证书认证的SSL连接。 * * 1.使用token handler:HttpClient依靠UserTokenHandler接口去决定给定的执行上下文是否针对特殊用户。 * 由handler返回的token对象被期望只识别当前的用户如果上下文是针对特殊说明的用户,但如果上下文不包含任何 * 针对当前用户的资源或详细信息,这个token为空。用户token将被用来保证用户特定资源将不会被其它用户共享或重用。 * * 默认的UserTokenHandler接口实现使用Principal类的一个实例去代表HTTP连接的一个状态对象,前提是该对象能够从指定的执行上下文中获取到。 * DefaultUserTokenHandler将基于认证计划如NTLM或客户端认证开启的SSL会话使用连接得principal委托。如果两个认证都不可用,那么将会返回一个空的token(null)。 * @throws IOException * @throws ClientProtocolException */ public void m3() throws ClientProtocolException, IOException{ CloseableHttpClient httpClient = HttpClients.createDefault(); HttpClientContext context = HttpClientContext.create(); HttpGet httpGet = new HttpGet("http://localhost:8081/res/upload/001.jpg"); CloseableHttpResponse response = httpClient.execute(httpGet, context); try { Principal principal = context.getUserToken(Principal.class); System.out.println(principal); } finally { response.close(); } } /** * 如果默认的token handler不满足需要,用户可以自定义实现。 */ public void m4(){ UserTokenHandler userTokenHandler = new UserTokenHandler() { public Object getUserToken(HttpContext context) { return context.getAttribute("my-token"); } }; CloseableHttpClient httpClient = HttpClients.custom() .setUserTokenHandler(userTokenHandler) .build(); } /** * 2.持久化状态连接:一个持久连接携带了一个状态对象,该对象可以被重用, * 只要在请求被执行的时候,该相同的状态对象是绑定在执行上下文上的。 * 所以,保证相同的上下文在同一用户发起的后续请求执行时可重用或者用户token优先于 * 请求执行而被绑定在上下文真的很重要。 * @throws IOException * @throws ClientProtocolException */ public void m5() throws ClientProtocolException, IOException{ 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 principal1 = context1.getUserToken(Principal.class); HttpClientContext context2 = HttpClientContext.create(); context2.setUserToken(principal1); HttpGet httpGet2 = new HttpGet("http://localhost:8080/"); CloseableHttpResponse response2 = httpClient.execute(httpGet2, context2); try { HttpEntity entity2 = response1.getEntity(); } finally { response2.close(); } } /** * 使用FutureRequestExecutionService:使用FutureRequestExecutionService,你可以调度http调用, * 并且可以把response当做一个Future任务.这对多次调用一个web服务很有用.用该类的优势在于你能 * 使用多线程调度并发请求,设置任务过期时间,或在一个响应不在需要的时候取消它们. * * FutureRequestExecutionService封装了带有一个HttpRequestFutureTask(继承自FutureTask)的请求, * 这能允许你既能取消任务也能跟踪各种度量比如请求持续时间. * * futureRequestExecutionService构造器需要任何现存的httpClient实例和一个ExecutorService实例. * 当两者都配置好后,调整最大连接数和你将使用的线程数很重要.当有比连接数更多的线程数时,连接可能 * 会启动超时因为没有足够可用的连接.当有比线程数更多的连接数时,futureRequestExecutionService * 将不会使用它们的全部. */ public void m6(){ HttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(5).build(); ExecutorService executorService = Executors.newFixedThreadPool(5); FutureRequestExecutionService futureRequestExecutionService = new FutureRequestExecutionService(httpClient, executorService); } /** * 1.调度计划请求:调度请求只需要提供一个HttpUriRequest, HttpContext和一个ResponseHandler. * 因为该请求是被executor服务处理的,一个ResponseHandler是受托者. * * 2.取消任务:调度任务可能会被取消.如果任务不再执行但是仅仅在排队执行,它将永远不会执行. * 如果它正在执行且mayInterruptIfRunning参数设置为true,abort()方法将会被在请求上调用; * 否则响应将会被忽视但是请求将允许正常执行.任何后续对task.get()的调用将会失败并产生一个 * IllegalStateException异常.应该要注意取消任务时只释放了客户端资源.请求实际上可能在服务端被正常处理了. * @throws ExecutionException * @throws InterruptedException */ public void m7() throws InterruptedException, ExecutionException{ HttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(5).build(); ExecutorService executorService = Executors.newFixedThreadPool(5); FutureRequestExecutionService futureRequestExecutionService = new FutureRequestExecutionService(httpClient, executorService); 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(); task.cancel(true); task.get();//throws an Exception } /** * 回调:为了手工代替调用task.get()方法,你可以用一个FutureCallback实例,该实例在请求完成时回去回调. * 以下是和用在HttpAsyncClient中的接口一样. */ public void m8(){ HttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(5).build(); ExecutorService executorService = Executors.newFixedThreadPool(5); FutureRequestExecutionService futureRequestExecutionService = new FutureRequestExecutionService(httpClient, executorService); HttpRequestFutureTask<Boolean> task = futureRequestExecutionService.execute( new HttpGet("www.google.com"), HttpClientContext.create(), new OkidokiHandler(), new MyCallback()); } //自定义响应处理器类 private final class OkidokiHandler implements ResponseHandler<Boolean>{ public Boolean handleResponse(HttpResponse response) throws ClientProtocolException, IOException { return response.getStatusLine().getStatusCode() == 200; } } //自定义任务回调类 private final class MyCallback implements FutureCallback<Boolean>{ public void cancelled() { // TODO Auto-generated method stub //do something } public void completed(Boolean arg0) { // TODO Auto-generated method stub //do something } public void failed(Exception arg0) { // TODO Auto-generated method stub //do something } } /** * 指标(metrics):FutureRequestExecutionService典型应用于有大量web服务访问的应用中. * 为了便于监控或配置协调,FutureRequestExecutionService跟踪若干指标. * * 每一个HttpRequestFutureTask提供了方法去获取任务调度/开始/结束的时间.此外, * 请求和任务持续时间也可用.这些指标被聚集在FutureRequestExecutionService的一个 * FutureRequestExecutionMetrics实例里,该实例可以通过 FutureRequestExecutionService.metrics()访问. * */ public void m9(){ HttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(5).build(); ExecutorService executorService = Executors.newFixedThreadPool(5); FutureRequestExecutionService futureRequestExecutionService = new FutureRequestExecutionService(httpClient, executorService); HttpRequestFutureTask<Boolean> task = futureRequestExecutionService.execute( new HttpGet("www.google.com"), HttpClientContext.create(), new OkidokiHandler(), new MyCallback()); task.scheduledTime();//任务调度时间戳 task.startedTime();//任务启动时间戳 task.endedTime();//任务结束时间戳 task.requestDuration();//请求持续时间 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();//平均任务时间 } }