/* * 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.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.cookie.Cookie; import io.reactivex.netty.channel.AllocatingTransformer; import io.reactivex.netty.channel.AppendTransformerEvent; import io.reactivex.netty.channel.Connection; import io.reactivex.netty.events.Clock; import io.reactivex.netty.events.EventAttributeKeys; import io.reactivex.netty.events.EventPublisher; import io.reactivex.netty.internal.VoidToAnythingCast; import io.reactivex.netty.protocol.http.TrailingHeaders; import io.reactivex.netty.protocol.http.client.HttpClientRequest; import io.reactivex.netty.protocol.http.client.HttpClientResponse; import io.reactivex.netty.protocol.http.client.events.HttpClientEventsListener; import io.reactivex.netty.protocol.http.internal.OperatorTrailer; import io.reactivex.netty.protocol.http.ws.client.internal.WebSocketRequestImpl; import io.reactivex.netty.protocol.tcp.client.TcpClient; import rx.Observable; import rx.Subscriber; import rx.functions.Func0; import rx.functions.Func1; import rx.functions.Func2; import java.util.ArrayList; import java.util.Collections; 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.concurrent.TimeUnit; import static java.util.concurrent.TimeUnit.*; public final class HttpClientRequestImpl<I, O> extends HttpClientRequest<I, O> { public static final int NO_REDIRECTS = -1; private final List<AppendTransformerEvent> immutableTransformers; private final List<Transformer> immutableResponseTransformers; private final RawRequest<I, O> rawRequest; private final TcpClient<?, HttpClientResponse<O>> client; private final Func1<I, Boolean> flushOnEachSelector = new Func1<I, Boolean>() { @Override public Boolean call(I next) { return true; } }; private HttpClientRequestImpl(final RawRequest<I, O> rawRequest, final TcpClient<?, HttpClientResponse<O>> client, List<AppendTransformerEvent> immutableTransformers, List<Transformer> immutableResponseTransformers) { super(new OnSubscribeFuncImpl<>(client, rawRequest, immutableResponseTransformers, immutableTransformers)); this.rawRequest = rawRequest; this.client = client; this.immutableTransformers = immutableTransformers; this.immutableResponseTransformers = immutableResponseTransformers; } @Override public Observable<HttpClientResponse<O>> writeContent(Observable<I> contentSource) { @SuppressWarnings("rawtypes") Observable rawObservable = contentSource; return _writeContentRaw(rawObservable, false); } @Override public Observable<HttpClientResponse<O>> writeContentAndFlushOnEach(Observable<I> contentSource) { return writeContent(contentSource, flushOnEachSelector); } @Override public Observable<HttpClientResponse<O>> writeStringContent(Observable<String> contentSource) { @SuppressWarnings("rawtypes") Observable rawObservable = contentSource; return _writeContentRaw(rawObservable, false); } @Override public Observable<HttpClientResponse<O>> writeBytesContent(Observable<byte[]> contentSource) { @SuppressWarnings("rawtypes") Observable rawObservable = contentSource; return _writeContentRaw(rawObservable, false); } @Override public Observable<HttpClientResponse<O>> writeContent(Observable<I> contentSource, Func1<I, Boolean> flushSelector) { @SuppressWarnings("rawtypes") Observable rawObservable = contentSource; return _writeContentRaw(rawObservable, flushSelector, false); } @Override public Observable<HttpClientResponse<O>> writeStringContent(Observable<String> contentSource, Func1<String, Boolean> flushSelector) { @SuppressWarnings("rawtypes") Observable rawObservable = contentSource; return _writeContentRaw(rawObservable, flushSelector, false); } @Override public Observable<HttpClientResponse<O>> writeBytesContent(Observable<byte[]> contentSource, Func1<byte[], Boolean> flushSelector) { @SuppressWarnings("rawtypes") Observable rawObservable = contentSource; return _writeContentRaw(rawObservable, flushSelector, false); } @Override public <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeContent(Observable<I> contentSource, final Func0<T> trailerFactory, final Func2<T, I, T> trailerMutator) { @SuppressWarnings("rawtypes") Observable rawObservable = contentSource; return _writeContentRaw(OperatorTrailer.liftFrom(rawObservable, trailerFactory, trailerMutator), true); } @Override public <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeStringContent(Observable<String> contentSource, Func0<T> trailerFactory, Func2<T, String, T> trailerMutator) { @SuppressWarnings("rawtypes") Observable rawObservable = contentSource; return _writeContentRaw(OperatorTrailer.liftFrom(rawObservable, trailerFactory, trailerMutator), true); } @Override public <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeBytesContent(Observable<byte[]> contentSource, Func0<T> trailerFactory, Func2<T, byte[], T> trailerMutator) { @SuppressWarnings("rawtypes") Observable rawObservable = contentSource; return _writeContentRaw(OperatorTrailer.liftFrom(rawObservable, trailerFactory, trailerMutator), true); } @Override public <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeContent(Observable<I> contentSource, Func0<T> trailerFactory, Func2<T, I, T> trailerMutator, Func1<I, Boolean> flushSelector) { @SuppressWarnings("rawtypes") Observable rawObservable = contentSource; return _writeContentRaw(OperatorTrailer.liftFrom(rawObservable, trailerFactory, trailerMutator), flushSelector, true); } @Override public <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeStringContent( Observable<String> contentSource, Func0<T> trailerFactory, Func2<T, String, T> trailerMutator, Func1<String, Boolean> flushSelector) { @SuppressWarnings("rawtypes") Observable rawObservable = contentSource; return _writeContentRaw(OperatorTrailer.liftFrom(rawObservable, trailerFactory, trailerMutator), flushSelector, true); } @Override public <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeBytesContent( Observable<byte[]> contentSource, Func0<T> trailerFactory, Func2<T, byte[], T> trailerMutator, Func1<byte[], Boolean> flushSelector) { @SuppressWarnings("rawtypes") Observable rawObservable = contentSource; return _writeContentRaw(OperatorTrailer.liftFrom(rawObservable, trailerFactory, trailerMutator), flushSelector, true); } @Override public HttpClientRequestImpl<I, O> readTimeOut(int timeOut, TimeUnit timeUnit) { return _copy(client.readTimeOut(timeOut, timeUnit)); } @Override public HttpClientRequestImpl<I, O> followRedirects(int maxRedirects) { final Redirector<I, O> redirector = new Redirector<>(maxRedirects, client); HttpClientRequestImpl<I, O> toReturn = _copy(client, rawRequest.followRedirect(redirector)); redirector.setOriginalRequest(toReturn.rawRequest); return toReturn; } @Override public HttpClientRequestImpl<I, O> followRedirects(boolean follow) { return follow ? followRedirects(Redirector.DEFAULT_MAX_REDIRECTS) : followRedirects(NO_REDIRECTS); } @Override public HttpClientRequestImpl<I, O> setMethod(HttpMethod method) { return _copy(client, rawRequest.setMethod(method)); } @Override public HttpClientRequestImpl<I, O> setUri(String newUri) { return _copy(client, rawRequest.setUri(newUri)); } @Override public HttpClientRequestImpl<I, O> addHeader(CharSequence name, Object value) { return _copy(client, rawRequest.addHeader(name, value)); } @Override public HttpClientRequest<I, O> addHeaders(Map<? extends CharSequence, ? extends Iterable<Object>> headers) { return _copy(client, rawRequest.addHeaders(headers)); } @Override public HttpClientRequestImpl<I, O> addCookie(Cookie cookie) { return _copy(client, rawRequest.addCookie(cookie)); } @Override public HttpClientRequestImpl<I, O> addDateHeader(CharSequence name, Date value) { return _copy(client, rawRequest.addDateHeader(name, value)); } @Override public HttpClientRequestImpl<I, O> addDateHeader(CharSequence name, Iterable<Date> values) { return _copy(client, rawRequest.addDateHeader(name, values)); } @Override public HttpClientRequestImpl<I, O> addHeaderValues(CharSequence name, Iterable<Object> values) { return _copy(client, rawRequest.addHeaderValues(name, values)); } @Override public HttpClientRequestImpl<I, O> setDateHeader(CharSequence name, Date value) { return _copy(client, rawRequest.setDateHeader(name, value)); } @Override public HttpClientRequestImpl<I, O> setHeader(CharSequence name, Object value) { return _copy(client, rawRequest.setHeader(name, value)); } @Override public HttpClientRequest<I, O> setHeaders(Map<? extends CharSequence, ? extends Iterable<Object>> headers) { return _copy(client, rawRequest.setHeaders(headers)); } @Override public HttpClientRequestImpl<I, O> setDateHeader(CharSequence name, Iterable<Date> values) { return _copy(client, rawRequest.setDateHeader(name, values)); } @Override public HttpClientRequestImpl<I, O> setHeaderValues(CharSequence name, Iterable<Object> values) { return _copy(client, rawRequest.setHeaderValues(name, values)); } @Override public HttpClientRequestImpl<I, O> removeHeader(CharSequence name) { return _copy(client, rawRequest.removeHeader(name)); } @Override public HttpClientRequestImpl<I, O> setKeepAlive(boolean keepAlive) { return _copy(client, rawRequest.setKeepAlive(keepAlive)); } @Override public HttpClientRequestImpl<I, O> setTransferEncodingChunked() { return _copy(client, rawRequest.setTransferEncodingChunked()); } @Override public <II> HttpClientRequestImpl<II, O> transformContent(AllocatingTransformer<II, I> transformer) { final List<AppendTransformerEvent> newTransformers = new ArrayList<>(immutableTransformers); @SuppressWarnings("unchecked") AppendTransformerEvent e = new AppendTransformerEvent(transformer); newTransformers.add(e); @SuppressWarnings("unchecked") RawRequest<II, O> cast = (RawRequest<II, O>) this.rawRequest; return new HttpClientRequestImpl<>(cast, client, newTransformers, immutableResponseTransformers); } @Override public <OO> HttpClientRequestImpl<I, OO> transformResponseContent(Transformer<O, OO> transformer) { final List<Transformer> newTransformers = new ArrayList<>(immutableResponseTransformers); newTransformers.add(transformer); @SuppressWarnings("unchecked") RawRequest<I, OO> cast = (RawRequest<I, OO>) this.rawRequest; TcpClient rawClient = client; @SuppressWarnings("unchecked") TcpClient<?, HttpClientResponse<OO>> _client = (TcpClient<?, HttpClientResponse<OO>>)rawClient; return new HttpClientRequestImpl<>(cast, _client, immutableTransformers, newTransformers); } @Override public WebSocketRequestImpl<O> requestWebSocketUpgrade() { return WebSocketRequestImpl.createNew(this); } @Override public boolean containsHeader(CharSequence name) { return rawRequest.getHeaders().headers().contains(name); } @Override public boolean containsHeaderWithValue(CharSequence name, CharSequence value, boolean caseInsensitiveValueMatch) { return rawRequest.getHeaders().headers().contains(name, value, caseInsensitiveValueMatch); } @Override public String getHeader(CharSequence name) { return rawRequest.getHeaders().headers().get(name); } @Override public List<String> getAllHeaders(CharSequence name) { return rawRequest.getHeaders().headers().getAll(name); } @Override public Iterator<Entry<CharSequence, CharSequence>> headerIterator() { return rawRequest.getHeaders().headers().iteratorCharSequence(); } @Override public Set<String> getHeaderNames() { return rawRequest.getHeaders().headers().names(); } @Override public HttpVersion getHttpVersion() { return rawRequest.getHeaders().protocolVersion(); } @Override public HttpMethod getMethod() { return rawRequest.getHeaders().method(); } @Override public String getUri() { return rawRequest.getHeaders().uri(); } public static <I, O> HttpClientRequestImpl<I, O> create(final HttpVersion version, final HttpMethod httpMethod, final String uri, final TcpClient<?, HttpClientResponse<O>> client, int maxRedirects) { Redirector<I, O> redirector = NO_REDIRECTS == maxRedirects ? null : new Redirector<I, O>(maxRedirects, client ); final RawRequest<I, O> rawRequest = RawRequest.create(version, httpMethod, uri, redirector); if (null != redirector) { redirector.setOriginalRequest(rawRequest); } return create(rawRequest, client); } public static <I, O> HttpClientRequestImpl<I, O> create(final HttpVersion version, final HttpMethod httpMethod, final String uri, final TcpClient<?, HttpClientResponse<O>> client) { return create(version, httpMethod, uri, client, NO_REDIRECTS); } public static <I, O> HttpClientRequestImpl<I, O> create(final RawRequest<I, O> rawRequest, final TcpClient<?, HttpClientResponse<O>> client) { return new HttpClientRequestImpl<>(rawRequest, client, Collections.<AppendTransformerEvent>emptyList(), Collections.<Transformer>emptyList()); } public TcpClient<?, HttpClientResponse<O>> getClient() { return client; } @SuppressWarnings("unchecked") private <II, OO> HttpClientRequestImpl<II, OO> _copy(TcpClient<?, HttpClientResponse<OO>> c) { return _copy(c, (RawRequest<II, OO>)rawRequest); } @SuppressWarnings("unchecked") private <II, OO> HttpClientRequestImpl<II, OO> _copy(TcpClient<?, HttpClientResponse<OO>> c, RawRequest<II, OO> rawRequest) { return new HttpClientRequestImpl<>(rawRequest, c, immutableTransformers, immutableResponseTransformers); } @SuppressWarnings("rawtypes") private Observable<HttpClientResponse<O>> _writeContentRaw(Observable rawContent, boolean hasTrailers) { return _writeContentRaw(rawContent, null, hasTrailers); } @SuppressWarnings("rawtypes") private Observable<HttpClientResponse<O>> _writeContentRaw(Observable rawContent, Func1<?, Boolean> flushSelector, boolean hasTrailers) { final RawRequest<I, O> r = RawRequest.create(rawRequest.getHeaders(), rawContent, flushSelector, hasTrailers, rawRequest.getRedirector()); return new HttpClientRequestImpl<>(r, client, immutableTransformers, immutableResponseTransformers); } public RawRequest<I, O> unsafeRawRequest() { return rawRequest; } private static class OnSubscribeFuncImpl<I, O> implements OnSubscribe<HttpClientResponse<O>> { @SuppressWarnings("rawtypes") private final Observable source; private final TcpClient<?, HttpClientResponse<O>> client; public OnSubscribeFuncImpl(final TcpClient<?, HttpClientResponse<O>> client, RawRequest<I, O> rawRequest, List<Transformer> responseTransformers, List<AppendTransformerEvent> requestTransformers) { this.client = client; ConnToResponseFunc<I, O> connToResponseFunc = new ConnToResponseFunc<>(rawRequest, responseTransformers, requestTransformers); Observable<HttpClientResponse<O>> source = this.client.createConnectionRequest() .take(1) .switchMap(connToResponseFunc); if (null != rawRequest.getRedirector()) { source = source.switchMap(rawRequest.getRedirector()); } this.source = source; } @Override @SuppressWarnings("unchecked") public void call(Subscriber<? super HttpClientResponse<O>> subscriber) { @SuppressWarnings("rawtypes") final Subscriber rawSub = subscriber; source.unsafeSubscribe(rawSub); } } private static class ConnToResponseFunc<I, O> implements Func1<Connection<HttpClientResponse<O>, ?>, Observable<HttpClientResponse<O>>> { private final RawRequest<I, O> rawRequest; private List<Transformer> responseTransformers; private List<AppendTransformerEvent> requestTransformers; public ConnToResponseFunc(RawRequest<I, O> rawRequest, List<Transformer> responseTransformers, List<AppendTransformerEvent> requestTransformers) { this.rawRequest = rawRequest; this.responseTransformers = responseTransformers; this.requestTransformers = requestTransformers; } @Override public Observable<HttpClientResponse<O>> call(final Connection<HttpClientResponse<O>, ?> conn) { for (AppendTransformerEvent requestTransformer : requestTransformers) { conn.unsafeNettyChannel().pipeline().fireUserEventTriggered(requestTransformer); } final Observable<HttpClientResponse<O>> input = conn.getInput(); final HttpClientEventsListener eventsListener = conn.unsafeNettyChannel().attr(HttpChannelProvider.HTTP_CLIENT_EVENT_LISTENER).get(); final EventPublisher eventPublisher = conn.unsafeNettyChannel().attr(EventAttributeKeys.EVENT_PUBLISHER).get(); return writeRequest(conn).lift(new RequestWriteMetricsOperator(eventsListener, eventPublisher)) .map(new VoidToAnythingCast<HttpClientResponse<O>>()) .ignoreElements() .concatWith(input.take(1)) .map(new Func1<HttpClientResponse<O>, HttpClientResponse<O>>() { @SuppressWarnings("unchecked") @Override public HttpClientResponse<O> call(HttpClientResponse<O> r) { HttpClientResponse rp = HttpClientResponseImpl.newInstance(r, conn); for (Transformer transformer : responseTransformers) { rp = rp.transformContent(transformer); } return (HttpClientResponse<O>) rp; } }); } @SuppressWarnings("unchecked") protected Observable<Void> writeRequest(Connection<HttpClientResponse<O>, ?> conn) { return conn.write(rawRequest.asObservable(conn)); } } private static class RequestWriteMetricsOperator implements Operator<Void, Void> { private final EventPublisher eventPublisher; private final HttpClientEventsListener eventsListener; public RequestWriteMetricsOperator(HttpClientEventsListener eventsListener, EventPublisher eventPublisher) { this.eventPublisher = eventPublisher; this.eventsListener = eventsListener; } @Override public Subscriber<? super Void> call(final Subscriber<? super Void> o) { final long startTimeNanos = eventPublisher.publishingEnabled() ? Clock.newStartTimeNanos() : -1; if (eventPublisher.publishingEnabled()) { eventsListener.onRequestSubmitted(); } return new Subscriber<Void>(o) { @Override public void onCompleted() { if (eventPublisher.publishingEnabled()) { eventsListener.onRequestWriteComplete(Clock.onEndNanos(startTimeNanos), NANOSECONDS); } o.onCompleted(); } @Override public void onError(Throwable e) { if (eventPublisher.publishingEnabled()) { eventsListener.onRequestWriteFailed(Clock.onEndNanos(startTimeNanos), NANOSECONDS, e); } o.onError(e); } @Override public void onNext(Void aVoid) { o.onNext(aVoid); } }; } } }