Skip to content

Commit e754973

Browse files
committed
Allow msgpack im the server
1 parent 61ab171 commit e754973

File tree

2 files changed

+92
-13
lines changed

2 files changed

+92
-13
lines changed

lib/msgpack/any.zig

+7
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ pub fn packAny(writer: anytype, comptime T: type, value: T) !void {
7676
.Bool => return packBool(writer, T, value),
7777
.Int => return packInt(writer, T, value),
7878
.Float => return packFloat(writer, T, value),
79+
.Array => |arr_info| {
80+
if (arr_info.child == u8) {
81+
return packString(writer, &value);
82+
}
83+
},
7984
.Pointer => |ptr_info| {
8085
if (ptr_info.size == .Slice) {
8186
switch (ptr_info.child) {
@@ -86,6 +91,8 @@ pub fn packAny(writer: anytype, comptime T: type, value: T) !void {
8691
return packArray(writer, T, value);
8792
},
8893
}
94+
} else if (ptr_info.size == .One) {
95+
return packAny(writer, ptr_info.child, value.*);
8996
}
9097
},
9198
.Struct => return packStruct(writer, T, value),

src/server.zig

+85-13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ const httpz = @import("httpz");
33
const json = std.json;
44
const log = std.log.scoped(.server);
55

6+
const msgpack = @import("msgpack");
7+
68
const MultiIndex = @import("MultiIndex.zig");
79
const IndexData = MultiIndex.IndexRef;
810
const common = @import("common.zig");
@@ -92,6 +94,10 @@ const max_search_timeout = 10000;
9294
const SearchRequestJSON = struct {
9395
query: []const u32,
9496
timeout: u32 = 0,
97+
98+
pub fn msgpackFormat() msgpack.StructFormat {
99+
return .{ .as_map = .{ .key = .{ .field_name_prefix = 1 } } };
100+
}
95101
};
96102

97103
const SearchResultJSON = struct {
@@ -122,22 +128,84 @@ fn releaseIndex(ctx: *Context, index: *IndexData) void {
122128
ctx.indexes.releaseIndex(index);
123129
}
124130

125-
fn getJsonBody(comptime T: type, req: *httpz.Request, res: *httpz.Response) !?T {
126-
const body_or_null = req.json(T) catch {
131+
const ContentType = enum {
132+
json,
133+
msgpack,
134+
};
135+
136+
fn parseContentTypeHeader(content_type: []const u8) !ContentType {
137+
if (std.mem.eql(u8, content_type, "application/json")) {
138+
return .json;
139+
} else if (std.mem.eql(u8, content_type, "application/vnd.msgpack")) {
140+
return .msgpack;
141+
}
142+
return error.InvalidContentType;
143+
}
144+
145+
fn parseAcceptHeader(req: *httpz.Request) ContentType {
146+
if (req.header("accept")) |accept_header| {
147+
if (std.mem.eql(u8, accept_header, "application/json")) {
148+
return .json;
149+
} else if (std.mem.eql(u8, accept_header, "application/vnd.msgpack")) {
150+
return .msgpack;
151+
}
152+
}
153+
return .json;
154+
}
155+
156+
fn writeResponse(value: anytype, req: *httpz.Request, res: *httpz.Response) !void {
157+
const content_type = parseAcceptHeader(req);
158+
159+
switch (content_type) {
160+
.json => try res.json(value, .{}),
161+
.msgpack => {
162+
res.header("content-type", "application/vnd.msgpack");
163+
try msgpack.encode(res.writer(), @TypeOf(value), value);
164+
},
165+
}
166+
}
167+
168+
fn getRequestBody(comptime T: type, req: *httpz.Request, res: *httpz.Response) !?T {
169+
const content = req.body() orelse {
127170
res.status = 400;
128-
try res.json(.{ .status = "invalid body" }, .{});
171+
try writeResponse(.{ .status = "no content" }, req, res);
129172
return null;
130173
};
131-
if (body_or_null == null) {
132-
res.status = 400;
133-
try res.json(.{ .status = "invalid body" }, .{});
174+
175+
const content_type_name = req.header("content-type") orelse {
176+
res.status = 415;
177+
try writeResponse(.{ .status = "missing content type header" }, req, res);
178+
return null;
179+
};
180+
const content_type = parseContentTypeHeader(content_type_name) catch {
181+
res.status = 415;
182+
try writeResponse(.{ .status = "unsupported content type" }, req, res);
134183
return null;
184+
};
185+
186+
switch (content_type) {
187+
.json => {
188+
return json.parseFromSliceLeaky(T, req.arena, content, .{}) catch {
189+
res.status = 400;
190+
try writeResponse(.{ .status = "invalid body" }, req, res);
191+
return null;
192+
};
193+
},
194+
.msgpack => {
195+
var stream = std.io.fixedBufferStream(content);
196+
return msgpack.decode(stream.reader(), req.arena, T) catch {
197+
res.status = 400;
198+
try writeResponse(.{ .status = "invalid body" }, req, res);
199+
return null;
200+
};
201+
},
135202
}
136-
return body_or_null.?;
203+
204+
unreachable;
137205
}
138206

139207
fn handleSearch(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
140-
const body = try getJsonBody(SearchRequestJSON, req, res) orelse return;
208+
const body = try getRequestBody(SearchRequestJSON, req, res) orelse return;
141209

142210
const index_ref = try getIndex(ctx, req, res, true) orelse return;
143211
const index = &index_ref.index;
@@ -157,7 +225,7 @@ fn handleSearch(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void
157225
const results = index.search(body.query, req.arena, deadline) catch |err| {
158226
log.err("index search error: {}", .{err});
159227
res.status = 500;
160-
return res.json(.{ .status = "internal error" }, .{});
228+
return writeResponse(.{ .status = "internal error" }, req, res);
161229
};
162230

163231
var results_json = SearchResultsJSON{ .results = try req.arena.alloc(SearchResultJSON, results.count()) };
@@ -166,15 +234,19 @@ fn handleSearch(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void
166234
results_json.results[i] = SearchResultJSON{ .id = r.id, .score = r.score };
167235
}
168236
}
169-
return res.json(results_json, .{});
237+
return writeResponse(results_json, req, res);
170238
}
171239

172240
const UpdateRequestJSON = struct {
173241
changes: []Change,
242+
243+
pub fn msgpackFormat() msgpack.StructFormat {
244+
return .{ .as_map = .{ .key = .{ .field_name_prefix = 1 } } };
245+
}
174246
};
175247

176248
fn handleUpdate(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
177-
const body = try getJsonBody(UpdateRequestJSON, req, res) orelse return;
249+
const body = try getRequestBody(UpdateRequestJSON, req, res) orelse return;
178250

179251
const index_ref = try getIndex(ctx, req, res, true) orelse return;
180252
const index = &index_ref.index;
@@ -185,10 +257,10 @@ fn handleUpdate(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void
185257
index.update(body.changes) catch |err| {
186258
log.err("index search error: {}", .{err});
187259
res.status = 500;
188-
return res.json(.{ .status = "internal error" }, .{});
260+
return writeResponse(.{ .status = "internal error" }, req, res);
189261
};
190262

191-
return res.json(.{ .status = "ok" }, .{});
263+
return writeResponse(.{ .status = "ok" }, req, res);
192264
}
193265

194266
fn handleHeadIndex(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {

0 commit comments

Comments
 (0)