Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide content-length in the HTTPFields' literal when creating a response #457

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 5 additions & 1 deletion Sources/Hummingbird/Codable/JSON/JSONCoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ extension JSONEncoder: ResponseEncoder {
var buffer = context.allocator.buffer(capacity: 0)
let data = try self.encode(value)
buffer.writeBytes(data)

return Response(
status: .ok,
headers: [.contentType: "application/json; charset=utf-8"],
headers: HTTPFields(
contentType: "application/json; charset=utf-8",
contentLength: buffer.readableBytes
),
body: .init(byteBuffer: buffer)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ extension URLEncodedFormEncoder: ResponseEncoder {
var buffer = context.allocator.buffer(capacity: 0)
let string = try self.encode(value)
buffer.writeString(string)

return Response(
status: .ok,
headers: [.contentType: "application/x-www-form-urlencoded"],
headers: HTTPFields(
contentType: "application/x-www-form-urlencoded",
contentLength: buffer.readableBytes
),
body: .init(byteBuffer: buffer)
)
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/Hummingbird/Router/EndpointResponder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ struct EndpointResponders<Context: BaseRequestContext>: Sendable {
}

mutating func addResponder(for method: HTTPRequest.Method, responder: any HTTPResponder<Context>) {
guard self.methods[method] == nil else {
guard !self.methods.keys.contains(method) else {
preconditionFailure("\(method.rawValue) already has a handler")
}
self.methods[method] = responder
}

mutating func autoGenerateHeadEndpoint() {
if self.methods[.head] == nil, let get = methods[.get] {
if !self.methods.keys.contains(.head), let get = methods[.get] {
self.methods[.head] = CallbackResponder { request, context in
let response = try await get.respond(to: request, context: context)
return response.createHeadResponse()
Expand Down
29 changes: 25 additions & 4 deletions Sources/Hummingbird/Router/ResponseGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,14 @@ extension String: ResponseGenerator {
/// Generate response holding string
public func response(from request: Request, context: some BaseRequestContext) -> Response {
let buffer = context.allocator.buffer(string: self)
return Response(status: .ok, headers: [.contentType: "text/plain; charset=utf-8"], body: .init(byteBuffer: buffer))
return Response(
status: .ok,
headers: HTTPFields(
contentType: "text/plain; charset=utf-8",
contentLength: buffer.readableBytes
),
body: .init(byteBuffer: buffer)
)
}
}

Expand All @@ -42,15 +49,29 @@ extension Substring: ResponseGenerator {
/// Generate response holding string
public func response(from request: Request, context: some BaseRequestContext) -> Response {
let buffer = context.allocator.buffer(substring: self)
return Response(status: .ok, headers: [.contentType: "text/plain; charset=utf-8"], body: .init(byteBuffer: buffer))
return Response(
status: .ok,
headers: HTTPFields(
contentType: "text/plain; charset=utf-8",
contentLength: buffer.readableBytes
),
body: .init(byteBuffer: buffer)
)
}
}

/// Extend ByteBuffer to conform to ResponseGenerator
extension ByteBuffer: ResponseGenerator {
/// Generate response holding bytebuffer
public func response(from request: Request, context: some BaseRequestContext) -> Response {
Response(status: .ok, headers: [.contentType: "application/octet-stream"], body: .init(byteBuffer: self))
Response(
status: .ok,
headers: HTTPFields(
contentType: "application/octet-stream",
contentLength: self.readableBytes
),
body: .init(byteBuffer: self)
)
}
}

Expand All @@ -69,7 +90,7 @@ extension Optional: ResponseGenerator where Wrapped: ResponseGenerator {
case .some(let wrapped):
return try wrapped.response(from: request, context: context)
case .none:
return Response(status: .noContent, headers: [:], body: .init())
return Response(status: .noContent)
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions Sources/Hummingbird/Utils/HTTPFields+Payload.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import HTTPTypes

extension HTTPFields {
init(contentType: String, contentLength: Int) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to centralize this as an internal helper, so that we can tweak the formula from here

self.init()

// Content-Type, Content-Length, Server, Date + 2 extra headers
// This should cover our expected amount of headers
self.reserveCapacity(6)

self[.contentType] = contentType
self[.contentLength] = String(describing: contentLength)
}
}
2 changes: 1 addition & 1 deletion Sources/HummingbirdCore/Response/Response.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public struct Response: Sendable {
public init(status: HTTPResponse.Status, headers: HTTPFields = .init(), body: ResponseBody = .init()) {
self.head = .init(status: status, headerFields: headers)
self.body = body
if let contentLength = body.contentLength, headers[.contentLength] == nil {
if let contentLength = body.contentLength, !headers.contains(.contentLength) {
self.head.headerFields[.contentLength] = String(describing: contentLength)
}
}
Expand Down
Loading