Skip to content

Commit b741026

Browse files
committed
Refactor command handling
1 parent adb4337 commit b741026

File tree

11 files changed

+207
-186
lines changed

11 files changed

+207
-186
lines changed

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: 85 additions & 100 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;
@@ -27,15 +28,13 @@ public static class Helpers
2728
HttpMethod.Delete.Method,
2829
HttpMethod.Options.Method
2930
};
30-
31+
3132
public static readonly JsonSerializerOptions JsonSerializerOptions = new()
3233
{
3334
WriteIndented = true,
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,26 +62,11 @@ 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);
69+
// builder.UseDefaults();
8670
builder
8771
.UseHelp("--help", "-?", "/?")
8872
.UseEnvironmentVariableDirective()
@@ -95,54 +79,93 @@ public static void InitializeSupportOptions(IServiceProvider serviceProvider)
9579
.CancelOnProcessTermination()
9680
.AddMiddleware(async (invocationContext, next) =>
9781
{
98-
var context = DependencyResolver.ResolveRequiredService<HttpContext>();
99-
context.CancellationToken = invocationContext.GetCancellationToken();
100-
await DependencyResolver.ResolveRequiredService<IRequestExecutor>()
101-
.ExecuteAsync(context);
102-
var output = await DependencyResolver.ResolveRequiredService<IOutputFormatter>()
103-
.GetOutput(context);
104-
invocationContext.Console.Out.WriteLine(output.Trim());
105-
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+
106109
await next(invocationContext);
107-
});
108-
_commandParser = builder.Build();
110+
})
111+
;
112+
return builder.Build();
109113
}
110114

111-
private static Parser _commandParser = null!;
112-
113-
private static Command InitializeCommand()
115+
private static Command InitializeCommandInternal(IServiceProvider serviceProvider, Func<InvocationContext, Task>? handler = null)
114116
{
115117
var command = new RootCommand()
116118
{
117119
Name = "http",
118120
};
119-
//var methodArgument = new Argument<HttpMethod>("method")
120-
//{
121-
// Description = "Request method",
122-
// Arity = ArgumentArity.ZeroOrOne,
123-
//};
124-
//methodArgument.SetDefaultValue(HttpMethod.Get.Method);
125-
//var allowedMethods = HttpMethods.ToArray();
126-
//methodArgument.AddSuggestions(allowedMethods);
127-
128-
//command.AddArgument(methodArgument);
129-
//var urlArgument = new Argument<string>("url")
130-
//{
131-
// Description = "Request url",
132-
// Arity = ArgumentArity.ExactlyOne
133-
//};
134-
//command.AddArgument(urlArgument);
135-
136-
foreach (var option in SupportedOptions)
121+
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+
)
137150
{
138151
command.AddOption(option);
139152
}
140153
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);
141165
return command;
142166
}
143-
144-
// ReSharper disable once InconsistentNaming
145-
public static IServiceCollection RegisterHTTPieServices(this IServiceCollection serviceCollection)
167+
168+
public static IServiceCollection RegisterApplicationServices(this IServiceCollection serviceCollection)
146169
{
147170
serviceCollection
148171
.AddSingleton<IRequestExecutor, RequestExecutor>()
@@ -207,54 +230,16 @@ public static IServiceCollection RegisterHTTPieServices(this IServiceCollection
207230
.AddResponseMiddleware<JsonSchemaValidationMiddleware>()
208231
;
209232
}
210-
211-
public static void InitRequestModel(HttpContext httpContext, string commandLine)
212-
=> InitRequestModel(httpContext, CommandLineStringSplitter.Instance.Split(commandLine).ToArray());
213-
214-
public static void InitRequestModel(HttpContext httpContext, string[] args)
215-
{
216-
// should output helps
217-
if (args.Contains("-?") || args.Contains("--help"))
218-
{
219-
return;
220-
}
221-
var requestModel = httpContext.Request;
222-
requestModel.ParseResult = _commandParser.Parse(args);
223-
224-
var method = requestModel.ParseResult.UnmatchedTokens.FirstOrDefault(x => HttpMethods.Contains(x));
225-
if (!string.IsNullOrEmpty(method))
226-
{
227-
requestModel.Method = new HttpMethod(method);
228-
}
229-
// Url
230-
requestModel.Url = requestModel.ParseResult.UnmatchedTokens.FirstOrDefault(x =>
231-
!x.StartsWith("-", StringComparison.Ordinal)
232-
&& !HttpMethods.Contains(x))
233-
?? string.Empty;
234-
if (string.IsNullOrEmpty(requestModel.Url))
235-
{
236-
throw new InvalidOperationException("The request url can not be null");
237-
}
238-
requestModel.Options = args
239-
.Where(x => x.StartsWith('-'))
240-
.ToArray();
241-
#nullable disable
242-
requestModel.RequestItems = requestModel.ParseResult.UnmatchedTokens
243-
.Except(new[] { method, requestModel.Url })
244-
.Where(x => !x.StartsWith('-'))
245-
.ToArray();
246-
#nullable restore
247-
}
248-
233+
249234
public static async Task<int> Handle(this IServiceProvider services, string[] args)
250235
{
251-
InitRequestModel(services.GetRequiredService<HttpContext>(), args);
252-
return await _commandParser.InvokeAsync(args);
236+
var commandParser = services.ConstructCommand();
237+
return await commandParser.InvokeAsync(args);
253238
}
254-
255-
public static async Task<int> Handle(this IServiceProvider services, string commandLine)
239+
240+
public static async Task<int> Handle(this IServiceProvider services, string commandLine, Func<InvocationContext, Task>? handler = null)
256241
{
257-
InitRequestModel(services.GetRequiredService<HttpContext>(), commandLine);
258-
return await _commandParser.InvokeAsync(commandLine);
242+
var commandParser = services.ConstructCommand(handler);
243+
return await commandParser.InvokeAsync(commandLine);
259244
}
260245
}

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)