HttpCore 第二章 阻塞式I/O模型

第二章 阻塞式I/O模型

Java中的阻塞式I/O,或者叫经典I/O,代表一个高效的、方便的I/O模型,特别适合于高性能要求的应用,并发连接的数量相对稳定。现代JVM能够处理高效的上下文切换,只要连接数低于1000且所有连接都忙于传送数据,那么阻塞式I/O模型应该为原始数据的吞吐量提供最佳性能。然而对于那些大部分时间处于空闲状态的连接,上下文切换的开销是巨大的,非阻塞式I/O可能是一个更好的选择。

2.1. 阻塞式HTTP连接

HTTP连接负责HTTP消息的序列化和反序列化。用户应该很少有需要直接使用HTTP连接对象。有更高级的协议组件用于执行和处理HTTP请求。然而,在一些场景中,直接与HTTP连接交互可能是有必要的。例如,访问属性,如连接状态、socket超时时间或者本地地址和远端地址。

请牢牢记住HTTP连接不是线程安全的。我们强烈建议限制所有与HTTP连接对象的交互都在一个线程里。HttpConnection接口和子接口里唯一一个线程安全的方法是HttpConnection#shutdown()

2.1.1. 使用阻塞式HTTP连接

HttpCore不提供开放连接的所有支持,因为建立一个新连接的过程-尤其是在客户端-是非常复杂的,它涉及到一个或多个鉴权或者隧道代理。相反,阻塞式HTTP连接可以绑定到任意的网络socket上。

Socket socket = <...>

DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1024);

conn.bind(socket);

System.out.println(conn.isOpen());

HttpConnectionMetrics metrics = conn.getMetrics();

System.out.println(metrics.getRequestCount());

System.out.println(metrics.getResponseCount());

System.out.println(metrics.getReceivedBytesCount());

System.out.println(metrics.getSentBytesCount());

不管客户端还是服务端,HTTP连接接口发送和接收消息都分为两个阶段。首先发送的是消息头。依赖消息头的属性,后面可能会跟着消息体。请注意,为了通知消息已经处理完成了,总是关闭底层的内容流式很重要的。直接从底层连接的输入流发送内容的HTTP实体必须确保完全消费消息体的内容,因为这个连接要被复用。

客户端请求执行的简化处理可能看起来是这样的:

Socket socket = <...>

DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1024);

conn.bind(socket);

HttpRequest request = new BasicHttpRequest("GET""/");

conn.sendRequestHeader(request);

HttpResponse response = conn.receiveResponseHeader();

conn.receiveResponseEntity(response);

HttpEntity entity = response.getEntity();

if (entity != null) {

    // Do something useful with the entity and, when done, ensure all

    // content has been consumed, so that the underlying connection

    // can be re-used

EntityUtils.consume(entity);

}

服务端的请求处理的简化过程可能看起来是这样的:

Socket socket = <...>

DefaultBHttpServerConnection conn = new DefaultBHttpServerConnection(8 * 1024);

conn.bind(socket);

HttpRequest request = conn.receiveRequestHeader();

if (request instanceof HttpEntityEnclosingRequest) {

    conn.receiveRequestEntity((HttpEntityEnclosingRequest) request);

    HttpEntity entity = ((HttpEntityEnclosingRequest) request)

            .getEntity();

    if (entity != null) {

        // Do something useful with the entity and, when done, ensure all

        // content has been consumed, so that the underlying connection

        // could be re-used

        EntityUtils.consume(entity);

    }

}

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,

        200, "OK") ;

response.setEntity(new StringEntity("Got it") );

conn.sendResponseHeader(response);

conn.sendResponseEntity(response);

请注意,用户应该很少需要用到这些低级的方法,正常情况下应该使用更高级的服务实现代替。

2.1.2. 使用阻塞式I/O传送内容

HTTP连接使用HttpEntity接口管理内容传送的处理过程。HTTP连接生成一个实体对象,来封装输入消息的内容流。请注意HttpServerConnection#receiveRequestEntity()HttpClientConnection#receiveResponseEntity() 不会获取或者缓存任何输入的数据。它们仅仅基于输入消息的属性注入一个合适的内容编码器。使用HttpEntity#getContent()就可以通过读取封装实体的内容输入流来获取内容。输入的数据会自动被解码,对于数据消费者来说是完全透明的。同样地,HTTP连接依赖于HttpEntity#writeTo(OutputStream)方法产生输出消息的内容。如果输出消息封装了实体,那么基于这个消息的属性会自动对内容编码。

2.1.3. 支持的内容传送机制

HTTP连接的默认实现支持HTTP/1.1规范中定义的三种内容传送机制:

限定Content-Length:内容实体的终止由 Content-Length 头的值决定。最大的实体长度是:Long#MAX_VALUE

恒等编码Identity coding内容实体的终止又关闭底层连接来限定(流终止的条件)由于显而易见的原因,恒等编码只能用于服务端。最大的实体长度是无限的。

块编码Chunk coding以小块发送内容。最大实体长度:无限。

依赖于消息中封装的实体的属性会自动创建合适的内容流。

2.1.4. 终止HTTP连接

HTTP连接可以调用HttpConnection#close()方法优雅关闭,或者调用HttpConnection#shutdown()强制关闭。前一个方法尝试冲刷所有缓存的数据,然后终止连接,可能会不确定地阻塞。HttpConnection#close()方法不是线程安全的。后一个方法不冲刷内部的缓冲区终止连接,将控制权尽快返回给调用者而无需阻塞很长时间。HttpConnection#shutdown()方法是线程安全的。

2.2. HTTP异常处理

所有的HttpCore组件都潜在地可能抛出两种异常IOExceptionHttpException。如果I/O失败,例如socket超时或者重置,则抛IOException。如果HTTP失败,例如违反HTTP协议,就会抛出HttpException。通常I/O错误都不认为是致命的,且是可恢复的,而HTTP协议错误通常被认为是致命的且是不能自动恢复。

2.2.1. 协议异常

ProtocolException 表示严重违反HTTP协议,通常导致HTTP消息的处理立刻终止。

2.3. 阻塞式HTTP协议处理器

2.3.1. HTTP服务

HttpService是服务端HTTP协议处理器,它基于阻塞式I/O模型,实现了RFC 2616中描述的服务端消息处理的基本要求。

HttpService依赖于HttpProcessor实例来生成所有输出消息的强制的协议头,适用于通用的、交叉消息转换到所有的输入和输出消息,而HTTP请求处理器只关心应用特定的内容生成和处理。

HttpProcessor httpproc = HttpProcessorBuilder.create()

        .add(new ResponseDate())

        .add(new ResponseServer("MyServer-HTTP/1.1"))

        .add(new ResponseContent())

        .add(new ResponseConnControl())

        .build();

HttpService httpService = new HttpService(httpproc, null);

2.3.1.1. HTTP请求处理器

HttpRequestHandler接口表示处理一组特定的HTTP请求的例行程序。HttpService被设计为关系关心协议特定的方面,而独立的请求处理器关心的是应用特定的HTTP处理。请求处理器的目的就是要生成一个具有内容实体的响应对象,并被发送回给客户端。

HttpRequestHandler myRequestHandler = new HttpRequestHandler() {

    public void handle(

            HttpRequest request,

            HttpResponse response,

            HttpContext contextthrows HttpException, IOException {

        response.setStatusCode(HttpStatus.SC_OK);

        response.setEntity(

                new StringEntity("some important message",

                        ContentType.TEXT_PLAIN));

}

};

2.3.1.2. 请求处理器解析器

HTTP请求处理器通常由HttpRequestHandlerResolver管理,每一个请求URI都匹配一个请求处理器。HttpCore实现了非常简单的请求处理器解析器,基于琐碎的模式匹配算法:HttpRequestHandlerRegistry只支持三种格式:*<uri>* and *<uri>

HttpProcessor httpproc = <...>

HttpRequestHandler myRequestHandler1 = <...>

HttpRequestHandler myRequestHandler2 = <...>

HttpRequestHandler myRequestHandler3 = <...>

UriHttpRequestHandlerMapper handlerMapper = new UriHttpRequestHandlerMapper();

handlerMapper.register("/service/*", myRequestHandler1);

handlerMapper.register("*.do", myRequestHandler2);

handlerMapper.register("*", myRequestHandler3);

HttpService httpService = new HttpService(httpprochandlerMapper);

鼓励用户提供更加复杂的HttpRequestHandlerResolver实现,例如基于正则表达式。

2.3.1.3. 使用HTTP服务处理请求

当完全初始化并配置时,HttpService可以用于执行和处理活动的HTTP连接的请求。HttpService#handleRequest()方法读取输入的请求,并生成响应,发送回给客户端。这个方法可以在循环中执行以处理持久连接中的多个请求。HttpService#handleRequest()方法在多个线程中执行是线程安全的。这允许同时在多个连接上处理请求,只要HTTPService使用的所有协议拦截器和请求处理器是线程安全的

HttpService httpService = <...>

HttpServerConnection conn = <...>

HttpContext context = <...>

boolean active = true;

try {

    while (active && conn.isOpen()) {

        httpService.handleRequest(conncontext);

    }

finally {

conn.shutdown();

}

2.3.2. HTTP请求执行器

HttpRequestExecutor是基于阻塞式I/O模型的客户端协议处理器,它实现了客户端消息处理的HTTP协议基本要求(在RFC2616中描述)。HttpRequestExecutor依赖于HttpProcessor实例,为了所有的外发消息生成强制的协议头,对于所有接收消息和外发消息应用通用的、交叉消息转换。一旦执行请求,和收到响应,应用特定的处理可以再HttpRequestExecutor外面实习。

HttpClientConnection conn = <...>

HttpProcessor httpproc = HttpProcessorBuilder.create()

        .add(new RequestContent())

        .add(new RequestTargetHost())

        .add(new RequestConnControl())

        .add(new RequestUserAgent("MyClient/1.1"))

        .add(new RequestExpectContinue(true))

        .build();

HttpRequestExecutor httpexecutor = new HttpRequestExecutor();

HttpRequest request = new BasicHttpRequest("GET""/");

HttpCoreContext context = HttpCoreContext.create();

httpexecutor.preProcess(request, httpproc, context);

HttpResponse response = httpexecutor.execute(requestconncontext);

httpexecutor.postProcess(response, httpproc, context);

HttpEntity entity = response.getEntity();

EntityUtils.consume(entity);

HttpRequestExecutor从多个线程同时执行是安全的。这允许同时执行多个连接的请求,只要HttpRequestExecutor使用的所有协议拦截器是线程安全的。

2.3.3. 连接持久化/复用

ConnectionReuseStrategy接口用于确定是否底层的连接可以在当前的消息处理完之后被复用,以处理将来接收的消息。默认的连接复用策略尝试尽可能地保持连接存活。首先,它会检查用于发送消息的HTTP协议版本。HTTP/1.1默认是持久连接,而HTTP/1.0则不是。其次,它会检查Connection头的值。表明是否在对端重用连接,可以在Connection头信息中发送Keep-Alive或者Close值。之后,如果有封装实体的话,基于封装实体的属性,这个策略可以决定连接是否可安全重用。

2.4. 连接池

高效的客户端HTTP传输经常需要有效的复用持久连接。HttpCore提供了持久HTTP连接的管理池以帮助实现连接的复用处理。连接池的实现是线程安全的,目前可以被多个消费者使用。

默认情况下,连接池只允许总共20条并发连接,每一个路由2条并发连接。两条连接限制是基于HTTP规范的要求。然而,在实践中,这有很多限制。用户可以修改连接池配置,允许更多的并发连接(依赖于特定的应用上下文)。

HttpHost target = new HttpHost("localhost");

BasicConnPool connpool = new BasicConnPool();

connpool.setMaxTotal(200);

connpool.setDefaultMaxPerRoute(10);

connpool.setMaxPerRoute(target, 20);

Future<BasicPoolEntry> future = connpool.lease(targetnull);

BasicPoolEntry poolEntry = future.get();

HttpClientConnection conn = poolEntry.getConnection();

请注意,连接池无法知道一个专线连接是否仍然在使用中。一旦连接不再使用,即使连接可以被复用,连接池的用户也要负责将连接释放回连接池。

BasicConnPool connpool = <...>

Future<BasicPoolEntry> future = connpool.lease(target, null);

BasicPoolEntry poolEntry = future.get();

try {

    HttpClientConnection conn = poolEntry.getConnection();

finally {

connpool.release(poolEntrytrue);

}

连接池的状态可以在运行时查询:

HttpHost target = new HttpHost("localhost");

BasicConnPool connpool = <...>

PoolStats totalStats = connpool.getTotalStats();

System.out.println("total available: " + totalStats.getAvailable());

System.out.println("total leased: " + totalStats.getLeased());

System.out.println("total pending: " + totalStats.getPending());

PoolStats targetStats = connpool.getStats(target);

System.out.println("target available: " + targetStats.getAvailable());

System.out.println("target leased: " + targetStats.getLeased());

System.out.println("target pending: " + targetStats.getPending());


2.5. 支持TLS/SSL

阻塞连接可以绑定到任意的socket上。这使得SSL非常容易。任何SSLSocket实例都可以绑定到一个阻塞连接上,以实现所有的消息都通过安全的TLS/SSL连接传送。

SSLContext sslcontext = SSLContexts.createSystemDefault();

SocketFactory sf = sslcontext.getSocketFactory();

SSLSocket socket = (SSLSocketsf.createSocket("somehost", 443);

// Enforce TLS and disable SSL

socket.setEnabledProtocols(new String[] {

        "TLSv1",

        "TLSv1.1",

        "TLSv1.2" });

// Enforce strong ciphers

socket.setEnabledCipherSuites(new String[] {

        "TLS_RSA_WITH_AES_256_CBC_SHA",

        "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",

        "TLS_DHE_DSS_WITH_AES_256_CBC_SHA" });

DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1204);

conn.bind(socket);

2.6. 内嵌的HTTP服务端

4.4版本开始,HttpCore装配了一个基于上述的阻塞I/O组件的内嵌HTTP服务端。

HttpRequestHandler requestHandler = <...>

HttpProcessor httpProcessor = <...>

SocketConfig socketConfig = SocketConfig.custom()

        .setSoTimeout(15000)

        .setTcpNoDelay(true)

        .build();

final HttpServer server = ServerBootstrap.bootstrap()

        .setListenerPort(8080)

        .setHttpProcessor(httpProcessor)

        .setSocketConfig(socketConfig)

        .setExceptionLogger(new StdErrorExceptionLogger())

        .registerHandler("*"requestHandler)

        .create();

server.start();

server.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);

 

Runtime.getRuntime().addShutdownHook(new Thread() {

    @Override

    public void run() {

        server.shutdown(5, TimeUnit.SECONDS);

}

});

 

org.apache.http.ConnectionClosedException.class org.apache.http.ConnectionReuseStrategy.class org.apache.http.Consts.class org.apache.http.ContentTooLongException.class org.apache.http.ExceptionLogger.class org.apache.http.FormattedHeader.class org.apache.http.Header.class org.apache.http.HeaderElement.class org.apache.http.HeaderElementIterator.class org.apache.http.HeaderIterator.class org.apache.http.HttpClientConnection.class org.apache.http.HttpConnection.class org.apache.http.HttpConnectionFactory.class org.apache.http.HttpConnectionMetrics.class org.apache.http.HttpEntity.class org.apache.http.HttpEntityEnclosingRequest.class org.apache.http.HttpException.class org.apache.http.HttpHeaders.class org.apache.http.HttpHost.class org.apache.http.HttpInetConnection.class org.apache.http.HttpMessage.class org.apache.http.HttpRequest.class org.apache.http.HttpRequestFactory.class org.apache.http.HttpRequestInterceptor.class org.apache.http.HttpResponse.class org.apache.http.HttpResponseFactory.class org.apache.http.HttpResponseInterceptor.class org.apache.http.HttpServerConnection.class org.apache.http.HttpStatus.class org.apache.http.HttpVersion.class org.apache.http.MalformedChunkCodingException.class org.apache.http.MessageConstraintException.class org.apache.http.MethodNotSupportedException.class org.apache.http.NameValuePair.class org.apache.http.NoHttpResponseException.class org.apache.http.ParseException.class org.apache.http.ProtocolException.class org.apache.http.ProtocolVersion.class org.apache.http.ReasonPhraseCatalog.class org.apache.http.RequestLine.class org.apache.http.StatusLine.class org.apache.http.TokenIterator.class org.apache.http.TruncatedChunkException.class org.apache.http.UnsupportedHttpVersionException.class org.apache.http.annotation.Contract.class org.apache.http.annotation.Experimental.class org.apache.http.annotation.Obsolete.class org.apache.http.annotation.ThreadingBehavior.class org.apache.http.annotation.package-info.class org.apache.http.concurrent.BasicFuture.class org.apache.http.concurrent.Cancellable.class org.apache.http.concurrent.FutureCallback.class org.apache.http.concurrent.package-info.class org.apache.http.config.ConnectionConfig.class org.apache.http.config.Lookup.class org.apache.http.config.MessageConstraints.class org.apache.http.config.Registry.class org.apache.http.config.RegistryBuilder.class org.apache.http.config.SocketConfig.class org.apache.http.config.package-info.class org.apache.http.entity.AbstractHttpEntity.class org.apache.http.entity.BasicHttpEntity.class org.apache.http.entity.BufferedHttpEntity.class org.apache.http.entity.ByteArrayEntity.class org.apache.http.entity.ContentLengthStrategy.class org.apache.http.entity.ContentProducer.class org.apache.http.entity.ContentType.class org.apache.http.entity.EntityTemplate.class org.apache.http.entity.FileEntity.class org.apache.http.entity.HttpEntityWrapper.class org.apache.http.entity.InputStreamEntity.class org.apache.http.entity.SerializableEntity.class org.apache.http.entity.StringEntity.class org.apache.http.entity.package-info.class org.apache.http.impl.AbstractHttpClientConnection.class org.apache.http.impl.AbstractHttpServerConnection.class org.apache.http.impl.BHttpConnectionBase.class org.apache.http.impl.ConnSupport.class org.apache.http.impl.DefaultBHttpClientConnection.class org.apache.http.impl.DefaultBHttpClientConnectionFactory.class org.apache.http.impl.DefaultBHttpServerConnection.class org.apache.http.impl.DefaultBHttpServerConnectionFactory.class org.apache.http.impl.DefaultConnectionReuseStrategy.class org.apache.http.impl.DefaultHttpClientConnection.class org.apache.http.impl.DefaultHttpRequestFactory.class org.apache.http.impl.DefaultHttpResponseFactory.class org.apache.http.impl.DefaultHttpServerConnection.class org.apache.http.impl.EnglishReasonPhraseCatalog.class org.apache.http.impl.HttpConnectionMetricsImpl.class org.apache.http.impl.NoConnectionReuseStrategy.class org.apache.http.impl.SocketHttpClientConnection.class org.apache.http.impl.SocketHttpServerConnection.class org.apache.http.impl.bootstrap.HttpServer.class org.apache.http.impl.bootstrap.RequestListener.class org.apache.http.impl.bootstrap.SSLServerSetupHandler.class org.apache.http.impl.bootstrap.ServerBootstrap.class org.apache.http.impl.bootstrap.ThreadFactoryImpl.class
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值