Skip to content

Commit 46e2c6f

Browse files
authored
[WebPubSub] Make hub methods virtual. (Azure#25161)
# All SDK Contribution checklist: This checklist is used to make sure that common guidelines for a pull request are followed. - [ ] **Please open PR in `Draft` mode if it is:** - Work in progress or not intended to be merged. - Encountering multiple pipeline failures and working on fixes. - [ ] If an SDK is being regenerated based on a new swagger spec, a link to the pull request containing these swagger spec changes has been included above. - [ ] **I have read the [contribution guidelines](https://github.com/Azure/azure-sdk-for-net/blob/main/CONTRIBUTING.md).** - [ ] **The pull request does not introduce [breaking changes](https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/breaking-change-rules.md).** ### [General Guidelines and Best Practices](https://github.com/Azure/azure-sdk-for-net/blob/main/CONTRIBUTING.md#general-guidelines) - [ ] Title of the pull request is clear and informative. - [ ] There are a small number of commits, each of which have an informative message. This means that previously merged commits do not appear in the history of the PR. For more information on cleaning up the commits in your PR, [see this page](https://github.com/Azure/azure-powershell/blob/master/documentation/development-docs/cleaning-up-commits.md). ### [Testing Guidelines](https://github.com/Azure/azure-sdk-for-net/blob/main/CONTRIBUTING.md#testing-guidelines) - [ ] Pull request includes test coverage for the included changes. ### [SDK Generation Guidelines](https://github.com/Azure/azure-sdk-for-net/blob/main/CONTRIBUTING.md#sdk-generation-guidelines) - [ ] The generate.cmd file for the SDK has been updated with the version of AutoRest, as well as the commitid of your swagger spec or link to the swagger spec, used to generate the code. (Track 2 only) - [ ] The `*.csproj` and `AssemblyInfo.cs` files have been updated with the new version of the SDK. Please double check nuget.org current release version. ## Additional management plane SDK specific contribution checklist: Note: Only applies to `Microsoft.Azure.Management.[RP]` or `Azure.ResourceManager.[RP]` - [ ] Include updated [management metadata](https://github.com/Azure/azure-sdk-for-net/tree/main/eng/mgmt/mgmtmetadata). - [ ] Update AzureRP.props to add/remove version info to maintain up to date API versions. ### Management plane SDK Troubleshooting - If this is very first SDK for a services and you are adding new service folders directly under /SDK, please add `new service` label and/or contact assigned reviewer. - If the check fails at the `Verify Code Generation` step, please ensure: - Do not modify any code in generated folders. - Do not selectively include/remove generated files in the PR. - Do use `generate.ps1/cmd` to generate this PR instead of calling `autorest` directly. Please pay attention to the @microsoft.csharp version output after running `generate.ps1`. If it is lower than current released version (2.3.82), please run it again as it should pull down the latest version. **Note: We have recently updated the PSH module called by `generate.ps1` to emit additional data. This would help reduce/eliminate the Code Verification check error. Please run following command**: `dotnet msbuild eng/mgmt.proj /t:Util /p:UtilityName=InstallPsModules` ### Old outstanding PR cleanup Please note: If PRs (including draft) has been out for more than 60 days and there are no responses from our query or followups, they will be closed to maintain a concise list for our reviewers.
1 parent c429da7 commit 46e2c6f

15 files changed

+285
-179
lines changed

sdk/webpubsub/Microsoft.Azure.WebPubSub.AspNetCore/README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ var serviceClient = new WebPubSubServiceClient(new Uri(endpoint), "some_hub", ne
4545

4646
For information about general Web PubSub concepts [Concepts in Azure Web PubSub](https://docs.microsoft.com/azure/azure-web-pubsub/key-concepts)
4747

48+
### `WebPubSubHub`
49+
50+
`WebPubSubHub` is an abstract class to let users implement the subscribed Web PubSub service events. After user register the [event handler](https://docs.microsoft.com/azure/azure-web-pubsub/howto-develop-eventhandler) in service side, these events will be forwarded from service to server. And `WebPubSubHub` provides 4 methods mapping to the service events to enable users deal with these events, for example, client management, validations or working with `Azure.Messaging.WebPubSub` to broadcast the messages. See samples below for details.
51+
52+
> NOTE
53+
>
54+
> Among the 4 methods, `OnConnectAsync()` and `OnMessageReceivedAsync()` are blocking events that service will respect server returns. Besides the mapped correct response, server can throw exceptions whenever the request is against the server side logic. And `UnauthorizedAccessException` will be converted to `401Unauthorized` and rest will be converted to `500InternalServerError` along with exception message to return service. Then service will drop current client connection.
55+
4856
## Examples
4957

5058
### Add Web PubSub service with options
@@ -76,13 +84,13 @@ public void Configure(IApplicationBuilder app)
7684
### Handle Upstream event
7785

7886
```C#
79-
public override ValueTask<WebPubSubEventResponse> OnConnectAsync(ConnectEventRequest request, CancellationToken cancellationToken)
87+
public override ValueTask<ConnectEventResponse> OnConnectAsync(ConnectEventRequest request, CancellationToken cancellationToken)
8088
{
8189
var response = new ConnectEventResponse
8290
{
8391
UserId = request.ConnectionContext.UserId
8492
};
85-
return new ValueTask<WebPubSubEventResponse>(response);
93+
return new ValueTask<ConnectEventResponse>(response);
8694
}
8795
```
8896

sdk/webpubsub/Microsoft.Azure.WebPubSub.AspNetCore/api/Microsoft.Azure.WebPubSub.AspNetCore.netcoreapp3.1.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,16 @@ namespace Microsoft.Azure.WebPubSub.AspNetCore
1010
public abstract partial class WebPubSubHub
1111
{
1212
protected WebPubSubHub() { }
13-
public abstract System.Threading.Tasks.ValueTask<Microsoft.Azure.WebPubSub.Common.WebPubSubEventResponse> OnConnectAsync(Microsoft.Azure.WebPubSub.Common.ConnectEventRequest request, System.Threading.CancellationToken cancellationToken);
13+
public virtual System.Threading.Tasks.ValueTask<Microsoft.Azure.WebPubSub.Common.ConnectEventResponse> OnConnectAsync(Microsoft.Azure.WebPubSub.Common.ConnectEventRequest request, System.Threading.CancellationToken cancellationToken) { throw null; }
1414
public virtual System.Threading.Tasks.Task OnConnectedAsync(Microsoft.Azure.WebPubSub.Common.ConnectedEventRequest request) { throw null; }
1515
public virtual System.Threading.Tasks.Task OnDisconnectedAsync(Microsoft.Azure.WebPubSub.Common.DisconnectedEventRequest request) { throw null; }
16-
public abstract System.Threading.Tasks.ValueTask<Microsoft.Azure.WebPubSub.Common.WebPubSubEventResponse> OnMessageReceivedAsync(Microsoft.Azure.WebPubSub.Common.UserEventRequest request, System.Threading.CancellationToken cancellationToken);
16+
public virtual System.Threading.Tasks.ValueTask<Microsoft.Azure.WebPubSub.Common.UserEventResponse> OnMessageReceivedAsync(Microsoft.Azure.WebPubSub.Common.UserEventRequest request, System.Threading.CancellationToken cancellationToken) { throw null; }
1717
}
1818
public partial class WebPubSubOptions
1919
{
2020
public WebPubSubOptions() { }
2121
public Microsoft.Azure.WebPubSub.AspNetCore.WebPubSubValidationOptions ValidationOptions { get { throw null; } set { } }
2222
}
23-
public static partial class WebPubSubRequestExtensions
24-
{
25-
public static System.Threading.Tasks.Task<Microsoft.Azure.WebPubSub.Common.WebPubSubEventRequest> ReadWebPubSubEventAsync(this Microsoft.AspNetCore.Http.HttpRequest request, Microsoft.Azure.WebPubSub.AspNetCore.WebPubSubValidationOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
26-
}
2723
public partial class WebPubSubValidationOptions
2824
{
2925
public WebPubSubValidationOptions(System.Collections.Generic.IEnumerable<string> connectionStrings) { }

sdk/webpubsub/Microsoft.Azure.WebPubSub.AspNetCore/api/Microsoft.Azure.WebPubSub.AspNetCore.netstandard2.0.cs

Lines changed: 0 additions & 26 deletions
This file was deleted.

sdk/webpubsub/Microsoft.Azure.WebPubSub.AspNetCore/src/Extensions/WebPubSubDependencyInjectionExtensions.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
3-
#if NETCOREAPP3_0_OR_GREATER
43
using System;
54
using Microsoft.Azure.WebPubSub.AspNetCore;
6-
using Microsoft.Extensions.DependencyInjection.Extensions;
75

86
namespace Microsoft.Extensions.DependencyInjection
97
{
@@ -54,4 +52,3 @@ public static IServiceCollection AddWebPubSub(this IServiceCollection services)
5452
}
5553
}
5654
}
57-
#endif

sdk/webpubsub/Microsoft.Azure.WebPubSub.AspNetCore/src/Extensions/WebPubSubEndpointRouteBuilderExtensions.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
#if NETCOREAPP3_0_OR_GREATER
2-
using System;
1+
using System;
32
using Microsoft.AspNetCore.Routing;
43
using Microsoft.Azure.WebPubSub.AspNetCore;
54
using Microsoft.Extensions.DependencyInjection;
@@ -45,5 +44,4 @@ public static IEndpointConventionBuilder MapWebPubSubHub<THub>(
4544
return endpoints.Map(path, app.Build());
4645
}
4746
}
48-
}
49-
#endif
47+
}

sdk/webpubsub/Microsoft.Azure.WebPubSub.AspNetCore/src/Extensions/WebPubSubRequestExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace Microsoft.Azure.WebPubSub.AspNetCore
2121
/// <summary>
2222
/// Helper methods to parse upstream requests.
2323
/// </summary>
24-
public static class WebPubSubRequestExtensions
24+
internal static class WebPubSubRequestExtensions
2525
{
2626
/// <summary>
2727
/// Parse request to system/user type ServiceRequest.
@@ -30,7 +30,7 @@ public static class WebPubSubRequestExtensions
3030
/// <param name="options"></param>
3131
/// <param name="cancellationToken"></param>
3232
/// <returns>Deserialize <see cref="WebPubSubEventRequest"/></returns>
33-
public static async Task<WebPubSubEventRequest> ReadWebPubSubEventAsync(this HttpRequest request, WebPubSubValidationOptions options = null, CancellationToken cancellationToken = default)
33+
internal static async Task<WebPubSubEventRequest> ReadWebPubSubEventAsync(this HttpRequest request, WebPubSubValidationOptions options = null, CancellationToken cancellationToken = default)
3434
{
3535
if (request == null)
3636
{

sdk/webpubsub/Microsoft.Azure.WebPubSub.AspNetCore/src/Internal/ServiceRequestHandlerAdapter.cs

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.AspNetCore.Http;
1010
using Microsoft.Azure.WebPubSub.Common;
1111
using Microsoft.Extensions.DependencyInjection;
12+
using Microsoft.Extensions.Logging;
1213
using Microsoft.Extensions.Options;
1314

1415
namespace Microsoft.Azure.WebPubSub.AspNetCore
@@ -17,21 +18,16 @@ internal class ServiceRequestHandlerAdapter
1718
{
1819
private readonly WebPubSubOptions _options;
1920
private readonly IServiceProvider _provider;
21+
private readonly ILogger _logger;
2022

2123
// <hubName, HubImpl>
2224
private readonly Dictionary<string, WebPubSubHub> _hubRegistry = new(StringComparer.OrdinalIgnoreCase);
2325

24-
public ServiceRequestHandlerAdapter(IServiceProvider provider, IOptions<WebPubSubOptions> options)
26+
public ServiceRequestHandlerAdapter(IServiceProvider provider, IOptions<WebPubSubOptions> options, ILogger<ServiceRequestHandlerAdapter> logger)
2527
{
26-
_provider = provider;
28+
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
2729
_options = options.Value;
28-
}
29-
30-
// for tests.
31-
internal ServiceRequestHandlerAdapter(WebPubSubOptions options, WebPubSubHub hub)
32-
{
33-
_options = options;
34-
_hubRegistry.Add(hub.GetType().Name, hub);
30+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
3531
}
3632

3733
public void RegisterHub<THub>() where THub : WebPubSubHub
@@ -81,6 +77,7 @@ public async Task HandleRequest(HttpContext context)
8177
try
8278
{
8379
var serviceRequest = await request.ReadWebPubSubEventAsync(_options.ValidationOptions, context.RequestAborted);
80+
Log.StartToHandleRequest(_logger, serviceRequest.ConnectionContext);
8481

8582
switch (serviceRequest)
8683
{
@@ -90,71 +87,66 @@ public async Task HandleRequest(HttpContext context)
9087
if (preflightRequest.IsValid)
9188
{
9289
context.Response.Headers.Add(Constants.Headers.WebHookAllowedOrigin, Constants.AllowedAllOrigins);
93-
return;
90+
break;
9491
}
9592
context.Response.StatusCode = StatusCodes.Status400BadRequest;
9693
await context.Response.WriteAsync("Abuse Protection validation failed.").ConfigureAwait(false);
97-
return;
94+
break;
9895
}
9996
case ConnectEventRequest connectEventRequest:
10097
{
10198
var response = await hub.OnConnectAsync(connectEventRequest, context.RequestAborted).ConfigureAwait(false);
102-
if (response is EventErrorResponse error)
99+
// default as null is allowed.
100+
if (response != null)
103101
{
104-
context.Response.StatusCode = ConvertToStatusCode(error.Code);
105-
context.Response.ContentType = Constants.ContentTypes.PlainTextContentType;
106-
await context.Response.WriteAsync(error.ErrorMessage).ConfigureAwait(false);
107-
return;
102+
SetConnectionState(ref context, connectEventRequest.ConnectionContext, response.States);
103+
await context.Response.WriteAsync(JsonSerializer.Serialize(response)).ConfigureAwait(false);
108104
}
109-
else if (response is ConnectEventResponse connectResponse)
110-
{
111-
SetConnectionState(ref context, connectEventRequest.ConnectionContext, connectResponse.States);
112-
await context.Response.WriteAsync(JsonSerializer.Serialize(connectResponse)).ConfigureAwait(false);
113-
return;
114-
}
115-
// other response is invalid, igonre.
116-
return;
105+
break;
117106
}
118107
case UserEventRequest messageRequest:
119108
{
120109
var response = await hub.OnMessageReceivedAsync(messageRequest, context.RequestAborted).ConfigureAwait(false);
121-
if (response is EventErrorResponse error)
110+
// default as null is allowed.
111+
if (response != null)
122112
{
123-
context.Response.StatusCode = ConvertToStatusCode(error.Code);
124-
context.Response.ContentType = Constants.ContentTypes.PlainTextContentType;
125-
await context.Response.WriteAsync(error.ErrorMessage).ConfigureAwait(false);
126-
return;
113+
SetConnectionState(ref context, messageRequest.ConnectionContext, response.States);
127114
}
128-
else if (response is UserEventResponse msgResponse)
115+
if (response.Message != null)
129116
{
130-
SetConnectionState(ref context, messageRequest.ConnectionContext, msgResponse.States);
131-
context.Response.ContentType = ConvertToContentType(msgResponse.DataType);
132-
var payload = msgResponse.Message.ToArray();
117+
context.Response.ContentType = ConvertToContentType(response.DataType);
118+
var payload = response.Message.ToArray();
133119
await context.Response.Body.WriteAsync(payload, 0, payload.Length).ConfigureAwait(false);
134-
return;
135120
}
136-
// other response is invalid, igonre.
137-
return;
121+
break;
138122
}
139123
case ConnectedEventRequest connectedEvent:
140124
{
141125
_ = hub.OnConnectedAsync(connectedEvent).ConfigureAwait(false);
142-
return;
126+
break;
143127
}
144128
case DisconnectedEventRequest disconnectedEvent:
145129
{
146130
_ = hub.OnDisconnectedAsync(disconnectedEvent).ConfigureAwait(false);
147-
return;
131+
break;
148132
}
149133
default:
150-
return;
134+
break;
151135
}
136+
Log.SucceededToHandleRequest(_logger, serviceRequest.ConnectionContext);
152137
}
153138
catch (UnauthorizedAccessException ex)
154139
{
140+
Log.FailedToHandleRequest(_logger, ex.Message, ex);
155141
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
156142
await context.Response.WriteAsync(ex.Message).ConfigureAwait(false);
157-
return;
143+
}
144+
catch (Exception ex)
145+
{
146+
Log.FailedToHandleRequest(_logger, ex.Message, ex);
147+
// logging to service.
148+
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
149+
await context.Response.WriteAsync(ex.Message).ConfigureAwait(false);
158150
}
159151
}
160152

@@ -167,15 +159,6 @@ private static void SetConnectionState(ref HttpContext context, WebPubSubConnect
167159
}
168160
}
169161

170-
private static int ConvertToStatusCode(WebPubSubErrorCode errorCode) =>
171-
errorCode switch
172-
{
173-
WebPubSubErrorCode.UserError => StatusCodes.Status400BadRequest,
174-
WebPubSubErrorCode.Unauthorized => StatusCodes.Status401Unauthorized,
175-
// default and server error returns 500
176-
_ => StatusCodes.Status500InternalServerError
177-
};
178-
179162
private static string ConvertToContentType(MessageDataType dataType) =>
180163
dataType switch
181164
{
@@ -198,5 +181,32 @@ private THub Create<THub>() where THub : WebPubSubHub
198181
}
199182
return hub;
200183
}
184+
185+
private static class Log
186+
{
187+
private static readonly Action<ILogger, string, string, string, Exception> _startToHandleRequest =
188+
LoggerMessage.Define<string, string, string>(LogLevel.Debug, new EventId(1, "StartToHandleRequest"), "Start to handle request, connectionId: {connectionId}, eventType: {eventType}, eventName: {eventName}");
189+
190+
private static readonly Action<ILogger, string, string, string, Exception> _succeededToHandleRequest =
191+
LoggerMessage.Define<string, string, string>(LogLevel.Debug, new EventId(2, "SucceededToHandleRequest"), "Succeeded to handle request, connectionId: {connectionId}, eventType: {eventType}, eventName: {eventName}");
192+
193+
private static readonly Action<ILogger, string, Exception> _failedToHandleRequest =
194+
LoggerMessage.Define<string>(LogLevel.Warning, new EventId(3, "FailedToHandleRequest"), "Handle request failed. {error}");
195+
196+
public static void StartToHandleRequest(ILogger logger, WebPubSubConnectionContext context)
197+
{
198+
_startToHandleRequest(logger, context?.ConnectionId, context?.EventType.ToString(), context?.EventName, null);
199+
}
200+
201+
public static void SucceededToHandleRequest(ILogger logger, WebPubSubConnectionContext context)
202+
{
203+
_succeededToHandleRequest(logger, context?.ConnectionId, context?.EventType.ToString(), context?.EventName, null);
204+
}
205+
206+
public static void FailedToHandleRequest(ILogger logger, string error, Exception exception)
207+
{
208+
_failedToHandleRequest(logger, error, exception);
209+
}
210+
}
201211
}
202212
}

sdk/webpubsub/Microsoft.Azure.WebPubSub.AspNetCore/src/Internal/WebPubSubMarkerService.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Text;
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
43

54
namespace Microsoft.Azure.WebPubSub.AspNetCore
65
{

0 commit comments

Comments
 (0)