3 using System.Threading;
4 using System.Threading.Tasks;
5 using System.Collections.Generic;
6 using System.Net.WebSockets;
7 using System.Reflection;
10 using NUnit.Framework;
12 using MonoTests.Helpers;
14 namespace MonoTests.System.Net.WebSockets
17 public class ClientWebSocketTest
19 const string EchoServerUrl = "ws://corefx-net.cloudapp.net/WebSocket/EchoWebSocket.ashx";
21 ClientWebSocket socket;
22 MethodInfo headerSetMethod;
28 socket = new ClientWebSocket ();
29 Port = NetworkHelpers.FindFreePort ();
32 HttpListener _listener;
33 HttpListener listener {
35 if (_listener != null)
38 var tmp = new HttpListener ();
39 tmp.Prefixes.Add ("http://localhost:" + Port + "/");
41 return _listener = tmp;
46 public void Teardown ()
48 if (_listener != null) {
53 if (socket.State == WebSocketState.Open)
54 socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (2000);
60 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
61 public void ServerHandshakeReturnCrapStatusCodeTest ()
64 #pragma warning disable 4014
65 HandleHttpRequestAsync ((req, resp) => resp.StatusCode = 418);
66 #pragma warning restore 4014
68 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
69 } catch (AggregateException e) {
70 AssertWebSocketException (e, WebSocketError.Success, typeof (WebException));
73 Assert.Fail ("Should have thrown");
77 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
78 public void ServerHandshakeReturnWrongUpgradeHeader ()
80 #pragma warning disable 4014
81 HandleHttpRequestAsync ((req, resp) => {
82 resp.StatusCode = 101;
83 resp.Headers["Upgrade"] = "gtfo";
85 #pragma warning restore 4014
87 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
88 } catch (AggregateException e) {
89 AssertWebSocketException (e, WebSocketError.Success);
92 Assert.Fail ("Should have thrown");
96 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
97 public void ServerHandshakeReturnWrongConnectionHeader ()
99 #pragma warning disable 4014
100 HandleHttpRequestAsync ((req, resp) => {
101 resp.StatusCode = 101;
102 resp.Headers["Upgrade"] = "websocket";
103 // Mono http request doesn't like the forcing, test still valid since the default connection header value is empty
104 //ForceSetHeader (resp.Headers, "Connection", "Foo");
106 #pragma warning restore 4014
108 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
109 } catch (AggregateException e) {
110 AssertWebSocketException (e, WebSocketError.Success);
113 Assert.Fail ("Should have thrown");
117 [Category ("MobileNotWorking")] // The test hangs when ran as part of the entire BCL test suite. Works when only this fixture is ran
118 public void EchoTest ()
120 const string Payload = "This is a websocket test";
122 Assert.AreEqual (WebSocketState.None, socket.State);
123 socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait ();
124 Assert.AreEqual (WebSocketState.Open, socket.State);
126 var sendBuffer = Encoding.ASCII.GetBytes (Payload);
127 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
129 var receiveBuffer = new byte[Payload.Length];
130 var resp = socket.ReceiveAsync (new ArraySegment<byte> (receiveBuffer), CancellationToken.None).Result;
132 Assert.AreEqual (Payload.Length, resp.Count);
133 Assert.IsTrue (resp.EndOfMessage);
134 Assert.AreEqual (WebSocketMessageType.Text, resp.MessageType);
135 Assert.AreEqual (Payload, Encoding.ASCII.GetString (receiveBuffer, 0, resp.Count));
137 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
138 Assert.AreEqual (WebSocketState.Closed, socket.State);
142 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
143 public void CloseOutputAsyncTest ()
145 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
146 Assert.AreEqual (WebSocketState.Open, socket.State);
148 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
149 Assert.AreEqual (WebSocketState.CloseSent, socket.State);
151 var resp = socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Result;
152 Assert.AreEqual (WebSocketState.Closed, socket.State);
153 Assert.AreEqual (WebSocketMessageType.Close, resp.MessageType);
154 Assert.AreEqual (WebSocketCloseStatus.NormalClosure, resp.CloseStatus);
155 Assert.AreEqual (string.Empty, resp.CloseStatusDescription);
159 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
160 public void CloseAsyncTest ()
162 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
163 Assert.AreEqual (WebSocketState.Open, socket.State);
165 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
166 Assert.AreEqual (WebSocketState.Closed, socket.State);
169 [Test, ExpectedException (typeof (InvalidOperationException))]
170 public void SendAsyncArgTest_NotConnected ()
172 socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None);
175 [Test, ExpectedException (typeof (ArgumentNullException))]
176 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
177 public void SendAsyncArgTest_NoArray ()
179 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
180 socket.SendAsync (new ArraySegment<byte> (), WebSocketMessageType.Text, true, CancellationToken.None);
183 [Test, ExpectedException (typeof (InvalidOperationException))]
184 public void ReceiveAsyncArgTest_NotConnected ()
186 socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None);
189 [Test, ExpectedException (typeof (ArgumentNullException))]
190 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
191 public void ReceiveAsyncArgTest_NoArray ()
193 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
194 socket.ReceiveAsync (new ArraySegment<byte> (), CancellationToken.None);
198 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
199 public void ReceiveAsyncWrongState_Closed ()
202 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
203 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
204 Assert.IsTrue (socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Wait (5000));
205 } catch (AggregateException e) {
206 AssertWebSocketException (e, WebSocketError.Success);
209 Assert.Fail ("Should have thrown");
213 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
214 public void SendAsyncWrongState_Closed ()
217 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
218 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
219 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
220 } catch (AggregateException e) {
221 AssertWebSocketException (e, WebSocketError.Success);
224 Assert.Fail ("Should have thrown");
228 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
229 public void SendAsyncWrongState_CloseSent ()
232 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
233 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
234 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
235 } catch (AggregateException e) {
236 AssertWebSocketException (e, WebSocketError.Success);
239 Assert.Fail ("Should have thrown");
243 [Category ("NotWorking")] // FIXME: test relies on unimplemented HttpListenerContext.AcceptWebSocketAsync (), reenable it when the method is implemented
244 public void SendAsyncEndOfMessageTest ()
246 var cancellationToken = new CancellationTokenSource (TimeSpan.FromSeconds (30)).Token;
247 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Text, cancellationToken).Wait (5000);
248 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Text, cancellationToken).Wait (5000);
249 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
250 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
253 public async Task SendAsyncEndOfMessageTest (bool expectedEndOfMessage, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken)
255 using (var client = new ClientWebSocket ()) {
256 // Configure the listener.
257 var serverReceive = HandleHttpWebSocketRequestAsync<WebSocketReceiveResult> (async socket => await socket.ReceiveAsync (new ArraySegment<byte> (new byte[32]), cancellationToken), cancellationToken);
259 // Connect to the listener and make the request.
260 await client.ConnectAsync (new Uri ("ws://localhost:" + Port + "/"), cancellationToken);
261 await client.SendAsync (new ArraySegment<byte> (Encoding.UTF8.GetBytes ("test")), webSocketMessageType, expectedEndOfMessage, cancellationToken);
263 // Wait for the listener to handle the request and return its result.
264 var result = await serverReceive;
266 // Cleanup and check results.
267 await client.CloseAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
268 Assert.AreEqual (expectedEndOfMessage, result.EndOfMessage, "EndOfMessage should be " + expectedEndOfMessage);
272 async Task<T> HandleHttpWebSocketRequestAsync<T> (Func<WebSocket, Task<T>> action, CancellationToken cancellationToken)
274 var ctx = await this.listener.GetContextAsync ();
275 var wsContext = await ctx.AcceptWebSocketAsync (null);
276 var result = await action (wsContext.WebSocket);
277 await wsContext.WebSocket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
281 async Task HandleHttpRequestAsync (Action<HttpListenerRequest, HttpListenerResponse> handler)
283 var ctx = await listener.GetContextAsync ();
284 handler (ctx.Request, ctx.Response);
285 ctx.Response.Close ();
288 void AssertWebSocketException (AggregateException e, WebSocketError error, Type inner = null)
290 var wsEx = e.InnerException as WebSocketException;
291 Console.WriteLine (e.InnerException.ToString ());
292 Assert.IsNotNull (wsEx, "Not a websocketexception");
293 Assert.AreEqual (error, wsEx.WebSocketErrorCode);
295 Assert.IsNotNull (wsEx.InnerException);
296 Assert.IsTrue (inner.IsInstanceOfType (wsEx.InnerException));
300 void ForceSetHeader (WebHeaderCollection headers, string name, string value)
302 if (headerSetMethod == null)
303 headerSetMethod = typeof (WebHeaderCollection).GetMethod ("AddValue", BindingFlags.NonPublic);
304 headerSetMethod.Invoke (headers, new[] { name, value });