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://echo.websocket.org";
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 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 public void ServerHandshakeReturnWrongUpgradeHeader ()
69 #pragma warning disable 4014
70 HandleHttpRequestAsync ((req, resp) => {
71 resp.StatusCode = 101;
72 resp.Headers["Upgrade"] = "gtfo";
74 #pragma warning restore 4014
76 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
77 } catch (AggregateException e) {
78 AssertWebSocketException (e, WebSocketError.Success);
81 Assert.Fail ("Should have thrown");
85 public void ServerHandshakeReturnWrongConnectionHeader ()
87 #pragma warning disable 4014
88 HandleHttpRequestAsync ((req, resp) => {
89 resp.StatusCode = 101;
90 resp.Headers["Upgrade"] = "websocket";
91 // Mono http request doesn't like the forcing, test still valid since the default connection header value is empty
92 //ForceSetHeader (resp.Headers, "Connection", "Foo");
94 #pragma warning restore 4014
96 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
97 } catch (AggregateException e) {
98 AssertWebSocketException (e, WebSocketError.Success);
101 Assert.Fail ("Should have thrown");
105 [Category ("AndroidNotWorking")] // The test hangs
106 public void EchoTest ()
108 const string Payload = "This is a websocket test";
110 Assert.AreEqual (WebSocketState.None, socket.State);
111 socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait ();
112 Assert.AreEqual (WebSocketState.Open, socket.State);
114 var sendBuffer = Encoding.ASCII.GetBytes (Payload);
115 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
117 var receiveBuffer = new byte[Payload.Length];
118 var resp = socket.ReceiveAsync (new ArraySegment<byte> (receiveBuffer), CancellationToken.None).Result;
120 Assert.AreEqual (Payload.Length, resp.Count);
121 Assert.IsTrue (resp.EndOfMessage);
122 Assert.AreEqual (WebSocketMessageType.Text, resp.MessageType);
123 Assert.AreEqual (Payload, Encoding.ASCII.GetString (receiveBuffer, 0, resp.Count));
125 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
126 Assert.AreEqual (WebSocketState.Closed, socket.State);
130 public void CloseOutputAsyncTest ()
132 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
133 Assert.AreEqual (WebSocketState.Open, socket.State);
135 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
136 Assert.AreEqual (WebSocketState.CloseSent, socket.State);
138 var resp = socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Result;
139 Assert.AreEqual (WebSocketState.Closed, socket.State);
140 Assert.AreEqual (WebSocketMessageType.Close, resp.MessageType);
141 Assert.AreEqual (WebSocketCloseStatus.NormalClosure, resp.CloseStatus);
142 Assert.AreEqual (string.Empty, resp.CloseStatusDescription);
146 public void CloseAsyncTest ()
148 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
149 Assert.AreEqual (WebSocketState.Open, socket.State);
151 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
152 Assert.AreEqual (WebSocketState.Closed, socket.State);
155 [Test, ExpectedException (typeof (InvalidOperationException))]
156 public void SendAsyncArgTest_NotConnected ()
158 socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None);
161 [Test, ExpectedException (typeof (ArgumentNullException))]
162 public void SendAsyncArgTest_NoArray ()
164 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
165 socket.SendAsync (new ArraySegment<byte> (), WebSocketMessageType.Text, true, CancellationToken.None);
168 [Test, ExpectedException (typeof (InvalidOperationException))]
169 public void ReceiveAsyncArgTest_NotConnected ()
171 socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None);
174 [Test, ExpectedException (typeof (ArgumentNullException))]
175 public void ReceiveAsyncArgTest_NoArray ()
177 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
178 socket.ReceiveAsync (new ArraySegment<byte> (), CancellationToken.None);
182 public void ReceiveAsyncWrongState_Closed ()
185 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
186 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
187 Assert.IsTrue (socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Wait (5000));
188 } catch (AggregateException e) {
189 AssertWebSocketException (e, WebSocketError.Success);
192 Assert.Fail ("Should have thrown");
196 public void SendAsyncWrongState_Closed ()
199 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
200 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
201 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
202 } catch (AggregateException e) {
203 AssertWebSocketException (e, WebSocketError.Success);
206 Assert.Fail ("Should have thrown");
210 public void SendAsyncWrongState_CloseSent ()
213 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
214 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
215 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
216 } catch (AggregateException e) {
217 AssertWebSocketException (e, WebSocketError.Success);
220 Assert.Fail ("Should have thrown");
224 [Category ("NotWorking")] // FIXME: test relies on unimplemented HttpListenerContext.AcceptWebSocketAsync (), reenable it when the method is implemented
225 public void SendAsyncEndOfMessageTest ()
227 var cancellationToken = new CancellationTokenSource (TimeSpan.FromSeconds (30)).Token;
228 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Text, cancellationToken).Wait (5000);
229 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Text, cancellationToken).Wait (5000);
230 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
231 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
234 public async Task SendAsyncEndOfMessageTest (bool expectedEndOfMessage, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken)
236 using (var client = new ClientWebSocket ()) {
237 // Configure the listener.
238 var serverReceive = HandleHttpWebSocketRequestAsync<WebSocketReceiveResult> (async socket => await socket.ReceiveAsync (new ArraySegment<byte> (new byte[32]), cancellationToken), cancellationToken);
240 // Connect to the listener and make the request.
241 await client.ConnectAsync (new Uri ("ws://localhost:" + Port + "/"), cancellationToken);
242 await client.SendAsync (new ArraySegment<byte> (Encoding.UTF8.GetBytes ("test")), webSocketMessageType, expectedEndOfMessage, cancellationToken);
244 // Wait for the listener to handle the request and return its result.
245 var result = await serverReceive;
247 // Cleanup and check results.
248 await client.CloseAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
249 Assert.AreEqual (expectedEndOfMessage, result.EndOfMessage, "EndOfMessage should be " + expectedEndOfMessage);
253 async Task<T> HandleHttpWebSocketRequestAsync<T> (Func<WebSocket, Task<T>> action, CancellationToken cancellationToken)
255 var ctx = await this.listener.GetContextAsync ();
256 var wsContext = await ctx.AcceptWebSocketAsync (null);
257 var result = await action (wsContext.WebSocket);
258 await wsContext.WebSocket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
262 async Task HandleHttpRequestAsync (Action<HttpListenerRequest, HttpListenerResponse> handler)
264 var ctx = await listener.GetContextAsync ();
265 handler (ctx.Request, ctx.Response);
266 ctx.Response.Close ();
269 void AssertWebSocketException (AggregateException e, WebSocketError error, Type inner = null)
271 var wsEx = e.InnerException as WebSocketException;
272 Console.WriteLine (e.InnerException.ToString ());
273 Assert.IsNotNull (wsEx, "Not a websocketexception");
274 Assert.AreEqual (error, wsEx.WebSocketErrorCode);
276 Assert.IsNotNull (wsEx.InnerException);
277 Assert.IsTrue (inner.IsInstanceOfType (wsEx.InnerException));
281 void ForceSetHeader (WebHeaderCollection headers, string name, string value)
283 if (headerSetMethod == null)
284 headerSetMethod = typeof (WebHeaderCollection).GetMethod ("AddValue", BindingFlags.NonPublic);
285 headerSetMethod.Invoke (headers, new[] { name, value });