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 if (!socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000)) {
163 Assert.Inconclusive (socket.State.ToString ());
167 Assert.AreEqual (WebSocketState.Open, socket.State);
169 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
170 Assert.AreEqual (WebSocketState.Closed, socket.State);
174 [ExpectedException (typeof (InvalidOperationException))]
175 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
176 public void SendAsyncArgTest_NotConnected ()
178 socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None);
181 [Test, ExpectedException (typeof (ArgumentNullException))]
182 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
183 public void SendAsyncArgTest_NoArray ()
185 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
186 socket.SendAsync (new ArraySegment<byte> (), WebSocketMessageType.Text, true, CancellationToken.None);
190 [ExpectedException (typeof (InvalidOperationException))]
191 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
192 public void ReceiveAsyncArgTest_NotConnected ()
194 socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None);
197 [Test, ExpectedException (typeof (ArgumentNullException))]
198 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
199 public void ReceiveAsyncArgTest_NoArray ()
201 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
202 socket.ReceiveAsync (new ArraySegment<byte> (), CancellationToken.None);
206 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
207 public void ReceiveAsyncWrongState_Closed ()
210 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
211 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
212 Assert.IsTrue (socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Wait (5000));
213 } catch (AggregateException e) {
214 AssertWebSocketException (e, WebSocketError.Success);
217 Assert.Fail ("Should have thrown");
221 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
222 public void SendAsyncWrongState_Closed ()
225 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
226 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
227 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
228 } catch (AggregateException e) {
229 AssertWebSocketException (e, WebSocketError.Success);
232 Assert.Fail ("Should have thrown");
236 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
237 public void SendAsyncWrongState_CloseSent ()
240 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
241 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
242 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
243 } catch (AggregateException e) {
244 AssertWebSocketException (e, WebSocketError.Success);
247 Assert.Fail ("Should have thrown");
251 [Category ("NotWorking")] // FIXME: test relies on unimplemented HttpListenerContext.AcceptWebSocketAsync (), reenable it when the method is implemented
252 public void SendAsyncEndOfMessageTest ()
254 var cancellationToken = new CancellationTokenSource (TimeSpan.FromSeconds (30)).Token;
255 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Text, cancellationToken).Wait (5000);
256 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Text, cancellationToken).Wait (5000);
257 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
258 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
261 public async Task SendAsyncEndOfMessageTest (bool expectedEndOfMessage, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken)
263 using (var client = new ClientWebSocket ()) {
264 // Configure the listener.
265 var serverReceive = HandleHttpWebSocketRequestAsync<WebSocketReceiveResult> (async socket => await socket.ReceiveAsync (new ArraySegment<byte> (new byte[32]), cancellationToken), cancellationToken);
267 // Connect to the listener and make the request.
268 await client.ConnectAsync (new Uri ("ws://localhost:" + Port + "/"), cancellationToken);
269 await client.SendAsync (new ArraySegment<byte> (Encoding.UTF8.GetBytes ("test")), webSocketMessageType, expectedEndOfMessage, cancellationToken);
271 // Wait for the listener to handle the request and return its result.
272 var result = await serverReceive;
274 // Cleanup and check results.
275 await client.CloseAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
276 Assert.AreEqual (expectedEndOfMessage, result.EndOfMessage, "EndOfMessage should be " + expectedEndOfMessage);
280 async Task<T> HandleHttpWebSocketRequestAsync<T> (Func<WebSocket, Task<T>> action, CancellationToken cancellationToken)
282 var ctx = await this.listener.GetContextAsync ();
283 var wsContext = await ctx.AcceptWebSocketAsync (null);
284 var result = await action (wsContext.WebSocket);
285 await wsContext.WebSocket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
289 async Task HandleHttpRequestAsync (Action<HttpListenerRequest, HttpListenerResponse> handler)
291 var ctx = await listener.GetContextAsync ();
292 handler (ctx.Request, ctx.Response);
293 ctx.Response.Close ();
296 void AssertWebSocketException (AggregateException e, WebSocketError error, Type inner = null)
298 var wsEx = e.InnerException as WebSocketException;
299 Console.WriteLine (e.InnerException.ToString ());
300 Assert.IsNotNull (wsEx, "Not a websocketexception");
301 Assert.AreEqual (error, wsEx.WebSocketErrorCode);
303 Assert.IsNotNull (wsEx.InnerException);
304 Assert.IsTrue (inner.IsInstanceOfType (wsEx.InnerException));
308 void ForceSetHeader (WebHeaderCollection headers, string name, string value)
310 if (headerSetMethod == null)
311 headerSetMethod = typeof (WebHeaderCollection).GetMethod ("AddValue", BindingFlags.NonPublic);
312 headerSetMethod.Invoke (headers, new[] { name, value });