mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 14:06:08 +00:00
Implement MIME type inference
When serving content from `public/`, use MIME type lookup to provide an appropriate content-type header. MIME types borrowed from: https://mimetype.io/all-types https://github.com/patrickmccallum/mimetype-io/blob/master/src/mimeData.json
This commit is contained in:
parent
be85c13369
commit
d855b9f703
@ -32,10 +32,11 @@ If you are interested in _Jetzig_ you will probably find these tools interesting
|
|||||||
* :white_check_mark: Request/response headers.
|
* :white_check_mark: Request/response headers.
|
||||||
* :white_check_mark: Stack trace output on error.
|
* :white_check_mark: Stack trace output on error.
|
||||||
* :white_check_mark: Static content generation.
|
* :white_check_mark: Static content generation.
|
||||||
* :x: Param/JSON payload parsing/abstracting.
|
* :white_check_mark: Param/JSON payload parsing/abstracting.
|
||||||
* :x: Static content paramater definitions.
|
* :white_check_mark: Static content parameter definitions.
|
||||||
|
* :white_check_mark: Middleware interface.
|
||||||
|
* :white_check_mark: MIME type inference.
|
||||||
* :x: Environment configurations (develompent/production/etc.)
|
* :x: Environment configurations (develompent/production/etc.)
|
||||||
* :x: Middleware extensions (for e.g. authentication).
|
|
||||||
* :x: Email delivery.
|
* :x: Email delivery.
|
||||||
* :x: Custom/non-conventional routes.
|
* :x: Custom/non-conventional routes.
|
||||||
* :x: General-purpose cache.
|
* :x: General-purpose cache.
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const GenerateRoutes = @import("src/GenerateRoutes.zig");
|
pub const GenerateRoutes = @import("src/GenerateRoutes.zig");
|
||||||
|
pub const GenerateMimeTypes = @import("src/GenerateMimeTypes.zig");
|
||||||
pub const TemplateFn = @import("src/jetzig.zig").TemplateFn;
|
pub const TemplateFn = @import("src/jetzig.zig").TemplateFn;
|
||||||
pub const StaticRequest = @import("src/jetzig.zig").StaticRequest;
|
pub const StaticRequest = @import("src/jetzig.zig").StaticRequest;
|
||||||
pub const http = @import("src/jetzig/http.zig");
|
pub const http = @import("src/jetzig/http.zig");
|
||||||
@ -20,9 +21,12 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mime_module = try GenerateMimeTypes.generateMimeModule(b);
|
||||||
|
|
||||||
b.installArtifact(lib);
|
b.installArtifact(lib);
|
||||||
|
|
||||||
const jetzig_module = b.addModule("jetzig", .{ .root_source_file = .{ .path = "src/jetzig.zig" } });
|
const jetzig_module = b.addModule("jetzig", .{ .root_source_file = .{ .path = "src/jetzig.zig" } });
|
||||||
|
jetzig_module.addImport("mime_types", mime_module);
|
||||||
lib.root_module.addImport("jetzig", jetzig_module);
|
lib.root_module.addImport("jetzig", jetzig_module);
|
||||||
|
|
||||||
const zmpl_dep = b.dependency(
|
const zmpl_dep = b.dependency(
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
.version = "0.0.0",
|
.version = "0.0.0",
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.zmpl = .{
|
.zmpl = .{
|
||||||
.url = "https://github.com/jetzig-framework/zmpl/archive/41ac97a026e124f93d316eb838fa015a5a52bb15.tar.gz",
|
.url = "https://github.com/jetzig-framework/zmpl/archive/84a712349e0cf679fc5c9900b805335d51d9ce86.tar.gz",
|
||||||
.hash = "122053b4c76247572201afbbec8fbde8c014c25984a3ca780ed4f6d514cc939038df",
|
.hash = "1220e9f2133f6cd24c370850cbe3816e3d8b97c33dd822bf7eaf8f6f0ea2cfd2f8db",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
0
demo/public/styles.css
Normal file
0
demo/public/styles.css
Normal file
47
src/GenerateMimeTypes.zig
Normal file
47
src/GenerateMimeTypes.zig
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const JsonMimeType = struct {
|
||||||
|
name: []const u8,
|
||||||
|
fileTypes: [][]const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Invoked at build time to parse mimeData.json into an array of `MimeType` which can then be
|
||||||
|
/// written out as a Zig struct and imported at runtime.
|
||||||
|
pub fn generateMimeModule(build: *std.Build) !*std.Build.Module {
|
||||||
|
const file = try std.fs.openFileAbsolute(build.pathFromRoot("src/jetzig/http/mime/mimeData.json"), .{});
|
||||||
|
const stat = try file.stat();
|
||||||
|
const json = try file.readToEndAlloc(build.allocator, stat.size);
|
||||||
|
defer build.allocator.free(json);
|
||||||
|
|
||||||
|
const parsed_mime_types = try std.json.parseFromSlice(
|
||||||
|
[]JsonMimeType,
|
||||||
|
build.allocator,
|
||||||
|
json,
|
||||||
|
.{ .ignore_unknown_fields = true },
|
||||||
|
);
|
||||||
|
|
||||||
|
var buf = std.ArrayList(u8).init(build.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
const writer = buf.writer();
|
||||||
|
|
||||||
|
try writer.writeAll("pub const MimeType = struct { name: []const u8, file_type: []const u8 };");
|
||||||
|
try writer.writeAll("pub const mime_types = [_]MimeType{\n");
|
||||||
|
for (parsed_mime_types.value) |mime_type| {
|
||||||
|
for (mime_type.fileTypes) |file_type| {
|
||||||
|
const entry = try std.fmt.allocPrint(
|
||||||
|
build.allocator,
|
||||||
|
\\.{{ .name = "{s}", .file_type = "{s}" }},
|
||||||
|
\\
|
||||||
|
,
|
||||||
|
.{ mime_type.name, file_type },
|
||||||
|
);
|
||||||
|
try writer.writeAll(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try writer.writeAll("};\n");
|
||||||
|
|
||||||
|
const write_files = build.addWriteFiles();
|
||||||
|
const generated_file = write_files.add("mime_types.zig", buf.items);
|
||||||
|
return build.createModule(.{ .root_source_file = generated_file });
|
||||||
|
}
|
@ -33,6 +33,7 @@ pub const View = views.View;
|
|||||||
pub const config = struct {
|
pub const config = struct {
|
||||||
pub const max_bytes_request_body: usize = std.math.pow(usize, 2, 16);
|
pub const max_bytes_request_body: usize = std.math.pow(usize, 2, 16);
|
||||||
pub const max_bytes_static_content: usize = std.math.pow(usize, 2, 16);
|
pub const max_bytes_static_content: usize = std.math.pow(usize, 2, 16);
|
||||||
|
pub const public_content = .{ .path = "public" };
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) !App {
|
pub fn init(allocator: std.mem.Allocator) !App {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const jetzig = @import("../jetzig.zig");
|
const jetzig = @import("../jetzig.zig");
|
||||||
|
const mime_types = @import("mime_types").mime_types; // Generated at build time.
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
@ -18,6 +19,10 @@ pub fn deinit(self: Self) void {
|
|||||||
/// automatically created at build time. `templates` should be
|
/// automatically created at build time. `templates` should be
|
||||||
/// `@import("src/app/views/zmpl.manifest.zig").templates`, created by Zmpl at compile time.
|
/// `@import("src/app/views/zmpl.manifest.zig").templates`, created by Zmpl at compile time.
|
||||||
pub fn start(self: Self, routes: []jetzig.views.Route, templates: []jetzig.TemplateFn) !void {
|
pub fn start(self: Self, routes: []jetzig.views.Route, templates: []jetzig.TemplateFn) !void {
|
||||||
|
var mime_map = jetzig.http.mime.MimeMap.init(self.allocator);
|
||||||
|
defer mime_map.deinit();
|
||||||
|
try mime_map.build();
|
||||||
|
|
||||||
var server = jetzig.http.Server.init(
|
var server = jetzig.http.Server.init(
|
||||||
self.allocator,
|
self.allocator,
|
||||||
self.host,
|
self.host,
|
||||||
@ -25,6 +30,7 @@ pub fn start(self: Self, routes: []jetzig.views.Route, templates: []jetzig.Templ
|
|||||||
self.server_options,
|
self.server_options,
|
||||||
routes,
|
routes,
|
||||||
templates,
|
templates,
|
||||||
|
&mime_map,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (routes) |*route| {
|
for (routes) |*route| {
|
||||||
|
@ -10,3 +10,4 @@ pub const Headers = @import("http/Headers.zig");
|
|||||||
pub const Query = @import("http/Query.zig");
|
pub const Query = @import("http/Query.zig");
|
||||||
pub const status_codes = @import("http/status_codes.zig");
|
pub const status_codes = @import("http/status_codes.zig");
|
||||||
pub const middleware = @import("http/middleware.zig");
|
pub const middleware = @import("http/middleware.zig");
|
||||||
|
pub const mime = @import("http/mime.zig");
|
||||||
|
@ -26,6 +26,7 @@ options: ServerOptions,
|
|||||||
start_time: i128 = undefined,
|
start_time: i128 = undefined,
|
||||||
routes: []jetzig.views.Route,
|
routes: []jetzig.views.Route,
|
||||||
templates: []jetzig.TemplateFn,
|
templates: []jetzig.TemplateFn,
|
||||||
|
mime_map: *jetzig.http.mime.MimeMap,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
@ -36,6 +37,7 @@ pub fn init(
|
|||||||
options: ServerOptions,
|
options: ServerOptions,
|
||||||
routes: []jetzig.views.Route,
|
routes: []jetzig.views.Route,
|
||||||
templates: []jetzig.TemplateFn,
|
templates: []jetzig.TemplateFn,
|
||||||
|
mime_map: *jetzig.http.mime.MimeMap,
|
||||||
) Self {
|
) Self {
|
||||||
const server = std.http.Server.init(.{ .reuse_address = true });
|
const server = std.http.Server.init(.{ .reuse_address = true });
|
||||||
|
|
||||||
@ -49,6 +51,7 @@ pub fn init(
|
|||||||
.options = options,
|
.options = options,
|
||||||
.routes = routes,
|
.routes = routes,
|
||||||
.templates = templates,
|
.templates = templates,
|
||||||
|
.mime_map = mime_map,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,8 +332,8 @@ fn matchRoute(self: *Self, request: *jetzig.http.Request, static: bool) !?jetzig
|
|||||||
const StaticResource = struct { content: []const u8, mime_type: []const u8 = "application/octet-stream" };
|
const StaticResource = struct { content: []const u8, mime_type: []const u8 = "application/octet-stream" };
|
||||||
|
|
||||||
fn matchStaticResource(self: *Self, request: *jetzig.http.Request) !?StaticResource {
|
fn matchStaticResource(self: *Self, request: *jetzig.http.Request) !?StaticResource {
|
||||||
const public_content = try matchPublicContent(request);
|
const public_resource = try self.matchPublicContent(request);
|
||||||
if (public_content) |content| return .{ .content = content };
|
if (public_resource) |resource| return resource;
|
||||||
|
|
||||||
const static_content = try self.matchStaticContent(request);
|
const static_content = try self.matchStaticContent(request);
|
||||||
if (static_content) |content| return .{
|
if (static_content) |content| return .{
|
||||||
@ -344,11 +347,14 @@ fn matchStaticResource(self: *Self, request: *jetzig.http.Request) !?StaticResou
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matchPublicContent(request: *jetzig.http.Request) !?[]const u8 {
|
fn matchPublicContent(self: *Self, request: *jetzig.http.Request) !?StaticResource {
|
||||||
if (request.path.len < 2) return null;
|
if (request.path.len < 2) return null;
|
||||||
if (request.method != .GET) return null;
|
if (request.method != .GET) return null;
|
||||||
|
|
||||||
var iterable_dir = std.fs.cwd().openDir("public", .{ .iterate = true }) catch |err| {
|
var iterable_dir = std.fs.cwd().openDir(
|
||||||
|
jetzig.config.public_content.path,
|
||||||
|
.{ .iterate = true, .no_follow = true },
|
||||||
|
) catch |err| {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
error.FileNotFound => return null,
|
error.FileNotFound => return null,
|
||||||
else => return err,
|
else => return err,
|
||||||
@ -363,11 +369,17 @@ fn matchPublicContent(request: *jetzig.http.Request) !?[]const u8 {
|
|||||||
if (file.kind != .file) continue;
|
if (file.kind != .file) continue;
|
||||||
|
|
||||||
if (std.mem.eql(u8, file.path, request.path[1..])) {
|
if (std.mem.eql(u8, file.path, request.path[1..])) {
|
||||||
return try iterable_dir.readFileAlloc(
|
const content = try iterable_dir.readFileAlloc(
|
||||||
request.allocator,
|
request.allocator,
|
||||||
file.path,
|
file.path,
|
||||||
jetzig.config.max_bytes_static_content,
|
jetzig.config.max_bytes_static_content,
|
||||||
);
|
);
|
||||||
|
const extension = std.fs.path.extension(file.path);
|
||||||
|
const mime_type = if (self.mime_map.get(extension)) |mime| mime else "application/octet-stream";
|
||||||
|
return .{
|
||||||
|
.content = content,
|
||||||
|
.mime_type = mime_type,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
54
src/jetzig/http/mime.zig
Normal file
54
src/jetzig/http/mime.zig
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Mime types borrowed from here:
|
||||||
|
// https://mimetype.io/all-types
|
||||||
|
// https://github.com/patrickmccallum/mimetype-io/blob/master/src/mimeData.json
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const mime_types = @import("mime_types").mime_types; // Generated at build time.
|
||||||
|
|
||||||
|
/// Provides information about a given MIME Type.
|
||||||
|
pub const MimeType = struct {
|
||||||
|
name: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Attempts to map a given extension to a mime type.
|
||||||
|
pub fn fromExtension(extension: []const u8) ?MimeType {
|
||||||
|
for (mime_types) |mime_type| {
|
||||||
|
if (std.mem.eql(u8, extension, mime_type.file_type)) return .{ .name = mime_type.name };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const MimeMap = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
map: std.StringHashMap([]const u8),
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) MimeMap {
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.map = std.StringHashMap([]const u8).init(allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *MimeMap) void {
|
||||||
|
var it = self.map.iterator();
|
||||||
|
while (it.next()) |item| {
|
||||||
|
self.allocator.free(item.key_ptr.*);
|
||||||
|
self.allocator.free(item.value_ptr.*);
|
||||||
|
}
|
||||||
|
self.map.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self: *MimeMap) !void {
|
||||||
|
for (mime_types) |mime_type| {
|
||||||
|
try self.map.put(
|
||||||
|
try self.allocator.dupe(u8, mime_type.file_type),
|
||||||
|
try self.allocator.dupe(u8, mime_type.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *MimeMap, file_type: []const u8) ?[]const u8 {
|
||||||
|
return self.map.get(file_type);
|
||||||
|
}
|
||||||
|
};
|
13872
src/jetzig/http/mime/mimeData.json
Normal file
13872
src/jetzig/http/mime/mimeData.json
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user