Skip to content

Commit 1f63ff7

Browse files
committed
Merge branch 'master' of github.com:joncloud/https
2 parents b596be9 + 01af984 commit 1f63ff7

File tree

9 files changed

+302
-15
lines changed

9 files changed

+302
-15
lines changed

azure-pipelines.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
trigger:
2+
- master
3+
4+
pool:
5+
vmImage: 'ubuntu-latest'
6+
7+
variables:
8+
solution: '**/*.sln'
9+
buildPlatform: 'Any CPU'
10+
buildConfiguration: 'Release'
11+
12+
steps:
13+
- task: DotNetCoreInstaller@1
14+
displayName: Install .NET Core 3.0 Preview
15+
inputs:
16+
includePreviewVersions: true
17+
packageType: sdk
18+
version: 3.0.100-preview7-012821
19+
20+
- task: DotNetCoreCLI@2
21+
displayName: Pack https
22+
inputs:
23+
command: pack
24+
publishWebProjects: false
25+
projects: '**/*.csproj'
26+
arguments: --configuration $(buildConfiguration) --output $(build.ArtifactStagingDirectory)
27+
28+
- task: DotNetCoreCLI@2
29+
displayName: Test https
30+
inputs:
31+
command: test
32+
projects: '**/*Test*.csproj'
33+
34+
- task: PublishBuildArtifacts@1
35+
inputs:
36+
PathtoPublish: '$(build.ArtifactStagingDirectory)'
37+
ArtifactName: 'https'
38+
Parallel: true
39+

src/https/Program.cs

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -261,20 +261,8 @@ public async Task<int> RunAsync(string[] args)
261261
var stdoutWriter = new StreamWriter(stdout) { AutoFlush = true };
262262
{
263263
var renderer = new Renderer(stdoutWriter, stderrWriter);
264-
265-
var http = options.IgnoreCertificate
266-
? new HttpClient(
267-
new HttpClientHandler
268-
{
269-
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
270-
}
271-
)
272-
: new HttpClient();
273264

274-
if (options.Timeout.HasValue)
275-
{
276-
http.Timeout = options.Timeout.Value;
277-
}
265+
var http = CreateHttpClient(options);
278266

279267
var request = new HttpRequestMessage(
280268
command.Method ?? HttpMethod.Get,
@@ -341,6 +329,35 @@ public async Task<int> RunAsync(string[] args)
341329

342330
return 0;
343331
}
332+
333+
static HttpClient CreateHttpClient(Options options)
334+
{
335+
var http = default(HttpClient);
336+
if (options.RequiresHandler)
337+
{
338+
var handler = new HttpClientHandler();
339+
if (options.IgnoreCertificate)
340+
{
341+
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
342+
}
343+
if (options.StopAutoRedirects)
344+
{
345+
handler.AllowAutoRedirect = false;
346+
}
347+
http = new HttpClient(handler);
348+
}
349+
else
350+
{
351+
http = new HttpClient();
352+
}
353+
354+
if (options.Timeout.HasValue)
355+
{
356+
http.Timeout = options.Timeout.Value;
357+
}
358+
359+
return http;
360+
}
344361
}
345362

346363
enum ContentType
@@ -358,16 +375,19 @@ class Options
358375
public TimeSpan? Timeout { get; }
359376
public bool Version { get; }
360377
public bool Help { get; }
378+
public bool StopAutoRedirects { get; }
361379

380+
public bool RequiresHandler => IgnoreCertificate || StopAutoRedirects;
362381

363-
public Options(ContentType requestContentType, string xmlRootName, bool ignoreCertificate, TimeSpan? timeout, bool version, bool help)
382+
public Options(ContentType requestContentType, string xmlRootName, bool ignoreCertificate, TimeSpan? timeout, bool version, bool help, bool stopAutoRedirects)
364383
{
365384
RequestContentType = requestContentType;
366385
XmlRootName = xmlRootName;
367386
IgnoreCertificate = ignoreCertificate;
368387
Timeout = timeout;
369388
Version = version;
370389
Help = help;
390+
StopAutoRedirects = stopAutoRedirects;
371391
}
372392

373393
public static IEnumerable<string> GetOptionHelp()
@@ -379,6 +399,7 @@ public static IEnumerable<string> GetOptionHelp()
379399
yield return "--timeout=<VALUE> Sets the timeout of the request using System.TimeSpan.TryParse (https://docs.microsoft.com/en-us/dotnet/api/system.timespan.parse)";
380400
yield return "--version Displays the application verison.";
381401
yield return "--xml=<ROOT_NAME> Renders the content arguments as application/xml using the optional xml root name.";
402+
yield return "--stop-auto-redirects Prevents redirects from automatically being processed.";
382403
}
383404

384405
static int GetArgValueIndex(string arg)
@@ -400,6 +421,7 @@ public static Options Parse(IEnumerable<string> args)
400421
var timeout = default(TimeSpan?);
401422
var help = false;
402423
var version = false;
424+
var stopAutoRedirects = false;
403425
foreach (var arg in args)
404426
{
405427
if (arg.StartsWith("--json"))
@@ -451,8 +473,12 @@ public static Options Parse(IEnumerable<string> args)
451473
{
452474
help = true;
453475
}
476+
else if (arg.StartsWith("--stop-auto-redirects"))
477+
{
478+
stopAutoRedirects = true;
479+
}
454480
}
455-
return new Options(requestContentType, xmlRootName, ignoreCertificate, timeout, version, help);
481+
return new Options(requestContentType, xmlRootName, ignoreCertificate, timeout, version, help, stopAutoRedirects);
456482
}
457483
}
458484

tests/https.Tests/Https.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System.IO;
2+
using System.Threading.Tasks;
3+
4+
namespace Https.Tests
5+
{
6+
public static class Https
7+
{
8+
public static async Task<HttpsResult> ExecuteAsync(params string[] args)
9+
{
10+
using var stdin = new MemoryStream();
11+
12+
return await ExecuteAsync(stdin, args);
13+
}
14+
15+
public static async Task<HttpsResult> ExecuteAsync(Stream stdin, params string[] args)
16+
{
17+
var stdout = new MemoryStream();
18+
var stderr = new MemoryStream();
19+
20+
var exitCode = await new Program(() => stderr, () => stdin, () => stdout, false)
21+
.RunAsync(args);
22+
23+
return new HttpsResult(exitCode, stdout, stderr);
24+
}
25+
}
26+
}

tests/https.Tests/HttpsResult.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
6+
namespace Https.Tests
7+
{
8+
public class HttpsResult : IDisposable
9+
{
10+
public int ExitCode { get; }
11+
public MemoryStream StdOut { get; }
12+
public string Status { get; }
13+
public IReadOnlyDictionary<string, string> Headers { get; }
14+
15+
public HttpsResult(int exitCode, MemoryStream stdout, MemoryStream stderr)
16+
{
17+
ExitCode = exitCode;
18+
19+
StdOut = stdout;
20+
StdOut.Position = 0;
21+
22+
stderr.Position = 0;
23+
var lines = new StreamReader(stderr)
24+
.ReadToEnd()
25+
.Split(Environment.NewLine);
26+
27+
Status = lines[0];
28+
29+
var headers = new Dictionary<string, string>();
30+
foreach (var line in lines.Skip(1))
31+
{
32+
var pos = line.IndexOf(':');
33+
if (pos > -1)
34+
{
35+
var key = line.Substring(0, pos);
36+
var value = line.Substring(pos + 2);
37+
headers[key] = value;
38+
}
39+
}
40+
Headers = headers;
41+
42+
stderr.Dispose();
43+
}
44+
45+
public void Dispose()
46+
{
47+
StdOut.Dispose();
48+
}
49+
}
50+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System.IO;
2+
using System.Threading.Tasks;
3+
using Xunit;
4+
5+
namespace Https.Tests
6+
{
7+
public class IntegrationTests : IClassFixture<WebHostFixture>
8+
{
9+
readonly WebHostFixture _fixture;
10+
public IntegrationTests(WebHostFixture fixture) =>
11+
_fixture = fixture;
12+
13+
[Fact]
14+
public async Task MirrorTests()
15+
{
16+
var args = new[]
17+
{
18+
"post", $"{_fixture.Url}/Mirror", "--json", "foo=bar", "lorem=ipsum"
19+
};
20+
21+
var result = await Https.ExecuteAsync(args);
22+
23+
var json = new StreamReader(result.StdOut).ReadToEnd();
24+
Assert.Equal("{\"foo\":\"bar\",\"lorem\":\"ipsum\"}", json);
25+
}
26+
27+
[Fact]
28+
public async Task RedirectTest_ShouldShow3XXResponse_GivenStopAutoRedirects()
29+
{
30+
var args = new[]
31+
{
32+
"get", "http://localhost:5000/Redirect", "--stop-auto-redirects"
33+
};
34+
35+
var result = await Https.ExecuteAsync(args);
36+
37+
Assert.Equal("HTTP/1.1 301 Moved Permanently", result.Status);
38+
Assert.Equal("http://localhost:5000/Mirror", result.Headers["Location"]);
39+
}
40+
}
41+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Microsoft.AspNetCore.Http;
2+
using System.Threading.Tasks;
3+
4+
namespace Https.Tests
5+
{
6+
class MirrorMiddleware
7+
{
8+
readonly RequestDelegate _next;
9+
public MirrorMiddleware(RequestDelegate next) =>
10+
_next = next;
11+
public async Task InvokeAsync(HttpContext context)
12+
{
13+
if (context.Request.Path.StartsWithSegments("/Mirror"))
14+
{
15+
if (!string.IsNullOrEmpty(context.Request.ContentType))
16+
{
17+
context.Response.ContentType = context.Request.ContentType;
18+
}
19+
20+
await context.Request.Body.CopyToAsync(context.Response.Body);
21+
}
22+
else
23+
{
24+
await _next(context);
25+
}
26+
}
27+
}
28+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Microsoft.AspNetCore.Http;
2+
using System.Threading.Tasks;
3+
4+
namespace Https.Tests
5+
{
6+
class RedirectMiddleware
7+
{
8+
readonly RequestDelegate _next;
9+
public RedirectMiddleware(RequestDelegate next) =>
10+
_next = next;
11+
public async Task InvokeAsync(HttpContext context)
12+
{
13+
if (context.Request.Path.StartsWithSegments("/Redirect"))
14+
{
15+
context.Response.StatusCode = StatusCodes.Status301MovedPermanently;
16+
context.Response.Headers.Add("Location", "http://localhost:5000/Mirror");
17+
}
18+
else
19+
{
20+
await _next(context);
21+
}
22+
}
23+
}
24+
}

tests/https.Tests/Startup.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Microsoft.AspNetCore.Builder;
2+
using Microsoft.AspNetCore.Hosting;
3+
using Microsoft.AspNetCore.Http;
4+
5+
namespace Https.Tests
6+
{
7+
public class Startup
8+
{
9+
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
10+
{
11+
app.UseMiddleware<RedirectMiddleware>();
12+
app.UseMiddleware<MirrorMiddleware>();
13+
14+
app.Run(async (context) =>
15+
{
16+
await context.Response.WriteAsync("Hello!");
17+
});
18+
}
19+
}
20+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using Microsoft.AspNetCore;
2+
using Microsoft.AspNetCore.Hosting;
3+
using System;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
7+
namespace Https.Tests
8+
{
9+
public class WebHostFixture : IDisposable
10+
{
11+
readonly IWebHost _webHost;
12+
readonly Task _task;
13+
readonly CancellationTokenSource _cts;
14+
public string Url { get; }
15+
16+
public WebHostFixture()
17+
{
18+
_webHost = WebHost.CreateDefaultBuilder()
19+
.UseUrls(Url = "http://localhost:5000")
20+
.UseStartup<Startup>()
21+
.Build();
22+
_cts = new CancellationTokenSource();
23+
_task = _webHost.RunAsync(_cts.Token);
24+
}
25+
26+
public async void Dispose()
27+
{
28+
await _webHost.StopAsync();
29+
_cts.Cancel();
30+
_cts.Dispose();
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)