Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Azure Function Isolated Process Refinements (#5392)
- Loading branch information
1 parent
0190045
commit df0fccd
Showing
41 changed files
with
895 additions
and
562 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
1 change: 1 addition & 0 deletions
1
src/HotChocolate/AspNetCore/src/AspNetCore/Resources/index.html
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 @@ | ||
<html></html> |
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
105 changes: 105 additions & 0 deletions
105
...e/AzureFunctions/src/HotChocolate.AzureFunctions.IsolatedProcess/AzureHeaderDictionary.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,105 @@ | ||
using System.Collections; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Azure.Functions.Worker.Http; | ||
using Microsoft.Extensions.Primitives; | ||
using Microsoft.Net.Http.Headers; | ||
|
||
namespace HotChocolate.AzureFunctions.IsolatedProcess; | ||
|
||
internal sealed class AzureHeaderDictionary : IHeaderDictionary | ||
{ | ||
private readonly HttpResponse _response; | ||
private readonly HttpResponseData _responseData; | ||
|
||
public AzureHeaderDictionary(HttpResponse response, HttpResponseData responseData) | ||
{ | ||
_response = response; | ||
_responseData = responseData; | ||
} | ||
|
||
public void Add(KeyValuePair<string, StringValues> item) | ||
{ | ||
_response.Headers.Add(item.Key, item.Value); | ||
_responseData.Headers.Add(item.Key, (IEnumerable<string>)item.Value); | ||
} | ||
|
||
public void Clear() | ||
{ | ||
_response.Headers.Clear(); | ||
_responseData.Headers.Clear(); | ||
} | ||
|
||
public bool Contains(KeyValuePair<string, StringValues> item) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
public void CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
public bool Remove(KeyValuePair<string, StringValues> item) | ||
{ | ||
var success = _response.Headers.Remove(item); | ||
_responseData.Headers.Remove(item.Key); | ||
return success; | ||
} | ||
|
||
public int Count => _response.Headers.Count; | ||
|
||
public bool IsReadOnly => _response.Headers.IsReadOnly; | ||
|
||
public void Add(string key, StringValues value) | ||
{ | ||
_response.Headers.Add(key, value); | ||
_responseData.Headers.Add(key, (IEnumerable<string>)value); | ||
} | ||
|
||
public bool ContainsKey(string key) | ||
=> _response.Headers.ContainsKey(key); | ||
|
||
public bool Remove(string key) | ||
{ | ||
var success = _response.Headers.Remove(key); | ||
_responseData.Headers.Remove(key); | ||
return success; | ||
} | ||
|
||
public bool TryGetValue(string key, out StringValues value) | ||
=> _response.Headers.TryGetValue(key, out value); | ||
|
||
public StringValues this[string key] | ||
{ | ||
get => _response.Headers[key]; | ||
set | ||
{ | ||
_response.Headers[key] = value; | ||
_responseData.Headers.Add(key, (IEnumerable<string>)value); | ||
} | ||
} | ||
|
||
public long? ContentLength | ||
{ | ||
get => _response.Headers.ContentLength; | ||
set | ||
{ | ||
_response.Headers.ContentLength = value; | ||
_responseData.Headers.Add( | ||
HeaderNames.ContentLength, | ||
(string?)_response.Headers[HeaderNames.ContentLength]); | ||
} | ||
} | ||
|
||
public ICollection<string> Keys => _response.Headers.Keys; | ||
|
||
public ICollection<StringValues> Values => _response.Headers.Values; | ||
|
||
public IEnumerator<KeyValuePair<string, StringValues>> GetEnumerator() | ||
=> _response.Headers.GetEnumerator(); | ||
|
||
IEnumerator IEnumerable.GetEnumerator() | ||
{ | ||
return GetEnumerator(); | ||
} | ||
} |
99 changes: 99 additions & 0 deletions
99
...colate/AzureFunctions/src/HotChocolate.AzureFunctions.IsolatedProcess/AzureHttpContext.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,99 @@ | ||
using System.Security.Claims; | ||
using HotChocolate.AspNetCore; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Http.Features; | ||
using Microsoft.Azure.Functions.Worker.Http; | ||
using Microsoft.Extensions.Primitives; | ||
using Microsoft.Net.Http.Headers; | ||
|
||
namespace HotChocolate.AzureFunctions.IsolatedProcess; | ||
|
||
internal sealed class AzureHttpContext : HttpContext | ||
{ | ||
private readonly DefaultHttpContext _innerContext; | ||
private readonly AzureHttpResponse _innerResponse; | ||
|
||
public AzureHttpContext(HttpRequestData requestData) | ||
{ | ||
if (requestData is null) | ||
{ | ||
throw new ArgumentNullException(nameof(requestData)); | ||
} | ||
|
||
_innerContext = new DefaultHttpContext(); | ||
_innerResponse = new AzureHttpResponse(_innerContext.Response, requestData); | ||
|
||
var contentType = | ||
requestData.Headers.TryGetValues(HeaderNames.ContentType, out var headerValue) | ||
? headerValue.First() | ||
: ContentType.Json; | ||
|
||
_innerContext.User = new ClaimsPrincipal(requestData.Identities); | ||
|
||
var request = _innerContext.Request; | ||
request.Method = requestData.Method; | ||
request.Scheme = requestData.Url.Scheme; | ||
request.Host = new HostString(requestData.Url.Host, requestData.Url.Port); | ||
request.Path = new PathString(requestData.Url.AbsolutePath); | ||
request.QueryString = new QueryString(requestData.Url.Query); | ||
request.Body = requestData.Body; | ||
request.ContentType = contentType; | ||
|
||
foreach (var (key, value) in requestData.Headers) | ||
{ | ||
request.Headers.TryAdd(key, new StringValues(value.ToArray())); | ||
} | ||
} | ||
|
||
public override IFeatureCollection Features => _innerContext.Features; | ||
|
||
public override HttpRequest Request => _innerContext.Request; | ||
|
||
public override HttpResponse Response => _innerResponse; | ||
|
||
public override ConnectionInfo Connection => _innerContext.Connection; | ||
|
||
public override WebSocketManager WebSockets => _innerContext.WebSockets; | ||
|
||
public override ClaimsPrincipal User | ||
{ | ||
get => _innerContext.User; | ||
set => _innerContext.User = value; | ||
} | ||
|
||
public override IDictionary<object, object?> Items | ||
{ | ||
get => _innerContext.Items; | ||
set => _innerContext.Items = value; | ||
} | ||
|
||
public override IServiceProvider RequestServices | ||
{ | ||
get => _innerContext.RequestServices; | ||
set => _innerContext.RequestServices = value; | ||
} | ||
|
||
public override CancellationToken RequestAborted | ||
{ | ||
get => _innerContext.RequestAborted; | ||
set => _innerContext.RequestAborted = value; | ||
} | ||
|
||
public override string TraceIdentifier | ||
{ | ||
get => _innerContext.TraceIdentifier; | ||
set => _innerContext.TraceIdentifier = value; | ||
} | ||
|
||
public override ISession Session | ||
{ | ||
get => _innerContext.Session; | ||
set => _innerContext.Session = value; | ||
} | ||
|
||
public override void Abort() | ||
=> _innerContext.Abort(); | ||
|
||
public HttpResponseData CreateResponseData() | ||
=> _innerResponse.ResponseData; | ||
} |
97 changes: 97 additions & 0 deletions
97
...olate/AzureFunctions/src/HotChocolate.AzureFunctions.IsolatedProcess/AzureHttpResponse.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,97 @@ | ||
using System.Net; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Azure.Functions.Worker.Http; | ||
using Microsoft.Net.Http.Headers; | ||
|
||
namespace HotChocolate.AzureFunctions.IsolatedProcess; | ||
|
||
internal sealed class AzureHttpResponse : HttpResponse | ||
{ | ||
private readonly HttpResponse _response; | ||
private readonly HttpRequestData _requestData; | ||
private readonly object _sync = new(); | ||
private HttpResponseData? _responseData; | ||
private AzureHeaderDictionary? _headers; | ||
|
||
public AzureHttpResponse(HttpResponse response, HttpRequestData requestData) | ||
{ | ||
_response = response; | ||
_requestData = requestData; | ||
} | ||
|
||
internal HttpResponseData ResponseData | ||
{ | ||
get | ||
{ | ||
if (_responseData is null) | ||
{ | ||
lock (_sync) | ||
{ | ||
if (_responseData is null) | ||
{ | ||
_responseData = _requestData.CreateResponse(); | ||
} | ||
} | ||
} | ||
|
||
return _responseData; | ||
} | ||
} | ||
|
||
public override HttpContext HttpContext => _response.HttpContext; | ||
|
||
public override int StatusCode | ||
{ | ||
get => (int)ResponseData.StatusCode; | ||
set => ResponseData.StatusCode = (HttpStatusCode)value; | ||
} | ||
|
||
public override IHeaderDictionary Headers | ||
{ | ||
get | ||
{ | ||
if (_headers is null) | ||
{ | ||
lock (_sync) | ||
{ | ||
if (_headers is null) | ||
{ | ||
_headers = new AzureHeaderDictionary(_response, ResponseData); | ||
} | ||
} | ||
} | ||
return _headers; | ||
} | ||
} | ||
|
||
public override Stream Body | ||
{ | ||
get => ResponseData.Body; | ||
set => ResponseData.Body = value; | ||
} | ||
|
||
public override long? ContentLength | ||
{ | ||
get => Headers.ContentLength; | ||
set => Headers.ContentLength = value; | ||
} | ||
|
||
public override string? ContentType | ||
{ | ||
get => Headers[HeaderNames.ContentType]; | ||
set => Headers[HeaderNames.ContentType] = value; | ||
} | ||
|
||
public override IResponseCookies Cookies => _response.Cookies; | ||
|
||
public override bool HasStarted => _response.HasStarted; | ||
|
||
public override void OnStarting(Func<object, Task> callback, object state) | ||
=> throw new NotSupportedException(); | ||
|
||
public override void OnCompleted(Func<object, Task> callback, object state) | ||
=> throw new NotSupportedException(); | ||
|
||
public override void Redirect(string location, bool permanent) | ||
=> throw new NotSupportedException(); | ||
} |
Oops, something went wrong.