├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── WebSocketProxy.Sample
├── App.config
├── HelloWorldController.cs
├── Program.cs
├── Properties
│ └── AssemblyInfo.cs
├── Resources
│ └── Content
│ │ └── index.html
├── WebSocketProxy.Sample.csproj
└── packages.config
├── WebSocketProxy.sln
└── WebSocketProxy
├── Host.cs
├── HttpPacket.cs
├── HttpPacketBuilder.cs
├── Logger.cs
├── Properties
└── AssemblyInfo.cs
├── TcpConnectionManager.cs
├── TcpHost.cs
├── TcpProxyConfiguration.cs
├── TcpProxyServer.cs
├── TcpRoute.cs
├── TcpStateContainer.cs
├── WebSocketHeaders.cs
├── WebSocketProxy.csproj
└── WebSocketProxy.nuspec
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | *_i.c
39 | *_p.c
40 | *_i.h
41 | *.ilk
42 | *.meta
43 | *.obj
44 | *.pch
45 | *.pdb
46 | *.pgc
47 | *.pgd
48 | *.rsp
49 | *.sbr
50 | *.tlb
51 | *.tli
52 | *.tlh
53 | *.tmp
54 | *.tmp_proj
55 | *.log
56 | *.vspscc
57 | *.vssscc
58 | .builds
59 | *.pidb
60 | *.svclog
61 | *.scc
62 |
63 | # Visual Studio profiler
64 | *.psess
65 | *.vsp
66 | *.vspx
67 | *.sap
68 |
69 | # TFS 2012 Local Workspace
70 | $tf/
71 |
72 | # Guidance Automation Toolkit
73 | *.gpState
74 |
75 | # ReSharper is a .NET coding add-in
76 | _ReSharper*/
77 | *.[Rr]e[Ss]harper
78 | *.DotSettings.user
79 |
80 | # JustCode is a .NET coding add-in
81 | .JustCode
82 |
83 | # TeamCity is a build add-in
84 | _TeamCity*
85 |
86 | # DotCover is a Code Coverage Tool
87 | *.dotCover
88 |
89 | # NCrunch
90 | _NCrunch_*
91 | .*crunch*.local.xml
92 | nCrunchTemp_*
93 |
94 | # MightyMoose
95 | *.mm.*
96 | AutoTest.Net/
97 |
98 | # Web workbench (sass)
99 | .sass-cache/
100 |
101 | # Installshield output folder
102 | [Ee]xpress/
103 |
104 | # DocProject is a documentation generator add-in
105 | DocProject/buildhelp/
106 | DocProject/Help/*.HxT
107 | DocProject/Help/*.HxC
108 | DocProject/Help/*.hhc
109 | DocProject/Help/*.hhk
110 | DocProject/Help/*.hhp
111 | DocProject/Help/Html2
112 | DocProject/Help/html
113 |
114 | # Click-Once directory
115 | publish/
116 |
117 | # Publish Web Output
118 | *.[Pp]ublish.xml
119 | *.azurePubxml
120 | # TODO: Comment the next line if you want to checkin your web deploy settings
121 | # but database connection strings (with potential passwords) will be unencrypted
122 | *.pubxml
123 | *.publishproj
124 |
125 | # NuGet Packages
126 | *.nupkg
127 | # The packages folder can be ignored because of Package Restore
128 | **/packages/*
129 | # except build/, which is used as an MSBuild target.
130 | !**/packages/build/
131 | # Uncomment if necessary however generally it will be regenerated when needed
132 | #!**/packages/repositories.config
133 |
134 | # Windows Azure Build Output
135 | csx/
136 | *.build.csdef
137 |
138 | # Windows Store app package directory
139 | AppPackages/
140 |
141 | # Visual Studio cache files
142 | # files ending in .cache can be ignored
143 | *.[Cc]ache
144 | # but keep track of directories ending in .cache
145 | !*.[Cc]ache/
146 |
147 | # Others
148 | ClientBin/
149 | [Ss]tyle[Cc]op.*
150 | ~$*
151 | *~
152 | *.dbmdl
153 | *.dbproj.schemaview
154 | *.pfx
155 | *.publishsettings
156 | node_modules/
157 | orleans.codegen.cs
158 |
159 | # RIA/Silverlight projects
160 | Generated_Code/
161 |
162 | # Backup & report files from converting an old project file
163 | # to a newer Visual Studio version. Backup files are not needed,
164 | # because we have git ;-)
165 | _UpgradeReport_Files/
166 | Backup*/
167 | UpgradeLog*.XML
168 | UpgradeLog*.htm
169 |
170 | # SQL Server files
171 | *.mdf
172 | *.ldf
173 |
174 | # Business Intelligence projects
175 | *.rdl.data
176 | *.bim.layout
177 | *.bim_*.settings
178 |
179 | # Microsoft Fakes
180 | FakesAssemblies/
181 |
182 | # Node.js Tools for Visual Studio
183 | .ntvs_analysis.dat
184 |
185 | # Visual Studio 6 build log
186 | *.plg
187 |
188 | # Visual Studio 6 workspace options file
189 | *.opt
190 |
191 | # Visual Studio LightSwitch build output
192 | **/*.HTMLClient/GeneratedArtifacts
193 | **/*.DesktopClient/GeneratedArtifacts
194 | **/*.DesktopClient/ModelManifest.xml
195 | **/*.Server/GeneratedArtifacts
196 | **/*.Server/ModelManifest.xml
197 | _Pvt_Extensions
198 |
199 | # Application Specific
200 | WebSocketProxy.NuGetPackage
201 |
202 | # Ignore all bin and obj directories
203 | /**/bin/*
204 | /**/obj/*
205 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | solution: WebSocketProxy.sln
3 | sudo: required
4 | script:
5 | - xbuild /p:Configuration=Release WebSocketProxy.sln
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Life Emotions
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | WebSocketProxy  []()
2 | =======
3 |
4 | WebSocketProxy is a lightweight C# library which allows you to connect to an HTTP server and websocket server through the same TCP port.
5 | This library is independent on which web frameworks are used for handling websocket and http requests.
6 |
7 | # Installation
8 |
9 | Installation can be done using NuGet.
10 |
11 | ```
12 | Install-Package WebSocketProxy
13 | ```
14 |
15 | # Sample Usage
16 | The project "WebSocket.Sample" contains a sample usage using [NancyFX](https://github.com/NancyFx/Nancy) and [Fleck](https://github.com/statianzo/Fleck).
17 |
18 | The first step is to build the configuration object in which you set the endpoints that are listening for HTTP (Nancy) and WebSocket requests (Fleck) and the public endpoint which will be listening to both kinds of requests.
19 | ```csharp
20 | TcpProxyConfiguration configuration = new TcpProxyConfiguration()
21 | {
22 | PublicHost = new Host()
23 | {
24 | IpAddress = IPAddress.Parse("0.0.0.0"),
25 | Port = 8080
26 | },
27 | HttpHost = new Host()
28 | {
29 | IpAddress = IPAddress.Loopback,
30 | Port = 8081
31 | },
32 | WebSocketHost = new Host()
33 | {
34 |
35 | IpAddress = IPAddress.Loopback,
36 | Port = 8082
37 | }
38 | };
39 |
40 | ```
41 | Then, initialize Nancy and Fleck, followed by the WebSocketServer.
42 |
43 | ```csharp
44 | using (var nancyHost = new NancyHost(new Uri("http://localhost:8081")))
45 | using (var websocketServer = new WebSocketServer("ws://0.0.0.0:8082"))
46 | using (var tcpProxy = new TcpProxyServer(configuration))
47 | {
48 | // Initialize Nancy
49 | nancyHost.Start();
50 |
51 | // Initialize Fleck
52 | websocketServer.Start(connection =>
53 | {
54 | connection.OnOpen = () => Console.WriteLine("Connection on open");
55 | connection.OnClose = () => Console.WriteLine("Connection on close");
56 | connection.OnMessage = message => Console.WriteLine("Message: " + message);
57 | });
58 |
59 | // Initialize the proxy
60 | tcpProxy.Start();
61 |
62 | Console.WriteLine("Press [Enter] to stop");
63 | Console.ReadLine();
64 | }
65 | ```
66 | By pointing the web browser to the port 8080, you will receive an html page which initializes a websocket connection.
67 |
68 | # SSL Support
69 | This library supports HTTPS and WSS. Simply pass the certificate path and password in the configuration object and all incoming requests will be unencrypted and routed to the corresponding library.
70 |
71 |
--------------------------------------------------------------------------------
/WebSocketProxy.Sample/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/WebSocketProxy.Sample/HelloWorldController.cs:
--------------------------------------------------------------------------------
1 | using Nancy;
2 |
3 | namespace WebSocketProxy.Sample
4 | {
5 | public class HelloWorldController : NancyModule
6 | {
7 | public HelloWorldController()
8 | {
9 | Get["/"] = v => View["Content/index.html"];
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/WebSocketProxy.Sample/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using Fleck;
4 | using Nancy.Hosting.Self;
5 |
6 | namespace WebSocketProxy.Sample
7 | {
8 | class Program
9 | {
10 | static void Main(string[] args)
11 | {
12 | TcpProxyConfiguration configuration = new TcpProxyConfiguration()
13 | {
14 | PublicHost = new Host()
15 | {
16 | IpAddress = IPAddress.Parse("0.0.0.0"),
17 | Port = 8080
18 | },
19 | HttpHost = new Host()
20 | {
21 | IpAddress = IPAddress.Loopback,
22 | Port = 8081
23 | },
24 | WebSocketHost = new Host()
25 | {
26 |
27 | IpAddress = IPAddress.Loopback,
28 | Port = 8082
29 | }
30 | };
31 |
32 |
33 | using (var nancyHost = new NancyHost(new Uri("http://localhost:8081")))
34 | using (var websocketServer = new WebSocketServer("ws://0.0.0.0:8082"))
35 | using (var tcpProxy = new TcpProxyServer(configuration))
36 | {
37 | nancyHost.Start();
38 | websocketServer.Start(connection =>
39 | {
40 | connection.OnOpen = () => Console.WriteLine("COnnection on open");
41 | connection.OnClose = () => Console.WriteLine("Connection on close");
42 | connection.OnMessage = message => Console.WriteLine("Message: " + message);
43 | });
44 |
45 | tcpProxy.Start();
46 |
47 | Console.WriteLine("Press [Enter] to stop");
48 | Console.ReadLine();
49 | }
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/WebSocketProxy.Sample/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("WebSocketProxy.Sample")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("WebSocketProxy.Sample")]
13 | [assembly: AssemblyCopyright("Copyright © 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("dd602a26-f8ca-407c-ba30-7e1fd01c6ec7")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/WebSocketProxy.Sample/Resources/Content/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | WebSocketProxy Hello World
6 |
7 |
8 |
9 | This is the WebSocket Proxy Hello World Page
10 |
11 |
12 |
33 |
34 |
--------------------------------------------------------------------------------
/WebSocketProxy.Sample/WebSocketProxy.Sample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {DD602A26-F8CA-407C-BA30-7E1FD01C6EC7}
8 | Exe
9 | Properties
10 | WebSocketProxy.Sample
11 | WebSocketProxy.Sample
12 | v4.5
13 | 512
14 | true
15 |
16 |
17 |
18 | AnyCPU
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 |
27 |
28 | AnyCPU
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 |
36 |
37 | Always
38 |
39 |
40 |
41 | ..\packages\Fleck.0.13.0.57\lib\net40\Fleck.dll
42 | True
43 |
44 |
45 | ..\packages\Nancy.1.2.0\lib\net40\Nancy.dll
46 | True
47 |
48 |
49 | ..\packages\Nancy.Hosting.Self.1.2.0\lib\net40\Nancy.Hosting.Self.dll
50 | True
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | {BDA44986-B4D2-43E2-9486-CAC4F62EEB4F}
73 | WebSocketProxy
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
91 |
--------------------------------------------------------------------------------
/WebSocketProxy.Sample/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/WebSocketProxy.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.23107.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSocketProxy", "WebSocketProxy\WebSocketProxy.csproj", "{BDA44986-B4D2-43E2-9486-CAC4F62EEB4F}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSocketProxy.Sample", "WebSocketProxy.Sample\WebSocketProxy.Sample.csproj", "{DD602A26-F8CA-407C-BA30-7E1FD01C6EC7}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {BDA44986-B4D2-43E2-9486-CAC4F62EEB4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {BDA44986-B4D2-43E2-9486-CAC4F62EEB4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {BDA44986-B4D2-43E2-9486-CAC4F62EEB4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {BDA44986-B4D2-43E2-9486-CAC4F62EEB4F}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {DD602A26-F8CA-407C-BA30-7E1FD01C6EC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {DD602A26-F8CA-407C-BA30-7E1FD01C6EC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {DD602A26-F8CA-407C-BA30-7E1FD01C6EC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {DD602A26-F8CA-407C-BA30-7E1FD01C6EC7}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/WebSocketProxy/Host.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 |
3 | namespace WebSocketProxy
4 | {
5 | public class Host
6 | {
7 | public Host()
8 | {
9 | Port = -1;
10 | }
11 |
12 | public IPAddress IpAddress { get; set; }
13 |
14 | public int Port { get; set; }
15 |
16 | public bool IsSpecified
17 | {
18 | get
19 | {
20 | return Port != -1 && IpAddress != null;
21 | }
22 | }
23 |
24 | public override string ToString()
25 | {
26 | return IpAddress + ":" + Port;
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/WebSocketProxy/HttpPacket.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Text;
4 |
5 | namespace WebSocketProxy
6 | {
7 | ///
8 | /// Simplified HttpPacket representation
9 | ///
10 | internal class HttpPacket
11 | {
12 | private readonly IDictionary _headers;
13 |
14 | public HttpPacket()
15 | {
16 | _headers = new Dictionary();
17 | }
18 |
19 | public void AddHeader(string fieldName, string fieldValue)
20 | {
21 | if (string.IsNullOrWhiteSpace(fieldName) || string.IsNullOrWhiteSpace(fieldValue))
22 | return;
23 |
24 | // Http Headers can be repeated per RFC2616
25 | if (_headers.ContainsKey(fieldName))
26 | {
27 | _headers[fieldName] += ',' + fieldValue;
28 | }
29 | else
30 | {
31 | _headers.Add(fieldName, fieldValue);
32 | }
33 | }
34 |
35 | public string GetHeaderOrDefault(string fieldName, string defaultValue)
36 | {
37 | if (string.IsNullOrWhiteSpace(fieldName)) return defaultValue;
38 |
39 | string result;
40 | return !_headers.TryGetValue(fieldName, out result) ? defaultValue : result;
41 | }
42 |
43 | public bool IsWebSocketPacket
44 | {
45 | get { return _headers.Keys.Any(headerName => WebSocketHeaders.ClientHeaders.Contains(headerName.ToLowerInvariant().Trim())); }
46 | }
47 |
48 | public override string ToString()
49 | {
50 | StringBuilder sb = new StringBuilder();
51 | foreach (string key in _headers.Keys)
52 | {
53 | sb.Append(key);
54 | sb.Append(" : ");
55 | sb.Append(_headers[key]);
56 | sb.Append("\n");
57 | }
58 |
59 | return string.Format("HttpPacket: \n{0}\n", sb);
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/WebSocketProxy/HttpPacketBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace WebSocketProxy
4 | {
5 | internal class HttpPacketBuilder
6 | {
7 | public static HttpPacket BuildPacket(string data)
8 | {
9 | if (string.IsNullOrWhiteSpace(data))
10 | return null;
11 |
12 | try
13 | {
14 | HttpPacket result = new HttpPacket();
15 |
16 | string[] splitted = data.Split('\n');
17 |
18 | // There are no headers... nothing to do here...
19 | if (splitted.Length < 2)
20 | return null;
21 |
22 | for (int i = 1; i < splitted.Length; i++)
23 | {
24 | string header = splitted[i];
25 |
26 | // End of HTTP headers (newline)
27 | if (string.IsNullOrWhiteSpace(header))
28 | break;
29 |
30 | string[] splittedHeader = header.Split(':');
31 |
32 | if (splittedHeader.Length < 2)
33 | continue;
34 |
35 | // header name
36 | string fieldName = splittedHeader[0].Trim().ToLowerInvariant();
37 |
38 | // header value
39 | string value = string.Empty;
40 | for (int j = 1; j < splittedHeader.Length; j++)
41 | {
42 | value = value + splittedHeader[j];
43 | }
44 |
45 | result.AddHeader(fieldName, value.Trim());
46 | }
47 |
48 | return result;
49 |
50 | }
51 | catch (Exception)
52 | {
53 | return null;
54 | }
55 | }
56 |
57 | }
58 | }
--------------------------------------------------------------------------------
/WebSocketProxy/Logger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace WebSocketProxy
4 | {
5 | public class Logger
6 | {
7 | public Action Info { get; set; }
8 |
9 | public Action Error { get; set; }
10 |
11 | public Logger()
12 | {
13 | Info = (message) => Console.WriteLine("{0}:{1}", DateTime.Now, message);
14 | Error = (message, error) => Console.WriteLine("{0}:{1} - {2}",
15 | DateTime.Now, message, error != null ? error.Message : string.Empty);
16 | }
17 |
18 | public void Log(string message)
19 | {
20 | if (Info != null && !string.IsNullOrWhiteSpace(message))
21 | {
22 | Info(message);
23 | }
24 | }
25 |
26 | public void Warn(string message, Exception e)
27 | {
28 | if (Error != null && !string.IsNullOrWhiteSpace(message))
29 | {
30 | Error(message, e);
31 | }
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/WebSocketProxy/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyTitle("WebSocketProxy")]
8 | [assembly: AssemblyDescription("WebSocket Proxy acts as gateway for HTTP and WebSocket servers")]
9 | [assembly: AssemblyCompany("life.emotions")]
10 | [assembly: AssemblyProduct("SwaggerWcf")]
11 | [assembly: AssemblyCopyright("Copyright © 2015")]
12 | [assembly: AssemblyTrademark("")]
13 | [assembly: AssemblyCulture("")]
14 |
15 | #if DEBUG
16 | [assembly: AssemblyConfiguration("Debug")]
17 | #else
18 | [assembly: AssemblyConfiguration("Release")]
19 | #endif
20 |
21 | // Setting ComVisible to false makes the types in this assembly not visible
22 | // to COM components. If you need to access a type in this assembly from
23 | // COM, set the ComVisible attribute to true on that type.
24 | [assembly: ComVisible(false)]
25 |
26 | // The following GUID is for the ID of the typelib if this project is exposed to COM
27 | [assembly: Guid("54d57407-92ae-4f22-b327-65e4c79ae515")]
28 |
29 | // Version information for an assembly consists of the following four values:
30 | //
31 | // Major Version
32 | // Minor Version
33 | // Build Number
34 | // Revision
35 | //
36 | // You can specify all the values or you can default the Build and Revision Numbers
37 | // by using the '*' as shown below:
38 | // [assembly: AssemblyVersion("1.0.*")]
39 | [assembly: AssemblyVersion("1.0.2")]
40 | [assembly: AssemblyFileVersion("1.0.2")]
41 | [assembly: AssemblyInformationalVersion("1.0.2")]
42 |
--------------------------------------------------------------------------------
/WebSocketProxy/TcpConnectionManager.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace WebSocketProxy
4 | {
5 | internal class TcpConnectionManager
6 | {
7 | private readonly ICollection _connections;
8 |
9 | public TcpConnectionManager()
10 | {
11 | _connections = new List();
12 | }
13 |
14 | public int ConnectionCount
15 | {
16 | get
17 | {
18 | lock(_connections)
19 | {
20 | return _connections.Count;
21 | }
22 | }
23 | }
24 |
25 | public void AddRoute(TcpRoute route)
26 | {
27 | if (route == null || !route.Connected) return;
28 |
29 | lock (_connections)
30 | {
31 | route.Disconnected += route_Disconnected;
32 | _connections.Add(route);
33 |
34 | route.Start();
35 | }
36 | }
37 |
38 | void route_Disconnected(TcpRoute route)
39 | {
40 | if (route == null) return;
41 |
42 | lock (_connections)
43 | {
44 | route.Disconnected -= route_Disconnected;
45 | _connections.Remove(route);
46 | }
47 | }
48 |
49 | }
50 | }
--------------------------------------------------------------------------------
/WebSocketProxy/TcpHost.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Net;
4 | using System.Net.Security;
5 | using System.Net.Sockets;
6 | using System.Security.Authentication;
7 | using System.Security.Cryptography.X509Certificates;
8 | using System.Threading.Tasks;
9 |
10 | namespace WebSocketProxy
11 | {
12 | internal class TcpHost : IDisposable
13 | {
14 | private bool _closed;
15 |
16 | #region Events
17 |
18 | public delegate void DataAvailableDelegate(TcpHost host, byte[] data, int length);
19 |
20 | public event DataAvailableDelegate DataAvailable;
21 |
22 | protected void OnDataAvailable(byte[] data, int length)
23 | {
24 | if (DataAvailable != null)
25 | {
26 | DataAvailable(this, data, length);
27 | }
28 | }
29 |
30 | public delegate void DisconnectedDelegate(TcpHost host);
31 |
32 | public event DisconnectedDelegate Disconnected;
33 |
34 | protected void OnDisconnected()
35 | {
36 | if (Disconnected != null)
37 | {
38 | Disconnected(this);
39 | }
40 | }
41 |
42 | #endregion
43 |
44 | private const int DefaultBufferLength = 4 * 1024;
45 |
46 | private readonly TcpClient _tcpClient;
47 | private readonly byte[] _buffer;
48 |
49 | public Stream Stream { get; private set; }
50 |
51 | public bool Connected
52 | {
53 | get { return _tcpClient.Connected; }
54 | }
55 |
56 | #region Constructors
57 |
58 | public static TcpHost ManufactureDefault(IPAddress address, int port)
59 | {
60 | return new TcpHost(new TcpClient(address.ToString(), port));
61 | }
62 |
63 | public TcpHost(TcpClient tcpClient)
64 | {
65 | _buffer = new byte[DefaultBufferLength];
66 | _tcpClient = tcpClient;
67 | Stream = _tcpClient.GetStream();
68 | }
69 |
70 |
71 | #endregion
72 |
73 |
74 | public async Task AuthenticateAsync(X509Certificate2 certificate)
75 | {
76 | if (certificate == null)
77 | return;
78 |
79 | SslStream sslStream = new SslStream(Stream, false);
80 | Stream = sslStream;
81 | await sslStream.AuthenticateAsServerAsync(certificate);
82 | }
83 |
84 | public void BeginAuthenticationAsServer(X509Certificate2 certificate, SslProtocols protocols, AsyncCallback callback, object state)
85 | {
86 | SslStream sslStream = new SslStream(Stream, false);
87 | Stream = sslStream;
88 | sslStream.BeginAuthenticateAsServer(certificate, false, protocols, false, callback, state);
89 | }
90 |
91 | public void EndAuthenticationAsServer(IAsyncResult asyncResult)
92 | {
93 | SslStream sslStream = Stream as SslStream;
94 | if (sslStream != null)
95 | {
96 | sslStream.EndAuthenticateAsServer(asyncResult);
97 | }
98 |
99 | Stream = sslStream;
100 | }
101 |
102 | public void Send(byte[] data, int length)
103 | {
104 | try
105 | {
106 | if (!_closed)
107 | {
108 | Stream.Write(data, 0, length);
109 | }
110 |
111 | }
112 | catch (Exception)
113 | {
114 | OnDisconnected();
115 | }
116 |
117 | }
118 |
119 | public void StartReading()
120 | {
121 | try
122 | {
123 | if (!_closed)
124 | {
125 | Stream.BeginRead(_buffer, 0, _buffer.Length, ReadAsyncCallback, Stream);
126 | }
127 | }
128 | catch (Exception)
129 | {
130 | OnDisconnected();
131 | }
132 |
133 | }
134 |
135 | private void ReadAsyncCallback(IAsyncResult asyncResult)
136 | {
137 | if (_tcpClient.Connected && !_closed)
138 | {
139 | try
140 | {
141 | int numberOfBytesRead = Stream.EndRead(asyncResult);
142 |
143 | // No more data to read. Close the connection
144 | if (numberOfBytesRead > 0)
145 | {
146 | OnDataAvailable(_buffer, numberOfBytesRead);
147 | StartReading();
148 | }
149 | else
150 | {
151 | Close();
152 | OnDisconnected();
153 | }
154 |
155 |
156 | }
157 | catch (IOException)
158 | {
159 | OnDisconnected();
160 | }
161 | catch (ObjectDisposedException)
162 | {
163 | //
164 | }
165 | }
166 | else
167 | {
168 | OnDisconnected();
169 | }
170 | }
171 |
172 | public void Close()
173 | {
174 | try
175 | {
176 | if (_tcpClient == null || !_tcpClient.Connected) return;
177 |
178 | _closed = true;
179 | _tcpClient.Close();
180 | }
181 | catch (Exception)
182 | {
183 | //
184 | }
185 |
186 | }
187 |
188 | public void Dispose()
189 | {
190 | Close();
191 | }
192 | }
193 | }
--------------------------------------------------------------------------------
/WebSocketProxy/TcpProxyConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System.Security.Authentication;
2 |
3 | namespace WebSocketProxy
4 | {
5 | public class TcpProxyConfiguration
6 | {
7 | private const SslProtocols DefaultProtocols = SslProtocols.Ssl2 | SslProtocols.Ssl3 | SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
8 |
9 | #region Constructors
10 |
11 | public TcpProxyConfiguration() : this(DefaultProtocols)
12 | {
13 | //
14 | }
15 |
16 | public TcpProxyConfiguration(SslProtocols protocols)
17 | {
18 | SslProtocols = protocols;
19 | }
20 |
21 | #endregion
22 |
23 |
24 | ///
25 | /// Public interface in which the proxy will be listening
26 | ///
27 | public Host PublicHost { get; set; }
28 |
29 | ///
30 | /// Host which, when specified, will redirect the WebSocket packets
31 | ///
32 | public Host WebSocketHost { get; set; }
33 |
34 | ///
35 | /// Host which, when specified, will redirect plain HTTP packets
36 | ///
37 | public Host HttpHost { get; set; }
38 |
39 | ///
40 | /// If true, all packets will be decrypted using the SSL certificate specified in
41 | ///
42 | public bool EnableSslCertificate
43 | {
44 | get { return SslCertificatePath != null; }
45 | }
46 |
47 | ///
48 | /// FileSystem path to the SSL certificate
49 | ///
50 | public string SslCertificatePath { get; set; }
51 |
52 | ///
53 | /// Instalation Password of the certificate specified in
54 | ///
55 | public string SslCertificatePassword { get; set; }
56 |
57 | ///
58 | /// Enabled SSL protocols, in case EnableSslCertificate is set to true
59 | ///
60 | public SslProtocols SslProtocols { get; set; }
61 | }
62 | }
--------------------------------------------------------------------------------
/WebSocketProxy/TcpProxyServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Net.Sockets;
4 | using System.Security.Cryptography.X509Certificates;
5 | using System.Text;
6 |
7 | namespace WebSocketProxy
8 | {
9 | public class TcpProxyServer : IDisposable
10 | {
11 | private readonly TcpListener _tcpListener;
12 | private readonly TcpProxyConfiguration _configuration;
13 | private X509Certificate2 _certificate;
14 | private readonly TcpConnectionManager _tcpConnectionManager;
15 | private bool _closing;
16 |
17 | private readonly Logger _logger;
18 |
19 | public Logger Log
20 | {
21 | get { return _logger; }
22 | }
23 |
24 |
25 | public int ConnectionCount
26 | {
27 | get { return _tcpConnectionManager.ConnectionCount; }
28 | }
29 |
30 | #region Constructors
31 |
32 | public TcpProxyServer(TcpProxyConfiguration configuration)
33 | {
34 | _logger = new Logger();
35 | _tcpConnectionManager = new TcpConnectionManager();
36 | _configuration = configuration;
37 | _tcpListener = new TcpListener(_configuration.PublicHost.IpAddress, _configuration.PublicHost.Port);
38 | }
39 |
40 | #endregion
41 |
42 | public void Start()
43 | {
44 | if (_configuration.EnableSslCertificate)
45 | {
46 | _certificate = _configuration.SslCertificatePassword != null
47 | ? new X509Certificate2(_configuration.SslCertificatePath, _configuration.SslCertificatePassword)
48 | : new X509Certificate2(_configuration.SslCertificatePath);
49 | }
50 |
51 | _tcpListener.Start();
52 | DoBeginListenForClients();
53 |
54 | Log.Info(string.Format("Proxy Server started at {0}", _configuration.PublicHost));
55 | }
56 |
57 | private void DoBeginListenForClients()
58 | {
59 | try
60 | {
61 | _tcpListener.BeginAcceptTcpClient(TcpClientAcceptCallback, _tcpListener);
62 | }
63 | catch (InvalidOperationException)
64 | {
65 | // socket has been closed
66 | }
67 | catch (IOException)
68 | {
69 | //
70 | }
71 | }
72 |
73 | private void TcpClientAcceptCallback(IAsyncResult asyncResult)
74 | {
75 | if (_closing)
76 | return;
77 |
78 | DoBeginListenForClients();
79 |
80 | try
81 | {
82 | TcpClient client = _tcpListener.EndAcceptTcpClient(asyncResult);
83 |
84 | TcpHost host = new TcpHost(client);
85 | if (_certificate != null)
86 | {
87 | host.BeginAuthenticationAsServer(_certificate, _configuration.SslProtocols, AuthenticationCallback,
88 | host);
89 | }
90 | else
91 | {
92 | ReadFirstPacket(host);
93 | }
94 | }
95 | catch (IOException)
96 | {
97 | // TCP Listener may not be valid anymore
98 | }
99 | }
100 |
101 | private void AuthenticationCallback(IAsyncResult asyncResult)
102 | {
103 | if (asyncResult == null) return;
104 |
105 | TcpHost host = asyncResult.AsyncState as TcpHost;
106 | if (host == null)
107 | return;
108 |
109 | try
110 | {
111 | host.EndAuthenticationAsServer(asyncResult);
112 | ReadFirstPacket(host);
113 | }
114 | catch (IOException)
115 | {
116 | // Somehow, the authentication failed... Close the connection
117 | host.Close();
118 | }
119 | }
120 |
121 | private void ReadFirstPacket(TcpHost clientMachine)
122 | {
123 | byte[] data = new byte[4 * 1024];
124 |
125 | try
126 | {
127 | TcpStateContainer state = new TcpStateContainer(clientMachine, data);
128 | clientMachine.Stream.BeginRead(data, 0, data.Length, FirstPacketReadCallback, state);
129 | }
130 | catch (IOException)
131 | {
132 | clientMachine.Close();
133 | }
134 | }
135 |
136 | private void FirstPacketReadCallback(IAsyncResult asyncResult)
137 | {
138 | if (asyncResult == null) return;
139 |
140 | TcpStateContainer asyncState = asyncResult.AsyncState as TcpStateContainer;
141 | if (asyncState == null)
142 | return;
143 |
144 | TcpHost clientMachine = asyncState.Client;
145 | byte[] data = asyncState.DataBuffer;
146 |
147 | try
148 | {
149 | int numBytes = clientMachine.Stream.EndRead(asyncResult);
150 | string stringData = Encoding.UTF8.GetString(data, 0, numBytes);
151 |
152 | HttpPacket packet = HttpPacketBuilder.BuildPacket(stringData);
153 |
154 | if (packet == null)
155 | {
156 | clientMachine.Close();
157 | return;
158 | }
159 |
160 | Host serverMachineHost = packet.IsWebSocketPacket
161 | ? _configuration.WebSocketHost
162 | : _configuration.HttpHost;
163 |
164 | if (serverMachineHost != null && serverMachineHost.IsSpecified)
165 | {
166 | TcpHost serverMachine = TcpHost.ManufactureDefault(serverMachineHost.IpAddress,
167 | serverMachineHost.Port);
168 |
169 | serverMachine.Send(data, numBytes);
170 |
171 | TcpRoute route = new TcpRoute(clientMachine, serverMachine);
172 | _tcpConnectionManager.AddRoute(route);
173 | }
174 | else
175 | {
176 | clientMachine.Close();
177 | }
178 | }
179 | catch (IOException) {}
180 | }
181 |
182 | public void Dispose()
183 | {
184 | if (_tcpListener == null)
185 | return;
186 |
187 | _closing = true;
188 |
189 | try
190 | {
191 | _tcpListener.Stop();
192 | }
193 | catch (IOException)
194 | {
195 | // In case the listener is not listening anymore
196 | }
197 | }
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/WebSocketProxy/TcpRoute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace WebSocketProxy
4 | {
5 | internal class TcpRoute : IDisposable
6 | {
7 | private readonly TcpHost _clientMachine;
8 | private readonly TcpHost _serverMachine;
9 |
10 | #region Events
11 |
12 | public delegate void DisconnectedDelegate(TcpRoute route);
13 |
14 | public event DisconnectedDelegate Disconnected;
15 |
16 | protected void OnDisconnected()
17 | {
18 | if (Disconnected != null)
19 | {
20 | Disconnected(this);
21 | }
22 | }
23 |
24 | #endregion
25 |
26 | public bool Connected
27 | {
28 | get { return _clientMachine.Connected && _serverMachine.Connected; }
29 | }
30 |
31 | public TcpRoute(TcpHost clientMachine, TcpHost serverMachine)
32 | {
33 | _clientMachine = clientMachine;
34 | _serverMachine = serverMachine;
35 | }
36 |
37 | public void Start()
38 | {
39 | RegisterHostEvents();
40 | _clientMachine.StartReading();
41 | _serverMachine.StartReading();
42 | }
43 |
44 | void _serverMachine_DataAvailable(TcpHost host, byte[] data, int length)
45 | {
46 | _clientMachine.Send(data, length);
47 | }
48 |
49 | void _clientMachine_DataAvailable(TcpHost host, byte[] data, int length)
50 | {
51 | _serverMachine.Send(data, length);
52 | }
53 |
54 | void _serverMachine_Disconnected(TcpHost host)
55 | {
56 | Stop();
57 | }
58 |
59 | void _clientMachine_Disconnected(TcpHost host)
60 | {
61 | Stop();
62 | }
63 |
64 | private void RegisterHostEvents()
65 | {
66 | _clientMachine.Disconnected += _clientMachine_Disconnected;
67 | _serverMachine.Disconnected += _serverMachine_Disconnected;
68 |
69 | _clientMachine.DataAvailable += _clientMachine_DataAvailable;
70 | _serverMachine.DataAvailable += _serverMachine_DataAvailable;
71 | }
72 |
73 |
74 | public void Stop()
75 | {
76 | _clientMachine.Close();
77 | _serverMachine.Close();
78 |
79 | OnDisconnected();
80 | }
81 |
82 |
83 | public void Dispose()
84 | {
85 | Stop();
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/WebSocketProxy/TcpStateContainer.cs:
--------------------------------------------------------------------------------
1 | namespace WebSocketProxy
2 | {
3 | ///
4 | /// Helper class for managing the state in a Stream.BeginRead operation
5 | ///
6 | internal class TcpStateContainer
7 | {
8 | public TcpStateContainer(TcpHost client, byte[] dataBuffer)
9 | {
10 | Client = client;
11 | DataBuffer = dataBuffer;
12 | }
13 |
14 | public TcpHost Client { get; set; }
15 |
16 | public byte[] DataBuffer { get; set; }
17 | }
18 | }
--------------------------------------------------------------------------------
/WebSocketProxy/WebSocketHeaders.cs:
--------------------------------------------------------------------------------
1 | namespace WebSocketProxy
2 | {
3 | internal class WebSocketHeaders
4 | {
5 | public static readonly string[] ClientHeaders =
6 | {
7 | "sec-websocket-key",
8 | "sec-websocket-extensions",
9 | "sec-websocket-protocol",
10 | "sec-websocket-protocol"
11 | };
12 | }
13 | }
--------------------------------------------------------------------------------
/WebSocketProxy/WebSocketProxy.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {BDA44986-B4D2-43E2-9486-CAC4F62EEB4F}
8 | Library
9 | Properties
10 | WebSocketProxy
11 | WebSocketProxy
12 | v4.5
13 | 512
14 |
15 |
16 |
17 |
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 | false
26 | 5
27 |
28 |
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 | false
36 | 5
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
72 |
--------------------------------------------------------------------------------
/WebSocketProxy/WebSocketProxy.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $title$
5 | $version$
6 | $title$
7 | $author$
8 | $author$
9 | https://raw.githubusercontent.com/lifeemotions/websocketproxy/master/LICENSE
10 | https://github.com/lifeemotions/websocketproxy
11 | false
12 | $description$
13 | fix to avoid excepion when stopping
14 | Copyright © 2015
15 | .net websocket http ssl proxy
16 |
17 |
--------------------------------------------------------------------------------