Java tutorial
/* * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * */ package org.apache.http.impl.nio.client; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; import org.apache.http.ConnectionClosedException; import org.apache.http.ConnectionReuseStrategy; import org.apache.http.HttpException; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpRequestWrapper; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.concurrent.BasicFuture; import org.apache.http.conn.ConnectionKeepAliveStrategy; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.nio.ContentDecoder; import org.apache.http.nio.ContentEncoder; import org.apache.http.nio.IOControl; import org.apache.http.nio.NHttpClientConnection; import org.apache.http.nio.conn.NHttpClientConnectionManager; import org.apache.http.nio.protocol.HttpAsyncRequestProducer; import org.apache.http.nio.protocol.HttpAsyncResponseConsumer; import org.apache.http.nio.protocol.Pipelined; import org.apache.http.protocol.HttpCoreContext; import org.apache.http.protocol.HttpProcessor; import org.apache.http.util.Args; import org.apache.http.util.Asserts; /** * {@link org.apache.http.nio.protocol.HttpAsyncClientExchangeHandler} implementation * that supports HTTP message pipelining. * <p> * Instances of this class are expected to be accessed by one thread at a time only. * The {@link #cancel()} method can be called concurrently by multiple threads. */ @Pipelined class PipeliningClientExchangeHandlerImpl<T> extends AbstractClientExchangeHandler { private final HttpHost target; private final Queue<HttpAsyncRequestProducer> requestProducerQueue; private final Queue<HttpAsyncResponseConsumer<T>> responseConsumerQueue; private final Queue<HttpRequest> requestQueue; private final Queue<T> resultQueue; private final HttpClientContext localContext; private final BasicFuture<List<T>> resultFuture; private final HttpProcessor httpProcessor; private final AtomicReference<HttpAsyncRequestProducer> requestProducerRef; private final AtomicReference<HttpAsyncResponseConsumer<T>> responseConsumerRef; public PipeliningClientExchangeHandlerImpl(final Log log, final HttpHost target, final List<? extends HttpAsyncRequestProducer> requestProducers, final List<? extends HttpAsyncResponseConsumer<T>> responseConsumers, final HttpClientContext localContext, final BasicFuture<List<T>> resultFuture, final NHttpClientConnectionManager connmgr, final HttpProcessor httpProcessor, final ConnectionReuseStrategy connReuseStrategy, final ConnectionKeepAliveStrategy keepaliveStrategy) { super(log, localContext, resultFuture, connmgr, connReuseStrategy, keepaliveStrategy); Args.notNull(target, "HTTP target"); Args.notEmpty(requestProducers, "Request producer list"); Args.notEmpty(responseConsumers, "Response consumer list"); Args.check(requestProducers.size() == responseConsumers.size(), "Number of request producers does not match that of response consumers"); this.target = target; this.requestProducerQueue = new ConcurrentLinkedQueue<HttpAsyncRequestProducer>(requestProducers); this.responseConsumerQueue = new ConcurrentLinkedQueue<HttpAsyncResponseConsumer<T>>(responseConsumers); this.requestQueue = new ConcurrentLinkedQueue<HttpRequest>(); this.resultQueue = new ConcurrentLinkedQueue<T>(); this.localContext = localContext; this.resultFuture = resultFuture; this.httpProcessor = httpProcessor; this.requestProducerRef = new AtomicReference<HttpAsyncRequestProducer>(null); this.responseConsumerRef = new AtomicReference<HttpAsyncResponseConsumer<T>>(null); } private void closeProducer(final HttpAsyncRequestProducer requestProducer) { if (requestProducer != null) { try { requestProducer.close(); } catch (final IOException ex) { this.log.debug("I/O error closing request producer", ex); } } } private void closeConsumer(final HttpAsyncResponseConsumer<?> responseConsumer) { if (responseConsumer != null) { try { responseConsumer.close(); } catch (final IOException ex) { this.log.debug("I/O error closing response consumer", ex); } } } @Override void releaseResources() { closeProducer(this.requestProducerRef.getAndSet(null)); closeConsumer(this.responseConsumerRef.getAndSet(null)); while (!this.requestProducerQueue.isEmpty()) { closeProducer(this.requestProducerQueue.remove()); } while (!this.responseConsumerQueue.isEmpty()) { closeConsumer(this.responseConsumerQueue.remove()); } this.requestQueue.clear(); this.resultQueue.clear(); } @Override void executionFailed(final Exception ex) { final HttpAsyncRequestProducer requestProducer = this.requestProducerRef.get(); if (requestProducer != null) { requestProducer.failed(ex); } final HttpAsyncResponseConsumer<T> responseConsumer = this.responseConsumerRef.get(); if (responseConsumer != null) { responseConsumer.failed(ex); } for (final HttpAsyncResponseConsumer<T> cancellable : this.responseConsumerQueue) { cancellable.cancel(); } } @Override boolean executionCancelled() { final HttpAsyncResponseConsumer<T> responseConsumer = this.responseConsumerRef.get(); final boolean cancelled = responseConsumer != null && responseConsumer.cancel(); this.resultFuture.cancel(); return cancelled; } public void start() throws HttpException, IOException { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + getId() + "] start execution"); } final HttpRoute route = new HttpRoute(this.target); setRoute(route); this.localContext.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, this.target); this.localContext.setAttribute(HttpClientContext.HTTP_ROUTE, route); requestConnection(); } @Override public HttpRequest generateRequest() throws IOException, HttpException { verifytRoute(); if (!isRouteEstablished()) { onRouteToTarget(); onRouteComplete(); } final NHttpClientConnection localConn = getConnection(); this.localContext.setAttribute(HttpCoreContext.HTTP_CONNECTION, localConn); Asserts.check(this.requestProducerRef.get() == null, "Inconsistent state: currentRequest producer is not null"); final HttpAsyncRequestProducer requestProducer = this.requestProducerQueue.poll(); if (requestProducer == null) { return null; } this.requestProducerRef.set(requestProducer); final HttpRequest original = requestProducer.generateRequest(); final HttpRequestWrapper currentRequest = HttpRequestWrapper.wrap(original); final RequestConfig config = this.localContext.getRequestConfig(); if (config.getSocketTimeout() > 0) { localConn.setSocketTimeout(config.getSocketTimeout()); } this.httpProcessor.process(currentRequest, this.localContext); this.requestQueue.add(currentRequest); setCurrentRequest(currentRequest); return currentRequest; } @Override public void produceContent(final ContentEncoder encoder, final IOControl ioctrl) throws IOException { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + getId() + "] produce content"); } final HttpAsyncRequestProducer requestProducer = this.requestProducerRef.get(); Asserts.check(requestProducer != null, "Inconsistent state: request producer is null"); requestProducer.produceContent(encoder, ioctrl); if (encoder.isCompleted()) { requestProducer.resetRequest(); } } @Override public void requestCompleted() { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + getId() + "] Request completed"); } final HttpAsyncRequestProducer requestProducer = this.requestProducerRef.getAndSet(null); Asserts.check(requestProducer != null, "Inconsistent state: request producer is null"); requestProducer.requestCompleted(this.localContext); try { requestProducer.close(); } catch (final IOException ioex) { this.log.debug(ioex.getMessage(), ioex); } } @Override public void responseReceived(final HttpResponse response) throws IOException, HttpException { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + getId() + "] Response received " + response.getStatusLine()); } Asserts.check(this.responseConsumerRef.get() == null, "Inconsistent state: response consumer is not null"); final HttpAsyncResponseConsumer<T> responseConsumer = this.responseConsumerQueue.poll(); Asserts.check(responseConsumer != null, "Inconsistent state: response consumer queue is empty"); this.responseConsumerRef.set(responseConsumer); final HttpRequest request = this.requestQueue.poll(); Asserts.check(request != null, "Inconsistent state: request queue is empty"); this.localContext.setAttribute(HttpCoreContext.HTTP_REQUEST, request); this.localContext.setAttribute(HttpCoreContext.HTTP_RESPONSE, response); this.httpProcessor.process(response, this.localContext); responseConsumer.responseReceived(response); setCurrentResponse(response); } @Override public void consumeContent(final ContentDecoder decoder, final IOControl ioctrl) throws IOException { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + getId() + "] Consume content"); } final HttpAsyncResponseConsumer<T> responseConsumer = this.responseConsumerRef.get(); Asserts.check(responseConsumer != null, "Inconsistent state: response consumer is null"); responseConsumer.consumeContent(decoder, ioctrl); } @Override public void responseCompleted() throws IOException, HttpException { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + getId() + "] Response processed"); } final boolean keepAlive = manageConnectionPersistence(); final HttpAsyncResponseConsumer<T> responseConsumer = this.responseConsumerRef.getAndSet(null); Asserts.check(responseConsumer != null, "Inconsistent state: response consumer is null"); try { responseConsumer.responseCompleted(this.localContext); final T result = responseConsumer.getResult(); final Exception ex = responseConsumer.getException(); try { responseConsumer.close(); } catch (final IOException ioex) { this.log.debug(ioex.getMessage(), ioex); } if (result != null) { this.resultQueue.add(result); } else { failed(ex); } if (!this.resultFuture.isDone() && this.responseConsumerQueue.isEmpty()) { this.resultFuture.completed(new ArrayList<T>(this.resultQueue)); this.resultQueue.clear(); } if (this.resultFuture.isDone()) { close(); } else { if (!keepAlive) { failed(new ConnectionClosedException("Connection closed")); } else { final NHttpClientConnection localConn = getConnection(); if (localConn != null) { localConn.requestOutput(); } else { requestConnection(); } } } } catch (final RuntimeException ex) { failed(ex); throw ex; } } @Override public void inputTerminated() { failed(new ConnectionClosedException("Connection closed")); } public void abortConnection() { discardConnection(); } }