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