Skip to content

Commit

Permalink
Making HttpClient interface (Azure#576)
Browse files Browse the repository at this point in the history
  • Loading branch information
anuchandy authored Mar 12, 2019
1 parent c81e682 commit 368e089
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@
import com.microsoft.rest.v3.http.HttpClient;
import com.microsoft.rest.v3.http.HttpRequest;
import com.microsoft.rest.v3.http.HttpResponse;
import com.microsoft.rest.v3.http.ProxyOptions;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

/**
* This HttpClient attempts to mimic the behavior of http://httpbin.org without ever making a network call.
*/
public class MockHttpClient extends HttpClient {
public class MockHttpClient implements HttpClient {
private static final HttpResponse mockResponse = new MockHttpResponse(200);
private final List<HttpRequest> requests;

Expand All @@ -35,4 +37,19 @@ public Mono<HttpResponse> send(HttpRequest request) {

return Mono.just(mockResponse);
}

@Override
public HttpClient proxy(Supplier<ProxyOptions> proxyOptions) {
throw new IllegalStateException("MockHttpClient.proxy");
}

@Override
public HttpClient wiretap(boolean enableWiretap) {
throw new IllegalStateException("MockHttpClient.wiretap");
}

@Override
public HttpClient port(int port) {
throw new IllegalStateException("MockHttpClient.port");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.microsoft.rest.v3.http.HttpMethod;
import com.microsoft.rest.v3.http.HttpRequest;
import com.microsoft.rest.v3.http.HttpResponse;
import com.microsoft.rest.v3.http.ProxyOptions;
import com.microsoft.rest.v3.util.FluxUtil;
import reactor.core.publisher.Mono;

Expand All @@ -26,11 +27,12 @@
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

/**
* This HttpClient attempts to mimic the behavior of http://httpbin.org without ever making a network call.
*/
public class MockAzureHttpClient extends HttpClient {
public class MockAzureHttpClient implements HttpClient {
private int pollsRemaining;

private int getRequests;
Expand Down Expand Up @@ -276,6 +278,21 @@ else if (pollType.equalsIgnoreCase(LocationPollStrategy.HEADER_NAME)) {
return Mono.<HttpResponse>just(response);
}

@Override
public HttpClient proxy(Supplier<ProxyOptions> proxyOptions) {
throw new IllegalStateException("MockHttpClient.proxy");
}

@Override
public HttpClient wiretap(boolean enableWiretap) {
throw new IllegalStateException("MockHttpClient.wiretap");
}

@Override
public HttpClient port(int port) {
throw new IllegalStateException("MockHttpClient.port");
}

private static Map<String,String> queryToMap(String url) {
final Map<String,String> result = new HashMap<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@
/**
* A generic interface for sending HTTP requests and getting responses.
*/
public abstract class HttpClient {
public interface HttpClient {
/**
* Send the provided request asynchronously.
*
* @param request The HTTP request to send
* @return A {@link Mono} that emits response asynchronously
*/
public abstract Mono<HttpResponse> send(HttpRequest request);
Mono<HttpResponse> send(HttpRequest request);

/**
* Create default HttpClient instance.
*
* @return the HttpClient
*/
public static HttpClient createDefault() {
static HttpClient createDefault() {
return new ReactorNettyClient();
}

Expand All @@ -36,66 +36,21 @@ public static HttpClient createDefault() {
* @param proxyOptions the proxy configuration supplier
* @return a HttpClient with proxy applied
*/
public final HttpClient proxy(Supplier<ProxyOptions> proxyOptions) {
return this.setProxy(proxyOptions);
}
HttpClient proxy(Supplier<ProxyOptions> proxyOptions);

/**
* Apply or remove a wire logger configuration.
*
* @param enableWiretap wiretap config
* @return a HttpClient with wire logging enabled or disabled
*/
public final HttpClient wiretap(boolean enableWiretap) {
return this.setWiretap(enableWiretap);
}
HttpClient wiretap(boolean enableWiretap);

/**
* Set the port that client should connect to.
*
* @param port the port
* @return a HttpClient with port applied
*/
public final HttpClient port(int port) {
return this.setPort(port);
}

/**
* Set the proxy.
*
* The concrete implementation of HttpClient should override this
* method and apply the provided configuration.
*
* @param proxyOptionsSupplier the proxy configuration supplier
* @return a HttpClient with proxy applied
*/
protected HttpClient setProxy(Supplier<ProxyOptions> proxyOptionsSupplier) {
return this;
}

/**
* Set wiretap.
*
* The concrete implementation of HttpClient should override this
* method and apply the provided configuration.
*
* @param enableWiretap wiretap config
* @return a HttpClient with wire logging enabled or disabled
*/
protected HttpClient setWiretap(boolean enableWiretap) {
return this;
}

/**
* Set the port that client should connect to.
*
* The concrete implementation of HttpClient should override this
* method and apply the provided configuration.
*
* @param port the port
* @return a HttpClient with port applied
*/
protected HttpClient setPort(int port) {
return this;
}
HttpClient port(int port);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

/**
* HttpClient that is implemented using reactor-netty.
*/
class ReactorNettyClient extends HttpClient {
class ReactorNettyClient implements HttpClient {
private reactor.netty.http.client.HttpClient httpClient;

/**
Expand All @@ -47,6 +48,16 @@ private ReactorNettyClient(reactor.netty.http.client.HttpClient httpClient) {
this.httpClient = httpClient;
}

/**
* Creates ReactorNettyClient with provided http client with configuration applied.
*
* @param httpClient the reactor http client
* @param config the configuration to apply on the http client
*/
private ReactorNettyClient(reactor.netty.http.client.HttpClient httpClient, Function<reactor.netty.http.client.HttpClient, reactor.netty.http.client.HttpClient> config) {
this.httpClient = config.apply(httpClient);
}

@Override
public Mono<HttpResponse> send(final HttpRequest request) {
Objects.requireNonNull(request.httpMethod());
Expand Down Expand Up @@ -169,38 +180,20 @@ Connection internConnection() {
}

@Override
protected final HttpClient setProxy(Supplier<ProxyOptions> proxyOptionsSupplier) {
return new ClientProxyOptions(this.httpClient, proxyOptionsSupplier);
public final HttpClient proxy(Supplier<ProxyOptions> proxyOptionsSupplier) {
return new ReactorNettyClient(this.httpClient, client -> client.tcpConfiguration(c -> {
ProxyOptions options = proxyOptionsSupplier.get();
return c.proxy(ts -> ts.type(options.type().value()).address(options.address()));
}));
}

@Override
protected final HttpClient setWiretap(boolean enableWiretap) {
return new ClientWiretap(this.httpClient, enableWiretap);
public final HttpClient wiretap(boolean enableWiretap) {
return new ReactorNettyClient(this.httpClient, client -> client.wiretap(enableWiretap));
}

@Override
protected final HttpClient setPort(int port) {
return new ClientPort(this.httpClient, port);
}

private static class ClientProxyOptions extends ReactorNettyClient {
ClientProxyOptions(reactor.netty.http.client.HttpClient httpClient, Supplier<ProxyOptions> proxyOptions) {
super(httpClient.tcpConfiguration(tcpClient -> {
ProxyOptions options = proxyOptions.get();
return tcpClient.proxy(ts -> ts.type(options.type().value()).address(options.address()));
}));
}
}

private static class ClientWiretap extends ReactorNettyClient {
ClientWiretap(reactor.netty.http.client.HttpClient httpClient, boolean enableWiretap) {
super(httpClient.wiretap(enableWiretap));
}
}

private static class ClientPort extends ReactorNettyClient {
ClientPort(reactor.netty.http.client.HttpClient httpClient, int port) {
super(httpClient.port(port));
}
public final HttpClient port(int port) {
return new ReactorNettyClient(this.httpClient, client -> client.port(port));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.microsoft.rest.v3.http.HttpResponse;
import com.microsoft.rest.v3.http.MockHttpClient;
import com.microsoft.rest.v3.http.MockHttpResponse;
import com.microsoft.rest.v3.http.ProxyOptions;
import org.junit.Test;
import reactor.core.publisher.Mono;

Expand All @@ -23,6 +24,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
Expand Down Expand Up @@ -158,7 +160,7 @@ interface ServiceErrorWithCharsetService {
public void ServiceErrorWithResponseContentType() {
ServiceErrorWithCharsetService service = RestProxy.create(
ServiceErrorWithCharsetService.class,
new HttpPipeline(new HttpClient() {
new HttpPipeline(new SimpleMockHttpClient() {
@Override
public Mono<HttpResponse> send(HttpRequest request) {
HttpHeaders headers = new HttpHeaders();
Expand All @@ -182,7 +184,7 @@ public Mono<HttpResponse> send(HttpRequest request) {
public void ServiceErrorWithResponseContentTypeBadJSON() {
ServiceErrorWithCharsetService service = RestProxy.create(
ServiceErrorWithCharsetService.class,
new HttpPipeline(new HttpClient() {
new HttpPipeline(new SimpleMockHttpClient() {
@Override
public Mono<HttpResponse> send(HttpRequest request) {
HttpHeaders headers = new HttpHeaders();
Expand All @@ -206,7 +208,7 @@ public Mono<HttpResponse> send(HttpRequest request) {
public void ServiceErrorWithResponseContentTypeCharset() {
ServiceErrorWithCharsetService service = RestProxy.create(
ServiceErrorWithCharsetService.class,
new HttpPipeline(new HttpClient() {
new HttpPipeline(new SimpleMockHttpClient() {
@Override
public Mono<HttpResponse> send(HttpRequest request) {
HttpHeaders headers = new HttpHeaders();
Expand All @@ -230,7 +232,7 @@ public Mono<HttpResponse> send(HttpRequest request) {
public void ServiceErrorWithResponseContentTypeCharsetBadJSON() {
ServiceErrorWithCharsetService service = RestProxy.create(
ServiceErrorWithCharsetService.class,
new HttpPipeline(new HttpClient() {
new HttpPipeline(new SimpleMockHttpClient() {
@Override
public Mono<HttpResponse> send(HttpRequest request) {
HttpHeaders headers = new HttpHeaders();
Expand Down Expand Up @@ -386,4 +388,26 @@ public void serviceHeaderCollectionPackagePrivateFields() {
private static void assertContains(String value, String expectedSubstring) {
assertTrue("Expected \"" + value + "\" to contain \"" + expectedSubstring + "\".", value.contains(expectedSubstring));
}


private static abstract class SimpleMockHttpClient implements HttpClient {

@Override
public abstract Mono<HttpResponse> send(HttpRequest request);

@Override
public HttpClient proxy(Supplier<ProxyOptions> proxyOptions) {
throw new IllegalStateException("MockHttpClient.proxy not implemented.");
}

@Override
public HttpClient wiretap(boolean enableWiretap) {
throw new IllegalStateException("MockHttpClient.wiretap not implemented.");
}

@Override
public HttpClient port(int port) {
throw new IllegalStateException("MockHttpClient.port not implemented.");
}
}
}
Loading

0 comments on commit 368e089

Please sign in to comment.