-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add RequestOptions & handling in RestProxy #22334
Changes from all commits
6e64359
7373153
ba8aa05
3aaf0c4
3405650
548ad2c
9795241
0620138
fef0373
b0689bb
281629d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
package com.azure.core.http.rest; | ||
|
||
import com.azure.core.annotation.QueryParam; | ||
import com.azure.core.http.HttpHeader; | ||
import com.azure.core.http.HttpRequest; | ||
import com.azure.core.util.BinaryData; | ||
|
||
import java.util.function.Consumer; | ||
|
||
/** | ||
* This class contains the options to customize a HTTP request. {@link RequestOptions} can be | ||
* used to configure the request headers, query params, the request body, or add a callback | ||
* to modify all aspects of the HTTP request. | ||
* | ||
* <p> | ||
* An instance of fully configured {@link RequestOptions} can be passed to a service method that | ||
* preconfigures known components of the request like URL, path params etc, further modifying both | ||
* un-configured, or preconfigured components. | ||
* </p> | ||
* | ||
* <p> | ||
* To demonstrate how this class can be used to construct a request, let's use a Pet Store service as an example. The | ||
* list of APIs available on this service are <a href="https://petstore.swagger.io/#/pet">documented in the swagger definition.</a> | ||
* </p> | ||
* | ||
* <p><strong>Creating an instance of RequestOptions</strong></p> | ||
* {@codesnippet com.azure.core.http.rest.requestoptions.instantiation} | ||
* | ||
* <p><strong>Configuring the request with JSON body and making a HTTP POST request</strong></p> | ||
* To <a href="https://petstore.swagger.io/#/pet/addPet">add a new pet to the pet store</a>, a HTTP POST call should | ||
* be made to the service with the details of the pet that is to be added. The details of the pet are included as the | ||
* request body in JSON format. | ||
* | ||
* The JSON structure for the request is defined as follows: | ||
* <pre>{@code | ||
* { | ||
* "id": 0, | ||
* "category": { | ||
* "id": 0, | ||
* "name": "string" | ||
* }, | ||
* "name": "doggie", | ||
* "photoUrls": [ | ||
* "string" | ||
* ], | ||
* "tags": [ | ||
* { | ||
* "id": 0, | ||
* "name": "string" | ||
* } | ||
* ], | ||
* "status": "available" | ||
* } | ||
* }</pre> | ||
* | ||
* To create a concrete request, Json builder provided in javax package is used here for demonstration. However, any | ||
* other Json building library can be used to achieve similar results. | ||
* | ||
* {@codesnippet com.azure.core.http.rest.requestoptions.createjsonrequest} | ||
* | ||
* Now, this string representation of the JSON request can be set as body of RequestOptions | ||
* | ||
* {@codesnippet com.azure.core.http.rest.requestoptions.postrequest} | ||
*/ | ||
public final class RequestOptions { | ||
private Consumer<HttpRequest> requestCallback = request -> { }; | ||
private boolean throwOnError = true; | ||
private BinaryData requestBody; | ||
|
||
/** | ||
* Gets the request callback, applying all the configurations set on this RequestOptions. | ||
* @return the request callback | ||
*/ | ||
Consumer<HttpRequest> getRequestCallback() { | ||
return this.requestCallback; | ||
} | ||
|
||
/** | ||
* Gets whether or not to throw an exception when an HTTP response with a status code indicating an error | ||
* (400 or above) is received. | ||
* | ||
* @return true if to throw on status codes of 400 or above, false if not. Default is true. | ||
*/ | ||
boolean isThrowOnError() { | ||
return this.throwOnError; | ||
} | ||
|
||
/** | ||
* Adds a header to the HTTP request. | ||
* @param header the header key | ||
* @param value the header value | ||
* | ||
* @return the modified RequestOptions object | ||
*/ | ||
public RequestOptions addHeader(String header, String value) { | ||
this.requestCallback = this.requestCallback.andThen(request -> { | ||
HttpHeader httpHeader = request.getHeaders().get(header); | ||
if (httpHeader == null) { | ||
request.getHeaders().set(header, value); | ||
} else { | ||
httpHeader.addValue(value); | ||
} | ||
}); | ||
return this; | ||
} | ||
|
||
/** | ||
* Adds a query parameter to the request URL. The parameter name and value will be URL encoded. | ||
* To use an already encoded parameter name and value, call {@code addQueryParam("name", "value", true)}. | ||
* | ||
* @param parameterName the name of the query parameter | ||
* @param value the value of the query parameter | ||
* @return the modified RequestOptions object | ||
*/ | ||
public RequestOptions addQueryParam(String parameterName, String value) { | ||
return addQueryParam(parameterName, value, false); | ||
} | ||
|
||
/** | ||
* Adds a query parameter to the request URL, specifying whether the parameter is already encoded. | ||
* A value true for this argument indicates that value of {@link QueryParam#value()} is already encoded | ||
* hence engine should not encode it, by default value will be encoded. | ||
* | ||
* @param parameterName the name of the query parameter | ||
* @param value the value of the query parameter | ||
* @param encoded whether or not this query parameter is already encoded | ||
* @return the modified RequestOptions object | ||
*/ | ||
public RequestOptions addQueryParam(String parameterName, String value, boolean encoded) { | ||
this.requestCallback = this.requestCallback.andThen(request -> { | ||
String url = request.getUrl().toString(); | ||
String encodedParameterName = encoded ? parameterName : UrlEscapers.QUERY_ESCAPER.escape(parameterName); | ||
String encodedParameterValue = encoded ? value : UrlEscapers.QUERY_ESCAPER.escape(value); | ||
request.setUrl(url + (url.contains("?") ? "&" : "?") + encodedParameterName + "=" + encodedParameterValue); | ||
}); | ||
return this; | ||
} | ||
|
||
/** | ||
* Adds a custom request callback to modify the HTTP request before it's sent by the HttpClient. | ||
* The modifications made on a RequestOptions object is applied in order on the request. | ||
* | ||
* @param requestCallback the request callback | ||
* @return the modified RequestOptions object | ||
*/ | ||
public RequestOptions addRequestCallback(Consumer<HttpRequest> requestCallback) { | ||
this.requestCallback = this.requestCallback.andThen(requestCallback); | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the body to send as part of the HTTP request. | ||
* @param requestBody the request body data | ||
* @return the modified RequestOptions object | ||
*/ | ||
public RequestOptions setBody(BinaryData requestBody) { | ||
this.requestCallback = this.requestCallback.andThen(request -> { | ||
request.setBody(requestBody.toBytes()); | ||
}); | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets whether or not to throw an exception when an HTTP response with a status code indicating an error | ||
* (400 or above) is received. By default an exception will be thrown when an error response is received. | ||
* | ||
* @param throwOnError true if to throw on status codes of 400 or above, false if not. Default is true. | ||
* @return the modified RequestOptions object | ||
*/ | ||
public RequestOptions setThrowOnError(boolean throwOnError) { | ||
this.throwOnError = throwOnError; | ||
return this; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -347,6 +347,16 @@ public Context setContext(Object[] swaggerMethodArguments) { | |
return (context != null) ? context : Context.NONE; | ||
} | ||
|
||
/** | ||
* Get the {@link RequestOptions} passed into the proxy method. | ||
* | ||
* @param swaggerMethodArguments the arguments passed to the proxy method | ||
* @return the request options | ||
*/ | ||
public RequestOptions setRequestOptions(Object[] swaggerMethodArguments) { | ||
return CoreUtils.findFirstOfType(swaggerMethodArguments, RequestOptions.class); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an ancillary performance question, does the Java proxy interface pass the Object[] in a consistent order? I'm wondering if there could be an optimization for this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AutoRest generated code does, but I'm not sure if azure-core could make the assumption that all code follows the same order. However, a |
||
} | ||
|
||
/** | ||
* Get whether or not the provided response status code is one of the expected status codes for this Swagger | ||
* method. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
package com.azure.core.http.rest; | ||
|
||
import com.azure.core.http.HttpMethod; | ||
import com.azure.core.util.BinaryData; | ||
|
||
import javax.json.Json; | ||
import javax.json.JsonArray; | ||
import javax.json.JsonObject; | ||
|
||
/** | ||
* JavaDoc code snippets for {@link RequestOptions}. | ||
*/ | ||
public class RequestOptionsJavaDocCodeSnippets { | ||
|
||
/** | ||
* Sample to demonstrate how to create an instance of {@link RequestOptions}. | ||
* @return An instance of {@link RequestOptions}. | ||
*/ | ||
public RequestOptions createInstance() { | ||
// BEGIN: com.azure.core.http.rest.requestoptions.instantiation | ||
RequestOptions options = new RequestOptions() | ||
.setBody(BinaryData.fromString("{\"name\":\"Fluffy\"}")) | ||
.addHeader("x-ms-pet-version", "2021-06-01"); | ||
// END: com.azure.core.http.rest.requestoptions.instantiation | ||
return options; | ||
} | ||
|
||
/** | ||
* Sample to demonstrate setting the JSON request body in a {@link RequestOptions}. | ||
* @return An instance of {@link RequestOptions}. | ||
*/ | ||
public RequestOptions setJsonRequestBodyInRequestOptions() { | ||
// BEGIN: com.azure.core.http.rest.requestoptions.createjsonrequest | ||
JsonArray photoUrls = Json.createArrayBuilder() | ||
.add("https://imgur.com/pet1") | ||
.add("https://imgur.com/pet2") | ||
.build(); | ||
|
||
JsonArray tags = Json.createArrayBuilder() | ||
.add(Json.createObjectBuilder() | ||
.add("id", 0) | ||
.add("name", "Labrador") | ||
.build()) | ||
.add(Json.createObjectBuilder() | ||
.add("id", 1) | ||
.add("name", "2021") | ||
.build()) | ||
.build(); | ||
|
||
JsonObject requestBody = Json.createObjectBuilder() | ||
.add("id", 0) | ||
.add("name", "foo") | ||
.add("status", "available") | ||
.add("category", Json.createObjectBuilder().add("id", 0).add("name", "dog")) | ||
.add("photoUrls", photoUrls) | ||
.add("tags", tags) | ||
.build(); | ||
|
||
String requestBodyStr = requestBody.toString(); | ||
// END: com.azure.core.http.rest.requestoptions.createjsonrequest | ||
|
||
// BEGIN: com.azure.core.http.rest.requestoptions.postrequest | ||
RequestOptions options = new RequestOptions() | ||
.addRequestCallback(request -> request | ||
// may already be set if request is created from a client | ||
.setUrl("https://petstore.example.com/pet") | ||
.setHttpMethod(HttpMethod.POST) | ||
.setBody(requestBodyStr) | ||
.setHeader("Content-Type", "application/json")); | ||
// END: com.azure.core.http.rest.requestoptions.postrequest | ||
return options; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once it is merge this should use
RequestContent
. Right now, this is creating another copy of the underlyingBinaryData
data.