{"id":28391668,"url":"https://github.com/pvginkel/fastproxy","last_synced_at":"2025-07-23T16:06:02.155Z","repository":{"id":143288067,"uuid":"165797665","full_name":"pvginkel/FastProxy","owner":"pvginkel","description":"Simple, fast, hackable, .NET proxy server for load testing and unit testing.","archived":false,"fork":false,"pushed_at":"2019-08-23T20:06:33.000Z","size":367,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-31T20:46:06.628Z","etag":null,"topics":["csharp","proxy","testing"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pvginkel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-01-15T06:30:22.000Z","updated_at":"2021-08-13T01:11:13.000Z","dependencies_parsed_at":null,"dependency_job_id":"349a442c-a80a-434f-b0a1-237d9591e9cc","html_url":"https://github.com/pvginkel/FastProxy","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/pvginkel/FastProxy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pvginkel%2FFastProxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pvginkel%2FFastProxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pvginkel%2FFastProxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pvginkel%2FFastProxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pvginkel","download_url":"https://codeload.github.com/pvginkel/FastProxy/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pvginkel%2FFastProxy/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261955779,"owners_count":23235946,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","proxy","testing"],"created_at":"2025-05-31T10:02:20.032Z","updated_at":"2025-06-25T21:30:52.788Z","avatar_url":"https://github.com/pvginkel.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FastProxy\r\n\r\nFastProxy is a simple, fast, .NET TCP/IP proxy server.\r\n\r\nThe primary use case for FastProxy is for use in unit test or load test applications that test applications or components that expose services over the internet.\r\n\r\n[Install from NuGet](https://www.nuget.org/packages/FastProxy).\r\n\r\n[API Documentation](https://pvginkel.github.io/FastProxy/).\r\n\r\n## Introduction\r\n\r\nFastProxy is a simple proxy server that allows you to get control over network connections between a server and a client. When building applications that e.g. need to be resilient against connection drops, it's very difficult to simulate this. FastProxy gives you the tools you need to properly test this and make your applications resilient against e.g. connection drops.\r\n\r\nThe proxy server is built to be as fast as possible, to ensure it has as little impact on e.g. load tests as possible. Without any custom listeners configured, it will not allocation any memory after a connection has been established. The load testers are able to reach a 500 Mb/s data transfer speed tested on a 4 core Xeon server. Internally it uses [SocketAsyncEventArgs](https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socketasynceventargs) and lock free multithreading to make the proxy server as high performance and low impact as possible without requiring any external dependencies or unsafe code.\r\n\r\nFastProxy has been tested on .NET Framework and .NET Core 2.2 and .NET Core 3.0 preview 7.\r\n\r\n## Usage\r\n\r\nThe FastProxy project exposes a `ProxyServer` class. This class implements the .NET proxy that you would put between your server and client. The most trivial use case is as follows:\r\n\r\n```cs\r\npublic static void ProxyEcho()\r\n{\r\n    using (var echoServer = new EchoServer(new IPEndPoint(IPAddress.Loopback, 0)))\r\n    {\r\n        echoServer.Start();\r\n\r\n        var connector = new SimpleConnector(echoServer.EndPoint);\r\n\r\n        using (var proxyServer = new ProxyServer(new IPEndPoint(IPAddress.Loopback, 0), connector))\r\n        {\r\n            proxyServer.Start();\r\n\r\n            var block = Encoding.UTF8.GetBytes(\"Hello world!\");\r\n\r\n            using (var echoClient = new EchoPingClient(proxyServer.EndPoint, block))\r\n            {\r\n                echoClient.Start();\r\n\r\n                echoClient.Ping();\r\n            }\r\n        }\r\n    }\r\n}\r\n```\r\n\r\nThe `EchoServer` and `EchoPingClient` are a simple server and client implementation used in the FastProxy project for testing purposes. The `EchoServer` simply echoes back everything it receives, and the `EchoPingClient` sends out data and waits for it to be returned.\r\n\r\nA `ProxyServer` instance requires the following inputs:\r\n\r\n* An `IPEndPoint` at which to run the server. The above example specifies `0` as the port, which means a port will be chosen at random. The `EndPoint` property can be used to retrieve this port;\r\n* An `IConnector` to configure incoming connections.\r\n\r\nThe above example uses the `SimpleConnector` implementation to use a fixed `IPEndPoint` and optional `IListener`. The `IPEndPoint` provided is used by the proxy server to proxy the incoming connection to. The `SimpleConnector` class takes an optional `IListener` used to integrate with the proxy server. There are a number of stock listeners in the project, or you can create your own.\r\n\r\n## Examples\r\n\r\nThe section below has a number of samples on what you can do with FastProxy.\r\n\r\n### Load balancing\r\n\r\nThe `IConnector` allows you to integrate with the `ProxyServer` to control what happens when a new connection is established. One of the parameters you need to return is the `IPEndPoint` specifying the address of the upstream server. A simple use case for this is load balancing.\r\n\r\n```cs\r\npublic static void LoadBalancedProxyEcho()\r\n{\r\n    using (var echoServer1 = new EchoServer(new IPEndPoint(IPAddress.Loopback, 0)))\r\n    using (var echoServer2 = new EchoServer(new IPEndPoint(IPAddress.Loopback, 0)))\r\n    using (var echoServer3 = new EchoServer(new IPEndPoint(IPAddress.Loopback, 0)))\r\n    using (var echoServer4 = new EchoServer(new IPEndPoint(IPAddress.Loopback, 0)))\r\n    {\r\n        echoServer1.Start();\r\n        echoServer2.Start();\r\n        echoServer3.Start();\r\n        echoServer4.Start();\r\n\r\n        var connector = new RoundRobinLoadBalancingConnector(\r\n            echoServer1.EndPoint,\r\n            echoServer2.EndPoint,\r\n            echoServer3.EndPoint,\r\n            echoServer4.EndPoint\r\n        );\r\n\r\n        using (var proxyServer = new ProxyServer(new IPEndPoint(IPAddress.Loopback, 0), connector))\r\n        {\r\n            proxyServer.Start();\r\n\r\n            var block = Encoding.UTF8.GetBytes(\"Hello world!\");\r\n\r\n            for (int i = 0; i \u003c 32; i++)\r\n            {\r\n                using (var echoClient = new EchoPingClient(proxyServer.EndPoint, block))\r\n                {\r\n                    echoClient.Start();\r\n\r\n                    echoClient.Ping();\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\npublic class RoundRobinLoadBalancingConnector : IConnector\r\n{\r\n    private readonly IPEndPoint[] endpoints;\r\n    private int nextEndpoint;\r\n\r\n    public RoundRobinLoadBalancingConnector(params IPEndPoint[] endpoints)\r\n    {\r\n        this.endpoints = endpoints;\r\n    }\r\n\r\n    public ConnectResult Connect(out IPEndPoint endpoint, out IListener listener)\r\n    {\r\n        int nextEndpoint = Interlocked.Increment(ref this.nextEndpoint);\r\n\r\n        endpoint = endpoints[nextEndpoint % endpoints.Length];\r\n        listener = null;\r\n\r\n        return ConnectResult.Accept;\r\n    }\r\n}\r\n```\r\n\r\nThe above example is similar to the `EchoServer` example at the top, except that it starts four servers. Then, a customer `IConnector` implementation is used to control which of the servers to pick when accepting an incoming connection.\r\n\r\nThe `RoundRobinLoadBalancingConnector` implementation above takes an array of endpoints (the ones of the different servers), and every time a new connection is made, the next one is used. This ensures that every new incoming connection connects to a different upstream server in a round robin fashion.\r\n\r\n### Bandwidth throttling\r\n\r\nThe FastProxy project contains a number of stock listeners. The example below uses the bandwidth throttling listener.\r\n\r\n```cs\r\npublic static void BandwidthThrottlingProxyEcho()\r\n{\r\n    using (var echoServer = new EchoServer(new IPEndPoint(IPAddress.Loopback, 0)))\r\n    {\r\n        echoServer.Start();\r\n\r\n        var listener = new ThrottlingListener(\r\n            SinkListener.Instance,\r\n            10 * 1024 /* 10 Kb/s */\r\n        );\r\n        var connector = new SimpleConnector(echoServer.EndPoint, listener);\r\n\r\n        using (var proxyServer = new ProxyServer(new IPEndPoint(IPAddress.Loopback, 0), connector))\r\n        {\r\n            proxyServer.Start();\r\n\r\n            var block = Encoding.UTF8.GetBytes(new string('?', 1024));\r\n\r\n            var stopwatch = Stopwatch.StartNew();\r\n\r\n            using (var echoClient = new EchoPingClient(proxyServer.EndPoint, block))\r\n            {\r\n                echoClient.Start();\r\n\r\n                for (int i = 0; i \u003c 100; i++)\r\n                {\r\n                    echoClient.Ping();\r\n                }\r\n            }\r\n\r\n            Console.WriteLine(stopwatch.Elapsed);\r\n        }\r\n    }\r\n}\r\n```\r\n\r\nThis example configures a `ThrottlingListener` to throttle connections at 10 Kb/s. Note that the listener requires another listener as the first parameter. All stock listeners follow this pattern, allowing you to chain multiple listeners together.\r\n\r\nThe data block transferred by the echo client is 1 Kb in size. With the bandwidth throttled to 10 Kb/s, the console output will show an elapsed time of roughly 10 seconds.\r\n\r\nTo implement connection throttling, the `ThrottlingListener` uses the `OperationContinuation` class. Whenever data is sent to or received from the client or server, the `IListener.DataReceived` method is called with the number of bytes received from either end, and the direction in which the data is flowing. This method expects an `OperationResult`. The `DataReceived` method can return one of three values:\r\n\r\n* `OperationResult.Continue`: Allow the data to be forwarded to the server or client;\r\n* `OperationResult.CloseClient`: Abort the connection. This is used by the `ChaosConnector` to simulate network errors;\r\n* `OperationContinuation.Result`: A pending result to be completed later.\r\n\r\nThe last option is used to allow the `OperationResult` outcome to be decided at a later time.\r\n\r\nThe `ThrottlingListener` uses this as follows:\r\n\r\n* A budget is calculated for how much data can be received within a specific timeframe. By default, this is calculated per 100 ms;\r\n* If within a timeframe the budget is exceeded, the `ThrottlingListener` will return `OperationContinuation.Result`'s, and store the `OperationContinuation` instances in a list;\r\n* Then, when a timer expires, the budget is reset and all stored `OperationContinuation`'s will have their outcome set, allowing the data to be forwarded to the server or client.\r\n\r\n### Delaying the outcome of an operation\r\n\r\nThe above example describes the working of the `OperationContinuation` class. The example below uses this to delay transfer for a set time:\r\n\r\n```cs\r\npublic static void DelayTransferEchoServer()\r\n{\r\n    using (var echoServer = new EchoServer(new IPEndPoint(IPAddress.Loopback, 0)))\r\n    {\r\n        echoServer.Start();\r\n\r\n        var listener = new FixedDelayListener(\r\n            SinkListener.Instance,\r\n            TimeSpan.FromSeconds(0.5)\r\n        );\r\n        var connector = new SimpleConnector(echoServer.EndPoint, listener);\r\n\r\n        using (var proxyServer = new ProxyServer(new IPEndPoint(IPAddress.Loopback, 0), connector))\r\n        {\r\n            proxyServer.Start();\r\n\r\n            var block = Encoding.UTF8.GetBytes(\"Hello world!\");\r\n\r\n            using (var echoClient = new EchoPingClient(proxyServer.EndPoint, block))\r\n            {\r\n                echoClient.Start();\r\n\r\n                var stopwatch = Stopwatch.StartNew();\r\n\r\n                echoClient.Ping();\r\n\r\n                Console.WriteLine(stopwatch.Elapsed);\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\npublic class FixedDelayListener : DelegatingListener\r\n{\r\n    private readonly TimeSpan delay;\r\n\r\n    public FixedDelayListener(IListener inner, TimeSpan delay)\r\n        : base(inner)\r\n    {\r\n        this.delay = delay;\r\n    }\r\n\r\n    public override OperationResult DataReceived(int bytesTransferred, Direction direction)\r\n    {\r\n        // Call the base listener and return that value if it's not continue.\r\n\r\n        var result = base.DataReceived(bytesTransferred, direction);\r\n        if (result.Outcome != OperationOutcome.Continue)\r\n            return result;\r\n\r\n        // Create an operation continuation and schedule it to be ran after\r\n        // some time.\r\n\r\n        var continuation = new OperationContinuation();\r\n\r\n        Task.Run(async () =\u003e\r\n        {\r\n            await Task.Delay(delay);\r\n\r\n            continuation.SetOutcome(OperationOutcome.Continue);\r\n        });\r\n\r\n        return continuation.Result;\r\n    }\r\n}\r\n```\r\n\r\nThe `FixedDelayListener` example above implements a listener that will delay every data transfer for some time.\r\n\r\nThis class inherits from `DelegatingListener`. This class implements some patterns to properly structure your listener and should be used as a base class for any listener.\r\n\r\nThe implementation of the `DataReceived` method does the following:\r\n\r\n* The base class implementation is called. If this returns a result with an outcome other than `OperationOutcome.Continue`, that result is returned immediately. This ensures that you play nice with any other configured listeners;\r\n* Then, an `OperationContinuation` is instantiated. The `Result` property provides you with an `OperationResult` that's configured to complete once you set the outcome of the `OperationContinuation`. This is returned from the method;\r\n* In parallel, a timer is started that will, after a configured time interval, sets the outcome of the `OperationContinuation` to `OperationOutcome.Continue`.\r\n\r\nIf you run this example, the console will show an elapsed time close to one second (twice the configured delay, since the data is echoed back from the server).\r\n\r\n### Simulation network errors\r\n\r\nThe `ChaosConnector` allows you to simulate network errors.\r\n\r\n```cs\r\npublic static void SimulateNetworkFailureEchoServer()\r\n{\r\n    using (var echoServer = new EchoServer(new IPEndPoint(IPAddress.Loopback, 0)))\r\n    {\r\n        echoServer.Start();\r\n\r\n        var configuration = new ChaosConfiguration\r\n        {\r\n            Reject =\r\n            {\r\n                Percentage = 0.5\r\n            }\r\n        };\r\n        var connector = new ChaosConnector(\r\n            configuration,\r\n            new SimpleConnector(echoServer.EndPoint, SinkListener.Instance)\r\n        );\r\n\r\n        using (var proxyServer = new ProxyServer(new IPEndPoint(IPAddress.Loopback, 0), connector))\r\n        {\r\n            proxyServer.Start();\r\n\r\n            var block = Encoding.UTF8.GetBytes(\"Hello world!\");\r\n\r\n            int errors = 0;\r\n\r\n            for (int i = 0; i \u003c 100; i++)\r\n            {\r\n                using (var echoClient = new EchoPingClient(proxyServer.EndPoint, block))\r\n                {\r\n                    echoClient.ExceptionOccured += (s, e) =\u003e Interlocked.Increment(ref errors);\r\n                    echoClient.Start();\r\n                    echoClient.Ping();\r\n                }\r\n            }\r\n\r\n            Console.WriteLine(errors);\r\n        }\r\n    }\r\n}\r\n```\r\n\r\nThe above example configures a `ChaosConnector` to simulate network failures. In this example, we just configure the `ChaosRejectConfiguration.Percentage` property, specifying what percentage of incoming connections we want to reject. With this configuration, the console will print a number close to 50. It won't print that exactly, because the `ChaosConnector` internally uses `Random` to make these decisions.\r\n\r\nThe `ChaosConfiguration` class has quite a few options to simulate network failure. See the [API documentation](https://pvginkel.github.io/FastProxy/html/T_FastProxy_Listeners_Chaos_ChaosConfiguration.htm) for more details.\r\n\r\nN.b. the name Chaos came from the [Chaos Monkey Netflix](https://github.com/Netflix/chaosmonkey) created. Basically the `ChaosConnector` allows you to have a simple Chaos Monkey embedded into your .NET application.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpvginkel%2Ffastproxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpvginkel%2Ffastproxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpvginkel%2Ffastproxy/lists"}