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 HttpListener listener {
24 if (_listener != null)
26 var tmp = new HttpListener ();
27 tmp.Prefixes.Add ("http://localhost:" + Port + "/");
29 return _listener = tmp;
32 ClientWebSocket _socket;
33 ClientWebSocket socket { get { return _socket ?? (_socket = new ClientWebSocket ()); } }
34 MethodInfo headerSetMethod;
37 public void Teardown ()
39 if (_listener != null) {
43 if (_socket != null) {
44 if (_socket.State == WebSocketState.Open)
45 _socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (2000);
52 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
53 public void ServerHandshakeReturnCrapStatusCodeTest ()
56 #pragma warning disable 4014
57 HandleHttpRequestAsync ((req, resp) => resp.StatusCode = 418);
58 #pragma warning restore 4014
60 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
61 } catch (AggregateException e) {
62 AssertWebSocketException (e, WebSocketError.Success, typeof (WebException));
65 Assert.Fail ("Should have thrown");
69 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
70 public void ServerHandshakeReturnWrongUpgradeHeader ()
72 #pragma warning disable 4014
73 HandleHttpRequestAsync ((req, resp) => {
74 resp.StatusCode = 101;
75 resp.Headers["Upgrade"] = "gtfo";
77 #pragma warning restore 4014
79 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
80 } catch (AggregateException e) {
81 AssertWebSocketException (e, WebSocketError.Success);
84 Assert.Fail ("Should have thrown");
88 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
89 public void ServerHandshakeReturnWrongConnectionHeader ()
91 #pragma warning disable 4014
92 HandleHttpRequestAsync ((req, resp) => {
93 resp.StatusCode = 101;
94 resp.Headers["Upgrade"] = "websocket";
95 // Mono http request doesn't like the forcing, test still valid since the default connection header value is empty
96 //ForceSetHeader (resp.Headers, "Connection", "Foo");
98 #pragma warning restore 4014
100 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
101 } catch (AggregateException e) {
102 AssertWebSocketException (e, WebSocketError.Success);
105 Assert.Fail ("Should have thrown");
109 [Category ("MobileNotWorking")] // The test hangs when ran as part of the entire BCL test suite. Works when only this fixture is ran
110 public void EchoTest ()
112 const string Payload = "This is a websocket test";
114 Assert.AreEqual (WebSocketState.None, socket.State);
115 socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait ();
116 Assert.AreEqual (WebSocketState.Open, socket.State);
118 var sendBuffer = Encoding.ASCII.GetBytes (Payload);
119 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
121 var receiveBuffer = new byte[Payload.Length];
122 var resp = socket.ReceiveAsync (new ArraySegment<byte> (receiveBuffer), CancellationToken.None).Result;
124 Assert.AreEqual (Payload.Length, resp.Count);
125 Assert.IsTrue (resp.EndOfMessage);
126 Assert.AreEqual (WebSocketMessageType.Text, resp.MessageType);
127 Assert.AreEqual (Payload, Encoding.ASCII.GetString (receiveBuffer, 0, resp.Count));
129 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
130 Assert.AreEqual (WebSocketState.Closed, socket.State);
134 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
135 public void CloseOutputAsyncTest ()
137 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
138 Assert.AreEqual (WebSocketState.Open, socket.State);
140 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
141 Assert.AreEqual (WebSocketState.CloseSent, socket.State);
143 var resp = socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Result;
144 Assert.AreEqual (WebSocketState.Closed, socket.State);
145 Assert.AreEqual (WebSocketMessageType.Close, resp.MessageType);
146 Assert.AreEqual (WebSocketCloseStatus.NormalClosure, resp.CloseStatus);
147 Assert.AreEqual (string.Empty, resp.CloseStatusDescription);
151 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
152 public void CloseAsyncTest ()
154 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
155 Assert.AreEqual (WebSocketState.Open, socket.State);
157 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
158 Assert.AreEqual (WebSocketState.Closed, socket.State);
161 [Test, ExpectedException (typeof (InvalidOperationException))]
162 public void SendAsyncArgTest_NotConnected ()
164 socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None);
167 [Test, ExpectedException (typeof (ArgumentNullException))]
168 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
169 public void SendAsyncArgTest_NoArray ()
171 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
172 socket.SendAsync (new ArraySegment<byte> (), WebSocketMessageType.Text, true, CancellationToken.None);
175 [Test, ExpectedException (typeof (InvalidOperationException))]
176 public void ReceiveAsyncArgTest_NotConnected ()
178 socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), 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 ReceiveAsyncArgTest_NoArray ()
185 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
186 socket.ReceiveAsync (new ArraySegment<byte> (), CancellationToken.None);
190 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
191 public void ReceiveAsyncWrongState_Closed ()
194 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
195 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
196 Assert.IsTrue (socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Wait (5000));
197 } catch (AggregateException e) {
198 AssertWebSocketException (e, WebSocketError.Success);
201 Assert.Fail ("Should have thrown");
205 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
206 public void SendAsyncWrongState_Closed ()
209 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
210 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
211 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
212 } catch (AggregateException e) {
213 AssertWebSocketException (e, WebSocketError.Success);
216 Assert.Fail ("Should have thrown");
220 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
221 public void SendAsyncWrongState_CloseSent ()
224 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
225 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
226 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
227 } catch (AggregateException e) {
228 AssertWebSocketException (e, WebSocketError.Success);
231 Assert.Fail ("Should have thrown");
235 [Category ("NotWorking")] // FIXME: test relies on unimplemented HttpListenerContext.AcceptWebSocketAsync (), reenable it when the method is implemented
236 public void SendAsyncEndOfMessageTest ()
238 var cancellationToken = new CancellationTokenSource (TimeSpan.FromSeconds (30)).Token;
239 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Text, cancellationToken).Wait (5000);
240 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Text, cancellationToken).Wait (5000);
241 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
242 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
245 public async Task SendAsyncEndOfMessageTest (bool expectedEndOfMessage, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken)
247 using (var client = new ClientWebSocket ()) {
248 // Configure the listener.
249 var serverReceive = HandleHttpWebSocketRequestAsync<WebSocketReceiveResult> (async socket => await socket.ReceiveAsync (new ArraySegment<byte> (new byte[32]), cancellationToken), cancellationToken);
251 // Connect to the listener and make the request.
252 await client.ConnectAsync (new Uri ("ws://localhost:" + Port + "/"), cancellationToken);
253 await client.SendAsync (new ArraySegment<byte> (Encoding.UTF8.GetBytes ("test")), webSocketMessageType, expectedEndOfMessage, cancellationToken);
255 // Wait for the listener to handle the request and return its result.
256 var result = await serverReceive;
258 // Cleanup and check results.
259 await client.CloseAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
260 Assert.AreEqual (expectedEndOfMessage, result.EndOfMessage, "EndOfMessage should be " + expectedEndOfMessage);
264 async Task<T> HandleHttpWebSocketRequestAsync<T> (Func<WebSocket, Task<T>> action, CancellationToken cancellationToken)
266 var ctx = await this.listener.GetContextAsync ();
267 var wsContext = await ctx.AcceptWebSocketAsync (null);
268 var result = await action (wsContext.WebSocket);
269 await wsContext.WebSocket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
273 async Task HandleHttpRequestAsync (Action<HttpListenerRequest, HttpListenerResponse> handler)
275 var ctx = await listener.GetContextAsync ();
276 handler (ctx.Request, ctx.Response);
277 ctx.Response.Close ();
280 void AssertWebSocketException (AggregateException e, WebSocketError error, Type inner = null)
282 var wsEx = e.InnerException as WebSocketException;
283 Console.WriteLine (e.InnerException.ToString ());
284 Assert.IsNotNull (wsEx, "Not a websocketexception");
285 Assert.AreEqual (error, wsEx.WebSocketErrorCode);
287 Assert.IsNotNull (wsEx.InnerException);
288 Assert.IsTrue (inner.IsInstanceOfType (wsEx.InnerException));
292 void ForceSetHeader (WebHeaderCollection headers, string name, string value)
294 if (headerSetMethod == null)
295 headerSetMethod = typeof (WebHeaderCollection).GetMethod ("AddValue", BindingFlags.NonPublic);
296 headerSetMethod.Invoke (headers, new[] { name, value });