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";
20 int Port = NetworkHelpers.FindFreePort ();
21 HttpListener listener;
22 ClientWebSocket socket;
23 MethodInfo headerSetMethod;
28 listener = new HttpListener ();
29 listener.Prefixes.Add ("http://localhost:" + Port + "/");
31 socket = new ClientWebSocket ();
35 public void Teardown ()
37 if (listener != null) {
42 if (socket.State == WebSocketState.Open)
43 socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (2000);
50 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
51 public void ServerHandshakeReturnCrapStatusCodeTest ()
54 #pragma warning disable 4014
55 HandleHttpRequestAsync ((req, resp) => resp.StatusCode = 418);
56 #pragma warning restore 4014
58 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
59 } catch (AggregateException e) {
60 AssertWebSocketException (e, WebSocketError.Success, typeof (WebException));
63 Assert.Fail ("Should have thrown");
67 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
68 public void ServerHandshakeReturnWrongUpgradeHeader ()
70 #pragma warning disable 4014
71 HandleHttpRequestAsync ((req, resp) => {
72 resp.StatusCode = 101;
73 resp.Headers["Upgrade"] = "gtfo";
75 #pragma warning restore 4014
77 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
78 } catch (AggregateException e) {
79 AssertWebSocketException (e, WebSocketError.Success);
82 Assert.Fail ("Should have thrown");
86 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
87 public void ServerHandshakeReturnWrongConnectionHeader ()
89 #pragma warning disable 4014
90 HandleHttpRequestAsync ((req, resp) => {
91 resp.StatusCode = 101;
92 resp.Headers["Upgrade"] = "websocket";
93 // Mono http request doesn't like the forcing, test still valid since the default connection header value is empty
94 //ForceSetHeader (resp.Headers, "Connection", "Foo");
96 #pragma warning restore 4014
98 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
99 } catch (AggregateException e) {
100 AssertWebSocketException (e, WebSocketError.Success);
103 Assert.Fail ("Should have thrown");
107 [Category ("MobileNotWorking")] // The test hangs when ran as part of the entire BCL test suite. Works when only this fixture is ran
108 public void EchoTest ()
110 const string Payload = "This is a websocket test";
112 Assert.AreEqual (WebSocketState.None, socket.State);
113 socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait ();
114 Assert.AreEqual (WebSocketState.Open, socket.State);
116 var sendBuffer = Encoding.ASCII.GetBytes (Payload);
117 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
119 var receiveBuffer = new byte[Payload.Length];
120 var resp = socket.ReceiveAsync (new ArraySegment<byte> (receiveBuffer), CancellationToken.None).Result;
122 Assert.AreEqual (Payload.Length, resp.Count);
123 Assert.IsTrue (resp.EndOfMessage);
124 Assert.AreEqual (WebSocketMessageType.Text, resp.MessageType);
125 Assert.AreEqual (Payload, Encoding.ASCII.GetString (receiveBuffer, 0, resp.Count));
127 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
128 Assert.AreEqual (WebSocketState.Closed, socket.State);
132 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
133 public void CloseOutputAsyncTest ()
135 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
136 Assert.AreEqual (WebSocketState.Open, socket.State);
138 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
139 Assert.AreEqual (WebSocketState.CloseSent, socket.State);
141 var resp = socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Result;
142 Assert.AreEqual (WebSocketState.Closed, socket.State);
143 Assert.AreEqual (WebSocketMessageType.Close, resp.MessageType);
144 Assert.AreEqual (WebSocketCloseStatus.NormalClosure, resp.CloseStatus);
145 Assert.AreEqual (string.Empty, resp.CloseStatusDescription);
149 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
150 public void CloseAsyncTest ()
152 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
153 Assert.AreEqual (WebSocketState.Open, socket.State);
155 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
156 Assert.AreEqual (WebSocketState.Closed, socket.State);
159 [Test, ExpectedException (typeof (InvalidOperationException))]
160 public void SendAsyncArgTest_NotConnected ()
162 socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None);
165 [Test, ExpectedException (typeof (ArgumentNullException))]
166 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
167 public void SendAsyncArgTest_NoArray ()
169 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
170 socket.SendAsync (new ArraySegment<byte> (), WebSocketMessageType.Text, true, CancellationToken.None);
173 [Test, ExpectedException (typeof (InvalidOperationException))]
174 public void ReceiveAsyncArgTest_NotConnected ()
176 socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None);
179 [Test, ExpectedException (typeof (ArgumentNullException))]
180 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
181 public void ReceiveAsyncArgTest_NoArray ()
183 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
184 socket.ReceiveAsync (new ArraySegment<byte> (), CancellationToken.None);
188 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
189 public void ReceiveAsyncWrongState_Closed ()
192 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
193 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
194 Assert.IsTrue (socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Wait (5000));
195 } catch (AggregateException e) {
196 AssertWebSocketException (e, WebSocketError.Success);
199 Assert.Fail ("Should have thrown");
203 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
204 public void SendAsyncWrongState_Closed ()
207 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
208 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
209 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
210 } catch (AggregateException e) {
211 AssertWebSocketException (e, WebSocketError.Success);
214 Assert.Fail ("Should have thrown");
218 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
219 public void SendAsyncWrongState_CloseSent ()
222 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
223 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
224 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
225 } catch (AggregateException e) {
226 AssertWebSocketException (e, WebSocketError.Success);
229 Assert.Fail ("Should have thrown");
233 [Category ("NotWorking")] // FIXME: test relies on unimplemented HttpListenerContext.AcceptWebSocketAsync (), reenable it when the method is implemented
234 public void SendAsyncEndOfMessageTest ()
236 var cancellationToken = new CancellationTokenSource (TimeSpan.FromSeconds (30)).Token;
237 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Text, cancellationToken).Wait (5000);
238 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Text, cancellationToken).Wait (5000);
239 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
240 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
243 public async Task SendAsyncEndOfMessageTest (bool expectedEndOfMessage, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken)
245 using (var client = new ClientWebSocket ()) {
246 // Configure the listener.
247 var serverReceive = HandleHttpWebSocketRequestAsync<WebSocketReceiveResult> (async socket => await socket.ReceiveAsync (new ArraySegment<byte> (new byte[32]), cancellationToken), cancellationToken);
249 // Connect to the listener and make the request.
250 await client.ConnectAsync (new Uri ("ws://localhost:" + Port + "/"), cancellationToken);
251 await client.SendAsync (new ArraySegment<byte> (Encoding.UTF8.GetBytes ("test")), webSocketMessageType, expectedEndOfMessage, cancellationToken);
253 // Wait for the listener to handle the request and return its result.
254 var result = await serverReceive;
256 // Cleanup and check results.
257 await client.CloseAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
258 Assert.AreEqual (expectedEndOfMessage, result.EndOfMessage, "EndOfMessage should be " + expectedEndOfMessage);
262 async Task<T> HandleHttpWebSocketRequestAsync<T> (Func<WebSocket, Task<T>> action, CancellationToken cancellationToken)
264 var ctx = await this.listener.GetContextAsync ();
265 var wsContext = await ctx.AcceptWebSocketAsync (null);
266 var result = await action (wsContext.WebSocket);
267 await wsContext.WebSocket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
271 async Task HandleHttpRequestAsync (Action<HttpListenerRequest, HttpListenerResponse> handler)
273 var ctx = await listener.GetContextAsync ();
274 handler (ctx.Request, ctx.Response);
275 ctx.Response.Close ();
278 void AssertWebSocketException (AggregateException e, WebSocketError error, Type inner = null)
280 var wsEx = e.InnerException as WebSocketException;
281 Console.WriteLine (e.InnerException.ToString ());
282 Assert.IsNotNull (wsEx, "Not a websocketexception");
283 Assert.AreEqual (error, wsEx.WebSocketErrorCode);
285 Assert.IsNotNull (wsEx.InnerException);
286 Assert.IsTrue (inner.IsInstanceOfType (wsEx.InnerException));
290 void ForceSetHeader (WebHeaderCollection headers, string name, string value)
292 if (headerSetMethod == null)
293 headerSetMethod = typeof (WebHeaderCollection).GetMethod ("AddValue", BindingFlags.NonPublic);
294 headerSetMethod.Invoke (headers, new[] { name, value });