From 3beea430fde438a79e36da1806a078467a8bf38f Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Fri, 16 Apr 2021 13:29:09 +0800 Subject: [PATCH 01/17] Init version for Web PubSub function bindings --- eng/Packages.Data.props | 1 + .../Directory.Build.props | 7 + ...oft.Azure.WebJobs.Extensions.WebPubSub.sln | 31 ++ .../README.md | 120 ++++++++ .../client/WebPubSubTests/Program.cs | 96 +++++++ .../WebPubSubTests/WebPubSubClient.csproj | 12 + .../samples/simplechat/client/index.html | 248 ++++++++++++++++ .../samples/simplechat/csharp/.gitignore | 264 ++++++++++++++++++ .../samples/simplechat/csharp/Functions.cs | 120 ++++++++ ...mpleChat20210323161418 - Zip Deploy.pubxml | 19 ++ ...jixin-simplechat-linux - Zip Deploy.pubxml | 19 ++ .../jixin-wps-simplechat - Zip Deploy.pubxml | 18 ++ .../profile.arm.json | 175 ++++++++++++ .../Properties/serviceDependencies.json | 11 + .../Properties/serviceDependencies.local.json | 11 + .../simplechat/csharp/SimpleChat.csproj | 23 ++ .../samples/simplechat/csharp/host.json | 11 + .../csharp/local.settings.sample.json | 13 + .../simplechat/js/functionapp/.gitignore | 26 ++ .../js/functionapp/connect/function.json | 13 + .../js/functionapp/connect/index.js | 20 ++ .../js/functionapp/connected/function.json | 19 ++ .../js/functionapp/connected/index.js | 30 ++ .../js/functionapp/extensions.csproj | 12 + .../simplechat/js/functionapp/host.json | 8 + .../js/functionapp/local.settings.sample.json | 13 + .../js/functionapp/login/function.json | 23 ++ .../simplechat/js/functionapp/login/index.js | 7 + .../js/functionapp/message/function.json | 20 ++ .../js/functionapp/message/index.js | 18 ++ .../src/Bindings/ConnectionContext.cs | 50 ++++ .../src/Bindings/MessageDataType.cs | 21 ++ .../src/Bindings/ServiceResponse.cs | 59 ++++ .../src/Bindings/WebPubSubAsyncCollector.cs | 45 +++ .../src/Bindings/WebPubSubConnection.cs | 35 +++ .../src/Bindings/WebPubSubErrorCode.cs | 21 ++ .../src/Bindings/WebPubSubEvent.cs | 40 +++ .../src/Bindings/WebPubSubEventType.cs | 19 ++ .../src/Bindings/WebPubSubMessage.cs | 85 ++++++ .../Bindings/WebPubSubMessageJsonConverter.cs | 24 ++ .../src/Bindings/WebPubSubOperation.cs | 41 +++ .../src/Config/WebPubSubConfigProvider.cs | 153 ++++++++++ .../Config/WebPubSubJobsBuilderExtensions.cs | 34 +++ .../src/Config/WebPubSubOptions.cs | 36 +++ .../src/Constants.cs | 56 ++++ ....Azure.WebJobs.Extensions.WebPubSub.csproj | 16 ++ .../src/Properties/AssemblyInfo.cs | 7 + .../src/Services/IWebPubSubService.cs | 34 +++ .../Protocols/ClientCertificateInfo.cs | 13 + .../Services/Protocols/ConnectEventRequest.cs | 23 ++ .../Protocols/ConnectEventResponse.cs | 22 ++ .../Protocols/DisconnectEventRequest.cs | 13 + .../src/Services/RequestType.cs | 21 ++ .../src/Services/ServiceConfigParser.cs | 56 ++++ .../src/Services/WebPubSubService.cs | 111 ++++++++ .../Trigger/IWebPubSubTriggerDispatcher.cs | 17 ++ .../src/Trigger/WebPubSubListener.cs | 46 +++ .../src/Trigger/WebPubSubTriggerAttribute.cs | 56 ++++ .../src/Trigger/WebPubSubTriggerBinding.cs | 261 +++++++++++++++++ .../WebPubSubTriggerBindingProvider.cs | 40 +++ .../src/Trigger/WebPubSubTriggerDispatcher.cs | 261 +++++++++++++++++ .../src/Trigger/WebPubSubTriggerEvent.cs | 35 +++ .../src/Utilities.cs | 145 ++++++++++ .../src/WebPubSubAttribute.cs | 19 ++ .../src/WebPubSubConnectionAttribute.cs | 36 +++ .../src/WebPubSubWebJobsStartup.cs | 17 ++ .../tests/Common/FakeTypeLocator.cs | 14 + .../tests/Common/TestExtensionConfig.cs | 27 ++ .../tests/Common/TestHelpers.cs | 139 +++++++++ .../tests/Common/TestListener.cs | 27 ++ .../tests/Common/TestListenerBase.cs | 6 + .../tests/JObjectTests.cs | 103 +++++++ .../tests/JobHostEndToEndTests.cs | 10 + ....WebJobs.Extensions.WebPubSub.Tests.csproj | 24 ++ .../tests/WebPubSubAsyncCollectorTests.cs | 61 ++++ .../tests/WebPubSubServiceTests.cs | 24 ++ .../tests/WebPubSubTriggerDispatcherTests.cs | 126 +++++++++ .../WebPubSubTriggerValueProviderTests.cs | 42 +++ 78 files changed, 3979 insertions(+) create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/Directory.Build.props create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/Microsoft.Azure.WebJobs.Extensions.WebPubSub.sln create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/Program.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/WebPubSubClient.csproj create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/index.html create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/.gitignore create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Functions.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/SimpleChat20210323161418 - Zip Deploy.pubxml create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-simplechat-linux - Zip Deploy.pubxml create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-wps-simplechat - Zip Deploy.pubxml create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/ServiceDependencies/jixin-wps-simplechat - Zip Deploy/profile.arm.json create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.json create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.local.json create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/SimpleChat.csproj create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/host.json create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/local.settings.sample.json create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/.gitignore create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/function.json create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/index.js create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/function.json create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/index.js create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/extensions.csproj create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/host.json create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/local.settings.sample.json create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/function.json create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/index.js create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/function.json create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/index.js create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectionContext.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/MessageDataType.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ServiceResponse.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubAsyncCollector.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubConnection.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubErrorCode.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubEvent.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubEventType.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessage.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageJsonConverter.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubOperation.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubJobsBuilderExtensions.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubOptions.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Constants.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Properties/AssemblyInfo.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/IWebPubSubService.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ClientCertificateInfo.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ConnectEventRequest.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ConnectEventResponse.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/DisconnectEventRequest.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/RequestType.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/IWebPubSubTriggerDispatcher.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubListener.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerAttribute.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBindingProvider.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerEvent.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubAttribute.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubWebJobsStartup.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/FakeTypeLocator.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestExtensionConfig.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListener.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListenerBase.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JobHostEndToEndTests.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props index d53be3a65a72..f00ab8fcf6d9 100644 --- a/eng/Packages.Data.props +++ b/eng/Packages.Data.props @@ -84,6 +84,7 @@ + diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/Directory.Build.props b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/Directory.Build.props new file mode 100644 index 000000000000..805ca8beaf23 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/Directory.Build.props @@ -0,0 +1,7 @@ + + + true + + + + diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/Microsoft.Azure.WebJobs.Extensions.WebPubSub.sln b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/Microsoft.Azure.WebJobs.Extensions.WebPubSub.sln new file mode 100644 index 000000000000..f6d288f1fdd4 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/Microsoft.Azure.WebJobs.Extensions.WebPubSub.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30803.129 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.WebJobs.Extensions.WebPubSub", "src\Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj", "{6CF5F6B5-F8D6-4D92-9AE5-F766B1C83EED}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests", "tests\Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj", "{89759B66-5CF1-4A86-9D07-D3DA48300AE9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6CF5F6B5-F8D6-4D92-9AE5-F766B1C83EED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CF5F6B5-F8D6-4D92-9AE5-F766B1C83EED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CF5F6B5-F8D6-4D92-9AE5-F766B1C83EED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CF5F6B5-F8D6-4D92-9AE5-F766B1C83EED}.Release|Any CPU.Build.0 = Release|Any CPU + {89759B66-5CF1-4A86-9D07-D3DA48300AE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89759B66-5CF1-4A86-9D07-D3DA48300AE9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89759B66-5CF1-4A86-9D07-D3DA48300AE9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89759B66-5CF1-4A86-9D07-D3DA48300AE9}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AFE7981B-5322-4E4D-BACC-3380116A52F5} + EndGlobalSection +EndGlobal diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md new file mode 100644 index 000000000000..ce62f7e48a81 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md @@ -0,0 +1,120 @@ +# Azure WebJobs EventGrid client library for .NET + +This extension provides functionality for receiving Web PubSub webhook calls in Azure Functions, allowing you to easily write functions that respond to any event published to Web PubSub. + +## Getting started + +### Install the package + +Install the Event Grid extension with [NuGet][nuget]: + +```Powershell +dotnet add package Microsoft.Azure.WebJobs.Extensions.WebPubSub +``` + +### Prerequisites + +You must have an [Azure subscription](https://azure.microsoft.com/free/) and an Azure resource group with a Web PubSub resource. Follow this [step-by-step tutorial](https://review.docs.microsoft.com/azure/azure-web-pubsub/howto-develop-create-instance?branch=release-azure-web-pubsub) to create an Azure Web PubSub instance. + +## Key concepts + +### Using Web PubSub input binding + +Please follow the [input binding tutorial](https://scaling-garbanzo-3fe86650.pages.github.io/references/functions-bindings#input-binding) to learn about using this extension for building `WebPubSubConnection` to create Websockets connection to service with input binding. + +### Using Web PubSub output binding + +Please follow the [output binding tutorial](https://scaling-garbanzo-3fe86650.pages.github.io/references/functions-bindings#output-binding) to learn about using this extension for publishing Web PubSub messages. + +### Using Web PubSub trigger + +Please follow the [trigger binding tutorial](https://scaling-garbanzo-3fe86650.pages.github.io/references/functions-bindings#trigger-binding) to learn about triggering an Azure Function when an event is sent from service upstream. + +## Examples + +### Functions that uses Web PubSub input binding + +```C# Snippet:WebPubSubInputBindingFunction +[FunctionName("WebPubSubInputBindingFunction")] +public static WebPubSubConnection Run( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req, + [WebPubSubConnection(Hub = "simplechat", UserId = "{query.userid}")] WebPubSubConnection connection) +{ + Console.WriteLine("login"); + return connection; +} +``` + +### Functions that uses Web PubSub output binding + +```c# Snippet:WebPubSubOutputBindingFunction +[FunctionName("WebPubSubOutputBindingFunction")] +public static async Task RunAsync( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req, + [WebPubSub(Hub = "simplechat")] IAsyncCollector webpubsubEvent) +{ + await webpubsubEvent.AddAsync(new WebPubSubEvent + { + Operation = Operation.SendToAll, + Message = new Message("Hello Web PubSub"), + DataType = MessageDataType.Text + }); +} +``` + +### Functions that uses Web PubSub trigger + +```C# Snippet:EventGridTriggerFunction +[FunctionName("EventGridTriggerFunction")] +public static async Task RunAsync( + [WebPubSubTrigger("message", EventType.User)] + ConnectionContext context, + WebPubSubMessage message, + MessageDataType dataType) +{ + Console.WriteLine($"Request from: {context.userId}"); + Console.WriteLine($"Request message: {message.Body.ToString()}"); + Console.WriteLine($"Request message DataType: {dataType}"); + return new MessageResponse + { + Message = new WebPubSubMessage("ack"), + }; +} +``` + +## Troubleshooting + +Please refer to [Monitor Azure Functions](https://docs.microsoft.com/azure/azure-functions/functions-monitoring) for troubleshooting guidance. + +## Next steps + +Read the [introduction to Azure Function](https://docs.microsoft.com/azure/azure-functions/functions-overview) or [creating an Azure Function guide](https://docs.microsoft.com/azure/azure-functions/functions-create-first-azure-function). + +## Contributing + +See our [CONTRIBUTING.md][contrib] for details on building, +testing, and contributing to this library. + +This project welcomes contributions and suggestions. Most contributions require +you to agree to a Contributor License Agreement (CLA) declaring that you have +the right to, and actually do, grant us the rights to use your contribution. For +details, visit [cla.microsoft.com][cla]. + +This project has adopted the [Microsoft Open Source Code of Conduct][coc]. +For more information see the [Code of Conduct FAQ][coc_faq] +or contact [opencode@microsoft.com][coc_contact] with any +additional questions or comments. + +![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-net%2Fsdk%2Fsearch%2FMicrosoft.Azure.WebJobs.Extensions.EventGrid%2FREADME.png) + + +[source]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/search/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src +[package]: https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.WebPubSub/ +[docs]: https://docs.microsoft.com/dotnet/api/Microsoft.Azure.WebJobs.Extensions.WebPubSub +[nuget]: https://www.nuget.org/ + +[contrib]: https://github.com/Azure/azure-sdk-for-net/tree/master/CONTRIBUTING.md +[cla]: https://cla.microsoft.com +[coc]: https://opensource.microsoft.com/codeofconduct/ +[coc_faq]: https://opensource.microsoft.com/codeofconduct/faq/ +[coc_contact]: mailto:opencode@microsoft.com diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/Program.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/Program.cs new file mode 100644 index 000000000000..42c2bd584504 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/Program.cs @@ -0,0 +1,96 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Net.WebSockets; +using System.Text; +using System.Threading.Tasks; + +using Newtonsoft.Json; + +namespace WebPubSubTests +{ + class Program + { + private const string DefaultLogin = "http://localhost:7071/api/login"; + private static HttpClient _httpClient = new HttpClient(); + + static async Task Main(string[] args) + { + Console.WriteLine("== [Simple Chat] =="); + + Console.WriteLine("[1] Enter login func url:"); + var loginUrl = Console.ReadLine(); + if (string.IsNullOrEmpty(loginUrl) || !Uri.TryCreate(loginUrl, UriKind.Absolute, out var url)) + { + Console.WriteLine($"Invalid url, use default local func: {DefaultLogin}"); + loginUrl = DefaultLogin; + } + + Console.WriteLine("[2] Enter userId:"); + var userId = Console.ReadLine(); + + var response = await _httpClient.GetAsync($"{loginUrl}?userid={userId}"); + var result = await response.Content.ReadAsStringAsync(); + + var connection = JsonConvert.DeserializeObject(result); + + using var webSocket = new ClientWebSocket(); + await webSocket.ConnectAsync(new Uri(connection.Url), default); + Console.WriteLine("Connected"); + + _ = ReceiveMessageAsync(webSocket); + + await SendMessageAsync(webSocket); + } + + private static async Task ReceiveMessageAsync(ClientWebSocket webSocket) + { + var ms = new MemoryStream(); + Memory buffer = new byte[1024]; + // receive loop + while (true) + { + var receiveResult = await webSocket.ReceiveAsync(buffer, default); + // Need to check again for NetCoreApp2.2 because a close can happen between a 0-byte read and the actual read + if (receiveResult.MessageType == WebSocketMessageType.Close) + { + try + { + await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default); + } + catch + { + // It is possible that the remote is already closed + } + break; + } + await ms.WriteAsync(buffer.Slice(0, receiveResult.Count)); + if (receiveResult.EndOfMessage) + { + Console.WriteLine($"[Received]: {Encoding.UTF8.GetString(ms.ToArray())}"); + ms.SetLength(0); + } + } + } + + private static async Task SendMessageAsync(ClientWebSocket webSocket) + { + while (true) + { + Console.WriteLine("[3] Input text to chat:"); + var message = Console.ReadLine(); + if (!string.IsNullOrEmpty(message)) + { + var msgBuffer = Encoding.UTF8.GetBytes(message); + await webSocket.SendAsync(new ArraySegment(msgBuffer), WebSocketMessageType.Text, true, default); + } + } + } + + private sealed class Connection + { + public string Url { get; set; } + public string AccessToken { get; set; } + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/WebPubSubClient.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/WebPubSubClient.csproj new file mode 100644 index 000000000000..b84e63f50619 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/WebPubSubClient.csproj @@ -0,0 +1,12 @@ + + + + Exe + net5.0 + + + + + + + diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/index.html b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/index.html new file mode 100644 index 000000000000..a7aa3b6a91d7 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/index.html @@ -0,0 +1,248 @@ + + + + + + + + + A Simple Serverless WebSocket Chat + + + + + + + + + +
+ + +
+ + + + + + + + + + \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/.gitignore b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/.gitignore new file mode 100644 index 000000000000..ff5b00c506bd --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/.gitignore @@ -0,0 +1,264 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Azure Functions localsettings file +local.settings.json + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Functions.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Functions.cs new file mode 100644 index 000000000000..dbe6faf99d33 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Functions.cs @@ -0,0 +1,120 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.Http; +using Microsoft.Azure.WebJobs.Extensions.WebPubSub; +using Newtonsoft.Json; +using System; +using System.Threading.Tasks; + +namespace SimpleChat +{ + public static class Functions + { + [FunctionName("login")] + public static WebPubSubConnection GetClientConnection( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req, + [WebPubSubConnection(UserId = "{query.userid}", ConnectionStringSetting = "abc", Hub ="testhub")] WebPubSubConnection connection) + { + Console.WriteLine("login"); + return connection; + } + + [FunctionName("connect")] + public static ServiceResponse Connect( + [WebPubSubTrigger("simplechat", WebPubSubEventType.System, "connect")] ConnectionContext connectionContext) + { + Console.WriteLine($"Received client connect with connectionId: {connectionContext.ConnectionId}"); + if (connectionContext.UserId == "attacker") + { + return new ErrorResponse(WebPubSubErrorCode.Unauthorized); + } + return new ConnectResponse + { + UserId = connectionContext.UserId + }; + } + + // multi tasks sample + [FunctionName("connected")] + public static async Task Connected( + [WebPubSubTrigger(WebPubSubEventType.System, "connected")] ConnectionContext connectionContext, + [WebPubSub] IAsyncCollector webpubsubEvent) + { + await webpubsubEvent.AddAsync(new WebPubSubEvent + { + Message = new WebPubSubMessage(new ClientContent($"{connectionContext.UserId} connected.").ToString()), + }); + + await webpubsubEvent.AddAsync(new WebPubSubEvent + { + Operation = WebPubSubOperation.AddUserToGroup, + UserId = connectionContext.UserId, + Group = "group1" + }); + await webpubsubEvent.AddAsync(new WebPubSubEvent + { + Operation = WebPubSubOperation.SendToUser, + UserId = connectionContext.UserId, + Group = "group1", + Message = new WebPubSubMessage(new ClientContent($"{connectionContext.UserId} joined group: group1.").ToString()), + }); + } + + // single message sample + [FunctionName("broadcast")] + public static async Task Broadcast( + [WebPubSubTrigger(WebPubSubEventType.User, "message")] WebPubSubMessage message, + [WebPubSub(Hub = "simplechat")] IAsyncCollector webpubsubEvent) + { + await webpubsubEvent.AddAsync(new WebPubSubEvent + { + Operation = WebPubSubOperation.SendToAll, + Message = message, + }); + + return new MessageResponse + { + Message = new WebPubSubMessage("ack"), + }; + } + + [FunctionName("disconnect")] + [return: WebPubSub] + public static WebPubSubEvent Disconnect( + [WebPubSubTrigger(WebPubSubEventType.System, "disconnected")] ConnectionContext connectionContext) + { + Console.WriteLine("Disconnect."); + return new WebPubSubEvent + { + Operation = WebPubSubOperation.SendToAll, + Message = new WebPubSubMessage(new ClientContent($"{connectionContext.UserId} disconnect.").ToString()) + }; + } + + [JsonObject] + public sealed class ClientContent + { + [JsonProperty("from")] + public string From { get; set; } + [JsonProperty("content")] + public string Content { get; set; } + + public ClientContent(string message) + { + From = "[System]"; + Content = message; + } + + public ClientContent(string from, string message) + { + From = from; + Content = message; + } + + public override string ToString() + { + return JsonConvert.SerializeObject(this); + } + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/SimpleChat20210323161418 - Zip Deploy.pubxml b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/SimpleChat20210323161418 - Zip Deploy.pubxml new file mode 100644 index 000000000000..3a5f19bbd8ec --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/SimpleChat20210323161418 - Zip Deploy.pubxml @@ -0,0 +1,19 @@ + + + + + ZipDeploy + AzureWebSite + Release + Any CPU + https://simplechat20210323161418.azurewebsites.net + False + /subscriptions/9caf2a1e-9c49-49b6-89a2-56bdec7e3f97/resourcegroups/jixin-test/providers/Microsoft.Web/sites/SimpleChat20210323161418 + $SimpleChat20210323161418 + <_SavePWD>True + true + https://simplechat20210323161418.scm.azurewebsites.net/ + + \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-simplechat-linux - Zip Deploy.pubxml b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-simplechat-linux - Zip Deploy.pubxml new file mode 100644 index 000000000000..8ac4c6853416 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-simplechat-linux - Zip Deploy.pubxml @@ -0,0 +1,19 @@ + + + + + ZipDeploy + AzureWebSite + Release + Any CPU + https://jixin-simplechat-linux.azurewebsites.net + False + /subscriptions/9caf2a1e-9c49-49b6-89a2-56bdec7e3f97/resourcegroups/jixin-test/providers/Microsoft.Web/sites/jixin-simplechat-linux + $jixin-simplechat-linux + <_SavePWD>True + true + https://jixin-simplechat-linux.scm.azurewebsites.net/ + + \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-wps-simplechat - Zip Deploy.pubxml b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-wps-simplechat - Zip Deploy.pubxml new file mode 100644 index 000000000000..7ba3110381c4 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-wps-simplechat - Zip Deploy.pubxml @@ -0,0 +1,18 @@ + + + + + ZipDeploy + AzureWebSite + Release + Any CPU + https://jixin-wps-simplechat.azurewebsites.net + False + /subscriptions/9caf2a1e-9c49-49b6-89a2-56bdec7e3f97/resourcegroups/jixin-test/providers/Microsoft.Web/sites/jixin-wps-simplechat + $jixin-wps-simplechat + <_SavePWD>True + https://jixin-wps-simplechat.scm.azurewebsites.net/ + + \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/ServiceDependencies/jixin-wps-simplechat - Zip Deploy/profile.arm.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/ServiceDependencies/jixin-wps-simplechat - Zip Deploy/profile.arm.json new file mode 100644 index 000000000000..e9fad2ee6f7a --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/ServiceDependencies/jixin-wps-simplechat - Zip Deploy/profile.arm.json @@ -0,0 +1,175 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_dependencyType": "function.windows.appService" + }, + "parameters": { + "resourceGroupName": { + "type": "string", + "defaultValue": "jixin-test", + "metadata": { + "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking." + } + }, + "resourceGroupLocation": { + "type": "string", + "defaultValue": "eastus", + "metadata": { + "description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support." + } + }, + "resourceName": { + "type": "string", + "defaultValue": "jixin-wps-simplechat", + "metadata": { + "description": "Name of the main resource to be created by this template." + } + }, + "resourceLocation": { + "type": "string", + "defaultValue": "[parameters('resourceGroupLocation')]", + "metadata": { + "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there." + } + } + }, + "resources": [ + { + "type": "Microsoft.Resources/resourceGroups", + "name": "[parameters('resourceGroupName')]", + "location": "[parameters('resourceGroupLocation')]", + "apiVersion": "2019-10-01" + }, + { + "type": "Microsoft.Resources/deployments", + "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "apiVersion": "2019-10-01", + "dependsOn": [ + "[parameters('resourceGroupName')]" + ], + "properties": { + "mode": "Incremental", + "expressionEvaluationOptions": { + "scope": "inner" + }, + "parameters": { + "resourceGroupName": { + "value": "[parameters('resourceGroupName')]" + }, + "resourceGroupLocation": { + "value": "[parameters('resourceGroupLocation')]" + }, + "resourceName": { + "value": "[parameters('resourceName')]" + }, + "resourceLocation": { + "value": "[parameters('resourceLocation')]" + } + }, + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceGroupName": { + "type": "string" + }, + "resourceGroupLocation": { + "type": "string" + }, + "resourceName": { + "type": "string" + }, + "resourceLocation": { + "type": "string" + } + }, + "variables": { + "storage_name": "[toLower(concat('storage', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId))))]", + "appServicePlan_name": "[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", + "storage_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Storage/storageAccounts/', variables('storage_name'))]", + "appServicePlan_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]", + "function_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/sites/', parameters('resourceName'))]" + }, + "resources": [ + { + "location": "[parameters('resourceLocation')]", + "name": "[parameters('resourceName')]", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "tags": { + "[concat('hidden-related:', variables('appServicePlan_ResourceId'))]": "empty" + }, + "dependsOn": [ + "[variables('appServicePlan_ResourceId')]", + "[variables('storage_ResourceId')]" + ], + "kind": "functionapp", + "properties": { + "name": "[parameters('resourceName')]", + "kind": "functionapp", + "httpsOnly": true, + "reserved": false, + "serverFarmId": "[variables('appServicePlan_ResourceId')]", + "siteConfig": { + "alwaysOn": true + } + }, + "identity": { + "type": "SystemAssigned" + }, + "resources": [ + { + "name": "appsettings", + "type": "config", + "apiVersion": "2015-08-01", + "dependsOn": [ + "[variables('function_ResourceId')]" + ], + "properties": { + "AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]", + "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]", + "FUNCTIONS_EXTENSION_VERSION": "~3", + "FUNCTIONS_WORKER_RUNTIME": "dotnet" + } + } + ] + }, + { + "location": "[parameters('resourceGroupLocation')]", + "name": "[variables('storage_name')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2017-10-01", + "tags": { + "[concat('hidden-related:', concat('/providers/Microsoft.Web/sites/', parameters('resourceName')))]": "empty" + }, + "properties": { + "supportsHttpsTrafficOnly": true + }, + "sku": { + "name": "Standard_LRS" + }, + "kind": "Storage" + }, + { + "location": "[parameters('resourceGroupLocation')]", + "name": "[variables('appServicePlan_name')]", + "type": "Microsoft.Web/serverFarms", + "apiVersion": "2015-08-01", + "sku": { + "name": "S1", + "tier": "Standard", + "family": "S", + "size": "S1" + }, + "properties": { + "name": "[variables('appServicePlan_name')]" + } + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.json new file mode 100644 index 000000000000..df4dcc9d888d --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights" + }, + "storage1": { + "type": "storage", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.local.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.local.json new file mode 100644 index 000000000000..b804a2893992 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.local.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights.sdk" + }, + "storage1": { + "type": "storage.emulator", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/SimpleChat.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/SimpleChat.csproj new file mode 100644 index 000000000000..90406b1846e1 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/SimpleChat.csproj @@ -0,0 +1,23 @@ + + + netcoreapp3.1 + v3 + 03c9ef80-6005-44fc-b5a4-7d0dd308a5a0 + + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/host.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/host.json new file mode 100644 index 000000000000..bb3b8dadd7d6 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/host.json @@ -0,0 +1,11 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingExcludedTypes": "Request", + "samplingSettings": { + "isEnabled": true + } + } + } +} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/local.settings.sample.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/local.settings.sample.json new file mode 100644 index 000000000000..1a7ae51e1c92 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/local.settings.sample.json @@ -0,0 +1,13 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet", + "WebPubSubConnectionString": "", + "WebPubSubHub": "simplechat" + }, + "Host": { + "LocalHttpPort": 7071, + "CORS": "*" + } +} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/.gitignore b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/.gitignore new file mode 100644 index 000000000000..479aa6a84d0c --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/.gitignore @@ -0,0 +1,26 @@ + +bin +obj +csx +.vs +edge +Publish + +*.user +*.suo +*.cscfg +*.Cache +project.lock.json + +/packages +/TestResults + +/tools/NuGet.exe +/App_Data +/secrets +/data +.secrets +appsettings.json +local.settings.json + +node_modules diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/function.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/function.json new file mode 100644 index 000000000000..6e4fbe15f22b --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/function.json @@ -0,0 +1,13 @@ +{ + "disabled": false, + "bindings": [ + { + "type": "webPubSubTrigger", + "name": "connectionContext", + "direction": "in", + "hub": "simplechat", + "eventName": "connect", + "eventType": "system" + } + ] +} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/index.js b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/index.js new file mode 100644 index 000000000000..a41ef423b11d --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/index.js @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +module.exports = async function (context, connectionContext) { + if (connectionContext.userId == "attacker") + { + var connectResponse = { + "code": "unauthorized", + "errorMessage": "invalid user" + } + } + else + { + var connectResponse = { + "userId": connectionContext.userId + }; + } + return connectResponse; +}; + diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/function.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/function.json new file mode 100644 index 000000000000..9e66a78a0184 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/function.json @@ -0,0 +1,19 @@ +{ + "disabled": false, + "bindings": [ + { + "type": "webPubSubTrigger", + "direction": "in", + "name": "connectionContext", + "hub": "simplechat", + "eventName": "connected", + "eventType": "system" + }, + { + "type": "webPubSub", + "name": "webPubSubEvent", + "hub": "simplechat", + "direction": "out" + } + ] +} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/index.js b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/index.js new file mode 100644 index 000000000000..edde5685234c --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/index.js @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +module.exports = function (context, connectionContext) { + context.bindings.webPubSubEvent = []; + context.bindings.webPubSubEvent.push({ + "operation": "sendToAll", + "message": JSON.stringify({ + from: '[System]', + content: `${context.bindingData.connectionContext.userId} connected.` + }), + "dataType" : "json" + }); + + context.bindings.webPubSubEvent.push({ + "operation": "addUserToGroup", + "userId": `${context.bindingData.connectionContext.userId}`, + "group": "group1" + }); + + context.bindings.webPubSubEvent.push({ + "operation": "sendToAll", + "message": JSON.stringify({ + from: '[System]', + content: `${context.bindingData.connectionContext.userId} joined group: group1.` + }), + "dataType": "json" + }); + context.done(); +}; \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/extensions.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/extensions.csproj new file mode 100644 index 000000000000..5573d335b2c2 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/extensions.csproj @@ -0,0 +1,12 @@ + + + netstandard2.0 + + ** + + + + + + + diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/host.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/host.json new file mode 100644 index 000000000000..8d1d95abe665 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/host.json @@ -0,0 +1,8 @@ +{ + "version": "2.0", + "extensions": { + "http": { + "routePrefix": "api" + } + } +} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/local.settings.sample.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/local.settings.sample.json new file mode 100644 index 000000000000..8623e1d7590b --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/local.settings.sample.json @@ -0,0 +1,13 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "WebPubSubHub": "simplechat", + "WebPubSubConnectionString": "", + "FUNCTIONS_WORKER_RUNTIME": "node" + }, + "Host": { + "LocalHttpPort": 7071, + "CORS": "*" + } +} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/function.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/function.json new file mode 100644 index 000000000000..c9f998c1ac27 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/function.json @@ -0,0 +1,23 @@ +{ + "disabled": false, + "bindings": [ + { + "authLevel": "anonymous", + "type": "httpTrigger", + "direction": "in", + "name": "req" + }, + { + "type": "http", + "direction": "out", + "name": "res" + }, + { + "type": "webPubSubConnection", + "name": "connection", + "userId": "{query.userid}", + "hub": "simplechat", + "direction": "in" + } + ] +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/index.js b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/index.js new file mode 100644 index 000000000000..511e0301e9e0 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/index.js @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +module.exports = function (context, req, connection) { + context.res = { body: connection }; + context.done(); +}; \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/function.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/function.json new file mode 100644 index 000000000000..1e85680c498b --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/function.json @@ -0,0 +1,20 @@ +{ + "disabled": false, + "bindings": [ + { + "type": "webPubSubTrigger", + "direction": "in", + "name": "message", + "dataType": "string", + "hub": "simplechat", + "eventName": "message", + "eventType": "user" + }, + { + "type": "webPubSub", + "name": "webPubSubEvent", + "hub": "simplechat", + "direction": "out" + } + ] +} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/index.js b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/index.js new file mode 100644 index 000000000000..f7fb89d1a328 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/index.js @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +module.exports = async function (context, message) { + context.bindings.webPubSubEvent = { + "operation": "sendToAll", + "message": message, + "dataType": context.bindingData.dataType + }; + var response = { + "message": JSON.stringify({ + from: '[System]', + content: 'ack.' + }), + "dataType" : "json" + }; + return response; +}; \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectionContext.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectionContext.cs new file mode 100644 index 000000000000..a19265a3cccf --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectionContext.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +using Microsoft.Extensions.Primitives; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class ConnectionContext + { + /// + /// The type of the message. + /// + public WebPubSubEventType EventType { get; internal set; } + + /// + /// The event name of the message. + /// + public string EventName { get; internal set; } + + /// + /// The hub which message belongs to. + /// + public string Hub { get; internal set; } + + /// + /// The connection-id of the client which send the message. + /// + public string ConnectionId { get; internal set; } + + /// + /// The user identity of the client which send the message. + /// + public string UserId { get; internal set; } + + /// + /// The signature for validation + /// + public string Signature { get; internal set; } + + /// + /// The headers of request. + /// + public Dictionary Headers { get; internal set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/MessageDataType.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/MessageDataType.cs new file mode 100644 index 000000000000..680fdfffe1cb --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/MessageDataType.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.Serialization; + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum MessageDataType + { + [EnumMember(Value = "binary")] + Binary, + [EnumMember(Value = "json")] + Json, + [EnumMember(Value = "text")] + Text + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ServiceResponse.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ServiceResponse.cs new file mode 100644 index 000000000000..f3cae5b556f1 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ServiceResponse.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + public abstract class ServiceResponse + { + } + + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class MessageResponse : ServiceResponse + { + [JsonProperty(Required = Required.Always)] + public WebPubSubMessage Message { get; set; } + + public MessageDataType DataType { get; set; } = MessageDataType.Text; + } + + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class ConnectResponse : ServiceResponse + { + [JsonProperty(Required = Required.Default)] + public string UserId { get; set; } + + [JsonProperty(Required = Required.Default)] + public string[] Groups { get; set; } + + [JsonProperty(Required = Required.Default)] + public string Subprotocol { get; set; } + + [JsonProperty(Required = Required.Default)] + public string[] Roles { get; set; } + } + + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class ErrorResponse : ServiceResponse + { + [JsonProperty(Required = Required.Always)] + public WebPubSubErrorCode Code { get; set; } + + [JsonProperty(Required = Required.Default)] + public string ErrorMessage { get; set; } + + + public ErrorResponse(WebPubSubErrorCode code, string message = null) + { + Code = code; + ErrorMessage = message; + } + + [JsonConstructor] + public ErrorResponse() + { + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubAsyncCollector.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubAsyncCollector.cs new file mode 100644 index 000000000000..1f709c9684ec --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubAsyncCollector.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Reflection; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal class WebPubSubAsyncCollector: IAsyncCollector + { + private readonly IWebPubSubService _service; + + internal WebPubSubAsyncCollector(IWebPubSubService service) + { + _service = service; + } + + public async Task AddAsync(WebPubSubEvent item, CancellationToken cancellationToken = default) + { + if (item == null) + { + throw new ArgumentNullException("Binding Object."); + } + + try + { + var method = typeof(IWebPubSubService).GetMethod(item.Operation.ToString(), + BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + + await (Task)method.Invoke(_service, new object[] { item }); + } + catch (Exception ex) + { + throw new ArgumentException($"Not supported operation: {item.Operation}, exception: {ex}"); + } + } + + public Task FlushAsync(CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubConnection.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubConnection.cs new file mode 100644 index 000000000000..cab1d90e3567 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubConnection.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Web; + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class WebPubSubConnection + { + public WebPubSubConnection(string baseUrl, string accessToken) + { + BaseUrl = baseUrl; + AccessToken = accessToken; + Url = $"{baseUrl}?access_token={accessToken}"; + } + + public WebPubSubConnection(Uri url) + { + Url = url.ToString(); + BaseUrl = $"{url.Scheme}://{url.Authority}{url.AbsolutePath}"; + AccessToken = HttpUtility.ParseQueryString(url.Query)["access_token"]; + } + + public string BaseUrl { get;} + + public string Url { get;} + + public string AccessToken { get;} + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubErrorCode.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubErrorCode.cs new file mode 100644 index 000000000000..c1051d6d155b --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubErrorCode.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.Serialization; + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum WebPubSubErrorCode + { + [EnumMember(Value = "unauthorized")] + Unauthorized, + [EnumMember(Value = "userError")] + UserError, + [EnumMember(Value = "serverError")] + ServerError + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubEvent.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubEvent.cs new file mode 100644 index 000000000000..8a8cbbbd1ce2 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubEvent.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.ComponentModel.DataAnnotations; + +using Azure.Messaging.WebPubSub; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + /// + /// Output binding object to invoke rest calls to services + /// + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class WebPubSubEvent + { + [Required] + [JsonRequired] + public WebPubSubOperation Operation { get; set; } + + public string Group { get; set; } + + public string UserId { get; set; } + + public string ConnectionId { get; set; } + + public string[] Excluded { get; set; } + + public string Reason { get; set; } + + [JsonConverter(typeof(StringEnumConverter))] + public WebPubSubPermission Permission { get; set; } + + public WebPubSubMessage Message { get; set; } + + public MessageDataType DataType { get; set; } = MessageDataType.Text; + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubEventType.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubEventType.cs new file mode 100644 index 000000000000..74b61a045f2f --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubEventType.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.Serialization; +using System.Text.Json.Serialization; + +using Newtonsoft.Json.Converters; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum WebPubSubEventType + { + [EnumMember(Value = "system")] + System, + [EnumMember(Value = "user")] + User, + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessage.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessage.cs new file mode 100644 index 000000000000..1d677276268c --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessage.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + /// + /// Message to communicate with service + /// + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + [JsonConverter(typeof(MessageJsonConverter))] + public class WebPubSubMessage + { + private readonly BinaryData _body; + + public WebPubSubMessage(Stream message) + { + _body = BinaryData.FromStream(message); + } + + public WebPubSubMessage(string message) + { + _body = BinaryData.FromString(message); + } + + public WebPubSubMessage(byte[] message) + { + _body = BinaryData.FromBytes(message); + } + + public byte[] ToArray() + { + return _body.ToArray(); + } + + public override string ToString() + { + return _body.ToString(); + } + + public Stream ToStream() + { + return _body.ToStream(); + } + + public BinaryData ToBinaryData() + { + return _body; + } + } + + internal static class MessageExtensions + { + public static object Convert(this WebPubSubMessage message, Type targetType) + { + if (targetType == typeof(JObject)) + { + return JObject.FromObject(message.ToArray()); + } + + if (targetType == typeof(Stream)) + { + return message.ToStream(); + } + + if (targetType == typeof(byte[])) + { + return message.ToArray(); + } + + if (targetType == typeof(string)) + { + return message.ToString(); + } + + return null; + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageJsonConverter.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageJsonConverter.cs new file mode 100644 index 000000000000..bd6708ead8f8 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageJsonConverter.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal class MessageJsonConverter : JsonConverter + { + public override WebPubSubMessage ReadJson(JsonReader reader, Type objectType, WebPubSubMessage existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var jObject = JToken.Load(reader); + + return new WebPubSubMessage(jObject.ToString()); + } + + public override void WriteJson(JsonWriter writer, WebPubSubMessage value, JsonSerializer serializer) + { + serializer.Serialize(writer, value.ToString()); + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubOperation.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubOperation.cs new file mode 100644 index 000000000000..329fedcdcdfe --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubOperation.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + /// + /// Supported operations of rest calls. + /// + [JsonConverter(typeof(StringEnumConverter))] + public enum WebPubSubOperation + { + [EnumMember(Value = "sendToAll")] + SendToAll, + [EnumMember(Value = "closeClientConnection")] + CloseClientConnection, + [EnumMember(Value = "sendToConnection")] + SendToConnection, + [EnumMember(Value = "sendToGroup")] + SendToGroup, + [EnumMember(Value = "addConnectionToGroup")] + AddConnectionToGroup, + [EnumMember(Value = "removeConnectionFromGroup")] + RemoveConnectionFromGroup, + [EnumMember(Value = "sendToUser")] + SendToUser, + [EnumMember(Value = "addToGroup")] + AddUserToGroup, + [EnumMember(Value = "removeUserFromGroup")] + RemoveUserFromGroup, + [EnumMember(Value = "removeUserFromAllGroups")] + RemoveUserFromAllGroups, + [EnumMember(Value = "grandGroupPermission")] + GrantGroupPermission, + [EnumMember(Value = "revokeGroupPermission")] + RevokeGroupPermission + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs new file mode 100644 index 000000000000..dd563fbee6db --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Azure.WebJobs.Description; +using Microsoft.Azure.WebJobs.Host.Bindings; +using Microsoft.Azure.WebJobs.Host.Config; +using Microsoft.Azure.WebJobs.Logging; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [Extension("WebPubSub", "webpubsub")] + internal class WebPubSubConfigProvider : IExtensionConfigProvider, IAsyncConverter + { + private readonly IConfiguration _configuration; + private readonly INameResolver _nameResolver; + private readonly ILogger _logger; + private readonly WebPubSubOptions _options; + private readonly IWebPubSubTriggerDispatcher _dispatcher; + + public WebPubSubConfigProvider( + IOptions options, + INameResolver nameResolver, + ILoggerFactory loggerFactory, + IConfiguration configuration) + { + _options = options.Value; + _logger = loggerFactory.CreateLogger(LogCategories.CreateTriggerCategory("WebPubSub")); + _nameResolver = nameResolver; + _configuration = configuration; + _dispatcher = new WebPubSubTriggerDispatcher(); + } + + public void Initialize(ExtensionConfigContext context) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + + if (string.IsNullOrEmpty(_options.ConnectionString)) + { + _options.ConnectionString = _nameResolver.Resolve(Constants.WebPubSubConnectionStringName); + AddSettings(_options.ConnectionString); + } + + if (string.IsNullOrEmpty(_options.Hub)) + { + _options.Hub = _nameResolver.Resolve(Constants.HubNameStringName); + } + +#pragma warning disable CS0618 // Type or member is obsolete + var url = context.GetWebhookHandler(); +#pragma warning restore CS0618 // Type or member is obsolete + _logger.LogInformation($"Registered Web PubSub negotiate Endpoint = {url?.GetLeftPart(UriPartial.Path)}"); + + // bindings + context + .AddConverter(JObject.FromObject) + .AddConverter(ConvertFromJObject) + .AddConverter(ConvertFromJObject); + + // Trigger binding + context.AddBindingRule() + .BindToTrigger(new WebPubSubTriggerBindingProvider(_dispatcher, _options)); + + var webpubsubConnectionAttributeRule = context.AddBindingRule(); + webpubsubConnectionAttributeRule.AddValidator(ValidateWebPubSubConnectionAttributeBinding); + webpubsubConnectionAttributeRule.BindToInput(GetClientConnection); + + var webPubSubAttributeRule = context.AddBindingRule(); + webPubSubAttributeRule.AddValidator(ValidateWebPubSubAttributeBinding); + webPubSubAttributeRule.BindToCollector(CreateCollector); + + _logger.LogInformation("Azure Web PubSub binding initialized"); + } + + public Task ConvertAsync(HttpRequestMessage input, CancellationToken cancellationToken) + { + return _dispatcher.ExecuteAsync(input, _options.AllowedHosts, _options.AccessKeys, cancellationToken); + } + + private void ValidateWebPubSubConnectionAttributeBinding(WebPubSubConnectionAttribute attribute, Type type) + { + ValidateConnectionString( + attribute.ConnectionStringSetting, + $"{nameof(WebPubSubConnectionAttribute)}.{nameof(WebPubSubConnectionAttribute.ConnectionStringSetting)}"); + } + + private void ValidateWebPubSubAttributeBinding(WebPubSubAttribute attribute, Type type) + { + ValidateConnectionString( + attribute.ConnectionStringSetting, + $"{nameof(WebPubSubAttribute)}.{nameof(WebPubSubAttribute.ConnectionStringSetting)}"); + } + + internal WebPubSubService GetService(WebPubSubAttribute attribute) + { + var connectionString = Utilities.FirstOrDefault(attribute.ConnectionStringSetting, _options.ConnectionString); + var hubName = Utilities.FirstOrDefault(attribute.Hub, _options.Hub); + return new WebPubSubService(connectionString, hubName); + } + + private IAsyncCollector CreateCollector(WebPubSubAttribute attribute) + { + return new WebPubSubAsyncCollector(GetService(attribute)); + } + + private WebPubSubConnection GetClientConnection(WebPubSubConnectionAttribute attribute) + { + var hub = Utilities.FirstOrDefault(attribute.Hub, _options.Hub); + var service = new WebPubSubService(attribute.ConnectionStringSetting, hub); + var claims = attribute.GetClaims(); + return service.GetClientConnection(claims); + } + + private void ValidateConnectionString(string attributeConnectionString, string attributeConnectionStringName) + { + AddSettings(attributeConnectionString); + var connectionString = Utilities.FirstOrDefault(attributeConnectionString, _options.ConnectionString); + + if (string.IsNullOrEmpty(connectionString)) + { + throw new InvalidOperationException(string.Format($"The Service connection string must be set either via an '{Constants.WebPubSubConnectionStringName}' app setting, via an '{Constants.WebPubSubConnectionStringName}' environment variable, or directly in code via {nameof(WebPubSubOptions)}.{nameof(WebPubSubOptions.ConnectionString)} or {{0}}.", + attributeConnectionStringName)); + } + } + + private void AddSettings(string connectionString) + { + if (!string.IsNullOrEmpty(connectionString)) + { + var item = new ServiceConfigParser(connectionString); + _options.AllowedHosts.Add(new Uri(item.Endpoint).Host); + _options.AccessKeys.Add(item.AccessKey); + } + } + + private T ConvertFromJObject(JObject input) + { + return input.ToObject(); + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubJobsBuilderExtensions.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubJobsBuilderExtensions.cs new file mode 100644 index 000000000000..d6ce652ab593 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubJobsBuilderExtensions.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + public static class WebPubSubJobsBuilderExtensions + { + public static IWebJobsBuilder AddWebPubSub(this IWebJobsBuilder builder) + { + + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.AddExtension() + .ConfigureOptions(ApplyConfiguration); + return builder; + } + + private static void ApplyConfiguration(IConfiguration config, WebPubSubOptions options) + { + if (config == null) + { + return; + } + + config.Bind(options); + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubOptions.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubOptions.cs new file mode 100644 index 000000000000..20bfe73f5b7b --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubOptions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using Microsoft.Azure.WebJobs.Hosting; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + public class WebPubSubOptions : IOptionsFormatter + { + public string ConnectionString { get; set; } + + public string Hub { get; set; } + + internal HashSet AllowedHosts { get; set; } = new HashSet(); + + internal HashSet AccessKeys { get; set; } = new HashSet(); + + /// + /// Formats the options as JSON objects for display. + /// + /// Options formatted as JSON. + public string Format() + { + // Not expose ConnectionString in logging. + JObject options = new JObject + { + { nameof(Hub), Hub } + }; + + return options.ToString(Formatting.Indented); + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Constants.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Constants.cs new file mode 100644 index 000000000000..45492ece15c0 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Constants.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal static class Constants + { + // WebPubSubOptions can be set by customers. + public const string WebPubSubConnectionStringName = "WebPubSubConnectionString"; + public const string HubNameStringName = "WebPubSubHub"; + + public static class ContentTypes + { + public const string JsonContentType = "application/json"; + public const string BinaryContentType = "application/octet-stream"; + public const string PlainTextContentType = "text/plain"; + } + + public static class Events + { + public const string ConnectEvent = "connect"; + public const string ConnectedEvent = "connected"; + public const string MessageEvent = "message"; + public const string DisconnectedEvent = "disconnected"; + } + + public static class Headers + { + public static class CloudEvents + { + private const string Prefix = "ce-"; + public const string Signature = Prefix + "signature"; + public const string Hub = Prefix + "hub"; + public const string ConnectionId = Prefix + "connectionId"; + public const string Id = Prefix + "id"; + public const string Time = Prefix + "time"; + public const string SpecVersion = Prefix + "specversion"; + public const string Type = Prefix + "type"; + public const string Source = Prefix + "source"; + public const string EventName = Prefix + "eventName"; + public const string UserId = Prefix + "userId"; + + public const string TypeSystemPrefix = "azure.webpubsub.sys."; + public const string TypeUserPrefix = "azure.webpubsub.user."; + } + + public const string WebHookRequestOrigin = "WebHook-Request-Origin"; + public const string WebHookAllowedOrigin = "WebHook-Allowed-Origin"; + } + + public class ErrorMessages + { + public const string NotSupportedDataType = "Message only supports text, binary, json. Current value is: "; + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj new file mode 100644 index 000000000000..7f7e4edad28b --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj @@ -0,0 +1,16 @@ + + + + $(RequiredTargetFrameworks) + Microsoft.Azure.WebJobs.Extensions.WebPubSub + 8.0 + 1.0.0-alpha.1 + $(NoWarn);AZC0001;CS1591 + + + + + + + + diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Properties/AssemblyInfo.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Properties/AssemblyInfo.cs new file mode 100644 index 000000000000..343540a98cbf --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/IWebPubSubService.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/IWebPubSubService.cs new file mode 100644 index 000000000000..5b2280d2e968 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/IWebPubSubService.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Threading.Tasks; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal interface IWebPubSubService + { + Task SendToAll(WebPubSubEvent webPubSubEvent); + + Task CloseClientConnection(WebPubSubEvent webPubSubEvent); + + Task SendToConnection(WebPubSubEvent webPubSubEvent); + + Task SendToGroup(WebPubSubEvent webPubSubEvent); + + Task AddConnectionToGroup(WebPubSubEvent webPubSubEvent); + + Task RemoveConnectionFromGroup(WebPubSubEvent webPubSubEvent); + + Task SendToUser(WebPubSubEvent webPubSubEvent); + + Task AddUserToGroup(WebPubSubEvent webPubSubEvent); + + Task RemoveUserFromGroup(WebPubSubEvent webPubSubEvent); + + Task RemoveUserFromAllGroups(WebPubSubEvent webPubSubEvent); + + Task GrantGroupPermission(WebPubSubEvent webPubSubEvent); + + Task RevokeGroupPermission(WebPubSubEvent webPubSubEvent); + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ClientCertificateInfo.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ClientCertificateInfo.cs new file mode 100644 index 000000000000..969c808ab268 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ClientCertificateInfo.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal sealed class ClientCertificateInfo + { + [JsonProperty("thumbprint")] + public string Thumbprint { get; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ConnectEventRequest.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ConnectEventRequest.cs new file mode 100644 index 000000000000..b40233b6c2bc --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ConnectEventRequest.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal sealed class ConnectEventRequest + { + [JsonProperty("claims")] + public IDictionary Claims { get; set; } + + [JsonProperty("query")] + public IDictionary Query { get; set; } + + [JsonProperty("subprotocols")] + public string[] Subprotocols { get; set; } + + [JsonProperty("clientCertificates")] + public ClientCertificateInfo[] ClientCertificates { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ConnectEventResponse.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ConnectEventResponse.cs new file mode 100644 index 000000000000..036545abd572 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/ConnectEventResponse.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal sealed class ConnectEventResponse + { + [JsonProperty("subprotocol")] + public string Subprotocol { get; set; } + + [JsonProperty("roles")] + public string[] Roles { get; set; } + + [JsonProperty("userId")] + public string UserId { get; set; } + + [JsonProperty("groups")] + public string[] Groups { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/DisconnectEventRequest.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/DisconnectEventRequest.cs new file mode 100644 index 000000000000..14c88faa3d5e --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/Protocols/DisconnectEventRequest.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal sealed class DisconnectEventRequest + { + [JsonProperty("reason")] + public string Reason { get; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/RequestType.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/RequestType.cs new file mode 100644 index 000000000000..c4b456afb52b --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/RequestType.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal enum RequestType + { + Ignored, + Connect, + Disconnect, + User + } + + internal static class RequestTypeExtensions + { + public static bool IsSyncMethod(this RequestType requestType) + { + return requestType == RequestType.Connect || requestType == RequestType.User; + } + } +} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs new file mode 100644 index 000000000000..a67a0234f5bc --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal class ServiceConfigParser + { + public string Endpoint { get; } + + public string AccessKey { get; } + + public string Version { get; } + + public string Port { get; } + + public ServiceConfigParser(string connectionString) + { + var settings = ParseConnectionString(connectionString); + + Endpoint = settings.ContainsKey("endpoint") ? + settings["endpoint"] : + throw new ArgumentNullException(nameof(Endpoint)); + AccessKey = settings.ContainsKey("accesskey") ? + settings["accesskey"] : + throw new ArgumentNullException(nameof(AccessKey)); + + Version = settings.ContainsKey("version") ? settings["version"] : null; + Port = settings.ContainsKey("port") ? settings["port"] : null; + } + + private Dictionary ParseConnectionString(string connectionString) + { + if (string.IsNullOrEmpty(connectionString)) + { + throw new ArgumentException("Web PubSub Service connection string is empty"); + } + + var setting = new Dictionary(); + var items = connectionString.Split(';'); + + try + { + setting = items.Where(x => x.Length > 0).ToDictionary(x => x.Split('=')[0].ToLower(), y => y.Split('=')[1]); + } + catch (Exception) + { + throw new ArgumentException($"Invalid Web PubSub connection string: {connectionString}"); + } + return setting; + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs new file mode 100644 index 000000000000..fd3c29516c7d --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; + +using Azure; +using Azure.Core; +using Azure.Messaging.WebPubSub; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal class WebPubSubService : IWebPubSubService + { + private readonly WebPubSubServiceClient _client; + private readonly ServiceConfigParser _serviceConfig; + + public WebPubSubService(string connectionString, string hubName) + { + _serviceConfig = new ServiceConfigParser(connectionString); + var url = !string.IsNullOrEmpty(_serviceConfig.Port) ? $"{_serviceConfig.Endpoint}:{_serviceConfig.Port}" : _serviceConfig.Endpoint; + _client = new WebPubSubServiceClient(new Uri(url), hubName, new AzureKeyCredential(_serviceConfig.AccessKey)); + } + + internal WebPubSubConnection GetClientConnection(IEnumerable claims = null) + { + var url = _client.GetClientAccessUri(default, claims?.ToArray()); + + #region TODO: Remove after SDK fix. + if (!_serviceConfig.Endpoint.StartsWith("https", StringComparison.OrdinalIgnoreCase)) + { + var replaced = url.AbsoluteUri.Replace("wss", "ws"); + url = new Uri(replaced); + } + #endregion + + return new WebPubSubConnection(url); + } + + public async Task AddConnectionToGroup(WebPubSubEvent webPubSubEvent) + { + await _client.AddConnectionToGroupAsync(webPubSubEvent.Group, webPubSubEvent.ConnectionId).ConfigureAwait(false); + } + + public async Task AddUserToGroup(WebPubSubEvent webPubSubEvent) + { + await _client.AddUserToGroupAsync(webPubSubEvent.Group, webPubSubEvent.UserId).ConfigureAwait(false); + } + + public async Task CloseClientConnection(WebPubSubEvent webPubSubEvent) + { + await _client.CloseClientConnectionAsync(webPubSubEvent.ConnectionId, webPubSubEvent.Reason).ConfigureAwait(false); + } + + public async Task GrantGroupPermission(WebPubSubEvent webPubSubEvent) + { + await _client.GrantPermissionAsync(webPubSubEvent.Permission, webPubSubEvent.ConnectionId).ConfigureAwait(false); + } + + public async Task RemoveConnectionFromGroup(WebPubSubEvent webPubSubEvent) + { + await _client.RemoveConnectionFromGroupAsync(webPubSubEvent.Group, webPubSubEvent.ConnectionId).ConfigureAwait(false); + } + + public async Task RemoveUserFromAllGroups(WebPubSubEvent webPubSubEvent) + { + await _client.RemoveUserFromAllGroupsAsync(webPubSubEvent.UserId).ConfigureAwait(false); + } + + public async Task RemoveUserFromGroup(WebPubSubEvent webPubSubEvent) + { + await _client.RemoveUserFromGroupAsync(webPubSubEvent.Group, webPubSubEvent.UserId).ConfigureAwait(false); + } + + public async Task RevokeGroupPermission(WebPubSubEvent webPubSubEvent) + { + await _client.RevokePermissionAsync(webPubSubEvent.Permission, webPubSubEvent.ConnectionId).ConfigureAwait(false); + } + + public async Task SendToAll(WebPubSubEvent webPubSubEvent) + { + var content = RequestContent.Create(webPubSubEvent.Message); + var contentType = Utilities.GetContentType(webPubSubEvent.DataType); + await _client.SendToAllAsync(content, contentType, webPubSubEvent.Excluded).ConfigureAwait(false); + } + + public async Task SendToConnection(WebPubSubEvent webPubSubEvent) + { + var content = RequestContent.Create(webPubSubEvent.Message.ToBinaryData()); + var contentType = Utilities.GetContentType(webPubSubEvent.DataType); + await _client.SendToConnectionAsync(webPubSubEvent.ConnectionId, content, contentType).ConfigureAwait(false); + } + + public async Task SendToGroup(WebPubSubEvent webPubSubEvent) + { + var content = RequestContent.Create(webPubSubEvent.Message.ToBinaryData()); + var contentType = Utilities.GetContentType(webPubSubEvent.DataType); + await _client.SendToGroupAsync(webPubSubEvent.Group, content, contentType, webPubSubEvent.Excluded).ConfigureAwait(false); + } + + public async Task SendToUser(WebPubSubEvent webPubSubEvent) + { + var content = RequestContent.Create(webPubSubEvent.Message.ToBinaryData()); + var contentType = Utilities.GetContentType(webPubSubEvent.DataType); + await _client.SendToUserAsync(webPubSubEvent.UserId, content, contentType).ConfigureAwait(false); + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/IWebPubSubTriggerDispatcher.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/IWebPubSubTriggerDispatcher.cs new file mode 100644 index 000000000000..e728b4ef3751 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/IWebPubSubTriggerDispatcher.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal interface IWebPubSubTriggerDispatcher + { + void AddListener(string key, WebPubSubListener listener); + + Task ExecuteAsync(HttpRequestMessage req, HashSet allowedHosts, HashSet AccessTokens, CancellationToken token = default); + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubListener.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubListener.cs new file mode 100644 index 000000000000..180773eed5c6 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubListener.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Azure.WebJobs.Host.Executors; +using Microsoft.Azure.WebJobs.Host.Listeners; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal class WebPubSubListener : IListener + { + public ITriggeredFunctionExecutor Executor { private set; get; } + + private readonly string _listenerKey; + private readonly IWebPubSubTriggerDispatcher _dispatcher; + + public WebPubSubListener(ITriggeredFunctionExecutor executor, string listenerKey, IWebPubSubTriggerDispatcher dispatcher) + { + _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + _listenerKey = listenerKey ?? throw new ArgumentNullException(nameof(listenerKey)); + Executor = executor ?? throw new ArgumentNullException(nameof(executor)); + } + + public void Cancel() + { + } + + public void Dispose() + { + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _dispatcher.AddListener(_listenerKey, this); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerAttribute.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerAttribute.cs new file mode 100644 index 000000000000..7c9939b65d33 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerAttribute.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.ComponentModel.DataAnnotations; + +using Microsoft.Azure.WebJobs.Description; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [AttributeUsage(AttributeTargets.Parameter)] +#pragma warning disable CS0618 // Type or member is obsolete + [Binding(TriggerHandlesReturnValue = true)] +#pragma warning restore CS0618 // Type or member is obsolete + public class WebPubSubTriggerAttribute : Attribute + { + + /// + /// Used to map to method name automatically + /// + /// + /// + /// + public WebPubSubTriggerAttribute(string hub, WebPubSubEventType eventType, string eventName) + { + Hub = hub; + EventName = eventName; + EventType = eventType; + } + + public WebPubSubTriggerAttribute(WebPubSubEventType eventType, string eventName) + : this ("", eventType, eventName) + { + } + + /// + /// The hub of request. + /// + [AutoResolve] + public string Hub { get; } + + /// + /// The event of the request + /// + [Required] + [AutoResolve] + public string EventName { get; } + + /// + /// The event type, allowed value is system or user + /// + [AutoResolve] + public WebPubSubEventType EventType { get; } + + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs new file mode 100644 index 000000000000..df1b760f8059 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs @@ -0,0 +1,261 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Azure.WebJobs.Host.Bindings; +using Microsoft.Azure.WebJobs.Host.Listeners; +using Microsoft.Azure.WebJobs.Host.Protocols; +using Microsoft.Azure.WebJobs.Host.Triggers; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal class WebPubSubTriggerBinding : ITriggerBinding + { + private readonly ParameterInfo _parameterInfo; + private readonly WebPubSubTriggerAttribute _attribute; + private readonly IWebPubSubTriggerDispatcher _dispatcher; + private readonly WebPubSubOptions _options; + + public WebPubSubTriggerBinding(ParameterInfo parameterInfo, WebPubSubTriggerAttribute attribute, WebPubSubOptions options, IWebPubSubTriggerDispatcher dispatcher) + { + _parameterInfo = parameterInfo ?? throw new ArgumentNullException(nameof(parameterInfo)); + _attribute = attribute ?? throw new ArgumentNullException(nameof(attribute)); + _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + _options = options ?? throw new ArgumentNullException(nameof(options)); + + BindingDataContract = CreateBindingContract(parameterInfo); + } + + public Type TriggerValueType => typeof(WebPubSubTriggerEvent); + + public IReadOnlyDictionary BindingDataContract { get; } + + public Task BindAsync(object value, ValueBindingContext context) + { + var bindingData = new Dictionary(StringComparer.OrdinalIgnoreCase); + + if (value is WebPubSubTriggerEvent triggerEvent) + { + AddBindingData(bindingData, triggerEvent); + + return Task.FromResult(new TriggerData(new WebPubSubTriggerValueProvider(_parameterInfo, triggerEvent), bindingData) + { + ReturnValueProvider = new TriggerReturnValueProvider(triggerEvent.TaskCompletionSource), + }); + } + + return Task.FromResult(null); + } + + public Task CreateListenerAsync(ListenerFactoryContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // Get listener key from attributes. + var hub = Utilities.FirstOrDefault(_attribute.Hub, _options.Hub); + if (string.IsNullOrEmpty(hub)) + { + throw new ArgumentNullException("Hub name should be configured in either attribute or appsettings."); + } + var attributeName = $"{hub}.{_attribute.EventType}.{_attribute.EventName}".ToLower(); + var listernerKey = attributeName; + + return Task.FromResult(new WebPubSubListener(context.Executor, listernerKey, _dispatcher)); + } + + public ParameterDescriptor ToParameterDescriptor() + { + return new ParameterDescriptor + { + Name = _parameterInfo.Name, + }; + } + + private void AddBindingData(Dictionary bindingData, WebPubSubTriggerEvent triggerEvent) + { + bindingData.Add(nameof(triggerEvent.ConnectionContext), triggerEvent.ConnectionContext); + bindingData.Add(nameof(triggerEvent.Message), triggerEvent.Message); + bindingData.Add(nameof(triggerEvent.DataType), triggerEvent.DataType); + bindingData.Add(nameof(triggerEvent.Claims), triggerEvent.Claims); + bindingData.Add(nameof(triggerEvent.Query), triggerEvent.Query); + bindingData.Add(nameof(triggerEvent.Reason), triggerEvent.Reason); + bindingData.Add(nameof(triggerEvent.Subprotocols), triggerEvent.Subprotocols); + bindingData.Add(nameof(triggerEvent.ClientCertificaties), triggerEvent.ClientCertificaties); + } + + /// + /// Defined what other bindings can use and return value. + /// + private IReadOnlyDictionary CreateBindingContract(ParameterInfo parameterInfo) + { + var contract = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "$return", typeof(object).MakeByRefType() }, + }; + + contract.Add(parameterInfo.Name, parameterInfo.ParameterType); + + return contract; + } + + /// + /// A provider that responsible for providing value in various type to be bond to function method parameter. + /// + internal class WebPubSubTriggerValueProvider : IValueBinder + { + private readonly ParameterInfo _parameter; + private readonly WebPubSubTriggerEvent _triggerEvent; + + public WebPubSubTriggerValueProvider(ParameterInfo parameter, WebPubSubTriggerEvent triggerEvent) + { + _parameter = parameter ?? throw new ArgumentNullException(nameof(parameter)); + _triggerEvent = triggerEvent ?? throw new ArgumentNullException(nameof(triggerEvent)); + } + + public Task GetValueAsync() + { + // Bind un-restrict name to default ConnectionContext with type recognized. + if (_parameter.ParameterType == typeof(ConnectionContext)) + { + return Task.FromResult(_triggerEvent.ConnectionContext); + } + + // Bind rest with name and type repected. + return Task.FromResult(GetValueByName(_parameter.Name, _parameter.ParameterType)); + } + + public string ToInvokeString() + { + return _parameter.Name; + } + + public Type Type => _parameter.ParameterType; + + // No use here + public Task SetValueAsync(object value, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + private object GetValueByName(string parameterName, Type targetType) + { + var property = Utilities.GetProperty(typeof(WebPubSubTriggerEvent), parameterName); + if (property != null) + { + var value = property.GetValue(_triggerEvent); + if (value == null || value.GetType() == targetType) + { + return value; + } + return ConvertTypeIfPossible(value, targetType); + } + return null; + } + + private object ConvertTypeIfPossible(object source, Type target) + { + if (source is WebPubSubMessage message) + { + return message.Convert(target); + } + if (target == typeof(JObject)) + { + return JToken.FromObject(source); + } + if (target == typeof(string)) + { + return JToken.FromObject(source).ToString(); + } + if (target == typeof(byte[])) + { + return Encoding.UTF8.GetBytes(JToken.FromObject(source).ToString()); + } + if (target == typeof(Stream)) + { + return new MemoryStream(Encoding.UTF8.GetBytes(JToken.FromObject(source).ToString())); + } + return null; + } + } + + /// + /// A provider to handle return value. + /// + internal class TriggerReturnValueProvider : IValueBinder + { + private readonly TaskCompletionSource _tcs; + + public TriggerReturnValueProvider(TaskCompletionSource tcs) + { + _tcs = tcs; + } + + public Task GetValueAsync() + { + // Useless for return value provider + return null; + } + + public string ToInvokeString() + { + // Useless for return value provider + return string.Empty; + } + + public Type Type => typeof(object).MakeByRefType(); + + public Task SetValueAsync(object value, CancellationToken cancellationToken) + { + if (value is string strValue) + { + var converted = ConvertToResponseIfPossible(JObject.Parse(strValue)); + _tcs.TrySetResult(converted); + } + else if (value is JObject jValue) + { + var converted = ConvertToResponseIfPossible(jValue); + _tcs.TrySetResult(converted); + } + else + { + _tcs.TrySetResult(value); + } + return Task.CompletedTask; + } + + internal static object ConvertToResponseIfPossible(JObject value) + { + // try cast by required field in order. + if (value["code"] != null) + { + return value.ToObject(); + } + + if (value["message"] != null) + { + return value.ToObject(); + } + + var connect = value.ToObject(); + if (connect != null) + { + return connect; + } + + // return null and not supported response will be ignored. + return null; + } + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBindingProvider.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBindingProvider.cs new file mode 100644 index 000000000000..8cd1c7e0cd09 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBindingProvider.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Reflection; +using System.Threading.Tasks; + +using Microsoft.Azure.WebJobs.Host.Triggers; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal class WebPubSubTriggerBindingProvider : ITriggerBindingProvider + { + private readonly IWebPubSubTriggerDispatcher _dispatcher; + private readonly WebPubSubOptions _options; + + public WebPubSubTriggerBindingProvider(IWebPubSubTriggerDispatcher dispatcher, WebPubSubOptions options) + { + _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + _options = options ?? throw new ArgumentNullException(nameof(options)); + } + + public Task TryCreateAsync(TriggerBindingProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var parameterInfo = context.Parameter; + var attribute = parameterInfo.GetCustomAttribute(false); + if (attribute == null) + { + return Task.FromResult(null); + } + + return Task.FromResult(new WebPubSubTriggerBinding(parameterInfo, attribute, _options, _dispatcher)); + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs new file mode 100644 index 000000000000..d17cd74d18b9 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs @@ -0,0 +1,261 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Azure.WebJobs.Host.Executors; +using Microsoft.Extensions.Primitives; +using Newtonsoft.Json; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal class WebPubSubTriggerDispatcher : IWebPubSubTriggerDispatcher + { + private Dictionary _listeners = new Dictionary(); + + public void AddListener(string key, WebPubSubListener listener) + { + if (_listeners.ContainsKey(key)) + { + throw new ArgumentException($"Duplicated binding attribute find: {string.Join(",", key.Split('.'))}"); + } + _listeners.Add(key, listener); + } + + public async Task ExecuteAsync(HttpRequestMessage req, + HashSet allowedHosts, + HashSet accessKeys, + CancellationToken token = default) + { + // Handle service abuse check. + if (RespondToServiceAbuseCheck(req, allowedHosts, out var abuseResponse)) + { + return abuseResponse; + } + + if (!TryParseRequest(req, out var context)) + { + return new HttpResponseMessage(HttpStatusCode.BadRequest); + } + + if (!ValidateSignature(context.ConnectionId, context.Signature, accessKeys)) + { + return new HttpResponseMessage(HttpStatusCode.Unauthorized); + } + + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var function = GetFunctionName(context); + + if (_listeners.TryGetValue(function, out var executor)) + { + WebPubSubMessage message = null; + MessageDataType dataType = MessageDataType.Text; + IDictionary claims = null; + IDictionary query = null; + string[] subprotocols = null; + ClientCertificateInfo[] certificates = null; + string reason = null; + + var requestType = Utilities.GetRequestType(context.EventType, context.EventName); + switch (requestType) + { + case RequestType.Connect: + { + var content = await req.Content.ReadAsStringAsync().ConfigureAwait(false); + var request = JsonConvert.DeserializeObject(content); + claims = request.Claims; + subprotocols = request.Subprotocols; + query = request.Query; + certificates = request.ClientCertificates; + break; + } + case RequestType.Disconnect: + { + var content = await req.Content.ReadAsStringAsync().ConfigureAwait(false); + var request = JsonConvert.DeserializeObject(content); + reason = request.Reason; + break; + } + case RequestType.User: + { + if (!ValidateContentType(req.Content.Headers.ContentType.MediaType, out dataType)) + { + return new HttpResponseMessage(HttpStatusCode.BadRequest) + { + Content = new StringContent($"{Constants.ErrorMessages.NotSupportedDataType}{req.Content.Headers.ContentType.MediaType}") + }; + } + + var payload = await req.Content.ReadAsByteArrayAsync().ConfigureAwait(false); + message = new WebPubSubMessage(payload); + break; + } + default: + break; + } + + var triggerEvent = new WebPubSubTriggerEvent + { + ConnectionContext = context, + Message = message, + DataType = dataType, + Claims = claims, + Query = query, + Subprotocols = subprotocols, + ClientCertificaties = certificates, + Reason = reason, + TaskCompletionSource = tcs + }; + await executor.Executor.TryExecuteAsync(new TriggeredFunctionData + { + TriggerValue = triggerEvent + }, token); + + // After function processed, return on-hold event reponses. + if (requestType.IsSyncMethod()) + { + try + { + using (token.Register(() => tcs.TrySetCanceled())) + { + var response = await tcs.Task.ConfigureAwait(false); + if (response is ErrorResponse error) + { + return Utilities.BuildErrorResponse(error); + } + else if (requestType == RequestType.Connect && response is ConnectResponse connect) + { + return Utilities.BuildResponse(connect); + } + else if (requestType == RequestType.User && response is MessageResponse msgResponse) + { + return Utilities.BuildResponse(msgResponse); + } + } + } + catch (Exception ex) + { + var error = new ErrorResponse(WebPubSubErrorCode.ServerError, ex.Message); + return Utilities.BuildErrorResponse(error); + } + } + + return new HttpResponseMessage(HttpStatusCode.OK); + } + // No function map to current request + return new HttpResponseMessage(HttpStatusCode.NotFound); + } + + private static bool TryParseRequest(HttpRequestMessage request, out ConnectionContext context) + { + // ConnectionId is required in upstream request, and method is POST. + if (!request.Headers.Contains(Constants.Headers.CloudEvents.ConnectionId) + || request.Method != HttpMethod.Post) + { + context = null; + return false; + } + + context = new ConnectionContext(); + try + { + context.ConnectionId = request.Headers.GetValues(Constants.Headers.CloudEvents.ConnectionId).FirstOrDefault(); + context.Hub = request.Headers.GetValues(Constants.Headers.CloudEvents.Hub).FirstOrDefault(); + context.EventType = Utilities.GetEventType(request.Headers.GetValues(Constants.Headers.CloudEvents.Type).FirstOrDefault()); + context.EventName = request.Headers.GetValues(Constants.Headers.CloudEvents.EventName).FirstOrDefault(); + context.Signature = request.Headers.GetValues(Constants.Headers.CloudEvents.Signature).FirstOrDefault(); + context.Headers = request.Headers.ToDictionary(x => x.Key, v => new StringValues(v.Value.ToArray()), StringComparer.OrdinalIgnoreCase); + + // UserId is optional, e.g. connect + if (request.Headers.TryGetValues(Constants.Headers.CloudEvents.UserId, out var values)) + { + context.UserId = values.FirstOrDefault(); + } + } + catch (Exception) + { + return false; + } + + return true; + } + + private static bool ValidateSignature(string connectionId, string signature, HashSet accessKeys) + { + foreach (var accessKey in accessKeys) + { + var signatures = Utilities.GetSignatureList(signature); + if (signatures == null) + { + continue; + } + using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(accessKey))) + { + var hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(connectionId)); + var hash = "sha256=" + BitConverter.ToString(hashBytes).Replace("-", ""); + if (signatures.Contains(hash, StringComparer.OrdinalIgnoreCase)) + { + return true; + } + else + { + continue; + } + } + } + return false; + } + + private static bool ValidateContentType(string mediaType, out MessageDataType dataType) + { + try + { + dataType = Utilities.GetDataType(mediaType); + return true; + } + catch (Exception) + { + dataType = MessageDataType.Binary; + return false; + } + } + + private static string GetFunctionName(ConnectionContext context) + { + return $"{context.Hub}.{context.EventType}.{context.EventName}".ToLower(); + } + + private static bool RespondToServiceAbuseCheck(HttpRequestMessage req, HashSet allowedHosts, out HttpResponseMessage response) + { + response = new HttpResponseMessage(); + // TODO: remove Get when function core is fully supported and AWPS service is updated. + if (req.Method == HttpMethod.Options || req.Method == HttpMethod.Get) + { + var hosts = req.Headers.GetValues(Constants.Headers.WebHookRequestOrigin); + if (hosts != null && hosts.Count() > 0) + { + foreach (var item in allowedHosts) + { + if (hosts.Contains(item)) + { + response.Headers.Add(Constants.Headers.WebHookAllowedOrigin, hosts); + return true; + } + } + response.StatusCode = HttpStatusCode.BadRequest; + } + return true; + } + return false; + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerEvent.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerEvent.cs new file mode 100644 index 000000000000..75077852fd8c --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerEvent.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal class WebPubSubTriggerEvent + { + /// + /// Web PubSub common request context from cloud event headers. + /// + public ConnectionContext ConnectionContext { get; set; } + + public WebPubSubMessage Message { get; set; } + + public MessageDataType DataType { get; set; } + + public string[] Subprotocols { get; set; } + + public IDictionary Claims { get; set; } + + public IDictionary Query { get; set; } + + public ClientCertificateInfo[] ClientCertificaties { get; set; } + + public string Reason { get; set; } + + /// + /// A TaskCompletionSource will set result when the function invocation has finished. + /// + public TaskCompletionSource TaskCompletionSource { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs new file mode 100644 index 000000000000..c354c920e5dc --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security.Claims; +using System.Text; + +using Microsoft.IdentityModel.Tokens; +using Newtonsoft.Json; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal static class Utilities + { + private const int MaxTokenLength = 4096; + private static readonly char[] HeaderSeparator = { ',' }; + + private static readonly JwtSecurityTokenHandler JwtTokenHandler = new JwtSecurityTokenHandler(); + + public static MediaTypeHeaderValue GetMediaType(MessageDataType dataType) => new MediaTypeHeaderValue(GetContentType(dataType)); + + public static string GetContentType(MessageDataType dataType) => + dataType switch + { + MessageDataType.Binary => Constants.ContentTypes.BinaryContentType, + MessageDataType.Text => Constants.ContentTypes.PlainTextContentType, + MessageDataType.Json => Constants.ContentTypes.JsonContentType, + // Default set binary type to align with service side logic + _ => Constants.ContentTypes.BinaryContentType + }; + + public static MessageDataType GetDataType(string mediaType) => + mediaType switch + { + Constants.ContentTypes.BinaryContentType => MessageDataType.Binary, + Constants.ContentTypes.JsonContentType => MessageDataType.Json, + Constants.ContentTypes.PlainTextContentType => MessageDataType.Text, + _ => throw new ArgumentException($"{Constants.ErrorMessages.NotSupportedDataType}{mediaType}") + }; + + public static WebPubSubEventType GetEventType(string ceType) + { + return ceType.StartsWith(Constants.Headers.CloudEvents.TypeSystemPrefix) ? + WebPubSubEventType.System : + WebPubSubEventType.User; + } + + public static HttpResponseMessage BuildResponse(MessageResponse response) + { + HttpResponseMessage result = new HttpResponseMessage(); + + if (response.Message != null) + { + result.Content = new StreamContent(response.Message.ToStream()); + } + result.Content.Headers.ContentType = GetMediaType(response.DataType); + + return result; + } + + public static HttpResponseMessage BuildResponse(ConnectResponse response) + { + HttpResponseMessage result = new HttpResponseMessage(); + + var connectEvent = new ConnectEventResponse + { + UserId = response.UserId, + Groups = response.Groups, + Subprotocol = response.Subprotocol, + Roles = response.Roles + }; + result.Content = new StringContent(JsonConvert.SerializeObject(connectEvent)); + + return result; + } + + public static HttpResponseMessage BuildErrorResponse(ErrorResponse error) + { + HttpResponseMessage result = new HttpResponseMessage(); + + result.StatusCode = GetStatusCode(error.Code); + result.Content = new StringContent(error.ErrorMessage); + return result; + } + + public static HttpStatusCode GetStatusCode(WebPubSubErrorCode errorCode) => + errorCode switch + { + WebPubSubErrorCode.UserError => HttpStatusCode.BadRequest, + WebPubSubErrorCode.Unauthorized => HttpStatusCode.Unauthorized, + WebPubSubErrorCode.ServerError => HttpStatusCode.InternalServerError, + _ => HttpStatusCode.InternalServerError + }; + + public static IReadOnlyList GetSignatureList(string signatures) + { + if (string.IsNullOrEmpty(signatures)) + { + return default; + } + + return signatures.Split(HeaderSeparator, StringSplitOptions.RemoveEmptyEntries); + } + + public static PropertyInfo[] GetProperties(Type type) + { + return type.GetProperties(BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + } + + public static PropertyInfo GetProperty(Type type, string name) + { + return type.GetProperty(name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + } + + public static string FirstOrDefault(params string[] values) + { + return values.FirstOrDefault(v => !string.IsNullOrEmpty(v)); + } + + public static RequestType GetRequestType(WebPubSubEventType eventType, string eventName) + { + if (eventType == WebPubSubEventType.User) + { + return RequestType.User; + } + if (eventName.Equals(Constants.Events.ConnectEvent)) + { + return RequestType.Connect; + } + if (eventName.Equals(Constants.Events.DisconnectedEvent)) + { + return RequestType.Disconnect; + } + return RequestType.Ignored; + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubAttribute.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubAttribute.cs new file mode 100644 index 000000000000..befee1569ca6 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubAttribute.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Microsoft.Azure.WebJobs.Description; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue)] + [Binding] + public class WebPubSubAttribute : Attribute + { + [ConnectionString] + public string ConnectionStringSetting { get; set; } = Constants.WebPubSubConnectionStringName; + + [AutoResolve] + public string Hub { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs new file mode 100644 index 000000000000..fca597130a58 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Security.Claims; + +using Microsoft.Azure.WebJobs.Description; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [AttributeUsage(AttributeTargets.ReturnValue | AttributeTargets.Parameter)] + [Binding] + public class WebPubSubConnectionAttribute : Attribute + { + [ConnectionString] + public string ConnectionStringSetting { get; set; } = Constants.WebPubSubConnectionStringName; + + [AutoResolve] + public string Hub { get; set; } + + [AutoResolve] + public string UserId { get; set; } + + internal IEnumerable GetClaims() + { + var claims = new List(); + if (!string.IsNullOrEmpty(UserId)) + { + claims.Add(new Claim(ClaimTypes.NameIdentifier, UserId)); + } + + return claims; + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubWebJobsStartup.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubWebJobsStartup.cs new file mode 100644 index 000000000000..066a3345bba1 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubWebJobsStartup.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Azure.WebJobs.Extensions.WebPubSub; +using Microsoft.Azure.WebJobs.Hosting; + +[assembly: WebJobsStartup(typeof(WebPubSubWebJobsStartup))] +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + public class WebPubSubWebJobsStartup : IWebJobsStartup + { + public void Configure(IWebJobsBuilder builder) + { + builder.AddWebPubSub(); + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/FakeTypeLocator.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/FakeTypeLocator.cs new file mode 100644 index 000000000000..e3e7d7cec9a4 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/FakeTypeLocator.cs @@ -0,0 +1,14 @@ +using System; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests +{ + internal class FakeTypeLocator + { + private Type type; + + public FakeTypeLocator(Type type) + { + this.type = type; + } + } +} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestExtensionConfig.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestExtensionConfig.cs new file mode 100644 index 000000000000..fcebce5c2531 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestExtensionConfig.cs @@ -0,0 +1,27 @@ +using System; +using Microsoft.Azure.WebJobs.Description; +using Microsoft.Azure.WebJobs.Host.Config; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests +{ + public class TestExtensionConfig : IExtensionConfigProvider + { + public void Initialize(ExtensionConfigContext context) + { + context.AddBindingRule(). + BindToInput(attr => attr.ToBeAutoResolve); + } + + [Binding] + private sealed class BindingDataAttribute : Attribute + { + public BindingDataAttribute(string toBeAutoResolve) + { + ToBeAutoResolve = toBeAutoResolve; + } + + [AutoResolve] + public string ToBeAutoResolve { get; set; } + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs new file mode 100644 index 000000000000..4623b6bbf2b8 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs @@ -0,0 +1,139 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Azure.WebJobs.Host.Config; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests +{ + internal static class TestHelpers + { + public static IHost NewHost(Type type, WebPubSubConfigProvider ext = null, Dictionary configuration = null, ILoggerProvider loggerProvider = null) + { + var builder = new HostBuilder() + .ConfigureServices(services => + { + services.AddSingleton(new FakeTypeLocator(type)); + if (ext != null) + { + services.AddSingleton(ext); + } + services.AddSingleton(new TestExtensionConfig()); + }) + .ConfigureWebJobs(webJobsBuilder => + { + webJobsBuilder.AddWebPubSub(); + webJobsBuilder.UseHostId(Guid.NewGuid().ToString("n")); + }) + .ConfigureLogging(logging => + { + logging.ClearProviders(); + logging.AddProvider(loggerProvider); + }); + + if (configuration != null) + { + builder.ConfigureAppConfiguration(b => + { + b.AddInMemoryCollection(configuration); + }); + } + + return builder.Build(); + } + + private sealed class FakeTypeLocator : ITypeLocator + { + private Type _type; + + public FakeTypeLocator(Type type) + { + _type = type; + } + + public IReadOnlyList GetTypes() + { + return new Type[] { _type }; + } + } + + public static JobHost GetJobHost(this IHost host) + { + return host.Services.GetService() as JobHost; + } + + private static string GetFormedType(WebPubSubEventType type, string eventName) + { + return type == WebPubSubEventType.User ? + $"{Constants.Headers.CloudEvents.TypeUserPrefix}{eventName}" : + $"{Constants.Headers.CloudEvents.TypeSystemPrefix}{eventName}"; + } + + public static HttpRequestMessage CreateHttpRequestMessage( + string hub, + WebPubSubEventType type, + string eventName, + string connectionId, + string[] signatures, + string contentType = Constants.ContentTypes.PlainTextContentType, + string httpMethod = "Post", + string host = null, + string userId = "testuser", + byte[] payload = null) + { + var context = new DefaultHttpContext(); + context.Request.ContentType = contentType; + context.Request.Method = httpMethod; + context.Request.Headers.Add(Constants.Headers.CloudEvents.Hub, hub); + context.Request.Headers.Add(Constants.Headers.CloudEvents.Type, GetFormedType(type, eventName)); + context.Request.Headers.Add(Constants.Headers.CloudEvents.EventName, eventName); + context.Request.Headers.Add(Constants.Headers.CloudEvents.ConnectionId, connectionId); + context.Request.Headers.Add(Constants.Headers.CloudEvents.Signature, string.Join(',', signatures)); + if (host != null) + { + context.Request.Headers.Add(Constants.Headers.WebHookRequestOrigin, host); + } + if (userId != null) + { + context.Request.Headers.Add(Constants.Headers.CloudEvents.UserId, userId); + } + context.Request.Body = payload == null ? Stream.Null : new MemoryStream(payload); + + return CreateHttpRequestMessageFromContext(context); + } + + private static HttpRequestMessage CreateHttpRequestMessageFromContext(HttpContext httpContext) + { + var httpRequest = httpContext.Request; + var uriString = + httpRequest.Scheme + "://" + + httpRequest.Host + + httpRequest.PathBase + + httpRequest.Path + + httpRequest.QueryString; + + var message = new HttpRequestMessage(new HttpMethod(httpRequest.Method), uriString); + + message.Properties[nameof(HttpContext)] = httpContext; + + message.Content = new StreamContent(httpRequest.Body); + + foreach (var header in httpRequest.Headers) + { + // Every header should be able to fit into one of the two header collections. + // Try message.Headers first since that accepts more of them. + if (!message.Headers.TryAddWithoutValidation(header.Key, (IEnumerable)header.Value)) + { + message.Content.Headers.TryAddWithoutValidation(header.Key, (IEnumerable)header.Value); + } + } + + return message; + } + } +} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListener.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListener.cs new file mode 100644 index 000000000000..1f86ccdfa0ec --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListener.cs @@ -0,0 +1,27 @@ +using Microsoft.Azure.WebJobs.Host.Listeners; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests +{ + public class TestListener : IListener + { + public void Cancel() + { + } + + public void Dispose() + { + } + + public Task StartAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListenerBase.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListenerBase.cs new file mode 100644 index 000000000000..4d3f2dba6110 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListenerBase.cs @@ -0,0 +1,6 @@ +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests +{ + public class TestListenerBase + { + } +} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs new file mode 100644 index 000000000000..672d4257f7e9 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs @@ -0,0 +1,103 @@ +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Xunit; +using static Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubTriggerBinding; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests +{ + public class JObjectTests + { + public static IEnumerable MessageTestData => + new List + { + new object[] {new WebPubSubMessage("Hello"), MessageDataType.Binary, "Hello" }, + new object[] {new WebPubSubMessage("Hello"), MessageDataType.Json, "Hello" }, + new object[] {new WebPubSubMessage("Hello"), MessageDataType.Text, "Hello" }, + new object[] {new WebPubSubMessage(Encoding.UTF8.GetBytes("Hello")) , MessageDataType.Binary, "Hello" }, + new object[] {new WebPubSubMessage(Encoding.UTF8.GetBytes("Hello")), MessageDataType.Json, "Hello" }, + new object[] {new WebPubSubMessage(Encoding.UTF8.GetBytes("Hello")), MessageDataType.Text, "Hello" }, + new object[] {new WebPubSubMessage(new MemoryStream(Encoding.UTF8.GetBytes("Hello"))), MessageDataType.Binary, "Hello" }, + new object[] {new WebPubSubMessage(new MemoryStream(Encoding.UTF8.GetBytes("Hello"))), MessageDataType.Json, "Hello" }, + new object[] {new WebPubSubMessage(new MemoryStream(Encoding.UTF8.GetBytes("Hello"))), MessageDataType.Text, "Hello" } + }; + + [Fact] + public void TestConvertFromJObject() + { + var wpsEvent = @"{ + ""operation"":""sendToUser"", + ""userId"": ""abc"", + ""message"": ""test"", + ""dataType"": ""text"" + }"; + + var jsevent = JObject.Parse(wpsEvent); + + var result = jsevent.ToObject(); + + Assert.Equal("test", result.Message.ToString()); + Assert.Equal(MessageDataType.Text, result.DataType); + Assert.Equal(WebPubSubOperation.SendToUser, result.Operation); + Assert.Equal("abc", result.UserId); + } + + [Theory] + [MemberData(nameof(MessageTestData))] + public void TestConvertMessageToAndFromJObject(WebPubSubMessage message, MessageDataType dataType, string expected) + { + var wpsEvent = new WebPubSubEvent + { + Operation = WebPubSubOperation.SendToConnection, + ConnectionId = "abc", + Message = message, + DataType = dataType + }; + + var jsObject = JObject.FromObject(wpsEvent); + + Assert.Equal("sendToConnection", jsObject["operation"].ToString()); + Assert.Equal("abc", jsObject["connectionId"].ToString()); + Assert.Equal(expected, jsObject["message"].ToString()); + + var result = jsObject.ToObject(); + + Assert.Equal(expected, result.Message.ToString()); + Assert.Equal(dataType, result.DataType); + Assert.Equal(WebPubSubOperation.SendToConnection, result.Operation); + Assert.Equal("abc", result.ConnectionId); + } + + [Fact] + public void ParseErrorResponse() + { + var test = @"{""code"":""unauthorized"",""errorMessage"":""not valid user.""}"; + var jObject = JObject.Parse(test); + + var result = TriggerReturnValueProvider.ConvertToResponseIfPossible(jObject); + + Assert.NotNull(result); + Assert.Equal(typeof(ErrorResponse), result.GetType()); + + var converted = (ErrorResponse)result; + Assert.Equal(WebPubSubErrorCode.Unauthorized, converted.Code); + Assert.Equal("not valid user.", converted.ErrorMessage); + } + + [Fact] + public void ParseConnectResponse() + { + var test = @"{""userId"":""aaa""}"; + var jObject = JObject.Parse(test); + + var result = TriggerReturnValueProvider.ConvertToResponseIfPossible(jObject); + + Assert.NotNull(result); + Assert.Equal(typeof(ConnectResponse), result.GetType()); + + var converted = (ConnectResponse)result; + Assert.Equal("aaa", converted.UserId); + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JobHostEndToEndTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JobHostEndToEndTests.cs new file mode 100644 index 000000000000..59ed51f6cf4b --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JobHostEndToEndTests.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests +{ + public class JobHostEndToEndTests + { + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj new file mode 100644 index 000000000000..adc274cffcfe --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj @@ -0,0 +1,24 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs new file mode 100644 index 000000000000..96934c3cde86 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs @@ -0,0 +1,61 @@ +using Moq; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests +{ + public class WebPubSubAsyncCollectorTests + { + [Fact] + public async Task AddAsync_WebPubSubEvent_SendAll() + { + var serviceMock = new Mock(); + var collector = new WebPubSubAsyncCollector(serviceMock.Object); + + var message = "new message"; + await collector.AddAsync(new WebPubSubEvent + { + Operation = WebPubSubOperation.SendToAll, + Message = new WebPubSubMessage(message), + DataType = MessageDataType.Text + }); + + serviceMock.Verify(c => c.SendToAll(It.IsAny()), Times.Once); + serviceMock.VerifyNoOtherCalls(); + + var actualData = (WebPubSubEvent)serviceMock.Invocations[0].Arguments[0]; + Assert.Equal(MessageDataType.Text, actualData.DataType); + Assert.Equal(message, actualData.Message.ToString()); + } + + //[Fact] + //public async Task AddAsync_WebPubSubEvent_SendAll() + //{ + // var serviceMock = new Mock(); + // var collector = new WebPubSubAsyncCollector(serviceMock.Object, "testhub"); + // + // var payload = Encoding.UTF8.GetBytes("new message"); + // await collector.AddAsync(new WebPubSubEvent + // { + // Operation = WebPubSubOperation.SendToAll, + // Message = new MemoryStream(payload), + // DataType = MessageDataType.Text + // }); + // + // serviceMock.Verify(c => c.SendToAll(It.IsAny()), Times.Once); + // serviceMock.VerifyNoOtherCalls(); + // + // var actualData = (WebPubSubEvent)serviceMock.Invocations[0].Arguments[0]; + // Assert.Equal(MessageDataType.Text, actualData.DataType); + // byte[] message = null; + // using (var memoryStream = new MemoryStream()) + // { + // actualData.Message.CopyTo(memoryStream); + // message = memoryStream.ToArray(); + // } + // Assert.Equal(payload, message); + //} + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs new file mode 100644 index 000000000000..128ac805c58e --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; +using System.Security.Claims; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests +{ + public class WebPubSubServiceTests + { + private const string NormConnectionString = "Endpoint=http://localhost;Port=8080;AccessKey=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGH;Version=1.0;"; + private const string SecConnectionString = "Endpoint=https://abc;AccessKey=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGH;Version=1.0;"; + + [Fact] + public void TestWebPubSubConnection() + { + var service = new WebPubSubService(NormConnectionString, "testHub"); + + var clientConnection = service.GetClientConnection(); + + Assert.NotNull(clientConnection); + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs new file mode 100644 index 000000000000..780c8bd2fc31 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs @@ -0,0 +1,126 @@ +using Microsoft.Azure.WebJobs.Host.Executors; +using Moq; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests +{ + public class WebPubSubTriggerDispatcherTests + { + private static (string ConnectionId, string AccessKey, string Signature) TestKey = + ("0f9c97a2f0bf4706afe87a14e0797b11", "7aab239577fd4f24bc919802fb629f5f", "sha256=7767effcb3946f3e1de039df4b986ef02c110b1469d02c0a06f41b3b727ab561"); + private const string TestHub = "testhub"; + private const WebPubSubEventType TestType = WebPubSubEventType.System; + private const string TestEvent = Constants.Events.ConnectedEvent; + + private static HashSet EmptySetting = new HashSet(); + private static HashSet ValidAccessKeys = new HashSet(new string[] { TestKey.AccessKey }); + private static string[] ValidSignature = new string[] { TestKey.Signature }; + + [Fact] + public async Task TestProcessRequest_ValidRequest() + { + var dispatcher = SetupDispatcher(); + var request = TestHelpers.CreateHttpRequestMessage(TestHub, TestType, TestEvent, TestKey.ConnectionId, ValidSignature); + var response = await dispatcher.ExecuteAsync(request, EmptySetting, ValidAccessKeys); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task TestProcessRequest_AllowNullUserId() + { + var dispatcher = SetupDispatcher(); + var request = TestHelpers.CreateHttpRequestMessage(TestHub, TestType, TestEvent, TestKey.ConnectionId, ValidSignature, userId: null); + var response = await dispatcher.ExecuteAsync(request, EmptySetting, ValidAccessKeys); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task TestProcessRequest_RouteNotFound() + { + var dispatcher = SetupDispatcher(); + var request = TestHelpers.CreateHttpRequestMessage("hub1", TestType, TestEvent, TestKey.ConnectionId, ValidSignature); + var response = await dispatcher.ExecuteAsync(request, EmptySetting, ValidAccessKeys); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task TestProcessRequest_SignatureInvalid() + { + var dispatcher = SetupDispatcher(); + var request = TestHelpers.CreateHttpRequestMessage(TestHub, TestType, TestEvent, TestKey.ConnectionId, new string[] { "abc" }); + var response = await dispatcher.ExecuteAsync(request, EmptySetting, ValidAccessKeys); + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + } + + [Fact] + public async Task TestProcessRequest_ConnectionIdNullBadRequest() + { + var dispatcher = SetupDispatcher(); + var request = TestHelpers.CreateHttpRequestMessage(TestHub, TestType, TestEvent, null, ValidSignature, httpMethod: "Delete"); + var response = await dispatcher.ExecuteAsync(request, EmptySetting, ValidAccessKeys); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public async Task TestProcessRequest_DeleteMethodBadRequest() + { + var dispatcher = SetupDispatcher(); + var request = TestHelpers.CreateHttpRequestMessage(TestHub, TestType, TestEvent, TestKey.ConnectionId, ValidSignature, httpMethod: "Delete"); + var response = await dispatcher.ExecuteAsync(request, EmptySetting, ValidAccessKeys); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Theory] + [InlineData("OPTIONS", "abc.com")] + [InlineData("GET", "abc.com")] + public async Task TestProcessRequest_AbuseProtectionValidOK(string method, string host) + { + var allowedHost = new HashSet(new string[] { host }); + var dispatcher = SetupDispatcher(); + var request = TestHelpers.CreateHttpRequestMessage(TestHub, TestType, TestEvent, TestKey.ConnectionId, new string[] { TestKey.Signature }, httpMethod: method, host: host); + var response = await dispatcher.ExecuteAsync(request, allowedHost, ValidAccessKeys); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Theory] + [InlineData("OPTIONS", "abc.com")] + [InlineData("GET", "abc.com")] + public async Task TestProcessRequest_AbuseProtectionInvalidBadRequest(string method, string allowedHost) + { + var allowedHosts = new HashSet(new string[] { allowedHost }); + var testhost = "def.com"; + var dispatcher = SetupDispatcher(); + var request = TestHelpers.CreateHttpRequestMessage(TestHub, TestType, TestEvent, TestKey.ConnectionId, new string[] { TestKey.Signature }, httpMethod: method, host: testhost); + var response = await dispatcher.ExecuteAsync(request, allowedHosts, ValidAccessKeys); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Theory] + [InlineData("application/xml", HttpStatusCode.BadRequest)] + public async Task TestProcessRequest_MessageMediaTypes(string mediaType, HttpStatusCode expectedCode) + { + var dispatcher = SetupDispatcher(TestHub, WebPubSubEventType.User, Constants.Events.MessageEvent); + var request = TestHelpers.CreateHttpRequestMessage(TestHub, WebPubSubEventType.User, Constants.Events.MessageEvent, TestKey.ConnectionId, ValidSignature, contentType: mediaType); + var response = await dispatcher.ExecuteAsync(request, EmptySetting, ValidAccessKeys).ConfigureAwait(false); + Assert.Equal(expectedCode, response.StatusCode); + } + + private WebPubSubTriggerDispatcher SetupDispatcher(string hub = TestHub, WebPubSubEventType type = TestType, string eventName = TestEvent) + { + var funcName = $"{hub}.{type}.{eventName}".ToLower(); + var dispatcher = new WebPubSubTriggerDispatcher(); + var executor = new Mock(); + executor.Setup(f => f.TryExecuteAsync(It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(new FunctionResult(true))); + var listener = new WebPubSubListener(executor.Object, funcName, dispatcher); + + dispatcher.AddListener(funcName, listener); + + return dispatcher; + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs new file mode 100644 index 000000000000..66af1b08b98b --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using Xunit; +using Newtonsoft.Json; +using Newtonsoft.Json.Bson; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests +{ + public class WebPubSubTriggerValueProviderTests + { + [Theory] + [InlineData("connectioncontext")] + [InlineData("reason")] + [InlineData("message")] + public void TestGetValueByName_Valid(string name) + { + var triggerEvent = new WebPubSubTriggerEvent + { + ConnectionContext = new ConnectionContext + { + ConnectionId = "000000", + EventName = "message", + EventType = WebPubSubEventType.User, + Hub = "testhub", + UserId = "user1" + }, + Reason = "reason", + Message = new WebPubSubMessage("message"), + }; + + var value = typeof(WebPubSubTriggerEvent) + .GetProperty(name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(triggerEvent); + Assert.NotNull(value); + } + } +} From 6b8e26272c972554c664c7adf4d7cca87c482a10 Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Fri, 16 Apr 2021 15:23:53 +0800 Subject: [PATCH 02/17] fix warns and dependencies. --- .../src/Bindings/WebPubSubAsyncCollector.cs | 6 +- .../src/Bindings/WebPubSubConnection.cs | 7 -- ....Azure.WebJobs.Extensions.WebPubSub.csproj | 4 +- .../tests/Common/TestHelpers.cs | 94 ++++++++++--------- .../tests/JObjectTests.cs | 9 +- .../tests/JobHostEndToEndTests.cs | 5 +- ....WebJobs.Extensions.WebPubSub.Tests.csproj | 7 +- .../tests/WebPubSubAsyncCollectorTests.cs | 7 +- .../tests/WebPubSubServiceTests.cs | 7 +- .../tests/WebPubSubTriggerDispatcherTests.cs | 10 +- .../WebPubSubTriggerValueProviderTests.cs | 11 +-- 11 files changed, 88 insertions(+), 79 deletions(-) diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubAsyncCollector.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubAsyncCollector.cs index 1f709c9684ec..a2b99f86387c 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubAsyncCollector.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubAsyncCollector.cs @@ -21,7 +21,7 @@ public async Task AddAsync(WebPubSubEvent item, CancellationToken cancellationTo { if (item == null) { - throw new ArgumentNullException("Binding Object."); + throw new ArgumentNullException(nameof(item)); } try @@ -29,7 +29,9 @@ public async Task AddAsync(WebPubSubEvent item, CancellationToken cancellationTo var method = typeof(IWebPubSubService).GetMethod(item.Operation.ToString(), BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); - await (Task)method.Invoke(_service, new object[] { item }); + var task = (Task)method.Invoke(_service, new object[] { item }); + + await task.ConfigureAwait(false); } catch (Exception ex) { diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubConnection.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubConnection.cs index cab1d90e3567..e7e1f0633388 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubConnection.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubConnection.cs @@ -12,13 +12,6 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] public class WebPubSubConnection { - public WebPubSubConnection(string baseUrl, string accessToken) - { - BaseUrl = baseUrl; - AccessToken = accessToken; - Url = $"{baseUrl}?access_token={accessToken}"; - } - public WebPubSubConnection(Uri url) { Url = url.ToString(); diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj index 7f7e4edad28b..94dcd60c630a 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj @@ -5,11 +5,13 @@ Microsoft.Azure.WebJobs.Extensions.WebPubSub 8.0 1.0.0-alpha.1 - $(NoWarn);AZC0001;CS1591 + $(NoWarn);AZC0001;CS1591;SA1636;CA1056 + true + diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs index 4623b6bbf2b8..caf0354c57bd 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Http; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using Microsoft.Azure.WebJobs.Host.Config; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -8,6 +10,7 @@ using System.Collections.Generic; using System.IO; using System.Net.Http; +using System.Net.Http.Headers; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests { @@ -76,64 +79,71 @@ private static string GetFormedType(WebPubSubEventType type, string eventName) public static HttpRequestMessage CreateHttpRequestMessage( string hub, - WebPubSubEventType type, - string eventName, + WebPubSubEventType type, + string eventName, string connectionId, string[] signatures, - string contentType = Constants.ContentTypes.PlainTextContentType, + string contentType = Constants.ContentTypes.PlainTextContentType, string httpMethod = "Post", string host = null, string userId = "testuser", byte[] payload = null) { - var context = new DefaultHttpContext(); - context.Request.ContentType = contentType; - context.Request.Method = httpMethod; - context.Request.Headers.Add(Constants.Headers.CloudEvents.Hub, hub); - context.Request.Headers.Add(Constants.Headers.CloudEvents.Type, GetFormedType(type, eventName)); - context.Request.Headers.Add(Constants.Headers.CloudEvents.EventName, eventName); - context.Request.Headers.Add(Constants.Headers.CloudEvents.ConnectionId, connectionId); - context.Request.Headers.Add(Constants.Headers.CloudEvents.Signature, string.Join(',', signatures)); + var context = new HttpRequestMessage(); + context.Method = new HttpMethod(httpMethod); + context.Headers.Add(Constants.Headers.CloudEvents.Hub, hub); + context.Headers.Add(Constants.Headers.CloudEvents.Type, GetFormedType(type, eventName)); + context.Headers.Add(Constants.Headers.CloudEvents.EventName, eventName); + context.Headers.Add(Constants.Headers.CloudEvents.ConnectionId, connectionId); + context.Headers.Add(Constants.Headers.CloudEvents.Signature, string.Join(",", signatures)); if (host != null) { - context.Request.Headers.Add(Constants.Headers.WebHookRequestOrigin, host); + context.Headers.Add(Constants.Headers.WebHookRequestOrigin, host); } if (userId != null) { - context.Request.Headers.Add(Constants.Headers.CloudEvents.UserId, userId); + context.Headers.Add(Constants.Headers.CloudEvents.UserId, userId); } - context.Request.Body = payload == null ? Stream.Null : new MemoryStream(payload); - - return CreateHttpRequestMessageFromContext(context); - } - - private static HttpRequestMessage CreateHttpRequestMessageFromContext(HttpContext httpContext) - { - var httpRequest = httpContext.Request; - var uriString = - httpRequest.Scheme + "://" + - httpRequest.Host + - httpRequest.PathBase + - httpRequest.Path + - httpRequest.QueryString; - - var message = new HttpRequestMessage(new HttpMethod(httpRequest.Method), uriString); + context.Content = payload == null ? null : new StreamContent(new MemoryStream(payload)); + context.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType); - message.Properties[nameof(HttpContext)] = httpContext; - - message.Content = new StreamContent(httpRequest.Body); - - foreach (var header in httpRequest.Headers) + foreach (var header in context.Headers) { - // Every header should be able to fit into one of the two header collections. - // Try message.Headers first since that accepts more of them. - if (!message.Headers.TryAddWithoutValidation(header.Key, (IEnumerable)header.Value)) - { - message.Content.Headers.TryAddWithoutValidation(header.Key, (IEnumerable)header.Value); - } + context.Content.Headers.TryAddWithoutValidation(header.Key, header.Value); } - return message; + return context; + + //return CreateHttpRequestMessageFromContext(context); } + + //private static HttpRequestMessage CreateHttpRequestMessageFromContext(HttpContext httpContext) + //{ + // var httpRequest = httpContext.Request; + // var uriString = + // httpRequest.Scheme + "://" + + // httpRequest.Host + + // httpRequest.PathBase + + // httpRequest.Path + + // httpRequest.QueryString; + // + // var message = new HttpRequestMessage(new HttpMethod(httpRequest.Method), uriString); + // + // message.Properties[nameof(HttpContext)] = httpContext; + // + // message.Content = new StreamContent(httpRequest.Body); + // + // foreach (var header in httpRequest.Headers) + // { + // // Every header should be able to fit into one of the two header collections. + // // Try message.Headers first since that accepts more of them. + // if (!message.Headers.TryAddWithoutValidation(header.Key, (IEnumerable)header.Value)) + // { + // message.Content.Headers.TryAddWithoutValidation(header.Key, (IEnumerable)header.Value); + // } + // } + // + // return message; + //} } } \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs index 672d4257f7e9..9765554f0eba 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs @@ -1,7 +1,10 @@ -using Newtonsoft.Json.Linq; +// 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.IO; using System.Text; +using Newtonsoft.Json.Linq; using Xunit; using static Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubTriggerBinding; @@ -32,9 +35,9 @@ public void TestConvertFromJObject() ""message"": ""test"", ""dataType"": ""text"" }"; - + var jsevent = JObject.Parse(wpsEvent); - + var result = jsevent.ToObject(); Assert.Equal("test", result.Message.ToString()); diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JobHostEndToEndTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JobHostEndToEndTests.cs index 59ed51f6cf4b..1ac11c4a18a3 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JobHostEndToEndTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JobHostEndToEndTests.cs @@ -1,4 +1,7 @@ -using System; +// 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.Text; diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj index adc274cffcfe..909c5c7c237a 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj @@ -1,17 +1,16 @@  - netcoreapp3.1 - + $(RequiredTargetFrameworks) + SA1636 false - - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs index 96934c3cde86..1eaba55dea2e 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs @@ -1,7 +1,8 @@ -using Moq; -using System.IO; -using System.Text; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System.Threading.Tasks; +using Moq; using Xunit; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs index 128ac805c58e..bb8bebdc0375 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs @@ -1,8 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using Xunit; -using System.Security.Claims; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests { diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs index 780c8bd2fc31..3b00fba34d89 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs @@ -1,17 +1,19 @@ -using Microsoft.Azure.WebJobs.Host.Executors; -using Moq; +// 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.Threading; using System.Threading.Tasks; +using Microsoft.Azure.WebJobs.Host.Executors; +using Moq; using Xunit; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests { public class WebPubSubTriggerDispatcherTests { - private static (string ConnectionId, string AccessKey, string Signature) TestKey = - ("0f9c97a2f0bf4706afe87a14e0797b11", "7aab239577fd4f24bc919802fb629f5f", "sha256=7767effcb3946f3e1de039df4b986ef02c110b1469d02c0a06f41b3b727ab561"); + private static (string ConnectionId, string AccessKey, string Signature) TestKey = ("0f9c97a2f0bf4706afe87a14e0797b11", "7aab239577fd4f24bc919802fb629f5f", "sha256=7767effcb3946f3e1de039df4b986ef02c110b1469d02c0a06f41b3b727ab561"); private const string TestHub = "testhub"; private const WebPubSubEventType TestType = WebPubSubEventType.System; private const string TestEvent = Constants.Events.ConnectedEvent; diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs index 66af1b08b98b..26fb0f465231 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System.Reflection; -using System.Text; using Xunit; -using Newtonsoft.Json; -using Newtonsoft.Json.Bson; -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; -using Newtonsoft.Json.Linq; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests { From d9445f783232e08ac12292a8e51554485fba26c5 Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Mon, 19 Apr 2021 11:32:11 +0800 Subject: [PATCH 03/17] fix styling check. --- .../src/Bindings/ConnectResponse.cs | 24 +++++++++ .../src/Bindings/ErrorResponse.cs | 29 +++++++++++ .../src/Bindings/MessageResponse.cs | 17 +++++++ .../src/Bindings/ServiceResponse.cs | 50 ------------------- .../src/Bindings/WebPubSubMessage.cs | 31 +----------- .../Bindings/WebPubSubMessageExtensions.cs | 38 ++++++++++++++ .../Bindings/WebPubSubMessageJsonConverter.cs | 4 +- .../src/Config/WebPubSubConfigProvider.cs | 7 ++- .../Config/WebPubSubJobsBuilderExtensions.cs | 1 - .../src/Properties/AssemblyInfo.cs | 4 +- .../src/Services/RequestType.cs | 8 --- .../src/Services/ServiceConfigParser.cs | 8 +-- .../src/Trigger/WebPubSubListener.cs | 2 +- .../src/Trigger/WebPubSubTriggerAttribute.cs | 4 +- .../src/Trigger/WebPubSubTriggerBinding.cs | 10 ++-- .../src/Trigger/WebPubSubTriggerDispatcher.cs | 14 +++--- .../src/Utilities.cs | 5 +- .../src/WebPubSubConnectionAttribute.cs | 1 - .../tests/Common/FakeTypeLocator.cs | 5 +- .../tests/Common/TestExtensionConfig.cs | 5 +- .../tests/Common/TestHelpers.cs | 41 +++------------ .../tests/Common/TestListener.cs | 5 +- .../tests/Common/TestListenerBase.cs | 5 +- .../tests/WebPubSubTriggerDispatcherTests.cs | 3 +- 24 files changed, 160 insertions(+), 161 deletions(-) create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectResponse.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ErrorResponse.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/MessageResponse.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageExtensions.cs diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectResponse.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectResponse.cs new file mode 100644 index 000000000000..769f2dd3b3f2 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectResponse.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class ConnectResponse : ServiceResponse + { + [JsonProperty(Required = Required.Default)] + public string UserId { get; set; } + + [JsonProperty(Required = Required.Default)] + public string[] Groups { get; set; } + + [JsonProperty(Required = Required.Default)] + public string Subprotocol { get; set; } + + [JsonProperty(Required = Required.Default)] + public string[] Roles { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ErrorResponse.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ErrorResponse.cs new file mode 100644 index 000000000000..1cdb5ed3cef9 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ErrorResponse.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class ErrorResponse : ServiceResponse + { + [JsonProperty(Required = Required.Always)] + public WebPubSubErrorCode Code { get; set; } + + [JsonProperty(Required = Required.Default)] + public string ErrorMessage { get; set; } + + public ErrorResponse(WebPubSubErrorCode code, string message = null) + { + Code = code; + ErrorMessage = message; + } + + [JsonConstructor] + public ErrorResponse() + { + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/MessageResponse.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/MessageResponse.cs new file mode 100644 index 000000000000..15e190bbb311 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/MessageResponse.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class MessageResponse : ServiceResponse + { + [JsonProperty(Required = Required.Always)] + public WebPubSubMessage Message { get; set; } + + public MessageDataType DataType { get; set; } = MessageDataType.Text; + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ServiceResponse.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ServiceResponse.cs index f3cae5b556f1..a51147948f50 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ServiceResponse.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ServiceResponse.cs @@ -1,59 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { public abstract class ServiceResponse { } - - [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] - public class MessageResponse : ServiceResponse - { - [JsonProperty(Required = Required.Always)] - public WebPubSubMessage Message { get; set; } - - public MessageDataType DataType { get; set; } = MessageDataType.Text; - } - - [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] - public class ConnectResponse : ServiceResponse - { - [JsonProperty(Required = Required.Default)] - public string UserId { get; set; } - - [JsonProperty(Required = Required.Default)] - public string[] Groups { get; set; } - - [JsonProperty(Required = Required.Default)] - public string Subprotocol { get; set; } - - [JsonProperty(Required = Required.Default)] - public string[] Roles { get; set; } - } - - [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] - public class ErrorResponse : ServiceResponse - { - [JsonProperty(Required = Required.Always)] - public WebPubSubErrorCode Code { get; set; } - - [JsonProperty(Required = Required.Default)] - public string ErrorMessage { get; set; } - - - public ErrorResponse(WebPubSubErrorCode code, string message = null) - { - Code = code; - ErrorMessage = message; - } - - [JsonConstructor] - public ErrorResponse() - { - } - } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessage.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessage.cs index 1d677276268c..614bb0209e7d 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessage.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessage.cs @@ -5,7 +5,6 @@ using System.IO; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub @@ -14,7 +13,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub /// Message to communicate with service /// [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] - [JsonConverter(typeof(MessageJsonConverter))] + [JsonConverter(typeof(WebPubSubMessageJsonConverter))] public class WebPubSubMessage { private readonly BinaryData _body; @@ -54,32 +53,4 @@ public BinaryData ToBinaryData() return _body; } } - - internal static class MessageExtensions - { - public static object Convert(this WebPubSubMessage message, Type targetType) - { - if (targetType == typeof(JObject)) - { - return JObject.FromObject(message.ToArray()); - } - - if (targetType == typeof(Stream)) - { - return message.ToStream(); - } - - if (targetType == typeof(byte[])) - { - return message.ToArray(); - } - - if (targetType == typeof(string)) - { - return message.ToString(); - } - - return null; - } - } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageExtensions.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageExtensions.cs new file mode 100644 index 000000000000..fd52f1301954 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; + +using Newtonsoft.Json.Linq; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal static class WebPubSubMessageExtensions + { + public static object Convert(this WebPubSubMessage message, Type targetType) + { + if (targetType == typeof(JObject)) + { + return JObject.FromObject(message.ToArray()); + } + + if (targetType == typeof(Stream)) + { + return message.ToStream(); + } + + if (targetType == typeof(byte[])) + { + return message.ToArray(); + } + + if (targetType == typeof(string)) + { + return message.ToString(); + } + + return null; + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageJsonConverter.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageJsonConverter.cs index bd6708ead8f8..c98f88275608 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageJsonConverter.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageJsonConverter.cs @@ -7,7 +7,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { - internal class MessageJsonConverter : JsonConverter + internal class WebPubSubMessageJsonConverter : JsonConverter { public override WebPubSubMessage ReadJson(JsonReader reader, Type objectType, WebPubSubMessage existingValue, bool hasExistingValue, JsonSerializer serializer) { @@ -15,7 +15,7 @@ public override WebPubSubMessage ReadJson(JsonReader reader, Type objectType, We return new WebPubSubMessage(jObject.ToString()); } - + public override void WriteJson(JsonWriter writer, WebPubSubMessage value, JsonSerializer serializer) { serializer.Serialize(writer, value.ToString()); diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs index dd563fbee6db..adab8ccc949b 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs @@ -44,7 +44,7 @@ public void Initialize(ExtensionConfigContext context) { if (context == null) { - throw new ArgumentNullException("context"); + throw new ArgumentNullException(nameof(context)); } if (string.IsNullOrEmpty(_options.ConnectionString)) @@ -130,8 +130,7 @@ private void ValidateConnectionString(string attributeConnectionString, string a if (string.IsNullOrEmpty(connectionString)) { - throw new InvalidOperationException(string.Format($"The Service connection string must be set either via an '{Constants.WebPubSubConnectionStringName}' app setting, via an '{Constants.WebPubSubConnectionStringName}' environment variable, or directly in code via {nameof(WebPubSubOptions)}.{nameof(WebPubSubOptions.ConnectionString)} or {{0}}.", - attributeConnectionStringName)); + throw new InvalidOperationException($"The Service connection string must be set either via an '{Constants.WebPubSubConnectionStringName}' app setting, via an '{Constants.WebPubSubConnectionStringName}' environment variable, or directly in code via {nameof(WebPubSubOptions)}.{nameof(WebPubSubOptions.ConnectionString)} or {attributeConnectionStringName}."); } } @@ -145,7 +144,7 @@ private void AddSettings(string connectionString) } } - private T ConvertFromJObject(JObject input) + private static T ConvertFromJObject(JObject input) { return input.ToObject(); } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubJobsBuilderExtensions.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubJobsBuilderExtensions.cs index d6ce652ab593..56635b0f83bc 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubJobsBuilderExtensions.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubJobsBuilderExtensions.cs @@ -10,7 +10,6 @@ public static class WebPubSubJobsBuilderExtensions { public static IWebJobsBuilder AddWebPubSub(this IWebJobsBuilder builder) { - if (builder == null) { throw new ArgumentNullException(nameof(builder)); diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Properties/AssemblyInfo.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Properties/AssemblyInfo.cs index 343540a98cbf..d60aea4c5502 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Properties/AssemblyInfo.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Properties/AssemblyInfo.cs @@ -3,5 +3,5 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests")] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file +[assembly: InternalsVisibleTo("Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d15ddcb29688295338af4b7686603fe614abd555e09efba8fb88ee09e1f7b1ccaeed2e8f823fa9eef3fdd60217fc012ea67d2479751a0b8c087a4185541b851bd8b16f8d91b840e51b1cb0ba6fe647997e57429265e85ef62d565db50a69ae1647d54d7bd855e4db3d8a91510e5bcbd0edfbbecaa20a7bd9ae74593daa7b11b4")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/RequestType.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/RequestType.cs index c4b456afb52b..b80bdb04315d 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/RequestType.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/RequestType.cs @@ -10,12 +10,4 @@ internal enum RequestType Disconnect, User } - - internal static class RequestTypeExtensions - { - public static bool IsSyncMethod(this RequestType requestType) - { - return requestType == RequestType.Connect || requestType == RequestType.User; - } - } } \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs index a67a0234f5bc..94811951cb26 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs @@ -23,16 +23,16 @@ public ServiceConfigParser(string connectionString) Endpoint = settings.ContainsKey("endpoint") ? settings["endpoint"] : - throw new ArgumentNullException(nameof(Endpoint)); + throw new ArgumentException(nameof(Endpoint)); AccessKey = settings.ContainsKey("accesskey") ? settings["accesskey"] : - throw new ArgumentNullException(nameof(AccessKey)); + throw new ArgumentException(nameof(AccessKey)); Version = settings.ContainsKey("version") ? settings["version"] : null; Port = settings.ContainsKey("port") ? settings["port"] : null; } - private Dictionary ParseConnectionString(string connectionString) + private static Dictionary ParseConnectionString(string connectionString) { if (string.IsNullOrEmpty(connectionString)) { @@ -44,7 +44,7 @@ private Dictionary ParseConnectionString(string connectionString try { - setting = items.Where(x => x.Length > 0).ToDictionary(x => x.Split('=')[0].ToLower(), y => y.Split('=')[1]); + setting = items.Where(x => x.Length > 0).ToDictionary(x => x.Split('=')[0], y => y.Split('=')[1], StringComparer.CurrentCultureIgnoreCase); } catch (Exception) { diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubListener.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubListener.cs index 180773eed5c6..04ae6431079e 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubListener.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubListener.cs @@ -12,7 +12,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { internal class WebPubSubListener : IListener { - public ITriggeredFunctionExecutor Executor { private set; get; } + public ITriggeredFunctionExecutor Executor { get; private set; } private readonly string _listenerKey; private readonly IWebPubSubTriggerDispatcher _dispatcher; diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerAttribute.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerAttribute.cs index 7c9939b65d33..9f4b03a2fb8a 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerAttribute.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerAttribute.cs @@ -14,7 +14,6 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub #pragma warning restore CS0618 // Type or member is obsolete public class WebPubSubTriggerAttribute : Attribute { - /// /// Used to map to method name automatically /// @@ -38,7 +37,7 @@ public WebPubSubTriggerAttribute(WebPubSubEventType eventType, string eventName) /// [AutoResolve] public string Hub { get; } - + /// /// The event of the request /// @@ -51,6 +50,5 @@ public WebPubSubTriggerAttribute(WebPubSubEventType eventType, string eventName) /// [AutoResolve] public WebPubSubEventType EventType { get; } - } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs index df1b760f8059..683c5a979369 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs @@ -66,9 +66,9 @@ public Task CreateListenerAsync(ListenerFactoryContext context) var hub = Utilities.FirstOrDefault(_attribute.Hub, _options.Hub); if (string.IsNullOrEmpty(hub)) { - throw new ArgumentNullException("Hub name should be configured in either attribute or appsettings."); + throw new ArgumentNullException($"Hub name should be configured in either attribute or appsettings."); } - var attributeName = $"{hub}.{_attribute.EventType}.{_attribute.EventName}".ToLower(); + var attributeName = $"{hub}.{_attribute.EventType}.{_attribute.EventName}"; var listernerKey = attributeName; return Task.FromResult(new WebPubSubListener(context.Executor, listernerKey, _dispatcher)); @@ -82,7 +82,7 @@ public ParameterDescriptor ToParameterDescriptor() }; } - private void AddBindingData(Dictionary bindingData, WebPubSubTriggerEvent triggerEvent) + private static void AddBindingData(Dictionary bindingData, WebPubSubTriggerEvent triggerEvent) { bindingData.Add(nameof(triggerEvent.ConnectionContext), triggerEvent.ConnectionContext); bindingData.Add(nameof(triggerEvent.Message), triggerEvent.Message); @@ -97,7 +97,7 @@ private void AddBindingData(Dictionary bindingData, WebPubSubTri /// /// Defined what other bindings can use and return value. /// - private IReadOnlyDictionary CreateBindingContract(ParameterInfo parameterInfo) + private static IReadOnlyDictionary CreateBindingContract(ParameterInfo parameterInfo) { var contract = new Dictionary(StringComparer.OrdinalIgnoreCase) { @@ -163,7 +163,7 @@ private object GetValueByName(string parameterName, Type targetType) return null; } - private object ConvertTypeIfPossible(object source, Type target) + private static object ConvertTypeIfPossible(object source, Type target) { if (source is WebPubSubMessage message) { diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs index d17cd74d18b9..a877044906f8 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs @@ -19,7 +19,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { internal class WebPubSubTriggerDispatcher : IWebPubSubTriggerDispatcher { - private Dictionary _listeners = new Dictionary(); + private Dictionary _listeners = new Dictionary(StringComparer.InvariantCultureIgnoreCase); public void AddListener(string key, WebPubSubListener listener) { @@ -30,8 +30,8 @@ public void AddListener(string key, WebPubSubListener listener) _listeners.Add(key, listener); } - public async Task ExecuteAsync(HttpRequestMessage req, - HashSet allowedHosts, + public async Task ExecuteAsync(HttpRequestMessage req, + HashSet allowedHosts, HashSet accessKeys, CancellationToken token = default) { @@ -118,10 +118,10 @@ public async Task ExecuteAsync(HttpRequestMessage req, await executor.Executor.TryExecuteAsync(new TriggeredFunctionData { TriggerValue = triggerEvent - }, token); + }, token).ConfigureAwait(false); // After function processed, return on-hold event reponses. - if (requestType.IsSyncMethod()) + if (requestType == RequestType.Connect || requestType == RequestType.User) { try { @@ -231,7 +231,7 @@ private static bool ValidateContentType(string mediaType, out MessageDataType da private static string GetFunctionName(ConnectionContext context) { - return $"{context.Hub}.{context.EventType}.{context.EventName}".ToLower(); + return $"{context.Hub}.{context.EventType}.{context.EventName}"; } private static bool RespondToServiceAbuseCheck(HttpRequestMessage req, HashSet allowedHosts, out HttpResponseMessage response) @@ -241,7 +241,7 @@ private static bool RespondToServiceAbuseCheck(HttpRequestMessage req, HashSet 0) + if (hosts != null && hosts.Any()) { foreach (var item in allowedHosts) { diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs index c354c920e5dc..52f60c925790 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs @@ -20,11 +20,8 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { internal static class Utilities { - private const int MaxTokenLength = 4096; private static readonly char[] HeaderSeparator = { ',' }; - private static readonly JwtSecurityTokenHandler JwtTokenHandler = new JwtSecurityTokenHandler(); - public static MediaTypeHeaderValue GetMediaType(MessageDataType dataType) => new MediaTypeHeaderValue(GetContentType(dataType)); public static string GetContentType(MessageDataType dataType) => @@ -48,7 +45,7 @@ public static MessageDataType GetDataType(string mediaType) => public static WebPubSubEventType GetEventType(string ceType) { - return ceType.StartsWith(Constants.Headers.CloudEvents.TypeSystemPrefix) ? + return ceType.StartsWith(Constants.Headers.CloudEvents.TypeSystemPrefix, StringComparison.OrdinalIgnoreCase) ? WebPubSubEventType.System : WebPubSubEventType.User; } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs index fca597130a58..5a1cafe7c0be 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs @@ -29,7 +29,6 @@ internal IEnumerable GetClaims() { claims.Add(new Claim(ClaimTypes.NameIdentifier, UserId)); } - return claims; } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/FakeTypeLocator.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/FakeTypeLocator.cs index e3e7d7cec9a4..40ed9e4ad7ba 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/FakeTypeLocator.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/FakeTypeLocator.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests { diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestExtensionConfig.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestExtensionConfig.cs index fcebce5c2531..09bc2ac6ec8f 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestExtensionConfig.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestExtensionConfig.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using Microsoft.Azure.WebJobs.Description; using Microsoft.Azure.WebJobs.Host.Config; diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs index caf0354c57bd..4c6fe43a26b1 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs @@ -104,46 +104,19 @@ public static HttpRequestMessage CreateHttpRequestMessage( { context.Headers.Add(Constants.Headers.CloudEvents.UserId, userId); } - context.Content = payload == null ? null : new StreamContent(new MemoryStream(payload)); - context.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType); + + if (payload != null) + { + context.Content = new StreamContent(new MemoryStream(payload)); + context.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType); + } foreach (var header in context.Headers) { - context.Content.Headers.TryAddWithoutValidation(header.Key, header.Value); + context.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value); } return context; - - //return CreateHttpRequestMessageFromContext(context); } - - //private static HttpRequestMessage CreateHttpRequestMessageFromContext(HttpContext httpContext) - //{ - // var httpRequest = httpContext.Request; - // var uriString = - // httpRequest.Scheme + "://" + - // httpRequest.Host + - // httpRequest.PathBase + - // httpRequest.Path + - // httpRequest.QueryString; - // - // var message = new HttpRequestMessage(new HttpMethod(httpRequest.Method), uriString); - // - // message.Properties[nameof(HttpContext)] = httpContext; - // - // message.Content = new StreamContent(httpRequest.Body); - // - // foreach (var header in httpRequest.Headers) - // { - // // Every header should be able to fit into one of the two header collections. - // // Try message.Headers first since that accepts more of them. - // if (!message.Headers.TryAddWithoutValidation(header.Key, (IEnumerable)header.Value)) - // { - // message.Content.Headers.TryAddWithoutValidation(header.Key, (IEnumerable)header.Value); - // } - // } - // - // return message; - //} } } \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListener.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListener.cs index 1f86ccdfa0ec..f71badc53ff9 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListener.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListener.cs @@ -1,4 +1,7 @@ -using Microsoft.Azure.WebJobs.Host.Listeners; +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Azure.WebJobs.Host.Listeners; using System.Threading; using System.Threading.Tasks; diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListenerBase.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListenerBase.cs index 4d3f2dba6110..e35c18a3080d 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListenerBase.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestListenerBase.cs @@ -1,4 +1,7 @@ -namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests { public class TestListenerBase { diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs index 3b00fba34d89..6c264b99cba1 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Net; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.WebJobs.Host.Executors; @@ -106,7 +107,7 @@ public async Task TestProcessRequest_AbuseProtectionInvalidBadRequest(string met public async Task TestProcessRequest_MessageMediaTypes(string mediaType, HttpStatusCode expectedCode) { var dispatcher = SetupDispatcher(TestHub, WebPubSubEventType.User, Constants.Events.MessageEvent); - var request = TestHelpers.CreateHttpRequestMessage(TestHub, WebPubSubEventType.User, Constants.Events.MessageEvent, TestKey.ConnectionId, ValidSignature, contentType: mediaType); + var request = TestHelpers.CreateHttpRequestMessage(TestHub, WebPubSubEventType.User, Constants.Events.MessageEvent, TestKey.ConnectionId, ValidSignature, contentType: mediaType, payload: Encoding.UTF8.GetBytes("Hello")); var response = await dispatcher.ExecuteAsync(request, EmptySetting, ValidAccessKeys).ConfigureAwait(false); Assert.Equal(expectedCode, response.StatusCode); } From 1e77a0a068703e3512b7575316b735647b8ab9f3 Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Mon, 19 Apr 2021 13:13:03 +0800 Subject: [PATCH 04/17] fix typo in readme and minor update. --- .../README.md | 8 ++++---- .../src/Utilities.cs | 6 ------ .../tests/WebPubSubServiceTests.cs | 9 ++++++--- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md index ce62f7e48a81..731d42492e40 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md @@ -1,4 +1,4 @@ -# Azure WebJobs EventGrid client library for .NET +# Azure WebJobs Web PubSub client library for .NET This extension provides functionality for receiving Web PubSub webhook calls in Azure Functions, allowing you to easily write functions that respond to any event published to Web PubSub. @@ -64,8 +64,8 @@ public static async Task RunAsync( ### Functions that uses Web PubSub trigger -```C# Snippet:EventGridTriggerFunction -[FunctionName("EventGridTriggerFunction")] +```C# Snippet:WebPubSubTriggerFunction +[FunctionName("WebPubSubTriggerFunction")] public static async Task RunAsync( [WebPubSubTrigger("message", EventType.User)] ConnectionContext context, @@ -105,7 +105,7 @@ For more information see the [Code of Conduct FAQ][coc_faq] or contact [opencode@microsoft.com][coc_contact] with any additional questions or comments. -![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-net%2Fsdk%2Fsearch%2FMicrosoft.Azure.WebJobs.Extensions.EventGrid%2FREADME.png) +![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-net%2Fsdk%2Fsearch%2FMicrosoft.Azure.WebJobs.Extensions.WebPubSub%2FREADME.png) [source]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/search/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs index 52f60c925790..379b8266e966 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs @@ -3,17 +3,11 @@ using System; using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; -using System.Runtime.InteropServices; -using System.Security.Claims; -using System.Text; - -using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs index bb8bebdc0375..82dd406a7613 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs @@ -10,14 +10,17 @@ public class WebPubSubServiceTests private const string NormConnectionString = "Endpoint=http://localhost;Port=8080;AccessKey=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGH;Version=1.0;"; private const string SecConnectionString = "Endpoint=https://abc;AccessKey=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGH;Version=1.0;"; - [Fact] - public void TestWebPubSubConnection() + [Theory] + [InlineData(NormConnectionString, "ws://localhost:8080/client/hubs/testHub")] + [InlineData(SecConnectionString, "wss://abc/client/hubs/testHub")] + public void TestWebPubSubConnection(string connectionString, string expectedBaseUrl) { - var service = new WebPubSubService(NormConnectionString, "testHub"); + var service = new WebPubSubService(connectionString, "testHub"); var clientConnection = service.GetClientConnection(); Assert.NotNull(clientConnection); + Assert.Equal(expectedBaseUrl, clientConnection.BaseUrl); } } } From 4c02f4d4bc82d8ab97584cad391fd14b81037877 Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Mon, 19 Apr 2021 13:19:27 +0800 Subject: [PATCH 05/17] remove deploy files. --- ...mpleChat20210323161418 - Zip Deploy.pubxml | 19 -- ...jixin-simplechat-linux - Zip Deploy.pubxml | 19 -- .../jixin-wps-simplechat - Zip Deploy.pubxml | 18 -- .../profile.arm.json | 175 ------------------ .../Properties/serviceDependencies.json | 11 -- .../Properties/serviceDependencies.local.json | 11 -- 6 files changed, 253 deletions(-) delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/SimpleChat20210323161418 - Zip Deploy.pubxml delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-simplechat-linux - Zip Deploy.pubxml delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-wps-simplechat - Zip Deploy.pubxml delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/ServiceDependencies/jixin-wps-simplechat - Zip Deploy/profile.arm.json delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.json delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.local.json diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/SimpleChat20210323161418 - Zip Deploy.pubxml b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/SimpleChat20210323161418 - Zip Deploy.pubxml deleted file mode 100644 index 3a5f19bbd8ec..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/SimpleChat20210323161418 - Zip Deploy.pubxml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - ZipDeploy - AzureWebSite - Release - Any CPU - https://simplechat20210323161418.azurewebsites.net - False - /subscriptions/9caf2a1e-9c49-49b6-89a2-56bdec7e3f97/resourcegroups/jixin-test/providers/Microsoft.Web/sites/SimpleChat20210323161418 - $SimpleChat20210323161418 - <_SavePWD>True - true - https://simplechat20210323161418.scm.azurewebsites.net/ - - \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-simplechat-linux - Zip Deploy.pubxml b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-simplechat-linux - Zip Deploy.pubxml deleted file mode 100644 index 8ac4c6853416..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-simplechat-linux - Zip Deploy.pubxml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - ZipDeploy - AzureWebSite - Release - Any CPU - https://jixin-simplechat-linux.azurewebsites.net - False - /subscriptions/9caf2a1e-9c49-49b6-89a2-56bdec7e3f97/resourcegroups/jixin-test/providers/Microsoft.Web/sites/jixin-simplechat-linux - $jixin-simplechat-linux - <_SavePWD>True - true - https://jixin-simplechat-linux.scm.azurewebsites.net/ - - \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-wps-simplechat - Zip Deploy.pubxml b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-wps-simplechat - Zip Deploy.pubxml deleted file mode 100644 index 7ba3110381c4..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/PublishProfiles/jixin-wps-simplechat - Zip Deploy.pubxml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - ZipDeploy - AzureWebSite - Release - Any CPU - https://jixin-wps-simplechat.azurewebsites.net - False - /subscriptions/9caf2a1e-9c49-49b6-89a2-56bdec7e3f97/resourcegroups/jixin-test/providers/Microsoft.Web/sites/jixin-wps-simplechat - $jixin-wps-simplechat - <_SavePWD>True - https://jixin-wps-simplechat.scm.azurewebsites.net/ - - \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/ServiceDependencies/jixin-wps-simplechat - Zip Deploy/profile.arm.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/ServiceDependencies/jixin-wps-simplechat - Zip Deploy/profile.arm.json deleted file mode 100644 index e9fad2ee6f7a..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/ServiceDependencies/jixin-wps-simplechat - Zip Deploy/profile.arm.json +++ /dev/null @@ -1,175 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_dependencyType": "function.windows.appService" - }, - "parameters": { - "resourceGroupName": { - "type": "string", - "defaultValue": "jixin-test", - "metadata": { - "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking." - } - }, - "resourceGroupLocation": { - "type": "string", - "defaultValue": "eastus", - "metadata": { - "description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support." - } - }, - "resourceName": { - "type": "string", - "defaultValue": "jixin-wps-simplechat", - "metadata": { - "description": "Name of the main resource to be created by this template." - } - }, - "resourceLocation": { - "type": "string", - "defaultValue": "[parameters('resourceGroupLocation')]", - "metadata": { - "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there." - } - } - }, - "resources": [ - { - "type": "Microsoft.Resources/resourceGroups", - "name": "[parameters('resourceGroupName')]", - "location": "[parameters('resourceGroupLocation')]", - "apiVersion": "2019-10-01" - }, - { - "type": "Microsoft.Resources/deployments", - "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", - "resourceGroup": "[parameters('resourceGroupName')]", - "apiVersion": "2019-10-01", - "dependsOn": [ - "[parameters('resourceGroupName')]" - ], - "properties": { - "mode": "Incremental", - "expressionEvaluationOptions": { - "scope": "inner" - }, - "parameters": { - "resourceGroupName": { - "value": "[parameters('resourceGroupName')]" - }, - "resourceGroupLocation": { - "value": "[parameters('resourceGroupLocation')]" - }, - "resourceName": { - "value": "[parameters('resourceName')]" - }, - "resourceLocation": { - "value": "[parameters('resourceLocation')]" - } - }, - "template": { - "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "resourceGroupName": { - "type": "string" - }, - "resourceGroupLocation": { - "type": "string" - }, - "resourceName": { - "type": "string" - }, - "resourceLocation": { - "type": "string" - } - }, - "variables": { - "storage_name": "[toLower(concat('storage', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId))))]", - "appServicePlan_name": "[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", - "storage_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Storage/storageAccounts/', variables('storage_name'))]", - "appServicePlan_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]", - "function_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/sites/', parameters('resourceName'))]" - }, - "resources": [ - { - "location": "[parameters('resourceLocation')]", - "name": "[parameters('resourceName')]", - "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", - "tags": { - "[concat('hidden-related:', variables('appServicePlan_ResourceId'))]": "empty" - }, - "dependsOn": [ - "[variables('appServicePlan_ResourceId')]", - "[variables('storage_ResourceId')]" - ], - "kind": "functionapp", - "properties": { - "name": "[parameters('resourceName')]", - "kind": "functionapp", - "httpsOnly": true, - "reserved": false, - "serverFarmId": "[variables('appServicePlan_ResourceId')]", - "siteConfig": { - "alwaysOn": true - } - }, - "identity": { - "type": "SystemAssigned" - }, - "resources": [ - { - "name": "appsettings", - "type": "config", - "apiVersion": "2015-08-01", - "dependsOn": [ - "[variables('function_ResourceId')]" - ], - "properties": { - "AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]", - "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]", - "FUNCTIONS_EXTENSION_VERSION": "~3", - "FUNCTIONS_WORKER_RUNTIME": "dotnet" - } - } - ] - }, - { - "location": "[parameters('resourceGroupLocation')]", - "name": "[variables('storage_name')]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2017-10-01", - "tags": { - "[concat('hidden-related:', concat('/providers/Microsoft.Web/sites/', parameters('resourceName')))]": "empty" - }, - "properties": { - "supportsHttpsTrafficOnly": true - }, - "sku": { - "name": "Standard_LRS" - }, - "kind": "Storage" - }, - { - "location": "[parameters('resourceGroupLocation')]", - "name": "[variables('appServicePlan_name')]", - "type": "Microsoft.Web/serverFarms", - "apiVersion": "2015-08-01", - "sku": { - "name": "S1", - "tier": "Standard", - "family": "S", - "size": "S1" - }, - "properties": { - "name": "[variables('appServicePlan_name')]" - } - } - ] - } - } - } - ] -} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.json deleted file mode 100644 index df4dcc9d888d..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "dependencies": { - "appInsights1": { - "type": "appInsights" - }, - "storage1": { - "type": "storage", - "connectionId": "AzureWebJobsStorage" - } - } -} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.local.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.local.json deleted file mode 100644 index b804a2893992..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Properties/serviceDependencies.local.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "dependencies": { - "appInsights1": { - "type": "appInsights.sdk" - }, - "storage1": { - "type": "storage.emulator", - "connectionId": "AzureWebJobsStorage" - } - } -} \ No newline at end of file From 6c313a96cc92443e8b522d15308aa4a9f13b14da Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Thu, 22 Apr 2021 16:24:27 +0800 Subject: [PATCH 06/17] fix and resolve comments --- eng/Packages.Data.props | 2 +- .../README.md | 2 +- .../client/WebPubSubTests/Program.cs | 96 ------- .../WebPubSubTests/WebPubSubClient.csproj | 12 - .../samples/simplechat/client/index.html | 248 ---------------- .../samples/simplechat/csharp/.gitignore | 264 ------------------ .../samples/simplechat/csharp/Functions.cs | 120 -------- .../simplechat/csharp/SimpleChat.csproj | 23 -- .../samples/simplechat/csharp/host.json | 11 - .../csharp/local.settings.sample.json | 13 - .../simplechat/js/functionapp/.gitignore | 26 -- .../js/functionapp/connect/function.json | 13 - .../js/functionapp/connect/index.js | 20 -- .../js/functionapp/connected/function.json | 19 -- .../js/functionapp/connected/index.js | 30 -- .../js/functionapp/extensions.csproj | 12 - .../simplechat/js/functionapp/host.json | 8 - .../js/functionapp/local.settings.sample.json | 13 - .../js/functionapp/login/function.json | 23 -- .../simplechat/js/functionapp/login/index.js | 7 - .../js/functionapp/message/function.json | 20 -- .../js/functionapp/message/index.js | 18 -- .../src/Bindings/ConnectionContext.cs | 2 +- .../src/Config/WebPubSubConfigProvider.cs | 7 +- .../src/Config/WebPubSubOptions.cs | 4 +- .../src/Services/ServiceConfigParser.cs | 11 +- .../src/Services/WebPubSubService.cs | 20 +- .../src/Trigger/WebPubSubTriggerBinding.cs | 42 +-- .../src/Trigger/WebPubSubTriggerDispatcher.cs | 92 +++++- .../src/Trigger/WebPubSubTriggerEvent.cs | 2 +- .../src/Utilities.cs | 11 +- .../src/WebPubSubConnectionAttribute.cs | 10 - .../tests/JObjectTests.cs | 72 ++++- .../tests/WebPubSubServiceTests.cs | 2 +- .../tests/WebPubSubTriggerDispatcherTests.cs | 3 +- 35 files changed, 180 insertions(+), 1098 deletions(-) delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/Program.cs delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/WebPubSubClient.csproj delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/index.html delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/.gitignore delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Functions.cs delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/SimpleChat.csproj delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/host.json delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/local.settings.sample.json delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/.gitignore delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/function.json delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/index.js delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/function.json delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/index.js delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/extensions.csproj delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/host.json delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/local.settings.sample.json delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/function.json delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/index.js delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/function.json delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/index.js diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props index f00ab8fcf6d9..13d9f781e90e 100644 --- a/eng/Packages.Data.props +++ b/eng/Packages.Data.props @@ -84,7 +84,7 @@ - + diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md index 731d42492e40..a5a9cd507998 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md @@ -6,7 +6,7 @@ This extension provides functionality for receiving Web PubSub webhook calls in ### Install the package -Install the Event Grid extension with [NuGet][nuget]: +Install the Web PubSub extension with [NuGet][nuget]: ```Powershell dotnet add package Microsoft.Azure.WebJobs.Extensions.WebPubSub diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/Program.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/Program.cs deleted file mode 100644 index 42c2bd584504..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/Program.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.IO; -using System.Net.Http; -using System.Net.WebSockets; -using System.Text; -using System.Threading.Tasks; - -using Newtonsoft.Json; - -namespace WebPubSubTests -{ - class Program - { - private const string DefaultLogin = "http://localhost:7071/api/login"; - private static HttpClient _httpClient = new HttpClient(); - - static async Task Main(string[] args) - { - Console.WriteLine("== [Simple Chat] =="); - - Console.WriteLine("[1] Enter login func url:"); - var loginUrl = Console.ReadLine(); - if (string.IsNullOrEmpty(loginUrl) || !Uri.TryCreate(loginUrl, UriKind.Absolute, out var url)) - { - Console.WriteLine($"Invalid url, use default local func: {DefaultLogin}"); - loginUrl = DefaultLogin; - } - - Console.WriteLine("[2] Enter userId:"); - var userId = Console.ReadLine(); - - var response = await _httpClient.GetAsync($"{loginUrl}?userid={userId}"); - var result = await response.Content.ReadAsStringAsync(); - - var connection = JsonConvert.DeserializeObject(result); - - using var webSocket = new ClientWebSocket(); - await webSocket.ConnectAsync(new Uri(connection.Url), default); - Console.WriteLine("Connected"); - - _ = ReceiveMessageAsync(webSocket); - - await SendMessageAsync(webSocket); - } - - private static async Task ReceiveMessageAsync(ClientWebSocket webSocket) - { - var ms = new MemoryStream(); - Memory buffer = new byte[1024]; - // receive loop - while (true) - { - var receiveResult = await webSocket.ReceiveAsync(buffer, default); - // Need to check again for NetCoreApp2.2 because a close can happen between a 0-byte read and the actual read - if (receiveResult.MessageType == WebSocketMessageType.Close) - { - try - { - await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default); - } - catch - { - // It is possible that the remote is already closed - } - break; - } - await ms.WriteAsync(buffer.Slice(0, receiveResult.Count)); - if (receiveResult.EndOfMessage) - { - Console.WriteLine($"[Received]: {Encoding.UTF8.GetString(ms.ToArray())}"); - ms.SetLength(0); - } - } - } - - private static async Task SendMessageAsync(ClientWebSocket webSocket) - { - while (true) - { - Console.WriteLine("[3] Input text to chat:"); - var message = Console.ReadLine(); - if (!string.IsNullOrEmpty(message)) - { - var msgBuffer = Encoding.UTF8.GetBytes(message); - await webSocket.SendAsync(new ArraySegment(msgBuffer), WebSocketMessageType.Text, true, default); - } - } - } - - private sealed class Connection - { - public string Url { get; set; } - public string AccessToken { get; set; } - } - } -} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/WebPubSubClient.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/WebPubSubClient.csproj deleted file mode 100644 index b84e63f50619..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/WebPubSubTests/WebPubSubClient.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - net5.0 - - - - - - - diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/index.html b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/index.html deleted file mode 100644 index a7aa3b6a91d7..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/client/index.html +++ /dev/null @@ -1,248 +0,0 @@ - - - - - - - - - A Simple Serverless WebSocket Chat - - - - - - - - - -
- - -
- - - - - - - - - - \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/.gitignore b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/.gitignore deleted file mode 100644 index ff5b00c506bd..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/.gitignore +++ /dev/null @@ -1,264 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# Azure Functions localsettings file -local.settings.json - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# DNX -project.lock.json -project.fragment.lock.json -artifacts/ - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -#*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config -# NuGet v3's project.json files produces more ignoreable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -node_modules/ -orleans.codegen.cs - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Functions.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Functions.cs deleted file mode 100644 index dbe6faf99d33..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/Functions.cs +++ /dev/null @@ -1,120 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.Extensions.Http; -using Microsoft.Azure.WebJobs.Extensions.WebPubSub; -using Newtonsoft.Json; -using System; -using System.Threading.Tasks; - -namespace SimpleChat -{ - public static class Functions - { - [FunctionName("login")] - public static WebPubSubConnection GetClientConnection( - [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req, - [WebPubSubConnection(UserId = "{query.userid}", ConnectionStringSetting = "abc", Hub ="testhub")] WebPubSubConnection connection) - { - Console.WriteLine("login"); - return connection; - } - - [FunctionName("connect")] - public static ServiceResponse Connect( - [WebPubSubTrigger("simplechat", WebPubSubEventType.System, "connect")] ConnectionContext connectionContext) - { - Console.WriteLine($"Received client connect with connectionId: {connectionContext.ConnectionId}"); - if (connectionContext.UserId == "attacker") - { - return new ErrorResponse(WebPubSubErrorCode.Unauthorized); - } - return new ConnectResponse - { - UserId = connectionContext.UserId - }; - } - - // multi tasks sample - [FunctionName("connected")] - public static async Task Connected( - [WebPubSubTrigger(WebPubSubEventType.System, "connected")] ConnectionContext connectionContext, - [WebPubSub] IAsyncCollector webpubsubEvent) - { - await webpubsubEvent.AddAsync(new WebPubSubEvent - { - Message = new WebPubSubMessage(new ClientContent($"{connectionContext.UserId} connected.").ToString()), - }); - - await webpubsubEvent.AddAsync(new WebPubSubEvent - { - Operation = WebPubSubOperation.AddUserToGroup, - UserId = connectionContext.UserId, - Group = "group1" - }); - await webpubsubEvent.AddAsync(new WebPubSubEvent - { - Operation = WebPubSubOperation.SendToUser, - UserId = connectionContext.UserId, - Group = "group1", - Message = new WebPubSubMessage(new ClientContent($"{connectionContext.UserId} joined group: group1.").ToString()), - }); - } - - // single message sample - [FunctionName("broadcast")] - public static async Task Broadcast( - [WebPubSubTrigger(WebPubSubEventType.User, "message")] WebPubSubMessage message, - [WebPubSub(Hub = "simplechat")] IAsyncCollector webpubsubEvent) - { - await webpubsubEvent.AddAsync(new WebPubSubEvent - { - Operation = WebPubSubOperation.SendToAll, - Message = message, - }); - - return new MessageResponse - { - Message = new WebPubSubMessage("ack"), - }; - } - - [FunctionName("disconnect")] - [return: WebPubSub] - public static WebPubSubEvent Disconnect( - [WebPubSubTrigger(WebPubSubEventType.System, "disconnected")] ConnectionContext connectionContext) - { - Console.WriteLine("Disconnect."); - return new WebPubSubEvent - { - Operation = WebPubSubOperation.SendToAll, - Message = new WebPubSubMessage(new ClientContent($"{connectionContext.UserId} disconnect.").ToString()) - }; - } - - [JsonObject] - public sealed class ClientContent - { - [JsonProperty("from")] - public string From { get; set; } - [JsonProperty("content")] - public string Content { get; set; } - - public ClientContent(string message) - { - From = "[System]"; - Content = message; - } - - public ClientContent(string from, string message) - { - From = from; - Content = message; - } - - public override string ToString() - { - return JsonConvert.SerializeObject(this); - } - } - } -} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/SimpleChat.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/SimpleChat.csproj deleted file mode 100644 index 90406b1846e1..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/SimpleChat.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - netcoreapp3.1 - v3 - 03c9ef80-6005-44fc-b5a4-7d0dd308a5a0 - - - - - - - - - - - PreserveNewest - - - PreserveNewest - Never - - - diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/host.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/host.json deleted file mode 100644 index bb3b8dadd7d6..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/host.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingExcludedTypes": "Request", - "samplingSettings": { - "isEnabled": true - } - } - } -} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/local.settings.sample.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/local.settings.sample.json deleted file mode 100644 index 1a7ae51e1c92..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/csharp/local.settings.sample.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "IsEncrypted": false, - "Values": { - "AzureWebJobsStorage": "UseDevelopmentStorage=true", - "FUNCTIONS_WORKER_RUNTIME": "dotnet", - "WebPubSubConnectionString": "", - "WebPubSubHub": "simplechat" - }, - "Host": { - "LocalHttpPort": 7071, - "CORS": "*" - } -} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/.gitignore b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/.gitignore deleted file mode 100644 index 479aa6a84d0c..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ - -bin -obj -csx -.vs -edge -Publish - -*.user -*.suo -*.cscfg -*.Cache -project.lock.json - -/packages -/TestResults - -/tools/NuGet.exe -/App_Data -/secrets -/data -.secrets -appsettings.json -local.settings.json - -node_modules diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/function.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/function.json deleted file mode 100644 index 6e4fbe15f22b..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/function.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "disabled": false, - "bindings": [ - { - "type": "webPubSubTrigger", - "name": "connectionContext", - "direction": "in", - "hub": "simplechat", - "eventName": "connect", - "eventType": "system" - } - ] -} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/index.js b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/index.js deleted file mode 100644 index a41ef423b11d..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connect/index.js +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -module.exports = async function (context, connectionContext) { - if (connectionContext.userId == "attacker") - { - var connectResponse = { - "code": "unauthorized", - "errorMessage": "invalid user" - } - } - else - { - var connectResponse = { - "userId": connectionContext.userId - }; - } - return connectResponse; -}; - diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/function.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/function.json deleted file mode 100644 index 9e66a78a0184..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/function.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "disabled": false, - "bindings": [ - { - "type": "webPubSubTrigger", - "direction": "in", - "name": "connectionContext", - "hub": "simplechat", - "eventName": "connected", - "eventType": "system" - }, - { - "type": "webPubSub", - "name": "webPubSubEvent", - "hub": "simplechat", - "direction": "out" - } - ] -} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/index.js b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/index.js deleted file mode 100644 index edde5685234c..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/connected/index.js +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -module.exports = function (context, connectionContext) { - context.bindings.webPubSubEvent = []; - context.bindings.webPubSubEvent.push({ - "operation": "sendToAll", - "message": JSON.stringify({ - from: '[System]', - content: `${context.bindingData.connectionContext.userId} connected.` - }), - "dataType" : "json" - }); - - context.bindings.webPubSubEvent.push({ - "operation": "addUserToGroup", - "userId": `${context.bindingData.connectionContext.userId}`, - "group": "group1" - }); - - context.bindings.webPubSubEvent.push({ - "operation": "sendToAll", - "message": JSON.stringify({ - from: '[System]', - content: `${context.bindingData.connectionContext.userId} joined group: group1.` - }), - "dataType": "json" - }); - context.done(); -}; \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/extensions.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/extensions.csproj deleted file mode 100644 index 5573d335b2c2..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/extensions.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - netstandard2.0 - - ** - - - - - - - diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/host.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/host.json deleted file mode 100644 index 8d1d95abe665..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/host.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "version": "2.0", - "extensions": { - "http": { - "routePrefix": "api" - } - } -} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/local.settings.sample.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/local.settings.sample.json deleted file mode 100644 index 8623e1d7590b..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/local.settings.sample.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "IsEncrypted": false, - "Values": { - "AzureWebJobsStorage": "UseDevelopmentStorage=true", - "WebPubSubHub": "simplechat", - "WebPubSubConnectionString": "", - "FUNCTIONS_WORKER_RUNTIME": "node" - }, - "Host": { - "LocalHttpPort": 7071, - "CORS": "*" - } -} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/function.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/function.json deleted file mode 100644 index c9f998c1ac27..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/function.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "disabled": false, - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req" - }, - { - "type": "http", - "direction": "out", - "name": "res" - }, - { - "type": "webPubSubConnection", - "name": "connection", - "userId": "{query.userid}", - "hub": "simplechat", - "direction": "in" - } - ] -} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/index.js b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/index.js deleted file mode 100644 index 511e0301e9e0..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/login/index.js +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -module.exports = function (context, req, connection) { - context.res = { body: connection }; - context.done(); -}; \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/function.json b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/function.json deleted file mode 100644 index 1e85680c498b..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/function.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "disabled": false, - "bindings": [ - { - "type": "webPubSubTrigger", - "direction": "in", - "name": "message", - "dataType": "string", - "hub": "simplechat", - "eventName": "message", - "eventType": "user" - }, - { - "type": "webPubSub", - "name": "webPubSubEvent", - "hub": "simplechat", - "direction": "out" - } - ] -} \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/index.js b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/index.js deleted file mode 100644 index f7fb89d1a328..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/samples/simplechat/js/functionapp/message/index.js +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -module.exports = async function (context, message) { - context.bindings.webPubSubEvent = { - "operation": "sendToAll", - "message": message, - "dataType": context.bindingData.dataType - }; - var response = { - "message": JSON.stringify({ - from: '[System]', - content: 'ack.' - }), - "dataType" : "json" - }; - return response; -}; \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectionContext.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectionContext.cs index a19265a3cccf..708d0e8c923c 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectionContext.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectionContext.cs @@ -23,7 +23,7 @@ public class ConnectionContext public string EventName { get; internal set; } /// - /// The hub which message belongs to. + /// The hub which the message belongs to. /// public string Hub { get; internal set; } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs index adab8ccc949b..af9d75741e64 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs @@ -37,7 +37,7 @@ public WebPubSubConfigProvider( _logger = loggerFactory.CreateLogger(LogCategories.CreateTriggerCategory("WebPubSub")); _nameResolver = nameResolver; _configuration = configuration; - _dispatcher = new WebPubSubTriggerDispatcher(); + _dispatcher = new WebPubSubTriggerDispatcher(_logger); } public void Initialize(ExtensionConfigContext context) @@ -119,8 +119,7 @@ private WebPubSubConnection GetClientConnection(WebPubSubConnectionAttribute att { var hub = Utilities.FirstOrDefault(attribute.Hub, _options.Hub); var service = new WebPubSubService(attribute.ConnectionStringSetting, hub); - var claims = attribute.GetClaims(); - return service.GetClientConnection(claims); + return service.GetClientConnection(attribute.UserId); } private void ValidateConnectionString(string attributeConnectionString, string attributeConnectionStringName) @@ -139,7 +138,7 @@ private void AddSettings(string connectionString) if (!string.IsNullOrEmpty(connectionString)) { var item = new ServiceConfigParser(connectionString); - _options.AllowedHosts.Add(new Uri(item.Endpoint).Host); + _options.AllowedHosts.Add(item.Endpoint.Host); _options.AccessKeys.Add(item.AccessKey); } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubOptions.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubOptions.cs index 20bfe73f5b7b..bf77105d9e97 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubOptions.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubOptions.cs @@ -10,10 +10,10 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { public class WebPubSubOptions : IOptionsFormatter { - public string ConnectionString { get; set; } - public string Hub { get; set; } + internal string ConnectionString { get; set; } + internal HashSet AllowedHosts { get; set; } = new HashSet(); internal HashSet AccessKeys { get; set; } = new HashSet(); diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs index 94811951cb26..598ad8805788 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs @@ -3,33 +3,34 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { internal class ServiceConfigParser { - public string Endpoint { get; } + public Uri Endpoint { get; } public string AccessKey { get; } public string Version { get; } - public string Port { get; } + public int Port { get; } public ServiceConfigParser(string connectionString) { var settings = ParseConnectionString(connectionString); Endpoint = settings.ContainsKey("endpoint") ? - settings["endpoint"] : + new Uri(settings["endpoint"]) : throw new ArgumentException(nameof(Endpoint)); AccessKey = settings.ContainsKey("accesskey") ? settings["accesskey"] : throw new ArgumentException(nameof(AccessKey)); Version = settings.ContainsKey("version") ? settings["version"] : null; - Port = settings.ContainsKey("port") ? settings["port"] : null; + Port = settings.ContainsKey("port") ? int.Parse(settings["port"], CultureInfo.InvariantCulture) : 80; } private static Dictionary ParseConnectionString(string connectionString) @@ -48,7 +49,7 @@ private static Dictionary ParseConnectionString(string connectio } catch (Exception) { - throw new ArgumentException($"Invalid Web PubSub connection string: {connectionString}"); + throw new ArgumentException($"Invalid Web PubSub connection string, please check"); } return setting; } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs index fd3c29516c7d..7d97a64de2d9 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs @@ -2,9 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; using Azure; @@ -21,16 +18,19 @@ internal class WebPubSubService : IWebPubSubService public WebPubSubService(string connectionString, string hubName) { _serviceConfig = new ServiceConfigParser(connectionString); - var url = !string.IsNullOrEmpty(_serviceConfig.Port) ? $"{_serviceConfig.Endpoint}:{_serviceConfig.Port}" : _serviceConfig.Endpoint; - _client = new WebPubSubServiceClient(new Uri(url), hubName, new AzureKeyCredential(_serviceConfig.AccessKey)); + UriBuilder endpoint = _serviceConfig.Port == 80 ? + new UriBuilder(_serviceConfig.Endpoint) : + new UriBuilder(_serviceConfig.Endpoint.Scheme, _serviceConfig.Endpoint.Host, _serviceConfig.Port); + + _client = new WebPubSubServiceClient(endpoint.Uri, hubName, new AzureKeyCredential(_serviceConfig.AccessKey)); } - internal WebPubSubConnection GetClientConnection(IEnumerable claims = null) + internal WebPubSubConnection GetClientConnection(string userId = null, string[] roles = null) { - var url = _client.GetClientAccessUri(default, claims?.ToArray()); + var url = _client.GetClientAccessUri(userId, roles); - #region TODO: Remove after SDK fix. - if (!_serviceConfig.Endpoint.StartsWith("https", StringComparison.OrdinalIgnoreCase)) + #region TODO: Remove after SDK fix. Work-around to support http. + if (!_serviceConfig.Endpoint.Scheme.StartsWith("https", StringComparison.OrdinalIgnoreCase)) { var replaced = url.AbsoluteUri.Replace("wss", "ws"); url = new Uri(replaced); @@ -82,7 +82,7 @@ public async Task RevokeGroupPermission(WebPubSubEvent webPubSubEvent) public async Task SendToAll(WebPubSubEvent webPubSubEvent) { - var content = RequestContent.Create(webPubSubEvent.Message); + var content = RequestContent.Create(webPubSubEvent.Message.ToBinaryData()); var contentType = Utilities.GetContentType(webPubSubEvent.DataType); await _client.SendToAllAsync(content, contentType, webPubSubEvent.Excluded).ConfigureAwait(false); } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs index 683c5a979369..b2a5df2f88dd 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs @@ -66,7 +66,7 @@ public Task CreateListenerAsync(ListenerFactoryContext context) var hub = Utilities.FirstOrDefault(_attribute.Hub, _options.Hub); if (string.IsNullOrEmpty(hub)) { - throw new ArgumentNullException($"Hub name should be configured in either attribute or appsettings."); + throw new ArgumentException("Hub name should be configured in either attribute or appsettings."); } var attributeName = $"{hub}.{_attribute.EventType}.{_attribute.EventName}"; var listernerKey = attributeName; @@ -91,7 +91,7 @@ private static void AddBindingData(Dictionary bindingData, WebPu bindingData.Add(nameof(triggerEvent.Query), triggerEvent.Query); bindingData.Add(nameof(triggerEvent.Reason), triggerEvent.Reason); bindingData.Add(nameof(triggerEvent.Subprotocols), triggerEvent.Subprotocols); - bindingData.Add(nameof(triggerEvent.ClientCertificaties), triggerEvent.ClientCertificaties); + bindingData.Add(nameof(triggerEvent.ClientCertificates), triggerEvent.ClientCertificates); } /// @@ -217,45 +217,9 @@ public string ToInvokeString() public Task SetValueAsync(object value, CancellationToken cancellationToken) { - if (value is string strValue) - { - var converted = ConvertToResponseIfPossible(JObject.Parse(strValue)); - _tcs.TrySetResult(converted); - } - else if (value is JObject jValue) - { - var converted = ConvertToResponseIfPossible(jValue); - _tcs.TrySetResult(converted); - } - else - { - _tcs.TrySetResult(value); - } + _tcs.TrySetResult(value); return Task.CompletedTask; } - - internal static object ConvertToResponseIfPossible(JObject value) - { - // try cast by required field in order. - if (value["code"] != null) - { - return value.ToObject(); - } - - if (value["message"] != null) - { - return value.ToObject(); - } - - var connect = value.ToObject(); - if (connect != null) - { - return connect; - } - - // return null and not supported response will be ignored. - return null; - } } } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs index a877044906f8..12aa671fc7e6 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs @@ -12,14 +12,22 @@ using System.Threading.Tasks; using Microsoft.Azure.WebJobs.Host.Executors; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { internal class WebPubSubTriggerDispatcher : IWebPubSubTriggerDispatcher { private Dictionary _listeners = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly ILogger _logger; + + public WebPubSubTriggerDispatcher(ILogger logger) + { + _logger = logger; + } public void AddListener(string key, WebPubSubListener listener) { @@ -111,7 +119,7 @@ public async Task ExecuteAsync(HttpRequestMessage req, Claims = claims, Query = query, Subprotocols = subprotocols, - ClientCertificaties = certificates, + ClientCertificates = certificates, Reason = reason, TaskCompletionSource = tcs }; @@ -128,18 +136,13 @@ await executor.Executor.TryExecuteAsync(new TriggeredFunctionData using (token.Register(() => tcs.TrySetCanceled())) { var response = await tcs.Task.ConfigureAwait(false); - if (response is ErrorResponse error) - { - return Utilities.BuildErrorResponse(error); - } - else if (requestType == RequestType.Connect && response is ConnectResponse connect) - { - return Utilities.BuildResponse(connect); - } - else if (requestType == RequestType.User && response is MessageResponse msgResponse) + var validResponse = BuildValidResponse(response, requestType); + + if (validResponse != null) { - return Utilities.BuildResponse(msgResponse); + return validResponse; } + _logger.LogWarning($"Invalid response type regarding current request: {requestType}"); } } catch (Exception ex) @@ -257,5 +260,72 @@ private static bool RespondToServiceAbuseCheck(HttpRequestMessage req, HashSet(JObject item, out T response) + { + try + { + response = item.ToObject(); + return true; + } + catch (JsonSerializationException) + { + // ignore invalid response + } + response = default(T); + return false; + } + + internal static HttpResponseMessage BuildValidResponse(object response, RequestType requestType) + { + JObject converted = null; + bool needConvert = false; + if (response is JObject jObject) + { + converted = jObject; + needConvert = true; + } + else if (response is string str) + { + converted = JObject.Parse(str); + needConvert = true; + } + + // Check error + if (needConvert && TryConvertResponse(converted, out ErrorResponse error)) + { + return Utilities.BuildErrorResponse(error); + } + else if (response is ErrorResponse) + { + return Utilities.BuildErrorResponse((ErrorResponse)response); + } + + if (requestType == RequestType.Connect) + { + if (needConvert) + { + return Utilities.BuildResponse(converted.ToString()); + } + else if (response is ConnectResponse) + { + return Utilities.BuildResponse((ConnectResponse)response); + } + } + + if (requestType == RequestType.User) + { + if (needConvert && TryConvertResponse(converted, out MessageResponse msgResponse)) + { + return Utilities.BuildResponse(msgResponse); + } + else if (response is MessageResponse) + { + return Utilities.BuildResponse((MessageResponse)response); + } + } + + return null; + } } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerEvent.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerEvent.cs index 75077852fd8c..047a018f18ea 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerEvent.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerEvent.cs @@ -23,7 +23,7 @@ internal class WebPubSubTriggerEvent public IDictionary Query { get; set; } - public ClientCertificateInfo[] ClientCertificaties { get; set; } + public ClientCertificateInfo[] ClientCertificates { get; set; } public string Reason { get; set; } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs index 379b8266e966..6ede0caf544b 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Utilities.cs @@ -68,7 +68,16 @@ public static HttpResponseMessage BuildResponse(ConnectResponse response) Subprotocol = response.Subprotocol, Roles = response.Roles }; - result.Content = new StringContent(JsonConvert.SerializeObject(connectEvent)); + + return BuildResponse(JsonConvert.SerializeObject(connectEvent), MessageDataType.Json); + } + + public static HttpResponseMessage BuildResponse(string response, MessageDataType dataType = MessageDataType.Text) + { + HttpResponseMessage result = new HttpResponseMessage(); + + result.Content = new StringContent(response); + result.Content.Headers.ContentType = GetMediaType(dataType); return result; } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs index 5a1cafe7c0be..fc6e27a2d838 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs @@ -21,15 +21,5 @@ public class WebPubSubConnectionAttribute : Attribute [AutoResolve] public string UserId { get; set; } - - internal IEnumerable GetClaims() - { - var claims = new List(); - if (!string.IsNullOrEmpty(UserId)) - { - claims.Add(new Claim(ClaimTypes.NameIdentifier, UserId)); - } - return claims; - } } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs index 9765554f0eba..095e03d8513b 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs @@ -3,10 +3,12 @@ using System.Collections.Generic; using System.IO; +using System.Net; +using System.Net.Http; using System.Text; +using System.Threading.Tasks; using Newtonsoft.Json.Linq; using Xunit; -using static Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubTriggerBinding; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests { @@ -73,34 +75,76 @@ public void TestConvertMessageToAndFromJObject(WebPubSubMessage message, Message } [Fact] - public void ParseErrorResponse() + public async Task ParseErrorResponse() { var test = @"{""code"":""unauthorized"",""errorMessage"":""not valid user.""}"; - var jObject = JObject.Parse(test); - var result = TriggerReturnValueProvider.ConvertToResponseIfPossible(jObject); + var result = BuildResponse(test, RequestType.Connect); Assert.NotNull(result); - Assert.Equal(typeof(ErrorResponse), result.GetType()); + Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode); - var converted = (ErrorResponse)result; - Assert.Equal(WebPubSubErrorCode.Unauthorized, converted.Code); - Assert.Equal("not valid user.", converted.ErrorMessage); + var message = await result.Content.ReadAsStringAsync(); + Assert.Equal("not valid user.", message); } [Fact] - public void ParseConnectResponse() + public async Task ParseConnectResponse() { var test = @"{""userId"":""aaa""}"; - var jObject = JObject.Parse(test); - var result = TriggerReturnValueProvider.ConvertToResponseIfPossible(jObject); + var result = BuildResponse(test, RequestType.Connect); Assert.NotNull(result); - Assert.Equal(typeof(ConnectResponse), result.GetType()); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); - var converted = (ConnectResponse)result; - Assert.Equal("aaa", converted.UserId); + var response = await result.Content.ReadAsStringAsync(); + var message = (JObject.Parse(response)).ToObject(); + Assert.Equal("aaa", message.UserId); + } + + [Fact] + public async Task ParseMessageResponse() + { + var test = @"{""message"":""test"", ""dataType"":""text""}"; + + var result = BuildResponse(test, RequestType.User); + + Assert.NotNull(result); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + + var message = await result.Content.ReadAsStringAsync(); + Assert.Equal("test", message); + Assert.Equal(Constants.ContentTypes.PlainTextContentType, result.Content.Headers.ContentType.MediaType); + } + + [Fact] + public void ParseMessageResponse_InvalidReturnNull() + { + var test = @"{""message"":""test"", ""dataType"":""hello""}"; + + var result = BuildResponse(test, RequestType.User); + + Assert.Null(result); + } + + [Fact] + public async Task ParseConnectResponse_ContentMatches() + { + var test = @"{""test"":""test"",""errorMessage"":""not valid user.""}"; + var expected = JObject.Parse(test); + + var result = BuildResponse(test, RequestType.Connect); + var content = await result.Content.ReadAsStringAsync(); + var actual = JObject.Parse(content); + + Assert.NotNull(result); + Assert.Equal(expected, actual); + } + + private static HttpResponseMessage BuildResponse(string input, RequestType requestType) + { + return WebPubSubTriggerDispatcher.BuildValidResponse(input, requestType); } } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs index 82dd406a7613..2ec2be51bcc7 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs @@ -13,7 +13,7 @@ public class WebPubSubServiceTests [Theory] [InlineData(NormConnectionString, "ws://localhost:8080/client/hubs/testHub")] [InlineData(SecConnectionString, "wss://abc/client/hubs/testHub")] - public void TestWebPubSubConnection(string connectionString, string expectedBaseUrl) + public void TestWebPubSubConnection_Scheme(string connectionString, string expectedBaseUrl) { var service = new WebPubSubService(connectionString, "testHub"); diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs index 6c264b99cba1..5db1a2474534 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.WebJobs.Host.Executors; +using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; @@ -115,7 +116,7 @@ public async Task TestProcessRequest_MessageMediaTypes(string mediaType, HttpSta private WebPubSubTriggerDispatcher SetupDispatcher(string hub = TestHub, WebPubSubEventType type = TestType, string eventName = TestEvent) { var funcName = $"{hub}.{type}.{eventName}".ToLower(); - var dispatcher = new WebPubSubTriggerDispatcher(); + var dispatcher = new WebPubSubTriggerDispatcher(NullLogger.Instance); var executor = new Mock(); executor.Setup(f => f.TryExecuteAsync(It.IsAny(), It.IsAny())) .Returns(Task.FromResult(new FunctionResult(true))); From 116f650c6e44e83432c1773d64a8170c2b0ad023 Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Thu, 22 Apr 2021 16:42:10 +0800 Subject: [PATCH 07/17] add changelog. --- .../CHANGELOG.md | 5 +++++ .../src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/CHANGELOG.md diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/CHANGELOG.md b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/CHANGELOG.md new file mode 100644 index 000000000000..ba0d8a765484 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/CHANGELOG.md @@ -0,0 +1,5 @@ +# Release History + +## 1.0.0-beta.1 (2021-04-26) + +- The initial beta release of Microsoft.Azure.WebJobs.Extensions.WebPubSub 1.0.0 \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj index 94dcd60c630a..5cda94beb19a 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj @@ -3,8 +3,7 @@ $(RequiredTargetFrameworks) Microsoft.Azure.WebJobs.Extensions.WebPubSub - 8.0 - 1.0.0-alpha.1 + 1.0.0-beta.1 $(NoWarn);AZC0001;CS1591;SA1636;CA1056 true From 549937303b4dca0b20101c97c62b9f1e2737289c Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Fri, 23 Apr 2021 17:21:15 +0800 Subject: [PATCH 08/17] fix bug --- .../src/Services/ServiceConfigParser.cs | 6 ++++-- .../src/Services/WebPubSubService.cs | 2 +- .../src/Trigger/WebPubSubTriggerDispatcher.cs | 13 +++++++++---- .../tests/WebPubSubServiceTests.cs | 11 +++++++++++ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs index 598ad8805788..1603570fb023 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs @@ -10,6 +10,8 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { internal class ServiceConfigParser { + private static char[] _valueSeparator = new char[] { '=' }; + public Uri Endpoint { get; } public string AccessKey { get; } @@ -45,11 +47,11 @@ private static Dictionary ParseConnectionString(string connectio try { - setting = items.Where(x => x.Length > 0).ToDictionary(x => x.Split('=')[0], y => y.Split('=')[1], StringComparer.CurrentCultureIgnoreCase); + setting = items.Where(x => x.Length > 0).ToDictionary(x => x.Split(_valueSeparator, 2)[0], y => y.Split(_valueSeparator, 2)[1], StringComparer.InvariantCultureIgnoreCase); } catch (Exception) { - throw new ArgumentException($"Invalid Web PubSub connection string, please check"); + throw new ArgumentException($"Invalid Web PubSub connection string, please check."); } return setting; } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs index 7d97a64de2d9..886b048e18a1 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs @@ -22,7 +22,7 @@ public WebPubSubService(string connectionString, string hubName) new UriBuilder(_serviceConfig.Endpoint) : new UriBuilder(_serviceConfig.Endpoint.Scheme, _serviceConfig.Endpoint.Host, _serviceConfig.Port); - _client = new WebPubSubServiceClient(endpoint.Uri, hubName, new AzureKeyCredential(_serviceConfig.AccessKey)); + _client = new WebPubSubServiceClient(connectionString, hubName); } internal WebPubSubConnection GetClientConnection(string userId = null, string[] roles = null) diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs index 12aa671fc7e6..e7fac2f5d71e 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs @@ -136,13 +136,18 @@ await executor.Executor.TryExecuteAsync(new TriggeredFunctionData using (token.Register(() => tcs.TrySetCanceled())) { var response = await tcs.Task.ConfigureAwait(false); - var validResponse = BuildValidResponse(response, requestType); - if (validResponse != null) + // Skip no returns + if (response != null) { - return validResponse; + var validResponse = BuildValidResponse(response, requestType); + + if (validResponse != null) + { + return validResponse; + } + _logger.LogWarning($"Invalid response type {response.GetType()} regarding current request: {requestType}"); } - _logger.LogWarning($"Invalid response type regarding current request: {requestType}"); } } catch (Exception ex) diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs index 2ec2be51bcc7..110bc854eff2 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs @@ -22,5 +22,16 @@ public void TestWebPubSubConnection_Scheme(string connectionString, string expec Assert.NotNull(clientConnection); Assert.Equal(expectedBaseUrl, clientConnection.BaseUrl); } + + [Fact] + public void TestConfigParser() + { + var testconnection = "Endpoint=http://abc;Port=888;AccessKey=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGH==A;Version=1.0;"; + var configs = new ServiceConfigParser(testconnection); + + Assert.Equal("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGH==A", configs.AccessKey); + Assert.Equal("http://abc/", configs.Endpoint.ToString()); + Assert.Equal(888, configs.Port); + } } } From 86fa0824bf7882ccd6b7bed43d56434d462a2433 Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Sun, 25 Apr 2021 18:56:05 +0800 Subject: [PATCH 09/17] refactor and resolve some comments. --- .../Bindings/Output/AddConnectionToGroup.cs | 16 ++++ .../src/Bindings/Output/AddUserToGroup.cs | 16 ++++ .../BinaryDataExtensions.cs} | 9 ++- .../Output/BinaryDataJsonConverter.cs | 27 +++++++ .../Bindings/Output/CloseClientConnection.cs | 16 ++++ .../Bindings/Output/GrantGroupPermission.cs | 17 +++++ .../Output/RemoveConnectionFromGroup.cs | 16 ++++ .../Output/RemoveUserFromAllGroups.cs | 14 ++++ .../Bindings/Output/RemoveUserFromGroup.cs | 16 ++++ .../Bindings/Output/RevokeGroupPermission.cs | 17 +++++ .../src/Bindings/Output/SendToAll.cs | 20 +++++ .../src/Bindings/Output/SendToConnection.cs | 20 +++++ .../src/Bindings/Output/SendToGroup.cs | 22 ++++++ .../src/Bindings/Output/SendToUser.cs | 20 +++++ .../src/Bindings/Output/WebPubSubOperation.cs | 28 +++++++ .../Output/WebPubSubOperationJsonConverter.cs | 69 +++++++++++++++++ .../WebPubSubOperationKind.cs} | 2 +- .../{ => Response}/ConnectResponse.cs | 0 .../Bindings/{ => Response}/ErrorResponse.cs | 0 .../{ => Response}/MessageResponse.cs | 4 +- .../{ => Response}/ServiceResponse.cs | 0 .../{ => Response}/WebPubSubErrorCode.cs | 0 .../src/Bindings/WebPubSubAsyncCollector.cs | 8 +- .../src/Bindings/WebPubSubEvent.cs | 40 ---------- .../src/Bindings/WebPubSubMessage.cs | 56 -------------- .../Bindings/WebPubSubMessageJsonConverter.cs | 24 ------ .../src/Config/WebPubSubConfigProvider.cs | 11 +-- .../src/Services/IWebPubSubService.cs | 24 +++--- .../src/Services/ServiceConfigParser.cs | 3 +- .../src/Services/WebPubSubService.cs | 41 +++++----- .../src/Trigger/WebPubSubTriggerBinding.cs | 2 +- .../src/Trigger/WebPubSubTriggerDispatcher.cs | 4 +- .../src/Trigger/WebPubSubTriggerEvent.cs | 3 +- .../tests/JObjectTests.cs | 74 ++++++------------- .../tests/WebPubSubAsyncCollectorTests.cs | 10 +-- .../WebPubSubTriggerValueProviderTests.cs | 3 +- 36 files changed, 420 insertions(+), 232 deletions(-) create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/AddConnectionToGroup.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/AddUserToGroup.cs rename sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/{WebPubSubMessageExtensions.cs => Output/BinaryDataExtensions.cs} (77%) create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataJsonConverter.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/CloseClientConnection.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/GrantGroupPermission.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveConnectionFromGroup.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveUserFromAllGroups.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveUserFromGroup.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RevokeGroupPermission.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToAll.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToConnection.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToGroup.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToUser.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperation.cs create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperationJsonConverter.cs rename sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/{WebPubSubOperation.cs => Output/WebPubSubOperationKind.cs} (97%) rename sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/{ => Response}/ConnectResponse.cs (100%) rename sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/{ => Response}/ErrorResponse.cs (100%) rename sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/{ => Response}/MessageResponse.cs (81%) rename sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/{ => Response}/ServiceResponse.cs (100%) rename sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/{ => Response}/WebPubSubErrorCode.cs (100%) delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubEvent.cs delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessage.cs delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageJsonConverter.cs diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/AddConnectionToGroup.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/AddConnectionToGroup.cs new file mode 100644 index 000000000000..de465f53f9a0 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/AddConnectionToGroup.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class AddConnectionToGroup : WebPubSubOperation + { + public string ConnectionId { get; set; } + + public string Group { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/AddUserToGroup.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/AddUserToGroup.cs new file mode 100644 index 000000000000..98d6d58e65b9 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/AddUserToGroup.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class AddUserToGroup : WebPubSubOperation + { + public string UserId { get; set; } + + public string Group { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageExtensions.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataExtensions.cs similarity index 77% rename from sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageExtensions.cs rename to sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataExtensions.cs index fd52f1301954..eab02cca42cc 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageExtensions.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataExtensions.cs @@ -8,9 +8,9 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { - internal static class WebPubSubMessageExtensions + internal static class BinaryDataExtensions { - public static object Convert(this WebPubSubMessage message, Type targetType) + public static object Convert(this BinaryData message, Type targetType) { if (targetType == typeof(JObject)) { @@ -32,6 +32,11 @@ public static object Convert(this WebPubSubMessage message, Type targetType) return message.ToString(); } + if (targetType == typeof(BinaryData)) + { + return message; + } + return null; } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataJsonConverter.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataJsonConverter.cs new file mode 100644 index 000000000000..b0a09d06b632 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataJsonConverter.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal class BinaryDataJsonConverter : JsonConverter + { + public override BinaryData ReadJson(JsonReader reader, Type objectType, BinaryData existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.String) + { + return BinaryData.FromString(serializer.Deserialize(reader)); + } + // string JObject + return BinaryData.FromString(JToken.Load(reader).ToString()); + } + + public override void WriteJson(JsonWriter writer, BinaryData value, JsonSerializer serializer) + { + serializer.Serialize(writer, value.ToString()); + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/CloseClientConnection.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/CloseClientConnection.cs new file mode 100644 index 000000000000..25582768a5f3 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/CloseClientConnection.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class CloseClientConnection : WebPubSubOperation + { + public string ConnectionId { get; set; } + + public string Reason { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/GrantGroupPermission.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/GrantGroupPermission.cs new file mode 100644 index 000000000000..f68a2c505d36 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/GrantGroupPermission.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Azure.Messaging.WebPubSub; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class GrantGroupPermission : WebPubSubOperation + { + public string ConnectionId { get; set; } + + public WebPubSubPermission Permission { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveConnectionFromGroup.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveConnectionFromGroup.cs new file mode 100644 index 000000000000..8f7d00bf10b1 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveConnectionFromGroup.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class RemoveConnectionFromGroup : WebPubSubOperation + { + public string ConnectionId { get; set; } + + public string Group { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveUserFromAllGroups.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveUserFromAllGroups.cs new file mode 100644 index 000000000000..2695fb8b14f8 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveUserFromAllGroups.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class RemoveUserFromAllGroups : WebPubSubOperation + { + public string UserId { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveUserFromGroup.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveUserFromGroup.cs new file mode 100644 index 000000000000..1eadb87f8be0 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RemoveUserFromGroup.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class RemoveUserFromGroup : WebPubSubOperation + { + public string UserId { get; set; } + + public string Group { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RevokeGroupPermission.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RevokeGroupPermission.cs new file mode 100644 index 000000000000..f979f7a27388 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RevokeGroupPermission.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Azure.Messaging.WebPubSub; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class RevokeGroupPermission : WebPubSubOperation + { + public string ConnectionId { get; set; } + + public WebPubSubPermission Permission { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToAll.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToAll.cs new file mode 100644 index 000000000000..01d42587d84f --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToAll.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class SendToAll : WebPubSubOperation + { + [JsonConverter(typeof(BinaryDataJsonConverter))] + public BinaryData Message { get; set; } + + public MessageDataType DataType { get; set; } + + public string[] Excluded { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToConnection.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToConnection.cs new file mode 100644 index 000000000000..9e24e5d7eedb --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToConnection.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class SendToConnection : WebPubSubOperation + { + public string ConnectionId { get; set; } + + [JsonConverter(typeof(BinaryDataJsonConverter))] + public BinaryData Message { get; set; } + + public MessageDataType DataType { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToGroup.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToGroup.cs new file mode 100644 index 000000000000..4fa79aa36d2c --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToGroup.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class SendToGroup : WebPubSubOperation + { + public string Group { get; set; } + + [JsonConverter(typeof(BinaryDataJsonConverter))] + public BinaryData Message { get; set; } + + public MessageDataType DataType { get; set; } + + public string[] Excluded { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToUser.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToUser.cs new file mode 100644 index 000000000000..8210fd0604ff --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToUser.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class SendToUser : WebPubSubOperation + { + public string UserId { get; set; } + + [JsonConverter(typeof(BinaryDataJsonConverter))] + public BinaryData Message { get; set; } + + public MessageDataType DataType { get; set; } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperation.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperation.cs new file mode 100644 index 000000000000..042a4e7138d8 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperation.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using System; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public abstract class WebPubSubOperation + { + private WebPubSubOperationKind _operationKind; + + public WebPubSubOperationKind OperationKind + { + get + { + return (WebPubSubOperationKind)Enum.Parse(typeof(WebPubSubOperationKind), GetType().Name); + } + set + { + // used in type-less for deserialize. + _operationKind = value; + } + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperationJsonConverter.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperationJsonConverter.cs new file mode 100644 index 000000000000..abe22cd49057 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperationJsonConverter.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + internal class WebPubSubOperationJsonConverter : JsonConverter + { + public override WebPubSubOperation ReadJson(JsonReader reader, Type objectType, WebPubSubOperation existingValue, bool hasExistingValue, JsonSerializer serializer) + { + try + { + if (reader == null) + { + throw new ArgumentNullException(nameof(reader)); + } + if (objectType == null) + { + throw new ArgumentNullException(nameof(objectType)); + } + if (serializer == null) + { + throw new ArgumentNullException(nameof(serializer)); + } + + if (reader.TokenType == JsonToken.Null) + { + return null; + } + + JObject jObject = JObject.Load(reader); + var kind = jObject.Properties().SingleOrDefault(p => + p.Name.Equals("operationKind", StringComparison.OrdinalIgnoreCase)).Value.ToString().ToLowerInvariant(); + + existingValue = kind switch + { + "sendtoall" => jObject.ToObject(), + "sendtogroup" => jObject.ToObject(), + "sendtoconnection" => jObject.ToObject(), + "sendtouser" => jObject.ToObject(), + "addusertogroup" => jObject.ToObject(), + "removeuserfromgroup" => jObject.ToObject(), + "addconnectiontogroup" => jObject.ToObject(), + "removeconnectionfromgroup" => jObject.ToObject(), + "removeuserfromallgroups" => jObject.ToObject(), + "closeclientconnection" => jObject.ToObject(), + "grantgrouppermission" => jObject.ToObject(), + "revokegrouppermission" => jObject.ToObject(), + _ => jObject.ToObject() + }; + hasExistingValue = true; + return existingValue; + } + catch (Exception) + { + return null; + } + } + + public override void WriteJson(JsonWriter writer, WebPubSubOperation value, JsonSerializer serializer) + { + serializer.Serialize(writer, value.ToString()); + } + } +} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubOperation.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperationKind.cs similarity index 97% rename from sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubOperation.cs rename to sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperationKind.cs index 329fedcdcdfe..19b4fe223446 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubOperation.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperationKind.cs @@ -11,7 +11,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub /// Supported operations of rest calls. /// [JsonConverter(typeof(StringEnumConverter))] - public enum WebPubSubOperation + public enum WebPubSubOperationKind { [EnumMember(Value = "sendToAll")] SendToAll, diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectResponse.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/ConnectResponse.cs similarity index 100% rename from sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ConnectResponse.cs rename to sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/ConnectResponse.cs diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ErrorResponse.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/ErrorResponse.cs similarity index 100% rename from sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ErrorResponse.cs rename to sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/ErrorResponse.cs diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/MessageResponse.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/MessageResponse.cs similarity index 81% rename from sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/MessageResponse.cs rename to sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/MessageResponse.cs index 15e190bbb311..04662a2c53b4 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/MessageResponse.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/MessageResponse.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using System; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { @@ -10,7 +11,8 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub public class MessageResponse : ServiceResponse { [JsonProperty(Required = Required.Always)] - public WebPubSubMessage Message { get; set; } + [JsonConverter(typeof(BinaryDataJsonConverter))] + public BinaryData Message { get; set; } public MessageDataType DataType { get; set; } = MessageDataType.Text; } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ServiceResponse.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/ServiceResponse.cs similarity index 100% rename from sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/ServiceResponse.cs rename to sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/ServiceResponse.cs diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubErrorCode.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/WebPubSubErrorCode.cs similarity index 100% rename from sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubErrorCode.cs rename to sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Response/WebPubSubErrorCode.cs diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubAsyncCollector.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubAsyncCollector.cs index a2b99f86387c..7242477bbdb6 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubAsyncCollector.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubAsyncCollector.cs @@ -8,7 +8,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { - internal class WebPubSubAsyncCollector: IAsyncCollector + internal class WebPubSubAsyncCollector : IAsyncCollector { private readonly IWebPubSubService _service; @@ -17,7 +17,7 @@ internal WebPubSubAsyncCollector(IWebPubSubService service) _service = service; } - public async Task AddAsync(WebPubSubEvent item, CancellationToken cancellationToken = default) + public async Task AddAsync(WebPubSubOperation item, CancellationToken cancellationToken = default) { if (item == null) { @@ -26,7 +26,7 @@ public async Task AddAsync(WebPubSubEvent item, CancellationToken cancellationTo try { - var method = typeof(IWebPubSubService).GetMethod(item.Operation.ToString(), + var method = typeof(IWebPubSubService).GetMethod(item.OperationKind.ToString(), BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); var task = (Task)method.Invoke(_service, new object[] { item }); @@ -35,7 +35,7 @@ public async Task AddAsync(WebPubSubEvent item, CancellationToken cancellationTo } catch (Exception ex) { - throw new ArgumentException($"Not supported operation: {item.Operation}, exception: {ex}"); + throw new ArgumentException($"Not supported operation: {item.OperationKind}, exception: {ex}"); } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubEvent.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubEvent.cs deleted file mode 100644 index 8a8cbbbd1ce2..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubEvent.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.ComponentModel.DataAnnotations; - -using Azure.Messaging.WebPubSub; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; - -namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub -{ - /// - /// Output binding object to invoke rest calls to services - /// - [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] - public class WebPubSubEvent - { - [Required] - [JsonRequired] - public WebPubSubOperation Operation { get; set; } - - public string Group { get; set; } - - public string UserId { get; set; } - - public string ConnectionId { get; set; } - - public string[] Excluded { get; set; } - - public string Reason { get; set; } - - [JsonConverter(typeof(StringEnumConverter))] - public WebPubSubPermission Permission { get; set; } - - public WebPubSubMessage Message { get; set; } - - public MessageDataType DataType { get; set; } = MessageDataType.Text; - } -} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessage.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessage.cs deleted file mode 100644 index 614bb0209e7d..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessage.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.IO; - -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - -namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub -{ - /// - /// Message to communicate with service - /// - [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] - [JsonConverter(typeof(WebPubSubMessageJsonConverter))] - public class WebPubSubMessage - { - private readonly BinaryData _body; - - public WebPubSubMessage(Stream message) - { - _body = BinaryData.FromStream(message); - } - - public WebPubSubMessage(string message) - { - _body = BinaryData.FromString(message); - } - - public WebPubSubMessage(byte[] message) - { - _body = BinaryData.FromBytes(message); - } - - public byte[] ToArray() - { - return _body.ToArray(); - } - - public override string ToString() - { - return _body.ToString(); - } - - public Stream ToStream() - { - return _body.ToStream(); - } - - public BinaryData ToBinaryData() - { - return _body; - } - } -} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageJsonConverter.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageJsonConverter.cs deleted file mode 100644 index c98f88275608..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/WebPubSubMessageJsonConverter.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub -{ - internal class WebPubSubMessageJsonConverter : JsonConverter - { - public override WebPubSubMessage ReadJson(JsonReader reader, Type objectType, WebPubSubMessage existingValue, bool hasExistingValue, JsonSerializer serializer) - { - var jObject = JToken.Load(reader); - - return new WebPubSubMessage(jObject.ToString()); - } - - public override void WriteJson(JsonWriter writer, WebPubSubMessage value, JsonSerializer serializer) - { - serializer.Serialize(writer, value.ToString()); - } - } -} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs index af9d75741e64..c41ebf63d6b2 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs @@ -14,6 +14,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub @@ -66,8 +67,8 @@ public void Initialize(ExtensionConfigContext context) // bindings context .AddConverter(JObject.FromObject) - .AddConverter(ConvertFromJObject) - .AddConverter(ConvertFromJObject); + .AddConverter(ConvertWebPubSubOperationFromJObject) + .AddConverter(ConvertWebPubSubOperationFromJObject); // Trigger binding context.AddBindingRule() @@ -110,7 +111,7 @@ internal WebPubSubService GetService(WebPubSubAttribute attribute) return new WebPubSubService(connectionString, hubName); } - private IAsyncCollector CreateCollector(WebPubSubAttribute attribute) + private IAsyncCollector CreateCollector(WebPubSubAttribute attribute) { return new WebPubSubAsyncCollector(GetService(attribute)); } @@ -143,9 +144,9 @@ private void AddSettings(string connectionString) } } - private static T ConvertFromJObject(JObject input) + private static T ConvertWebPubSubOperationFromJObject(JObject input) { - return input.ToObject(); + return JsonConvert.DeserializeObject(input.ToString(), new WebPubSubOperationJsonConverter()); } } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/IWebPubSubService.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/IWebPubSubService.cs index 5b2280d2e968..2e6ef67bec4f 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/IWebPubSubService.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/IWebPubSubService.cs @@ -7,28 +7,28 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { internal interface IWebPubSubService { - Task SendToAll(WebPubSubEvent webPubSubEvent); + Task SendToAll(SendToAll webPubSubEvent); - Task CloseClientConnection(WebPubSubEvent webPubSubEvent); + Task CloseClientConnection(CloseClientConnection webPubSubEvent); - Task SendToConnection(WebPubSubEvent webPubSubEvent); + Task SendToConnection(SendToConnection webPubSubEvent); - Task SendToGroup(WebPubSubEvent webPubSubEvent); + Task SendToGroup(SendToGroup webPubSubEvent); - Task AddConnectionToGroup(WebPubSubEvent webPubSubEvent); + Task AddConnectionToGroup(AddConnectionToGroup webPubSubEvent); - Task RemoveConnectionFromGroup(WebPubSubEvent webPubSubEvent); + Task RemoveConnectionFromGroup(RemoveConnectionFromGroup webPubSubEvent); - Task SendToUser(WebPubSubEvent webPubSubEvent); + Task SendToUser(SendToUser webPubSubEvent); - Task AddUserToGroup(WebPubSubEvent webPubSubEvent); + Task AddUserToGroup(AddUserToGroup webPubSubEvent); - Task RemoveUserFromGroup(WebPubSubEvent webPubSubEvent); + Task RemoveUserFromGroup(RemoveUserFromGroup webPubSubEvent); - Task RemoveUserFromAllGroups(WebPubSubEvent webPubSubEvent); + Task RemoveUserFromAllGroups(RemoveUserFromAllGroups webPubSubEvent); - Task GrantGroupPermission(WebPubSubEvent webPubSubEvent); + Task GrantGroupPermission(GrantGroupPermission webPubSubEvent); - Task RevokeGroupPermission(WebPubSubEvent webPubSubEvent); + Task RevokeGroupPermission(RevokeGroupPermission webPubSubEvent); } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs index 1603570fb023..abfbd00ca4a9 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub internal class ServiceConfigParser { private static char[] _valueSeparator = new char[] { '=' }; + private static char _partSeparator = ';'; public Uri Endpoint { get; } @@ -43,7 +44,7 @@ private static Dictionary ParseConnectionString(string connectio } var setting = new Dictionary(); - var items = connectionString.Split(';'); + var items = connectionString.Split(_partSeparator); try { diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs index 886b048e18a1..3a290bb6b208 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/WebPubSubService.cs @@ -4,7 +4,6 @@ using System; using System.Threading.Tasks; -using Azure; using Azure.Core; using Azure.Messaging.WebPubSub; @@ -15,14 +14,10 @@ internal class WebPubSubService : IWebPubSubService private readonly WebPubSubServiceClient _client; private readonly ServiceConfigParser _serviceConfig; - public WebPubSubService(string connectionString, string hubName) + public WebPubSubService(string connectionString, string hub) { _serviceConfig = new ServiceConfigParser(connectionString); - UriBuilder endpoint = _serviceConfig.Port == 80 ? - new UriBuilder(_serviceConfig.Endpoint) : - new UriBuilder(_serviceConfig.Endpoint.Scheme, _serviceConfig.Endpoint.Host, _serviceConfig.Port); - - _client = new WebPubSubServiceClient(connectionString, hubName); + _client = new WebPubSubServiceClient(connectionString, hub); } internal WebPubSubConnection GetClientConnection(string userId = null, string[] roles = null) @@ -40,70 +35,70 @@ internal WebPubSubConnection GetClientConnection(string userId = null, string[] return new WebPubSubConnection(url); } - public async Task AddConnectionToGroup(WebPubSubEvent webPubSubEvent) + public async Task AddConnectionToGroup(AddConnectionToGroup webPubSubEvent) { await _client.AddConnectionToGroupAsync(webPubSubEvent.Group, webPubSubEvent.ConnectionId).ConfigureAwait(false); } - public async Task AddUserToGroup(WebPubSubEvent webPubSubEvent) + public async Task AddUserToGroup(AddUserToGroup webPubSubEvent) { await _client.AddUserToGroupAsync(webPubSubEvent.Group, webPubSubEvent.UserId).ConfigureAwait(false); } - public async Task CloseClientConnection(WebPubSubEvent webPubSubEvent) + public async Task CloseClientConnection(CloseClientConnection webPubSubEvent) { await _client.CloseClientConnectionAsync(webPubSubEvent.ConnectionId, webPubSubEvent.Reason).ConfigureAwait(false); } - public async Task GrantGroupPermission(WebPubSubEvent webPubSubEvent) + public async Task GrantGroupPermission(GrantGroupPermission webPubSubEvent) { await _client.GrantPermissionAsync(webPubSubEvent.Permission, webPubSubEvent.ConnectionId).ConfigureAwait(false); } - public async Task RemoveConnectionFromGroup(WebPubSubEvent webPubSubEvent) + public async Task RemoveConnectionFromGroup(RemoveConnectionFromGroup webPubSubEvent) { await _client.RemoveConnectionFromGroupAsync(webPubSubEvent.Group, webPubSubEvent.ConnectionId).ConfigureAwait(false); } - public async Task RemoveUserFromAllGroups(WebPubSubEvent webPubSubEvent) + public async Task RemoveUserFromAllGroups(RemoveUserFromAllGroups webPubSubEvent) { await _client.RemoveUserFromAllGroupsAsync(webPubSubEvent.UserId).ConfigureAwait(false); } - public async Task RemoveUserFromGroup(WebPubSubEvent webPubSubEvent) + public async Task RemoveUserFromGroup(RemoveUserFromGroup webPubSubEvent) { await _client.RemoveUserFromGroupAsync(webPubSubEvent.Group, webPubSubEvent.UserId).ConfigureAwait(false); } - public async Task RevokeGroupPermission(WebPubSubEvent webPubSubEvent) + public async Task RevokeGroupPermission(RevokeGroupPermission webPubSubEvent) { await _client.RevokePermissionAsync(webPubSubEvent.Permission, webPubSubEvent.ConnectionId).ConfigureAwait(false); } - public async Task SendToAll(WebPubSubEvent webPubSubEvent) + public async Task SendToAll(SendToAll webPubSubEvent) { - var content = RequestContent.Create(webPubSubEvent.Message.ToBinaryData()); + var content = RequestContent.Create(webPubSubEvent.Message); var contentType = Utilities.GetContentType(webPubSubEvent.DataType); await _client.SendToAllAsync(content, contentType, webPubSubEvent.Excluded).ConfigureAwait(false); } - public async Task SendToConnection(WebPubSubEvent webPubSubEvent) + public async Task SendToConnection(SendToConnection webPubSubEvent) { - var content = RequestContent.Create(webPubSubEvent.Message.ToBinaryData()); + var content = RequestContent.Create(webPubSubEvent.Message); var contentType = Utilities.GetContentType(webPubSubEvent.DataType); await _client.SendToConnectionAsync(webPubSubEvent.ConnectionId, content, contentType).ConfigureAwait(false); } - public async Task SendToGroup(WebPubSubEvent webPubSubEvent) + public async Task SendToGroup(SendToGroup webPubSubEvent) { - var content = RequestContent.Create(webPubSubEvent.Message.ToBinaryData()); + var content = RequestContent.Create(webPubSubEvent.Message); var contentType = Utilities.GetContentType(webPubSubEvent.DataType); await _client.SendToGroupAsync(webPubSubEvent.Group, content, contentType, webPubSubEvent.Excluded).ConfigureAwait(false); } - public async Task SendToUser(WebPubSubEvent webPubSubEvent) + public async Task SendToUser(SendToUser webPubSubEvent) { - var content = RequestContent.Create(webPubSubEvent.Message.ToBinaryData()); + var content = RequestContent.Create(webPubSubEvent.Message); var contentType = Utilities.GetContentType(webPubSubEvent.DataType); await _client.SendToUserAsync(webPubSubEvent.UserId, content, contentType).ConfigureAwait(false); } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs index b2a5df2f88dd..1a7d21eae88a 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs @@ -165,7 +165,7 @@ private object GetValueByName(string parameterName, Type targetType) private static object ConvertTypeIfPossible(object source, Type target) { - if (source is WebPubSubMessage message) + if (source is BinaryData message) { return message.Convert(target); } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs index e7fac2f5d71e..01bd6fd55d21 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs @@ -65,7 +65,7 @@ public async Task ExecuteAsync(HttpRequestMessage req, if (_listeners.TryGetValue(function, out var executor)) { - WebPubSubMessage message = null; + BinaryData message = null; MessageDataType dataType = MessageDataType.Text; IDictionary claims = null; IDictionary query = null; @@ -104,7 +104,7 @@ public async Task ExecuteAsync(HttpRequestMessage req, } var payload = await req.Content.ReadAsByteArrayAsync().ConfigureAwait(false); - message = new WebPubSubMessage(payload); + message = BinaryData.FromBytes(payload); break; } default: diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerEvent.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerEvent.cs index 047a018f18ea..8b973c70e00f 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerEvent.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerEvent.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -13,7 +14,7 @@ internal class WebPubSubTriggerEvent /// public ConnectionContext ConnectionContext { get; set; } - public WebPubSubMessage Message { get; set; } + public BinaryData Message { get; set; } public MessageDataType DataType { get; set; } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs index 095e03d8513b..85cc637712e3 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs @@ -1,12 +1,14 @@ // 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.IO; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Xunit; @@ -14,64 +16,30 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests { public class JObjectTests { - public static IEnumerable MessageTestData => - new List - { - new object[] {new WebPubSubMessage("Hello"), MessageDataType.Binary, "Hello" }, - new object[] {new WebPubSubMessage("Hello"), MessageDataType.Json, "Hello" }, - new object[] {new WebPubSubMessage("Hello"), MessageDataType.Text, "Hello" }, - new object[] {new WebPubSubMessage(Encoding.UTF8.GetBytes("Hello")) , MessageDataType.Binary, "Hello" }, - new object[] {new WebPubSubMessage(Encoding.UTF8.GetBytes("Hello")), MessageDataType.Json, "Hello" }, - new object[] {new WebPubSubMessage(Encoding.UTF8.GetBytes("Hello")), MessageDataType.Text, "Hello" }, - new object[] {new WebPubSubMessage(new MemoryStream(Encoding.UTF8.GetBytes("Hello"))), MessageDataType.Binary, "Hello" }, - new object[] {new WebPubSubMessage(new MemoryStream(Encoding.UTF8.GetBytes("Hello"))), MessageDataType.Json, "Hello" }, - new object[] {new WebPubSubMessage(new MemoryStream(Encoding.UTF8.GetBytes("Hello"))), MessageDataType.Text, "Hello" } - }; - - [Fact] - public void TestConvertFromJObject() + [Theory] + [InlineData(nameof(SendToAll))] + [InlineData(nameof(SendToConnection))] + [InlineData(nameof(SendToGroup))] + [InlineData(nameof(SendToUser))] + [InlineData(nameof(AddConnectionToGroup))] + [InlineData(nameof(AddUserToGroup))] + [InlineData(nameof(RemoveConnectionFromGroup))] + [InlineData(nameof(RemoveUserFromAllGroups))] + [InlineData(nameof(RemoveUserFromGroup))] + [InlineData(nameof(CloseClientConnection))] + [InlineData(nameof(GrantGroupPermission))] + [InlineData(nameof(RevokeGroupPermission))] + public void TestOutputConvert(string operationKind) { - var wpsEvent = @"{ - ""operation"":""sendToUser"", - ""userId"": ""abc"", - ""message"": ""test"", - ""dataType"": ""text"" - }"; + var input = @"{ ""operationKind"":""{0}"",""userId"":""user"", ""group"":""group1"",""connectionId"":""connection"",""message"":""test"",""dataType"":""text"", ""reason"":""close""}"; - var jsevent = JObject.Parse(wpsEvent); + var replacedInput = input.Replace("{0}", operationKind); - var result = jsevent.ToObject(); + var jObject = JObject.Parse(replacedInput); - Assert.Equal("test", result.Message.ToString()); - Assert.Equal(MessageDataType.Text, result.DataType); - Assert.Equal(WebPubSubOperation.SendToUser, result.Operation); - Assert.Equal("abc", result.UserId); - } + var converted = JsonConvert.DeserializeObject(replacedInput, new WebPubSubOperationJsonConverter()); - [Theory] - [MemberData(nameof(MessageTestData))] - public void TestConvertMessageToAndFromJObject(WebPubSubMessage message, MessageDataType dataType, string expected) - { - var wpsEvent = new WebPubSubEvent - { - Operation = WebPubSubOperation.SendToConnection, - ConnectionId = "abc", - Message = message, - DataType = dataType - }; - - var jsObject = JObject.FromObject(wpsEvent); - - Assert.Equal("sendToConnection", jsObject["operation"].ToString()); - Assert.Equal("abc", jsObject["connectionId"].ToString()); - Assert.Equal(expected, jsObject["message"].ToString()); - - var result = jsObject.ToObject(); - - Assert.Equal(expected, result.Message.ToString()); - Assert.Equal(dataType, result.DataType); - Assert.Equal(WebPubSubOperation.SendToConnection, result.Operation); - Assert.Equal("abc", result.ConnectionId); + Assert.Equal(operationKind, converted.OperationKind.ToString()); } [Fact] diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs index 1eaba55dea2e..8f00a18f1b12 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs @@ -1,6 +1,7 @@ // 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.Threading.Tasks; using Moq; using Xunit; @@ -16,17 +17,16 @@ public async Task AddAsync_WebPubSubEvent_SendAll() var collector = new WebPubSubAsyncCollector(serviceMock.Object); var message = "new message"; - await collector.AddAsync(new WebPubSubEvent + await collector.AddAsync(new SendToAll { - Operation = WebPubSubOperation.SendToAll, - Message = new WebPubSubMessage(message), + Message = BinaryData.FromString(message), DataType = MessageDataType.Text }); - serviceMock.Verify(c => c.SendToAll(It.IsAny()), Times.Once); + serviceMock.Verify(c => c.SendToAll(It.IsAny()), Times.Once); serviceMock.VerifyNoOtherCalls(); - var actualData = (WebPubSubEvent)serviceMock.Invocations[0].Arguments[0]; + var actualData = (SendToAll)serviceMock.Invocations[0].Arguments[0]; Assert.Equal(MessageDataType.Text, actualData.DataType); Assert.Equal(message, actualData.Message.ToString()); } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs index 26fb0f465231..173183490e3d 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs @@ -1,6 +1,7 @@ // 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.Reflection; using Xunit; @@ -25,7 +26,7 @@ public void TestGetValueByName_Valid(string name) UserId = "user1" }, Reason = "reason", - Message = new WebPubSubMessage("message"), + Message = BinaryData.FromString("message"), }; var value = typeof(WebPubSubTriggerEvent) From 175dd015b90cda37dc70e38462b5d571e61f4ff8 Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Mon, 26 Apr 2021 11:33:17 +0800 Subject: [PATCH 10/17] resolve a few comments --- eng/Packages.Data.props | 2 +- .../README.md | 21 ++++--- .../src/Config/WebPubSubConfigProvider.cs | 2 +- .../tests/JObjectTests.cs | 59 +++++++++---------- ....WebJobs.Extensions.WebPubSub.Tests.csproj | 4 +- .../tests/WebPubSubAsyncCollectorTests.cs | 8 +-- .../tests/WebPubSubServiceTests.cs | 17 +++--- .../tests/WebPubSubTriggerDispatcherTests.cs | 45 +++++++------- .../WebPubSubTriggerValueProviderTests.cs | 9 ++- 9 files changed, 78 insertions(+), 89 deletions(-) diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props index 13d9f781e90e..ddc517d8fc83 100644 --- a/eng/Packages.Data.props +++ b/eng/Packages.Data.props @@ -80,11 +80,11 @@ + - diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md index a5a9cd507998..67cdd8672363 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md @@ -20,15 +20,15 @@ You must have an [Azure subscription](https://azure.microsoft.com/free/) and an ### Using Web PubSub input binding -Please follow the [input binding tutorial](https://scaling-garbanzo-3fe86650.pages.github.io/references/functions-bindings#input-binding) to learn about using this extension for building `WebPubSubConnection` to create Websockets connection to service with input binding. +Please follow the [input binding tutorial](https://azure.github.io/azure-webpubsub/references/functions-bindings#input-binding) to learn about using this extension for building `WebPubSubConnection` to create Websockets connection to service with input binding. ### Using Web PubSub output binding -Please follow the [output binding tutorial](https://scaling-garbanzo-3fe86650.pages.github.io/references/functions-bindings#output-binding) to learn about using this extension for publishing Web PubSub messages. +Please follow the [output binding tutorial](https://azure.github.io/azure-webpubsub/references/functions-bindings#output-binding) to learn about using this extension for publishing Web PubSub messages. ### Using Web PubSub trigger -Please follow the [trigger binding tutorial](https://scaling-garbanzo-3fe86650.pages.github.io/references/functions-bindings#trigger-binding) to learn about triggering an Azure Function when an event is sent from service upstream. +Please follow the [trigger binding tutorial](https://azure.github.io/azure-webpubsub/references/functions-bindings#trigger-binding) to learn about triggering an Azure Function when an event is sent from service upstream. ## Examples @@ -51,12 +51,11 @@ public static WebPubSubConnection Run( [FunctionName("WebPubSubOutputBindingFunction")] public static async Task RunAsync( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req, - [WebPubSub(Hub = "simplechat")] IAsyncCollector webpubsubEvent) + [WebPubSub(Hub = "simplechat")] IAsyncCollector operation) { - await webpubsubEvent.AddAsync(new WebPubSubEvent + await operation.AddAsync(new SendToAll { - Operation = Operation.SendToAll, - Message = new Message("Hello Web PubSub"), + Message = BinaryData.FromString("Hello Web PubSub"), DataType = MessageDataType.Text }); } @@ -67,17 +66,17 @@ public static async Task RunAsync( ```C# Snippet:WebPubSubTriggerFunction [FunctionName("WebPubSubTriggerFunction")] public static async Task RunAsync( - [WebPubSubTrigger("message", EventType.User)] + [WebPubSubTrigger("message", WebPubSubEventType.User)] ConnectionContext context, - WebPubSubMessage message, + string message, MessageDataType dataType) { Console.WriteLine($"Request from: {context.userId}"); - Console.WriteLine($"Request message: {message.Body.ToString()}"); + Console.WriteLine($"Request message: {message}"); Console.WriteLine($"Request message DataType: {dataType}"); return new MessageResponse { - Message = new WebPubSubMessage("ack"), + Message = BinaryData.FromString("ack"), }; } ``` diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs index c41ebf63d6b2..f8fc559cb433 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs @@ -130,7 +130,7 @@ private void ValidateConnectionString(string attributeConnectionString, string a if (string.IsNullOrEmpty(connectionString)) { - throw new InvalidOperationException($"The Service connection string must be set either via an '{Constants.WebPubSubConnectionStringName}' app setting, via an '{Constants.WebPubSubConnectionStringName}' environment variable, or directly in code via {nameof(WebPubSubOptions)}.{nameof(WebPubSubOptions.ConnectionString)} or {attributeConnectionStringName}."); + throw new InvalidOperationException($"The Service connection string must be set either via an '{Constants.WebPubSubConnectionStringName}' app setting, via an '{Constants.WebPubSubConnectionStringName}' environment variable, or {attributeConnectionStringName}."); } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs index 85cc637712e3..c30d94c55ea1 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs @@ -1,34 +1,29 @@ // 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.IO; using System.Net; using System.Net.Http; -using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Xunit; +using NUnit.Framework; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests { public class JObjectTests { - [Theory] - [InlineData(nameof(SendToAll))] - [InlineData(nameof(SendToConnection))] - [InlineData(nameof(SendToGroup))] - [InlineData(nameof(SendToUser))] - [InlineData(nameof(AddConnectionToGroup))] - [InlineData(nameof(AddUserToGroup))] - [InlineData(nameof(RemoveConnectionFromGroup))] - [InlineData(nameof(RemoveUserFromAllGroups))] - [InlineData(nameof(RemoveUserFromGroup))] - [InlineData(nameof(CloseClientConnection))] - [InlineData(nameof(GrantGroupPermission))] - [InlineData(nameof(RevokeGroupPermission))] + [TestCase(nameof(SendToAll))] + [TestCase(nameof(SendToConnection))] + [TestCase(nameof(SendToGroup))] + [TestCase(nameof(SendToUser))] + [TestCase(nameof(AddConnectionToGroup))] + [TestCase(nameof(AddUserToGroup))] + [TestCase(nameof(RemoveConnectionFromGroup))] + [TestCase(nameof(RemoveUserFromAllGroups))] + [TestCase(nameof(RemoveUserFromGroup))] + [TestCase(nameof(CloseClientConnection))] + [TestCase(nameof(GrantGroupPermission))] + [TestCase(nameof(RevokeGroupPermission))] public void TestOutputConvert(string operationKind) { var input = @"{ ""operationKind"":""{0}"",""userId"":""user"", ""group"":""group1"",""connectionId"":""connection"",""message"":""test"",""dataType"":""text"", ""reason"":""close""}"; @@ -39,10 +34,10 @@ public void TestOutputConvert(string operationKind) var converted = JsonConvert.DeserializeObject(replacedInput, new WebPubSubOperationJsonConverter()); - Assert.Equal(operationKind, converted.OperationKind.ToString()); + Assert.AreEqual(operationKind, converted.OperationKind.ToString()); } - [Fact] + [TestCase] public async Task ParseErrorResponse() { var test = @"{""code"":""unauthorized"",""errorMessage"":""not valid user.""}"; @@ -50,13 +45,13 @@ public async Task ParseErrorResponse() var result = BuildResponse(test, RequestType.Connect); Assert.NotNull(result); - Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode); + Assert.AreEqual(HttpStatusCode.Unauthorized, result.StatusCode); var message = await result.Content.ReadAsStringAsync(); - Assert.Equal("not valid user.", message); + Assert.AreEqual("not valid user.", message); } - [Fact] + [TestCase] public async Task ParseConnectResponse() { var test = @"{""userId"":""aaa""}"; @@ -64,14 +59,14 @@ public async Task ParseConnectResponse() var result = BuildResponse(test, RequestType.Connect); Assert.NotNull(result); - Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); var response = await result.Content.ReadAsStringAsync(); var message = (JObject.Parse(response)).ToObject(); - Assert.Equal("aaa", message.UserId); + Assert.AreEqual("aaa", message.UserId); } - [Fact] + [TestCase] public async Task ParseMessageResponse() { var test = @"{""message"":""test"", ""dataType"":""text""}"; @@ -79,14 +74,14 @@ public async Task ParseMessageResponse() var result = BuildResponse(test, RequestType.User); Assert.NotNull(result); - Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); var message = await result.Content.ReadAsStringAsync(); - Assert.Equal("test", message); - Assert.Equal(Constants.ContentTypes.PlainTextContentType, result.Content.Headers.ContentType.MediaType); + Assert.AreEqual("test", message); + Assert.AreEqual(Constants.ContentTypes.PlainTextContentType, result.Content.Headers.ContentType.MediaType); } - [Fact] + [TestCase] public void ParseMessageResponse_InvalidReturnNull() { var test = @"{""message"":""test"", ""dataType"":""hello""}"; @@ -96,7 +91,7 @@ public void ParseMessageResponse_InvalidReturnNull() Assert.Null(result); } - [Fact] + [TestCase] public async Task ParseConnectResponse_ContentMatches() { var test = @"{""test"":""test"",""errorMessage"":""not valid user.""}"; @@ -107,7 +102,7 @@ public async Task ParseConnectResponse_ContentMatches() var actual = JObject.Parse(content); Assert.NotNull(result); - Assert.Equal(expected, actual); + Assert.AreEqual(expected, actual); } private static HttpResponseMessage BuildResponse(string input, RequestType requestType) diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj index 909c5c7c237a..4f16efe43a26 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj @@ -9,8 +9,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs index 8f00a18f1b12..90fc58cd1d1b 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubAsyncCollectorTests.cs @@ -4,13 +4,13 @@ using System; using System.Threading.Tasks; using Moq; -using Xunit; +using NUnit.Framework; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests { public class WebPubSubAsyncCollectorTests { - [Fact] + [TestCase] public async Task AddAsync_WebPubSubEvent_SendAll() { var serviceMock = new Mock(); @@ -27,8 +27,8 @@ await collector.AddAsync(new SendToAll serviceMock.VerifyNoOtherCalls(); var actualData = (SendToAll)serviceMock.Invocations[0].Arguments[0]; - Assert.Equal(MessageDataType.Text, actualData.DataType); - Assert.Equal(message, actualData.Message.ToString()); + Assert.AreEqual(MessageDataType.Text, actualData.DataType); + Assert.AreEqual(message, actualData.Message.ToString()); } //[Fact] diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs index 110bc854eff2..0ea70bdd2b6c 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubServiceTests.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using Xunit; +using NUnit.Framework; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests { @@ -10,9 +10,8 @@ public class WebPubSubServiceTests private const string NormConnectionString = "Endpoint=http://localhost;Port=8080;AccessKey=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGH;Version=1.0;"; private const string SecConnectionString = "Endpoint=https://abc;AccessKey=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGH;Version=1.0;"; - [Theory] - [InlineData(NormConnectionString, "ws://localhost:8080/client/hubs/testHub")] - [InlineData(SecConnectionString, "wss://abc/client/hubs/testHub")] + [TestCase(NormConnectionString, "ws://localhost:8080/client/hubs/testHub")] + [TestCase(SecConnectionString, "wss://abc/client/hubs/testHub")] public void TestWebPubSubConnection_Scheme(string connectionString, string expectedBaseUrl) { var service = new WebPubSubService(connectionString, "testHub"); @@ -20,18 +19,18 @@ public void TestWebPubSubConnection_Scheme(string connectionString, string expec var clientConnection = service.GetClientConnection(); Assert.NotNull(clientConnection); - Assert.Equal(expectedBaseUrl, clientConnection.BaseUrl); + Assert.AreEqual(expectedBaseUrl, clientConnection.BaseUrl); } - [Fact] + [TestCase] public void TestConfigParser() { var testconnection = "Endpoint=http://abc;Port=888;AccessKey=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGH==A;Version=1.0;"; var configs = new ServiceConfigParser(testconnection); - Assert.Equal("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGH==A", configs.AccessKey); - Assert.Equal("http://abc/", configs.Endpoint.ToString()); - Assert.Equal(888, configs.Port); + Assert.AreEqual("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGH==A", configs.AccessKey); + Assert.AreEqual("http://abc/", configs.Endpoint.ToString()); + Assert.AreEqual(888, configs.Port); } } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs index 5db1a2474534..f80f15be1ea4 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs @@ -9,7 +9,7 @@ using Microsoft.Azure.WebJobs.Host.Executors; using Microsoft.Extensions.Logging.Abstractions; using Moq; -using Xunit; +using NUnit.Framework; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests { @@ -24,75 +24,73 @@ public class WebPubSubTriggerDispatcherTests private static HashSet ValidAccessKeys = new HashSet(new string[] { TestKey.AccessKey }); private static string[] ValidSignature = new string[] { TestKey.Signature }; - [Fact] + [TestCase] public async Task TestProcessRequest_ValidRequest() { var dispatcher = SetupDispatcher(); var request = TestHelpers.CreateHttpRequestMessage(TestHub, TestType, TestEvent, TestKey.ConnectionId, ValidSignature); var response = await dispatcher.ExecuteAsync(request, EmptySetting, ValidAccessKeys); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); } - [Fact] + [TestCase] public async Task TestProcessRequest_AllowNullUserId() { var dispatcher = SetupDispatcher(); var request = TestHelpers.CreateHttpRequestMessage(TestHub, TestType, TestEvent, TestKey.ConnectionId, ValidSignature, userId: null); var response = await dispatcher.ExecuteAsync(request, EmptySetting, ValidAccessKeys); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); } - [Fact] + [TestCase] public async Task TestProcessRequest_RouteNotFound() { var dispatcher = SetupDispatcher(); var request = TestHelpers.CreateHttpRequestMessage("hub1", TestType, TestEvent, TestKey.ConnectionId, ValidSignature); var response = await dispatcher.ExecuteAsync(request, EmptySetting, ValidAccessKeys); - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); } - [Fact] + [TestCase] public async Task TestProcessRequest_SignatureInvalid() { var dispatcher = SetupDispatcher(); var request = TestHelpers.CreateHttpRequestMessage(TestHub, TestType, TestEvent, TestKey.ConnectionId, new string[] { "abc" }); var response = await dispatcher.ExecuteAsync(request, EmptySetting, ValidAccessKeys); - Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode); } - [Fact] + [TestCase] public async Task TestProcessRequest_ConnectionIdNullBadRequest() { var dispatcher = SetupDispatcher(); var request = TestHelpers.CreateHttpRequestMessage(TestHub, TestType, TestEvent, null, ValidSignature, httpMethod: "Delete"); var response = await dispatcher.ExecuteAsync(request, EmptySetting, ValidAccessKeys); - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode); } - [Fact] + [TestCase] public async Task TestProcessRequest_DeleteMethodBadRequest() { var dispatcher = SetupDispatcher(); var request = TestHelpers.CreateHttpRequestMessage(TestHub, TestType, TestEvent, TestKey.ConnectionId, ValidSignature, httpMethod: "Delete"); var response = await dispatcher.ExecuteAsync(request, EmptySetting, ValidAccessKeys); - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode); } - [Theory] - [InlineData("OPTIONS", "abc.com")] - [InlineData("GET", "abc.com")] + [TestCase("OPTIONS", "abc.com")] + [TestCase("GET", "abc.com")] public async Task TestProcessRequest_AbuseProtectionValidOK(string method, string host) { var allowedHost = new HashSet(new string[] { host }); var dispatcher = SetupDispatcher(); var request = TestHelpers.CreateHttpRequestMessage(TestHub, TestType, TestEvent, TestKey.ConnectionId, new string[] { TestKey.Signature }, httpMethod: method, host: host); var response = await dispatcher.ExecuteAsync(request, allowedHost, ValidAccessKeys); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); } - [Theory] - [InlineData("OPTIONS", "abc.com")] - [InlineData("GET", "abc.com")] + [TestCase("OPTIONS", "abc.com")] + [TestCase("GET", "abc.com")] public async Task TestProcessRequest_AbuseProtectionInvalidBadRequest(string method, string allowedHost) { var allowedHosts = new HashSet(new string[] { allowedHost }); @@ -100,17 +98,16 @@ public async Task TestProcessRequest_AbuseProtectionInvalidBadRequest(string met var dispatcher = SetupDispatcher(); var request = TestHelpers.CreateHttpRequestMessage(TestHub, TestType, TestEvent, TestKey.ConnectionId, new string[] { TestKey.Signature }, httpMethod: method, host: testhost); var response = await dispatcher.ExecuteAsync(request, allowedHosts, ValidAccessKeys); - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode); } - [Theory] - [InlineData("application/xml", HttpStatusCode.BadRequest)] + [TestCase("application/xml", HttpStatusCode.BadRequest)] public async Task TestProcessRequest_MessageMediaTypes(string mediaType, HttpStatusCode expectedCode) { var dispatcher = SetupDispatcher(TestHub, WebPubSubEventType.User, Constants.Events.MessageEvent); var request = TestHelpers.CreateHttpRequestMessage(TestHub, WebPubSubEventType.User, Constants.Events.MessageEvent, TestKey.ConnectionId, ValidSignature, contentType: mediaType, payload: Encoding.UTF8.GetBytes("Hello")); var response = await dispatcher.ExecuteAsync(request, EmptySetting, ValidAccessKeys).ConfigureAwait(false); - Assert.Equal(expectedCode, response.StatusCode); + Assert.AreEqual(expectedCode, response.StatusCode); } private WebPubSubTriggerDispatcher SetupDispatcher(string hub = TestHub, WebPubSubEventType type = TestType, string eventName = TestEvent) diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs index 173183490e3d..d681c13b6a1e 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerValueProviderTests.cs @@ -3,16 +3,15 @@ using System; using System.Reflection; -using Xunit; +using NUnit.Framework; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests { public class WebPubSubTriggerValueProviderTests { - [Theory] - [InlineData("connectioncontext")] - [InlineData("reason")] - [InlineData("message")] + [TestCase("connectioncontext")] + [TestCase("reason")] + [TestCase("message")] public void TestGetValueByName_Valid(string name) { var triggerEvent = new WebPubSubTriggerEvent From 887dcb230fee91725242c82f27978a6c3e81aa38 Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Tue, 27 Apr 2021 18:41:07 +0800 Subject: [PATCH 11/17] resolve comments. --- eng/Packages.Data.props | 2 +- .../Bindings/Output/BinaryDataExtensions.cs | 1 - .../Output/BinaryDataJsonConverter.cs | 31 ++++- .../Bindings/Output/GrantGroupPermission.cs | 2 + .../Bindings/Output/RevokeGroupPermission.cs | 2 + .../src/Bindings/Output/SendToAll.cs | 3 +- .../src/Bindings/Output/SendToConnection.cs | 3 +- .../src/Bindings/Output/SendToGroup.cs | 3 +- .../src/Bindings/Output/SendToUser.cs | 3 +- .../src/Bindings/Output/WebPubSubOperation.cs | 9 +- .../Output/WebPubSubOperationJsonConverter.cs | 69 ----------- .../Bindings/Output/WebPubSubOperationKind.cs | 41 ------- .../src/Config/WebPubSubConfigProvider.cs | 111 ++++++++++++++++-- ....Azure.WebJobs.Extensions.WebPubSub.csproj | 2 +- .../src/Services/ServiceConfigParser.cs | 4 +- .../src/Trigger/WebPubSubTriggerBinding.cs | 20 ++++ .../src/Trigger/WebPubSubTriggerDispatcher.cs | 48 ++++---- .../tests/Common/TestHelpers.cs | 8 +- .../tests/JObjectTests.cs | 4 +- .../tests/WebPubSubTriggerDispatcherTests.cs | 8 +- 20 files changed, 204 insertions(+), 170 deletions(-) delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperationJsonConverter.cs delete mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperationKind.cs diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props index ddc517d8fc83..79229ecbbd97 100644 --- a/eng/Packages.Data.props +++ b/eng/Packages.Data.props @@ -80,7 +80,7 @@ - + diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataExtensions.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataExtensions.cs index eab02cca42cc..66a6d41722b9 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataExtensions.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataExtensions.cs @@ -3,7 +3,6 @@ using System; using System.IO; - using Newtonsoft.Json.Linq; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataJsonConverter.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataJsonConverter.cs index b0a09d06b632..06921c78b682 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataJsonConverter.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataJsonConverter.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; +using System.Globalization; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -15,13 +17,40 @@ public override BinaryData ReadJson(JsonReader reader, Type objectType, BinaryDa { return BinaryData.FromString(serializer.Deserialize(reader)); } + + var value = JToken.Load(reader); + + if (TryLoadBinary(value, out var bytes)) + { + return BinaryData.FromBytes(bytes); + } // string JObject - return BinaryData.FromString(JToken.Load(reader).ToString()); + return BinaryData.FromString(value.ToString()); } public override void WriteJson(JsonWriter writer, BinaryData value, JsonSerializer serializer) { serializer.Serialize(writer, value.ToString()); } + + private static bool TryLoadBinary(JToken input, out byte[] output) + { + var converted = new List(); + if (input["type"] != null && input["type"].ToString().Equals("Buffer", StringComparison.OrdinalIgnoreCase)) + { + var data = input["data"]; + if (data is JArray bytes) + { + foreach (var item in bytes) + { + converted.Add(byte.Parse(item.ToString(), CultureInfo.InvariantCulture)); + } + output = converted.ToArray(); + return true; + } + } + output = null; + return false; + } } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/GrantGroupPermission.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/GrantGroupPermission.cs index f68a2c505d36..44c5bb85b243 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/GrantGroupPermission.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/GrantGroupPermission.cs @@ -13,5 +13,7 @@ public class GrantGroupPermission : WebPubSubOperation public string ConnectionId { get; set; } public WebPubSubPermission Permission { get; set; } + + public string TargetName { get; set; } } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RevokeGroupPermission.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RevokeGroupPermission.cs index f979f7a27388..a50b1a0f53e7 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RevokeGroupPermission.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/RevokeGroupPermission.cs @@ -13,5 +13,7 @@ public class RevokeGroupPermission : WebPubSubOperation public string ConnectionId { get; set; } public WebPubSubPermission Permission { get; set; } + + public string TargetName { get; set; } } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToAll.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToAll.cs index 01d42587d84f..b4bfc7b900f8 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToAll.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToAll.cs @@ -10,10 +10,9 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] public class SendToAll : WebPubSubOperation { - [JsonConverter(typeof(BinaryDataJsonConverter))] public BinaryData Message { get; set; } - public MessageDataType DataType { get; set; } + public MessageDataType DataType { get; set; } = MessageDataType.Binary; public string[] Excluded { get; set; } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToConnection.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToConnection.cs index 9e24e5d7eedb..db8624af7401 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToConnection.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToConnection.cs @@ -12,9 +12,8 @@ public class SendToConnection : WebPubSubOperation { public string ConnectionId { get; set; } - [JsonConverter(typeof(BinaryDataJsonConverter))] public BinaryData Message { get; set; } - public MessageDataType DataType { get; set; } + public MessageDataType DataType { get; set; } = MessageDataType.Binary; } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToGroup.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToGroup.cs index 4fa79aa36d2c..37c0f6c4edab 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToGroup.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToGroup.cs @@ -12,10 +12,9 @@ public class SendToGroup : WebPubSubOperation { public string Group { get; set; } - [JsonConverter(typeof(BinaryDataJsonConverter))] public BinaryData Message { get; set; } - public MessageDataType DataType { get; set; } + public MessageDataType DataType { get; set; } = MessageDataType.Binary; public string[] Excluded { get; set; } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToUser.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToUser.cs index 8210fd0604ff..ca37f6bdfbf9 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToUser.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/SendToUser.cs @@ -12,9 +12,8 @@ public class SendToUser : WebPubSubOperation { public string UserId { get; set; } - [JsonConverter(typeof(BinaryDataJsonConverter))] public BinaryData Message { get; set; } - public MessageDataType DataType { get; set; } + public MessageDataType DataType { get; set; } = MessageDataType.Binary; } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperation.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperation.cs index 042a4e7138d8..70d99b17fe0b 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperation.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperation.cs @@ -3,25 +3,22 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -using System; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] public abstract class WebPubSubOperation { - private WebPubSubOperationKind _operationKind; - - public WebPubSubOperationKind OperationKind + public string OperationKind { get { - return (WebPubSubOperationKind)Enum.Parse(typeof(WebPubSubOperationKind), GetType().Name); + return GetType().Name; } set { // used in type-less for deserialize. - _operationKind = value; + _ = value; } } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperationJsonConverter.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperationJsonConverter.cs deleted file mode 100644 index abe22cd49057..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperationJsonConverter.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub -{ - internal class WebPubSubOperationJsonConverter : JsonConverter - { - public override WebPubSubOperation ReadJson(JsonReader reader, Type objectType, WebPubSubOperation existingValue, bool hasExistingValue, JsonSerializer serializer) - { - try - { - if (reader == null) - { - throw new ArgumentNullException(nameof(reader)); - } - if (objectType == null) - { - throw new ArgumentNullException(nameof(objectType)); - } - if (serializer == null) - { - throw new ArgumentNullException(nameof(serializer)); - } - - if (reader.TokenType == JsonToken.Null) - { - return null; - } - - JObject jObject = JObject.Load(reader); - var kind = jObject.Properties().SingleOrDefault(p => - p.Name.Equals("operationKind", StringComparison.OrdinalIgnoreCase)).Value.ToString().ToLowerInvariant(); - - existingValue = kind switch - { - "sendtoall" => jObject.ToObject(), - "sendtogroup" => jObject.ToObject(), - "sendtoconnection" => jObject.ToObject(), - "sendtouser" => jObject.ToObject(), - "addusertogroup" => jObject.ToObject(), - "removeuserfromgroup" => jObject.ToObject(), - "addconnectiontogroup" => jObject.ToObject(), - "removeconnectionfromgroup" => jObject.ToObject(), - "removeuserfromallgroups" => jObject.ToObject(), - "closeclientconnection" => jObject.ToObject(), - "grantgrouppermission" => jObject.ToObject(), - "revokegrouppermission" => jObject.ToObject(), - _ => jObject.ToObject() - }; - hasExistingValue = true; - return existingValue; - } - catch (Exception) - { - return null; - } - } - - public override void WriteJson(JsonWriter writer, WebPubSubOperation value, JsonSerializer serializer) - { - serializer.Serialize(writer, value.ToString()); - } - } -} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperationKind.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperationKind.cs deleted file mode 100644 index 19b4fe223446..000000000000 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/WebPubSubOperationKind.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; - -namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub -{ - /// - /// Supported operations of rest calls. - /// - [JsonConverter(typeof(StringEnumConverter))] - public enum WebPubSubOperationKind - { - [EnumMember(Value = "sendToAll")] - SendToAll, - [EnumMember(Value = "closeClientConnection")] - CloseClientConnection, - [EnumMember(Value = "sendToConnection")] - SendToConnection, - [EnumMember(Value = "sendToGroup")] - SendToGroup, - [EnumMember(Value = "addConnectionToGroup")] - AddConnectionToGroup, - [EnumMember(Value = "removeConnectionFromGroup")] - RemoveConnectionFromGroup, - [EnumMember(Value = "sendToUser")] - SendToUser, - [EnumMember(Value = "addToGroup")] - AddUserToGroup, - [EnumMember(Value = "removeUserFromGroup")] - RemoveUserFromGroup, - [EnumMember(Value = "removeUserFromAllGroups")] - RemoveUserFromAllGroups, - [EnumMember(Value = "grandGroupPermission")] - GrantGroupPermission, - [EnumMember(Value = "revokeGroupPermission")] - RevokeGroupPermission - } -} diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs index f8fc559cb433..541fe080ddc7 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs @@ -2,19 +2,19 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Linq; +using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.WebJobs.Description; -using Microsoft.Azure.WebJobs.Host.Bindings; using Microsoft.Azure.WebJobs.Host.Config; using Microsoft.Azure.WebJobs.Logging; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub @@ -64,11 +64,14 @@ public void Initialize(ExtensionConfigContext context) #pragma warning restore CS0618 // Type or member is obsolete _logger.LogInformation($"Registered Web PubSub negotiate Endpoint = {url?.GetLeftPart(UriPartial.Path)}"); + // register JsonConverters + RegisterJsonConverter(); + // bindings context .AddConverter(JObject.FromObject) - .AddConverter(ConvertWebPubSubOperationFromJObject) - .AddConverter(ConvertWebPubSubOperationFromJObject); + .AddConverter(ConvertToWebPubSubOperation) + .AddConverter(ConvertToWebPubSubOperationArray); // Trigger binding context.AddBindingRule() @@ -130,7 +133,7 @@ private void ValidateConnectionString(string attributeConnectionString, string a if (string.IsNullOrEmpty(connectionString)) { - throw new InvalidOperationException($"The Service connection string must be set either via an '{Constants.WebPubSubConnectionStringName}' app setting, via an '{Constants.WebPubSubConnectionStringName}' environment variable, or {attributeConnectionStringName}."); + throw new InvalidOperationException($"The Service connection string must be set either via an '{Constants.WebPubSubConnectionStringName}' app setting, via an '{Constants.WebPubSubConnectionStringName}' environment variable, or directly in code via {nameof(WebPubSubOptions)}.{nameof(WebPubSubOptions.ConnectionString)} or {attributeConnectionStringName}."); } } @@ -144,9 +147,103 @@ private void AddSettings(string connectionString) } } - private static T ConvertWebPubSubOperationFromJObject(JObject input) + internal static void RegisterJsonConverter() + { + JsonConvert.DefaultSettings = () => new JsonSerializerSettings + { + Converters = new List + { + new StringEnumConverter(), + new BinaryDataJsonConverter() + } + }; + } + + internal static WebPubSubOperation ConvertToWebPubSubOperation(JObject input) + { + if (input.TryGetValue("operationKind", StringComparison.OrdinalIgnoreCase, out var kind)) + { + if (kind.ToString().Equals(nameof(SendToAll), StringComparison.OrdinalIgnoreCase)) + { + CheckDataType(input); + return input.ToObject(); + } + else if (kind.ToString().Equals(nameof(SendToConnection), StringComparison.OrdinalIgnoreCase)) + { + CheckDataType(input); + return input.ToObject(); + } + else if (kind.ToString().Equals(nameof(SendToUser), StringComparison.OrdinalIgnoreCase)) + { + CheckDataType(input); + return input.ToObject(); + } + else if (kind.ToString().Equals(nameof(SendToGroup), StringComparison.OrdinalIgnoreCase)) + { + CheckDataType(input); + return input.ToObject(); + } + else if (kind.ToString().Equals(nameof(AddUserToGroup), StringComparison.OrdinalIgnoreCase)) + { + return input.ToObject(); + } + else if (kind.ToString().Equals(nameof(RemoveUserFromGroup), StringComparison.OrdinalIgnoreCase)) + { + return input.ToObject(); + } + else if (kind.ToString().Equals(nameof(RemoveUserFromAllGroups), StringComparison.OrdinalIgnoreCase)) + { + return input.ToObject(); + } + else if (kind.ToString().Equals(nameof(AddConnectionToGroup), StringComparison.OrdinalIgnoreCase)) + { + return input.ToObject(); + } + else if (kind.ToString().Equals(nameof(RemoveConnectionFromGroup), StringComparison.OrdinalIgnoreCase)) + { + return input.ToObject(); + } + else if (kind.ToString().Equals(nameof(CloseClientConnection), StringComparison.OrdinalIgnoreCase)) + { + return input.ToObject(); + } + else if (kind.ToString().Equals(nameof(GrantGroupPermission), StringComparison.OrdinalIgnoreCase)) + { + return input.ToObject(); + } + else if (kind.ToString().Equals(nameof(RevokeGroupPermission), StringComparison.OrdinalIgnoreCase)) + { + return input.ToObject(); + } + } + return input.ToObject(); + } + + internal static WebPubSubOperation[] ConvertToWebPubSubOperationArray(JArray input) { - return JsonConvert.DeserializeObject(input.ToString(), new WebPubSubOperationJsonConverter()); + var result = new List(); + foreach (var item in input) + { + result.Add(ConvertToWebPubSubOperation((JObject)item)); + } + return result.ToArray(); + } + + // Binary data accepts ArrayBuffer only. + private static void CheckDataType(JObject input) + { + if (input.TryGetValue("dataType", StringComparison.OrdinalIgnoreCase, out var value)) + { + var dataType = value.ToObject(); + + input.TryGetValue("message", StringComparison.OrdinalIgnoreCase, out var message); + + if (dataType == MessageDataType.Binary && + !(message["type"] != null && message["type"].ToString().Equals("Buffer", StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException("MessageDataType is binary, please use ArrayBuffer as message type."); + } + } } } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj index 5cda94beb19a..e3f2ac2aab2b 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj @@ -3,7 +3,7 @@ $(RequiredTargetFrameworks) Microsoft.Azure.WebJobs.Extensions.WebPubSub - 1.0.0-beta.1 + 1.0.0-beta.2 $(NoWarn);AZC0001;CS1591;SA1636;CA1056 true diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs index abfbd00ca4a9..981c3de311ac 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Services/ServiceConfigParser.cs @@ -10,8 +10,8 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { internal class ServiceConfigParser { - private static char[] _valueSeparator = new char[] { '=' }; - private static char _partSeparator = ';'; + private static readonly char[] _valueSeparator = new char[] { '=' }; + private const char _partSeparator = ';'; public Uri Endpoint { get; } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs index 1a7d21eae88a..a89a1d6ea4a3 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerBinding.cs @@ -105,10 +105,30 @@ private static IReadOnlyDictionary CreateBindingContract(Parameter }; contract.Add(parameterInfo.Name, parameterInfo.ParameterType); + SafeAddContract(() => contract.Add("ConnectionContext", parameterInfo.ParameterType)); + SafeAddContract(() => contract.Add("Message", typeof(BinaryData))); + SafeAddContract(() => contract.Add("DataType", typeof(MessageDataType))); + SafeAddContract(() => contract.Add("Claims", typeof(IDictionary))); + SafeAddContract(() => contract.Add("Query", typeof(IDictionary))); + SafeAddContract(() => contract.Add("Reason", typeof(string))); + SafeAddContract(() => contract.Add("Subprotocols", typeof(string[]))); + SafeAddContract(() => contract.Add("ClientCertificates", typeof(ClientCertificateInfo[]))); return contract; } + private static void SafeAddContract(Action addValue) + { + try + { + addValue(); + } + catch + { + // ignore dup + } + } + /// /// A provider that responsible for providing value in various type to be bond to function method parameter. /// diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs index 01bd6fd55d21..dc7fb474f929 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Trigger/WebPubSubTriggerDispatcher.cs @@ -21,7 +21,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub { internal class WebPubSubTriggerDispatcher : IWebPubSubTriggerDispatcher { - private Dictionary _listeners = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly Dictionary _listeners = new(StringComparer.InvariantCultureIgnoreCase); private readonly ILogger _logger; public WebPubSubTriggerDispatcher(ILogger logger) @@ -176,17 +176,17 @@ private static bool TryParseRequest(HttpRequestMessage request, out ConnectionCo context = new ConnectionContext(); try { - context.ConnectionId = request.Headers.GetValues(Constants.Headers.CloudEvents.ConnectionId).FirstOrDefault(); - context.Hub = request.Headers.GetValues(Constants.Headers.CloudEvents.Hub).FirstOrDefault(); - context.EventType = Utilities.GetEventType(request.Headers.GetValues(Constants.Headers.CloudEvents.Type).FirstOrDefault()); - context.EventName = request.Headers.GetValues(Constants.Headers.CloudEvents.EventName).FirstOrDefault(); - context.Signature = request.Headers.GetValues(Constants.Headers.CloudEvents.Signature).FirstOrDefault(); + context.ConnectionId = request.Headers.GetValues(Constants.Headers.CloudEvents.ConnectionId).SingleOrDefault(); + context.Hub = request.Headers.GetValues(Constants.Headers.CloudEvents.Hub).SingleOrDefault(); + context.EventType = Utilities.GetEventType(request.Headers.GetValues(Constants.Headers.CloudEvents.Type).SingleOrDefault()); + context.EventName = request.Headers.GetValues(Constants.Headers.CloudEvents.EventName).SingleOrDefault(); + context.Signature = request.Headers.GetValues(Constants.Headers.CloudEvents.Signature).SingleOrDefault(); context.Headers = request.Headers.ToDictionary(x => x.Key, v => new StringValues(v.Value.ToArray()), StringComparer.OrdinalIgnoreCase); // UserId is optional, e.g. connect if (request.Headers.TryGetValues(Constants.Headers.CloudEvents.UserId, out var values)) { - context.UserId = values.FirstOrDefault(); + context.UserId = values.SingleOrDefault(); } } catch (Exception) @@ -206,18 +206,16 @@ private static bool ValidateSignature(string connectionId, string signature, Has { continue; } - using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(accessKey))) + using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(accessKey)); + var hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(connectionId)); + var hash = "sha256=" + BitConverter.ToString(hashBytes).Replace("-", ""); + if (signatures.Contains(hash, StringComparer.OrdinalIgnoreCase)) { - var hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(connectionId)); - var hash = "sha256=" + BitConverter.ToString(hashBytes).Replace("-", ""); - if (signatures.Contains(hash, StringComparer.OrdinalIgnoreCase)) - { - return true; - } - else - { - continue; - } + return true; + } + else + { + continue; } } return false; @@ -277,7 +275,7 @@ private static bool TryConvertResponse(JObject item, out T response) { // ignore invalid response } - response = default(T); + response = default; return false; } @@ -301,9 +299,9 @@ internal static HttpResponseMessage BuildValidResponse(object response, RequestT { return Utilities.BuildErrorResponse(error); } - else if (response is ErrorResponse) + else if (response is ErrorResponse errorResponse) { - return Utilities.BuildErrorResponse((ErrorResponse)response); + return Utilities.BuildErrorResponse(errorResponse); } if (requestType == RequestType.Connect) @@ -312,9 +310,9 @@ internal static HttpResponseMessage BuildValidResponse(object response, RequestT { return Utilities.BuildResponse(converted.ToString()); } - else if (response is ConnectResponse) + else if (response is ConnectResponse connectResponse) { - return Utilities.BuildResponse((ConnectResponse)response); + return Utilities.BuildResponse(connectResponse); } } @@ -324,9 +322,9 @@ internal static HttpResponseMessage BuildValidResponse(object response, RequestT { return Utilities.BuildResponse(msgResponse); } - else if (response is MessageResponse) + else if (response is MessageResponse messageResponse) { - return Utilities.BuildResponse((MessageResponse)response); + return Utilities.BuildResponse(messageResponse); } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs index 4c6fe43a26b1..d60df22e72de 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestHelpers.cs @@ -52,7 +52,7 @@ public static IHost NewHost(Type type, WebPubSubConfigProvider ext = null, Dicti private sealed class FakeTypeLocator : ITypeLocator { - private Type _type; + private readonly Type _type; public FakeTypeLocator(Type type) { @@ -89,8 +89,10 @@ public static HttpRequestMessage CreateHttpRequestMessage( string userId = "testuser", byte[] payload = null) { - var context = new HttpRequestMessage(); - context.Method = new HttpMethod(httpMethod); + var context = new HttpRequestMessage() + { + Method = new HttpMethod(httpMethod) + }; context.Headers.Add(Constants.Headers.CloudEvents.Hub, hub); context.Headers.Add(Constants.Headers.CloudEvents.Type, GetFormedType(type, eventName)); context.Headers.Add(Constants.Headers.CloudEvents.EventName, eventName); diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs index c30d94c55ea1..625d1e18f57f 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs @@ -26,13 +26,15 @@ public class JObjectTests [TestCase(nameof(RevokeGroupPermission))] public void TestOutputConvert(string operationKind) { + WebPubSubConfigProvider.RegisterJsonConverter(); + var input = @"{ ""operationKind"":""{0}"",""userId"":""user"", ""group"":""group1"",""connectionId"":""connection"",""message"":""test"",""dataType"":""text"", ""reason"":""close""}"; var replacedInput = input.Replace("{0}", operationKind); var jObject = JObject.Parse(replacedInput); - var converted = JsonConvert.DeserializeObject(replacedInput, new WebPubSubOperationJsonConverter()); + var converted = WebPubSubConfigProvider.ConvertToWebPubSubOperation(jObject); Assert.AreEqual(operationKind, converted.OperationKind.ToString()); } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs index f80f15be1ea4..f2d397318ed3 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/WebPubSubTriggerDispatcherTests.cs @@ -20,9 +20,9 @@ public class WebPubSubTriggerDispatcherTests private const WebPubSubEventType TestType = WebPubSubEventType.System; private const string TestEvent = Constants.Events.ConnectedEvent; - private static HashSet EmptySetting = new HashSet(); - private static HashSet ValidAccessKeys = new HashSet(new string[] { TestKey.AccessKey }); - private static string[] ValidSignature = new string[] { TestKey.Signature }; + private static readonly HashSet EmptySetting = new(); + private static readonly HashSet ValidAccessKeys = new(new string[] { TestKey.AccessKey }); + private static readonly string[] ValidSignature = new string[] { TestKey.Signature }; [TestCase] public async Task TestProcessRequest_ValidRequest() @@ -110,7 +110,7 @@ public async Task TestProcessRequest_MessageMediaTypes(string mediaType, HttpSta Assert.AreEqual(expectedCode, response.StatusCode); } - private WebPubSubTriggerDispatcher SetupDispatcher(string hub = TestHub, WebPubSubEventType type = TestType, string eventName = TestEvent) + private static WebPubSubTriggerDispatcher SetupDispatcher(string hub = TestHub, WebPubSubEventType type = TestType, string eventName = TestEvent) { var funcName = $"{hub}.{type}.{eventName}".ToLower(); var dispatcher = new WebPubSubTriggerDispatcher(NullLogger.Instance); From 8e121804dff65b16476391a09333d33a42b6a582 Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Wed, 28 Apr 2021 10:11:20 +0800 Subject: [PATCH 12/17] add generated api. --- ...obs.Extensions.WebPubSub.netstandard2.0.cs | 234 ++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/api/Microsoft.Azure.WebJobs.Extensions.WebPubSub.netstandard2.0.cs diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/api/Microsoft.Azure.WebJobs.Extensions.WebPubSub.netstandard2.0.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/api/Microsoft.Azure.WebJobs.Extensions.WebPubSub.netstandard2.0.cs new file mode 100644 index 000000000000..6d1b87d8a4e9 --- /dev/null +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/api/Microsoft.Azure.WebJobs.Extensions.WebPubSub.netstandard2.0.cs @@ -0,0 +1,234 @@ +namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub +{ + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class AddConnectionToGroup : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation + { + public AddConnectionToGroup() { } + public string ConnectionId { get { throw null; } set { } } + public string Group { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class AddUserToGroup : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation + { + public AddUserToGroup() { } + public string Group { get { throw null; } set { } } + public string UserId { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class CloseClientConnection : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation + { + public CloseClientConnection() { } + public string ConnectionId { get { throw null; } set { } } + public string Reason { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class ConnectionContext + { + public ConnectionContext() { } + public string ConnectionId { get { throw null; } } + public string EventName { get { throw null; } } + public Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubEventType EventType { get { throw null; } } + public System.Collections.Generic.Dictionary Headers { get { throw null; } } + public string Hub { get { throw null; } } + public string Signature { get { throw null; } } + public string UserId { get { throw null; } } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class ConnectResponse : Microsoft.Azure.WebJobs.Extensions.WebPubSub.ServiceResponse + { + public ConnectResponse() { } + [Newtonsoft.Json.JsonPropertyAttribute(Required=Newtonsoft.Json.Required.Default)] + public string[] Groups { get { throw null; } set { } } + [Newtonsoft.Json.JsonPropertyAttribute(Required=Newtonsoft.Json.Required.Default)] + public string[] Roles { get { throw null; } set { } } + [Newtonsoft.Json.JsonPropertyAttribute(Required=Newtonsoft.Json.Required.Default)] + public string Subprotocol { get { throw null; } set { } } + [Newtonsoft.Json.JsonPropertyAttribute(Required=Newtonsoft.Json.Required.Default)] + public string UserId { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class ErrorResponse : Microsoft.Azure.WebJobs.Extensions.WebPubSub.ServiceResponse + { + [Newtonsoft.Json.JsonConstructorAttribute] + public ErrorResponse() { } + public ErrorResponse(Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubErrorCode code, string message = null) { } + [Newtonsoft.Json.JsonPropertyAttribute(Required=Newtonsoft.Json.Required.Always)] + public Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubErrorCode Code { get { throw null; } set { } } + [Newtonsoft.Json.JsonPropertyAttribute(Required=Newtonsoft.Json.Required.Default)] + public string ErrorMessage { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class GrantGroupPermission : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation + { + public GrantGroupPermission() { } + public string ConnectionId { get { throw null; } set { } } + public Azure.Messaging.WebPubSub.WebPubSubPermission Permission { get { throw null; } set { } } + public string TargetName { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonConverterAttribute(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public enum MessageDataType + { + [System.Runtime.Serialization.EnumMemberAttribute(Value="binary")] + Binary = 0, + [System.Runtime.Serialization.EnumMemberAttribute(Value="json")] + Json = 1, + [System.Runtime.Serialization.EnumMemberAttribute(Value="text")] + Text = 2, + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class MessageResponse : Microsoft.Azure.WebJobs.Extensions.WebPubSub.ServiceResponse + { + public MessageResponse() { } + public Microsoft.Azure.WebJobs.Extensions.WebPubSub.MessageDataType DataType { get { throw null; } set { } } + [Newtonsoft.Json.JsonPropertyAttribute(Required=Newtonsoft.Json.Required.Always)] + public System.BinaryData Message { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class RemoveConnectionFromGroup : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation + { + public RemoveConnectionFromGroup() { } + public string ConnectionId { get { throw null; } set { } } + public string Group { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class RemoveUserFromAllGroups : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation + { + public RemoveUserFromAllGroups() { } + public string UserId { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class RemoveUserFromGroup : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation + { + public RemoveUserFromGroup() { } + public string Group { get { throw null; } set { } } + public string UserId { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class RevokeGroupPermission : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation + { + public RevokeGroupPermission() { } + public string ConnectionId { get { throw null; } set { } } + public Azure.Messaging.WebPubSub.WebPubSubPermission Permission { get { throw null; } set { } } + public string TargetName { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class SendToAll : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation + { + public SendToAll() { } + public Microsoft.Azure.WebJobs.Extensions.WebPubSub.MessageDataType DataType { get { throw null; } set { } } + public string[] Excluded { get { throw null; } set { } } + public System.BinaryData Message { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class SendToConnection : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation + { + public SendToConnection() { } + public string ConnectionId { get { throw null; } set { } } + public Microsoft.Azure.WebJobs.Extensions.WebPubSub.MessageDataType DataType { get { throw null; } set { } } + public System.BinaryData Message { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class SendToGroup : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation + { + public SendToGroup() { } + public Microsoft.Azure.WebJobs.Extensions.WebPubSub.MessageDataType DataType { get { throw null; } set { } } + public string[] Excluded { get { throw null; } set { } } + public string Group { get { throw null; } set { } } + public System.BinaryData Message { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class SendToUser : Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubOperation + { + public SendToUser() { } + public Microsoft.Azure.WebJobs.Extensions.WebPubSub.MessageDataType DataType { get { throw null; } set { } } + public System.BinaryData Message { get { throw null; } set { } } + public string UserId { get { throw null; } set { } } + } + public abstract partial class ServiceResponse + { + protected ServiceResponse() { } + } + [Microsoft.Azure.WebJobs.Description.BindingAttribute] + [System.AttributeUsageAttribute(System.AttributeTargets.Parameter | System.AttributeTargets.ReturnValue)] + public partial class WebPubSubAttribute : System.Attribute + { + public WebPubSubAttribute() { } + [Microsoft.Azure.WebJobs.Description.ConnectionStringAttribute] + public string ConnectionStringSetting { get { throw null; } set { } } + [Microsoft.Azure.WebJobs.Description.AutoResolveAttribute] + public string Hub { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public partial class WebPubSubConnection + { + public WebPubSubConnection(System.Uri url) { } + public string AccessToken { get { throw null; } } + public string BaseUrl { get { throw null; } } + public string Url { get { throw null; } } + } + [Microsoft.Azure.WebJobs.Description.BindingAttribute] + [System.AttributeUsageAttribute(System.AttributeTargets.Parameter | System.AttributeTargets.ReturnValue)] + public partial class WebPubSubConnectionAttribute : System.Attribute + { + public WebPubSubConnectionAttribute() { } + [Microsoft.Azure.WebJobs.Description.ConnectionStringAttribute] + public string ConnectionStringSetting { get { throw null; } set { } } + [Microsoft.Azure.WebJobs.Description.AutoResolveAttribute] + public string Hub { get { throw null; } set { } } + [Microsoft.Azure.WebJobs.Description.AutoResolveAttribute] + public string UserId { get { throw null; } set { } } + } + [Newtonsoft.Json.JsonConverterAttribute(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public enum WebPubSubErrorCode + { + [System.Runtime.Serialization.EnumMemberAttribute(Value="unauthorized")] + Unauthorized = 0, + [System.Runtime.Serialization.EnumMemberAttribute(Value="userError")] + UserError = 1, + [System.Runtime.Serialization.EnumMemberAttribute(Value="serverError")] + ServerError = 2, + } + [System.Text.Json.Serialization.JsonConverterAttribute(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public enum WebPubSubEventType + { + [System.Runtime.Serialization.EnumMemberAttribute(Value="system")] + System = 0, + [System.Runtime.Serialization.EnumMemberAttribute(Value="user")] + User = 1, + } + public static partial class WebPubSubJobsBuilderExtensions + { + public static Microsoft.Azure.WebJobs.IWebJobsBuilder AddWebPubSub(this Microsoft.Azure.WebJobs.IWebJobsBuilder builder) { throw null; } + } + [Newtonsoft.Json.JsonObjectAttribute(NamingStrategyType=typeof(Newtonsoft.Json.Serialization.CamelCaseNamingStrategy))] + public abstract partial class WebPubSubOperation + { + protected WebPubSubOperation() { } + public string OperationKind { get { throw null; } set { } } + } + public partial class WebPubSubOptions : Microsoft.Azure.WebJobs.Hosting.IOptionsFormatter + { + public WebPubSubOptions() { } + public string Hub { get { throw null; } set { } } + public string Format() { throw null; } + } + [Microsoft.Azure.WebJobs.Description.BindingAttribute(TriggerHandlesReturnValue=true)] + [System.AttributeUsageAttribute(System.AttributeTargets.Parameter)] + public partial class WebPubSubTriggerAttribute : System.Attribute + { + public WebPubSubTriggerAttribute(Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubEventType eventType, string eventName) { } + public WebPubSubTriggerAttribute(string hub, Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubEventType eventType, string eventName) { } + [Microsoft.Azure.WebJobs.Description.AutoResolveAttribute] + [System.ComponentModel.DataAnnotations.RequiredAttribute] + public string EventName { get { throw null; } } + [Microsoft.Azure.WebJobs.Description.AutoResolveAttribute] + public Microsoft.Azure.WebJobs.Extensions.WebPubSub.WebPubSubEventType EventType { get { throw null; } } + [Microsoft.Azure.WebJobs.Description.AutoResolveAttribute] + public string Hub { get { throw null; } } + } + public partial class WebPubSubWebJobsStartup : Microsoft.Azure.WebJobs.Hosting.IWebJobsStartup + { + public WebPubSubWebJobsStartup() { } + public void Configure(Microsoft.Azure.WebJobs.IWebJobsBuilder builder) { } + } +} From bf4cd720e8961030bb170d20a05f990aab302dfa Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Wed, 28 Apr 2021 10:27:10 +0800 Subject: [PATCH 13/17] Add package description and update changelog --- .../CHANGELOG.md | 2 ++ .../src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj | 1 + ...Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj | 5 +---- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/CHANGELOG.md b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/CHANGELOG.md index ba0d8a765484..9a5a07edb942 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/CHANGELOG.md +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/CHANGELOG.md @@ -1,5 +1,7 @@ # Release History +## 1.0.0-beta.2 (Unreleased) + ## 1.0.0-beta.1 (2021-04-26) - The initial beta release of Microsoft.Azure.WebJobs.Extensions.WebPubSub 1.0.0 \ No newline at end of file diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj index e3f2ac2aab2b..41afb714b008 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj @@ -3,6 +3,7 @@ $(RequiredTargetFrameworks) Microsoft.Azure.WebJobs.Extensions.WebPubSub + This Azure Functions extension for Web PubSub 1.0.0-beta.2 $(NoWarn);AZC0001;CS1591;SA1636;CA1056 true diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj index 4f16efe43a26..502eea7d5a0c 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests.csproj @@ -10,10 +10,7 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + From 60d35e707a4059aed17fa4703c2d59da7289b0c2 Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Wed, 28 Apr 2021 14:53:58 +0800 Subject: [PATCH 14/17] update readme. --- .../README.md | 29 +++++++++++++++---- .../Output/BinaryDataJsonConverter.cs | 22 +++++++------- .../tests/JObjectTests.cs | 11 +++++++ 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md index 67cdd8672363..191b5b2e37e2 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md @@ -9,26 +9,34 @@ This extension provides functionality for receiving Web PubSub webhook calls in Install the Web PubSub extension with [NuGet][nuget]: ```Powershell -dotnet add package Microsoft.Azure.WebJobs.Extensions.WebPubSub +dotnet add package Microsoft.Azure.WebJobs.Extensions.WebPubSub --prerelease ``` ### Prerequisites You must have an [Azure subscription](https://azure.microsoft.com/free/) and an Azure resource group with a Web PubSub resource. Follow this [step-by-step tutorial](https://review.docs.microsoft.com/azure/azure-web-pubsub/howto-develop-create-instance?branch=release-azure-web-pubsub) to create an Azure Web PubSub instance. +### Authenticate the client + +In order to let the extension publish events, you will need to provide valid `access_token` for the request client. + +You can work with [Web PubSub Input binding](#using-web-pubsub-input-binding) to help client generate a valid access token. + ## Key concepts ### Using Web PubSub input binding -Please follow the [input binding tutorial](https://azure.github.io/azure-webpubsub/references/functions-bindings#input-binding) to learn about using this extension for building `WebPubSubConnection` to create Websockets connection to service with input binding. +Please follow the [input binding tutorial](#functions-that-uses-web-pubsub-input-binding) to learn about using this extension for building `WebPubSubConnection` to create Websockets connection to service with input binding. ### Using Web PubSub output binding -Please follow the [output binding tutorial](https://azure.github.io/azure-webpubsub/references/functions-bindings#output-binding) to learn about using this extension for publishing Web PubSub messages. +Please follow the [output binding tutorial](#functions-that-uses-web-pubsub-output-binding) to learn about using this extension for publishing Web PubSub messages. ### Using Web PubSub trigger -Please follow the [trigger binding tutorial](https://azure.github.io/azure-webpubsub/references/functions-bindings#trigger-binding) to learn about triggering an Azure Function when an event is sent from service upstream. +Please follow the [trigger binding tutorial](#functions-that-uses-web-pubsub-trigger) to learn about triggering an Azure Function when an event is sent from service upstream. + +In `Connect` and `Message` events, function will respect return values to send back service. Then service will depend on the response to proceed the request or else. The responses and events are paired. For example, `Connect` will only respect `ConnectResponse` or `ErrorResponse`, and ignore other returns. When `ErrorResponse` is returned, service will drop client connection. Please follow the [trigger binding return value tutorial](#functions-that-uses-web-pubsub-trigger-return-value) to learn about using the trigger return value. ## Examples @@ -65,7 +73,7 @@ public static async Task RunAsync( ```C# Snippet:WebPubSubTriggerFunction [FunctionName("WebPubSubTriggerFunction")] -public static async Task RunAsync( +public static void Run( [WebPubSubTrigger("message", WebPubSubEventType.User)] ConnectionContext context, string message, @@ -74,9 +82,20 @@ public static async Task RunAsync( Console.WriteLine($"Request from: {context.userId}"); Console.WriteLine($"Request message: {message}"); Console.WriteLine($"Request message DataType: {dataType}"); +} +``` + +### Functions that uses Web PubSub trigger return value + +```C# Snippet:WebPubSubTriggerReturnValueFunction +[FunctionName("WebPubSubTriggerReturnValueFunction")] +public static MessageResponse RunAsync( + [WebPubSubTrigger("message", WebPubSubEventType.User)] ConnectionContext context) +{ return new MessageResponse { Message = BinaryData.FromString("ack"), + DataType = MessageDataType.Text }; } ``` diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataJsonConverter.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataJsonConverter.cs index 06921c78b682..e482070658c0 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataJsonConverter.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Bindings/Output/BinaryDataJsonConverter.cs @@ -35,22 +35,20 @@ public override void WriteJson(JsonWriter writer, BinaryData value, JsonSerializ private static bool TryLoadBinary(JToken input, out byte[] output) { - var converted = new List(); - if (input["type"] != null && input["type"].ToString().Equals("Buffer", StringComparison.OrdinalIgnoreCase)) + if (input["type"] != null) { - var data = input["data"]; - if (data is JArray bytes) - { - foreach (var item in bytes) - { - converted.Add(byte.Parse(item.ToString(), CultureInfo.InvariantCulture)); - } - output = converted.ToArray(); - return true; - } + var target = input.ToObject(); + output = target.Data; + return true; } output = null; return false; } + + private sealed class ArrayBuffer + { + public string Type { get; set; } + public byte[] Data { get; set; } + } } } diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs index 625d1e18f57f..be4f632c3f5d 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JObjectTests.cs @@ -1,6 +1,7 @@ // 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; using System.Net.Http; using System.Threading.Tasks; @@ -39,6 +40,16 @@ public void TestOutputConvert(string operationKind) Assert.AreEqual(operationKind, converted.OperationKind.ToString()); } + [TestCase] + public void TestBinaryDataConvertFromByteArray() + { + var testData = @"{""type"":""Buffer"", ""data"": [66, 105, 110, 97, 114, 121, 68, 97, 116, 97]}"; + + var converted = JsonConvert.DeserializeObject(testData, new BinaryDataJsonConverter()); + + Assert.AreEqual("BinaryData", converted.ToString()); + } + [TestCase] public async Task ParseErrorResponse() { From 2d25612735fc1add1c8685458df45f3a3542fae7 Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Wed, 28 Apr 2021 15:10:13 +0800 Subject: [PATCH 15/17] update authenticate part --- .../README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md index 191b5b2e37e2..9c018e213d1b 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md @@ -18,9 +18,23 @@ You must have an [Azure subscription](https://azure.microsoft.com/free/) and an ### Authenticate the client -In order to let the extension publish events, you will need to provide valid `access_token` for the request client. +In order to let the extension work with Azure Web PubSub service, you will need to provide a valid `ConnectionString`. -You can work with [Web PubSub Input binding](#using-web-pubsub-input-binding) to help client generate a valid access token. +You can find the **Keys** for you Azure Web PubSub service in the [Azure Portal](https://portal.azure.com/). + +The `AzureWebJobsStorage` connection string is used to preserve the processing checkpoint information as required refer to [Storage considerations](https://docs.microsoft.com/azure/azure-functions/storage-considerations#storage-account-requirements) + +For the local development use the `local.settings.json` file to store the connection string: + +```json +{ + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "": "Endpoint=https://.webpubsub.azure.com;AccessKey=;Version=1.0;" + } +} +``` +When deployed use the [application settings](https://docs.microsoft.com/azure/azure-functions/functions-how-to-use-azure-function-app-settings) to set the connection string. ## Key concepts From 90e3b62a2e4ed870382cdc911fddc6029d09fffb Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Wed, 28 Apr 2021 15:13:05 +0800 Subject: [PATCH 16/17] minor improve description. --- .../Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md index 9c018e213d1b..62add20f2178 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md @@ -24,7 +24,7 @@ You can find the **Keys** for you Azure Web PubSub service in the [Azure Portal] The `AzureWebJobsStorage` connection string is used to preserve the processing checkpoint information as required refer to [Storage considerations](https://docs.microsoft.com/azure/azure-functions/storage-considerations#storage-account-requirements) -For the local development use the `local.settings.json` file to store the connection string: +For the local development use the `local.settings.json` file to store the connection string, `` can be set to `WebPubSubConnectionString` as default supported in the extension, or you can set customized names by mapping it with `ConnectionStringSetting = ` in function binding attributes: ```json { From f6cdb9f27cbc1699f4d2658ab59ada34b6861b4b Mon Sep 17 00:00:00 2001 From: Jialin Xin Date: Wed, 28 Apr 2021 15:41:09 +0800 Subject: [PATCH 17/17] remove snippet. --- .../README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md index 62add20f2178..8548d66881c6 100644 --- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md +++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/README.md @@ -56,7 +56,7 @@ In `Connect` and `Message` events, function will respect return values to send b ### Functions that uses Web PubSub input binding -```C# Snippet:WebPubSubInputBindingFunction +```cs [FunctionName("WebPubSubInputBindingFunction")] public static WebPubSubConnection Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req, @@ -69,7 +69,7 @@ public static WebPubSubConnection Run( ### Functions that uses Web PubSub output binding -```c# Snippet:WebPubSubOutputBindingFunction +```cs [FunctionName("WebPubSubOutputBindingFunction")] public static async Task RunAsync( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req, @@ -85,7 +85,7 @@ public static async Task RunAsync( ### Functions that uses Web PubSub trigger -```C# Snippet:WebPubSubTriggerFunction +```cs [FunctionName("WebPubSubTriggerFunction")] public static void Run( [WebPubSubTrigger("message", WebPubSubEventType.User)] @@ -101,7 +101,7 @@ public static void Run( ### Functions that uses Web PubSub trigger return value -```C# Snippet:WebPubSubTriggerReturnValueFunction +```cs [FunctionName("WebPubSubTriggerReturnValueFunction")] public static MessageResponse RunAsync( [WebPubSubTrigger("message", WebPubSubEventType.User)] ConnectionContext context)