com.linecorp.armeria.client.http.HttpClientDelegate.java Source code

Java tutorial

Introduction

Here is the source code for com.linecorp.armeria.client.http.HttpClientDelegate.java

Source

/*
 * Copyright 2016 LINE Corporation
 *
 * LINE Corporation 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.
 */
package com.linecorp.armeria.client.http;

import static java.util.Objects.requireNonNull;

import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;

import com.linecorp.armeria.client.Client;
import com.linecorp.armeria.client.ClientRequestContext;
import com.linecorp.armeria.client.Endpoint;
import com.linecorp.armeria.client.SessionOptions;
import com.linecorp.armeria.client.pool.DefaultKeyedChannelPool;
import com.linecorp.armeria.client.pool.KeyedChannelPool;
import com.linecorp.armeria.client.pool.KeyedChannelPoolHandler;
import com.linecorp.armeria.client.pool.KeyedChannelPoolHandlerAdapter;
import com.linecorp.armeria.client.pool.PoolKey;
import com.linecorp.armeria.common.ClosedSessionException;
import com.linecorp.armeria.common.SessionProtocol;
import com.linecorp.armeria.common.http.HttpHeaderNames;
import com.linecorp.armeria.common.http.HttpHeaders;
import com.linecorp.armeria.common.http.HttpRequest;
import com.linecorp.armeria.common.http.HttpResponse;
import com.linecorp.armeria.common.util.CompletionActions;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoop;
import io.netty.channel.pool.ChannelHealthChecker;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;

final class HttpClientDelegate implements Client<HttpRequest, HttpResponse> {

    private static final KeyedChannelPoolHandlerAdapter<PoolKey> NOOP_POOL_HANDLER = new KeyedChannelPoolHandlerAdapter<>();

    private static final ChannelHealthChecker POOL_HEALTH_CHECKER = ch -> ch.eventLoop()
            .newSucceededFuture(HttpSession.get(ch).isActive());

    final ConcurrentMap<EventLoop, KeyedChannelPool<PoolKey>> map = new ConcurrentHashMap<>();

    private final Bootstrap baseBootstrap;
    private final SessionOptions options;

    HttpClientDelegate(Bootstrap baseBootstrap, SessionOptions options) {
        this.baseBootstrap = requireNonNull(baseBootstrap, "baseBootstrap");
        this.options = requireNonNull(options, "options");

        assert baseBootstrap.config().group() == null;
    }

    @Override
    public HttpResponse execute(ClientRequestContext ctx, HttpRequest req) throws Exception {
        final Endpoint endpoint = ctx.endpoint().resolve().withDefaultPort(ctx.sessionProtocol().defaultPort());
        autoFillHeaders(ctx, endpoint, req);

        final PoolKey poolKey = new PoolKey(InetSocketAddress.createUnresolved(endpoint.host(), endpoint.port()),
                ctx.sessionProtocol());

        final EventLoop eventLoop = ctx.eventLoop();
        final Future<Channel> channelFuture = pool(eventLoop).acquire(poolKey);
        final DecodedHttpResponse res = new DecodedHttpResponse(eventLoop);

        if (channelFuture.isDone()) {
            if (channelFuture.isSuccess()) {
                Channel ch = channelFuture.getNow();
                invoke0(ch, ctx, req, res, poolKey);
            } else {
                res.close(channelFuture.cause());
            }
        } else {
            channelFuture.addListener((Future<Channel> future) -> {
                if (future.isSuccess()) {
                    Channel ch = future.getNow();
                    invoke0(ch, ctx, req, res, poolKey);
                } else {
                    res.close(channelFuture.cause());
                }
            });
        }

        return res;
    }

    private static void autoFillHeaders(ClientRequestContext ctx, Endpoint endpoint, HttpRequest req) {
        requireNonNull(req, "req");
        final HttpHeaders headers = req.headers();
        headers.set(HttpHeaderNames.USER_AGENT, HttpHeaderUtil.USER_AGENT.toString());

        if (headers.authority() == null) {
            final String hostname = endpoint.host();
            final int port = endpoint.port();

            final String authority;
            if (port == ctx.sessionProtocol().defaultPort()) {
                authority = hostname;
            } else {
                final StringBuilder buf = new StringBuilder(hostname.length() + 6);
                buf.append(hostname);
                buf.append(':');
                buf.append(port);
                authority = buf.toString();
            }

            headers.authority(authority);
        }

        // Add the handlers specified in ClientOptions.
        if (ctx.hasAttr(ClientRequestContext.HTTP_HEADERS)) {
            headers.setAll(ctx.attr(ClientRequestContext.HTTP_HEADERS).get());
        }
    }

    private KeyedChannelPool<PoolKey> pool(EventLoop eventLoop) {
        KeyedChannelPool<PoolKey> pool = map.get(eventLoop);
        if (pool != null) {
            return pool;
        }

        return map.computeIfAbsent(eventLoop, e -> {
            final Bootstrap bootstrap = baseBootstrap.clone();
            bootstrap.group(eventLoop);

            Function<PoolKey, Future<Channel>> factory = new HttpSessionChannelFactory(bootstrap, options);

            final KeyedChannelPoolHandler<PoolKey> handler = options.poolHandlerDecorator()
                    .apply(NOOP_POOL_HANDLER);

            //TODO(inch772) handle options.maxConcurrency();
            final KeyedChannelPool<PoolKey> newPool = new DefaultKeyedChannelPool<>(eventLoop, factory,
                    POOL_HEALTH_CHECKER, handler, true);

            eventLoop.terminationFuture().addListener((FutureListener<Object>) f -> {
                map.remove(eventLoop);
                newPool.close();
            });

            return newPool;
        });
    }

    void invoke0(Channel channel, ClientRequestContext ctx, HttpRequest req, DecodedHttpResponse res,
            PoolKey poolKey) {

        final HttpSession session = HttpSession.get(channel);
        res.init(session.inboundTrafficController());
        final SessionProtocol sessionProtocol = session.protocol();
        if (sessionProtocol == null) {
            res.close(ClosedSessionException.get());
            return;
        }

        if (session.invoke(ctx, req, res)) {
            // Return the channel to the pool.
            final KeyedChannelPool<PoolKey> pool = KeyedChannelPool.findPool(channel);
            if (sessionProtocol.isMultiplex()) {
                pool.release(poolKey, channel);
            } else {
                req.closeFuture().handle((ret, cause) -> pool.release(poolKey, channel))
                        .exceptionally(CompletionActions::log);
            }
        }
    }

    void close() {
        map.values().forEach(KeyedChannelPool::close);
    }
}