org.vertx.java.core.http.impl.DefaultHttpClientRequest.java Source code

Java tutorial

Introduction

Here is the source code for org.vertx.java.core.http.impl.DefaultHttpClientRequest.java

Source

/*
 * Copyright (c) 2011-2013 The original author or authors
 * ------------------------------------------------------
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 *     The Eclipse Public License is available at
 *     http://www.eclipse.org/legal/epl-v10.html
 *
 *     The Apache License v2.0 is available at
 *     http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 */

package org.vertx.java.core.http.impl;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.*;
import org.vertx.java.core.Handler;
import org.vertx.java.core.MultiMap;
import org.vertx.java.core.buffer.Buffer;
import org.vertx.java.core.http.HttpClientRequest;
import org.vertx.java.core.http.HttpClientResponse;
import org.vertx.java.core.impl.DefaultContext;

import java.util.concurrent.TimeoutException;

/**
 *
 * @author <a href="http://tfox.org">Tim Fox</a>
 */
public class DefaultHttpClientRequest implements HttpClientRequest {
    private final DefaultHttpClient client;
    private final HttpRequest request;
    private final Handler<HttpClientResponse> respHandler;
    private Handler<Void> continueHandler;
    private final DefaultContext context;
    private final boolean raw;
    private boolean chunked;
    private ClientConnection conn;
    private Handler<Void> drainHandler;
    private Handler<Throwable> exceptionHandler;
    private boolean headWritten;
    private boolean completed;
    private ByteBuf pendingChunks;
    private int pendingMaxSize = -1;
    private boolean connecting;
    private boolean writeHead;
    private long written;
    private long currentTimeoutTimerId = -1;
    private MultiMap headers;
    private boolean exceptionOccurred;
    private long lastDataReceived;

    DefaultHttpClientRequest(final DefaultHttpClient client, final String method, final String uri,
            final Handler<HttpClientResponse> respHandler, final DefaultContext context) {
        this(client, method, uri, respHandler, context, false);
    }

    private DefaultHttpClientRequest(final DefaultHttpClient client, final String method, final String uri,
            final Handler<HttpClientResponse> respHandler, final DefaultContext context, final boolean raw) {
        this.client = client;
        this.request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(method), uri, false);
        this.chunked = false;
        this.respHandler = respHandler;
        this.context = context;
        this.raw = raw;

    }

    @Override
    public DefaultHttpClientRequest setChunked(boolean chunked) {
        check();
        if (written > 0) {
            throw new IllegalStateException("Cannot set chunked after data has been written on request");
        }
        this.chunked = chunked;
        return this;
    }

    @Override
    public boolean isChunked() {
        return chunked;
    }

    @Override
    public MultiMap headers() {
        if (headers == null) {
            headers = new HttpHeadersAdapter(request.headers());
        }
        return headers;
    }

    @Override
    public HttpClientRequest putHeader(String name, String value) {
        check();
        headers().set(name, value);
        return this;
    }

    @Override
    public HttpClientRequest putHeader(String name, Iterable<String> values) {
        check();
        headers().set(name, values);
        return this;
    }

    @Override
    public DefaultHttpClientRequest write(Buffer chunk) {
        check();
        ByteBuf buf = chunk.getByteBuf();
        return write(buf, false);
    }

    @Override
    public DefaultHttpClientRequest write(String chunk) {
        check();
        return write(new Buffer(chunk));
    }

    @Override
    public DefaultHttpClientRequest write(String chunk, String enc) {
        check();
        return write(new Buffer(chunk, enc));
    }

    @Override
    public HttpClientRequest setWriteQueueMaxSize(int maxSize) {
        check();
        if (conn != null) {
            conn.doSetWriteQueueMaxSize(maxSize);
        } else {
            pendingMaxSize = maxSize;
        }
        return this;
    }

    @Override
    public boolean writeQueueFull() {
        check();
        if (conn != null) {
            return conn.doWriteQueueFull();
        } else {
            return false;
        }
    }

    @Override
    public HttpClientRequest drainHandler(Handler<Void> handler) {
        check();
        this.drainHandler = handler;
        if (conn != null) {
            conn.handleInterestedOpsChanged(); //If the channel is already drained, we want to call it immediately
        }
        return this;
    }

    @Override
    public HttpClientRequest exceptionHandler(final Handler<Throwable> handler) {
        check();
        this.exceptionHandler = new Handler<Throwable>() {
            @Override
            public void handle(Throwable event) {
                cancelOutstandingTimeoutTimer();
                handler.handle(event);
            }
        };
        return this;
    }

    @Override
    public HttpClientRequest continueHandler(Handler<Void> handler) {
        check();
        this.continueHandler = handler;
        return this;
    }

    @Override
    public DefaultHttpClientRequest sendHead() {
        check();
        if (conn != null) {
            if (!headWritten) {
                writeHead();
            }
        } else {
            connect();
            writeHead = true;
        }
        return this;
    }

    @Override
    public void end(String chunk) {
        end(new Buffer(chunk));
    }

    @Override
    public void end(String chunk, String enc) {
        end(new Buffer(chunk, enc));
    }

    @Override
    public void end(Buffer chunk) {
        check();
        if (!chunked && !contentLengthSet()) {
            headers().set(org.vertx.java.core.http.HttpHeaders.CONTENT_LENGTH, String.valueOf(chunk.length()));
        }
        write(chunk.getByteBuf(), true);
    }

    @Override
    public void end() {
        check();
        write(Unpooled.EMPTY_BUFFER, true);
    }

    @Override
    public HttpClientRequest setTimeout(final long timeoutMs) {
        cancelOutstandingTimeoutTimer();
        currentTimeoutTimerId = client.getVertx().setTimer(timeoutMs, new Handler<Long>() {
            @Override
            public void handle(Long event) {
                handleTimeout(timeoutMs);
            }
        });
        return this;
    }

    @Override
    public HttpClientRequest putHeader(CharSequence name, CharSequence value) {
        check();
        headers().set(name, value);
        return this;
    }

    @Override
    public HttpClientRequest putHeader(CharSequence name, Iterable<CharSequence> values) {
        check();
        headers().set(name, values);
        return this;
    }

    // Data has been received on the response
    void dataReceived() {
        if (currentTimeoutTimerId != -1) {
            lastDataReceived = System.currentTimeMillis();
        }
    }

    void handleDrained() {
        if (drainHandler != null) {
            drainHandler.handle(null);
        }
    }

    void handleException(Throwable t) {
        cancelOutstandingTimeoutTimer();
        exceptionOccurred = true;
        if (exceptionHandler != null) {
            exceptionHandler.handle(t);
        } else {
            context.reportException(t);
        }
    }

    void handleResponse(DefaultHttpClientResponse resp) {
        // If an exception occurred (e.g. a timeout fired) we won't receive the response.
        if (!exceptionOccurred) {
            cancelOutstandingTimeoutTimer();
            try {
                if (resp.statusCode() == 100) {
                    if (continueHandler != null) {
                        continueHandler.handle(null);
                    }
                } else {
                    respHandler.handle(resp);
                }
            } catch (Throwable t) {
                handleException(t);
            }
        }
    }

    private void cancelOutstandingTimeoutTimer() {
        if (currentTimeoutTimerId != -1) {
            client.getVertx().cancelTimer(currentTimeoutTimerId);
            currentTimeoutTimerId = -1;
        }
    }

    private void handleTimeout(long timeoutMs) {
        if (lastDataReceived == 0) {
            timeout(timeoutMs);
        } else {
            long now = System.currentTimeMillis();
            long timeSinceLastData = now - lastDataReceived;
            if (timeSinceLastData >= timeoutMs) {
                timeout(timeoutMs);
            } else {
                // reschedule
                lastDataReceived = 0;
                setTimeout(timeoutMs - timeSinceLastData);
            }
        }
    }

    private void timeout(long timeoutMs) {
        handleException(new TimeoutException("The timeout period of " + timeoutMs + "ms has been exceeded"));
    }

    private void connect() {
        if (!connecting) {
            //We defer actual connection until the first part of body is written or end is called
            //This gives the user an opportunity to set an exception handler before connecting so
            //they can capture any exceptions on connection
            client.getConnection(new Handler<ClientConnection>() {
                public void handle(ClientConnection conn) {
                    if (exceptionOccurred) {
                        // The request already timed out before it has left the pool waiter queue
                        // So return it
                        conn.close();
                    } else if (!conn.isClosed()) {
                        connected(conn);
                    } else {
                        // The connection has been closed - closed connections can be in the pool
                        // Get another connection - Note that we DO NOT call connectionClosed() on the pool at this point
                        // that is done asynchronously in the connection closeHandler()
                        connect();
                    }
                }
            }, exceptionHandler, context);

            connecting = true;
        }
    }

    private void connected(ClientConnection conn) {
        conn.setCurrentRequest(this);
        this.conn = conn;

        // If anything was written or the request ended before we got the connection, then
        // we need to write it now

        if (pendingMaxSize != -1) {
            conn.doSetWriteQueueMaxSize(pendingMaxSize);
        }

        if (pendingChunks != null) {
            ByteBuf pending = pendingChunks;
            pendingChunks = null;

            if (completed) {
                // we also need to write the head so optimize this and write all out in once
                writeHeadWithContent(pending, true);

                conn.endRequest();
            } else {
                writeHeadWithContent(pending, false);
            }
        } else {
            if (completed) {
                // we also need to write the head so optimize this and write all out in once
                writeHeadWithContent(Unpooled.EMPTY_BUFFER, true);
                conn.endRequest();
            } else {
                if (writeHead) {
                    writeHead();
                }
            }
        }
    }

    private boolean contentLengthSet() {
        if (headers != null) {
            return request.headers().contains(org.vertx.java.core.http.HttpHeaders.CONTENT_LENGTH);
        } else {
            return false;
        }
    }

    private void writeHead() {
        prepareHeaders();
        conn.write(request);
        headWritten = true;
    }

    private void writeHeadWithContent(ByteBuf buf, boolean end) {
        prepareHeaders();
        if (end) {
            conn.write(new AssembledFullHttpRequest(request, buf));
        } else {
            conn.write(new AssembledHttpRequest(request, buf));
        }
        headWritten = true;
    }

    private void prepareHeaders() {
        HttpHeaders headers = request.headers();
        headers.remove(org.vertx.java.core.http.HttpHeaders.TRANSFER_ENCODING);
        if (!raw) {
            if (!headers.contains(org.vertx.java.core.http.HttpHeaders.HOST)) {
                request.headers().set(org.vertx.java.core.http.HttpHeaders.HOST, conn.hostHeader);
            }
            if (chunked) {
                HttpHeaders.setTransferEncodingChunked(request);
            }
        }
        if (client.getTryUseCompression()
                && request.headers().get(org.vertx.java.core.http.HttpHeaders.ACCEPT_ENCODING) == null) {
            // if compression should be used but nothing is specified by the user support deflate and gzip.
            request.headers().set(org.vertx.java.core.http.HttpHeaders.ACCEPT_ENCODING,
                    org.vertx.java.core.http.HttpHeaders.DEFLATE_GZIP);

        }
    }

    private DefaultHttpClientRequest write(ByteBuf buff, boolean end) {
        int readableBytes = buff.readableBytes();
        if (readableBytes == 0 && !end) {
            // nothing to write to the connection just return
            return this;
        }

        if (end) {
            completed = true;
        }

        written += buff.readableBytes();

        if (!end && !raw && !chunked && !contentLengthSet()) {
            throw new IllegalStateException(
                    "You must set the Content-Length header to be the total size of the message "
                            + "body BEFORE sending any data if you are not using HTTP chunked encoding.");
        }

        if (conn == null) {
            if (pendingChunks == null) {
                pendingChunks = buff;
            } else {
                CompositeByteBuf pending;
                if (pendingChunks instanceof CompositeByteBuf) {
                    pending = (CompositeByteBuf) pendingChunks;
                } else {
                    pending = Unpooled.compositeBuffer();
                    pending.addComponent(pendingChunks).writerIndex(pendingChunks.writerIndex());
                    pendingChunks = pending;
                }
                pending.addComponent(buff).writerIndex(pending.writerIndex() + buff.writerIndex());
            }
            connect();
        } else {
            if (!headWritten) {
                writeHeadWithContent(buff, end);
            } else {
                if (end) {
                    writeEndChunk(buff);
                } else {
                    sendChunk(buff);
                }
            }
            if (end) {
                conn.endRequest();
            }
        }
        return this;
    }

    private void sendChunk(ByteBuf buff) {
        conn.write(new DefaultHttpContent(buff));
    }

    private void writeEndChunk(ByteBuf buf) {
        if (buf.isReadable()) {
            conn.write(new DefaultLastHttpContent(buf, false));
        } else {
            conn.write(LastHttpContent.EMPTY_LAST_CONTENT);
        }
    }

    private void check() {
        checkComplete();
    }

    private void checkComplete() {
        if (completed) {
            throw new IllegalStateException("Request already complete");
        }
    }

}