2014-08-13 10:39:27 +01:00
using System ;
using System.Net ;
using System.Threading ;
using System.Threading.Tasks ;
using System.Collections.Generic ;
using System.Net.WebSockets ;
using System.Reflection ;
using System.Text ;
using NUnit.Framework ;
2015-08-26 07:17:56 -04:00
using MonoTests.Helpers ;
2014-08-13 10:39:27 +01:00
namespace MonoTests.System.Net.WebSockets
{
[TestFixture]
public class ClientWebSocketTest
{
2016-08-03 10:59:49 +00:00
const string EchoServerUrl = "ws://corefx-net.cloudapp.net/WebSocket/EchoWebSocket.ashx" ;
2015-08-26 07:17:56 -04:00
int Port = NetworkHelpers . FindFreePort ( ) ;
2016-11-10 13:04:39 +00:00
HttpListener _listener ;
HttpListener listener {
get {
if ( _listener ! = null )
return _listener ;
var tmp = new HttpListener ( ) ;
tmp . Prefixes . Add ( "http://localhost:" + Port + "/" ) ;
tmp . Start ( ) ;
return _listener = tmp ;
}
2014-08-13 10:39:27 +01:00
}
2016-11-10 13:04:39 +00:00
ClientWebSocket _socket ;
ClientWebSocket socket { get { return _socket ? ? ( _socket = new ClientWebSocket ( ) ) ; } }
MethodInfo headerSetMethod ;
2014-08-13 10:39:27 +01:00
[TearDown]
public void Teardown ( )
{
2016-11-10 13:04:39 +00:00
if ( _listener ! = null ) {
_listener . Stop ( ) ;
_listener = null ;
2014-08-13 10:39:27 +01:00
}
2016-11-10 13:04:39 +00:00
if ( _socket ! = null ) {
if ( _socket . State = = WebSocketState . Open )
_socket . CloseAsync ( WebSocketCloseStatus . NormalClosure , string . Empty , CancellationToken . None ) . Wait ( 2000 ) ;
_socket . Dispose ( ) ;
_socket = null ;
2014-08-13 10:39:27 +01:00
}
}
[Test]
2015-09-02 12:36:57 -04:00
[Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
2014-08-13 10:39:27 +01:00
public void ServerHandshakeReturnCrapStatusCodeTest ( )
{
2015-01-13 10:44:36 +00:00
// On purpose,
#pragma warning disable 4014
2014-08-13 10:39:27 +01:00
HandleHttpRequestAsync ( ( req , resp ) = > resp . StatusCode = 418 ) ;
2015-01-13 10:44:36 +00:00
#pragma warning restore 4014
2014-08-13 10:39:27 +01:00
try {
Assert . IsTrue ( socket . ConnectAsync ( new Uri ( "ws://localhost:" + Port ) , CancellationToken . None ) . Wait ( 5000 ) ) ;
} catch ( AggregateException e ) {
AssertWebSocketException ( e , WebSocketError . Success , typeof ( WebException ) ) ;
return ;
}
Assert . Fail ( "Should have thrown" ) ;
}
[Test]
2015-09-02 12:36:57 -04:00
[Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
2014-08-13 10:39:27 +01:00
public void ServerHandshakeReturnWrongUpgradeHeader ( )
{
2015-01-13 10:44:36 +00:00
#pragma warning disable 4014
2014-08-13 10:39:27 +01:00
HandleHttpRequestAsync ( ( req , resp ) = > {
resp . StatusCode = 101 ;
resp . Headers [ "Upgrade" ] = "gtfo" ;
} ) ;
2015-01-13 10:44:36 +00:00
#pragma warning restore 4014
2014-08-13 10:39:27 +01:00
try {
Assert . IsTrue ( socket . ConnectAsync ( new Uri ( "ws://localhost:" + Port ) , CancellationToken . None ) . Wait ( 5000 ) ) ;
} catch ( AggregateException e ) {
AssertWebSocketException ( e , WebSocketError . Success ) ;
return ;
}
Assert . Fail ( "Should have thrown" ) ;
}
[Test]
2015-09-02 12:36:57 -04:00
[Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
2014-08-13 10:39:27 +01:00
public void ServerHandshakeReturnWrongConnectionHeader ( )
{
2015-01-13 10:44:36 +00:00
#pragma warning disable 4014
2014-08-13 10:39:27 +01:00
HandleHttpRequestAsync ( ( req , resp ) = > {
resp . StatusCode = 101 ;
resp . Headers [ "Upgrade" ] = "websocket" ;
// Mono http request doesn't like the forcing, test still valid since the default connection header value is empty
//ForceSetHeader (resp.Headers, "Connection", "Foo");
} ) ;
2015-01-13 10:44:36 +00:00
#pragma warning restore 4014
2014-08-13 10:39:27 +01:00
try {
Assert . IsTrue ( socket . ConnectAsync ( new Uri ( "ws://localhost:" + Port ) , CancellationToken . None ) . Wait ( 5000 ) ) ;
} catch ( AggregateException e ) {
AssertWebSocketException ( e , WebSocketError . Success ) ;
return ;
}
Assert . Fail ( "Should have thrown" ) ;
}
[Test]
2015-09-02 12:36:57 -04:00
[Category ("MobileNotWorking")] // The test hangs when ran as part of the entire BCL test suite. Works when only this fixture is ran
2014-08-13 10:39:27 +01:00
public void EchoTest ( )
{
const string Payload = "This is a websocket test" ;
Assert . AreEqual ( WebSocketState . None , socket . State ) ;
socket . ConnectAsync ( new Uri ( EchoServerUrl ) , CancellationToken . None ) . Wait ( ) ;
Assert . AreEqual ( WebSocketState . Open , socket . State ) ;
var sendBuffer = Encoding . ASCII . GetBytes ( Payload ) ;
Assert . IsTrue ( socket . SendAsync ( new ArraySegment < byte > ( sendBuffer ) , WebSocketMessageType . Text , true , CancellationToken . None ) . Wait ( 5000 ) ) ;
var receiveBuffer = new byte [ Payload . Length ] ;
var resp = socket . ReceiveAsync ( new ArraySegment < byte > ( receiveBuffer ) , CancellationToken . None ) . Result ;
Assert . AreEqual ( Payload . Length , resp . Count ) ;
Assert . IsTrue ( resp . EndOfMessage ) ;
Assert . AreEqual ( WebSocketMessageType . Text , resp . MessageType ) ;
Assert . AreEqual ( Payload , Encoding . ASCII . GetString ( receiveBuffer , 0 , resp . Count ) ) ;
Assert . IsTrue ( socket . CloseAsync ( WebSocketCloseStatus . NormalClosure , string . Empty , CancellationToken . None ) . Wait ( 5000 ) ) ;
Assert . AreEqual ( WebSocketState . Closed , socket . State ) ;
}
[Test]
2015-09-02 12:36:57 -04:00
[Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
2014-08-13 10:39:27 +01:00
public void CloseOutputAsyncTest ( )
{
Assert . IsTrue ( socket . ConnectAsync ( new Uri ( EchoServerUrl ) , CancellationToken . None ) . Wait ( 5000 ) ) ;
Assert . AreEqual ( WebSocketState . Open , socket . State ) ;
Assert . IsTrue ( socket . CloseOutputAsync ( WebSocketCloseStatus . NormalClosure , string . Empty , CancellationToken . None ) . Wait ( 5000 ) ) ;
Assert . AreEqual ( WebSocketState . CloseSent , socket . State ) ;
var resp = socket . ReceiveAsync ( new ArraySegment < byte > ( new byte [ 0 ] ) , CancellationToken . None ) . Result ;
Assert . AreEqual ( WebSocketState . Closed , socket . State ) ;
Assert . AreEqual ( WebSocketMessageType . Close , resp . MessageType ) ;
Assert . AreEqual ( WebSocketCloseStatus . NormalClosure , resp . CloseStatus ) ;
Assert . AreEqual ( string . Empty , resp . CloseStatusDescription ) ;
}
[Test]
2015-09-02 12:36:57 -04:00
[Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
2014-08-13 10:39:27 +01:00
public void CloseAsyncTest ( )
{
Assert . IsTrue ( socket . ConnectAsync ( new Uri ( EchoServerUrl ) , CancellationToken . None ) . Wait ( 5000 ) ) ;
Assert . AreEqual ( WebSocketState . Open , socket . State ) ;
Assert . IsTrue ( socket . CloseAsync ( WebSocketCloseStatus . NormalClosure , string . Empty , CancellationToken . None ) . Wait ( 5000 ) ) ;
Assert . AreEqual ( WebSocketState . Closed , socket . State ) ;
}
[Test, ExpectedException (typeof (InvalidOperationException))]
public void SendAsyncArgTest_NotConnected ( )
{
socket . SendAsync ( new ArraySegment < byte > ( new byte [ 0 ] ) , WebSocketMessageType . Text , true , CancellationToken . None ) ;
}
[Test, ExpectedException (typeof (ArgumentNullException))]
2015-09-02 12:36:57 -04:00
[Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
2014-08-13 10:39:27 +01:00
public void SendAsyncArgTest_NoArray ( )
{
Assert . IsTrue ( socket . ConnectAsync ( new Uri ( EchoServerUrl ) , CancellationToken . None ) . Wait ( 5000 ) ) ;
socket . SendAsync ( new ArraySegment < byte > ( ) , WebSocketMessageType . Text , true , CancellationToken . None ) ;
}
[Test, ExpectedException (typeof (InvalidOperationException))]
public void ReceiveAsyncArgTest_NotConnected ( )
{
socket . ReceiveAsync ( new ArraySegment < byte > ( new byte [ 0 ] ) , CancellationToken . None ) ;
}
[Test, ExpectedException (typeof (ArgumentNullException))]
2015-09-02 12:36:57 -04:00
[Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
2014-08-13 10:39:27 +01:00
public void ReceiveAsyncArgTest_NoArray ( )
{
Assert . IsTrue ( socket . ConnectAsync ( new Uri ( EchoServerUrl ) , CancellationToken . None ) . Wait ( 5000 ) ) ;
socket . ReceiveAsync ( new ArraySegment < byte > ( ) , CancellationToken . None ) ;
}
[Test]
2015-09-02 12:36:57 -04:00
[Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
2014-08-13 10:39:27 +01:00
public void ReceiveAsyncWrongState_Closed ( )
{
try {
Assert . IsTrue ( socket . ConnectAsync ( new Uri ( EchoServerUrl ) , CancellationToken . None ) . Wait ( 5000 ) ) ;
Assert . IsTrue ( socket . CloseAsync ( WebSocketCloseStatus . NormalClosure , string . Empty , CancellationToken . None ) . Wait ( 5000 ) ) ;
Assert . IsTrue ( socket . ReceiveAsync ( new ArraySegment < byte > ( new byte [ 0 ] ) , CancellationToken . None ) . Wait ( 5000 ) ) ;
} catch ( AggregateException e ) {
AssertWebSocketException ( e , WebSocketError . Success ) ;
return ;
}
Assert . Fail ( "Should have thrown" ) ;
}
[Test]
2015-09-02 12:36:57 -04:00
[Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
2014-08-13 10:39:27 +01:00
public void SendAsyncWrongState_Closed ( )
{
try {
Assert . IsTrue ( socket . ConnectAsync ( new Uri ( EchoServerUrl ) , CancellationToken . None ) . Wait ( 5000 ) ) ;
Assert . IsTrue ( socket . CloseAsync ( WebSocketCloseStatus . NormalClosure , string . Empty , CancellationToken . None ) . Wait ( 5000 ) ) ;
Assert . IsTrue ( socket . SendAsync ( new ArraySegment < byte > ( new byte [ 0 ] ) , WebSocketMessageType . Text , true , CancellationToken . None ) . Wait ( 5000 ) ) ;
} catch ( AggregateException e ) {
AssertWebSocketException ( e , WebSocketError . Success ) ;
return ;
}
Assert . Fail ( "Should have thrown" ) ;
}
[Test]
2015-09-02 12:36:57 -04:00
[Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
2014-08-13 10:39:27 +01:00
public void SendAsyncWrongState_CloseSent ( )
{
try {
Assert . IsTrue ( socket . ConnectAsync ( new Uri ( EchoServerUrl ) , CancellationToken . None ) . Wait ( 5000 ) ) ;
Assert . IsTrue ( socket . CloseOutputAsync ( WebSocketCloseStatus . NormalClosure , string . Empty , CancellationToken . None ) . Wait ( 5000 ) ) ;
Assert . IsTrue ( socket . SendAsync ( new ArraySegment < byte > ( new byte [ 0 ] ) , WebSocketMessageType . Text , true , CancellationToken . None ) . Wait ( 5000 ) ) ;
} catch ( AggregateException e ) {
AssertWebSocketException ( e , WebSocketError . Success ) ;
return ;
}
Assert . Fail ( "Should have thrown" ) ;
}
2015-04-07 09:35:12 +01:00
[Test]
[Category ("NotWorking")] // FIXME: test relies on unimplemented HttpListenerContext.AcceptWebSocketAsync (), reenable it when the method is implemented
public void SendAsyncEndOfMessageTest ( )
{
var cancellationToken = new CancellationTokenSource ( TimeSpan . FromSeconds ( 30 ) ) . Token ;
SendAsyncEndOfMessageTest ( false , WebSocketMessageType . Text , cancellationToken ) . Wait ( 5000 ) ;
SendAsyncEndOfMessageTest ( true , WebSocketMessageType . Text , cancellationToken ) . Wait ( 5000 ) ;
SendAsyncEndOfMessageTest ( false , WebSocketMessageType . Binary , cancellationToken ) . Wait ( 5000 ) ;
SendAsyncEndOfMessageTest ( true , WebSocketMessageType . Binary , cancellationToken ) . Wait ( 5000 ) ;
}
public async Task SendAsyncEndOfMessageTest ( bool expectedEndOfMessage , WebSocketMessageType webSocketMessageType , CancellationToken cancellationToken )
{
using ( var client = new ClientWebSocket ( ) ) {
// Configure the listener.
var serverReceive = HandleHttpWebSocketRequestAsync < WebSocketReceiveResult > ( async socket = > await socket . ReceiveAsync ( new ArraySegment < byte > ( new byte [ 32 ] ) , cancellationToken ) , cancellationToken ) ;
// Connect to the listener and make the request.
await client . ConnectAsync ( new Uri ( "ws://localhost:" + Port + "/" ) , cancellationToken ) ;
await client . SendAsync ( new ArraySegment < byte > ( Encoding . UTF8 . GetBytes ( "test" ) ) , webSocketMessageType , expectedEndOfMessage , cancellationToken ) ;
// Wait for the listener to handle the request and return its result.
var result = await serverReceive ;
// Cleanup and check results.
await client . CloseAsync ( WebSocketCloseStatus . NormalClosure , "Finished" , cancellationToken ) ;
Assert . AreEqual ( expectedEndOfMessage , result . EndOfMessage , "EndOfMessage should be " + expectedEndOfMessage ) ;
}
}
async Task < T > HandleHttpWebSocketRequestAsync < T > ( Func < WebSocket , Task < T > > action , CancellationToken cancellationToken )
{
var ctx = await this . listener . GetContextAsync ( ) ;
var wsContext = await ctx . AcceptWebSocketAsync ( null ) ;
var result = await action ( wsContext . WebSocket ) ;
await wsContext . WebSocket . CloseOutputAsync ( WebSocketCloseStatus . NormalClosure , "Finished" , cancellationToken ) ;
return result ;
}
2014-08-13 10:39:27 +01:00
async Task HandleHttpRequestAsync ( Action < HttpListenerRequest , HttpListenerResponse > handler )
{
var ctx = await listener . GetContextAsync ( ) ;
handler ( ctx . Request , ctx . Response ) ;
ctx . Response . Close ( ) ;
}
void AssertWebSocketException ( AggregateException e , WebSocketError error , Type inner = null )
{
var wsEx = e . InnerException as WebSocketException ;
Console . WriteLine ( e . InnerException . ToString ( ) ) ;
Assert . IsNotNull ( wsEx , "Not a websocketexception" ) ;
Assert . AreEqual ( error , wsEx . WebSocketErrorCode ) ;
if ( inner ! = null ) {
Assert . IsNotNull ( wsEx . InnerException ) ;
Assert . IsTrue ( inner . IsInstanceOfType ( wsEx . InnerException ) ) ;
}
}
void ForceSetHeader ( WebHeaderCollection headers , string name , string value )
{
if ( headerSetMethod = = null )
headerSetMethod = typeof ( WebHeaderCollection ) . GetMethod ( "AddValue" , BindingFlags . NonPublic ) ;
headerSetMethod . Invoke ( headers , new [ ] { name , value } ) ;
}
}
}