Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion FlyingFox/Sources/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ package extension AsyncSocket {
}

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

func writeFrame(_ frame: WSFrame) async throws {
Expand Down
24 changes: 18 additions & 6 deletions FlyingFox/Sources/HTTPDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import Foundation

struct HTTPDecoder {

var sharedRequestBufferSize: Int
var sharedRequestReplaySize: Int

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

func readBody(from bytes: some AsyncBufferedSequence<UInt8>, length: String?) async throws -> HTTPBodySequence {
guard let length = length.flatMap(Int.init) else {
return HTTPBodySequence(data: Data())
let length = length.flatMap(Int.init) ?? 0
guard sharedRequestBufferSize > 0 else {
throw SocketError.disconnected
}

if length <= sharedRequestReplaySize {
return HTTPBodySequence(shared: bytes, count: length, suggestedBufferSize: 4096)
if length <= sharedRequestBufferSize {
return try await HTTPBodySequence(data: readData(from: bytes, length: length), suggestedBufferSize: length)
} else if length <= sharedRequestReplaySize {
return HTTPBodySequence(shared: bytes, count: length, suggestedBufferSize: sharedRequestBufferSize)
} else {
let prefix = AsyncBufferedPrefixSequence(base: bytes, count: length)
return HTTPBodySequence(from: prefix, count: length, suggestedBufferSize: 4096)
return HTTPBodySequence(from: prefix, count: length, suggestedBufferSize: sharedRequestBufferSize)
}
}

private func readData(from bytes: some AsyncBufferedSequence<UInt8>, length: Int) async throws -> Data {
var iterator = bytes.makeAsyncIterator()
guard let buffer = try await iterator.nextBuffer(count: length),
buffer.count == length else {
throw SocketError.disconnected
}
return Data(buffer)
}
}

Expand Down
10 changes: 8 additions & 2 deletions FlyingFox/Sources/HTTPRoute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -313,11 +313,11 @@ private extension HTTPRoute {

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

let escaped = path.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
return HTTPDecoder(sharedRequestReplaySize: 0).readComponents(from: escaped ?? path)
return HTTPDecoder().readComponents(from: escaped ?? path)
}
}

Expand Down Expand Up @@ -379,3 +379,9 @@ public extension Array where Element == HTTPRoute.Parameter {
}
}
}

private extension HTTPDecoder {
init() {
self.init(sharedRequestBufferSize: 128, sharedRequestReplaySize: 1024)
}
}
4 changes: 3 additions & 1 deletion FlyingFox/Sources/HTTPServer+Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,27 @@ public extension HTTPServer {
struct Configuration: Sendable {
public var address: any SocketAddress
public var timeout: TimeInterval
public var sharedRequestBufferSize: Int
public var sharedRequestReplaySize: Int
public var pool: any AsyncSocketPool
public var logger: any Logging

public init(address: some SocketAddress,
timeout: TimeInterval = 15,
sharedRequestBufferSize: Int = 4_096,
sharedRequestReplaySize: Int = 2_097_152,
pool: any AsyncSocketPool = HTTPServer.defaultPool(),
logger: any Logging = HTTPServer.defaultLogger()) {
self.address = address
self.timeout = timeout
self.sharedRequestBufferSize = sharedRequestBufferSize
self.sharedRequestReplaySize = sharedRequestReplaySize
self.pool = pool
self.logger = logger
}
}
}


extension HTTPServer.Configuration {

init(port: UInt16,
Expand Down
2 changes: 1 addition & 1 deletion FlyingFox/Sources/HTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ public final actor HTTPServer {
private func makeConnection(socket: AsyncSocket) -> HTTPConnection {
HTTPConnection(
socket: socket,
decoder: HTTPDecoder(sharedRequestReplaySize: config.sharedRequestReplaySize),
decoder: HTTPDecoder(sharedRequestBufferSize: config.sharedRequestBufferSize, sharedRequestReplaySize: config.sharedRequestReplaySize),
logger: config.logger
)
}
Expand Down
2 changes: 1 addition & 1 deletion FlyingFox/Tests/HTTPConnectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ private extension HTTPConnection {
init(socket: AsyncSocket) {
self.init(
socket: socket,
decoder: HTTPDecoder(sharedRequestReplaySize: 1024),
decoder: HTTPDecoder.make(),
logger: .disabled
)
}
Expand Down
54 changes: 26 additions & 28 deletions FlyingFox/Tests/HTTPDecoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct HTTPDecoderTests {

@Test
func GETMethod_IsParsed() async throws {
let request = try await HTTPDecoder().decodeRequestFromString(
let request = try await HTTPDecoder.make().decodeRequestFromString(
"""
GET /hello HTTP/1.1\r
\r
Expand All @@ -52,7 +52,7 @@ struct HTTPDecoderTests {

@Test
func POSTMethod_IsParsed() async throws {
let request = try await HTTPDecoder().decodeRequestFromString(
let request = try await HTTPDecoder.make().decodeRequestFromString(
"""
POST /hello HTTP/1.1\r
\r
Expand All @@ -66,7 +66,7 @@ struct HTTPDecoderTests {

@Test
func CUSTOMMethod_IsParsed() async throws {
let request = try await HTTPDecoder().decodeRequestFromString(
let request = try await HTTPDecoder.make().decodeRequestFromString(
"""
FISH /hello HTTP/1.1\r
\r
Expand All @@ -80,7 +80,7 @@ struct HTTPDecoderTests {

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

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

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

@Test
func body_IsNotParsed_WhenContentLength_IsNotProvided() async throws {
let request = try await HTTPDecoder().decodeRequestFromString(
let request = try await HTTPDecoder.make().decodeRequestFromString(
"""
GET /hello HTTP/1.1\r
\r
Expand All @@ -164,7 +164,7 @@ struct HTTPDecoderTests {

@Test
func body_IsParsed_WhenContentLength_IsProvided() async throws {
let request = try await HTTPDecoder().decodeRequestFromString(
let request = try await HTTPDecoder.make().decodeRequestFromString(
"""
GET /hello HTTP/1.1\r
Content-Length: 5\r
Expand All @@ -181,7 +181,7 @@ struct HTTPDecoderTests {
@Test
func invalidStatusLine_ThrowsError() async {
await #expect(throws: HTTPDecoder.Error.self) {
try await HTTPDecoder().decodeRequestFromString(
try await HTTPDecoder.make().decodeRequestFromString(
"""
GET/hello HTTP/1.1\r
\r
Expand All @@ -193,50 +193,55 @@ struct HTTPDecoderTests {
@Test
func body_ThrowsError_WhenSequenceEnds() async throws {
await #expect(throws: SocketError.self) {
try await HTTPDecoder().readBody(from: AsyncBufferedEmptySequence(completeImmediately: true), length: "100").get()
try await HTTPDecoder.make(sharedRequestReplaySize: 1024).readBody(from: AsyncBufferedEmptySequence(completeImmediately: true), length: "100").get()
}
await #expect(throws: SocketError.self) {
try await HTTPDecoder.make(sharedRequestBufferSize: 1024).readBody(from: AsyncBufferedEmptySequence(completeImmediately: true), length: "100").get()
}
}

@Test
func bodySequence_CanReplay_WhenSizeIsLessThanMax() async throws {
let sequence = try await HTTPDecoder(sharedRequestReplaySize: 100).readBodyFromString("Fish & Chips")
let decoder = HTTPDecoder.make(sharedRequestBufferSize: 1, sharedRequestReplaySize: 100)
let sequence = try await decoder.readBodyFromString("Fish & Chips")
#expect(sequence.count == 12)
#expect(sequence.canReplay)
}

@Test
func bodySequence_CanNotReplay_WhenSizeIsGreaterThanMax() async throws {
let sequence = try await HTTPDecoder(sharedRequestReplaySize: 2).readBodyFromString("Fish & Chips")
let decoder = HTTPDecoder.make(sharedRequestBufferSize: 1, sharedRequestReplaySize: 2)
let sequence = try await decoder.readBodyFromString("Fish & Chips")
#expect(sequence.count == 12)
#expect(!sequence.canReplay)
}

@Test
func invalidPathDecodes() {
let comps = HTTPDecoder().makeComponents(from: nil)
let comps = HTTPDecoder.make().makeComponents(from: nil)
#expect(comps.path == "")
#expect(comps.query == [])
}

@Test
func percentEncodedPathDecodes() {
#expect(
HTTPDecoder().readComponents(from: "/fish%20chips").path == "/fish chips"
HTTPDecoder.make().readComponents(from: "/fish%20chips").path == "/fish chips"
)
#expect(
HTTPDecoder().readComponents(from: "/ocean/fish%20and%20chips").path == "/ocean/fish and chips"
HTTPDecoder.make().readComponents(from: "/ocean/fish%20and%20chips").path == "/ocean/fish and chips"
)
}

@Test
func percentQueryStringDecodes() {
#expect(
HTTPDecoder().readComponents(from: "/?fish=%F0%9F%90%9F").query == [
HTTPDecoder.make().readComponents(from: "/?fish=%F0%9F%90%9F").query == [
.init(name: "fish", value: "🐟")
]
)
#expect(
HTTPDecoder().readComponents(from: "?%F0%9F%90%A1=chips").query == [
HTTPDecoder.make().readComponents(from: "?%F0%9F%90%A1=chips").query == [
.init(name: "🐡", value: "chips")
]
)
Expand All @@ -248,7 +253,7 @@ struct HTTPDecoderTests {
urlComps.queryItems = [.init(name: "name", value: nil)]

#expect(
HTTPDecoder().makeComponents(from: urlComps).query == [
HTTPDecoder.make().makeComponents(from: urlComps).query == [
.init(name: "name", value: "")
]
)
Expand All @@ -257,7 +262,7 @@ struct HTTPDecoderTests {
@Test
func responseInvalidStatusLine_ThrowsErrorM() async throws {
await #expect(throws: HTTPDecoder.Error.self) {
try await HTTPDecoder().decodeRequestFromString(
try await HTTPDecoder.make().decodeRequestFromString(
"""
HTTP/1.1\r
\r
Expand All @@ -268,7 +273,7 @@ struct HTTPDecoderTests {

@Test
func responseBody_IsNotParsed_WhenContentLength_IsNotProvided() async throws {
let response = try await HTTPDecoder().decodeResponseFromString(
let response = try await HTTPDecoder.make().decodeResponseFromString(
"""
HTTP/1.1 202 OK \r
\r
Expand All @@ -283,7 +288,7 @@ struct HTTPDecoderTests {

@Test
func responseBody_IsParsed_WhenContentLength_IsProvided() async throws {
let response = try await HTTPDecoder().decodeResponseFromString(
let response = try await HTTPDecoder.make().decodeResponseFromString(
"""
HTTP/1.1 202 OK \r
Content-Length: 5\r
Expand Down Expand Up @@ -316,10 +321,3 @@ private extension HTTPDecoder {
)
}
}

private extension HTTPDecoder {

init() {
self.init(sharedRequestReplaySize: 1024)
}
}
11 changes: 10 additions & 1 deletion FlyingFox/Tests/HTTPRequest+Mock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ extension HTTPRequest {
}

static func make(method: HTTPMethod = .GET, _ url: String, headers: [HTTPHeader: String] = [:]) -> Self {
let (path, query) = HTTPDecoder(sharedRequestReplaySize: 0).readComponents(from: url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)
let (path, query) = HTTPDecoder.make().readComponents(from: url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)
return HTTPRequest.make(
method: method,
path: path,
Expand All @@ -65,3 +65,12 @@ extension HTTPRequest {
}
}
}

extension HTTPDecoder {
static func make(sharedRequestBufferSize: Int = 128, sharedRequestReplaySize: Int = 1024) -> HTTPDecoder {
HTTPDecoder(
sharedRequestBufferSize: sharedRequestBufferSize,
sharedRequestReplaySize: sharedRequestReplaySize
)
}
}
2 changes: 1 addition & 1 deletion FlyingFox/XCTests/HTTPConnectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ private extension HTTPConnection {
init(socket: AsyncSocket) {
self.init(
socket: socket,
decoder: HTTPDecoder(sharedRequestReplaySize: 1024),
decoder: HTTPDecoder.make(sharedRequestReplaySize: 1024),
logger: .disabled
)
}
Expand Down
Loading