All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.reactivex.netty.protocol.http.client.HttpClientImpl Maven / Gradle / Ivy

There is a newer version: 0.5.1
Show newest version
/*
 * Copyright 2014 Netflix, Inc.
 *
 * Licensed 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 io.reactivex.netty.protocol.http.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.reactivex.netty.channel.ObservableConnection;
import io.reactivex.netty.client.ClientChannelFactory;
import io.reactivex.netty.client.ClientConnectionFactory;
import io.reactivex.netty.client.ClientMetricsEvent;
import io.reactivex.netty.client.ConnectionPool;
import io.reactivex.netty.client.ConnectionPoolBuilder;
import io.reactivex.netty.client.RxClientImpl;
import io.reactivex.netty.metrics.Clock;
import io.reactivex.netty.metrics.MetricEventsSubject;
import io.reactivex.netty.pipeline.PipelineConfigurator;
import io.reactivex.netty.pipeline.PipelineConfiguratorComposite;
import rx.Observable;
import rx.functions.Action0;

public class HttpClientImpl extends RxClientImpl, HttpClientResponse> implements HttpClient {

    private final String hostHeaderValue;

    public HttpClientImpl(String name, ServerInfo serverInfo, Bootstrap clientBootstrap,
                          PipelineConfigurator, HttpClientRequest> pipelineConfigurator,
                          ClientConfig clientConfig,
                          ClientChannelFactory, HttpClientRequest> channelFactory,
                          ClientConnectionFactory, HttpClientRequest,
                                  ? extends ObservableConnection, HttpClientRequest>> connectionFactory,
                          MetricEventsSubject> eventsSubject) {
        super(name, serverInfo, clientBootstrap, pipelineConfigurator, clientConfig, channelFactory, connectionFactory,
              eventsSubject);
        hostHeaderValue = prepareHostHeaderValue();
    }

    public HttpClientImpl(String name, ServerInfo serverInfo, Bootstrap clientBootstrap,
                          PipelineConfigurator, HttpClientRequest> pipelineConfigurator,
                          ClientConfig clientConfig,
                          ConnectionPoolBuilder, HttpClientRequest> poolBuilder,
                          MetricEventsSubject> eventsSubject) {
        super(name, serverInfo, clientBootstrap, pipelineConfigurator, clientConfig, poolBuilder, eventsSubject);
        hostHeaderValue = prepareHostHeaderValue();
    }

    @Override
    public Observable> submit(HttpClientRequest request) {
        return submit(request, connect());
    }

    @Override
    public Observable> submit(HttpClientRequest request, ClientConfig config) {
        return submit(request, connect(), config);
    }

    protected Observable> submit(final HttpClientRequest request,
                                                       Observable, HttpClientRequest>> connectionObservable) {
        return submit(request, connectionObservable, null == clientConfig
                                                     ? HttpClientConfig.Builder.newDefaultConfig() : clientConfig);
    }

    protected Observable> submit(final HttpClientRequest request,
                                                       final Observable, HttpClientRequest>> connectionObservable,
                                                       final ClientConfig config) {
        final long startTimeMillis = Clock.newStartTimeMillis();
        HttpClientConfig httpClientConfig;
        if (config instanceof HttpClientConfig) {
            httpClientConfig = (HttpClientConfig) config;
        } else {
            httpClientConfig = new HttpClientConfig(config);
        }
        boolean followRedirect = shouldFollowRedirectForRequest(httpClientConfig, request);

        enrichRequest(request, httpClientConfig);
        Observable> toReturn =
                connectionObservable.lift(new RequestProcessingOperator(request, eventsSubject,
                                                                              httpClientConfig.getResponseSubscriptionTimeoutMs()));

        if (followRedirect) {
            toReturn = toReturn.lift(new RedirectOperator(request, this, httpClientConfig));
        }
        return toReturn.take(1).finallyDo(new Action0() {
            @Override
            public void call() {
                eventsSubject.onEvent(HttpClientMetricsEvent.REQUEST_PROCESSING_COMPLETE,
                                      Clock.onEndMillis(startTimeMillis));
            }
        });
    }

    @Override
    protected PipelineConfigurator, HttpClientRequest> adaptPipelineConfigurator(
            PipelineConfigurator, HttpClientRequest> pipelineConfigurator,
            ClientConfig clientConfig, MetricEventsSubject> eventsSubject) {
        PipelineConfigurator, HttpClientRequest> configurator =
                new PipelineConfiguratorComposite, HttpClientRequest>(pipelineConfigurator,
                                                     new ClientRequiredConfigurator(eventsSubject));
        return super.adaptPipelineConfigurator(configurator, clientConfig, eventsSubject);
    }

    protected boolean shouldFollowRedirectForRequest(HttpClientConfig config, HttpClientRequest request) {
        HttpClientConfig.RedirectsHandling redirectsHandling = config.getFollowRedirect();

        switch (redirectsHandling) {
            case Enable:
                return true;
            case Disable:
                return false;
            case Undefined:
                return request.getMethod() == HttpMethod.HEAD || request.getMethod() == HttpMethod.GET;
            default:
                return false;
        }
    }

    /*visible for testing*/ ConnectionPool, HttpClientRequest> getConnectionPool() {
        return pool;
    }

    private void enrichRequest(HttpClientRequest request, ClientConfig config) {

        request.setDynamicUriParts(serverInfo.getHost(), serverInfo.getPort(), false /*Set when we handle https*/);

        if(!request.getHeaders().contains(HttpHeaders.Names.HOST)) {
            request.getHeaders().add(HttpHeaders.Names.HOST, hostHeaderValue);
        }

        if (config instanceof HttpClientConfig) {
            HttpClientConfig httpClientConfig = (HttpClientConfig) config;
            if (httpClientConfig.getUserAgent() != null && request.getHeaders().get(HttpHeaders.Names.USER_AGENT) == null) {
                request.getHeaders().set(HttpHeaders.Names.USER_AGENT, httpClientConfig.getUserAgent());
            }
        }
    }

    private String prepareHostHeaderValue() {
        if (serverInfo.getPort() == 80 || serverInfo.getPort() == 443) {
            return serverInfo.getHost();
        }
        // Add port to the host header if the port is not standard port. Issue: https://github.com/ReactiveX/RxNetty/issues/258
        return serverInfo.getHost() + ':' + serverInfo.getPort();
    }
}