Use content-type header if present to detect format

Detect request format (HTML or JSON) from `content-type` header.

Previous:
- path extension (.json, .html)
- "Accept" header
- default (.UNKNOWN => .HTML)

Current:
- path extension (.json, .html)
- "Accept" header
- "Content-Type" header
- default (.UNKNOWN => .HTML)

Adjust `http.Headers` to do case-insensitive matching to conform to HTTP
spec: https://www.rfc-editor.org/rfc/rfc9110.html#name-field-names
This commit is contained in:
Bob Farrell 2024-03-10 11:31:39 +00:00
parent ea1962fcb8
commit dd54d55423
2 changed files with 29 additions and 3 deletions

View File

@ -18,10 +18,14 @@ pub fn deinit(self: *Self) void {
self.headers.deinit(self.allocator);
}
// Gets the first value for a given header identified by `name`.
// Gets the first value for a given header identified by `name`. Case-insensitive string comparison.
pub fn getFirstValue(self: *Self, name: []const u8) ?[]const u8 {
for (self.headers.items) |header| {
if (std.mem.eql(u8, header.name, name)) return header.value;
if (name.len != header.name.len) continue;
for (name, header.name) |expected, actual| {
if (std.ascii.toLower(expected) != std.ascii.toLower(actual)) continue;
}
return header.value;
}
return null;
}
@ -78,6 +82,14 @@ test "append" {
try std.testing.expectEqualStrings(headers.getFirstValue("foo").?, "bar");
}
test "case-insensitive matching" {
const allocator = std.testing.allocator;
var headers = Self.init(allocator);
defer headers.deinit();
try headers.append("Content-Type", "bar");
try std.testing.expectEqualStrings(headers.getFirstValue("content-type").?, "bar");
}
test "iterator" {
const allocator = std.testing.allocator;
var headers = Self.init(allocator);

View File

@ -151,7 +151,10 @@ pub fn render(self: *Self, status_code: jetzig.http.status_codes.StatusCode) jet
}
pub fn requestFormat(self: *Self) jetzig.http.Request.Format {
return self.extensionFormat() orelse self.acceptHeaderFormat() orelse .UNKNOWN;
return self.extensionFormat() orelse
self.acceptHeaderFormat() orelse
self.contentTypeHeaderFormat() orelse
.UNKNOWN;
}
pub fn getHeader(self: *Self, key: []const u8) ?[]const u8 {
@ -233,6 +236,17 @@ pub fn acceptHeaderFormat(self: *Self) ?jetzig.http.Request.Format {
return null;
}
pub fn contentTypeHeaderFormat(self: *Self) ?jetzig.http.Request.Format {
const acceptHeader = self.getHeader("content-type");
if (acceptHeader) |item| {
if (std.mem.eql(u8, item, "text/html")) return .HTML;
if (std.mem.eql(u8, item, "application/json")) return .JSON;
}
return null;
}
pub fn hash(self: *Self) ![]const u8 {
return try std.fmt.allocPrint(
self.allocator,