mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-04 17:30:48 +08:00 
			
		
		
		
	Feature/fix tracing (#297)
* hacked together tracing fix by wrapping middleware delegate in another delegate * #227 have re-implemented tracing, cleaned up trace names, probably still need some refactoring and tests as this was a bit of a hack job * #227 bit of checking for when we dont want to use tracing, also removed a unit test for websockets that wasnt a unit test, i stuck it there because i wanted the code coverage and now im paying the price, will have to work out a better way to do it * #227 a bit of refactoring to make this work better, still a bit hacky...would like to revisit the whole thing one day * #227 dont need this * #227 or this * #227 small refactor
This commit is contained in:
		@@ -1,6 +1,10 @@
 | 
			
		||||
using Ocelot.Configuration;
 | 
			
		||||
using System;
 | 
			
		||||
using Butterfly.Client.Tracing;
 | 
			
		||||
using Butterfly.OpenTracing;
 | 
			
		||||
using Ocelot.Configuration;
 | 
			
		||||
using Ocelot.Configuration.Creator;
 | 
			
		||||
using Ocelot.Configuration.File;
 | 
			
		||||
using Ocelot.Requester;
 | 
			
		||||
using Shouldly;
 | 
			
		||||
using TestStack.BDDfy;
 | 
			
		||||
using Xunit;
 | 
			
		||||
@@ -9,13 +13,54 @@ namespace Ocelot.UnitTests.Configuration
 | 
			
		||||
{
 | 
			
		||||
    public class HttpHandlerOptionsCreatorTests
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
 | 
			
		||||
        private IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
 | 
			
		||||
        private FileReRoute _fileReRoute;
 | 
			
		||||
        private HttpHandlerOptions _httpHandlerOptions;
 | 
			
		||||
        private IServiceTracer _serviceTracer;
 | 
			
		||||
 | 
			
		||||
        public HttpHandlerOptionsCreatorTests()
 | 
			
		||||
        {
 | 
			
		||||
            _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator();
 | 
			
		||||
            _serviceTracer = new FakeServiceTracer();
 | 
			
		||||
            _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(_serviceTracer);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_not_use_tracing_if_fake_tracer_registered()
 | 
			
		||||
        {
 | 
			
		||||
            var fileReRoute = new FileReRoute 
 | 
			
		||||
            {
 | 
			
		||||
                HttpHandlerOptions = new FileHttpHandlerOptions
 | 
			
		||||
                {
 | 
			
		||||
                    UseTracing = true
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var expectedOptions = new HttpHandlerOptions(false, false, false);
 | 
			
		||||
 | 
			
		||||
            this.Given(x => GivenTheFollowing(fileReRoute))
 | 
			
		||||
                .When(x => WhenICreateHttpHandlerOptions())
 | 
			
		||||
                .Then(x => ThenTheFollowingOptionsReturned(expectedOptions))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_use_tracing_if_real_tracer_registered()
 | 
			
		||||
        {
 | 
			
		||||
            var fileReRoute = new FileReRoute 
 | 
			
		||||
            {
 | 
			
		||||
                HttpHandlerOptions = new FileHttpHandlerOptions
 | 
			
		||||
                {
 | 
			
		||||
                    UseTracing = true
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var expectedOptions = new HttpHandlerOptions(false, false, true);
 | 
			
		||||
 | 
			
		||||
            this.Given(x => GivenTheFollowing(fileReRoute))
 | 
			
		||||
                .And(x => GivenARealTracer())
 | 
			
		||||
                .When(x => WhenICreateHttpHandlerOptions())
 | 
			
		||||
                .Then(x => ThenTheFollowingOptionsReturned(expectedOptions))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
@@ -68,5 +113,27 @@ namespace Ocelot.UnitTests.Configuration
 | 
			
		||||
            _httpHandlerOptions.UseCookieContainer.ShouldBe(expected.UseCookieContainer);
 | 
			
		||||
            _httpHandlerOptions.UseTracing.ShouldBe(expected.UseTracing);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenARealTracer()
 | 
			
		||||
        {
 | 
			
		||||
            var tracer = new RealTracer();
 | 
			
		||||
            _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(tracer);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        class RealTracer : IServiceTracer
 | 
			
		||||
        {
 | 
			
		||||
            public ITracer Tracer => throw new NotImplementedException();
 | 
			
		||||
 | 
			
		||||
            public string ServiceName => throw new NotImplementedException();
 | 
			
		||||
 | 
			
		||||
            public string Environment => throw new NotImplementedException();
 | 
			
		||||
 | 
			
		||||
            public string Identity => throw new NotImplementedException();
 | 
			
		||||
 | 
			
		||||
            public ISpan Start(ISpanBuilder spanBuilder)
 | 
			
		||||
            {
 | 
			
		||||
                throw new NotImplementedException();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,239 +0,0 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Net.WebSockets;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Consul;
 | 
			
		||||
using Microsoft.AspNetCore.Builder;
 | 
			
		||||
using Microsoft.AspNetCore.Hosting;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using Ocelot.Configuration.File;
 | 
			
		||||
using Ocelot.DependencyInjection;
 | 
			
		||||
using Ocelot.Middleware;
 | 
			
		||||
using Shouldly;
 | 
			
		||||
using TestStack.BDDfy;
 | 
			
		||||
using Xunit;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.UnitTests.Websockets
 | 
			
		||||
{
 | 
			
		||||
    public class WebSocketsProxyMiddlewareTests : IDisposable
 | 
			
		||||
    {
 | 
			
		||||
        private IWebHost _firstDownstreamHost;
 | 
			
		||||
        private readonly List<string> _firstRecieved;
 | 
			
		||||
        private WebHostBuilder _ocelotBuilder;
 | 
			
		||||
        private IWebHost _ocelotHost;
 | 
			
		||||
 | 
			
		||||
        public WebSocketsProxyMiddlewareTests()
 | 
			
		||||
        {
 | 
			
		||||
            _firstRecieved = new List<string>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public async Task should_proxy_websocket_input_to_downstream_service()
 | 
			
		||||
        {
 | 
			
		||||
            var downstreamPort = 5001;
 | 
			
		||||
            var downstreamHost = "localhost";
 | 
			
		||||
 | 
			
		||||
            var config = new FileConfiguration
 | 
			
		||||
            {
 | 
			
		||||
                ReRoutes = new List<FileReRoute>
 | 
			
		||||
                {
 | 
			
		||||
                    new FileReRoute
 | 
			
		||||
                    {
 | 
			
		||||
                        UpstreamPathTemplate = "/",
 | 
			
		||||
                        DownstreamPathTemplate = "/ws",
 | 
			
		||||
                        DownstreamScheme = "ws",
 | 
			
		||||
                        DownstreamHostAndPorts = new List<FileHostAndPort>
 | 
			
		||||
                        {
 | 
			
		||||
                            new FileHostAndPort
 | 
			
		||||
                            {
 | 
			
		||||
                                Host = downstreamHost,
 | 
			
		||||
                                Port = downstreamPort
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.Given(_ => GivenThereIsAConfiguration(config))
 | 
			
		||||
                .And(_ => StartFakeOcelotWithWebSockets())
 | 
			
		||||
                .And(_ => StartFakeDownstreamService($"http://{downstreamHost}:{downstreamPort}", "/ws"))
 | 
			
		||||
                .When(_ => StartClient("ws://localhost:5000/"))
 | 
			
		||||
                .Then(_ => _firstRecieved.Count.ShouldBe(10))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            _firstDownstreamHost?.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task StartFakeOcelotWithWebSockets()
 | 
			
		||||
        {
 | 
			
		||||
            _ocelotBuilder = new WebHostBuilder();
 | 
			
		||||
            _ocelotBuilder.ConfigureServices(s =>
 | 
			
		||||
            {
 | 
			
		||||
                s.AddSingleton(_ocelotBuilder);
 | 
			
		||||
                s.AddOcelot();
 | 
			
		||||
            });
 | 
			
		||||
            _ocelotBuilder.UseKestrel()
 | 
			
		||||
                .UseUrls("http://localhost:5000")
 | 
			
		||||
                .UseContentRoot(Directory.GetCurrentDirectory())
 | 
			
		||||
                .ConfigureAppConfiguration((hostingContext, config) =>
 | 
			
		||||
                {
 | 
			
		||||
                    config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
 | 
			
		||||
                    var env = hostingContext.HostingEnvironment;
 | 
			
		||||
                    config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
 | 
			
		||||
                        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
 | 
			
		||||
                    config.AddJsonFile("configuration.json");
 | 
			
		||||
                    config.AddEnvironmentVariables();
 | 
			
		||||
                })
 | 
			
		||||
                .ConfigureLogging((hostingContext, logging) =>
 | 
			
		||||
                {
 | 
			
		||||
                    logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
 | 
			
		||||
                    logging.AddConsole();
 | 
			
		||||
                })
 | 
			
		||||
                .Configure(app =>
 | 
			
		||||
                {
 | 
			
		||||
                    app.UseWebSockets();
 | 
			
		||||
                    app.UseOcelot().Wait();
 | 
			
		||||
                })
 | 
			
		||||
                .UseIISIntegration();
 | 
			
		||||
            _ocelotHost = _ocelotBuilder.Build();
 | 
			
		||||
            await _ocelotHost.StartAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
 | 
			
		||||
        {
 | 
			
		||||
            var configurationPath = Path.Combine(AppContext.BaseDirectory, "configuration.json");
 | 
			
		||||
 | 
			
		||||
            var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
 | 
			
		||||
 | 
			
		||||
            if (File.Exists(configurationPath))
 | 
			
		||||
            {
 | 
			
		||||
                File.Delete(configurationPath);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            File.WriteAllText(configurationPath, jsonConfiguration);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task StartFakeDownstreamService(string url, string path)
 | 
			
		||||
        {
 | 
			
		||||
            _firstDownstreamHost = new WebHostBuilder()
 | 
			
		||||
                .ConfigureServices(s => { }).UseKestrel()
 | 
			
		||||
                .UseUrls(url)
 | 
			
		||||
                .UseContentRoot(Directory.GetCurrentDirectory())
 | 
			
		||||
                .ConfigureAppConfiguration((hostingContext, config) =>
 | 
			
		||||
                {
 | 
			
		||||
                    config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
 | 
			
		||||
                    var env = hostingContext.HostingEnvironment;
 | 
			
		||||
                    config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
 | 
			
		||||
                        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
 | 
			
		||||
                    config.AddEnvironmentVariables();
 | 
			
		||||
                })
 | 
			
		||||
                .ConfigureLogging((hostingContext, logging) =>
 | 
			
		||||
                {
 | 
			
		||||
                    logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
 | 
			
		||||
                    logging.AddConsole();
 | 
			
		||||
                })
 | 
			
		||||
                .Configure(app =>
 | 
			
		||||
                {
 | 
			
		||||
                    app.UseWebSockets();
 | 
			
		||||
                    app.Use(async (context, next) =>
 | 
			
		||||
                    {
 | 
			
		||||
                        if (context.Request.Path == path)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (context.WebSockets.IsWebSocketRequest)
 | 
			
		||||
                            {
 | 
			
		||||
                                WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
 | 
			
		||||
                                await Echo(webSocket);
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                context.Response.StatusCode = 400;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            await next();
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                })
 | 
			
		||||
                .UseIISIntegration().Build();
 | 
			
		||||
            await _firstDownstreamHost.StartAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task StartClient(string url)
 | 
			
		||||
        {
 | 
			
		||||
            var client = new ClientWebSocket();
 | 
			
		||||
 | 
			
		||||
            await client.ConnectAsync(new Uri(url), CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            var sending = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                string line = "test";
 | 
			
		||||
                for (int i = 0; i < 10; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    var bytes = Encoding.UTF8.GetBytes(line);
 | 
			
		||||
 | 
			
		||||
                    await client.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true,
 | 
			
		||||
                        CancellationToken.None);
 | 
			
		||||
                    await Task.Delay(10);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            var receiving = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                var buffer = new byte[1024 * 4];
 | 
			
		||||
 | 
			
		||||
                while (true)
 | 
			
		||||
                {
 | 
			
		||||
                    var result = await client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
                    if (result.MessageType == WebSocketMessageType.Text)
 | 
			
		||||
                    {
 | 
			
		||||
                        _firstRecieved.Add(Encoding.UTF8.GetString(buffer, 0, result.Count));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    else if (result.MessageType == WebSocketMessageType.Close)
 | 
			
		||||
                    {
 | 
			
		||||
                        await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            await Task.WhenAll(sending, receiving);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task Echo(WebSocket webSocket)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var buffer = new byte[1024 * 4];
 | 
			
		||||
 | 
			
		||||
                var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
                while (!result.CloseStatus.HasValue)
 | 
			
		||||
                {
 | 
			
		||||
                    await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
                    result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception e)
 | 
			
		||||
            {
 | 
			
		||||
                Console.WriteLine(e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user