com.king.platform.net.http.netty.NettyHttpClient.java Source code

Java tutorial

Introduction

Here is the source code for com.king.platform.net.http.netty.NettyHttpClient.java

Source

// Copyright (C) king.com Ltd 2015
// https://github.com/king/king-http-client
// Author: Magnus Gustafsson
// License: Apache 2.0, https://raw.github.com/king/king-http-client/LICENSE-APACHE

package com.king.platform.net.http.netty;

import com.king.platform.net.http.*;
import com.king.platform.net.http.netty.backpressure.BackPressure;
import com.king.platform.net.http.netty.requestbuilder.HttpClientRequestBuilder;
import com.king.platform.net.http.netty.eventbus.*;
import com.king.platform.net.http.netty.metric.TimeStampRecorder;
import com.king.platform.net.http.netty.pool.ChannelPool;
import com.king.platform.net.http.netty.request.HttpClientRequestHandler;
import com.king.platform.net.http.netty.request.NettyHttpClientRequest;
import com.king.platform.net.http.netty.response.HttpClientResponseHandler;
import com.king.platform.net.http.netty.response.HttpRedirector;
import com.king.platform.net.http.netty.util.TimeProvider;
import io.netty.buffer.ByteBuf;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.Timer;
import org.slf4j.Logger;

import java.nio.ByteBuffer;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.slf4j.LoggerFactory.getLogger;

public class NettyHttpClient implements HttpClient {
    private final AtomicBoolean started = new AtomicBoolean();

    private final ConfMap confMap = new ConfMap();
    private final Executor httpClientCallbackExecutor;
    private final Executor httpClientExecuteExecutor;
    private final Timer cleanupTimer;
    private final TimeProvider timeProvider;

    private final Logger logger = getLogger(getClass());

    private final int nioThreads;
    private final ThreadFactory nioThreadFactory;
    private final RootEventBus rootEventBus;
    private final ChannelPool channelPool;

    private NioEventLoopGroup group;
    private ChannelManager channelManager;
    private BackPressure executionBackPressure;
    private Boolean executeOnCallingThread;

    public NettyHttpClient(int nioThreads, ThreadFactory nioThreadFactory, Executor httpClientCallbackExecutor,
            Executor httpClientExecuteExecutor, Timer cleanupTimer, TimeProvider timeProvider,
            final BackPressure executionBackPressure, RootEventBus rootEventBus, ChannelPool channelPool) {
        this.httpClientCallbackExecutor = httpClientCallbackExecutor;
        this.httpClientExecuteExecutor = httpClientExecuteExecutor;
        this.cleanupTimer = cleanupTimer;
        this.timeProvider = timeProvider;
        this.nioThreads = nioThreads;
        this.nioThreadFactory = nioThreadFactory;
        this.executionBackPressure = executionBackPressure;
        this.rootEventBus = rootEventBus;
        this.channelPool = channelPool;

        rootEventBus.subscribePermanently(Event.COMPLETED, new EventBusCallback1<HttpRequestContext>() {
            @Override
            public void onEvent(Event1<HttpRequestContext> event, HttpRequestContext httpRequestContext) {
                executionBackPressure.releaseSlot(httpRequestContext.getServerInfo());
            }
        });

        rootEventBus.subscribePermanently(Event.ERROR, new EventBusCallback2<HttpRequestContext, Throwable>() {
            @Override
            public void onEvent(Event2<HttpRequestContext, Throwable> event, HttpRequestContext httpRequestContext,
                    Throwable throwable) {
                executionBackPressure.releaseSlot(httpRequestContext.getServerInfo());
            }
        });

    }

    @Override
    public void start() {
        if (started.get()) {
            throw new IllegalStateException("Http client already started!");
        }

        started.set(true);

        executeOnCallingThread = confMap.get(ConfKeys.EXECUTE_ON_CALLING_THREAD);

        group = new NioEventLoopGroup(nioThreads, nioThreadFactory);

        HttpClientResponseHandler responseHandler = new HttpClientResponseHandler(new HttpRedirector());
        HttpClientRequestHandler requestHandler = new HttpClientRequestHandler();
        HttpClientHandler clientHandler = new HttpClientHandler(responseHandler, requestHandler);

        channelManager = new ChannelManager(group, clientHandler, cleanupTimer, timeProvider, channelPool, confMap,
                rootEventBus);
    }

    @Override
    public void shutdown() {
        started.set(false);

        if (group != null) {
            group.shutdownGracefully(0, 10, TimeUnit.SECONDS);
        }

    }

    @Override
    public <T> void setConf(ConfKeys<T> key, T value) {
        if (started.get()) {
            throw new RuntimeException("Can't set global config keys after the client has been started");
        }

        confMap.set(key, value);
    }

    public <T> Future<FutureResult<T>> execute(final NettyHttpClientRequest<T> nettyHttpClientRequest,
            final HttpCallback<T> httpCallback, final NioCallback nioCallback,
            ResponseBodyConsumer<T> responseBodyConsumer, int idleTimeoutMillis, int totalRequestTimeoutMillis,
            boolean followRedirects, boolean keepAlive) {

        if (!started.get()) {
            throw new IllegalStateException("Http client is not started!");
        }

        final RequestEventBus requestRequestEventBus = rootEventBus.createRequestEventBus();

        subscribeToHttpCallbackEvents(httpCallback, requestRequestEventBus);
        subscribeToNioCallbackEvents(nioCallback, requestRequestEventBus);

        if (httpCallback != null && responseBodyConsumer == null) {
            responseBodyConsumer = httpCallback.newResponseBodyConsumer();
        }

        if (responseBodyConsumer == null) {
            responseBodyConsumer = (ResponseBodyConsumer<T>) EMPTY_RESPONSE_BODY_CONSUMER;
        }

        final HttpRequestContext<T> httpRequestContext = new HttpRequestContext<>(nettyHttpClientRequest,
                requestRequestEventBus, responseBodyConsumer, idleTimeoutMillis, totalRequestTimeoutMillis,
                followRedirects, keepAlive, new TimeStampRecorder(timeProvider));

        ResponseFuture<T> future = new ResponseFuture<>(requestRequestEventBus, httpRequestContext);

        if (!executionBackPressure.acquireSlot(nettyHttpClientRequest.getServerInfo())) {
            requestRequestEventBus.triggerEvent(Event.ERROR, httpRequestContext,
                    new KingHttpException("Too many concurrent connections"));
            return future;
        }

        logger.trace("Executing httpRequest {}", httpRequestContext);

        if (executeOnCallingThread) {
            sendRequest(requestRequestEventBus, httpRequestContext);
        } else {
            httpClientExecuteExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    sendRequest(requestRequestEventBus, httpRequestContext);
                }
            });
        }

        return future;
    }

    private <T> void sendRequest(RequestEventBus requestRequestEventBus, HttpRequestContext<T> httpRequestContext) {
        try {
            channelManager.sendOnChannel(httpRequestContext, requestRequestEventBus);
        } catch (Throwable throwable) {
            requestRequestEventBus.triggerEvent(Event.ERROR, httpRequestContext, throwable);
        }
    }

    @Override
    public HttpClientRequest createGet(String uri) {
        if (!started.get()) {
            throw new IllegalStateException("Http client is not started!");
        }

        return new HttpClientRequestBuilder(this, HttpVersion.HTTP_1_1, HttpMethod.GET, uri, confMap);
    }

    @Override
    public HttpClientRequestWithBody createPost(String uri) {
        if (!started.get()) {
            throw new IllegalStateException("Http client is not started!");
        }

        return new HttpClientRequestBuilder(this, HttpVersion.HTTP_1_1, HttpMethod.POST, uri, confMap);
    }

    @Override
    public HttpClientRequestWithBody createPut(String uri) {
        if (!started.get()) {
            throw new IllegalStateException("Http client is not started!");
        }

        return new HttpClientRequestBuilder(this, HttpVersion.HTTP_1_1, HttpMethod.PUT, uri, confMap);
    }

    @Override
    public HttpClientRequest createDelete(String uri) {
        if (!started.get()) {
            throw new IllegalStateException("Http client is not started!");
        }

        return new HttpClientRequestBuilder(this, HttpVersion.HTTP_1_1, HttpMethod.DELETE, uri, confMap);
    }

    private <T> void subscribeToHttpCallbackEvents(final HttpCallback<T> httpCallback,
            RequestEventBus requestRequestEventBus) {
        if (httpCallback == null) {
            return;
        }

        requestRequestEventBus.subscribePermanently(Event.onHttpResponseDone, new RunOnceCallback1<HttpResponse>() {
            @Override
            public void onFirstEvent(Event1 event, final HttpResponse httpResponse) {
                httpClientCallbackExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        httpCallback.onCompleted(httpResponse);
                    }
                });
            }
        });

        requestRequestEventBus.subscribePermanently(Event.ERROR,
                new RunOnceCallback2<HttpRequestContext, Throwable>() {
                    @Override
                    public void onFirstEvent(Event2 event, HttpRequestContext httpRequestContext,
                            final Throwable throwable) {
                        httpClientCallbackExecutor.execute(new Runnable() {
                            @Override
                            public void run() {
                                httpCallback.onError(throwable);
                            }
                        });
                    }
                });

    }

    public <T> Future<FutureResult<T>> dispatchError(final HttpCallback<T> httpCallback,
            final Throwable throwable) {
        if (httpCallback != null) {
            httpClientCallbackExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    httpCallback.onError(throwable);
                }
            });
        }

        return ResponseFuture.error(throwable);

    }

    private void subscribeToNioCallbackEvents(final NioCallback nioCallback,
            RequestEventBus requestRequestEventBus) {
        if (nioCallback == null) {
            return;
        }

        requestRequestEventBus.subscribePermanently(Event.onConnecting, new EventBusCallback1<Void>() {
            @Override
            public void onEvent(Event1 event, Void payload) {
                nioCallback.onConnecting();
            }
        });

        requestRequestEventBus.subscribePermanently(Event.onConnected, new EventBusCallback1<Void>() {
            @Override
            public void onEvent(Event1 event, Void payload) {
                nioCallback.onConnected();
            }
        });

        requestRequestEventBus.subscribePermanently(Event.onWroteHeaders, new EventBusCallback1<Void>() {
            @Override
            public void onEvent(Event1 event, Void payload) {
                nioCallback.onWroteHeaders();
            }
        });

        requestRequestEventBus.subscribePermanently(Event.onWroteContentProgressed,
                new EventBusCallback2<Long, Long>() {
                    @Override
                    public void onEvent(Event2<Long, Long> event, Long progress, Long total) {
                        nioCallback.onWroteContentProgressed(progress, total);
                    }
                });

        requestRequestEventBus.subscribePermanently(Event.onWroteContentCompleted, new EventBusCallback1<Void>() {
            @Override
            public void onEvent(Event1 event, Void payload) {
                nioCallback.onWroteContentCompleted();
            }
        });

        requestRequestEventBus.subscribePermanently(Event.onReceivedStatus,
                new EventBusCallback1<HttpResponseStatus>() {
                    @Override
                    public void onEvent(Event1<HttpResponseStatus> event, HttpResponseStatus payload) {
                        nioCallback.onReceivedStatus(payload);
                    }
                });

        requestRequestEventBus.subscribePermanently(Event.onReceivedHeaders, new EventBusCallback1<HttpHeaders>() {
            @Override
            public void onEvent(Event1<HttpHeaders> event, HttpHeaders payload) {
                nioCallback.onReceivedHeaders(payload);
            }
        });

        requestRequestEventBus.subscribePermanently(Event.onReceivedContentPart,
                new EventBusCallback2<Integer, ByteBuf>() {
                    @Override
                    public void onEvent(Event2<Integer, ByteBuf> event, Integer length, ByteBuf contentPart) {
                        nioCallback.onReceivedContentPart(length, contentPart);
                    }
                });

        requestRequestEventBus.subscribePermanently(Event.onReceivedCompleted,
                new EventBusCallback2<HttpResponseStatus, HttpHeaders>() {
                    @Override
                    public void onEvent(Event2<HttpResponseStatus, HttpHeaders> event,
                            HttpResponseStatus httpResponseStatus, HttpHeaders httpHeaders) {
                        nioCallback.onReceivedCompleted(httpResponseStatus, httpHeaders);
                    }
                });

        requestRequestEventBus.subscribePermanently(Event.ERROR,
                new EventBusCallback2<HttpRequestContext, Throwable>() {
                    @Override
                    public void onEvent(Event2<HttpRequestContext, Throwable> event,
                            HttpRequestContext httpRequestContext, Throwable throwable) {
                        nioCallback.onError(throwable);
                    }
                });
    }

    private static final ResponseBodyConsumer<Void> EMPTY_RESPONSE_BODY_CONSUMER = new ResponseBodyConsumer<Void>() {
        @Override
        public void onBodyStart(String contentType, String charset, long contentLength) throws Exception {
        }

        @Override
        public void onReceivedContentPart(ByteBuffer buffer) throws Exception {
        }

        @Override
        public void onCompletedBody() throws Exception {
        }

        @Override
        public Void getBody() {
            return null;
        }
    };

}