-
Notifications
You must be signed in to change notification settings - Fork 272
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
HTTP APIs for .NET Isolated Parity (#2653)
This PR adds HTTP API parity (CallHttpAsync()) for .NET Isolated. Changes include: New DurableHttpRequest and DurableHttpResponse types in the Worker project New TaskOrchestrationContext extension method for CallHttpAsync() Additional changes in OutOfProcMiddleware to execute the BuiltIn::HttpActivity activity function by using the existing TaskHttpActivityShim (BuiltIn::HttpActivity is the reserved name to know when a TaskActivity should be an HTTP activity).
- Loading branch information
Showing
9 changed files
with
373 additions
and
1 deletion.
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
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
72 changes: 72 additions & 0 deletions
72
src/Worker.Extensions.DurableTask/HTTP/DurableHttpRequest.cs
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,72 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Net.Http; | ||
using System.Text.Json.Serialization; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Http; | ||
|
||
/// <summary> | ||
/// Request used to make an HTTP call through Durable Functions. | ||
/// </summary> | ||
public class DurableHttpRequest | ||
{ | ||
/// <summary> | ||
/// Initializes a new instance of the <see cref="DurableHttpRequest"/> class. | ||
/// </summary> | ||
public DurableHttpRequest(HttpMethod method, Uri uri) | ||
{ | ||
this.Method = method; | ||
this.Uri = uri; | ||
} | ||
|
||
/// <summary> | ||
/// HttpMethod used in the HTTP request made by the Durable Function. | ||
/// </summary> | ||
[JsonPropertyName("method")] | ||
[JsonConverter(typeof(HttpMethodConverter))] | ||
public HttpMethod Method { get; } | ||
|
||
/// <summary> | ||
/// Uri used in the HTTP request made by the Durable Function. | ||
/// </summary> | ||
[JsonPropertyName("uri")] | ||
public Uri Uri { get; } | ||
|
||
/// <summary> | ||
/// Headers passed with the HTTP request made by the Durable Function. | ||
/// </summary> | ||
[JsonPropertyName("headers")] | ||
[JsonConverter(typeof(HttpHeadersConverter))] | ||
public IDictionary<string, StringValues>? Headers { get; set; } | ||
|
||
/// <summary> | ||
/// Content passed with the HTTP request made by the Durable Function. | ||
/// </summary> | ||
[JsonPropertyName("content")] | ||
public string? Content { get; set; } | ||
|
||
/// <summary> | ||
/// Specifies whether the Durable HTTP APIs should automatically | ||
/// handle the asynchronous HTTP pattern. | ||
/// </summary> | ||
[JsonPropertyName("asynchronousPatternEnabled")] | ||
public bool AsynchronousPatternEnabled { get; set; } | ||
|
||
/// <summary> | ||
/// Defines retry policy for handling of failures in making the HTTP Request. These could be non-successful HTTP status codes | ||
/// in the response, a timeout in making the HTTP call, or an exception raised from the HTTP Client library. | ||
/// </summary> | ||
[JsonPropertyName("retryOptions")] | ||
public HttpRetryOptions? HttpRetryOptions { get; set; } | ||
|
||
/// <summary> | ||
/// The total timeout for the original HTTP request and any | ||
/// asynchronous polling. | ||
/// </summary> | ||
[JsonPropertyName("timeout")] | ||
public TimeSpan? Timeout { get; set; } | ||
} |
43 changes: 43 additions & 0 deletions
43
src/Worker.Extensions.DurableTask/HTTP/DurableHttpResponse.cs
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) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using System.Collections.Generic; | ||
using System.Net; | ||
using System.Text.Json.Serialization; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Http; | ||
|
||
/// <summary> | ||
/// Response received from the HTTP request made by the Durable Function. | ||
/// </summary> | ||
public class DurableHttpResponse | ||
{ | ||
/// <summary> | ||
/// Initializes a new instance of the <see cref="DurableHttpResponse"/> class. | ||
/// </summary> | ||
/// <param name="statusCode">HTTP Status code returned from the HTTP call.</param> | ||
public DurableHttpResponse(HttpStatusCode statusCode) | ||
{ | ||
this.StatusCode = statusCode; | ||
} | ||
|
||
/// <summary> | ||
/// Status code returned from an HTTP request. | ||
/// </summary> | ||
[JsonPropertyName("statusCode")] | ||
public HttpStatusCode StatusCode { get; } | ||
|
||
/// <summary> | ||
/// Headers in the response from an HTTP request. | ||
/// </summary> | ||
[JsonPropertyName("headers")] | ||
[JsonConverter(typeof(HttpHeadersConverter))] | ||
public IDictionary<string, StringValues>? Headers { get; init; } | ||
|
||
/// <summary> | ||
/// Content returned from an HTTP request. | ||
/// </summary> | ||
[JsonPropertyName("content")] | ||
public string? Content { get; init; } | ||
} |
84 changes: 84 additions & 0 deletions
84
src/Worker.Extensions.DurableTask/HTTP/HttpHeadersConverter.cs
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) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See LICENSE in the project root for license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Http; | ||
|
||
// StringValues does not deserialize as you would expect, so we need a custom mechanism | ||
// for serializing HTTP header collections | ||
internal class HttpHeadersConverter : JsonConverter<IDictionary<string, StringValues>> | ||
{ | ||
public override IDictionary<string, StringValues> Read( | ||
ref Utf8JsonReader reader, | ||
Type objectType, | ||
JsonSerializerOptions options) | ||
{ | ||
var headers = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase); | ||
|
||
if (reader.TokenType != JsonTokenType.StartObject) | ||
{ | ||
return headers; | ||
} | ||
|
||
var valueList = new List<string>(); | ||
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) | ||
{ | ||
string? propertyName = reader.GetString(); | ||
|
||
reader.Read(); | ||
|
||
// Header values can be either individual strings or string arrays | ||
StringValues values = default(StringValues); | ||
if (reader.TokenType == JsonTokenType.String) | ||
{ | ||
values = new StringValues(reader.GetString()); | ||
} | ||
else if (reader.TokenType == JsonTokenType.StartArray) | ||
{ | ||
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) | ||
{ | ||
valueList.Add(reader.GetString()); | ||
} | ||
|
||
values = new StringValues(valueList.ToArray()); | ||
valueList.Clear(); | ||
} | ||
|
||
headers[propertyName] = values; | ||
} | ||
|
||
return headers; | ||
} | ||
|
||
public override void Write( | ||
Utf8JsonWriter writer, | ||
IDictionary<string, StringValues> value, | ||
JsonSerializerOptions options) | ||
{ | ||
writer.WriteStartObject(); | ||
|
||
var headers = (IDictionary<string, StringValues>)value; | ||
foreach (var pair in headers) | ||
{ | ||
if (pair.Value.Count == 1) | ||
{ | ||
// serialize as a single string value | ||
writer.WriteString(pair.Key, pair.Value[0]); | ||
} | ||
else | ||
{ | ||
// serializes as an array | ||
writer.WriteStartArray(pair.Key); | ||
writer.WriteStringValue(pair.Value); | ||
writer.WriteEndArray(); | ||
} | ||
} | ||
|
||
writer.WriteEndObject(); | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
src/Worker.Extensions.DurableTask/HTTP/HttpMethodConverter.cs
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,33 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Net.Http; | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
|
||
namespace Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Http; | ||
|
||
internal class HttpMethodConverter : JsonConverter<HttpMethod> | ||
{ | ||
public override bool CanConvert(Type objectType) | ||
{ | ||
return typeof(HttpMethod).IsAssignableFrom(objectType); | ||
} | ||
|
||
public override HttpMethod Read( | ||
ref Utf8JsonReader reader, | ||
Type objectType, | ||
JsonSerializerOptions options) | ||
{ | ||
return new HttpMethod(reader.GetString()); | ||
} | ||
|
||
public override void Write( | ||
Utf8JsonWriter writer, | ||
HttpMethod value, | ||
JsonSerializerOptions options) | ||
{ | ||
writer.WriteStringValue(value.ToString()); | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
src/Worker.Extensions.DurableTask/HTTP/HttpRetryOptions.cs
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,81 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Net; | ||
using System.Text.Json.Serialization; | ||
|
||
namespace Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Http; | ||
|
||
/// <summary> | ||
/// Defines retry policies that can be passed as parameters to various operations. | ||
/// </summary> | ||
public class HttpRetryOptions | ||
{ | ||
// Would like to make this durability provider specific, but since this is a developer | ||
// facing type, that is difficult. | ||
private static readonly TimeSpan DefaultMaxRetryinterval = TimeSpan.FromDays(6); | ||
|
||
/// <summary> | ||
/// Creates a new instance SerializableRetryOptions with the supplied first retry and max attempts. | ||
/// </summary> | ||
public HttpRetryOptions(IList<HttpStatusCode>? statusCodesToRetry = null) | ||
{ | ||
this.StatusCodesToRetry = statusCodesToRetry ?? new List<HttpStatusCode>(); | ||
} | ||
|
||
/// <summary> | ||
/// Gets or sets the first retry interval. | ||
/// </summary> | ||
/// <value> | ||
/// The TimeSpan to wait for the first retries. | ||
/// </value> | ||
[JsonPropertyName("FirstRetryInterval")] | ||
public TimeSpan FirstRetryInterval { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the max retry interval. | ||
/// </summary> | ||
/// <value> | ||
/// The TimeSpan of the max retry interval, defaults to 6 days. | ||
/// </value> | ||
[JsonPropertyName("MaxRetryInterval")] | ||
public TimeSpan MaxRetryInterval { get; set; } = DefaultMaxRetryinterval; | ||
|
||
/// <summary> | ||
/// Gets or sets the backoff coefficient. | ||
/// </summary> | ||
/// <value> | ||
/// The backoff coefficient used to determine rate of increase of backoff. Defaults to 1. | ||
/// </value> | ||
[JsonPropertyName("BackoffCoefficient")] | ||
public double BackoffCoefficient { get; set; } = 1; | ||
|
||
/// <summary> | ||
/// Gets or sets the timeout for retries. | ||
/// </summary> | ||
/// <value> | ||
/// The TimeSpan timeout for retries, defaults to <see cref="TimeSpan.MaxValue"/>. | ||
/// </value> | ||
[JsonPropertyName("RetryTimeout")] | ||
public TimeSpan RetryTimeout { get; set; } = TimeSpan.MaxValue; | ||
|
||
/// <summary> | ||
/// Gets or sets the max number of attempts. | ||
/// </summary> | ||
/// <value> | ||
/// The maximum number of retry attempts. | ||
/// </value> | ||
[JsonPropertyName("MaxNumberOfAttempts")] | ||
public int MaxNumberOfAttempts { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the list of status codes upon which the | ||
/// retry logic specified by this object shall be triggered. | ||
/// If none are provided, all 4xx and 5xx status codes | ||
/// will be retried. | ||
/// </summary> | ||
[JsonPropertyName("StatusCodesToRetry")] | ||
public IList<HttpStatusCode> StatusCodesToRetry { get; } | ||
} |
52 changes: 52 additions & 0 deletions
52
src/Worker.Extensions.DurableTask/TaskOrchestrationContextExtensionMethods.cs
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,52 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
using Microsoft.Azure.Functions.Worker.Extensions.DurableTask; | ||
using Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Http; | ||
|
||
namespace Microsoft.DurableTask; | ||
|
||
/// <summary> | ||
/// Extensions for <see cref="TaskOrchestrationContext"/>. | ||
/// </summary> | ||
public static class TaskOrchestrationContextExtensionMethods | ||
{ | ||
/// <summary> | ||
/// Makes an HTTP call using the information in the DurableHttpRequest. | ||
/// </summary> | ||
/// <param name="context">The task orchestration context.</param> | ||
/// <param name="request">The DurableHttpRequest used to make the HTTP call.</param> | ||
/// <returns>DurableHttpResponse</returns> | ||
public static Task<DurableHttpResponse> CallHttpAsync(this TaskOrchestrationContext context, DurableHttpRequest request) | ||
{ | ||
if (context is null) | ||
{ | ||
throw new ArgumentNullException(nameof(context)); | ||
} | ||
|
||
return context.CallActivityAsync<DurableHttpResponse>(Constants.HttpTaskActivityReservedName, request); | ||
} | ||
|
||
/// <summary> | ||
/// Makes an HTTP call to the specified uri. | ||
/// </summary> | ||
/// <param name="context">The task orchestration context.</param> | ||
/// <param name="method">HttpMethod used for api call.</param> | ||
/// <param name="uri">uri used to make the HTTP call.</param> | ||
/// <param name="content">Content passed in the HTTP request.</param> | ||
/// <param name="retryOptions">The retry option for the HTTP task.</param> | ||
/// <returns>A <see cref="Task{DurableHttpResponse}"/>Result of the HTTP call.</returns> | ||
public static Task<DurableHttpResponse> CallHttpAsync(this TaskOrchestrationContext context, HttpMethod method, Uri uri, string? content = null, HttpRetryOptions? retryOptions = null) | ||
{ | ||
DurableHttpRequest request = new DurableHttpRequest(method, uri) | ||
{ | ||
Content = content, | ||
HttpRetryOptions = retryOptions, | ||
}; | ||
|
||
return context.CallHttpAsync(request); | ||
} | ||
} |
Oops, something went wrong.