4 using System.Threading;
5 using System.Threading.Tasks;
6 using System.Collections.Generic;
7 using System.Net.WebSockets;
8 using System.Reflection;
11 using NUnit.Framework;
13 using MonoTests.Helpers;
15 namespace MonoTests.System.Net.WebSockets
18 public class ClientWebSocketTest
20 const string EchoServerUrl = "ws://corefx-net.cloudapp.net/WebSocket/EchoWebSocket.ashx";
21 int Port = NetworkHelpers.FindFreePort ();
22 HttpListener listener;
23 ClientWebSocket socket;
24 MethodInfo headerSetMethod;
29 listener = new HttpListener ();
30 listener.Prefixes.Add ("http://localhost:" + Port + "/");
32 socket = new ClientWebSocket ();
36 public void Teardown ()
38 if (listener != null) {
43 if (socket.State == WebSocketState.Open)
44 socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (2000);
51 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
52 public void ServerHandshakeReturnCrapStatusCodeTest ()
55 #pragma warning disable 4014
56 HandleHttpRequestAsync ((req, resp) => resp.StatusCode = 418);
57 #pragma warning restore 4014
59 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
60 } catch (AggregateException e) {
61 AssertWebSocketException (e, WebSocketError.Success, typeof (WebException));
64 Assert.Fail ("Should have thrown");
68 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
69 public void ServerHandshakeReturnWrongUpgradeHeader ()
71 #pragma warning disable 4014
72 HandleHttpRequestAsync ((req, resp) => {
73 resp.StatusCode = 101;
74 resp.Headers["Upgrade"] = "gtfo";
76 #pragma warning restore 4014
78 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
79 } catch (AggregateException e) {
80 AssertWebSocketException (e, WebSocketError.Success);
83 Assert.Fail ("Should have thrown");
87 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
88 public void ServerHandshakeReturnWrongConnectionHeader ()
90 #pragma warning disable 4014
91 HandleHttpRequestAsync ((req, resp) => {
92 resp.StatusCode = 101;
93 resp.Headers["Upgrade"] = "websocket";
94 // Mono http request doesn't like the forcing, test still valid since the default connection header value is empty
95 //ForceSetHeader (resp.Headers, "Connection", "Foo");
97 #pragma warning restore 4014
99 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
100 } catch (AggregateException e) {
101 AssertWebSocketException (e, WebSocketError.Success);
104 Assert.Fail ("Should have thrown");
108 [Category ("MobileNotWorking")] // The test hangs when ran as part of the entire BCL test suite. Works when only this fixture is ran
109 public void EchoTest ()
111 const string Payload = "This is a websocket test";
113 Assert.AreEqual (WebSocketState.None, socket.State);
114 socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait ();
115 Assert.AreEqual (WebSocketState.Open, socket.State);
117 var sendBuffer = Encoding.ASCII.GetBytes (Payload);
118 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
120 var receiveBuffer = new byte[Payload.Length];
121 var resp = socket.ReceiveAsync (new ArraySegment<byte> (receiveBuffer), CancellationToken.None).Result;
123 Assert.AreEqual (Payload.Length, resp.Count);
124 Assert.IsTrue (resp.EndOfMessage);
125 Assert.AreEqual (WebSocketMessageType.Text, resp.MessageType);
126 Assert.AreEqual (Payload, Encoding.ASCII.GetString (receiveBuffer, 0, resp.Count));
128 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
129 Assert.AreEqual (WebSocketState.Closed, socket.State);
133 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
134 public void CloseOutputAsyncTest ()
136 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
137 Assert.AreEqual (WebSocketState.Open, socket.State);
139 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
140 Assert.AreEqual (WebSocketState.CloseSent, socket.State);
142 var resp = socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Result;
143 Assert.AreEqual (WebSocketState.Closed, socket.State);
144 Assert.AreEqual (WebSocketMessageType.Close, resp.MessageType);
145 Assert.AreEqual (WebSocketCloseStatus.NormalClosure, resp.CloseStatus);
146 Assert.AreEqual (string.Empty, resp.CloseStatusDescription);
150 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
151 public void CloseAsyncTest ()
153 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
154 Assert.AreEqual (WebSocketState.Open, socket.State);
156 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
157 Assert.AreEqual (WebSocketState.Closed, socket.State);
160 [Test, ExpectedException (typeof (InvalidOperationException))]
161 public void SendAsyncArgTest_NotConnected ()
163 socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None);
166 [Test, ExpectedException (typeof (ArgumentNullException))]
167 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
168 public void SendAsyncArgTest_NoArray ()
170 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
171 socket.SendAsync (new ArraySegment<byte> (), WebSocketMessageType.Text, true, CancellationToken.None);
174 [Test, ExpectedException (typeof (InvalidOperationException))]
175 public void ReceiveAsyncArgTest_NotConnected ()
177 socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None);
180 [Test, ExpectedException (typeof (ArgumentNullException))]
181 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
182 public void ReceiveAsyncArgTest_NoArray ()
184 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
185 socket.ReceiveAsync (new ArraySegment<byte> (), CancellationToken.None);
189 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
190 public void ReceiveAsyncWrongState_Closed ()
193 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
194 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
195 Assert.IsTrue (socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Wait (5000));
196 } catch (AggregateException e) {
197 AssertWebSocketException (e, WebSocketError.Success);
200 Assert.Fail ("Should have thrown");
204 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
205 public void SendAsyncWrongState_Closed ()
208 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
209 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
210 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
211 } catch (AggregateException e) {
212 AssertWebSocketException (e, WebSocketError.Success);
215 Assert.Fail ("Should have thrown");
219 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
220 public void SendAsyncWrongState_CloseSent ()
223 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
224 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
225 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
226 } catch (AggregateException e) {
227 AssertWebSocketException (e, WebSocketError.Success);
230 Assert.Fail ("Should have thrown");
234 [Category ("NotWorking")] // FIXME: test relies on unimplemented HttpListenerContext.AcceptWebSocketAsync (), reenable it when the method is implemented
235 public void SendAsyncEndOfMessageTest ()
237 var cancellationToken = new CancellationTokenSource (TimeSpan.FromSeconds (30)).Token;
238 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Text, cancellationToken).Wait (5000);
239 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Text, cancellationToken).Wait (5000);
240 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
241 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
244 public async Task SendAsyncEndOfMessageTest (bool expectedEndOfMessage, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken)
246 using (var client = new ClientWebSocket ()) {
247 // Configure the listener.
248 var serverReceive = HandleHttpWebSocketRequestAsync<WebSocketReceiveResult> (async socket => await socket.ReceiveAsync (new ArraySegment<byte> (new byte[32]), cancellationToken), cancellationToken);
250 // Connect to the listener and make the request.
251 await client.ConnectAsync (new Uri ("ws://localhost:" + Port + "/"), cancellationToken);
252 await client.SendAsync (new ArraySegment<byte> (Encoding.UTF8.GetBytes ("test")), webSocketMessageType, expectedEndOfMessage, cancellationToken);
254 // Wait for the listener to handle the request and return its result.
255 var result = await serverReceive;
257 // Cleanup and check results.
258 await client.CloseAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
259 Assert.AreEqual (expectedEndOfMessage, result.EndOfMessage, "EndOfMessage should be " + expectedEndOfMessage);
263 async Task<T> HandleHttpWebSocketRequestAsync<T> (Func<WebSocket, Task<T>> action, CancellationToken cancellationToken)
265 var ctx = await this.listener.GetContextAsync ();
266 var wsContext = await ctx.AcceptWebSocketAsync (null);
267 var result = await action (wsContext.WebSocket);
268 await wsContext.WebSocket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
272 async Task HandleHttpRequestAsync (Action<HttpListenerRequest, HttpListenerResponse> handler)
274 var ctx = await listener.GetContextAsync ();
275 handler (ctx.Request, ctx.Response);
276 ctx.Response.Close ();
279 void AssertWebSocketException (AggregateException e, WebSocketError error, Type inner = null)
281 var wsEx = e.InnerException as WebSocketException;
282 Console.WriteLine (e.InnerException.ToString ());
283 Assert.IsNotNull (wsEx, "Not a websocketexception");
284 Assert.AreEqual (error, wsEx.WebSocketErrorCode);
286 Assert.IsNotNull (wsEx.InnerException);
287 Assert.IsTrue (inner.IsInstanceOfType (wsEx.InnerException));
291 void ForceSetHeader (WebHeaderCollection headers, string name, string value)
293 if (headerSetMethod == null)
294 headerSetMethod = typeof (WebHeaderCollection).GetMethod ("AddValue", BindingFlags.NonPublic);
295 headerSetMethod.Invoke (headers, new[] { name, value });