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

io.reactivex.netty.protocol.http.client.internal.HttpClientResponseImpl Maven / Gradle / Ivy

There is a newer version: 0.5.3-rc.2
Show newest version
/*
 * Copyright 2016 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.internal;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseDecoder;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.util.ReferenceCountUtil;
import io.reactivex.netty.channel.Connection;
import io.reactivex.netty.channel.ContentSource;
import io.reactivex.netty.protocol.http.CookiesHolder;
import io.reactivex.netty.protocol.http.HttpHandlerNames;
import io.reactivex.netty.protocol.http.client.HttpClientResponse;
import io.reactivex.netty.protocol.http.internal.HttpContentSubscriberEvent;
import io.reactivex.netty.protocol.http.sse.ServerSentEvent;
import io.reactivex.netty.protocol.http.sse.client.ServerSentEventDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Observable.Transformer;
import rx.Subscriber;
import rx.functions.Func1;

import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;

import static io.netty.handler.codec.http.HttpHeaderNames.*;

public final class HttpClientResponseImpl extends HttpClientResponse {

    private static final Logger logger = LoggerFactory.getLogger(HttpClientResponseImpl.class);

    public static final String KEEP_ALIVE_HEADER_NAME = "Keep-Alive";
    private static final Pattern PATTERN_COMMA = Pattern.compile(",");
    private static final Pattern PATTERN_EQUALS = Pattern.compile("=");
    public static final String KEEP_ALIVE_TIMEOUT_HEADER_ATTR = "timeout";

    private final HttpResponse nettyResponse;
    private final Connection connection;
    private final CookiesHolder cookiesHolder;
    private final ContentSource contentSource;

    private HttpClientResponseImpl(HttpResponse nettyResponse) {
        this(nettyResponse, UnusableConnection.create());
    }

    private HttpClientResponseImpl(HttpResponse nettyResponse, Connection connection) {
        this.nettyResponse = nettyResponse;
        this.connection = connection;
        cookiesHolder = CookiesHolder.newClientResponseHolder(nettyResponse.headers());
        contentSource = new ContentSource<>(unsafeNettyChannel(), new ContentSourceSubscriptionFactory());
    }

    private HttpClientResponseImpl(HttpClientResponseImpl toCopy, ContentSource newSource) {
        nettyResponse = toCopy.nettyResponse;
        connection = toCopy.connection;
        cookiesHolder = toCopy.cookiesHolder;
        contentSource = newSource;
    }

    @Override
    public HttpVersion getHttpVersion() {
        return nettyResponse.protocolVersion();
    }

    @Override
    public HttpResponseStatus getStatus() {
        return nettyResponse.status();
    }

    @Override
    public Map> getCookies() {
        return cookiesHolder.getAllCookies();
    }

    @Override
    public boolean containsHeader(CharSequence name) {
        return nettyResponse.headers().contains(name);
    }

    @Override
    public boolean containsHeader(CharSequence name, CharSequence value, boolean ignoreCaseValue) {
        return nettyResponse.headers().contains(name, value, ignoreCaseValue);
    }

    @Override
    public Iterator> headerIterator() {
        return nettyResponse.headers().iteratorCharSequence();
    }

    @Override
    public String getHeader(CharSequence name) {
        return nettyResponse.headers().get(name);
    }

    @Override
    public String getHeader(CharSequence name, String defaultValue) {
        return nettyResponse.headers().get(name, defaultValue);
    }

    @Override
    public List getAllHeaderValues(CharSequence name) {
        return nettyResponse.headers().getAll(name);
    }

    @Override
    public long getContentLength() {
        return HttpUtil.getContentLength(nettyResponse);
    }

    @Override
    public long getContentLength(long defaultValue) {
        return HttpUtil.getContentLength(nettyResponse, defaultValue);
    }

    @Override
    public long getDateHeader(CharSequence name) {
        return nettyResponse.headers().getTimeMillis(name);
    }

    @Override
    public long getDateHeader(CharSequence name, long defaultValue) {
        return nettyResponse.headers().getTimeMillis(name, defaultValue);
    }

    @Override
    public String getHostHeader() {
        return nettyResponse.headers().get(HOST);
    }

    @Override
    public String getHost(String defaultValue) {
        return nettyResponse.headers().get(HOST, defaultValue);
    }

    @Override
    public int getIntHeader(CharSequence name) {
        return nettyResponse.headers().getInt(name);
    }

    @Override
    public int getIntHeader(CharSequence name, int defaultValue) {
        return nettyResponse.headers().getInt(name, defaultValue);
    }

    @Override
    public boolean isContentLengthSet() {
        return HttpUtil.isContentLengthSet(nettyResponse);
    }

    @Override
    public boolean isKeepAlive() {
        return HttpUtil.isKeepAlive(nettyResponse);
    }

    @Override
    public boolean isTransferEncodingChunked() {
        return HttpUtil.isTransferEncodingChunked(nettyResponse);
    }

    @Override
    public Set getHeaderNames() {
        return nettyResponse.headers().names();
    }

    @Override
    public HttpClientResponse addHeader(CharSequence name, Object value) {
        nettyResponse.headers().add(name, value);
        return this;
    }

    @Override
    public HttpClientResponse addCookie(Cookie cookie) {
        nettyResponse.headers().add(SET_COOKIE, ClientCookieEncoder.STRICT.encode(cookie));
        return this;
    }

    @Override
    public HttpClientResponse addDateHeader(CharSequence name, Date value) {
        nettyResponse.headers().set(name, value);
        return this;
    }

    @Override
    public HttpClientResponse addDateHeader(CharSequence name, Iterable values) {
        for (Date value : values) {
            nettyResponse.headers().add(name, value);
        }
        return this;
    }

    @Override
    public HttpClientResponse addHeader(CharSequence name, Iterable values) {
        nettyResponse.headers().add(name, values);
        return this;
    }

    @Override
    public HttpClientResponse setDateHeader(CharSequence name, Date value) {
        nettyResponse.headers().set(name, value);
        return this;
    }

    @Override
    public HttpClientResponse setHeader(CharSequence name, Object value) {
        nettyResponse.headers().set(name, value);
        return this;
    }

    @Override
    public HttpClientResponse setDateHeader(CharSequence name, Iterable values) {
        for (Date value : values) {
            nettyResponse.headers().set(name, value);
        }
        return this;
    }

    @Override
    public HttpClientResponse setHeader(CharSequence name, Iterable values) {
        nettyResponse.headers().set(name, values);
        return this;
    }

    @Override
    public HttpClientResponse removeHeader(CharSequence name) {
        nettyResponse.headers().remove(name);
        return this;
    }

    @Override
    public ContentSource getContentAsServerSentEvents() {
        if (containsHeader(CONTENT_TYPE, "text/event-stream", false)) {
            ChannelPipeline pipeline = unsafeNettyChannel().pipeline();
            ChannelHandlerContext decoderCtx = pipeline.context(HttpHandlerNames.HttpClientCodec.getName());
            if (null != decoderCtx) {
                pipeline.addAfter(decoderCtx.name(), HttpHandlerNames.SseClientCodec.getName(),
                                  new ServerSentEventDecoder());
            }
            return new ContentSource<>(unsafeNettyChannel(), new ContentSourceSubscriptionFactory());
        }

        return new ContentSource<>(new IllegalStateException("Response is not a server sent event response."));
    }

    @Override
    public ContentSource getContent() {
        return contentSource;
    }

    @Override
    public Observable discardContent() {
        return getContent().map(new Func1() {
            @Override
            public Void call(T t) {
                ReferenceCountUtil.release(t);
                return null;
            }
        }).ignoreElements();
    }

    @Override
    public  HttpClientResponse transformContent(Transformer transformer) {
        return new HttpClientResponseImpl<>(this, contentSource.transform(transformer));
    }

    @Override
    public Channel unsafeNettyChannel() {
        return unsafeConnection().unsafeNettyChannel();
    }

    @Override
    public Connection unsafeConnection() {
        return connection;
    }

    /**
     * Parses the timeout value from the HTTP keep alive header (with name {@link #KEEP_ALIVE_HEADER_NAME}) as described in
     * this spec
     *
     * @return The keep alive timeout or {@code null} if this response does not define the appropriate header value.
     */
    public Long getKeepAliveTimeoutSeconds() {
        String keepAliveHeader = nettyResponse.headers().get(KEEP_ALIVE_HEADER_NAME);
        if (null != keepAliveHeader && !keepAliveHeader.isEmpty()) {
            String[] pairs = PATTERN_COMMA.split(keepAliveHeader);
            if (pairs != null) {
                for (String pair: pairs) {
                    String[] nameValue = PATTERN_EQUALS.split(pair.trim());
                    if (nameValue != null && nameValue.length >= 2) {
                        String name = nameValue[0].trim().toLowerCase();
                        String value = nameValue[1].trim();
                        if (KEEP_ALIVE_TIMEOUT_HEADER_ATTR.equals(name)) {
                            try {
                                return Long.valueOf(value);
                            } catch (NumberFormatException e) {
                                logger.info("Invalid HTTP keep alive timeout value. Keep alive header: "
                                            + keepAliveHeader + ", timeout attribute value: " + nameValue[1], e);
                                return null;
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    /*Visible for the client bridge*/static  HttpClientResponseImpl unsafeCreate(HttpResponse nettyResponse) {
        return new HttpClientResponseImpl<>(nettyResponse);
    }

    public static  HttpClientResponse newInstance(HttpClientResponse unsafeInstance,
                                                        Connection connection) {
        HttpClientResponseImpl cast = (HttpClientResponseImpl) unsafeInstance;
        return new HttpClientResponseImpl<>(cast.nettyResponse, connection);
    }

    public static  HttpClientResponse newInstance(HttpResponse nettyResponse, Connection connection) {
        return new HttpClientResponseImpl<>(nettyResponse, connection);
    }

    private static class ContentSourceSubscriptionFactory implements Func1, Object> {
        @Override
        public Object call(Subscriber subscriber) {
            return new HttpContentSubscriberEvent<>(subscriber);
        }
    }
}