• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ThreeMammals / Ocelot / 14794531460

02 May 2025 11:42AM UTC coverage: 85.804% (+0.05%) from 85.75%
14794531460

Pull #2091

github

14d423
web-flow
Merge 98c11bb37 into e78fe42fd
Pull Request #2091: #930 Try to close WebSocket destination when state is Open or CloseReceived

14 of 18 new or added lines in 2 files covered. (77.78%)

9 existing lines in 3 files now uncovered.

5464 of 6368 relevant lines covered (85.8%)

4991.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.9
src/Ocelot/WebSockets/WebSocketsProxyMiddleware.cs
1
// Copyright (c) .NET Foundation. All rights reserved.
2
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3
// Modified https://github.com/aspnet/Proxy websockets class to use in Ocelot.
4

5
using Microsoft.AspNetCore.Http;
6
using Ocelot.Configuration;
7
using Ocelot.Logging;
8
using Ocelot.Middleware;
9
using Ocelot.Request.Middleware;
10
using System.Net.WebSockets;
11

12
namespace Ocelot.WebSockets;
13

14
public class WebSocketsProxyMiddleware : OcelotMiddleware
15
{
16
    private static readonly string[] NotForwardedWebSocketHeaders = new[]
×
17
    {
×
18
        "Connection", "Host", "Upgrade",
×
19
        "Sec-WebSocket-Accept", "Sec-WebSocket-Protocol", "Sec-WebSocket-Key", "Sec-WebSocket-Version", "Sec-WebSocket-Extensions",
×
20
    };
×
21

22
    private const int DefaultWebSocketBufferSize = 4096;
23
    private readonly RequestDelegate _next;
24
    private readonly IWebSocketsFactory _factory;
25

26
    public const string IgnoredSslWarningFormat = $"You have ignored all SSL warnings by using {nameof(DownstreamRoute.DangerousAcceptAnyServerCertificateValidator)} for this downstream route! {nameof(DownstreamRoute.UpstreamPathTemplate)}: '{{0}}', {nameof(DownstreamRoute.DownstreamPathTemplate)}: '{{1}}'.";
27
    public const string InvalidSchemeWarningFormat = "Invalid scheme has detected which will be replaced! Scheme '{0}' of the downstream '{1}'.";
28

29
    public WebSocketsProxyMiddleware(IOcelotLoggerFactory loggerFactory,
30
        RequestDelegate next,
31
        IWebSocketsFactory factory)
32
        : base(loggerFactory.CreateLogger<WebSocketsProxyMiddleware>())
6✔
33
    {
34
        _next = next;
6✔
35
        _factory = factory;
6✔
36
    }
6✔
37

38
    private static async Task PumpWebSocket(WebSocket source, WebSocket destination, int bufferSize, CancellationToken cancellationToken)
39
    {
40
        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bufferSize);
8✔
41
        var buffer = new byte[bufferSize];
8✔
42
        while (true)
×
43
        {
44
            WebSocketReceiveResult result;
45
            try
46
            {
47
                result = await source.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
8✔
48
            }
8✔
49
            catch (OperationCanceledException)
×
50
            {
NEW
UNCOV
51
                await destination.TryCloseOutputAsync(WebSocketCloseStatus.EndpointUnavailable, nameof(OperationCanceledException), cancellationToken);
×
UNCOV
52
                return;
×
53
            }
54
            catch (WebSocketException e)
×
55
            {
56
                if (e.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely)
×
57
                {
NEW
58
                    await destination.TryCloseOutputAsync(WebSocketCloseStatus.EndpointUnavailable, $"{nameof(WebSocketException)} when {nameof(e.WebSocketErrorCode)} is {nameof(WebSocketError.ConnectionClosedPrematurely)}", cancellationToken);
×
59
                    return;
×
60
                }
61

UNCOV
62
                throw;
×
63
            }
64

65
            if (result.MessageType == WebSocketMessageType.Close)
8✔
66
            {
67
                await destination.TryCloseOutputAsync(source.CloseStatus.Value, source.CloseStatusDescription, cancellationToken);
8✔
68
                return;
8✔
69
            }
70

NEW
UNCOV
71
            if (destination.State == WebSocketState.Open)
×
72
            {
NEW
UNCOV
73
                await destination.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, cancellationToken);
×
74
            }
UNCOV
75
        }
×
76
    }
8✔
77

78
    public async Task Invoke(HttpContext httpContext)
79
    {
80
        var downstreamRequest = httpContext.Items.DownstreamRequest();
4✔
81
        var downstreamRoute = httpContext.Items.DownstreamRoute();
4✔
82
        await Proxy(httpContext, downstreamRequest, downstreamRoute);
4✔
83
    }
4✔
84

85
    private async Task Proxy(HttpContext context, DownstreamRequest request, DownstreamRoute route)
86
    {
87
        ArgumentNullException.ThrowIfNull(context);
4✔
88
        ArgumentNullException.ThrowIfNull(request);
4✔
89
        ArgumentNullException.ThrowIfNull(route);
4✔
90

91
        if (!context.WebSockets.IsWebSocketRequest)
4✔
92
        {
93
            throw new InvalidOperationException();
×
94
        }
95

96
        var client = _factory.CreateClient(); // new ClientWebSocket();
4✔
97

98
        if (route.DangerousAcceptAnyServerCertificateValidator)
4✔
99
        {
100
            client.Options.RemoteCertificateValidationCallback = (request, certificate, chain, errors) => true;
2✔
101
            Logger.LogWarning(() => string.Format(IgnoredSslWarningFormat, route.UpstreamPathTemplate, route.DownstreamPathTemplate));
2✔
102
        }
103

104
        foreach (var protocol in context.WebSockets.WebSocketRequestedProtocols)
8✔
105
        {
106
            client.Options.AddSubProtocol(protocol);
×
107
        }
108

109
        foreach (var headerEntry in context.Request.Headers)
8✔
110
        {
111
            if (!NotForwardedWebSocketHeaders.Contains(headerEntry.Key, StringComparer.OrdinalIgnoreCase))
×
112
            {
113
                try
114
                {
115
                    client.Options.SetRequestHeader(headerEntry.Key, headerEntry.Value);
×
116
                }
×
117
                catch (ArgumentException)
×
118
                {
119
                    // Expected in .NET Framework for headers that are mistakenly considered restricted.
120
                    // See: https://github.com/dotnet/corefx/issues/26627
121
                    // .NET Core does not exhibit this issue, ironically due to a separate bug (https://github.com/dotnet/corefx/issues/18784)
122
                }
×
123
            }
124
        }
125

126
        // Only Uris starting with 'ws://' or 'wss://' are supported in System.Net.WebSockets.ClientWebSocket
127
        var scheme = request.Scheme;
4✔
128
        if (!scheme.StartsWith(Uri.UriSchemeWs))
4✔
129
        {
130
            Logger.LogWarning(() => string.Format(InvalidSchemeWarningFormat, scheme, request.ToUri()));
6✔
131
            request.Scheme = scheme == Uri.UriSchemeHttp
3✔
132
                ? Uri.UriSchemeWs
3✔
133
                : scheme == Uri.UriSchemeHttps ? Uri.UriSchemeWss : scheme;
3✔
134
        }
135

136
        var destinationUri = new Uri(request.ToUri());
4✔
137
        await client.ConnectAsync(destinationUri, context.RequestAborted);
4✔
138

139
        using var server = await context.WebSockets.AcceptWebSocketAsync(client.SubProtocol);
4✔
140
        await Task.WhenAll(
4✔
141
            PumpWebSocket(client.ToWebSocket(), server, DefaultWebSocketBufferSize, context.RequestAborted),
4✔
142
            PumpWebSocket(server, client.ToWebSocket(), DefaultWebSocketBufferSize, context.RequestAborted));
4✔
143
    }
4✔
144
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc