Skip to content

Commit

Permalink
Merge pull request Azure#198 from daschult/MakeHttpClientAsync
Browse files Browse the repository at this point in the history
Make http client async
  • Loading branch information
Dan Schulte authored Aug 18, 2017
2 parents 856a8f6 + c8594b9 commit d86700f
Show file tree
Hide file tree
Showing 13 changed files with 784 additions and 101 deletions.
117 changes: 103 additions & 14 deletions client-runtime/src/main/java/com/microsoft/rest/v2/RestProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.microsoft.rest.v2.annotations.GET;
import com.microsoft.rest.v2.annotations.HEAD;
import com.microsoft.rest.v2.annotations.HeaderParam;
import com.microsoft.rest.v2.annotations.Headers;
import com.microsoft.rest.v2.annotations.Host;
import com.microsoft.rest.v2.annotations.HostParam;
import com.microsoft.rest.v2.annotations.PATCH;
Expand All @@ -21,11 +22,17 @@
import com.microsoft.rest.v2.annotations.PathParam;
import com.microsoft.rest.v2.annotations.QueryParam;
import com.microsoft.rest.v2.http.HttpClient;
import com.microsoft.rest.v2.http.HttpHeader;
import com.microsoft.rest.v2.http.HttpRequest;
import com.microsoft.rest.v2.http.HttpResponse;
import com.microsoft.rest.v2.http.OkHttpClient;
import com.microsoft.rest.v2.http.UrlBuilder;
import rx.Completable;
import rx.Observable;
import rx.Single;
import rx.functions.Func1;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
Expand All @@ -51,7 +58,7 @@ private RestProxy(String host, HttpClient httpClient, SerializerAdapter<?> seria
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
public Object invoke(Object proxy, final Method method, Object[] args) throws Throwable {
final String methodName = method.getName();
final SwaggerMethodProxyDetails methodDetails = interfaceDetails.getMethodProxyDetails(methodName);

Expand All @@ -75,6 +82,10 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
request.withHeader(headerParameter.name(), headerParameter.encodedValue());
}

for (final HttpHeader header : methodDetails.getHeaders()) {
request.withHeader(header.getName(), header.getValue());
}

final Integer bodyContentMethodParameterIndex = methodDetails.bodyContentMethodParameterIndex();
if (bodyContentMethodParameterIndex != null) {
final Object bodyContentObject = args[bodyContentMethodParameterIndex];
Expand All @@ -84,19 +95,61 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
}
}

final HttpResponse response = httpClient.sendRequest(request);

final Class<?> returnType = method.getReturnType();
if (returnType.equals(Void.TYPE) || !response.hasBody()) {
return null;
} else if (returnType.isAssignableFrom(InputStream.class)) {
return response.bodyAsInputStream();
} else if (returnType.isAssignableFrom(byte[].class)) {
return response.bodyAsByteArray();
} else {
final String responseBodyString = response.bodyAsString();
return serializer.deserialize(responseBodyString, returnType);
Object result = null;
if (!methodDetails.isAsync()) {
final HttpResponse response = httpClient.sendRequest(request);

final Class<?> returnType = methodDetails.getReturnType();
if (returnType.equals(Void.TYPE) || !response.hasBody() || methodDetails.method().equalsIgnoreCase("HEAD")) {
result = null;
} else if (returnType.isAssignableFrom(InputStream.class)) {
result = response.bodyAsInputStream();
} else if (returnType.isAssignableFrom(byte[].class)) {
result = response.bodyAsByteArray();
} else {
final String responseBodyString = response.bodyAsString();
result = serializer.deserialize(responseBodyString, returnType);
}
}
else {
final Single<? extends HttpResponse> asyncResponse = httpClient.sendRequestAsync(request);
final Class<?> methodReturnType = method.getReturnType();
if (methodReturnType.equals(Single.class)) {
result = asyncResponse.flatMap(new Func1<HttpResponse, Single<?>>() {
@Override
public Single<?> call(HttpResponse response) {
Single<?> asyncResult;
final Class<?> singleReturnType = methodDetails.getReturnType();
if (methodDetails.method().equalsIgnoreCase("HEAD")) {
asyncResult = Single.just(null);
} else if (singleReturnType.isAssignableFrom(InputStream.class)) {
asyncResult = response.bodyAsInputStreamAsync();
} else if (singleReturnType.isAssignableFrom(byte[].class)) {
asyncResult = response.bodyAsByteArrayAsync();
} else {
final Single<String> asyncResponseBodyString = response.bodyAsStringAsync();
asyncResult = asyncResponseBodyString.flatMap(new Func1<String, Single<Object>>() {
@Override
public Single<Object> call(String responseBodyString) {
try {
return Single.just(serializer.deserialize(responseBodyString, singleReturnType));
}
catch (IOException e) {
return Single.error(e);
}
}
});
}
return asyncResult;
}
});
}
else if (method.getReturnType().equals(Completable.class)) {
result = Completable.fromSingle(asyncResponse);
}
}

return result;
}

/**
Expand Down Expand Up @@ -132,7 +185,8 @@ public static <A> A create(Class<A> swaggerInterface, HttpClient httpClient, Ser
host = hostAnnotation.value();
}

for (Method method : swaggerInterface.getDeclaredMethods()) {
final Method[] declaredMethods = swaggerInterface.getDeclaredMethods();
for (Method method : declaredMethods) {
final SwaggerMethodProxyDetails methodProxyDetails = interfaceProxyDetails.getMethodProxyDetails(method.getName());

if (method.isAnnotationPresent(GET.class)) {
Expand All @@ -154,6 +208,23 @@ else if (method.isAnnotationPresent(PATCH.class)) {
methodProxyDetails.setMethodAndRelativePath("PATCH", method.getAnnotation(PATCH.class).value());
}

if (method.isAnnotationPresent(Headers.class)) {
final Headers headersAnnotation = method.getAnnotation(Headers.class);
final String[] headers = headersAnnotation.value();
for (final String header : headers) {
final int colonIndex = header.indexOf(":");
if (colonIndex >= 0) {
final String headerName = header.substring(0, colonIndex).trim();
if (!headerName.isEmpty()) {
final String headerValue = header.substring(colonIndex + 1).trim();
if (!headerValue.isEmpty()) {
methodProxyDetails.addHeader(headerName, headerValue);
}
}
}
}
}

final Annotation[][] allParametersAnnotations = method.getParameterAnnotations();
for (int parameterIndex = 0; parameterIndex < allParametersAnnotations.length; ++parameterIndex) {
final Annotation[] parameterAnnotations = method.getParameterAnnotations()[parameterIndex];
Expand All @@ -180,6 +251,24 @@ else if (annotationType.equals(BodyParam.class)) {
}
}
}

final Class<?> returnType = method.getReturnType();
final boolean isAsync = (returnType == Single.class || returnType == Completable.class || returnType == Observable.class);
methodProxyDetails.setIsAsync(isAsync);
if (!isAsync) {
methodProxyDetails.setReturnType(returnType);
}
else {
final String asyncMethodName = method.getName();
final String syncMethodName = asyncMethodName.endsWith("Async") ? asyncMethodName.substring(0, asyncMethodName.length() - 5) : asyncMethodName;

for (Method possibleSyncMethod : declaredMethods) {
if (possibleSyncMethod.getName().equalsIgnoreCase(syncMethodName) && possibleSyncMethod.getReturnType() != Single.class) {
methodProxyDetails.setReturnType(possibleSyncMethod.getReturnType());
break;
}
}
}
}

RestProxy restProxy = new RestProxy(host, httpClient, serializer, interfaceProxyDetails);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

package com.microsoft.rest.v2;

import com.microsoft.rest.v2.http.HttpHeader;
import com.microsoft.rest.v2.http.HttpHeaders;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
Expand All @@ -22,7 +25,10 @@ class SwaggerMethodProxyDetails {
private final List<Substitution> pathSubstitutions = new ArrayList<>();
private final List<Substitution> querySubstitutions = new ArrayList<>();
private final List<Substitution> headerSubstitutions = new ArrayList<>();
private final HttpHeaders headers = new HttpHeaders();
private Integer bodyContentMethodParameterIndex;
private boolean isAsync;
private Class<?> returnType;

/**
* Get the HTTP method that will be used to complete the Swagger method's request.
Expand Down Expand Up @@ -175,6 +181,62 @@ public Integer bodyContentMethodParameterIndex() {
return bodyContentMethodParameterIndex;
}

/**
* Add the provided headerName and headerValue to the list of static headers to associated with
* the HTTP request.
* @param headerName The name of the header.
* @param headerValue The value of the header.
*/
public void addHeader(String headerName, String headerValue) {
headers.add(headerName, headerValue);
}

/**
* Get the static headers that have been added to the HTTP request.
* @return The static headers that have been added to the HTTP request.
*/
public Iterable<HttpHeader> getHeaders() {
return headers;
}

/**
* Set whether or not this object describes an asynchronous method.
* @param isAsync Whether or not this object describes an asynchronous method.
*/
public void setIsAsync(boolean isAsync) {
this.isAsync = isAsync;
}

/**
* Get whether or not this object describes an asynchronous method.
* @return Whether or not this object describes an asynchronous method.
*/
public boolean isAsync() {
return isAsync;
}

/**
* Set the synchronous return type for the method that this object describes. If the method is
* asynchronous, then returnType is the type of value that is returned when then asynchronous
* operation finishes. In other words, returnType is the parameterized type of the Single object
* that is returned from the method.
* @param returnType The synchronous return type for the method that this object describes.
*/
public void setReturnType(Class<?> returnType) {
this.returnType = returnType;
}

/**
* Get the synchronous return type for the method that this object describes. If the method is
* asynchronous, then the type of value that is returned when then asynchronous operation
* finishes will be returned. In other words, returnType is the parameterized type of the Single
* object that is returned from the method.
* @return The synchronous return type for the method that this object describes.
*/
public Class<?> getReturnType() {
return returnType;
}

private static String applySubstitutions(String originalValue, Iterable<Substitution> substitutions, Object[] methodArguments) {
String result = originalValue;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,30 @@

package com.microsoft.rest.v2.http;

import rx.Single;

import java.io.IOException;

/**
* A generic interface for sending HTTP requests and getting responses.
*/
public interface HttpClient {
public abstract class HttpClient {
/**
* Send the provided request and block until the response is received.
* @param request The HTTP request to send.
* @return The HTTP response received.
* @throws IOException On network issues.
*/
public HttpResponse sendRequest(HttpRequest request) throws IOException {
final Single<? extends HttpResponse> asyncResult = sendRequestAsync(request);
return asyncResult.toBlocking().value();
}

/**
* Send the provided request and block until the response is received.
* @param request The HTTP request to send.
* @return The HTTP response received.
* @throws IOException On network issues.
*/
HttpResponse sendRequest(HttpRequest request) throws IOException;
public abstract Single<? extends HttpResponse> sendRequestAsync(HttpRequest request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/

package com.microsoft.rest.v2.http;

/**
* A single header within a HTTP request. If multiple header values are added to a HTTP request with
* the same name (case-insensitive), then the values will be appended to the end of the same Header
* with commas separating them.
*/
public class HttpHeader {
private final String name;
private String value;

/**
* Create a new HttpHeader using the provided name and value.
* @param name The name of the HttpHeader.
* @param value The value of the HttpHeader.
*/
public HttpHeader(String name, String value) {
this.name = name;
this.value = value;
}

/**
* Get the name of this Header.
* @return The name of this Header.
*/
public String getName() {
return name;
}

/**
* Get the value of this Header.
* @return The value of this Header.
*/
public String getValue() {
return value;
}

/**
* Add another value to the end of this Header.
* @param value The value to add to the end of this Header.
*/
public void addValue(String value) {
value += "," + value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/

package com.microsoft.rest.v2.http;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
* A collection of headers that will be applied to a HTTP request.
*/
public class HttpHeaders implements Iterable<HttpHeader> {
private final Map<String, HttpHeader> headers = new HashMap<>();

/**
* Add the provided headerName and headerValue to the list of headers for this request.
* @param headerName The name of the header.
* @param headerValue The value of the header.
* @return This HttpRequest so that multiple operations can be chained together.
*/
public HttpHeaders add(String headerName, String headerValue) {
final String headerKey = headerName.toLowerCase();
if (!headers.containsKey(headerKey)) {
headers.put(headerKey, new HttpHeader(headerName, headerValue));
}
else {
headers.get(headerKey).addValue(headerValue);
}
return this;
}

@Override
public Iterator<HttpHeader> iterator() {
return headers.values().iterator();
}
}
Loading

0 comments on commit d86700f

Please sign in to comment.