How to use WaitForWebSocketConnectionRequest method of Microsoft.Playwright.Tests.TestServer.SimpleServer class

Best Playwright-dotnet code snippet using Microsoft.Playwright.Tests.TestServer.SimpleServer.WaitForWebSocketConnectionRequest

Run Playwright-dotnet automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

SimpleServer.cs

Source: SimpleServer.cs Github

copy
1/*
2 * MIT License
3 *
4 * Copyright (c) 2020 Darío Kondratiuk
5 * Modifications copyright (c) Microsoft Corporation.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in all
15 * copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 */
25
26using System;
27using System.Collections.Concurrent;
28using System.Collections.Generic;
29using System.Net;
30using System.Net.WebSockets;
31using System.Security.Cryptography.X509Certificates;
32using System.Text;
33using System.Threading;
34using System.Threading.Tasks;
35using Microsoft.AspNetCore.Builder;
36using Microsoft.AspNetCore.Hosting;
37using Microsoft.AspNetCore.Http;
38using Microsoft.AspNetCore.Http.Features;
39using Microsoft.Extensions.Configuration;
40using Microsoft.Extensions.DependencyInjection;
41
42namespace Microsoft.Playwright.Tests.TestServer
43{
44    public class SimpleServer
45    {
46        const int MaxMessageSize = 256 * 1024;
47
48        private readonly IDictionary<string, Action<HttpContext>> _subscribers;
49        private readonly IDictionary<string, Action<HttpContext>> _requestWaits;
50        private readonly IList<Action<HttpContext>> _waitForWebSocketConnectionRequestsWaits;
51        private readonly IDictionary<string, RequestDelegate> _routes;
52        private readonly IDictionary<string, (string username, string password)> _auths;
53        private readonly IDictionary<string, string> _csp;
54
55        private ArraySegment<byte> _onWebSocketConnectionData;
56        private readonly IWebHost _webHost;
57        private static int _counter;
58        private readonly Dictionary<int, WebSocket> _clients = new();
59
60        public int Port { get; }
61        public string Prefix { get; }
62        public string CrossProcessPrefix { get; }
63        public string EmptyPage { get; internal set; }
64
65        internal IList<string> GzipRoutes { get; }
66
67        public event EventHandler<RequestReceivedEventArgs> RequestReceived;
68
69        public static SimpleServer Create(int port, string contentRoot) => new(port, contentRoot, isHttps: false);
70
71        public static SimpleServer CreateHttps(int port, string contentRoot) => new(port, contentRoot, isHttps: true);
72
73        public SimpleServer(int port, string contentRoot, bool isHttps)
74        {
75            Port = port;
76            if (isHttps)
77            {
78                Prefix = $"https://localhost:{port}";
79                CrossProcessPrefix = $"https://127.0.0.1:{port}";
80            }
81            else
82            {
83                Prefix = $"http://localhost:{port}";
84                CrossProcessPrefix = $"http://127.0.0.1:{port}";
85            }
86
87            EmptyPage = $"{Prefix}/empty.html";
88
89            _subscribers = new ConcurrentDictionary<string, Action<HttpContext>>();
90            _requestWaits = new ConcurrentDictionary<string, Action<HttpContext>>();
91            _waitForWebSocketConnectionRequestsWaits = new List<Action<HttpContext>>();
92            _routes = new ConcurrentDictionary<string, RequestDelegate>();
93            _auths = new ConcurrentDictionary<string, (string username, string password)>();
94            _csp = new ConcurrentDictionary<string, string>();
95            GzipRoutes = new List<string>();
96
97            _webHost = new WebHostBuilder()
98                .ConfigureAppConfiguration((context, builder) => builder
99                    .SetBasePath(context.HostingEnvironment.ContentRootPath)
100                    .AddEnvironmentVariables()
101                )
102                .UseWebRoot("assets")
103                .Configure(app => app
104#if NETCOREAPP
105                    .UseWebSockets()
106#endif
107                    .UseDeveloperExceptionPage()
108                    .Use(async (context, next) =>
109                    {
110                        RequestReceived?.Invoke(this, new() { Request = context.Request });
111
112                        if (context.Request.Path == "/ws")
113                        {
114                            if (context.WebSockets.IsWebSocketRequest)
115                            {
116                                foreach (var wait in _waitForWebSocketConnectionRequestsWaits)
117                                {
118                                    wait(context);
119                                }
120                                var webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
121                                if (_onWebSocketConnectionData != null)
122                                {
123                                    await webSocket.SendAsync(_onWebSocketConnectionData, WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);
124                                }
125                                await ReceiveLoopAsync(webSocket, context.Request.Headers["User-Agent"].ToString().Contains("Firefox"), CancellationToken.None).ConfigureAwait(false);
126                            }
127                            else if (!context.Response.HasStarted)
128                            {
129                                context.Response.StatusCode = 400;
130                            }
131                            return;
132                        }
133
134                        if (_auths.TryGetValue(context.Request.Path, out var auth) && !Authenticate(auth.username, auth.password, context))
135                        {
136                            context.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"Secure Area\"");
137
138                            if (!context.Response.HasStarted)
139                            {
140                                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
141                            }
142                            await context.Response.WriteAsync("HTTP Error 401 Unauthorized: Access is denied").ConfigureAwait(false);
143                        }
144                        if (_subscribers.TryGetValue(context.Request.Path, out var subscriber))
145                        {
146                            subscriber(context);
147                        }
148                        if (_requestWaits.TryGetValue(context.Request.Path, out var requestWait))
149                        {
150                            requestWait(context);
151                        }
152                        if (_routes.TryGetValue(context.Request.Path + context.Request.QueryString, out var handler))
153                        {
154                            await handler(context).ConfigureAwait(false);
155                            return;
156                        }
157
158                        if (
159                            context.Request.Path.ToString().Contains("/cached/") &&
160                            !string.IsNullOrEmpty(context.Request.Headers["if-modified-since"]) &&
161                            !context.Response.HasStarted)
162                        {
163                            context.Response.StatusCode = StatusCodes.Status304NotModified;
164                        }
165
166                        await next().ConfigureAwait(false);
167                    })
168                    .UseMiddleware<SimpleCompressionMiddleware>(this)
169                    .UseStaticFiles(new StaticFileOptions
170                    {
171                        OnPrepareResponse = fileResponseContext =>
172                        {
173                            if (_csp.TryGetValue(fileResponseContext.Context.Request.Path, out string csp))
174                            {
175                                fileResponseContext.Context.Response.Headers["Content-Security-Policy"] = csp;
176                            }
177
178                            if (fileResponseContext.Context.Request.Path.ToString().EndsWith(".html"))
179                            {
180                                fileResponseContext.Context.Response.Headers["Content-Type"] = "text/html; charset=utf-8";
181
182                                if (fileResponseContext.Context.Request.Path.ToString().Contains("/cached/"))
183                                {
184                                    fileResponseContext.Context.Response.Headers["Cache-Control"] = "public, max-age=31536000, no-cache";
185                                    fileResponseContext.Context.Response.Headers["Last-Modified"] = DateTime.Now.ToString("s");
186                                }
187                                else
188                                {
189                                    fileResponseContext.Context.Response.Headers["Cache-Control"] = "no-cache, no-store";
190                                }
191                            }
192                        }
193                    }))
194                .UseKestrel(options =>
195                {
196                    if (isHttps)
197                    {
198                        var cert = new X509Certificate2("key.pfx", "aaaa");
199                        options.Listen(IPAddress.Loopback, port, listenOptions => listenOptions.UseHttps(cert));
200                    }
201                    else
202                    {
203                        options.Listen(IPAddress.Loopback, port);
204                    }
205                    options.Limits.MaxRequestBodySize = int.MaxValue;
206                })
207                .UseContentRoot(contentRoot)
208                .ConfigureServices((builder, services) =>
209                {
210                    services.Configure<FormOptions>(o =>
211                    {
212                        o.MultipartBodyLengthLimit = int.MaxValue;
213                    });
214                })
215                .Build();
216        }
217
218        public void SetAuth(string path, string username, string password) => _auths.Add(path, (username, password));
219
220        public void SetCSP(string path, string csp) => _csp.Add(path, csp);
221
222        public Task StartAsync() => _webHost.StartAsync();
223
224        public Task StopAsync()
225        {
226            Reset();
227
228            return _webHost.StopAsync();
229        }
230
231        public void Reset()
232        {
233            _routes.Clear();
234            _auths.Clear();
235            _csp.Clear();
236            _subscribers.Clear();
237            _requestWaits.Clear();
238            GzipRoutes.Clear();
239            _onWebSocketConnectionData = null;
240            foreach (var subscriber in _subscribers.Values)
241            {
242                subscriber(null);
243            }
244            _subscribers.Clear();
245        }
246
247        public void EnableGzip(string path) => GzipRoutes.Add(path);
248
249        public void SetRoute(string path, RequestDelegate handler) => _routes[path] = handler;
250
251        public void SendOnWebSocketConnection(string data) => _onWebSocketConnectionData = Encoding.UTF8.GetBytes(data);
252
253        public void SetRedirect(string from, string to) => SetRoute(from, context =>
254        {
255            context.Response.Redirect(to);
256            return Task.CompletedTask;
257        });
258
259        public void Subscribe(string path, Action<HttpContext> action)
260            => _subscribers.Add(path, action);
261
262        public async Task<T> WaitForRequest<T>(string path, Func<HttpRequest, T> selector)
263        {
264            var taskCompletion = new TaskCompletionSource<T>();
265            _requestWaits.Add(path, context =>
266            {
267                taskCompletion.SetResult(selector(context.Request));
268            });
269
270            var request = await taskCompletion.Task.ConfigureAwait(false);
271            _requestWaits.Remove(path);
272
273            return request;
274        }
275
276        public Task WaitForRequest(string path) => WaitForRequest(path, _ => true);
277
278        public async Task<HttpRequest> WaitForWebSocketConnectionRequest()
279        {
280            var taskCompletion = new TaskCompletionSource<HttpRequest>();
281            void entryCb(HttpContext context)
282            {
283                taskCompletion.SetResult(context.Request);
284            };
285            _waitForWebSocketConnectionRequestsWaits.Add(entryCb);
286
287            var request = await taskCompletion.Task.ConfigureAwait(false);
288            _waitForWebSocketConnectionRequestsWaits.Remove(entryCb);
289            return request;
290        }
291
292        private static bool Authenticate(string username, string password, HttpContext context)
293        {
294            string authHeader = context.Request.Headers["Authorization"];
295            if (authHeader != null && authHeader.StartsWith("Basic", StringComparison.Ordinal))
296            {
297                string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim();
298                var encoding = Encoding.GetEncoding("iso-8859-1");
299                string auth = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));
300
301                return auth == $"{username}:{password}";
302            }
303            return false;
304        }
305
306        private async Task ReceiveLoopAsync(WebSocket webSocket, bool sendCloseMessage, CancellationToken token)
307        {
308            int connectionId = NextConnectionId();
309            _clients.Add(connectionId, webSocket);
310
311            byte[] buffer = new byte[MaxMessageSize];
312
313            try
314            {
315                while (true)
316                {
317                    var result = await webSocket.ReceiveAsync(new(buffer), token).ConfigureAwait(false);
318
319                    if (result.MessageType == WebSocketMessageType.Close)
320                    {
321                        if (sendCloseMessage)
322                        {
323                            await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Close", CancellationToken.None).ConfigureAwait(false);
324                        }
325                        break;
326                    }
327
328                    var data = await ReadFrames(result, webSocket, buffer, token).ConfigureAwait(false);
329
330                    if (data.Count == 0)
331                    {
332                        break;
333                    }
334                }
335            }
336            finally
337            {
338                _clients.Remove(connectionId);
339            }
340        }
341
342        private async Task<ArraySegment<byte>> ReadFrames(WebSocketReceiveResult result, WebSocket webSocket, byte[] buffer, CancellationToken token)
343        {
344            int count = result.Count;
345
346            while (!result.EndOfMessage)
347            {
348                if (count >= MaxMessageSize)
349                {
350                    string closeMessage = string.Format("Maximum message size: {0} bytes.", MaxMessageSize);
351                    await webSocket.CloseAsync(WebSocketCloseStatus.MessageTooBig, closeMessage, token).ConfigureAwait(false);
352                    return new();
353                }
354
355                result = await webSocket.ReceiveAsync(new(buffer, count, MaxMessageSize - count), token).ConfigureAwait(false);
356                count += result.Count;
357
358            }
359            return new(buffer, 0, count);
360        }
361
362
363        private static int NextConnectionId()
364        {
365            int id = Interlocked.Increment(ref _counter);
366
367            if (id == int.MaxValue)
368            {
369                throw new("connection id limit reached: " + id);
370            }
371
372            return id;
373        }
374    }
375}
376
Full Screen

Accelerate Your Automation Test Cycles With LambdaTest

Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.

Try LambdaTest

Trigger WaitForWebSocketConnectionRequest code on LambdaTest Cloud Grid

Execute automation tests with WaitForWebSocketConnectionRequest on a cloud-based Grid of 3000+ real browsers and operating systems for both web and mobile applications.

Test now for Free
LambdaTestX

We use cookies to give you the best experience. Cookies help to provide a more personalized experience and relevant advertising for you, and web analytics for us. Learn More in our Cookies policy, Privacy & Terms of service

Allow Cookie
Sarah

I hope you find the best code examples for your project.

If you want to accelerate automated browser testing, try LambdaTest. Your first 100 automation testing minutes are FREE.

Sarah Elson (Product & Growth Lead)