5 // Jérémie Laval <jeremie dot laval at xamarin dot com>
7 // Copyright 2013 Xamarin Inc (http://www.xamarin.com).
9 // Lightly inspired from WebSocket4Net distributed under the Apache License 2.0
11 // Permission is hereby granted, free of charge, to any person obtaining a copy
12 // of this software and associated documentation files (the "Software"), to deal
13 // in the Software without restriction, including without limitation the rights
14 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 // copies of the Software, and to permit persons to whom the Software is
16 // furnished to do so, subject to the following conditions:
18 // The above copyright notice and this permission notice shall be included in
19 // all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
33 using System.Net.Sockets;
34 using System.Security.Principal;
35 using System.Security.Cryptography.X509Certificates;
36 using System.Runtime.CompilerServices;
37 using System.Collections.Generic;
38 using System.Threading;
39 using System.Threading.Tasks;
40 using System.Globalization;
42 using System.Security.Cryptography;
44 namespace System.Net.WebSockets
46 public class ClientWebSocket : WebSocket, IDisposable
48 const string VersionTag = "13";
50 ClientWebSocketOptions options;
55 WebConnection connection;
56 StreamWebSocket internalWebSocket;
58 public ClientWebSocket ()
60 options = new ClientWebSocketOptions ();
61 state = WebSocketState.None;
64 public override void Dispose ()
66 if (internalWebSocket != null)
67 internalWebSocket.Dispose ();
70 public override void Abort ()
72 if (internalWebSocket != null)
73 internalWebSocket.Abort ();
76 public ClientWebSocketOptions Options {
82 public override WebSocketState State {
84 if (internalWebSocket != null)
85 return internalWebSocket.State;
90 public override WebSocketCloseStatus? CloseStatus {
92 if (internalWebSocket != null)
93 return internalWebSocket.CloseStatus;
94 if (state != WebSocketState.Closed)
95 return (WebSocketCloseStatus?)null;
96 return WebSocketCloseStatus.Empty;
101 public override string CloseStatusDescription {
105 public override string SubProtocol {
106 get { return subProtocol; }
109 public async Task ConnectAsync (Uri uri, CancellationToken cancellationToken)
111 state = WebSocketState.Connecting;
112 var httpUri = new UriBuilder (uri);
113 if (uri.Scheme == "wss")
114 httpUri.Scheme = "https";
116 httpUri.Scheme = "http";
117 req = (HttpWebRequest)WebRequest.Create (httpUri.Uri);
118 req.ReuseConnection = true;
119 if (options.Cookies != null)
120 req.CookieContainer = options.Cookies;
122 if (options.CustomRequestHeaders.Count > 0) {
123 foreach (var header in options.CustomRequestHeaders)
124 req.Headers[header.Key] = header.Value;
127 var secKey = Convert.ToBase64String (Encoding.ASCII.GetBytes (Guid.NewGuid ().ToString ().Substring (0, 16)));
128 string expectedAccept = StreamWebSocket.CreateAcceptKey (secKey);
130 req.Headers["Upgrade"] = "WebSocket";
131 req.Headers["Sec-WebSocket-Version"] = VersionTag;
132 req.Headers["Sec-WebSocket-Key"] = secKey;
133 req.Headers["Sec-WebSocket-Origin"] = uri.Host;
134 if (options.SubProtocols.Count > 0)
135 req.Headers["Sec-WebSocket-Protocol"] = string.Join (",", options.SubProtocols);
137 if (options.Credentials != null)
138 req.Credentials = options.Credentials;
139 if (options.ClientCertificates != null)
140 req.ClientCertificates = options.ClientCertificates;
141 if (options.Proxy != null)
142 req.Proxy = options.Proxy;
143 req.UseDefaultCredentials = options.UseDefaultCredentials;
144 req.Connection = "Upgrade";
146 HttpWebResponse resp = null;
148 resp = (HttpWebResponse)(await req.GetResponseAsync ().ConfigureAwait (false));
149 } catch (Exception e) {
150 throw new WebSocketException (WebSocketError.Success, e);
153 if (resp.StatusCode != HttpStatusCode.SwitchingProtocols)
154 throw new WebSocketException ("The server returned status code '" + (int)resp.StatusCode + "' when status code '101' was expected");
155 if (!string.Equals (resp.Headers["Upgrade"], "WebSocket", StringComparison.OrdinalIgnoreCase)
156 || !string.Equals (resp.Headers["Connection"], "Upgrade", StringComparison.OrdinalIgnoreCase)
157 || !string.Equals (resp.Headers["Sec-WebSocket-Accept"], expectedAccept))
158 throw new WebSocketException ("HTTP header error during handshake");
159 if (resp.Headers["Sec-WebSocket-Protocol"] != null) {
160 if (!options.SubProtocols.Contains (resp.Headers["Sec-WebSocket-Protocol"]))
161 throw new WebSocketException (WebSocketError.UnsupportedProtocol);
162 subProtocol = resp.Headers["Sec-WebSocket-Protocol"];
164 state = WebSocketState.Open;
165 connection = req.StoredConnection;
166 internalWebSocket = new StreamWebSocket(connection.nstream, connection.nstream, connection.socket, subProtocol, true, new ArraySegment<byte>(new byte[0]));
169 public override Task SendAsync (ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
171 return internalWebSocket.SendAsync (buffer, messageType, endOfMessage, cancellationToken);
174 public override Task<WebSocketReceiveResult> ReceiveAsync (ArraySegment<byte> buffer, CancellationToken cancellationToken)
176 return internalWebSocket.ReceiveAsync (buffer, cancellationToken);
179 // The damn difference between those two methods is that CloseAsync will wait for server acknowledgement before completing
180 // while CloseOutputAsync will send the close packet and simply complete.
182 public override Task CloseAsync (WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
184 return internalWebSocket.CloseAsync (closeStatus, statusDescription, cancellationToken);
187 public override Task CloseOutputAsync (WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
189 return internalWebSocket.CloseOutputAsync (closeStatus, statusDescription, cancellationToken);