Skip to content

Commit

Permalink
Change WPS ConnectionContext.States to a Dict<string, BinaryData> (#2…
Browse files Browse the repository at this point in the history
  • Loading branch information
tg-msft authored Dec 6, 2021
1 parent e662d76 commit dc94809
Show file tree
Hide file tree
Showing 34 changed files with 888 additions and 305 deletions.
2 changes: 1 addition & 1 deletion eng/Packages.Data.props
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
<PackageReference Update="Azure.Messaging.EventHubs" Version="5.6.2" />
<PackageReference Update="Azure.Messaging.EventGrid" Version="4.7.0" />
<PackageReference Update="Azure.Messaging.ServiceBus" Version="7.5.0" />
<PackageReference Update="Azure.Messaging.WebPubSub" Version="1.0.0-beta.2" />
<PackageReference Update="Azure.Messaging.WebPubSub" Version="1.0.0" />
<PackageReference Update="Azure.Identity" Version="1.5.0" />
<PackageReference Update="Azure.Security.KeyVault.Secrets" Version="4.2.0" />
<PackageReference Update="Azure.Security.KeyVault.Keys" Version="4.2.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
# Release History

## 1.1.0-beta.1 (Unreleased)

### Features Added

### Breaking Changes
## 1.1.0 (2021-11-24)

### Bugs Fixed
- Changed the `ConnectionContext`'s `ConnectionStates` to correctly serialize as proper JSON when used with JavaScript.

### Other Changes
### Breaking Changes
- JavaScript developers using `request.connectionContext.states` no longer need to `JSON.parse(...)` its values. The values are already valid JSON.

## 1.0.0 (2021-11-09)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
{
/// <summary>
/// JsonConverter works for Functions JavaScript language object converters.
/// </summary>
internal class ConnectionStatesNewtonsoftConverter : JsonConverter<IReadOnlyDictionary<string, BinaryData>>
{
public override IReadOnlyDictionary<string, BinaryData> ReadJson(JsonReader reader, Type objectType, IReadOnlyDictionary<string, BinaryData> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var dict = new Dictionary<string, BinaryData>();
var jDict = JToken.Load(reader).ToObject<Dictionary<string, JToken>>();
foreach (var item in jDict)
{
dict.Add(item.Key, BinaryData.FromString(JsonConvert.SerializeObject(item.Value)));
}

return dict;
}

public override void WriteJson(JsonWriter writer, IReadOnlyDictionary<string, BinaryData> value, JsonSerializer serializer)
{
writer.WriteStartObject();
if (value != null)
{
foreach (KeyValuePair<string, BinaryData> pair in value)
{
writer.WritePropertyName(pair.Key);
writer.WriteRawValue(pair.Value.ToString());
}
}
writer.WriteEndObject();
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ internal string ActionName
{
return GetType().Name;
}
set
{
// used in type-less for deserialize.
_ = value;
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,10 @@ public override WebPubSubContext ReadJson(JsonReader reader, Type objectType, We
public override void WriteJson(JsonWriter writer, WebPubSubContext value, JsonSerializer serializer)
{
serializer.Converters.Add(new HttpResponseMessageJsonConverter());
// Request is using System.Json, use string as bridge to convert.
var request = ConvertString(value.Request);
var jobj = new JObject();
if (value.Request != null)
{
jobj.Add(new JProperty(WebPubSubContext.RequestPropertyName, JObject.Parse(request)));
jobj.Add(new JProperty(WebPubSubContext.RequestPropertyName, JObject.FromObject(value.Request, serializer)));
}
if (value.Response != null)
{
Expand All @@ -37,16 +35,5 @@ public override void WriteJson(JsonWriter writer, WebPubSubContext value, JsonSe
jobj.Add(WebPubSubContext.IsPreflightPropertyName, value.IsPreflight);
jobj.WriteTo(writer);
}

private static string ConvertString(WebPubSubEventRequest request) =>
request switch
{
ConnectedEventRequest connected => SystemJson.JsonSerializer.Serialize<ConnectedEventRequest>(connected),
ConnectEventRequest connect => SystemJson.JsonSerializer.Serialize<ConnectEventRequest>(connect),
UserEventRequest userEvent => SystemJson.JsonSerializer.Serialize<UserEventRequest>(userEvent),
DisconnectedEventRequest disconnected => SystemJson.JsonSerializer.Serialize<DisconnectedEventRequest>(disconnected),
PreflightRequest validation => SystemJson.JsonSerializer.Serialize<PreflightRequest>(validation),
_ => null
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ internal static void RegisterJsonConverter()
{
new StringEnumConverter(),
new BinaryDataJsonConverter(),
new JsonElementJsonConverter(),
new ConnectionStatesNewtonsoftConverter(),
},
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(RequiredTargetFrameworks)</TargetFrameworks>
<PackageId>Microsoft.Azure.WebJobs.Extensions.WebPubSub</PackageId>
<PackageTags>Azure, WebPubSub</PackageTags>
<Description>Azure Functions extension for the WebPubSub service</Description>
<Version>1.1.0-beta.1</Version>
<Version>1.1.0</Version>
<!--The ApiCompatVersion is managed automatically and should not generally be modified manually.-->
<ApiCompatVersion>1.0.0</ApiCompatVersion>
<NoWarn>$(NoWarn);AZC0001;CS8632;CA1056;CA2227</NoWarn>
<IsExtensionClientLibrary>true</IsExtensionClientLibrary>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\..\Microsoft.Azure.WebPubSub.Common\src\Shared\ConnectionStatesConverter.cs" LinkBase="Bindings\Shared" />
<Compile Include="..\..\Microsoft.Azure.WebPubSub.Common\src\Shared\WebPubSubStatusCode.cs" LinkBase="Bindings\Shared" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" />
<PackageReference Include="Microsoft.Azure.WebJobs" />
Expand All @@ -20,6 +25,11 @@

<ItemGroup>
<ProjectReference Include="..\..\Microsoft.Azure.WebPubSub.Common\src\Microsoft.Azure.WebPubSub.Common.csproj" />
<ProjectReference Include="..\..\Azure.Messaging.WebPubSub\src\Azure.Messaging.WebPubSub.csproj" />

<!--
TODO: Changing to a PackageReference since we only want to depend on the already GA'ed version of WebPubSub. Change back after release.
<ProjectReference Include="..\..\Azure.Messaging.WebPubSub\src\Azure.Messaging.WebPubSub.csproj" />
-->
<PackageReference Include="Azure.Messaging.WebPubSub" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebPubSub.Common;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json.Linq;

namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
{
Expand All @@ -21,12 +22,6 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
/// </summary>
internal static class WebPubSubRequestExtensions
{
/// <summary>
/// Parse request to system/user type ServiceRequest.
/// </summary>
/// <param name="request">Upstream HttpRequest.</param>
/// <param name="options"></param>
/// <returns>Deserialize <see cref="WebPubSubEventRequest"/></returns>
public static async Task<WebPubSubEventRequest> ReadWebPubSubRequestAsync(this HttpRequest request, WebPubSubValidationOptions options)
{
if (request == null)
Expand Down Expand Up @@ -146,35 +141,34 @@ internal static bool IsValidSignature(this WebPubSubConnectionContext connection
return false;
}

internal static Dictionary<string, object> DecodeConnectionStates(this string connectionStates)
internal static Dictionary<string, BinaryData> DecodeConnectionStates(this string connectionStates)
{
if (!string.IsNullOrEmpty(connectionStates))
{
var states = new Dictionary<string, object>();
var rawData = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(Convert.FromBase64String(connectionStates));
foreach (var state in rawData)
{
states.Add(state.Key, state.Value);
}
return states;
return JsonSerializer.Deserialize<IReadOnlyDictionary<string, BinaryData>>(Convert.FromBase64String(connectionStates), ConnectionStatesConverter.Options)
.ToDictionary(k => k.Key, v => v.Value);
}
return null;
}

internal static Dictionary<string,object> UpdateStates(this WebPubSubConnectionContext connectionContext, IReadOnlyDictionary<string, object> newStates)
internal static Dictionary<string, object> UpdateStates(this WebPubSubConnectionContext connectionContext, IReadOnlyDictionary<string, BinaryData> newStates)
{
// states cleared.
if (newStates == null)
{
return null;
}

if (connectionContext.States?.Count > 0 || newStates.Count > 0)
if (connectionContext.ConnectionStates?.Count > 0 || newStates.Count > 0)
{
var states = new Dictionary<string, object>();
if (connectionContext.States?.Count > 0)
if (connectionContext.ConnectionStates?.Count > 0)
{
states = connectionContext.States.ToDictionary(x => x.Key, y => y.Value);
foreach (var state in connectionContext.ConnectionStates)
{
states.Add(state.Key, state.Value);
}
}

// response states keep empty is no change.
Expand All @@ -194,7 +188,8 @@ internal static Dictionary<string,object> UpdateStates(this WebPubSubConnectionC

internal static string EncodeConnectionStates(this Dictionary<string, object> value)
{
return Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value)));
IReadOnlyDictionary<string, BinaryData> readOnlyDict = value.ToDictionary(x => x.Key, y => y.Value is BinaryData data ? data : FromObjectAsJsonExtended(y.Value));
return Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(readOnlyDict, ConnectionStatesConverter.Options)));
}

private static bool TryParseCloudEvents(this HttpRequest request, out WebPubSubConnectionContext connectionContext)
Expand All @@ -216,7 +211,7 @@ private static bool TryParseCloudEvents(this HttpRequest request, out WebPubSubC
userId = request.Headers.GetFirstHeaderValueOrDefault(Constants.Headers.CloudEvents.UserId);
}

Dictionary<string, object> states = null;
Dictionary<string, BinaryData> states = null;
// connection states.
if (request.Headers.ContainsKey(Constants.Headers.CloudEvents.State))
{
Expand Down Expand Up @@ -298,5 +293,18 @@ private static WebPubSubDataType GetDataType(this string mediaType) =>
Constants.ContentTypes.JsonContentType => WebPubSubDataType.Json,
_ => throw new ArgumentException($"Invalid content type: {mediaType}")
};

// support JToken for backward compatiblity.
private static BinaryData FromObjectAsJsonExtended<T>(T item, JsonSerializerOptions? options = null)
{
if (item is JToken)
{
return BinaryData.FromString(Newtonsoft.Json.JsonConvert.SerializeObject(item));
}
else
{
return BinaryData.FromObjectAsJson(item, options);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
using Microsoft.Azure.WebPubSub.Common;
using Microsoft.Extensions.Logging;

using NewtonsoftJsonLinq = Newtonsoft.Json.Linq;

namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
{
internal class WebPubSubTriggerDispatcher : IWebPubSubTriggerDispatcher
Expand Down Expand Up @@ -146,12 +148,15 @@ await executor.Executor.TryExecuteAsync(new TriggeredFunctionData
// Skip no returns
if (response != null)
{
var validResponse = Utilities.BuildValidResponse(response, requestType, context);

if (validResponse != null)
if (response is WebPubSubEventResponse wpsResponse)
{
return validResponse;
return Utilities.BuildValidResponse(wpsResponse, requestType, context);
}
if (response is NewtonsoftJsonLinq.JToken jResponse)
{
return Utilities.BuildValidResponse(jResponse, requestType, context);
}

_logger.LogWarning($"Invalid response type {response.GetType()} regarding current request: {requestType}");
}
}
Expand Down Expand Up @@ -191,7 +196,7 @@ private static bool TryParseCloudEvents(HttpRequestMessage request, out WebPubSu
{
userId = values.SingleOrDefault();
}
Dictionary<string, object> states = null;
Dictionary<string, BinaryData> states = null;
if (request.Headers.TryGetValues(Constants.Headers.CloudEvents.State, out var connectionStates))
{
states = connectionStates.SingleOrDefault().DecodeConnectionStates();
Expand Down
Loading

0 comments on commit dc94809

Please sign in to comment.