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;
14 namespace MonoTests.System.Net.WebSockets
17 public class ClientWebSocketTest
19 const string EchoServerUrl = "ws://echo.websocket.org";
20 const int Port = 42123;
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 public void ServerHandshakeReturnCrapStatusCodeTest ()
53 #pragma warning disable 4014
54 HandleHttpRequestAsync ((req, resp) => resp.StatusCode = 418);
55 #pragma warning restore 4014
57 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
58 } catch (AggregateException e) {
59 AssertWebSocketException (e, WebSocketError.Success, typeof (WebException));
62 Assert.Fail ("Should have thrown");
66 public void ServerHandshakeReturnWrongUpgradeHeader ()
68 #pragma warning disable 4014
69 HandleHttpRequestAsync ((req, resp) => {
70 resp.StatusCode = 101;
71 resp.Headers["Upgrade"] = "gtfo";
73 #pragma warning restore 4014
75 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
76 } catch (AggregateException e) {
77 AssertWebSocketException (e, WebSocketError.Success);
80 Assert.Fail ("Should have thrown");
84 public void ServerHandshakeReturnWrongConnectionHeader ()
86 #pragma warning disable 4014
87 HandleHttpRequestAsync ((req, resp) => {
88 resp.StatusCode = 101;
89 resp.Headers["Upgrade"] = "websocket";
90 // Mono http request doesn't like the forcing, test still valid since the default connection header value is empty
91 //ForceSetHeader (resp.Headers, "Connection", "Foo");
93 #pragma warning restore 4014
95 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
96 } catch (AggregateException e) {
97 AssertWebSocketException (e, WebSocketError.Success);
100 Assert.Fail ("Should have thrown");
104 public void EchoTest ()
106 const string Payload = "This is a websocket test";
108 Assert.AreEqual (WebSocketState.None, socket.State);
109 socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait ();
110 Assert.AreEqual (WebSocketState.Open, socket.State);
112 var sendBuffer = Encoding.ASCII.GetBytes (Payload);
113 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
115 var receiveBuffer = new byte[Payload.Length];
116 var resp = socket.ReceiveAsync (new ArraySegment<byte> (receiveBuffer), CancellationToken.None).Result;
118 Assert.AreEqual (Payload.Length, resp.Count);
119 Assert.IsTrue (resp.EndOfMessage);
120 Assert.AreEqual (WebSocketMessageType.Text, resp.MessageType);
121 Assert.AreEqual (Payload, Encoding.ASCII.GetString (receiveBuffer, 0, resp.Count));
123 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
124 Assert.AreEqual (WebSocketState.Closed, socket.State);
128 public void CloseOutputAsyncTest ()
130 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
131 Assert.AreEqual (WebSocketState.Open, socket.State);
133 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
134 Assert.AreEqual (WebSocketState.CloseSent, socket.State);
136 var resp = socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Result;
137 Assert.AreEqual (WebSocketState.Closed, socket.State);
138 Assert.AreEqual (WebSocketMessageType.Close, resp.MessageType);
139 Assert.AreEqual (WebSocketCloseStatus.NormalClosure, resp.CloseStatus);
140 Assert.AreEqual (string.Empty, resp.CloseStatusDescription);
144 public void CloseAsyncTest ()
146 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
147 Assert.AreEqual (WebSocketState.Open, socket.State);
149 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
150 Assert.AreEqual (WebSocketState.Closed, socket.State);
153 [Test, ExpectedException (typeof (InvalidOperationException))]
154 public void SendAsyncArgTest_NotConnected ()
156 socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None);
159 [Test, ExpectedException (typeof (ArgumentNullException))]
160 public void SendAsyncArgTest_NoArray ()
162 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
163 socket.SendAsync (new ArraySegment<byte> (), WebSocketMessageType.Text, true, CancellationToken.None);
166 [Test, ExpectedException (typeof (InvalidOperationException))]
167 public void ReceiveAsyncArgTest_NotConnected ()
169 socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None);
172 [Test, ExpectedException (typeof (ArgumentNullException))]
173 public void ReceiveAsyncArgTest_NoArray ()
175 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
176 socket.ReceiveAsync (new ArraySegment<byte> (), CancellationToken.None);
180 public void ReceiveAsyncWrongState_Closed ()
183 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
184 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
185 Assert.IsTrue (socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Wait (5000));
186 } catch (AggregateException e) {
187 AssertWebSocketException (e, WebSocketError.Success);
190 Assert.Fail ("Should have thrown");
194 public void SendAsyncWrongState_Closed ()
197 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
198 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
199 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
200 } catch (AggregateException e) {
201 AssertWebSocketException (e, WebSocketError.Success);
204 Assert.Fail ("Should have thrown");
208 public void SendAsyncWrongState_CloseSent ()
211 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
212 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
213 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
214 } catch (AggregateException e) {
215 AssertWebSocketException (e, WebSocketError.Success);
218 Assert.Fail ("Should have thrown");
222 [Category ("NotWorking")] // FIXME: test relies on unimplemented HttpListenerContext.AcceptWebSocketAsync (), reenable it when the method is implemented
223 public void SendAsyncEndOfMessageTest ()
225 var cancellationToken = new CancellationTokenSource (TimeSpan.FromSeconds (30)).Token;
226 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Text, cancellationToken).Wait (5000);
227 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Text, cancellationToken).Wait (5000);
228 SendAsyncEndOfMessageTest (false, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
229 SendAsyncEndOfMessageTest (true, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
232 public async Task SendAsyncEndOfMessageTest (bool expectedEndOfMessage, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken)
234 using (var client = new ClientWebSocket ()) {
235 // Configure the listener.
236 var serverReceive = HandleHttpWebSocketRequestAsync<WebSocketReceiveResult> (async socket => await socket.ReceiveAsync (new ArraySegment<byte> (new byte[32]), cancellationToken), cancellationToken);
238 // Connect to the listener and make the request.
239 await client.ConnectAsync (new Uri ("ws://localhost:" + Port + "/"), cancellationToken);
240 await client.SendAsync (new ArraySegment<byte> (Encoding.UTF8.GetBytes ("test")), webSocketMessageType, expectedEndOfMessage, cancellationToken);
242 // Wait for the listener to handle the request and return its result.
243 var result = await serverReceive;
245 // Cleanup and check results.
246 await client.CloseAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
247 Assert.AreEqual (expectedEndOfMessage, result.EndOfMessage, "EndOfMessage should be " + expectedEndOfMessage);
251 async Task<T> HandleHttpWebSocketRequestAsync<T> (Func<WebSocket, Task<T>> action, CancellationToken cancellationToken)
253 var ctx = await this.listener.GetContextAsync ();
254 var wsContext = await ctx.AcceptWebSocketAsync (null);
255 var result = await action (wsContext.WebSocket);
256 await wsContext.WebSocket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
260 async Task HandleHttpRequestAsync (Action<HttpListenerRequest, HttpListenerResponse> handler)
262 var ctx = await listener.GetContextAsync ();
263 handler (ctx.Request, ctx.Response);
264 ctx.Response.Close ();
267 void AssertWebSocketException (AggregateException e, WebSocketError error, Type inner = null)
269 var wsEx = e.InnerException as WebSocketException;
270 Console.WriteLine (e.InnerException.ToString ());
271 Assert.IsNotNull (wsEx, "Not a websocketexception");
272 Assert.AreEqual (error, wsEx.WebSocketErrorCode);
274 Assert.IsNotNull (wsEx.InnerException);
275 Assert.IsTrue (inner.IsInstanceOfType (wsEx.InnerException));
279 void ForceSetHeader (WebHeaderCollection headers, string name, string value)
281 if (headerSetMethod == null)
282 headerSetMethod = typeof (WebHeaderCollection).GetMethod ("AddValue", BindingFlags.NonPublic);
283 headerSetMethod.Invoke (headers, new[] { name, value });