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);
170 [ExpectedException (typeof (InvalidOperationException))]
171 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
172 public void SendAsyncArgTest_NotConnected ()
174 socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None);
177 [Test, ExpectedException (typeof (ArgumentNullException))]
178 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
179 public void SendAsyncArgTest_NoArray ()
181 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
182 socket.SendAsync (new ArraySegment<byte> (), WebSocketMessageType.Text, true, CancellationToken.None);
186 [ExpectedException (typeof (PlatformNotSupportedException))]
187 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
188 public void ReceiveAsyncArgTest_NotConnected ()
190 socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None);
193 [Test, ExpectedException (typeof (ArgumentNullException))]
194 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
195 public void ReceiveAsyncArgTest_NoArray ()
197 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
198 socket.ReceiveAsync (new ArraySegment<byte> (), CancellationToken.None);
202 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
203 public void ReceiveAsyncWrongState_Closed ()
206 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
207 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
208 Assert.IsTrue (socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Wait (5000));
209 } catch (AggregateException e) {
210 AssertWebSocketException (e, WebSocketError.Success);
213 Assert.Fail ("Should have thrown");
217 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
218 public void SendAsyncWrongState_Closed ()
221 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
222 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
223 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
224 } catch (AggregateException e) {
225 AssertWebSocketException (e, WebSocketError.Success);
228 Assert.Fail ("Should have thrown");
232 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
233 public void SendAsyncWrongState_CloseSent ()
236 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
237 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
238 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
239 } catch (AggregateException e) {
240 AssertWebSocketException (e, WebSocketError.Success);
243 Assert.Fail ("Should have thrown");
247 [Category ("NotWorking")] // FIXME: test relies on unimplemented HttpListenerContext.AcceptWebSocketAsync (), reenable it when the method is implemented
248 public void SendAsyncEndOfMessageTest ()
250 var cancellationToken = new CancellationTokenSource (TimeSpan.FromSeconds (30)).Token;
251 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Text, cancellationToken).Wait (5000);
252 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Text, cancellationToken).Wait (5000);
253 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
254 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
257 public async Task SendAsyncEndOfMessageTest (bool expectedEndOfMessage, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken)
259 using (var client = new ClientWebSocket ()) {
260 // Configure the listener.
261 var serverReceive = HandleHttpWebSocketRequestAsync<WebSocketReceiveResult> (async socket => await socket.ReceiveAsync (new ArraySegment<byte> (new byte[32]), cancellationToken), cancellationToken);
263 // Connect to the listener and make the request.
264 await client.ConnectAsync (new Uri ("ws://localhost:" + Port + "/"), cancellationToken);
265 await client.SendAsync (new ArraySegment<byte> (Encoding.UTF8.GetBytes ("test")), webSocketMessageType, expectedEndOfMessage, cancellationToken);
267 // Wait for the listener to handle the request and return its result.
268 var result = await serverReceive;
270 // Cleanup and check results.
271 await client.CloseAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
272 Assert.AreEqual (expectedEndOfMessage, result.EndOfMessage, "EndOfMessage should be " + expectedEndOfMessage);
276 async Task<T> HandleHttpWebSocketRequestAsync<T> (Func<WebSocket, Task<T>> action, CancellationToken cancellationToken)
278 var ctx = await this.listener.GetContextAsync ();
279 var wsContext = await ctx.AcceptWebSocketAsync (null);
280 var result = await action (wsContext.WebSocket);
281 await wsContext.WebSocket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
285 async Task HandleHttpRequestAsync (Action<HttpListenerRequest, HttpListenerResponse> handler)
287 var ctx = await listener.GetContextAsync ();
288 handler (ctx.Request, ctx.Response);
289 ctx.Response.Close ();
292 void AssertWebSocketException (AggregateException e, WebSocketError error, Type inner = null)
294 var wsEx = e.InnerException as WebSocketException;
295 Console.WriteLine (e.InnerException.ToString ());
296 Assert.IsNotNull (wsEx, "Not a websocketexception");
297 Assert.AreEqual (error, wsEx.WebSocketErrorCode);
299 Assert.IsNotNull (wsEx.InnerException);
300 Assert.IsTrue (inner.IsInstanceOfType (wsEx.InnerException));
304 void ForceSetHeader (WebHeaderCollection headers, string name, string value)
306 if (headerSetMethod == null)
307 headerSetMethod = typeof (WebHeaderCollection).GetMethod ("AddValue", BindingFlags.NonPublic);
308 headerSetMethod.Invoke (headers, new[] { name, value });