Skip to content
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

Change WPS ConnectionContext.States to a Dict<string, BinaryData> #25504

Merged
26 commits merged into from
Dec 6, 2021
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,44 @@
// 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;

using SystemJson = System.Text.Json;

namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
{
/// <summary>
/// JsonConverter works for Functions JavaScript language object converters.
/// </summary>
internal class ConnectionStatesNewtonsoftConverter : JsonConverter<IReadOnlyDictionary<string, BinaryData>>
{
JialinXin marked this conversation as resolved.
Show resolved Hide resolved
public override IReadOnlyDictionary<string, BinaryData> ReadJson(JsonReader reader, Type objectType, IReadOnlyDictionary<string, BinaryData> existingValue, bool hasExistingValue, JsonSerializer serializer)
tg-msft marked this conversation as resolved.
Show resolved Hide resolved
{
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());
JialinXin marked this conversation as resolved.
Show resolved Hide resolved
}
}
writer.WriteEndObject();
}
}
}

This file was deleted.

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,21 @@
<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" Link="Bindings\ConnectionStatesConverter.cs" />
JialinXin marked this conversation as resolved.
Show resolved Hide resolved
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" />
<PackageReference Include="Microsoft.Azure.WebJobs" />
Expand All @@ -20,6 +24,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,6 +22,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
/// </summary>
internal static class WebPubSubRequestExtensions
{
private static JsonSerializerOptions _innerSerializer => CreateSystemTextJsonSerializer();
JialinXin marked this conversation as resolved.
Show resolved Hide resolved
/// <summary>
/// Parse request to system/user type ServiceRequest.
/// </summary>
Expand Down Expand Up @@ -151,17 +153,17 @@ internal static Dictionary<string, object> DecodeConnectionStates(this string co
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)
var rawValue = JsonSerializer.Deserialize<IReadOnlyDictionary<string, BinaryData>>(Convert.FromBase64String(connectionStates), _innerSerializer);
foreach (var item in rawValue)
{
states.Add(state.Key, state.Value);
states.Add(item.Key, item.Value);
}
return states;
}
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, object> newStates)
{
// states cleared.
if (newStates == null)
Expand Down Expand Up @@ -194,7 +196,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> readOnlyDic = value.ToDictionary(x => x.Key, y => y.Value is BinaryData data ? data : FromObjectAsJsonExtended(y.Value));
return Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(readOnlyDic, _innerSerializer)));
}

private static bool TryParseCloudEvents(this HttpRequest request, out WebPubSubConnectionContext connectionContext)
Expand Down Expand Up @@ -298,5 +301,25 @@ private static WebPubSubDataType GetDataType(this string mediaType) =>
Constants.ContentTypes.JsonContentType => WebPubSubDataType.Json,
_ => throw new ArgumentException($"Invalid content type: {mediaType}")
};

private static JsonSerializerOptions CreateSystemTextJsonSerializer()
{
var options = new JsonSerializerOptions();
options.Converters.Add(new ConnectionStatesConverter());
return options;
}

// support JToken for backward compatiblity.
private static BinaryData FromObjectAsJsonExtended<T>(T item, JsonSerializerOptions? options = null)
tg-msft marked this conversation as resolved.
Show resolved Hide resolved
{
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 @@ -8,10 +8,11 @@
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

using Microsoft.Azure.WebPubSub.Common;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

using SystemJson = System.Text.Json;

namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
{
Expand Down Expand Up @@ -89,7 +90,7 @@ public static HttpResponseMessage BuildValidResponse(
object response, RequestType requestType,
WebPubSubConnectionContext context)
{
JsonDocument converted = null;
JObject jResponse = null;
string originStr = null;
bool needConvert = true;
if (response is WebPubSubEventResponse)
Expand All @@ -98,17 +99,17 @@ public static HttpResponseMessage BuildValidResponse(
}
else
{
// JObject or string type, use string to convert between JObject and JsonDocument.
// JObject or string type, use string to convert between NewtonsoftJson and SystemTextJson.
originStr = response.ToString();
converted = JsonDocument.Parse(originStr);
jResponse = response is JObject jObj ? jObj : JObject.Parse(originStr);
}

try
{
// Check error, errorCode is required for json convert, otherwise, ignored.
tg-msft marked this conversation as resolved.
Show resolved Hide resolved
if (needConvert && converted.RootElement.TryGetProperty("code", out var code))
if (needConvert && jResponse.TryGetValue("code", out var code))
{
var error = JsonSerializer.Deserialize<EventErrorResponse>(originStr);
var error = SystemJson.JsonSerializer.Deserialize<EventErrorResponse>(originStr);
tg-msft marked this conversation as resolved.
Show resolved Hide resolved
return BuildErrorResponse(error);
}
else if (response is EventErrorResponse errorResponse)
Expand All @@ -120,23 +121,23 @@ public static HttpResponseMessage BuildValidResponse(
{
if (needConvert)
{
var states = GetStatesFromJson(converted, originStr);
var states = GetStatesFromJson(jResponse);
var mergedStates = context.UpdateStates(states);
return BuildConnectEventResponse(originStr, mergedStates);
}
else if (response is ConnectEventResponse connectResponse)
{
var mergedStates = context.UpdateStates(connectResponse.States);
return BuildConnectEventResponse(JsonSerializer.Serialize(response), mergedStates);
return BuildConnectEventResponse(SystemJson.JsonSerializer.Serialize(response), mergedStates);
}
}
if (requestType == RequestType.User)
{
if (needConvert)
{
var states = GetStatesFromJson(converted, originStr);
var states = GetStatesFromJson(jResponse);
var mergedStates = context.UpdateStates(states);
return BuildUserEventResponse(JsonSerializer.Deserialize<UserEventResponse>(originStr), mergedStates);
return BuildUserEventResponse(SystemJson.JsonSerializer.Deserialize<UserEventResponse>(originStr), mergedStates);
}
else if (response is UserEventResponse messageResponse)
{
Expand Down Expand Up @@ -218,23 +219,23 @@ public static bool IsValidationRequest(this HttpRequestMessage req, out List<str
return false;
}

private static Dictionary<string, object> GetStatesFromJson(JsonDocument converted, string originStr)
private static Dictionary<string, object> GetStatesFromJson(JObject response)
{
if (converted.RootElement.TryGetProperty("states", out var val))
var states = new Dictionary<string, object>();
if (response.TryGetValue("states", out var val))
{
if (val.ValueKind == JsonValueKind.Object)
// val should be a JSON object of <key,value> pairs
if (val.Type == JTokenType.Object)
{
return JsonSerializer.Deserialize<StatesEntity>(originStr).States;
var strongType = JsonConvert.DeserializeObject<IReadOnlyDictionary<string, BinaryData>>(val.ToString());
JialinXin marked this conversation as resolved.
Show resolved Hide resolved
foreach (var item in strongType)
{
states.Add(item.Key, item.Value);
}
}
}
// We don't support clear states for JS
return new Dictionary<string, object>();
}

private sealed class StatesEntity
{
[JsonPropertyName("states")]
public Dictionary<string, object> States { get; set; }
return states;
}
}
}
Loading