@@ -3,6 +3,8 @@ const httpz = @import("httpz");
3
3
const json = std .json ;
4
4
const log = std .log .scoped (.server );
5
5
6
+ const msgpack = @import ("msgpack" );
7
+
6
8
const MultiIndex = @import ("MultiIndex.zig" );
7
9
const IndexData = MultiIndex .IndexRef ;
8
10
const common = @import ("common.zig" );
@@ -92,6 +94,10 @@ const max_search_timeout = 10000;
92
94
const SearchRequestJSON = struct {
93
95
query : []const u32 ,
94
96
timeout : u32 = 0 ,
97
+
98
+ pub fn msgpackFormat () msgpack.StructFormat {
99
+ return .{ .as_map = .{ .key = .{ .field_name_prefix = 1 } } };
100
+ }
95
101
};
96
102
97
103
const SearchResultJSON = struct {
@@ -122,22 +128,84 @@ fn releaseIndex(ctx: *Context, index: *IndexData) void {
122
128
ctx .indexes .releaseIndex (index );
123
129
}
124
130
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 {
127
170
res .status = 400 ;
128
- try res . json (.{ .status = "invalid body " }, .{} );
171
+ try writeResponse (.{ .status = "no content " }, req , res );
129
172
return null ;
130
173
};
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 );
134
183
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
+ },
135
202
}
136
- return body_or_null .? ;
203
+
204
+ unreachable ;
137
205
}
138
206
139
207
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 ;
141
209
142
210
const index_ref = try getIndex (ctx , req , res , true ) orelse return ;
143
211
const index = & index_ref .index ;
@@ -157,7 +225,7 @@ fn handleSearch(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void
157
225
const results = index .search (body .query , req .arena , deadline ) catch | err | {
158
226
log .err ("index search error: {}" , .{err });
159
227
res .status = 500 ;
160
- return res . json (.{ .status = "internal error" }, .{} );
228
+ return writeResponse (.{ .status = "internal error" }, req , res );
161
229
};
162
230
163
231
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
166
234
results_json .results [i ] = SearchResultJSON { .id = r .id , .score = r .score };
167
235
}
168
236
}
169
- return res . json (results_json , .{} );
237
+ return writeResponse (results_json , req , res );
170
238
}
171
239
172
240
const UpdateRequestJSON = struct {
173
241
changes : []Change ,
242
+
243
+ pub fn msgpackFormat () msgpack.StructFormat {
244
+ return .{ .as_map = .{ .key = .{ .field_name_prefix = 1 } } };
245
+ }
174
246
};
175
247
176
248
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 ;
178
250
179
251
const index_ref = try getIndex (ctx , req , res , true ) orelse return ;
180
252
const index = & index_ref .index ;
@@ -185,10 +257,10 @@ fn handleUpdate(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void
185
257
index .update (body .changes ) catch | err | {
186
258
log .err ("index search error: {}" , .{err });
187
259
res .status = 500 ;
188
- return res . json (.{ .status = "internal error" }, .{} );
260
+ return writeResponse (.{ .status = "internal error" }, req , res );
189
261
};
190
262
191
- return res . json (.{ .status = "ok" }, .{} );
263
+ return writeResponse (.{ .status = "ok" }, req , res );
192
264
}
193
265
194
266
fn handleHeadIndex (ctx : * Context , req : * httpz.Request , res : * httpz.Response ) ! void {
0 commit comments