io.vertx.core.http.impl.Http2ClientConnection.java Source code

Java tutorial

Introduction

Here is the source code for io.vertx.core.http.impl.Http2ClientConnection.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 io.vertx.core.http.impl;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http2.DefaultHttp2Headers;
import io.netty.handler.codec.http2.Http2Connection;
import io.netty.handler.codec.http2.Http2Error;
import io.netty.handler.codec.http2.Http2Exception;
import io.netty.handler.codec.http2.Http2Headers;
import io.netty.handler.codec.http2.Http2Stream;
import io.vertx.core.Context;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.VertxException;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.CaseInsensitiveHeaders;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.http.StreamResetException;
import io.vertx.core.impl.ContextImpl;
import io.vertx.core.net.NetSocket;
import io.vertx.core.spi.metrics.HttpClientMetrics;
import io.vertx.core.spi.metrics.NetworkMetrics;

import java.util.Map;

import static io.vertx.core.http.HttpHeaders.DEFLATE_GZIP;

/**
 * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
 */
class Http2ClientConnection extends Http2ConnectionBase implements HttpClientConnection {

    final Http2Pool http2Pool;
    final HttpClientMetrics metrics;
    final Object queueMetric;
    int streamCount;

    public Http2ClientConnection(Http2Pool http2Pool, Object queueMetric, ContextImpl context, Channel channel,
            VertxHttp2ConnectionHandler connHandler, HttpClientMetrics metrics) {
        super(channel, context, connHandler);
        this.http2Pool = http2Pool;
        this.metrics = metrics;
        this.queueMetric = queueMetric;
    }

    @Override
    public HttpClientMetrics metrics() {
        return metrics;
    }

    @Override
    void onGoAwaySent(int lastStreamId, long errorCode, ByteBuf debugData) {
        http2Pool.discard(Http2ClientConnection.this);
    }

    @Override
    void onGoAwayReceived(int lastStreamId, long errorCode, ByteBuf debugData) {
        super.onGoAwayReceived(lastStreamId, errorCode, debugData);
        http2Pool.discard(Http2ClientConnection.this);
    }

    @Override
    void onStreamClosed(Http2Stream nettyStream) {
        super.onStreamClosed(nettyStream);
        http2Pool.recycle(Http2ClientConnection.this);
    }

    synchronized HttpClientStream createStream() throws Http2Exception {
        Http2Connection conn = handler.connection();
        Http2Stream stream = conn.local().createStream(conn.local().incrementAndGetNextStreamId(), false);
        boolean writable = handler.encoder().flowController().isWritable(stream);
        Http2ClientStream clientStream = new Http2ClientStream(this, stream, writable);
        streams.put(clientStream.stream.id(), clientStream);
        return clientStream;
    }

    @Override
    public synchronized void handleClosed() {
        http2Pool.discard(this);
        super.handleClosed();
    }

    @Override
    public boolean isValid() {
        Http2Connection conn = handler.connection();
        return !isClosed() && !conn.goAwaySent() && !conn.goAwayReceived();
    }

    @Override
    public synchronized void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
            int padding, boolean endOfStream) throws Http2Exception {
        Http2ClientStream stream = (Http2ClientStream) streams.get(streamId);
        if (stream != null) {
            context.executeFromIO(() -> {
                stream.handleHeaders(headers, endOfStream);
            });
        }
    }

    @Override
    public synchronized void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
            Http2Headers headers, int padding) throws Http2Exception {
        Http2ClientStream stream = (Http2ClientStream) streams.get(streamId);
        if (stream != null) {
            Handler<HttpClientRequest> pushHandler = stream.pushHandler();
            if (pushHandler != null) {
                context.executeFromIO(() -> {
                    String rawMethod = headers.method().toString();
                    HttpMethod method = HttpUtils.toVertxMethod(rawMethod);
                    String uri = headers.path().toString();
                    String host = headers.authority() != null ? headers.authority().toString() : null;
                    MultiMap headersMap = new Http2HeadersAdaptor(headers);
                    Http2Stream promisedStream = handler.connection().stream(promisedStreamId);
                    int port = remoteAddress().port();
                    HttpClientRequestPushPromise pushReq = new HttpClientRequestPushPromise(this, promisedStream,
                            http2Pool.client, isSsl(), method, rawMethod, uri, host, port, headersMap);
                    if (metrics.isEnabled()) {
                        pushReq.metric(metrics.responsePushed(queueMetric, metric(), localAddress(),
                                remoteAddress(), pushReq));
                    }
                    streams.put(promisedStreamId, pushReq.getStream());
                    pushHandler.handle(pushReq);
                });
                return;
            }
        }
        handler.writeReset(promisedStreamId, Http2Error.CANCEL.code());
    }

    static class Http2ClientStream extends VertxHttp2Stream<Http2ClientConnection> implements HttpClientStream {

        private HttpClientRequestBase request;
        private HttpClientResponseImpl response;
        private boolean requestEnded;
        private boolean responseEnded;

        public Http2ClientStream(Http2ClientConnection conn, Http2Stream stream, boolean writable)
                throws Http2Exception {
            this(conn, null, stream, writable);
        }

        public Http2ClientStream(Http2ClientConnection conn, HttpClientRequestBase request, Http2Stream stream,
                boolean writable) throws Http2Exception {
            super(conn, stream, writable);
            this.request = request;
        }

        @Override
        public HttpVersion version() {
            return HttpVersion.HTTP_2;
        }

        @Override
        public int id() {
            return super.id();
        }

        @Override
        void handleEnd(MultiMap trailers) {
            if (conn.metrics.isEnabled()) {
                if (request.exceptionOccurred != null) {
                    conn.metrics.requestReset(request.metric());
                } else {
                    conn.metrics.responseEnd(request.metric(), response);
                }
            }
            responseEnded = true;
            // Should use a shared immutable object for CaseInsensitiveHeaders ?
            if (trailers == null) {
                trailers = new CaseInsensitiveHeaders();
            }
            response.handleEnd(null, trailers);
        }

        @Override
        void handleData(Buffer buf) {
            response.handleChunk(buf);
        }

        @Override
        void handleReset(long errorCode) {
            if (responseEnded) {
                return;
            }
            responseEnded = true;
            if (conn.metrics.isEnabled()) {
                conn.metrics.requestReset(request.metric());
            }
            handleException(new StreamResetException(errorCode));
        }

        @Override
        void handleClose() {
            if (!responseEnded) {
                responseEnded = true;
                if (conn.metrics.isEnabled()) {
                    conn.metrics.requestReset(request.metric());
                }
                handleException(new VertxException("Connection was closed")); // Put that in utility class
            }
        }

        @Override
        public void checkDrained() {
            synchronized (conn) {
                handleInterestedOpsChanged();
            }
        }

        @Override
        void handleInterestedOpsChanged() {
            if (request instanceof HttpClientRequestImpl && !isNotWritable()) {
                if (!isNotWritable()) {
                    ((HttpClientRequestImpl) request).handleDrained();
                }
            }
        }

        @Override
        void handleCustomFrame(int type, int flags, Buffer buff) {
            response.handleUnknowFrame(new HttpFrameImpl(type, flags, buff));
        }

        void handleHeaders(Http2Headers headers, boolean end) {
            if (response == null || response.statusCode() == 100) {
                int status;
                String statusMessage;
                try {
                    status = Integer.parseInt(headers.status().toString());
                    statusMessage = HttpResponseStatus.valueOf(status).reasonPhrase();
                } catch (Exception e) {
                    handleException(e);
                    writeReset(0x01 /* PROTOCOL_ERROR */);
                    return;
                }
                response = new HttpClientResponseImpl(request, HttpVersion.HTTP_2, this, status, statusMessage,
                        new Http2HeadersAdaptor(headers));
                if (conn.metrics.isEnabled()) {
                    conn.metrics.responseBegin(request.metric(), response);
                }
                request.handleResponse(response);
                if (end) {
                    onEnd();
                }
            } else if (end) {
                onEnd(new Http2HeadersAdaptor(headers));
            }
        }

        void handleException(Throwable exception) {
            if (!requestEnded || response == null) {
                context.executeFromIO(() -> {
                    request.handleException(exception);
                });
            }
            if (response != null) {
                context.executeFromIO(() -> {
                    response.handleException(exception);
                });
            }
        }

        Handler<HttpClientRequest> pushHandler() {
            return ((HttpClientRequestImpl) request).pushHandler();
        }

        @Override
        public void writeHead(HttpMethod method, String rawMethod, String uri, MultiMap headers, String hostHeader,
                boolean chunked) {
            writeHeadWithContent(method, rawMethod, uri, headers, hostHeader, chunked, null, false);
        }

        @Override
        public void writeHeadWithContent(HttpMethod method, String rawMethod, String uri, MultiMap headers,
                String hostHeader, boolean chunked, ByteBuf content, boolean end) {
            Http2Headers h = new DefaultHttp2Headers();
            h.method(method != HttpMethod.OTHER ? method.name() : rawMethod);
            if (method == HttpMethod.CONNECT) {
                if (hostHeader == null) {
                    throw new IllegalArgumentException("Missing :authority / host header");
                }
                h.authority(hostHeader);
            } else {
                h.path(uri);
                h.scheme("https");
                if (hostHeader != null) {
                    h.authority(hostHeader);
                }
            }
            if (headers != null && headers.size() > 0) {
                for (Map.Entry<String, String> header : headers) {
                    h.add(Http2HeadersAdaptor.toLowerCase(header.getKey()), header.getValue());
                }
            }
            if (conn.http2Pool.client.getOptions().isTryUseCompression()
                    && h.get(HttpHeaderNames.ACCEPT_ENCODING) == null) {
                h.set(HttpHeaderNames.ACCEPT_ENCODING, DEFLATE_GZIP);
            }
            if (conn.metrics.isEnabled()) {
                request.metric(conn.metrics.requestBegin(conn.queueMetric, conn.metric(), conn.localAddress(),
                        conn.remoteAddress(), request));
            }
            writeHeaders(h, end && content == null);
            if (content != null) {
                writeBuffer(content, end);
            } else {
                handlerContext.flush();
            }
        }

        @Override
        public void writeBuffer(ByteBuf buf, boolean end) {
            if (buf == null && end) {
                buf = Unpooled.EMPTY_BUFFER;
            }
            if (buf != null) {
                writeData(buf, end);
            }
            if (end) {
                handlerContext.flush();
            }
        }

        @Override
        public void writeFrame(int type, int flags, ByteBuf payload) {
            super.writeFrame(type, flags, payload);
        }

        @Override
        public Context getContext() {
            return context;
        }

        @Override
        public void doSetWriteQueueMaxSize(int size) {
        }

        @Override
        public boolean isNotWritable() {
            return super.isNotWritable();
        }

        @Override
        public void beginRequest(HttpClientRequestImpl request) {
            this.request = request;
        }

        @Override
        public void endRequest() {
            if (conn.metrics.isEnabled()) {
                conn.metrics.requestEnd(request.metric());
            }
            requestEnded = true;
        }

        @Override
        public void resetRequest(long code) {
            if (!(requestEnded && responseEnded)) {
                requestEnded = true;
                responseEnded = true;
                writeReset(code);
                if (conn.metrics.isEnabled()) {
                    conn.metrics.requestReset(request.metric());
                }
            }
        }

        @Override
        public void resetResponse(long code) {
            resetRequest(code);
        }

        @Override
        public HttpClientConnection connection() {
            return conn;
        }

        @Override
        public NetSocket createNetSocket() {
            return conn.toNetSocket(this);
        }
    }
}