* Copyright 2018-2019 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
 *      https://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,
 * See the License for the specific language governing permissions and
 * limitations under the License.

package org.springframework.cloud.function.web.source;

import java.time.Duration;
import java.util.function.Supplier;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.cloud.function.web.util.HeaderUtils;
import org.springframework.http.HttpStatus;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;

 * A {@link Supplier} that pulls data from an HTTP endpoint. Repeatedly polls the endpoint
 * until a non-2xx response is received, at which point it will repeatedly produced a
 * Mono at 1 sec intervals until the next 2xx response.
 * @author Dave Syer
 * @author Oleg Zhurakousky
public class HttpSupplier implements Supplier<Flux<?>> {

	private static Log logger = LogFactory.getLog(HttpSupplier.class);

	private WebClient client;

	private ExporterProperties props;

	 * @param client the WebClient to use. The baseUrl should be set.
	 * @param props the ExporterProperties to use to parameterize the requests.
	public HttpSupplier(WebClient client, ExporterProperties props) {
		this.client = client;
		this.props = props;

	public Flux<?> get() {
		return get(this.client);

	private Flux<?> get(WebClient client) {
		Flux<?> result = client.get().uri(this.props.getSource().getUrl()).exchange()
		if (this.props.isDebug()) {
			result = result.log();
		return result.onErrorResume(TerminateException.class, error -> Mono.empty());

	private Mono<?> transform(ClientResponse response) {
		HttpStatus status = response.statusCode();
		if (!status.is2xxSuccessful()) {
			if (this.props.isDebug()) {
				logger.info("Delaying supplier based on status=" + response.statusCode());
			return Mono.delay(Duration.ofSeconds(1));
		return response.bodyToMono(this.props.getSource().getType())
				.map(value -> message(response, value));

	private Object message(ClientResponse response, Object payload) {
		if (!this.props.getSource().isIncludeHeaders()) {
			return payload;
		return MessageBuilder.withPayload(payload)
				.setHeader("scf-sink-url", this.props.getSink().getUrl())
				.setHeader("scf-func-name", this.props.getSink().getName())

	private static class TerminateException extends RuntimeException {

		TerminateException() {
			super("Planned termination");

		public synchronized Throwable fillInStackTrace() {
			return this;