2019-05-24 17:28:08 +03:00
/ /
2016-11-09 17:31:02 +03:00
// MessageHandlers.cs
/ /
using System ;
using System.Collections ;
using System.Collections.Generic ;
using System.Threading ;
using System.Threading.Tasks ;
using System.Net ;
using System.Net.Http ;
using System.Linq ;
using System.IO ;
using NUnit.Framework ;
2019-01-10 19:45:20 +03:00
using System.Net.Http.Headers ;
using System.Text ;
2019-09-05 23:03:10 +03:00
using Foundation ;
2017-01-12 21:13:56 +03:00
#if MONOMAC
using Foundation ;
#endif
2018-07-04 12:44:14 +03:00
using ObjCRuntime ;
2016-11-09 17:31:02 +03:00
namespace MonoTests.System.Net.Http
{
[TestFixture]
public class MessageHandlerTest
{
2017-07-13 15:24:36 +03:00
void PrintHandlerToTest ( )
{
#if ! __WATCHOS__
Console . WriteLine ( new HttpClientHandler ( ) ) ;
Console . WriteLine ( new CFNetworkHandler ( ) ) ;
#endif
Console . WriteLine ( new NSUrlSessionHandler ( ) ) ;
}
2016-11-09 17:31:02 +03:00
HttpMessageHandler GetHandler ( Type handler_type )
{
return ( HttpMessageHandler ) Activator . CreateInstance ( handler_type ) ;
}
[Test]
#if ! __WATCHOS__
[TestCase (typeof (HttpClientHandler))]
[TestCase (typeof (CFNetworkHandler))]
#endif
[TestCase (typeof (NSUrlSessionHandler))]
public void DnsFailure ( Type handlerType )
{
2018-07-04 12:44:14 +03:00
TestRuntime . AssertSystemVersion ( PlatformName . MacOSX , 10 , 9 , throwIfOtherPlatform : false ) ;
TestRuntime . AssertSystemVersion ( PlatformName . iOS , 7 , 0 , throwIfOtherPlatform : false ) ;
2018-07-03 18:09:49 +03:00
2017-07-13 15:24:36 +03:00
PrintHandlerToTest ( ) ;
2016-11-09 17:31:02 +03:00
bool done = false ;
2019-02-13 14:22:24 +03:00
string response = null ;
2016-11-09 17:31:02 +03:00
Exception ex = null ;
TestRuntime . RunAsync ( DateTime . Now . AddSeconds ( 30 ) , async ( ) = >
{
try {
HttpClient client = new HttpClient ( GetHandler ( handlerType ) ) ;
2019-02-13 14:22:24 +03:00
response = await client . GetStringAsync ( "http://doesnotexist.xamarin.com" ) ;
2016-11-09 17:31:02 +03:00
} catch ( Exception e ) {
ex = e ;
} finally {
done = true ;
}
} , ( ) = > done ) ;
2018-12-03 19:43:58 +03:00
Assert . IsTrue ( done , "Did not time out" ) ;
2019-02-13 14:22:24 +03:00
Assert . IsNull ( response , $"Response is not null {response}" ) ;
[httpclient] Change NSUrlSessionHandler and CFNetworkHandler to throw HttpRequestException. Fix #6439 (#6477)
Our 3 different handlers were inconsistent with each others. Only the
managed version, `HttpClientHandler`, was throwing the documented
`HttpRequestException` exception.
The inconsistency make this hard to consume from .net standard libraries
even more when the type is not, itself, available in .net standard
stack trace (for NSUrlSessionHandler)
```
2019-07-02 10:58:08.352 gh6439[18579:15880723] System.Net.WebException: The Internet connection appears to be offline. ---> Foundation.NSErrorException: Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo={_kCFStreamErrorCodeKey=50, NSUnderlyingError=0x283b6d350 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <55894EBD-B898-4803-9981-46317EEFE280>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <55894EBD-B898-4803-9981-46317EEFE280>.<1>"
), NSLocalizedDescription=The Internet connection appears to be offline., NSErrorFailingURLStringKey=https://www.microsoft.com/fr-ca/, NSErrorFailingURLKey=https://www.microsoft.com/fr-ca/, _kCFStreamErrorDomainKey=1}
--- End of inner exception stack trace ---
at System.Net.Http.NSUrlSessionHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) [0x001d4] in /Users/poupou/git/xcode11/xamarin-macios/src/Foundation/NSUrlSessionHandler.cs:541
at System.Net.Http.HttpClient.SendAsyncWorker (System.Net.Http.HttpRequestMessage request, System.Net.Http.HttpCompletionOption completionOption, System.Threading.CancellationToken cancellationToken) [0x0009e] in /Users/poupou/git/xcode11/xamarin-macios/external/mono/mcs/class/System.Net.Http/System.Net.Http/HttpClient.cs:281
```
stack trace (for CFNetworkHandler)
```
2019-07-02 11:04:35.407 gh6439[18580:15881497] CoreFoundation.CFException: The operation couldn’t be completed. Network is down
at System.Net.Http.CFNetworkHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken, System.Boolean isFirstRequest) [0x002f2] in /Users/poupou/git/xcode11/xamarin-macios/src/System.Net.Http/CFNetworkHandler.cs:266
at System.Net.Http.CFNetworkHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) [0x00034] in /Users/poupou/git/xcode11/xamarin-macios/src/System.Net.Http/CFNetworkHandler.cs:199
at System.Net.Http.HttpClient.SendAsyncWorker (System.Net.Http.HttpRequestMessage request, System.Net.Http.HttpCompletionOption completionOption, System.Threading.CancellationToken cancellationToken) [0x0009e] in /Users/poupou/git/xcode11/xamarin-macios/external/mono/mcs/class/System.Net.Http/System.Net.Http/HttpClient.cs:281
```
references:
* https://github.com/xamarin/xamarin-macios/issues/6439
* https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.postasync?view=netstandard-2.0#System_Net_Http_HttpClient_PostAsync_System_String_System_Net_Http_HttpContent_System_Threading_CancellationToken_
2019-07-04 16:50:27 +03:00
Assert . IsInstanceOfType ( typeof ( HttpRequestException ) , ex , "Exception" ) ;
2016-11-09 17:31:02 +03:00
}
2017-07-13 15:24:36 +03:00
2018-12-11 00:59:11 +03:00
#if ! __WATCHOS__
// ensure that we do get the same number of cookies as the managed handler
[TestCase]
public void TestNSUrlSessionHandlerCookies ( )
{
bool areEqual = false ;
var manageCount = 0 ;
var nativeCount = 0 ;
Exception ex = null ;
TestRuntime . RunAsync ( DateTime . Now . AddSeconds ( 30 ) , async ( ) = >
{
try {
var managedClient = new HttpClient ( new HttpClientHandler ( ) ) ;
2019-11-14 13:45:33 +03:00
var managedResponse = await managedClient . GetAsync ( NetworkResources . MicrosoftUrl ) ;
2018-12-11 00:59:11 +03:00
if ( managedResponse . Headers . TryGetValues ( "Set-Cookie" , out var managedCookies ) ) {
var nativeClient = new HttpClient ( new NSUrlSessionHandler ( ) ) ;
2019-11-14 13:45:33 +03:00
var nativeResponse = await nativeClient . GetAsync ( NetworkResources . MicrosoftUrl ) ;
2018-12-11 00:59:11 +03:00
if ( managedResponse . Headers . TryGetValues ( "Set-Cookie" , out var nativeCookies ) ) {
manageCount = managedCookies . Count ( ) ;
nativeCount = nativeCookies . Count ( ) ;
areEqual = manageCount = = nativeCount ;
} else {
manageCount = - 1 ;
nativeCount = - 1 ;
areEqual = false ;
}
}
} catch ( Exception e ) {
ex = e ;
}
} , ( ) = > areEqual ) ;
Assert . IsTrue ( areEqual , $"Cookies are different - Managed {manageCount} vs Native {nativeCount}" ) ;
Assert . IsNull ( ex , "Exception" ) ;
}
#endif
2019-01-10 19:45:20 +03:00
// ensure that if we have a redirect, we do not have the auth headers in the following requests
#if ! __WATCHOS__
[TestCase (typeof (HttpClientHandler))]
[TestCase (typeof (CFNetworkHandler))]
#endif
[TestCase (typeof (NSUrlSessionHandler))]
public void RedirectionWithAuthorizationHeaders ( Type handlerType )
{
TestRuntime . AssertSystemVersion ( PlatformName . MacOSX , 10 , 9 , throwIfOtherPlatform : false ) ;
TestRuntime . AssertSystemVersion ( PlatformName . iOS , 7 , 0 , throwIfOtherPlatform : false ) ;
bool containsAuthorizarion = false ;
bool containsHeaders = false ;
string json = "" ;
bool done = false ;
Exception ex = null ;
TestRuntime . RunAsync ( DateTime . Now . AddSeconds ( 30 ) , async ( ) = >
{
try {
HttpClient client = new HttpClient ( GetHandler ( handlerType ) ) ;
2019-11-14 13:45:33 +03:00
client . BaseAddress = NetworkResources . Httpbin . Uri ;
2019-01-10 19:45:20 +03:00
var byteArray = new UTF8Encoding ( ) . GetBytes ( "username:password" ) ;
client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Basic" , Convert . ToBase64String ( byteArray ) ) ;
2019-11-14 13:45:33 +03:00
var result = await client . GetAsync ( NetworkResources . Httpbin . GetRedirectUrl ( 3 ) ) ;
2019-01-10 19:45:20 +03:00
// get the data returned from httpbin which contains the details of the requested performed.
json = await result . Content . ReadAsStringAsync ( ) ;
containsAuthorizarion = json . Contains ( "Authorization" ) ;
containsHeaders = json . Contains ( "headers" ) ; // ensure we do have the headers in the response
} catch ( Exception e ) {
ex = e ;
} finally {
done = true ;
}
} , ( ) = > done ) ;
2019-09-13 19:33:49 +03:00
if ( ! done ) { // timeouts happen in the bots due to dns issues, connection issues etc.. we do not want to fail
2019-02-12 21:27:23 +03:00
Assert . Inconclusive ( "Request timedout." ) ;
2019-02-28 17:35:36 +03:00
} else if ( ! containsHeaders ) {
Assert . Inconclusive ( "Response from httpbin does not contain headers, therefore we cannot ensure that if the authoriation is present." ) ;
2019-02-12 21:27:23 +03:00
} else {
Assert . IsFalse ( containsAuthorizarion , $"Authorization header did reach the final destination. {json}" ) ;
Assert . IsNull ( ex , $"Exception {ex} for {json}" ) ;
}
2019-01-10 19:45:20 +03:00
}
2019-05-23 15:09:10 +03:00
#if ! __WATCHOS__
[TestCase (typeof (HttpClientHandler))]
#endif
[TestCase (typeof (NSUrlSessionHandler))]
public void RejectSslCertificatesServicePointManager ( Type handlerType )
{
TestRuntime . AssertSystemVersion ( PlatformName . MacOSX , 10 , 9 , throwIfOtherPlatform : false ) ;
TestRuntime . AssertSystemVersion ( PlatformName . iOS , 7 , 0 , throwIfOtherPlatform : false ) ;
2019-05-30 22:45:58 +03:00
#if __MACOS__
if ( handlerType = = typeof ( NSUrlSessionHandler ) & & TestRuntime . CheckSystemVersion ( PlatformName . MacOSX , 10 , 10 , 0 ) & & ! TestRuntime . CheckSystemVersion ( PlatformName . MacOSX , 10 , 11 , 0 ) )
Assert . Ignore ( "Fails on macOS 10.10: https://github.com/xamarin/maccore/issues/1645" ) ;
#endif
2019-05-23 15:09:10 +03:00
bool servicePointManagerCbWasExcuted = false ;
bool done = false ;
Exception ex = null ;
2019-05-24 17:28:08 +03:00
HttpResponseMessage result = null ;
2019-05-23 15:09:10 +03:00
var handler = GetHandler ( handlerType ) ;
if ( handler is NSUrlSessionHandler ns ) {
ns . TrustOverride + = ( a , b ) = > {
servicePointManagerCbWasExcuted = true ;
// return false, since we want to test that the exception is raised
return false ;
} ;
} else {
ServicePointManager . ServerCertificateValidationCallback = ( sender , certificate , chain , errors ) = > {
servicePointManagerCbWasExcuted = true ;
// return false, since we want to test that the exception is raised
return false ;
} ;
}
TestRuntime . RunAsync ( DateTime . Now . AddSeconds ( 30 ) , async ( ) = >
{
try {
HttpClient client = new HttpClient ( handler ) ;
2019-11-14 13:45:33 +03:00
client . BaseAddress = NetworkResources . Httpbin . Uri ;
2019-05-23 15:09:10 +03:00
var byteArray = new UTF8Encoding ( ) . GetBytes ( "username:password" ) ;
client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Basic" , Convert . ToBase64String ( byteArray ) ) ;
2019-11-14 13:45:33 +03:00
result = await client . GetAsync ( NetworkResources . Httpbin . GetRedirectUrl ( 3 ) ) ;
2019-05-23 15:09:10 +03:00
} catch ( Exception e ) {
ex = e ;
} finally {
done = true ;
ServicePointManager . ServerCertificateValidationCallback = null ;
}
} , ( ) = > done ) ;
2019-05-24 17:28:08 +03:00
if ( ! done ) { // timeouts happen in the bots due to dns issues, connection issues etc.. we do not want to fail
2019-05-23 15:09:10 +03:00
Assert . Inconclusive ( "Request timedout." ) ;
} else {
// assert the exception type
2019-10-14 21:14:26 +03:00
Assert . IsNotNull ( ex , ( result = = null ) ? "Expected exception is missing and got no result" : $"Expected exception but got {result.Content.ReadAsStringAsync ().Result}" ) ;
2019-05-23 15:09:10 +03:00
Assert . IsInstanceOfType ( typeof ( HttpRequestException ) , ex ) ;
Assert . IsNotNull ( ex . InnerException ) ;
Assert . IsInstanceOfType ( typeof ( WebException ) , ex . InnerException ) ;
}
}
#if ! __WATCHOS__
[TestCase (typeof (HttpClientHandler))]
#endif
[TestCase (typeof (NSUrlSessionHandler))]
public void AcceptSslCertificatesServicePointManager ( Type handlerType )
{
TestRuntime . AssertSystemVersion ( PlatformName . MacOSX , 10 , 9 , throwIfOtherPlatform : false ) ;
TestRuntime . AssertSystemVersion ( PlatformName . iOS , 7 , 0 , throwIfOtherPlatform : false ) ;
bool servicePointManagerCbWasExcuted = false ;
bool done = false ;
Exception ex = null ;
var handler = GetHandler ( handlerType ) ;
if ( handler is NSUrlSessionHandler ns ) {
ns . TrustOverride + = ( a , b ) = > {
servicePointManagerCbWasExcuted = true ;
return true ;
} ;
} else {
ServicePointManager . ServerCertificateValidationCallback = ( sender , certificate , chain , errors ) = > {
servicePointManagerCbWasExcuted = true ;
return true ;
} ;
}
TestRuntime . RunAsync ( DateTime . Now . AddSeconds ( 30 ) , async ( ) = >
{
try {
HttpClient client = new HttpClient ( handler ) ;
2019-11-14 13:45:33 +03:00
client . BaseAddress = NetworkResources . Httpbin . Uri ;
2019-05-23 15:09:10 +03:00
var byteArray = new UTF8Encoding ( ) . GetBytes ( "username:password" ) ;
client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Basic" , Convert . ToBase64String ( byteArray ) ) ;
2019-11-14 13:45:33 +03:00
var result = await client . GetAsync ( NetworkResources . Httpbin . GetRedirectUrl ( 3 ) ) ;
2019-05-23 15:09:10 +03:00
} catch ( Exception e ) {
ex = e ;
} finally {
done = true ;
ServicePointManager . ServerCertificateValidationCallback = null ;
}
} , ( ) = > done ) ;
2019-05-24 17:28:08 +03:00
if ( ! done ) { // timeouts happen in the bots due to dns issues, connection issues etc.. we do not want to fail
2019-05-23 15:09:10 +03:00
Assert . Inconclusive ( "Request timedout." ) ;
} else {
// assert that we did not get an exception
if ( ex ! = null & & ex . InnerException ! = null ) {
// we could get here.. if we have a diff issue, in that case, lets get the exception message and assert is not the trust issue
Assert . AreNotEqual ( ex . InnerException . Message , "Error: TrustFailure" ) ;
}
}
}
2019-09-05 23:03:10 +03:00
[Test]
public void AssertDefaultValuesNSUrlSessionHandler ( )
{
using ( var handler = new NSUrlSessionHandler ( ) ) {
Assert . True ( handler . AllowAutoRedirect , "Default redirects value" ) ;
Assert . True ( handler . AllowsCellularAccess , "Default cellular data value." ) ;
}
using ( var config = NSUrlSessionConfiguration . DefaultSessionConfiguration ) {
config . AllowsCellularAccess = false ;
using ( var handler = new NSUrlSessionHandler ( config ) ) {
Assert . False ( handler . AllowsCellularAccess , "Configuration cellular data value." ) ;
}
}
}
2018-12-11 00:59:11 +03:00
}
}