Skip to content

Commit

Permalink
Merge pull request #1477 from microsoft/rsh/java-core-migration
Browse files Browse the repository at this point in the history
initial middleware implementations
  • Loading branch information
baywet authored Apr 7, 2022
2 parents bdfdec7 + dcc0b52 commit 4c377f1
Show file tree
Hide file tree
Showing 18 changed files with 1,336 additions and 6 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added

- Added generation of command options for headers defined in the OpenAPI metadata source file. (Shell)
- Added retry, redirect, chaos and telemetry handler in java.

### Changed

- Simplified field deserialization.(PHP) [#1493](https://github.com/microsoft/kiota/issues/1493)
- Fixed a bug where the generator would not strip the common namespace component id for models. [#1483](https://github.com/microsoft/kiota/issues/1483)
- Simplified field deserialization. [#1490](https://github.com/microsoft/kiota/issues/1490)
Expand Down
2 changes: 1 addition & 1 deletion abstractions/java/lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ publishing {
publications {
gpr(MavenPublication) {
artifactId 'kiota-abstractions'
version '1.0.31'
version '1.0.32'
from(components.java)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
/** Represents a request option. */
public interface RequestOption {

public <T extends RequestOption> Class<T> getType();
}
8 changes: 5 additions & 3 deletions http/java/okhttp/lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@ repositories {
dependencies {
// Use JUnit Jupiter API for testing.
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'

testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2'
// Use JUnit Jupiter Engine for testing.
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'

testImplementation 'org.mockito:mockito-inline:4.3.1'

// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:31.1-jre'
api 'com.squareup.okhttp3:okhttp:4.9.3'
api 'com.microsoft.kiota:kiota-abstractions:1.0.29'
api 'com.microsoft.kiota:kiota-abstractions:1.0.32'
}

publishing {
Expand All @@ -53,7 +55,7 @@ publishing {
publications {
gpr(MavenPublication) {
artifactId 'kiota-http-okhttplibrary'
version '1.0.17'
version '1.0.18'
from(components.java)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.microsoft.kiota.http.middleware.RedirectHandler;
import com.microsoft.kiota.http.middleware.RetryHandler;

import okhttp3.Interceptor;
import okhttp3.OkHttpClient;

Expand Down Expand Up @@ -37,6 +40,9 @@ public static OkHttpClient.Builder Create(@Nullable final Interceptor[] intercep
*/
@Nonnull
public static Interceptor[] CreateDefaultInterceptors() {
return new Interceptor[] {}; //TODO add the list of default interceptors when they are ready
return new Interceptor[] {
new RedirectHandler(),
new RetryHandler()
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.microsoft.kiota.store.BackingStoreFactory;
import com.microsoft.kiota.store.BackingStoreFactorySingleton;

import kotlin.OptIn;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
Expand Down Expand Up @@ -333,8 +334,9 @@ public void writeTo(BufferedSink sink) throws IOException {
for (final Map.Entry<String,String> header : requestInfo.headers.entrySet()) {
requestBuilder.addHeader(header.getKey(), header.getValue());
}

for(final RequestOption option : requestInfo.getRequestOptions()) {
requestBuilder.tag(option);
requestBuilder.tag(option.getType(), option);
}
return requestBuilder.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.microsoft.kiota.http.middleware;

import java.io.IOException;
import java.util.concurrent.ThreadLocalRandom;

import javax.annotation.Nonnull;

import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

/**
* DO NOT USE IN PRODUCTION
* interceptor that randomly fails the responses for unit testing purposes
*/
public class ChaosHandler implements Interceptor {
/**
* constant string being used
*/
private static final String RETRY_AFTER = "Retry-After";
/**
* Denominator for the failure rate (i.e. 1/X)
*/
private static final int failureRate = 3;
/**
* default value to return on retry after
*/
private static final String retryAfterValue = "10";
/**
* body to respond on failed requests
*/
private static final String responseBody = "{\"error\": {\"code\": \"TooManyRequests\",\"innerError\": {\"code\": \"429\",\"date\": \"2020-08-18T12:51:51\",\"message\": \"Please retry after\",\"request-id\": \"94fb3b52-452a-4535-a601-69e0a90e3aa2\",\"status\": \"429\"},\"message\": \"Please retry again later.\"}}";
/**
* Too many requests status code
*/
public static final int MSClientErrorCodeTooManyRequests = 429;

@Override
@Nonnull
public Response intercept(@Nonnull final Chain chain) throws IOException {
Request request = chain.request();

final int dice = ThreadLocalRandom.current().nextInt(1, Integer.MAX_VALUE);

if(dice % failureRate == 0) {
return new Response
.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(MSClientErrorCodeTooManyRequests)
.message("Too Many Requests")
.addHeader(RETRY_AFTER, retryAfterValue)
.body(ResponseBody.create(responseBody, MediaType.get("application/json")))
.build();
} else {
return chain.proceed(request);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package com.microsoft.kiota.http.middleware;

import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
import static java.net.HttpURLConnection.HTTP_SEE_OTHER;
import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT;
import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT;

import java.io.IOException;
import java.net.ProtocolException;

import javax.annotation.Nullable;
import javax.annotation.Nonnull;

import com.microsoft.kiota.http.middleware.options.RedirectHandlerOption;

import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

/**
* Middleware that determines whether a redirect information should be followed or not, and follows it if necessary.
*/
public class RedirectHandler implements Interceptor{

private RedirectHandlerOption mRedirectOption;

/**
* Initialize using default redirect options, default IShouldRedirect and max redirect value
*/
public RedirectHandler() {
this(null);
}

/**
* Initialize using custom redirect options.
* @param redirectOption pass instance of redirect options to be used
*/
public RedirectHandler(@Nullable final RedirectHandlerOption redirectOption) {
this.mRedirectOption = redirectOption;
if(redirectOption == null) {
this.mRedirectOption = new RedirectHandlerOption();
}
}

boolean isRedirected(Request request, Response response, int redirectCount, RedirectHandlerOption redirectOption) throws IOException {
// Check max count of redirects reached
if(redirectCount > redirectOption.maxRedirects()) return false;

// Location header empty then don't redirect
final String locationHeader = response.header("location");
if(locationHeader == null)
return false;

// If any of 301,302,303,307,308 then redirect
final int statusCode = response.code();
if(statusCode == HTTP_PERM_REDIRECT || //308
statusCode == HTTP_MOVED_PERM || //301
statusCode == HTTP_TEMP_REDIRECT || //307
statusCode == HTTP_SEE_OTHER || //303
statusCode == HTTP_MOVED_TEMP) //302
return true;

return false;
}

Request getRedirect(
final Request request,
final Response userResponse) throws ProtocolException {
String location = userResponse.header("Location");
if (location == null || location.length() == 0) return null;

// For relative URL in location header, the new url to redirect is relative to original request
if(location.startsWith("/")) {
if(request.url().toString().endsWith("/")) {
location = location.substring(1);
}
location = request.url() + location;
}

HttpUrl requestUrl = userResponse.request().url();

HttpUrl locationUrl = userResponse.request().url().resolve(location);

// Don't follow redirects to unsupported protocols.
if (locationUrl == null) return null;

// Most redirects don't include a request body.
Request.Builder requestBuilder = userResponse.request().newBuilder();

// When redirecting across hosts, drop all authentication headers. This
// is potentially annoying to the application layer since they have no
// way to retain them.
boolean sameScheme = locationUrl.scheme().equalsIgnoreCase(requestUrl.scheme());
boolean sameHost = locationUrl.host().toString().equalsIgnoreCase(requestUrl.host().toString());
if (!sameScheme || !sameHost) {
requestBuilder.removeHeader("Authorization");
}

// Response status code 303 See Other then POST changes to GET
if(userResponse.code() == HTTP_SEE_OTHER) {
requestBuilder.method("GET", null);
}

return requestBuilder.url(locationUrl).build();
}

// Intercept request and response made to network
@Override
@Nonnull
public Response intercept(@Nonnull final Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
int requestsCount = 1;

// Use should retry pass along with this request
RedirectHandlerOption redirectOption = request.tag(RedirectHandlerOption.class);
redirectOption = redirectOption != null ? redirectOption : this.mRedirectOption;

while(true) {
response = chain.proceed(request);
final boolean shouldRedirect = isRedirected(request, response, requestsCount, redirectOption)
&& redirectOption.shouldRedirect().shouldRedirect(response);
if(!shouldRedirect) break;

final Request followup = getRedirect(request, response);
if(followup != null) {
response.close();
request = followup;
requestsCount++;
}
}
return response;
}
}
Loading

0 comments on commit 4c377f1

Please sign in to comment.