/* * Copyright 2013-2017 the original author or authors. * * 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 org.cloudfoundry.reactor.util; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.util.AsciiString; import org.cloudfoundry.reactor.ConnectionContext; import org.cloudfoundry.reactor.TokenProvider; import org.reactivestreams.Publisher; import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Mono; import reactor.ipc.netty.http.client.HttpClientRequest; import reactor.ipc.netty.http.client.HttpClientResponse; import java.util.function.Function; import static io.netty.handler.codec.http.HttpHeaderNames.AUTHORIZATION; import static org.cloudfoundry.util.tuple.TupleUtils.function; public abstract class AbstractReactorOperations { protected static final AsciiString APPLICATION_ZIP = new AsciiString("application/zip"); private final ConnectionContext connectionContext; private final Mono<String> root; private final TokenProvider tokenProvider; protected AbstractReactorOperations(ConnectionContext connectionContext, Mono<String> root, TokenProvider tokenProvider) { this.connectionContext = connectionContext; this.root = root; this.tokenProvider = tokenProvider; } protected final <T> Mono<T> doDelete(Object requestPayload, Class<T> responseType, Function<UriComponentsBuilder, UriComponentsBuilder> uriTransformer, Function<Mono<HttpClientRequest>, Mono<HttpClientRequest>> requestTransformer, Function<Mono<HttpClientResponse>, Mono<HttpClientResponse>> responseTransformer) { return this.root .transform(transformUri(uriTransformer)) .then(uri -> this.connectionContext.getHttpClient() .delete(uri, request -> Mono.just(request) .map(AbstractReactorOperations::disableFailOnError) .transform(this::addAuthorization) .map(UserAgent::addUserAgent) .transform(requestTransformer) .transform(serializedRequest(requestPayload))) .doOnSubscribe(NetworkLogging.delete(uri)) .transform(NetworkLogging.response(uri))) .transform(this::invalidateToken) .transform(responseTransformer) .transform(ErrorPayloadMapper.fallback()) .transform(deserializedResponse(responseType)); } protected final <T> Mono<T> doGet(Class<T> responseType, Function<UriComponentsBuilder, UriComponentsBuilder> uriTransformer, Function<Mono<HttpClientRequest>, Mono<HttpClientRequest>> requestTransformer, Function<Mono<HttpClientResponse>, Mono<HttpClientResponse>> responseTransformer) { return doGet(uriTransformer, requestTransformer, inbound -> inbound .transform(responseTransformer)) .transform(deserializedResponse(responseType)); } protected final Mono<HttpClientResponse> doGet(Function<UriComponentsBuilder, UriComponentsBuilder> uriTransformer, Function<Mono<HttpClientRequest>, Mono<HttpClientRequest>> requestTransformer, Function<Mono<HttpClientResponse>, Mono<HttpClientResponse>> responseTransformer) { return this.root .transform(transformUri(uriTransformer)) .then(uri -> this.connectionContext.getHttpClient() .get(uri, request -> Mono.just(request) .map(AbstractReactorOperations::disableFailOnError) .transform(this::addAuthorization) .map(UserAgent::addUserAgent) .transform(requestTransformer) .then(HttpClientRequest::send)) .doOnSubscribe(NetworkLogging.get(uri)) .transform(NetworkLogging.response(uri))) .transform(this::invalidateToken) .transform(responseTransformer) .transform(ErrorPayloadMapper.fallback()); } protected final <T> Mono<T> doPatch(Object requestPayload, Class<T> responseType, Function<UriComponentsBuilder, UriComponentsBuilder> uriTransformer, Function<Mono<HttpClientRequest>, Mono<HttpClientRequest>> requestTransformer, Function<Mono<HttpClientResponse>, Mono<HttpClientResponse>> responseTransformer) { return this.root .transform(transformUri(uriTransformer)) .then(uri -> this.connectionContext.getHttpClient() .patch(uri, request -> Mono.just(request) .map(AbstractReactorOperations::disableChunkedTransfer) .map(AbstractReactorOperations::disableFailOnError) .transform(this::addAuthorization) .map(UserAgent::addUserAgent) .transform(requestTransformer) .transform(serializedRequest(requestPayload))) .doOnSubscribe(NetworkLogging.patch(uri)) .transform(NetworkLogging.response(uri))) .transform(this::invalidateToken) .transform(responseTransformer) .transform(ErrorPayloadMapper.fallback()) .transform(deserializedResponse(responseType)); } protected final <T> Mono<T> doPost(Object requestPayload, Class<T> responseType, Function<UriComponentsBuilder, UriComponentsBuilder> uriTransformer, Function<Mono<HttpClientRequest>, Mono<HttpClientRequest>> requestTransformer, Function<Mono<HttpClientResponse>, Mono<HttpClientResponse>> responseTransformer) { return doPost(responseType, uriTransformer, outbound -> outbound .transform(requestTransformer) .transform(serializedRequest(requestPayload)), responseTransformer); } protected final <T> Mono<T> doPost(Class<T> responseType, Function<UriComponentsBuilder, UriComponentsBuilder> uriTransformer, Function<Mono<HttpClientRequest>, Publisher<Void>> requestTransformer, Function<Mono<HttpClientResponse>, Mono<HttpClientResponse>> responseTransformer) { return this.root .transform(transformUri(uriTransformer)) .then(uri -> this.connectionContext.getHttpClient() .post(uri, request -> Mono.just(request) .map(AbstractReactorOperations::disableChunkedTransfer) .map(AbstractReactorOperations::disableFailOnError) .transform(this::addAuthorization) .map(UserAgent::addUserAgent) .transform(requestTransformer)) .doOnSubscribe(NetworkLogging.post(uri)) .transform(NetworkLogging.response(uri))) .transform(this::invalidateToken) .transform(responseTransformer) .transform(ErrorPayloadMapper.fallback()) .transform(deserializedResponse(responseType)); } protected final <T> Mono<T> doPut(Object requestPayload, Class<T> responseType, Function<UriComponentsBuilder, UriComponentsBuilder> uriTransformer, Function<Mono<HttpClientRequest>, Mono<HttpClientRequest>> requestTransformer, Function<Mono<HttpClientResponse>, Mono<HttpClientResponse>> responseTransformer) { return doPut(responseType, uriTransformer, outbound -> outbound .transform(requestTransformer) .transform(serializedRequest(requestPayload)), responseTransformer); } protected final <T> Mono<T> doPut(Class<T> responseType, Function<UriComponentsBuilder, UriComponentsBuilder> uriTransformer, Function<Mono<HttpClientRequest>, Publisher<Void>> requestTransformer, Function<Mono<HttpClientResponse>, Mono<HttpClientResponse>> responseTransformer) { return this.root .transform(transformUri(uriTransformer)) .then(uri -> this.connectionContext.getHttpClient() .put(uri, request -> Mono.just(request) .map(AbstractReactorOperations::disableChunkedTransfer) .map(AbstractReactorOperations::disableFailOnError) .transform(this::addAuthorization) .map(UserAgent::addUserAgent) .transform(requestTransformer)) .doOnSubscribe(NetworkLogging.put(uri)) .transform(NetworkLogging.response(uri))) .transform(this::invalidateToken) .transform(responseTransformer) .transform(ErrorPayloadMapper.fallback()) .transform(deserializedResponse(responseType)); } protected final Mono<HttpClientResponse> doWs(Function<UriComponentsBuilder, UriComponentsBuilder> uriTransformer, Function<Mono<HttpClientRequest>, Mono<HttpClientRequest>> requestTransformer, Function<Mono<HttpClientResponse>, Mono<HttpClientResponse>> responseTransformer) { return this.root .transform(transformUri(uriTransformer)) .then(uri -> this.connectionContext.getHttpClient() .get(uri, request -> Mono.just(request) .map(AbstractReactorOperations::disableFailOnError) .transform(this::addAuthorization) .map(UserAgent::addUserAgent) .transform(requestTransformer) .flatMapMany(HttpClientRequest::sendWebsocket)) .doOnSubscribe(NetworkLogging.ws(uri)) .transform(NetworkLogging.response(uri))) .transform(this::invalidateToken) .transform(responseTransformer) .transform(ErrorPayloadMapper.fallback()); } private static HttpClientRequest disableChunkedTransfer(HttpClientRequest request) { return request.chunkedTransfer(false); } private static HttpClientRequest disableFailOnError(HttpClientRequest request) { return request .failOnClientError(false) .failOnServerError(false); } private static Function<Mono<String>, Mono<String>> transformUri(Function<UriComponentsBuilder, UriComponentsBuilder> uriTransformer) { return uri -> uri .map(UriComponentsBuilder::fromUriString) .map(uriTransformer) .map(builder -> builder.build().encode().toUriString()); } private Mono<HttpClientRequest> addAuthorization(Mono<HttpClientRequest> outbound) { return Mono.when(outbound, this.tokenProvider.getToken(this.connectionContext)) .map(function((request, token) -> request.header(AUTHORIZATION, token))); } private <T> Function<Mono<HttpClientResponse>, Mono<T>> deserializedResponse(Class<T> responseType) { return inbound -> inbound .transform(JsonCodec.decode(this.connectionContext.getObjectMapper(), responseType)) .doOnError(JsonParsingException.class, e -> NetworkLogging.RESPONSE_LOGGER.error("{}\n{}", e.getCause().getMessage(), e.getPayload())); } private Mono<HttpClientResponse> invalidateToken(Mono<HttpClientResponse> inbound) { return inbound .then(response -> { if (response.status() == HttpResponseStatus.UNAUTHORIZED) { this.tokenProvider.invalidate(this.connectionContext); return inbound .transform(this::invalidateToken); } else { return Mono.just(response); } }); } private Function<Mono<HttpClientRequest>, Publisher<Void>> serializedRequest(Object requestPayload) { return outbound -> outbound .transform(JsonCodec.encode(this.connectionContext.getObjectMapper(), requestPayload)); } }