Skip to content

Commit

Permalink
Azure Function Isolated Process Refinements (#5392)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib committed Sep 12, 2022
1 parent 0190045 commit df0fccd
Show file tree
Hide file tree
Showing 41 changed files with 895 additions and 562 deletions.
Expand Up @@ -11,6 +11,7 @@
<ItemGroup>
<InternalsVisibleTo Include="HotChocolate.AspNetCore.Tests" />
<InternalsVisibleTo Include="HotChocolate.AspNetCore.Tests.Utilities" />
<InternalsVisibleTo Include="HotChocolate.AzureFunctions.IsolatedProcess" />
</ItemGroup>

<ItemGroup>
Expand Down
@@ -0,0 +1 @@
<html></html>
Expand Up @@ -770,20 +770,13 @@ public async Task New_Query_With_Streams_3()
.Add(response)
.MatchInline(
@"Headers:
Content-Type: multipart/mixed; boundary=""-""; charset=utf-8
Content-Type: text/event-stream; charset=utf-8
-------------------------->
Status Code: OK
-------------------------->
---
Content-Type: application/json; charset=utf-8
{""data"":{},""hasNext"":true}
---
Content-Type: application/json; charset=utf-8
{""path"":[],""data"":{""__typename"":""Query""},""hasNext"":false}
-----
{""event"":""next"",""data"":{""data"":{},""hasNext"":true}}
{""event"":""next"",""data"":{""path"":[],""data"":{""__typename"":""Query""},""hasNext"":false}}
{""event"":""complete""}
");
}
}
Expand Down
@@ -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();
}
}
@@ -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;
}
@@ -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();
}

0 comments on commit df0fccd

Please sign in to comment.