Skip to content

Commit 0231b69

Browse files
authored
Merge pull request #142 from swhitty/complete-data-request
Buffer HTTPRequest.body < 4096 bytes as Data
2 parents cd661f9 + 6c0f032 commit 0231b69

11 files changed

+106
-71
lines changed

FlyingFox/Sources/HTTPClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ package extension AsyncSocket {
5353
}
5454

5555
func readResponse() async throws -> HTTPResponse {
56-
try await HTTPDecoder(sharedRequestReplaySize: 102_400).decodeResponse(from: bytes)
56+
try await HTTPDecoder(sharedRequestBufferSize: 4096, sharedRequestReplaySize: 102_400).decodeResponse(from: bytes)
5757
}
5858

5959
func writeFrame(_ frame: WSFrame) async throws {

FlyingFox/Sources/HTTPDecoder.swift

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import Foundation
3434

3535
struct HTTPDecoder {
3636

37+
var sharedRequestBufferSize: Int
3738
var sharedRequestReplaySize: Int
3839

3940
func decodeRequest(from bytes: some AsyncBufferedSequence<UInt8>) async throws -> HTTPRequest {
@@ -115,16 +116,27 @@ struct HTTPDecoder {
115116
}
116117

117118
func readBody(from bytes: some AsyncBufferedSequence<UInt8>, length: String?) async throws -> HTTPBodySequence {
118-
guard let length = length.flatMap(Int.init) else {
119-
return HTTPBodySequence(data: Data())
119+
let length = length.flatMap(Int.init) ?? 0
120+
guard sharedRequestBufferSize > 0 else {
121+
throw SocketError.disconnected
120122
}
121-
122-
if length <= sharedRequestReplaySize {
123-
return HTTPBodySequence(shared: bytes, count: length, suggestedBufferSize: 4096)
123+
if length <= sharedRequestBufferSize {
124+
return try await HTTPBodySequence(data: readData(from: bytes, length: length), suggestedBufferSize: length)
125+
} else if length <= sharedRequestReplaySize {
126+
return HTTPBodySequence(shared: bytes, count: length, suggestedBufferSize: sharedRequestBufferSize)
124127
} else {
125128
let prefix = AsyncBufferedPrefixSequence(base: bytes, count: length)
126-
return HTTPBodySequence(from: prefix, count: length, suggestedBufferSize: 4096)
129+
return HTTPBodySequence(from: prefix, count: length, suggestedBufferSize: sharedRequestBufferSize)
130+
}
131+
}
132+
133+
private func readData(from bytes: some AsyncBufferedSequence<UInt8>, length: Int) async throws -> Data {
134+
var iterator = bytes.makeAsyncIterator()
135+
guard let buffer = try await iterator.nextBuffer(count: length),
136+
buffer.count == length else {
137+
throw SocketError.disconnected
127138
}
139+
return Data(buffer)
128140
}
129141
}
130142

FlyingFox/Sources/HTTPRoute.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,11 +313,11 @@ private extension HTTPRoute {
313313

314314
static func readComponents(from path: String) -> (path: String, query: [HTTPRequest.QueryItem]) {
315315
guard path.removingPercentEncoding == path else {
316-
return HTTPDecoder(sharedRequestReplaySize: 0).readComponents(from: path)
316+
return HTTPDecoder().readComponents(from: path)
317317
}
318318

319319
let escaped = path.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
320-
return HTTPDecoder(sharedRequestReplaySize: 0).readComponents(from: escaped ?? path)
320+
return HTTPDecoder().readComponents(from: escaped ?? path)
321321
}
322322
}
323323

@@ -379,3 +379,9 @@ public extension Array where Element == HTTPRoute.Parameter {
379379
}
380380
}
381381
}
382+
383+
private extension HTTPDecoder {
384+
init() {
385+
self.init(sharedRequestBufferSize: 128, sharedRequestReplaySize: 1024)
386+
}
387+
}

FlyingFox/Sources/HTTPServer+Configuration.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,25 +37,27 @@ public extension HTTPServer {
3737
struct Configuration: Sendable {
3838
public var address: any SocketAddress
3939
public var timeout: TimeInterval
40+
public var sharedRequestBufferSize: Int
4041
public var sharedRequestReplaySize: Int
4142
public var pool: any AsyncSocketPool
4243
public var logger: any Logging
4344

4445
public init(address: some SocketAddress,
4546
timeout: TimeInterval = 15,
47+
sharedRequestBufferSize: Int = 4_096,
4648
sharedRequestReplaySize: Int = 2_097_152,
4749
pool: any AsyncSocketPool = HTTPServer.defaultPool(),
4850
logger: any Logging = HTTPServer.defaultLogger()) {
4951
self.address = address
5052
self.timeout = timeout
53+
self.sharedRequestBufferSize = sharedRequestBufferSize
5154
self.sharedRequestReplaySize = sharedRequestReplaySize
5255
self.pool = pool
5356
self.logger = logger
5457
}
5558
}
5659
}
5760

58-
5961
extension HTTPServer.Configuration {
6062

6163
init(port: UInt16,

FlyingFox/Sources/HTTPServer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ public final actor HTTPServer {
208208
private func makeConnection(socket: AsyncSocket) -> HTTPConnection {
209209
HTTPConnection(
210210
socket: socket,
211-
decoder: HTTPDecoder(sharedRequestReplaySize: config.sharedRequestReplaySize),
211+
decoder: HTTPDecoder(sharedRequestBufferSize: config.sharedRequestBufferSize, sharedRequestReplaySize: config.sharedRequestReplaySize),
212212
logger: config.logger
213213
)
214214
}

FlyingFox/Tests/HTTPConnectionTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ private extension HTTPConnection {
149149
init(socket: AsyncSocket) {
150150
self.init(
151151
socket: socket,
152-
decoder: HTTPDecoder(sharedRequestReplaySize: 1024),
152+
decoder: HTTPDecoder.make(),
153153
logger: .disabled
154154
)
155155
}

FlyingFox/Tests/HTTPDecoderTests.swift

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ struct HTTPDecoderTests {
3838

3939
@Test
4040
func GETMethod_IsParsed() async throws {
41-
let request = try await HTTPDecoder().decodeRequestFromString(
41+
let request = try await HTTPDecoder.make().decodeRequestFromString(
4242
"""
4343
GET /hello HTTP/1.1\r
4444
\r
@@ -52,7 +52,7 @@ struct HTTPDecoderTests {
5252

5353
@Test
5454
func POSTMethod_IsParsed() async throws {
55-
let request = try await HTTPDecoder().decodeRequestFromString(
55+
let request = try await HTTPDecoder.make().decodeRequestFromString(
5656
"""
5757
POST /hello HTTP/1.1\r
5858
\r
@@ -66,7 +66,7 @@ struct HTTPDecoderTests {
6666

6767
@Test
6868
func CUSTOMMethod_IsParsed() async throws {
69-
let request = try await HTTPDecoder().decodeRequestFromString(
69+
let request = try await HTTPDecoder.make().decodeRequestFromString(
7070
"""
7171
FISH /hello HTTP/1.1\r
7272
\r
@@ -80,7 +80,7 @@ struct HTTPDecoderTests {
8080

8181
@Test
8282
func path_IsParsed() async throws {
83-
let request = try await HTTPDecoder().decodeRequestFromString(
83+
let request = try await HTTPDecoder.make().decodeRequestFromString(
8484
"""
8585
GET /hello/world?fish=Chips&with=Mushy%20Peas HTTP/1.1\r
8686
\r
@@ -101,7 +101,7 @@ struct HTTPDecoderTests {
101101

102102
@Test
103103
func naughtyPath_IsParsed() async throws {
104-
let request = try await HTTPDecoder().decodeRequestFromString(
104+
let request = try await HTTPDecoder.make().decodeRequestFromString(
105105
"""
106106
GET /../a/b/../c/./d.html?fish=Chips&with=Mushy%20Peas HTTP/1.1\r
107107
\r
@@ -128,7 +128,7 @@ struct HTTPDecoderTests {
128128

129129
@Test
130130
func headers_AreParsed() async throws {
131-
let request = try await HTTPDecoder().decodeRequestFromString(
131+
let request = try await HTTPDecoder.make().decodeRequestFromString(
132132
"""
133133
GET /hello HTTP/1.1\r
134134
Fish: Chips\r
@@ -149,7 +149,7 @@ struct HTTPDecoderTests {
149149

150150
@Test
151151
func body_IsNotParsed_WhenContentLength_IsNotProvided() async throws {
152-
let request = try await HTTPDecoder().decodeRequestFromString(
152+
let request = try await HTTPDecoder.make().decodeRequestFromString(
153153
"""
154154
GET /hello HTTP/1.1\r
155155
\r
@@ -164,7 +164,7 @@ struct HTTPDecoderTests {
164164

165165
@Test
166166
func body_IsParsed_WhenContentLength_IsProvided() async throws {
167-
let request = try await HTTPDecoder().decodeRequestFromString(
167+
let request = try await HTTPDecoder.make().decodeRequestFromString(
168168
"""
169169
GET /hello HTTP/1.1\r
170170
Content-Length: 5\r
@@ -181,7 +181,7 @@ struct HTTPDecoderTests {
181181
@Test
182182
func invalidStatusLine_ThrowsError() async {
183183
await #expect(throws: HTTPDecoder.Error.self) {
184-
try await HTTPDecoder().decodeRequestFromString(
184+
try await HTTPDecoder.make().decodeRequestFromString(
185185
"""
186186
GET/hello HTTP/1.1\r
187187
\r
@@ -193,50 +193,55 @@ struct HTTPDecoderTests {
193193
@Test
194194
func body_ThrowsError_WhenSequenceEnds() async throws {
195195
await #expect(throws: SocketError.self) {
196-
try await HTTPDecoder().readBody(from: AsyncBufferedEmptySequence(completeImmediately: true), length: "100").get()
196+
try await HTTPDecoder.make(sharedRequestReplaySize: 1024).readBody(from: AsyncBufferedEmptySequence(completeImmediately: true), length: "100").get()
197+
}
198+
await #expect(throws: SocketError.self) {
199+
try await HTTPDecoder.make(sharedRequestBufferSize: 1024).readBody(from: AsyncBufferedEmptySequence(completeImmediately: true), length: "100").get()
197200
}
198201
}
199202

200203
@Test
201204
func bodySequence_CanReplay_WhenSizeIsLessThanMax() async throws {
202-
let sequence = try await HTTPDecoder(sharedRequestReplaySize: 100).readBodyFromString("Fish & Chips")
205+
let decoder = HTTPDecoder.make(sharedRequestBufferSize: 1, sharedRequestReplaySize: 100)
206+
let sequence = try await decoder.readBodyFromString("Fish & Chips")
203207
#expect(sequence.count == 12)
204208
#expect(sequence.canReplay)
205209
}
206210

207211
@Test
208212
func bodySequence_CanNotReplay_WhenSizeIsGreaterThanMax() async throws {
209-
let sequence = try await HTTPDecoder(sharedRequestReplaySize: 2).readBodyFromString("Fish & Chips")
213+
let decoder = HTTPDecoder.make(sharedRequestBufferSize: 1, sharedRequestReplaySize: 2)
214+
let sequence = try await decoder.readBodyFromString("Fish & Chips")
210215
#expect(sequence.count == 12)
211216
#expect(!sequence.canReplay)
212217
}
213218

214219
@Test
215220
func invalidPathDecodes() {
216-
let comps = HTTPDecoder().makeComponents(from: nil)
221+
let comps = HTTPDecoder.make().makeComponents(from: nil)
217222
#expect(comps.path == "")
218223
#expect(comps.query == [])
219224
}
220225

221226
@Test
222227
func percentEncodedPathDecodes() {
223228
#expect(
224-
HTTPDecoder().readComponents(from: "/fish%20chips").path == "/fish chips"
229+
HTTPDecoder.make().readComponents(from: "/fish%20chips").path == "/fish chips"
225230
)
226231
#expect(
227-
HTTPDecoder().readComponents(from: "/ocean/fish%20and%20chips").path == "/ocean/fish and chips"
232+
HTTPDecoder.make().readComponents(from: "/ocean/fish%20and%20chips").path == "/ocean/fish and chips"
228233
)
229234
}
230235

231236
@Test
232237
func percentQueryStringDecodes() {
233238
#expect(
234-
HTTPDecoder().readComponents(from: "/?fish=%F0%9F%90%9F").query == [
239+
HTTPDecoder.make().readComponents(from: "/?fish=%F0%9F%90%9F").query == [
235240
.init(name: "fish", value: "🐟")
236241
]
237242
)
238243
#expect(
239-
HTTPDecoder().readComponents(from: "?%F0%9F%90%A1=chips").query == [
244+
HTTPDecoder.make().readComponents(from: "?%F0%9F%90%A1=chips").query == [
240245
.init(name: "🐡", value: "chips")
241246
]
242247
)
@@ -248,7 +253,7 @@ struct HTTPDecoderTests {
248253
urlComps.queryItems = [.init(name: "name", value: nil)]
249254

250255
#expect(
251-
HTTPDecoder().makeComponents(from: urlComps).query == [
256+
HTTPDecoder.make().makeComponents(from: urlComps).query == [
252257
.init(name: "name", value: "")
253258
]
254259
)
@@ -257,7 +262,7 @@ struct HTTPDecoderTests {
257262
@Test
258263
func responseInvalidStatusLine_ThrowsErrorM() async throws {
259264
await #expect(throws: HTTPDecoder.Error.self) {
260-
try await HTTPDecoder().decodeRequestFromString(
265+
try await HTTPDecoder.make().decodeRequestFromString(
261266
"""
262267
HTTP/1.1\r
263268
\r
@@ -268,7 +273,7 @@ struct HTTPDecoderTests {
268273

269274
@Test
270275
func responseBody_IsNotParsed_WhenContentLength_IsNotProvided() async throws {
271-
let response = try await HTTPDecoder().decodeResponseFromString(
276+
let response = try await HTTPDecoder.make().decodeResponseFromString(
272277
"""
273278
HTTP/1.1 202 OK \r
274279
\r
@@ -283,7 +288,7 @@ struct HTTPDecoderTests {
283288

284289
@Test
285290
func responseBody_IsParsed_WhenContentLength_IsProvided() async throws {
286-
let response = try await HTTPDecoder().decodeResponseFromString(
291+
let response = try await HTTPDecoder.make().decodeResponseFromString(
287292
"""
288293
HTTP/1.1 202 OK \r
289294
Content-Length: 5\r
@@ -316,10 +321,3 @@ private extension HTTPDecoder {
316321
)
317322
}
318323
}
319-
320-
private extension HTTPDecoder {
321-
322-
init() {
323-
self.init(sharedRequestReplaySize: 1024)
324-
}
325-
}

FlyingFox/Tests/HTTPRequest+Mock.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ extension HTTPRequest {
5050
}
5151

5252
static func make(method: HTTPMethod = .GET, _ url: String, headers: [HTTPHeader: String] = [:]) -> Self {
53-
let (path, query) = HTTPDecoder(sharedRequestReplaySize: 0).readComponents(from: url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)
53+
let (path, query) = HTTPDecoder.make().readComponents(from: url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)
5454
return HTTPRequest.make(
5555
method: method,
5656
path: path,
@@ -65,3 +65,12 @@ extension HTTPRequest {
6565
}
6666
}
6767
}
68+
69+
extension HTTPDecoder {
70+
static func make(sharedRequestBufferSize: Int = 128, sharedRequestReplaySize: Int = 1024) -> HTTPDecoder {
71+
HTTPDecoder(
72+
sharedRequestBufferSize: sharedRequestBufferSize,
73+
sharedRequestReplaySize: sharedRequestReplaySize
74+
)
75+
}
76+
}

FlyingFox/XCTests/HTTPConnectionTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ private extension HTTPConnection {
147147
init(socket: AsyncSocket) {
148148
self.init(
149149
socket: socket,
150-
decoder: HTTPDecoder(sharedRequestReplaySize: 1024),
150+
decoder: HTTPDecoder.make(sharedRequestReplaySize: 1024),
151151
logger: .disabled
152152
)
153153
}

0 commit comments

Comments
 (0)