-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Java HTTP Fault Injector Sample Using Storage Blobs (#1684)
Java HTTP Fault Injector Sample Using Storage Blobs
- Loading branch information
1 parent
98a65a4
commit 54285e3
Showing
6 changed files
with
267 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
tools/http-fault-injector/sample-clients/java/storage-blobs/pom.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<groupId>org.example</groupId> | ||
<artifactId>storage-blobs</artifactId> | ||
<version>1.0-SNAPSHOT</version> | ||
|
||
<properties> | ||
<maven.compiler.source>8</maven.compiler.source> | ||
<maven.compiler.target>8</maven.compiler.target> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>com.azure</groupId> | ||
<artifactId>azure-storage-blob</artifactId> | ||
<version>12.12.0</version> | ||
</dependency> | ||
</dependencies> | ||
|
||
</project> |
43 changes: 43 additions & 0 deletions
43
tools/http-fault-injector/sample-clients/java/storage-blobs/src/main/java/App.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import com.azure.core.http.HttpClient; | ||
import com.azure.core.util.BinaryData; | ||
import com.azure.core.util.Configuration; | ||
import com.azure.storage.blob.BlobClient; | ||
import com.azure.storage.blob.BlobClientBuilder; | ||
import com.azure.storage.common.policy.RequestRetryOptions; | ||
import com.azure.storage.common.policy.RetryPolicyType; | ||
|
||
import java.time.Duration; | ||
|
||
/** | ||
* This is a sample application using azure-storage-blob to send requests to the HTTP fault injector. All concepts | ||
* presented here are applicable to all Azure SDKs which use HTTP as its network transport. | ||
*/ | ||
public class App { | ||
public static void main(String[] args) { | ||
HttpClient httpClient = HttpClient.createDefault(); | ||
|
||
// You must either add the .NET developer certificate to the Java cacerts keystore, or uncomment the following | ||
// lines to disable SSL validation if using Netty/Reactor Netty as the underlying HttpClient. | ||
// | ||
// io.netty.handler.ssl.SslContext sslContext = io.netty.handler.ssl.SslContextBuilder | ||
// .forClient().trustManager(io.netty.handler.ssl.util.InsecureTrustManagerFactory.INSTANCE).build(); | ||
// httpClient = httpClient.secure(sslContextBuilder -> sslContextBuilder.sslContext(sslContext)); | ||
|
||
BlobClient blobClient = new BlobClientBuilder() | ||
.connectionString(Configuration.getGlobalConfiguration().get("STORAGE_CONNECTION_STRING")) | ||
.containerName("sample") | ||
.blobName("sample.txt") | ||
.retryOptions(new RequestRetryOptions(RetryPolicyType.FIXED, 3, Duration.ofMinutes(1), | ||
Duration.ofSeconds(1), Duration.ofSeconds(1), null)) | ||
// .httpClient(new FaultInjectorHttpClient(httpClient)) // Using an HttpClient is also a valid option. | ||
.addPolicy(new FaultInjectorUrlRewriterPolicy()) | ||
.buildClient(); | ||
|
||
System.out.println("Sending request..."); | ||
BinaryData content = blobClient.downloadContent(); | ||
System.out.printf("Content: %s%n", content); | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
...ult-injector/sample-clients/java/storage-blobs/src/main/java/FaultInjectorHttpClient.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import com.azure.core.http.HttpClient; | ||
import com.azure.core.http.HttpRequest; | ||
import com.azure.core.http.HttpResponse; | ||
import com.azure.core.util.Context; | ||
import reactor.core.publisher.Mono; | ||
|
||
import java.util.Objects; | ||
|
||
/** | ||
* General purpose {@link HttpClient} which re-writes request URLs to use the HTTP fault injector before using an | ||
* underlying {@link HttpClient} to send the network request. | ||
*/ | ||
public final class FaultInjectorHttpClient implements HttpClient { | ||
private final HttpClient httpClient; | ||
private final String host; | ||
private final int port; | ||
|
||
/** | ||
* Default constructor for {@link FaultInjectorHttpClient} which expects the HTTP fault injector to use {@link | ||
* Utils#DEFAULT_HTTP_FAULT_INJECTOR_HOST} and running on port {@link Utils#DEFAULT_HTTP_FAULT_INJECTOR_HTTPS_PORT}. | ||
* | ||
* @param httpClient The underlying {@link HttpClient} used to make network requests. | ||
*/ | ||
public FaultInjectorHttpClient(HttpClient httpClient) { | ||
this(httpClient, Utils.DEFAULT_HTTP_FAULT_INJECTOR_HOST, Utils.DEFAULT_HTTP_FAULT_INJECTOR_HTTPS_PORT); | ||
} | ||
|
||
/** | ||
* Constructor for {@link FaultInjectorHttpClient} which allows for the configuration of which {@code host} and | ||
* {@code port} the HTTP fault injector is using. | ||
* | ||
* @param httpClient The underlying {@link HttpClient} used to make network requests. | ||
* @param host The host HTTP fault injector is running on. | ||
* @param port The port HTTP fault injector is using. | ||
* @throws NullPointerException If {@code httpClient} or {@code host} is null. | ||
* @throws IllegalArgumentException If {@code host} is an empty string or {@code port} is an invalid port. | ||
*/ | ||
public FaultInjectorHttpClient(HttpClient httpClient, String host, int port) { | ||
this.httpClient = Objects.requireNonNull(httpClient, "'httpClient' cannot be null."); | ||
Utils.validateHostAndPort(host, port); | ||
|
||
this.host = host; | ||
this.port = port; | ||
} | ||
|
||
@Override | ||
public Mono<HttpResponse> send(HttpRequest request) { | ||
return send(request, Context.NONE); | ||
} | ||
|
||
@Override | ||
public Mono<HttpResponse> send(HttpRequest request, Context context) { | ||
return Mono.defer(() -> Mono.fromCallable(() -> Utils.rewriteUrlToUseFaultInjector(request, host, port))) | ||
.flatMap(rewrittenHttpRequest -> httpClient.send(rewrittenHttpRequest, context)); | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
...ector/sample-clients/java/storage-blobs/src/main/java/FaultInjectorUrlRewriterPolicy.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import com.azure.core.http.HttpPipelineCallContext; | ||
import com.azure.core.http.HttpPipelineNextPolicy; | ||
import com.azure.core.http.HttpPipelinePosition; | ||
import com.azure.core.http.HttpResponse; | ||
import com.azure.core.http.policy.HttpPipelinePolicy; | ||
import reactor.core.publisher.Mono; | ||
|
||
/** | ||
* General purpose {@link HttpPipelinePolicy} which re-writes the request URL to send it to the HTTP fault injector. | ||
*/ | ||
public final class FaultInjectorUrlRewriterPolicy implements HttpPipelinePolicy { | ||
private final String host; | ||
private final int port; | ||
|
||
/** | ||
* Default constructor for {@link FaultInjectorUrlRewriterPolicy} which expects the HTTP fault injector to use | ||
* {@link Utils#DEFAULT_HTTP_FAULT_INJECTOR_HOST} and running on port | ||
* {@link Utils#DEFAULT_HTTP_FAULT_INJECTOR_HTTPS_PORT}. | ||
*/ | ||
public FaultInjectorUrlRewriterPolicy() { | ||
this(Utils.DEFAULT_HTTP_FAULT_INJECTOR_HOST, Utils.DEFAULT_HTTP_FAULT_INJECTOR_HTTPS_PORT); | ||
} | ||
|
||
/** | ||
* Constructor used to configure re-writing the request URL to the non-default HTTP fault injector host and port. | ||
* | ||
* @param host The host HTTP fault injector is running on. | ||
* @param port The port HTTP fault injector is using. | ||
* @throws NullPointerException If {@code host} is null. | ||
* @throws IllegalArgumentException If {@code host} is an empty string or {@code port} is an invalid port. | ||
*/ | ||
public FaultInjectorUrlRewriterPolicy(String host, int port) { | ||
Utils.validateHostAndPort(host, port); | ||
|
||
this.host = host; | ||
this.port = port; | ||
} | ||
|
||
@Override | ||
public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) { | ||
context.setHttpRequest(Utils.rewriteUrlToUseFaultInjector(context.getHttpRequest(), host, port)); | ||
|
||
return next.process(); | ||
} | ||
|
||
@Override | ||
public HttpPipelinePosition getPipelinePosition() { | ||
// The policy should be ran per retry in case calls are made to a secondary, fail-over host. | ||
return HttpPipelinePosition.PER_RETRY; | ||
} | ||
} |
84 changes: 84 additions & 0 deletions
84
tools/http-fault-injector/sample-clients/java/storage-blobs/src/main/java/Utils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import com.azure.core.http.HttpRequest; | ||
|
||
import java.net.URI; | ||
import java.util.Objects; | ||
|
||
/** | ||
* Utility class containing constants and methods for re-writing {@link HttpRequest HttpRequests} to use the HTTP fault | ||
* injector. | ||
*/ | ||
public final class Utils { | ||
/** | ||
* The default host used by HTTP fault injector. | ||
*/ | ||
public static final String DEFAULT_HTTP_FAULT_INJECTOR_HOST = "localhost"; | ||
|
||
/** | ||
* The default HTTP port used by HTTP fault injector. | ||
*/ | ||
public static final int DEFAULT_HTTP_FAULT_INJECTOR_HTTP_PORT = 7777; | ||
|
||
/** | ||
* The default HTTPS port used by HTTP fault injector. | ||
*/ | ||
public static final int DEFAULT_HTTP_FAULT_INJECTOR_HTTPS_PORT = 7778; | ||
|
||
/** | ||
* The HTTP header used by HTTP fault injector to determine where it needs to forward a request. | ||
*/ | ||
public static final String HTTP_FAULT_INJECTOR_UPSTREAM_HOST_HEADER = "X-Upstream-Host"; | ||
|
||
/** | ||
* Utility method which re-writes the {@link HttpRequest HttpRequest's} URL to use the HTTP fault injector. | ||
* <p> | ||
* This will set the HTTP header {@link #HTTP_FAULT_INJECTOR_UPSTREAM_HOST_HEADER} to the request URL used by the | ||
* HTTP request before re-writing and will update the request URL to use the HTTP fault injector. | ||
* | ||
* @param request The {@link HttpRequest} having its URL re-written. | ||
* @param host The HTTP fault injector host. | ||
* @param port The HTTP fault injector port. | ||
* @return The updated {@link HttpRequest} with its URL re-written. | ||
* @throws NullPointerException If {@code request} or {@code host} are null. | ||
* @throws IllegalArgumentException If {@code host} is an empty string or {@code port} is | ||
* an invalid port. | ||
* @throws IllegalStateException If the request URL isn't valid or the HTTP fault injector URL isn't valid. | ||
*/ | ||
public static HttpRequest rewriteUrlToUseFaultInjector(HttpRequest request, String host, int port) { | ||
validateHostAndPort(host, port); | ||
|
||
try { | ||
URI requestUri = request.getUrl().toURI(); | ||
URI faultInjectorUri = new URI(requestUri.getScheme(), requestUri.getUserInfo(), host, | ||
port, requestUri.getPath(), requestUri.getQuery(), requestUri.getFragment()); | ||
|
||
String xUpstreamHost = (requestUri.getPort() < 0) | ||
? requestUri.getHost() | ||
: requestUri.getHost() + ":" + requestUri.getPort(); | ||
|
||
return request.setHeader(HTTP_FAULT_INJECTOR_UPSTREAM_HOST_HEADER, xUpstreamHost) | ||
.setUrl(faultInjectorUri.toURL()); | ||
} catch (Exception exception) { | ||
throw new IllegalStateException(exception); | ||
} | ||
} | ||
|
||
/* | ||
* Helper method for validating the HTTP fault injector host and port. | ||
*/ | ||
static void validateHostAndPort(String host, int port) { | ||
Objects.requireNonNull(host, "'host' cannot be null."); | ||
|
||
if (host.isEmpty()) { | ||
throw new IllegalArgumentException("'host' must be a non-empty string."); | ||
} | ||
|
||
if (port < 1 || port > 65535) { | ||
throw new IllegalArgumentException("'port' must be a valid port number."); | ||
} | ||
} | ||
|
||
private Utils() { } | ||
} |