Package io.undertow.client

Source Code of io.undertow.client.HttpClientConnectionImpl$ClientReadListener

/*
* JBoss, Home of Professional Open Source.
* Copyright 2013 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed 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.
*/

package io.undertow.client;

import java.io.IOException;
import java.net.SocketAddress;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

import io.undertow.UndertowLogger;
import io.undertow.util.HttpString;
import org.xnio.ChannelListener;
import org.xnio.ChannelListeners;
import org.xnio.IoUtils;
import org.xnio.Option;
import org.xnio.OptionMap;
import org.xnio.Pool;
import org.xnio.Pooled;
import org.xnio.XnioIoThread;
import org.xnio.XnioWorker;
import org.xnio.channels.AssembledConnectedStreamChannel;
import org.xnio.channels.ConnectedChannel;
import org.xnio.channels.ConnectedStreamChannel;
import org.xnio.channels.PushBackStreamChannel;
import org.xnio.channels.StreamSinkChannel;

import static io.undertow.client.UndertowClientMessages.MESSAGES;
import static org.xnio.Bits.allAreSet;
import static org.xnio.Bits.anyAreSet;
import static org.xnio.IoUtils.safeClose;

/**
* @author Emanuel Muckenhuber
*/
class HttpClientConnectionImpl extends HttpClientConnection implements ConnectedChannel {

    private final OptionMap options;
    private final ConnectedStreamChannel underlyingChannel;
    private final PushBackStreamChannel readChannel;

    private final Pool<ByteBuffer> bufferPool;
    private final HttpRequestQueueStrategy queuingStrategy;
    private final ClientReadListener readListener = new ClientReadListener();
    private final ChannelListener.Setter<ConnectedChannel> closeSetter;

    private static final int UPGRADED = 1 << 29;
    private static final int CLOSE_REQ = 1 << 30;
    private static final int CLOSED = 1 << 31;

    private volatile int state;
    private static final AtomicIntegerFieldUpdater<HttpClientConnectionImpl> stateUpdater = AtomicIntegerFieldUpdater.newUpdater(HttpClientConnectionImpl.class, "state");
    private volatile boolean pipelining;

    HttpClientConnectionImpl(final ConnectedStreamChannel underlyingChannel, final PushBackStreamChannel readChannel, final OptionMap options, final HttpClientImpl client) {
        super(client);
        this.options = options;
        this.underlyingChannel = underlyingChannel;
        this.readChannel = readChannel;
        this.bufferPool = client.getBufferPool();
        //
        queuingStrategy = HttpRequestQueueStrategy.create(this, options);
        closeSetter = ChannelListeners.<ConnectedChannel>getDelegatingSetter(underlyingChannel.getCloseSetter(), this);
        pipelining = queuingStrategy.supportsPipelining(); // TODO "wait" for the first response to determine this

        getCloseSetter().set(new ChannelListener<ConnectedChannel>() {
            @Override
            public void handleEvent(ConnectedChannel channel) {
                IoUtils.safeClose(HttpClientConnectionImpl.this);
                client.connectionClosed(HttpClientConnectionImpl.this);
            }
        });
    }

    ConnectedStreamChannel getChannel() {
        return underlyingChannel;
    }

    public Pool<ByteBuffer> getBufferPool() {
        return bufferPool;
    }

    @Override
    public SocketAddress getPeerAddress() {
        return underlyingChannel.getPeerAddress();
    }

    @Override
    public <A extends SocketAddress> A getPeerAddress(Class<A> type) {
        return underlyingChannel.getPeerAddress(type);
    }

    @Override
    public ChannelListener.Setter<? extends ConnectedChannel> getCloseSetter() {
        return closeSetter;
    }

    @Override
    public SocketAddress getLocalAddress() {
        return underlyingChannel.getLocalAddress();
    }

    @Override
    public <A extends SocketAddress> A getLocalAddress(Class<A> type) {
        return underlyingChannel.getLocalAddress(type);
    }

    @Override
    public XnioWorker getWorker() {
        return underlyingChannel.getWorker();
    }

    @Override
    public XnioIoThread getIoThread() {
        return underlyingChannel.getIoThread();
    }

    @Override
    public boolean isOpen() {
        return underlyingChannel.isOpen();
    }

    @Override
    public boolean supportsOption(Option<?> option) {
        return underlyingChannel.supportsOption(option);
    }

    @Override
    public <T> T getOption(Option<T> option) throws IOException {
        return underlyingChannel.getOption(option);
    }

    @Override
    public <T> T setOption(Option<T> option, T value) throws IllegalArgumentException, IOException {
        return underlyingChannel.setOption(option, value);
    }

    @Override
    OptionMap getOptions() {
        return options;
    }

    @Override
    public HttpClientRequest createRequest(HttpString method, URI target) {
        return internalCreateRequest(method, target, pipelining);
    }

    @Override
    public ConnectedStreamChannel performUpgrade() throws IOException {

        // Upgrade the connection
        // Set the upgraded flag already to prevent new requests after this one
        int oldState, newState;
        do {
            oldState = state;
            if (allAreSet(oldState, UPGRADED | CLOSE_REQ | CLOSED)) {
                throw new IOException(UndertowClientMessages.MESSAGES.connectionClosed());
            }
            newState = oldState | UPGRADED;
        } while (!stateUpdater.compareAndSet(this, oldState, newState));
        return new AssembledConnectedStreamChannel(readChannel, underlyingChannel);
    }


    /**
     * Create a http client request.
     *
     * @param method     the http method
     * @param target     the target uri
     * @param pipelining whether to potentially allow pipelining
     * @return a new request instance
     */
    protected HttpClientRequest internalCreateRequest(final HttpString method, final URI target, final boolean pipelining) {
        if (allAreSet(state, UPGRADED | CLOSE_REQ | CLOSE_REQ)) {
            return null;
        }
        return new HttpClientRequestImpl(this, underlyingChannel, method, target, pipelining);
    }

    @Override
    public void close() throws IOException {
        int oldState, newState;
        do {
            oldState = state;
            if (allAreSet(oldState, CLOSED)) {
                return;
            }
            newState = oldState | CLOSED | CLOSE_REQ;
        } while (!stateUpdater.compareAndSet(this, oldState, newState));
        underlyingChannel.close();
    }

    /**
     * Add a new request to the queue.
     *
     * @param request the request to addNewRequest
     * @throws IOException
     */
    void enqueueRequest(final PendingHttpRequest request) {
        int oldState, newState;
        do {
            oldState = state;
            if (anyAreSet(oldState, CLOSE_REQ | CLOSED)) {
                request.setFailed(new IOException(MESSAGES.connectionClosed()));
                return;
            }
            newState = oldState + 1;
        } while (!stateUpdater.compareAndSet(this, oldState, newState));
        UndertowLogger.CLIENT_LOGGER.tracef("adding new request %s %s", request, request.getRequest());
        queuingStrategy.addNewRequest(request);
    }

    /**
     * Notification that sending the request has completed.
     *
     * @param request the request
     */
    void sendingCompleted(final PendingHttpRequest request) {
        queuingStrategy.requestSent(request);
        UndertowLogger.CLIENT_LOGGER.tracef("request fully sent %s", request);
        int currentState = state;
        if (allAreSet(currentState, CLOSE_REQ)) {
            try {
                underlyingChannel.shutdownWrites();
            } catch (IOException e) {
                UndertowLogger.CLIENT_LOGGER.debugf(e, "failed to shutdown writes");
            }
        }
    }

    /**
     * Notification that the request has completed.
     *
     * @param request the request
     */
    void requestCompleted(final PendingHttpRequest request) {
        int currentState = stateUpdater.getAndDecrement(this);
        queuingStrategy.requestCompleted(request);
        UndertowLogger.CLIENT_LOGGER.tracef("request completed %s", request);
        if (allAreSet(currentState, CLOSE_REQ)) {
            try {
                close();
            } catch (IOException e) {
                UndertowLogger.CLIENT_LOGGER.debugf(e, "failed to close channel");
            }
        }
    }

    /**
     * Request a close after the next request completed.
     *
     * @throws IOException
     */
    void requestConnectionClose() throws IOException {
        int oldState, newState;
        do {
            oldState = state;
            if (anyAreSet(oldState, CLOSE_REQ | CLOSED)) {
                return;
            }
            newState = oldState | CLOSE_REQ;
        } while (!stateUpdater.compareAndSet(this, oldState, newState));
        UndertowLogger.CLIENT_LOGGER.tracef("request to close the connection");
        if (newState == CLOSE_REQ) {
            close();
        }
    }

    // Internal callback once a request is can start sending it's data
    void doSendRequest(final PendingHttpRequest request, boolean fromCallback) {
        int currentState = state;
        if (anyAreSet(currentState, CLOSE_REQ | CLOSED)) {
            request.setCancelled();
            sendingCompleted(request);
            return;
        }
        UndertowLogger.CLIENT_LOGGER.tracef("start sending request %s", request);
        if (fromCallback) { // Don't call startRequest in a read thread
            underlyingChannel.getWriteSetter().set(new ChannelListener<StreamSinkChannel>() {
                @Override
                public void handleEvent(StreamSinkChannel channel) {
                    request.startSendingRequest();
                }
            });
            underlyingChannel.resumeWrites();
        } else {
            request.startSendingRequest();
        }
    }

    // Internal callback to start reading the response for a request
    void doReadResponse(PendingHttpRequest request) {
        assert readListener.activeRequest == null;
        UndertowLogger.CLIENT_LOGGER.tracef("start reading response for %s", request);
        readListener.activeRequest = request;
        readChannel.getReadSetter().set(readListener);
        readChannel.resumeReads();
    }

    class ClientReadListener implements ChannelListener<PushBackStreamChannel> {

        volatile PendingHttpRequest activeRequest;

        @Override
        public void handleEvent(PushBackStreamChannel channel) {

            final PendingHttpRequest builder = activeRequest;
            final Pooled<ByteBuffer> pooled = bufferPool.allocate();
            final ByteBuffer buffer = pooled.getResource();
            boolean free = true;

            try {
                final ResponseParseState state = builder.getParseState();
                int res;
                do {
                    buffer.clear();
                    try {
                        res = channel.read(buffer);
                    } catch (IOException e) {
                        if (UndertowLogger.CLIENT_LOGGER.isDebugEnabled()) {
                            UndertowLogger.CLIENT_LOGGER.debugf(e, "Connection closed with IOException");
                        }
                        safeClose(channel);
                        return;
                    }

                    if (res == 0) {
                        if (!channel.isReadResumed()) {
                            channel.getReadSetter().set(this);
                            channel.resumeReads();
                        }
                        return;
                    } else if (res == -1) {
                        try {
                            channel.suspendReads();
                            channel.shutdownReads();
                            final StreamSinkChannel requestChannel = underlyingChannel;
                            requestChannel.shutdownWrites();
                            // will return false if there's a response queued ahead of this one, so we'll set up a listener then
                            if (!requestChannel.flush()) {
                                requestChannel.getWriteSetter().set(ChannelListeners.flushingChannelListener(null, null));
                                requestChannel.resumeWrites();
                            }
                            // Cancel the current active request
                            builder.setFailed(new IOException(MESSAGES.connectionClosed()));
                        } catch (IOException e) {
                            if (UndertowLogger.CLIENT_LOGGER.isDebugEnabled()) {
                                UndertowLogger.CLIENT_LOGGER.debugf(e, "Connection closed with IOException when attempting to shut down reads");
                            }
                            // Cancel the current active request
                            builder.setFailed(e);
                            IoUtils.safeClose(channel);
                            return;
                        }
                        return;
                    }

                    buffer.flip();

                    HttpResponseParser.INSTANCE.handle(buffer, state, builder);
                    if (buffer.hasRemaining()) {
                        free = false;
                        channel.unget(pooled);
                    }

                } while (!state.isComplete());

                channel.getReadSetter().set(null);
                channel.suspendReads();
                activeRequest = null;

                // Process the complete response
                builder.handleResponseComplete(HttpClientConnectionImpl.this, channel);

            } catch (Exception e) {
                UndertowLogger.CLIENT_LOGGER.exceptionProcessingRequest(e);
                IoUtils.safeClose(underlyingChannel);
            } finally {
                if (free) pooled.free();
            }
        }

    }

}
TOP

Related Classes of io.undertow.client.HttpClientConnectionImpl$ClientReadListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.