-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHttp10ServerStateMachineSpec.cs
More file actions
220 lines (177 loc) · 7.95 KB
/
Copy pathHttp10ServerStateMachineSpec.cs
File metadata and controls
220 lines (177 loc) · 7.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
using System.Text;
using Akka.TestKit.Xunit;
using GaudiHTTP.Protocol.Syntax.Http10.Server;
using GaudiHTTP.Server;
using GaudiHTTP.Server.Context.Features;
using GaudiHTTP.Tests.Shared;
using Microsoft.AspNetCore.Http.Features;
using Servus.Akka.Transport;
namespace GaudiHTTP.Tests.Protocol.Syntax.Http10.Server;
public sealed class Http10ServerStateMachineSpec() : TestKit(CiQuietConfig.Instance)
{
private static FakeServerOps MakeOps() => new();
private static IFeatureCollection CreateResponseContext()
{
var features = new GaudiFeatureCollection();
features.Set<IHttpRequestFeature>(new GaudiHttpRequestFeature());
features.Set<IHttpResponseFeature>(new GaudiHttpResponseFeature { StatusCode = 200 });
var bodyFeature = new GaudiHttpResponseBodyFeature();
features.Set<IHttpResponseBodyFeature>(bodyFeature);
features.Set<IHttpResponseBodyFeature>(bodyFeature);
return features;
}
private static async Task<IFeatureCollection> CreateResponseContextWithBody(string body)
{
var context = CreateResponseContext();
var bodyFeature = context.Get<IHttpResponseBodyFeature>()!;
var bytes = Encoding.ASCII.GetBytes(body);
await bodyFeature.Writer.WriteAsync(bytes);
await bodyFeature.Writer.CompleteAsync();
return context;
}
private static TransportBuffer CreateRequestBuffer(string requestText)
{
var bytes = Encoding.ASCII.GetBytes(requestText);
var buffer = TransportBuffer.Rent(bytes.Length);
bytes.CopyTo(buffer.FullMemory.Span);
buffer.Length = bytes.Length;
return buffer;
}
[Fact(Timeout = 5000)]
[Trait("RFC", "RFC1945-5")]
public void DecodeClientData_should_decode_complete_request()
{
var ops = MakeOps();
var sm = new Http10ServerStateMachine(new GaudiServerOptions().ToHttp1Options(), ops);
var requestBuffer = CreateRequestBuffer("GET /path HTTP/1.0\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n");
sm.DecodeClientData(TransportData.Rent(requestBuffer));
Assert.Single(ops.Requests);
var req = ops.Requests[0].Get<IHttpRequestFeature>()!;
Assert.Equal("GET", req.Method);
Assert.Equal("/path", req.Path);
}
[Fact(Timeout = 5000)]
[Trait("RFC", "RFC1945-5")]
public void DecodeClientData_should_not_complete_before_response()
{
var ops = MakeOps();
var sm = new Http10ServerStateMachine(new GaudiServerOptions().ToHttp1Options(), ops);
var requestBuffer = CreateRequestBuffer("GET / HTTP/1.0\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n");
sm.DecodeClientData(TransportData.Rent(requestBuffer));
Assert.False(sm.ShouldComplete);
Assert.Single(ops.Requests);
}
[Fact(Timeout = 5000)]
[Trait("RFC", "RFC1945")]
public void OnResponse_with_stream_body_and_no_content_length_should_use_connection_close_framing()
{
var ops = MakeOps();
var sm = new Http10ServerStateMachine(new GaudiServerOptions().ToHttp1Options(), ops);
var context = CreateResponseContext();
sm.OnResponse(context);
// Headers emitted without Content-Length
var td = ops.Outbound.OfType<TransportData>().First();
var text = Encoding.ASCII.GetString(td.Buffer.Memory.Span[..td.Buffer.Length]);
Assert.DoesNotContain("Content-Length", text);
// Connection close deferred until body completes
Assert.False(sm.ShouldComplete);
}
[Fact(Timeout = 5000)]
[Trait("RFC", "RFC1945")]
public async Task OnResponse_with_body_should_emit_transport_data_after_body_buffered()
{
var ops = MakeOps();
var sm = new Http10ServerStateMachine(new GaudiServerOptions().ToHttp1Options(), ops);
var context = await CreateResponseContextWithBody("hello");
sm.OnResponse(context);
Assert.Contains(ops.Outbound, o => o is TransportData);
var td = ops.Outbound.OfType<TransportData>().First();
var text = Encoding.ASCII.GetString(td.Buffer.Memory.Span[..td.Buffer.Length]);
Assert.Contains("Content-Length: 5", text);
Assert.Contains("hello", text);
}
[Fact(Timeout = 5000)]
[Trait("RFC", "RFC1945")]
public void OnResponse_with_buffered_body_should_emit_transport_data()
{
var ops = MakeOps();
var sm = new Http10ServerStateMachine(new GaudiServerOptions().ToHttp1Options(), ops);
var features = new GaudiFeatureCollection();
features.Set<IHttpRequestFeature>(new GaudiHttpRequestFeature());
var responseFeature = new GaudiHttpResponseFeature { StatusCode = 200 };
responseFeature.Headers["Content-Length"] = "0";
features.Set<IHttpResponseFeature>(responseFeature);
var bodyFeature = new GaudiHttpResponseBodyFeature();
features.Set<IHttpResponseBodyFeature>(bodyFeature);
sm.OnResponse(features);
Assert.True(sm.CanAcceptResponse);
}
[Fact(Timeout = 5000)]
[Trait("RFC", "RFC1945")]
public void CanAcceptResponse_should_always_be_true()
{
var ops = MakeOps();
var sm = new Http10ServerStateMachine(new GaudiServerOptions().ToHttp1Options(), ops);
Assert.True(sm.CanAcceptResponse);
}
[Fact(Timeout = 5000)]
[Trait("RFC", "RFC1945")]
public void Cleanup_should_abort_active_body()
{
var ops = MakeOps();
var sm = new Http10ServerStateMachine(new GaudiServerOptions().ToHttp1Options(), ops);
sm.Cleanup();
Assert.True(true);
}
[Fact(Timeout = 5000)]
[Trait("RFC", "RFC1945-3.1")]
public async Task OnResponse_should_use_http10_version_in_status_line()
{
var ops = MakeOps();
var sm = new Http10ServerStateMachine(new GaudiServerOptions().ToHttp1Options(), ops);
var requestBuffer = CreateRequestBuffer("GET / HTTP/1.0\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n");
sm.DecodeClientData(TransportData.Rent(requestBuffer));
var context = await CreateResponseContextWithBody("hello");
sm.OnResponse(context);
var td = ops.Outbound.OfType<TransportData>().First();
var text = Encoding.ASCII.GetString(td.Buffer.Memory.Span[..td.Buffer.Length]);
Assert.StartsWith("HTTP/1.0 ", text);
}
[Fact(Timeout = 5000)]
[Trait("RFC", "RFC1945-5.1.1")]
public void DecodeClientData_should_signal_error_for_unknown_method()
{
var ops = MakeOps();
var sm = new Http10ServerStateMachine(new GaudiServerOptions().ToHttp1Options(), ops);
var requestBuffer = CreateRequestBuffer("PATCH /path HTTP/1.0\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n");
sm.DecodeClientData(TransportData.Rent(requestBuffer));
Assert.Single(ops.Requests);
var req = ops.Requests[0].Get<IHttpRequestFeature>()!;
Assert.Equal("PATCH", req.Method);
}
[Fact(Timeout = 5000)]
[Trait("RFC", "RFC1945-5")]
public void DecodeClientData_should_detect_simple_request()
{
var ops = MakeOps();
var sm = new Http10ServerStateMachine(new GaudiServerOptions().ToHttp1Options(), ops);
var requestBuffer = CreateRequestBuffer("GET /path\r\n");
sm.DecodeClientData(TransportData.Rent(requestBuffer));
Assert.True(ops.Requests.Count <= 1);
}
[Fact(Timeout = 5000)]
[Trait("RFC", "RFC1945-7.2.2")]
public void DecodeClientData_should_handle_post_without_content_length()
{
var ops = MakeOps();
var sm = new Http10ServerStateMachine(new GaudiServerOptions().ToHttp1Options(), ops);
var requestBuffer = CreateRequestBuffer("POST /path HTTP/1.0\r\nHost: example.com\r\n\r\n");
sm.DecodeClientData(TransportData.Rent(requestBuffer));
if (ops.Requests.Count > 0)
{
var req = ops.Requests[0].Get<IHttpRequestFeature>()!;
var contentLength = req.Headers["Content-Length"];
Assert.True(string.IsNullOrEmpty(contentLength));
}
}
}