Skip to content

Commit 6db4def

Browse files
authored
Merge pull request #48 from WeihanLi/dev
0.5.1
2 parents a5ff34d + 025dc45 commit 6db4def

File tree

12 files changed

+215
-190
lines changed

12 files changed

+215
-190
lines changed

build/version.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<VersionMajor>0</VersionMajor>
44
<VersionMinor>5</VersionMinor>
5-
<VersionPatch>0</VersionPatch>
5+
<VersionPatch>1</VersionPatch>
66
<VersionPrefix>$(VersionMajor).$(VersionMinor).$(VersionPatch)</VersionPrefix>
77
<InformationalVersion>$(PackageVersion)</InformationalVersion>
88
</PropertyGroup>

src/HTTPie/Middleware/HttpSSLMiddleware.cs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,27 @@ public HttpSslMiddleware(HttpRequestModel requestModel)
1616
_requestModel = requestModel;
1717
}
1818

19-
private static readonly Option<bool> DisableSslVerifyOption = new(new[] { "--no-verify", "--verify=no" }, "disable ssl cert check");
20-
private static readonly Option<SslProtocols> SslProtocalOption = new("--ssl", "specific the ssl protocols, ssl3, tls, tls1.1, tls1.2, tls1.3");
19+
private static readonly Option<bool> DisableSslVerifyOption =
20+
new(new[] { "--no-verify", "--verify=no" }, "disable ssl cert check");
2121

22-
public Option[] SupportedOptions() => new Option[]
23-
{
24-
DisableSslVerifyOption,
25-
SslProtocalOption,
26-
};
22+
private static readonly Option<SslProtocols?> SslProtocalOption =
23+
new("--ssl", "specific the ssl protocols, ssl3, tls, tls1.1, tls1.2, tls1.3");
24+
25+
public Option[] SupportedOptions() => new Option[] { DisableSslVerifyOption, SslProtocalOption, };
2726

2827
public Task Invoke(HttpClientHandler httpClientHandler, Func<HttpClientHandler, Task> next)
2928
{
30-
if (_requestModel.Options.Contains("--verify=no")
31-
|| _requestModel.ParseResult.HasOption(DisableSslVerifyOption))
29+
if (_requestModel.ParseResult.HasOption(DisableSslVerifyOption))
3230
{
3331
// ignore server cert
3432
httpClientHandler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;
3533
}
34+
3635
// sslProtocols
37-
var sslOption = _requestModel.Options.FirstOrDefault(x => x.StartsWith("--ssl="))?["--ssl=".Length..];
38-
if (!string.IsNullOrEmpty(sslOption))
36+
var sslOption = _requestModel.ParseResult.GetValueForOption(SslProtocalOption);
37+
if (sslOption.HasValue)
3938
{
40-
sslOption = sslOption.Replace(".", string.Empty);
41-
if (Enum.TryParse(sslOption, out SslProtocols sslProtocols))
42-
httpClientHandler.SslProtocols = sslProtocols;
39+
httpClientHandler.SslProtocols = sslOption.Value;
4340
}
4441

4542
return next(httpClientHandler);

src/HTTPie/Models/HttpContext.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license.
33

44
using Newtonsoft.Json;
5+
using System.CommandLine.Invocation;
56
using WeihanLi.Common.Abstractions;
67

78
namespace HTTPie.Models;
@@ -25,7 +26,12 @@ public HttpContext(HttpRequestModel request, HttpResponseModel? response = null)
2526

2627
[System.Text.Json.Serialization.JsonIgnore]
2728
[JsonIgnore]
28-
public CancellationToken CancellationToken { get; set; }
29+
public CancellationToken CancellationToken => InvocationContext.GetCancellationToken();
30+
31+
[System.Text.Json.Serialization.JsonIgnore]
32+
[JsonIgnore]
33+
public InvocationContext InvocationContext { get; set; } = null!;
34+
2935
public IDictionary<string, object?> Properties { get; } = new Dictionary<string, object?>();
3036

3137
public void UpdateFlag(string flagName, bool value)

src/HTTPie/Program.cs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@
2929
}
3030
builder.SetMinimumLevel(debugEnabled ? LogLevel.Debug : LogLevel.Warning);
3131
});
32-
serviceCollection.RegisterHTTPieServices();
32+
serviceCollection.RegisterApplicationServices();
3333
await using var services = serviceCollection.BuildServiceProvider();
34-
DependencyResolver.SetDependencyResolver(services);
35-
Helpers.InitializeSupportOptions(services);
36-
if (args is not { Length: > 0 })
34+
35+
// output helps when no argument or there's only "-h"/"/h"
36+
if (args is not { Length: > 0 } ||
37+
(args.Length == 1 && args[0] is "-h" or "/h"))
3738
{
3839
args = new[] { "--help" };
3940
}
@@ -45,12 +46,4 @@
4546

4647
var logger = services.GetRequiredService<ILogger>();
4748
logger.PrintInputParameters(args.StringJoin(";"));
48-
try
49-
{
50-
return await services.Handle(args);
51-
}
52-
catch (Exception e)
53-
{
54-
logger.InvokeRequestException(e);
55-
return -1;
56-
}
49+
return await services.Handle(args);

src/HTTPie/Utilities/Helpers.cs

Lines changed: 93 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.Extensions.DependencyInjection.Extensions;
1010
using Microsoft.Extensions.Logging;
1111
using System.CommandLine.Builder;
12+
using System.CommandLine.Invocation;
1213
using System.CommandLine.IO;
1314
using System.Text.Encodings.Web;
1415
using System.Text.Json;
@@ -34,8 +35,6 @@ public static class Helpers
3435
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
3536
};
3637

37-
private static readonly HashSet<Option> SupportedOptions = new();
38-
3938
private static IServiceCollection AddHttpHandlerMiddleware<THttpHandlerMiddleware>(
4039
this IServiceCollection serviceCollection)
4140
where THttpHandlerMiddleware : IHttpHandlerMiddleware
@@ -63,82 +62,110 @@ private static IServiceCollection AddResponseMiddleware<TResponseMiddleware>(
6362
return serviceCollection;
6463
}
6564

66-
public static void InitializeSupportOptions(IServiceProvider serviceProvider)
65+
private static Parser ConstructCommand(this IServiceProvider serviceProvider, Func<InvocationContext, Task>? handler = null)
6766
{
68-
if (SupportedOptions.Count == 0)
69-
{
70-
foreach (var option in
71-
serviceProvider
72-
.GetServices<IHttpHandlerMiddleware>().SelectMany(x => x.SupportedOptions())
73-
.Union(serviceProvider.GetServices<IRequestMiddleware>().SelectMany(x => x.SupportedOptions())
74-
.Union(serviceProvider.GetServices<IResponseMiddleware>().SelectMany(x => x.SupportedOptions()))
75-
.Union(serviceProvider.GetRequiredService<IOutputFormatter>().SupportedOptions())
76-
.Union(serviceProvider.GetRequiredService<IRequestExecutor>().SupportedOptions()))
77-
.Union(serviceProvider.GetRequiredService<ILoadTestExporterSelector>().SupportedOptions())
78-
.Union(serviceProvider.GetServices<ILoadTestExporter>().SelectMany(x => x.SupportedOptions()))
79-
)
80-
{
81-
SupportedOptions.Add(option);
82-
}
83-
}
84-
var command = InitializeCommand();
67+
var command = InitializeCommandInternal(serviceProvider, handler);
8568
var builder = new CommandLineBuilder(command);
86-
builder.UseDefaults();
87-
_commandParser = builder.Build();
69+
// builder.UseDefaults();
70+
builder
71+
.UseHelp("--help", "-?", "/?")
72+
.UseEnvironmentVariableDirective()
73+
.UseParseDirective()
74+
.UseSuggestDirective()
75+
.RegisterWithDotnetSuggest()
76+
.UseTypoCorrections()
77+
.UseParseErrorReporting()
78+
.UseExceptionHandler()
79+
.CancelOnProcessTermination()
80+
.AddMiddleware(async (invocationContext, next) =>
81+
{
82+
var context = serviceProvider.GetRequiredService<HttpContext>();
83+
context.InvocationContext = invocationContext;
84+
85+
var requestModel = context.Request;
86+
requestModel.ParseResult = invocationContext.ParseResult;
87+
88+
var method = requestModel.ParseResult.UnmatchedTokens
89+
.FirstOrDefault(x => HttpMethods.Contains(x));
90+
if (!string.IsNullOrEmpty(method))
91+
{
92+
requestModel.Method = new HttpMethod(method);
93+
}
94+
// Url
95+
requestModel.Url = requestModel.ParseResult.UnmatchedTokens.FirstOrDefault(x =>
96+
!x.StartsWith("-", StringComparison.Ordinal)
97+
&& !HttpMethods.Contains(x))
98+
?? string.Empty;
99+
if (string.IsNullOrEmpty(requestModel.Url))
100+
{
101+
throw new InvalidOperationException("The request url can not be null");
102+
}
103+
requestModel.RequestItems = requestModel.ParseResult.UnmatchedTokens
104+
.Except(new[] { method, requestModel.Url })
105+
.WhereNotNull()
106+
.Where(x => !x.StartsWith('-'))
107+
.ToArray();
108+
109+
await next(invocationContext);
110+
})
111+
;
112+
return builder.Build();
88113
}
89114

90-
private static Parser _commandParser = null!;
91-
92-
private static Command InitializeCommand()
115+
private static Command InitializeCommandInternal(IServiceProvider serviceProvider, Func<InvocationContext, Task>? handler = null)
93116
{
94117
var command = new RootCommand()
95118
{
96119
Name = "http",
97120
};
98-
//var methodArgument = new Argument<HttpMethod>("method")
99-
//{
100-
// Description = "Request method",
101-
// Arity = ArgumentArity.ZeroOrOne,
102-
//};
103-
//methodArgument.SetDefaultValue(HttpMethod.Get.Method);
104-
//var allowedMethods = HttpMethods.ToArray();
105-
//methodArgument.AddSuggestions(allowedMethods);
106-
107-
//command.AddArgument(methodArgument);
108-
//var urlArgument = new Argument<string>("url")
109-
//{
110-
// Description = "Request url",
111-
// Arity = ArgumentArity.ExactlyOne
112-
//};
113-
//command.AddArgument(urlArgument);
114121

115-
foreach (var option in SupportedOptions)
122+
// var methodArgument = new Argument<HttpMethod>("method")
123+
// {
124+
// Description = "Request method",
125+
// Arity = ArgumentArity.ZeroOrOne,
126+
// };
127+
// methodArgument.SetDefaultValue(HttpMethod.Get.Method);
128+
// var allowedMethods = HttpMethods.ToArray();
129+
// methodArgument.AddCompletions(allowedMethods);
130+
// command.AddArgument(methodArgument);
131+
132+
// var urlArgument = new Argument<string>("url")
133+
// {
134+
// Description = "Request url",
135+
// Arity = ArgumentArity.ExactlyOne
136+
// };
137+
// command.AddArgument(urlArgument);
138+
139+
// options
140+
foreach (var option in
141+
serviceProvider
142+
.GetServices<IHttpHandlerMiddleware>().SelectMany(x => x.SupportedOptions())
143+
.Union(serviceProvider.GetServices<IRequestMiddleware>().SelectMany(x => x.SupportedOptions())
144+
.Union(serviceProvider.GetServices<IResponseMiddleware>().SelectMany(x => x.SupportedOptions()))
145+
.Union(serviceProvider.GetRequiredService<IOutputFormatter>().SupportedOptions())
146+
.Union(serviceProvider.GetRequiredService<IRequestExecutor>().SupportedOptions()))
147+
.Union(serviceProvider.GetRequiredService<ILoadTestExporterSelector>().SupportedOptions())
148+
.Union(serviceProvider.GetServices<ILoadTestExporter>().SelectMany(x => x.SupportedOptions()))
149+
)
116150
{
117151
command.AddOption(option);
118152
}
119-
command.SetHandler(async invocationContext =>
120-
{
121-
try
122-
{
123-
var context = DependencyResolver.ResolveRequiredService<HttpContext>();
124-
context.CancellationToken = invocationContext.GetCancellationToken();
125-
await DependencyResolver.ResolveRequiredService<IRequestExecutor>()
126-
.ExecuteAsync(context);
127-
var output = await DependencyResolver.ResolveRequiredService<IOutputFormatter>()
128-
.GetOutput(context);
129-
invocationContext.Console.Out.WriteLine(output.Trim());
130-
}
131-
catch (Exception e)
132-
{
133-
invocationContext.Console.Error.WriteLine($"Unhandled exception: {e}");
134-
}
135-
});
136153
command.TreatUnmatchedTokensAsErrors = false;
154+
155+
handler ??= async invocationContext =>
156+
{
157+
var context = serviceProvider.ResolveRequiredService<HttpContext>();
158+
await serviceProvider.ResolveRequiredService<IRequestExecutor>()
159+
.ExecuteAsync(context);
160+
var output = await serviceProvider.ResolveRequiredService<IOutputFormatter>()
161+
.GetOutput(context);
162+
invocationContext.Console.Out.WriteLine(output.Trim());
163+
};
164+
command.SetHandler(handler);
137165
return command;
138166
}
139167

140-
// ReSharper disable once InconsistentNaming
141-
public static IServiceCollection RegisterHTTPieServices(this IServiceCollection serviceCollection)
168+
public static IServiceCollection RegisterApplicationServices(this IServiceCollection serviceCollection)
142169
{
143170
serviceCollection
144171
.AddSingleton<IRequestExecutor, RequestExecutor>()
@@ -204,53 +231,15 @@ public static IServiceCollection RegisterHTTPieServices(this IServiceCollection
204231
;
205232
}
206233

207-
public static void InitRequestModel(HttpContext httpContext, string commandLine)
208-
=> InitRequestModel(httpContext, CommandLineStringSplitter.Instance.Split(commandLine).ToArray());
209-
210-
public static void InitRequestModel(HttpContext httpContext, string[] args)
211-
{
212-
// should output helps
213-
if (args.Contains("-h") || args.Contains("-?") || args.Contains("--help"))
214-
{
215-
return;
216-
}
217-
var requestModel = httpContext.Request;
218-
requestModel.ParseResult = _commandParser.Parse(args);
219-
220-
var method = requestModel.ParseResult.UnmatchedTokens.FirstOrDefault(x => HttpMethods.Contains(x));
221-
if (!string.IsNullOrEmpty(method))
222-
{
223-
requestModel.Method = new HttpMethod(method);
224-
}
225-
// Url
226-
requestModel.Url = requestModel.ParseResult.UnmatchedTokens.FirstOrDefault(x =>
227-
!x.StartsWith("-", StringComparison.Ordinal)
228-
&& !HttpMethods.Contains(x))
229-
?? string.Empty;
230-
if (string.IsNullOrEmpty(requestModel.Url))
231-
{
232-
throw new InvalidOperationException("The request url can not be null");
233-
}
234-
requestModel.Options = args
235-
.Where(x => x.StartsWith('-'))
236-
.ToArray();
237-
#nullable disable
238-
requestModel.RequestItems = requestModel.ParseResult.UnmatchedTokens
239-
.Except(new[] { method, requestModel.Url })
240-
.Where(x => !x.StartsWith('-'))
241-
.ToArray();
242-
#nullable restore
243-
}
244-
245234
public static async Task<int> Handle(this IServiceProvider services, string[] args)
246235
{
247-
InitRequestModel(services.GetRequiredService<HttpContext>(), args);
248-
return await _commandParser.InvokeAsync(args);
236+
var commandParser = services.ConstructCommand();
237+
return await commandParser.InvokeAsync(args);
249238
}
250239

251-
public static async Task<int> Handle(this IServiceProvider services, string commandLine)
240+
public static async Task<int> Handle(this IServiceProvider services, string commandLine, Func<InvocationContext, Task>? handler = null)
252241
{
253-
InitRequestModel(services.GetRequiredService<HttpContext>(), commandLine);
254-
return await _commandParser.InvokeAsync(commandLine);
242+
var commandParser = services.ConstructCommand(handler);
243+
return await commandParser.InvokeAsync(commandLine);
255244
}
256245
}

tests/HTTPie.IntegrationTest/HttpTestBase.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@ protected HttpTestBase()
1717
{
1818
Services = new ServiceCollection()
1919
.AddLogging()
20-
.RegisterHTTPieServices()
20+
.RegisterApplicationServices()
2121
.BuildServiceProvider();
22-
DependencyResolver.SetDependencyResolver(Services);
23-
Helpers.InitializeSupportOptions(Services);
2422
}
2523

2624
protected IServiceProvider Services { get; }
@@ -42,12 +40,7 @@ protected HttpResponseModel GetResponse()
4240

4341
protected async Task<int> Handle(string input)
4442
{
45-
return await Helpers.Handle(Services, input);
46-
}
47-
48-
protected async Task<int> Handle(string[] args)
49-
{
50-
return await Services.Handle(args);
43+
return await Services.Handle(input);
5144
}
5245

5346
protected async Task<string> GetOutput(string input)

tests/HTTPie.UnitTest/HTTPie.UnitTest.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<PropertyGroup>
44
<TargetFramework>net6.0</TargetFramework>
55
<IsPackable>false</IsPackable>
6+
<DefineConstants>$(DefineConstants);UnitTest</DefineConstants>
67
</PropertyGroup>
78
<ItemGroup>
89
<Using Include="HTTPie" />

0 commit comments

Comments
 (0)