8
8
using System . Linq ;
9
9
using System . Net ;
10
10
using System . Net . Http ;
11
+ using System . Net . Security ;
11
12
using System . Threading . Tasks ;
12
13
using AngleSharp . Dom . Html ;
13
14
using AngleSharp . Parser . Html ;
@@ -33,6 +34,7 @@ public class AspNetProcess : IDisposable
33
34
34
35
private string _certificatePath ;
35
36
private string _certificatePassword = Guid . NewGuid ( ) . ToString ( ) ;
37
+ private string _certificateThumbprint ;
36
38
37
39
internal readonly Uri ListeningUri ;
38
40
internal ProcessEx Process { get ; }
@@ -46,22 +48,21 @@ public AspNetProcess(
46
48
bool hasListeningUri = true ,
47
49
ILogger logger = null )
48
50
{
51
+ _certificatePath = Path . Combine ( workingDirectory , $ "{ Guid . NewGuid ( ) } .pfx") ;
52
+ EnsureDevelopmentCertificates ( ) ;
53
+
49
54
_output = output ;
50
55
_httpClient = new HttpClient ( new HttpClientHandler ( )
51
56
{
52
57
AllowAutoRedirect = true ,
53
58
UseCookies = true ,
54
59
CookieContainer = new CookieContainer ( ) ,
55
- ServerCertificateCustomValidationCallback = HttpClientHandler . DangerousAcceptAnyServerCertificateValidator ,
60
+ ServerCertificateCustomValidationCallback = ( request , certificate , chain , errors ) => ( certificate . Subject != "CN=localhost" && errors == SslPolicyErrors . None ) || certificate ? . Thumbprint == _certificateThumbprint ,
56
61
} )
57
62
{
58
63
Timeout = TimeSpan . FromMinutes ( 2 )
59
64
} ;
60
65
61
- _certificatePath = Path . Combine ( workingDirectory , $ "{ Guid . NewGuid ( ) } .pfx") ;
62
-
63
- EnsureDevelopmentCertificates ( ) ;
64
-
65
66
output . WriteLine ( "Running ASP.NET application..." ) ;
66
67
67
68
var arguments = published ? $ "exec { dllPath } " : "run" ;
@@ -70,8 +71,8 @@ public AspNetProcess(
70
71
71
72
var finalEnvironmentVariables = new Dictionary < string , string > ( environmentVariables )
72
73
{
73
- [ "ASPNETCORE_KESTREL__CERTIFICATES__DEFAULT__PATH " ] = _certificatePath ,
74
- [ "ASPNETCORE_KESTREL__CERTIFICATES__DEFAULT__PASSWORD " ] = _certificatePassword
74
+ [ "ASPNETCORE_Kestrel__Certificates__Default__Path " ] = _certificatePath ,
75
+ [ "ASPNETCORE_Kestrel__Certificates__Default__Password " ] = _certificatePassword
75
76
} ;
76
77
77
78
Process = ProcessEx . Run ( output , workingDirectory , DotNetMuxer . MuxerPathOrDefault ( ) , arguments , envVars : finalEnvironmentVariables ) ;
@@ -81,8 +82,8 @@ public AspNetProcess(
81
82
if ( hasListeningUri )
82
83
{
83
84
logger ? . LogInformation ( "AspNetProcess - Getting listening uri" ) ;
84
- ListeningUri = GetListeningUri ( output ) ?? throw new InvalidOperationException ( "Couldn't find the listening URL." ) ;
85
- logger ? . LogInformation ( $ "AspNetProcess - Got { ListeningUri . ToString ( ) } ") ;
85
+ ListeningUri = ResolveListeningUrl ( output ) ;
86
+ logger ? . LogInformation ( $ "AspNetProcess - Got { ListeningUri } ") ;
86
87
}
87
88
}
88
89
@@ -91,6 +92,7 @@ internal void EnsureDevelopmentCertificates()
91
92
var now = DateTimeOffset . Now ;
92
93
var manager = CertificateManager . Instance ;
93
94
var certificate = manager . CreateAspNetCoreHttpsDevelopmentCertificate ( now , now . AddYears ( 1 ) ) ;
95
+ _certificateThumbprint = certificate . Thumbprint ;
94
96
manager . ExportCertificate ( certificate , path : _certificatePath , includePrivateKey : true , _certificatePassword ) ;
95
97
}
96
98
@@ -134,13 +136,13 @@ public async Task AssertPagesOk(IEnumerable<Page> pages)
134
136
135
137
public async Task ContainsLinks ( Page page )
136
138
{
137
- var response = await RequestWithRetries ( client =>
139
+ var response = await RetryHelper . RetryRequest ( async ( ) =>
138
140
{
139
141
var request = new HttpRequestMessage (
140
142
HttpMethod . Get ,
141
143
new Uri ( ListeningUri , page . Url ) ) ;
142
- return client . SendAsync ( request ) ;
143
- } , _httpClient ) ;
144
+ return await _httpClient . SendAsync ( request ) ;
145
+ } , logger : NullLogger . Instance ) ;
144
146
145
147
Assert . Equal ( HttpStatusCode . OK , response . StatusCode ) ;
146
148
var parser = new HtmlParser ( ) ;
@@ -173,7 +175,7 @@ public async Task ContainsLinks(Page page)
173
175
Assert . True ( string . Equals ( anchor . Href , expectedLink ) , $ "Expected next link to be { expectedLink } but it was { anchor . Href } .") ;
174
176
var result = await RetryHelper . RetryRequest ( async ( ) =>
175
177
{
176
- return await RequestWithRetries ( client => client . GetAsync ( anchor . Href ) , _httpClient ) ;
178
+ return await _httpClient . GetAsync ( anchor . Href ) ;
177
179
} , logger : NullLogger . Instance ) ;
178
180
179
181
Assert . True ( IsSuccessStatusCode ( result ) , $ "{ anchor . Href } is a broken link!") ;
@@ -203,7 +205,7 @@ private async Task<T> RequestWithRetries<T>(Func<HttpClient, Task<T>> requester,
203
205
throw new InvalidOperationException ( "Max retries reached." ) ;
204
206
}
205
207
206
- private Uri GetListeningUri ( ITestOutputHelper output )
208
+ private Uri ResolveListeningUrl ( ITestOutputHelper output )
207
209
{
208
210
// Wait until the app is accepting HTTP requests
209
211
output . WriteLine ( "Waiting until ASP.NET application is accepting connections..." ) ;
@@ -232,21 +234,27 @@ private Uri GetListeningUri(ITestOutputHelper output)
232
234
233
235
private string GetListeningMessage ( )
234
236
{
237
+ var buffer = new List < string > ( ) ;
235
238
try
236
239
{
237
- return Process
238
- // This will timeout at most after 5 minutes.
239
- . OutputLinesAsEnumerable
240
- . Where ( line => line != null )
241
- // This used to do StartsWith, but this is less strict and can prevent issues (very rare) where
242
- // console logging interleaves with other console output in a bad way. For example:
243
- // dbugNow listening on: http://127.0.0.1:12857
244
- . FirstOrDefault ( line => line . Trim ( ) . Contains ( ListeningMessagePrefix , StringComparison . Ordinal ) ) ;
240
+ foreach ( var line in Process . OutputLinesAsEnumerable )
241
+ {
242
+ if ( line != null )
243
+ {
244
+ buffer . Add ( line ) ;
245
+ if ( line . Trim ( ) . Contains ( ListeningMessagePrefix , StringComparison . Ordinal ) )
246
+ {
247
+ return line ;
248
+ }
249
+ }
250
+ }
245
251
}
246
252
catch ( OperationCanceledException )
247
253
{
248
- return null ;
249
254
}
255
+
256
+ throw new InvalidOperationException ( @$ "Couldn't find listening url:
257
+ { string . Join ( Environment . NewLine , buffer ) } ") ;
250
258
}
251
259
252
260
private bool IsSuccessStatusCode ( HttpResponseMessage response )
@@ -260,14 +268,13 @@ public Task AssertOk(string requestUrl)
260
268
public Task AssertNotFound ( string requestUrl )
261
269
=> AssertStatusCode ( requestUrl , HttpStatusCode . NotFound ) ;
262
270
263
- internal Task < HttpResponseMessage > SendRequest ( string path )
264
- {
265
- return RequestWithRetries ( client => client . GetAsync ( new Uri ( ListeningUri , path ) ) , _httpClient ) ;
266
- }
271
+ internal Task < HttpResponseMessage > SendRequest ( string path ) =>
272
+ RetryHelper . RetryRequest ( ( ) => _httpClient . GetAsync ( new Uri ( ListeningUri , path ) ) , logger : NullLogger . Instance ) ;
267
273
268
274
public async Task AssertStatusCode ( string requestUrl , HttpStatusCode statusCode , string acceptContentType = null )
269
275
{
270
- var response = await RequestWithRetries ( client => {
276
+ var response = await RetryHelper . RetryRequest ( ( ) =>
277
+ {
271
278
var request = new HttpRequestMessage (
272
279
HttpMethod . Get ,
273
280
new Uri ( ListeningUri , requestUrl ) ) ;
@@ -277,8 +284,9 @@ public async Task AssertStatusCode(string requestUrl, HttpStatusCode statusCode,
277
284
request . Headers . Add ( "Accept" , acceptContentType ) ;
278
285
}
279
286
280
- return client . SendAsync ( request ) ;
281
- } , _httpClient ) ;
287
+ return _httpClient . SendAsync ( request ) ;
288
+ } , logger : NullLogger . Instance ) ;
289
+
282
290
Assert . True ( statusCode == response . StatusCode , $ "Expected { requestUrl } to have status '{ statusCode } ' but it was '{ response . StatusCode } '.") ;
283
291
}
284
292
0 commit comments