Merge pull request #1632 from alexanderkyte/bug24118
[mono.git] / mcs / class / System / Test / System.Net.WebSockets / ClientWebSocketTest.cs
1 #if NET_4_5
2 using System;
3 using System.Net;
4 using System.Threading;
5 using System.Threading.Tasks;
6 using System.Collections.Generic;
7 using System.Net.WebSockets;
8 using System.Reflection;
9 using System.Text;
10
11 using NUnit.Framework;
12
13 using MonoTests.Helpers;
14
15 namespace MonoTests.System.Net.WebSockets
16 {
17         [TestFixture]
18         public class ClientWebSocketTest
19         {
20                 const string EchoServerUrl = "ws://echo.websocket.org";
21                 int Port = NetworkHelpers.FindFreePort ();
22                 HttpListener listener;
23                 ClientWebSocket socket;
24                 MethodInfo headerSetMethod;
25
26                 [SetUp]
27                 public void Setup ()
28                 {
29                         listener = new HttpListener ();
30                         listener.Prefixes.Add ("http://localhost:" + Port + "/");
31                         listener.Start ();
32                         socket = new ClientWebSocket ();
33                 }
34
35                 [TearDown]
36                 public void Teardown ()
37                 {
38                         if (listener != null) {
39                                 listener.Stop ();
40                                 listener = null;
41                         }
42                         if (socket != null) {
43                                 if (socket.State == WebSocketState.Open)
44                                         socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (2000);
45                                 socket.Dispose ();
46                                 socket = null;
47                         }
48                 }
49
50                 [Test]
51                 public void ServerHandshakeReturnCrapStatusCodeTest ()
52                 {
53                         // On purpose, 
54                         #pragma warning disable 4014
55                         HandleHttpRequestAsync ((req, resp) => resp.StatusCode = 418);
56                         #pragma warning restore 4014
57                         try {
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));
61                                 return;
62                         }
63                         Assert.Fail ("Should have thrown");
64                 }
65
66                 [Test]
67                 public void ServerHandshakeReturnWrongUpgradeHeader ()
68                 {
69                         #pragma warning disable 4014
70                         HandleHttpRequestAsync ((req, resp) => {
71                                         resp.StatusCode = 101;
72                                         resp.Headers["Upgrade"] = "gtfo";
73                                 });
74                         #pragma warning restore 4014
75                         try {
76                                 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
77                         } catch (AggregateException e) {
78                                 AssertWebSocketException (e, WebSocketError.Success);
79                                 return;
80                         }
81                         Assert.Fail ("Should have thrown");
82                 }
83
84                 [Test]
85                 public void ServerHandshakeReturnWrongConnectionHeader ()
86                 {
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");
93                                 });
94                         #pragma warning restore 4014
95                         try {
96                                 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
97                         } catch (AggregateException e) {
98                                 AssertWebSocketException (e, WebSocketError.Success);
99                                 return;
100                         }
101                         Assert.Fail ("Should have thrown");
102                 }
103
104                 [Test]
105                 public void EchoTest ()
106                 {
107                         const string Payload = "This is a websocket test";
108
109                         Assert.AreEqual (WebSocketState.None, socket.State);
110                         socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait ();
111                         Assert.AreEqual (WebSocketState.Open, socket.State);
112
113                         var sendBuffer = Encoding.ASCII.GetBytes (Payload);
114                         Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
115
116                         var receiveBuffer = new byte[Payload.Length];
117                         var resp = socket.ReceiveAsync (new ArraySegment<byte> (receiveBuffer), CancellationToken.None).Result;
118
119                         Assert.AreEqual (Payload.Length, resp.Count);
120                         Assert.IsTrue (resp.EndOfMessage);
121                         Assert.AreEqual (WebSocketMessageType.Text, resp.MessageType);
122                         Assert.AreEqual (Payload, Encoding.ASCII.GetString (receiveBuffer, 0, resp.Count));
123
124                         Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
125                         Assert.AreEqual (WebSocketState.Closed, socket.State);
126                 }
127
128                 [Test]
129                 public void CloseOutputAsyncTest ()
130                 {
131                         Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
132                         Assert.AreEqual (WebSocketState.Open, socket.State);
133
134                         Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
135                         Assert.AreEqual (WebSocketState.CloseSent, socket.State);
136
137                         var resp = socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Result;
138                         Assert.AreEqual (WebSocketState.Closed, socket.State);
139                         Assert.AreEqual (WebSocketMessageType.Close, resp.MessageType);
140                         Assert.AreEqual (WebSocketCloseStatus.NormalClosure, resp.CloseStatus);
141                         Assert.AreEqual (string.Empty, resp.CloseStatusDescription);
142                 }
143
144                 [Test]
145                 public void CloseAsyncTest ()
146                 {
147                         Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
148                         Assert.AreEqual (WebSocketState.Open, socket.State);
149
150                         Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
151                         Assert.AreEqual (WebSocketState.Closed, socket.State);
152                 }
153
154                 [Test, ExpectedException (typeof (InvalidOperationException))]
155                 public void SendAsyncArgTest_NotConnected ()
156                 {
157                         socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None);
158                 }
159
160                 [Test, ExpectedException (typeof (ArgumentNullException))]
161                 public void SendAsyncArgTest_NoArray ()
162                 {
163                         Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
164                         socket.SendAsync (new ArraySegment<byte> (), WebSocketMessageType.Text, true, CancellationToken.None);
165                 }
166
167                 [Test, ExpectedException (typeof (InvalidOperationException))]
168                 public void ReceiveAsyncArgTest_NotConnected ()
169                 {
170                         socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None);
171                 }
172
173                 [Test, ExpectedException (typeof (ArgumentNullException))]
174                 public void ReceiveAsyncArgTest_NoArray ()
175                 {
176                         Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
177                         socket.ReceiveAsync (new ArraySegment<byte> (), CancellationToken.None);
178                 }
179
180                 [Test]
181                 public void ReceiveAsyncWrongState_Closed ()
182                 {
183                         try {
184                                 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
185                                 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
186                                 Assert.IsTrue (socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Wait (5000));
187                         } catch (AggregateException e) {
188                                 AssertWebSocketException (e, WebSocketError.Success);
189                                 return;
190                         }
191                         Assert.Fail ("Should have thrown");
192                 }
193
194                 [Test]
195                 public void SendAsyncWrongState_Closed ()
196                 {
197                         try {
198                                 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
199                                 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
200                                 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
201                         } catch (AggregateException e) {
202                                 AssertWebSocketException (e, WebSocketError.Success);
203                                 return;
204                         }
205                         Assert.Fail ("Should have thrown");
206                 }
207
208                 [Test]
209                 public void SendAsyncWrongState_CloseSent ()
210                 {
211                         try {
212                                 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
213                                 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
214                                 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
215                         } catch (AggregateException e) {
216                                 AssertWebSocketException (e, WebSocketError.Success);
217                                 return;
218                         }
219                         Assert.Fail ("Should have thrown");
220                 }
221
222                 [Test]
223                 [Category ("NotWorking")]  // FIXME: test relies on unimplemented HttpListenerContext.AcceptWebSocketAsync (), reenable it when the method is implemented
224                 public void SendAsyncEndOfMessageTest ()
225                 {
226                         var cancellationToken = new CancellationTokenSource (TimeSpan.FromSeconds (30)).Token;
227                         SendAsyncEndOfMessageTest (false, WebSocketMessageType.Text, cancellationToken).Wait (5000);
228                         SendAsyncEndOfMessageTest (true, WebSocketMessageType.Text, cancellationToken).Wait (5000);
229                         SendAsyncEndOfMessageTest (false, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
230                         SendAsyncEndOfMessageTest (true, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
231                 }
232
233                 public async Task SendAsyncEndOfMessageTest (bool expectedEndOfMessage, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken)
234                 {
235                         using (var client = new ClientWebSocket ()) {
236                                 // Configure the listener.
237                                 var serverReceive = HandleHttpWebSocketRequestAsync<WebSocketReceiveResult> (async socket => await socket.ReceiveAsync (new ArraySegment<byte> (new byte[32]), cancellationToken), cancellationToken);
238
239                                 // Connect to the listener and make the request.
240                                 await client.ConnectAsync (new Uri ("ws://localhost:" + Port + "/"), cancellationToken);
241                                 await client.SendAsync (new ArraySegment<byte> (Encoding.UTF8.GetBytes ("test")), webSocketMessageType, expectedEndOfMessage, cancellationToken);
242
243                                 // Wait for the listener to handle the request and return its result.
244                                 var result = await serverReceive;
245
246                                 // Cleanup and check results.
247                                 await client.CloseAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
248                                 Assert.AreEqual (expectedEndOfMessage, result.EndOfMessage, "EndOfMessage should be " + expectedEndOfMessage);
249                         }
250                 }
251
252                 async Task<T> HandleHttpWebSocketRequestAsync<T> (Func<WebSocket, Task<T>> action, CancellationToken cancellationToken)
253                 {
254                         var ctx = await this.listener.GetContextAsync ();
255                         var wsContext = await ctx.AcceptWebSocketAsync (null);
256                         var result = await action (wsContext.WebSocket);
257                         await wsContext.WebSocket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
258                         return result;
259                 }
260
261                 async Task HandleHttpRequestAsync (Action<HttpListenerRequest, HttpListenerResponse> handler)
262                 {
263                         var ctx = await listener.GetContextAsync ();
264                         handler (ctx.Request, ctx.Response);
265                         ctx.Response.Close ();
266                 }
267
268                 void AssertWebSocketException (AggregateException e, WebSocketError error, Type inner = null)
269                 {
270                         var wsEx = e.InnerException as WebSocketException;
271                         Console.WriteLine (e.InnerException.ToString ());
272                         Assert.IsNotNull (wsEx, "Not a websocketexception");
273                         Assert.AreEqual (error, wsEx.WebSocketErrorCode);
274                         if (inner != null) {
275                                 Assert.IsNotNull (wsEx.InnerException);
276                                 Assert.IsTrue (inner.IsInstanceOfType (wsEx.InnerException));
277                         }
278                 }
279
280                 void ForceSetHeader (WebHeaderCollection headers, string name, string value)
281                 {
282                         if (headerSetMethod == null)
283                                 headerSetMethod = typeof (WebHeaderCollection).GetMethod ("AddValue", BindingFlags.NonPublic);
284                         headerSetMethod.Invoke (headers, new[] { name, value });
285                 }
286         }
287 }
288
289 #endif