Package io.undertow.client

Source Code of io.undertow.client.PendingHttpRequest

package io.undertow.client;

import io.undertow.UndertowLogger;
import io.undertow.UndertowOptions;
import io.undertow.conduits.ChunkedStreamSourceConduit;
import io.undertow.conduits.ConduitListener;
import io.undertow.conduits.EmptyStreamSourceConduit;
import io.undertow.conduits.FixedLengthStreamSourceConduit;
import io.undertow.util.HeaderMap;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
import io.undertow.util.Methods;
import io.undertow.util.Protocols;
import org.xnio.OptionMap;
import org.xnio.Result;
import org.xnio.channels.PushBackStreamChannel;
import org.xnio.channels.StreamSourceChannel;
import org.xnio.conduits.ConduitStreamSourceChannel;
import org.xnio.conduits.StreamSourceChannelWrappingConduit;
import org.xnio.conduits.StreamSourceConduit;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

import static org.xnio.Bits.allAreClear;
import static org.xnio.Bits.allAreSet;
import static org.xnio.Bits.anyAreSet;

/**
* A pending http request.
*
* @author Emanuel Muckenhuber
*/
public final class PendingHttpRequest {

    private final ResponseParseState parseState = new ResponseParseState();

    private int statusCode;
    private HttpString protocol;
    private String reasonPhrase;
    private final HeaderMap responseHeaders = new HeaderMap();

    private final boolean pipeline;
    private final HttpClientRequestImpl request;
    private final HttpClientConnectionImpl connection;
    private final Result<HttpClientResponse> result;
    private final HttpContinueNotification continueHandler;

    private volatile int state = INITIAL;
    private static final AtomicIntegerFieldUpdater<PendingHttpRequest> stateUpdater = AtomicIntegerFieldUpdater.newUpdater(PendingHttpRequest.class, "state");

    // State
    private static final int INITIAL = 1 << 0;
    private static final int SENDING_REQUEST = 1 << 2;
    private static final int RECEIVING = 1 << 3;
    private static final int COMPLETED = 1 << 4;

    // Request properties
    private static final int OPEN_GATE = 1 << 29;
    private static final int FLUSH_HEADERS = 1 << 30;
    private static final int SHUTDOWN_WRITES= 1 << 31;

    PendingHttpRequest(final HttpClientRequestImpl request, final HttpClientConnectionImpl connection,
                       final boolean keepAlive, boolean hasContent, boolean expectContinue,
                       final boolean pipeline, final Result<HttpClientResponse> result, final HttpContinueNotification handler) {

        this.request = request;
        this.connection = connection;
        this.pipeline = pipeline;
        this.result = result;
        continueHandler = handler;
        if(! keepAlive) {
            state = state | SHUTDOWN_WRITES;
        }
        if(! hasContent || expectContinue) {
            state = state | FLUSH_HEADERS;
        }
        if(! expectContinue) {
            state = state | OPEN_GATE;
        }
    }

    public ResponseParseState getParseState() {
        return parseState;
    }

    HeaderMap getResponseHeaders() {
        return responseHeaders;
    }

    int getStatusCode() {
        return statusCode;
    }

    void setStatusCode(final int statusCode) {
        this.statusCode = statusCode;
    }

    String getReasonPhrase() {
        return reasonPhrase;
    }

    void setReasonPhrase(final String reasonPhrase) {
        this.reasonPhrase = reasonPhrase;
    }

    HttpString getProtocol() {
        return protocol;
    }

    @SuppressWarnings("unused")
    void setProtocol(final HttpString protocol) {
        this.protocol = protocol;
    }

    HttpClientRequest getRequest() {
        return request;
    }

    protected void setCancelled() {
        int oldVal, newVal;
        do {
            oldVal = state;
            if (allAreSet(oldVal, COMPLETED)) {
                return;
            }
            newVal = oldVal | COMPLETED;
        } while (! stateUpdater.compareAndSet(this, oldVal, newVal));
        result.setCancelled();
    }

    protected void setFailed(final IOException e) {
        int oldVal, newVal;
        do {
            oldVal = state;
            if (allAreSet(oldVal, COMPLETED)) {
                return;
            }
            newVal = oldVal | COMPLETED;
        } while (! stateUpdater.compareAndSet(this, oldVal, newVal));
        result.setException(e);
    }

    /**
     * Allow writing the request.
     */
    protected void startSendingRequest() {
        int oldVal, newVal;
        do {
            oldVal = state;
            if (anyAreSet(oldVal, SENDING_REQUEST | COMPLETED)) {
                return;
            }
            newVal = oldVal | SENDING_REQUEST;
        } while (! stateUpdater.compareAndSet(this, oldVal, newVal));
        final boolean openGate = allAreSet(newVal, OPEN_GATE);
        if(openGate) {
            // Start the response
            request.openGate();
        }
        if(allAreSet(newVal, FLUSH_HEADERS)) {
            request.flushHeaders(this, openGate);
        }
        // For empty streams the callbacks are getting called before
        if(allAreSet(newVal, SENDING_REQUEST | RECEIVING)) {
            // Prevent subsequent requests if the connection should be closed
            if(allAreSet(newVal, SHUTDOWN_WRITES)) {
                try {
                    connection.requestConnectionClose();
                } catch (IOException e) {
                    UndertowLogger.CLIENT_LOGGER.debugf(e, "failed to shutdown writes");
                }
            }
            // Done sending
            connection.sendingCompleted(this);
        }
    }

    /**
     * Notification after the request was fully sent.
     */
    protected void requestSent() {
        int oldVal, newVal;
        do {
            oldVal = state;
            if (anyAreSet(oldVal, RECEIVING | COMPLETED)) {
                return;
            }
            newVal = oldVal | RECEIVING;
        } while (! stateUpdater.compareAndSet(this, oldVal, newVal));
        // If the gate is open and we are done writing
        if(allAreSet(newVal, SENDING_REQUEST | RECEIVING)) {
            if(allAreSet(newVal, SHUTDOWN_WRITES)) {
                try {
                    connection.requestConnectionClose();
                } catch (IOException e) {
                    UndertowLogger.CLIENT_LOGGER.debugf(e, "failed to shutdown writes");
                }
            }
            // Done sending
            connection.sendingCompleted(this);
        }
    }

    /**
     * Completed when the response was fully read.
     *
     * @param close whether the connection should be closed after this request or not
     */
    protected void completed(boolean close) {
        int oldVal, newVal;
        do {
            oldVal = state;
            if (allAreSet(oldVal, COMPLETED)) {
                return;
            }
            newVal = oldVal | COMPLETED;
        } while (! stateUpdater.compareAndSet(this, oldVal, newVal));
        if(close) {
            try {
                connection.requestConnectionClose();
            } catch (IOException e) {
                UndertowLogger.CLIENT_LOGGER.debugf(e, "failed to shutdown reads and writes");
            }
        }
        // Complete
        connection.requestCompleted(this);
    }

    /**
     * Handle a fully parsed response.
     *
     * @param connection the http connection
     * @param channel the response channel
     */
    void handleResponseComplete(final HttpClientConnectionImpl connection, PushBackStreamChannel channel) {
        assert parseState.isComplete(); // The header needs to be fully parsed
        assert allAreClear(state, COMPLETED); // The request cannot not completed yet
        UndertowLogger.CLIENT_LOGGER.tracef("reading response headers complete for %s", this);

        // Handle http continue
        if(statusCode == 100) {
            if(continueHandler != null) {
                continueHandler.handleContinue(new HttpContinueNotification.ContinueContext() {
                    @Override
                    public void done() {
                        // open request gate
                        request.openGate();
                        // Clear the parse state
                        parseState.state = ResponseParseState.VERSION;
                        parseState.stringBuilder.setLength(0);
                        parseState.pos = 0;
                        // Now go on and process the actual response
                        connection.doReadResponse(PendingHttpRequest.this);
                    }
                });
            } else {
                // open request gate
                request.openGate();
                // Clear the parse state
                parseState.state = ResponseParseState.VERSION;
                parseState.stringBuilder.setLength(0);
                parseState.pos = 0;
                // Now go on and process the actual response
                connection.doReadResponse(this);
            }
            return;
        }

        final HeaderMap headers = getResponseHeaders();
        final boolean http11 = Protocols.HTTP_1_1.equals(getProtocol());

        boolean closeConnection;
        String connectionHeader = headers.getFirst(Headers.CONNECTION);
        if(http11) {
            closeConnection = connectionHeader == null ? false : Headers.CLOSE.equals(new HttpString(connectionHeader));
        } else if (Protocols.HTTP_1_0.equals(getProtocol())) {
            closeConnection = connectionHeader == null ? true : ! Headers.KEEP_ALIVE.equals(new HttpString(connectionHeader));
        } else {
            closeConnection = true;
        }

        boolean noContent = false;
        final int responseCode = this.statusCode;
        if ((responseCode >= 100 && responseCode < 200)
                || responseCode == 204
                || responseCode == 304) {

            noContent = true;
        }
        if(! noContent && Methods.HEAD_STRING.equals(request.getMethod())) {
            noContent = true;
        }
        // Process the content length and transfer encodings
        StreamSourceConduit conduit = new StreamSourceChannelWrappingConduit(channel);
        long contentLength = -1;
        if(noContent) {
            conduit = new EmptyStreamSourceConduit(channel.getIoThread());
        } else {
            String transferEncoding = Headers.IDENTITY.toString();
            if (headers.contains(Headers.TRANSFER_ENCODING)) {
                transferEncoding = headers.getLast(Headers.TRANSFER_ENCODING);
            } else if (http11 && ! headers.contains(Headers.CONTENT_LENGTH)) {
                transferEncoding = Headers.CHUNKED.toString();
            }

            if (! transferEncoding.equals(Headers.IDENTITY.toString())) {
                conduit = new ChunkedStreamSourceConduit(conduit, channel, connection.getBufferPool(), getFinishListener(closeConnection), request);
            } else if (headers.contains(Headers.CONTENT_LENGTH)) {
                contentLength = Long.parseLong(headers.getFirst(Headers.CONTENT_LENGTH));
                if(contentLength == 0L) {
                    conduit = new EmptyStreamSourceConduit(channel.getIoThread());
                    noContent = true;
                } else {
                    conduit = new FixedLengthStreamSourceConduit(conduit, contentLength, getFinishListener(closeConnection));
                }
            } else {
                closeConnection = true;
            }
        }
        // Create the http response
        final StreamSourceChannel responseChannel = new ConduitStreamSourceChannel(channel, conduit);
        final HttpClientResponse response = new HttpClientResponse(this, request, contentLength, responseChannel);
        result.setResult(response);

        // If there is no content to read, complete the request right away
        if(noContent) {
            completed(closeConnection);
        }
    }

    /**
     * Whether to allow pipelining of the next request or not.
     *
     * @return
     */
    boolean allowPipeline() {
        return pipeline;
    }

    /**
     * The request finish listener, which will put the response into a completed state after the complete response
     * was consumed.
     *
     * @param closeConnection whether to close the connection or not
     * @return the completion listener
     */
    ConduitListener<StreamSourceConduit> getFinishListener(final boolean closeConnection) {
        return new ConduitListener<StreamSourceConduit>() {
            @Override
            public void handleEvent(StreamSourceConduit conduit) {
                completed(closeConnection);
            }
        };
    }

    private static long maxEntitySize(final OptionMap options) {
        return options.get(UndertowOptions.MAX_ENTITY_SIZE, UndertowOptions.DEFAULT_MAX_ENTITY_SIZE);
    }

}
TOP

Related Classes of io.undertow.client.PendingHttpRequest

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.