Open utterances-bot opened 11 months ago
Good job, my friend!
It seems that the std.http module doesn’t handle the request body, I just read the source code, and could not find the related processing logic, is it that I miss something or what?
@cs50Mu what exactly are you trying to do? I think you can read the request body just fine with the examples in the post.
No, I know you can read the resp body, but I want to send a request body, is there any way to do that?
If you mean via POST
request, there is also an example in the post as follows:
// Make the connection to the server.
var request = try client.request(.POST, uri, headers, .{});
defer request.deinit();
request.transfer_encoding = .chunked;
// Send the request and headers to the server.
try request.start();
// Send the payload.
try request.writer().writeAll("Zig Bits!\n");
try request.finish();
// Wait for the server to send use a response.
try request.wait();
Other than that, I am not sure.
@orhun oh, I missed that Q&A part.. sorry about that, if only the zig doc can have some simple demo use cases
Thanks for the great blog, Can you add a route showing how to handle the static files located at folder www
?
学到了
I got the static files loaded as below:
const std = @import("std");
const hash = @import("./hash.zig");
const routes = @import("./routes.zig");
const http = std.http;
const log = std.log.scoped(.server);
const server_addr = "127.0.0.1";
const server_port = 8000;
// Run the server and handle incoming requests.
fn runServer(server: *http.Server, allocator: std.mem.Allocator) !void {
outer: while (true) {
// Accept incoming connection.
var response = try server.accept(.{
.allocator = allocator,
});
defer response.deinit();
// Avoid Nagle's algorithm.
// <https://en.wikipedia.org/wiki/Nagle%27s_algorithm>
try std.os.setsockopt(
response.connection.stream.handle,
std.os.IPPROTO.TCP,
std.os.TCP.NODELAY,
&std.mem.toBytes(@as(c_int, 1)),
);
while (response.reset() != .closing) {
// Handle errors during request processing.
response.wait() catch |err| switch (err) {
error.HttpHeadersInvalid => continue :outer,
error.EndOfStream => continue,
else => return err,
};
// Process the request.
handleRequest(&response, allocator) catch |err| {
log.err("Error {any}", .{err});
continue :outer;
};
}
log.err("browser closed", .{});
}
}
// Handle an individual request.
fn handleRequest(response: *http.Server.Response, allocator: std.mem.Allocator) !void {
// Log the request details.
log.info("{s} {s} {s}", .{ @tagName(response.request.method), @tagName(response.request.version), response.request.target });
// Read the request body.
const body = try response.reader().readAllAlloc(allocator, 8192);
defer allocator.free(body);
// Set "connection" header to "keep-alive" if present in request headers.
if (response.request.headers.contains("connection")) {
try response.headers.append("connection", "keep-alive");
}
const target = response.request.target;
const method = response.request.method;
log.debug("target: {any}/{s}", .{ method, target });
if (std.mem.startsWith(u8, target, "/favicon.ico")) {
return error.FaviconNotFound;
}
response.transfer_encoding = .chunked;
if (std.mem.containsAtLeast(u8, target, 1, ".")) {
// Set "content-type" header to "text/html".
const file = std.mem.trimLeft(u8, target, &[_]u8{'/'});
// Check the MIME type based on the file extension
// const extension = std.fs.path.extension(file);
// var mimeTypes = std.StringHashMap([]const u8).init(allocator);
// try hash.init(&mimeTypes);
// defer mimeTypes.deinit();
// if (mimeTypes.get(extension)) |mimeType| {
// try response.headers.append("content-type", mimeType);
// std.debug.print("The MIME type for {s} is {s}\n", .{ extension, mimeType });
// } else {
// log.err("Unknown extension: {s}", .{extension});
// return error.UnKnownExtension;
// }
// Or use instead if if do not want to use HashMap
// if (std.mem.eql(u8, extension, ".html")) {
try response.headers.append("content-type", "text/html");
// } else if (std.mem.eql(u8, extension, ".js")) {
// try response.headers.append("content-type", "application/javascript");
// } else if (std.mem.eql(u8, extension, ".css")) {
// try response.headers.append("content-type", "text/css");
// } else {
// log.err("Unknown extension: {s}", .{extension});
// try response.headers.append("content-type", "text/plain");
// }
const contents = readFile(allocator, file) catch |err| {
log.err("Error reading file {s} => {any}", .{ file, err });
return error.FileNotFound;
};
log.info("file read as: \n{s}", .{contents});
// Write the response body.
try response.do();
if (response.request.method != .HEAD) {
try response.writeAll(contents);
try response.finish();
allocator.free(contents);
}
} else {
// Set "content-type" header to "text/plain".
try response.headers.append("content-type", "text/plain");
var routeRegister = std.StringHashMap(*const fn (response: *http.Server.Response) void).init(allocator);
try routes.init(&routeRegister);
defer routeRegister.deinit();
if (routeRegister.get(target)) |handler| {
// try response.headers.append("content-type", mimeType);
std.debug.print("Calling handler: {s} for route: {s}\n", .{ handler, target });
// Write the response body.
handler(response);
} else {
log.err("Unknown route: {s}", .{target});
// Write the response body.
try response.do();
if (response.request.method != .HEAD) {
try response.writeAll("404: ");
try response.writeAll("Wrong path!\n");
try response.writeAll(target);
try response.finish();
}
return error.ErrorNoEntity;
}
}
}
pub fn main() !void {
// Create an allocator.
const allocator = std.heap.page_allocator;
// Initialize the server.
var server = http.Server.init(allocator, .{ .reuse_address = true });
defer server.deinit();
// Log the server address and port.
log.info("Server is running at {s}:{d}", .{ server_addr, server_port });
// Parse the server address.
const address = std.net.Address.parseIp(server_addr, server_port) catch unreachable;
try server.listen(address);
// Run the server.
runServer(&server, allocator) catch |err| {
// Handle server errors.
log.err("server error: {}\n", .{err});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
std.os.exit(1);
};
}
pub fn readFile(allocator: std.mem.Allocator, filename: []const u8) ![]u8 {
const file = try std.fs.cwd().openFile(filename, .{});
defer file.close();
const contents = try file.reader().readAllAlloc(allocator, std.math.maxInt(usize));
errdefer allocator.free(contents);
return contents;
}
Though I found it is not required to add the MIME type, but below is the file I used to define the main MIMEs:
// hash.zig
const std = @import("std");
pub fn init(mimeTypes: *std.StringHashMap([]const u8)) !void {
try mimeTypes.put(".html", "text/html");
try mimeTypes.put(".js", "application/javascript");
try mimeTypes.put(".css", "text/css");
}
And for handling the routes, i used:
// routes.zig
const std = @import("std");
const http = std.http;
//pub const LoadFunction = fn (i32) !void;
pub fn init(routeRegister: *std.StringHashMap(*const fn (response: *http.Server.Response) void)) !void {
try routeRegister.put("/load", load);
}
pub fn load(response: *http.Server.Response) void {
std.log.info("file is loaded, {}", .{response});
response.do() catch |e| {
std.log.err("{any}", .{e});
};
if (response.request.method != .HEAD) {
response.writeAll("Hold on ") catch |e| {
std.log.err("{any}", .{e});
};
response.writeAll("will load the route!\n") catch |e| {
std.log.err("{any}", .{e});
};
response.writeAll("I executed the handler :)") catch |e| {
std.log.err("{any}", .{e});
};
response.finish() catch |e| {
std.log.err("{any}", .{e});
};
}
}
How can I send JSON data with the code below:
var request = try client.request(.POST, uri, headers, .{});
defer request.deinit();
// Set the encoding for the POST request.
request.transfer_encoding = .chunked;
try request.start();
And how can I parse the returned JSON data using:
// Make the connection to the server.
var request = try client.request(.GET, uri, headers, .{});
defer request.deinit();
// Send the request and headers to the server.
try request.start();
// Wait for the server to send use a response.
try request.wait();
For some reason, the connection hangs for the "not found" route unless I explicitly writeAll
a few bytes into it. But at the same time it works fine for automatic /favicon.ico
requests.
This was great!
amazig thanks! ;)
Any reason why zig was slower than rust hyper?
Also I strongly agree with @jedisct1 on the TLS1.3 issue. Glad you found a solution though, that was such an interesting read!
Could you improve performance of your http server by spawning a thread for each handleRequest ? It seems to me that this server wont be able to accept a big load since there is 0 concurency, am I right ?
Yeah, the HTTP server is very bare bones and written just for demonstration purposes. Also the code needs an update for the latest Zig version (usual Zig problem).
Do you have a recommandation on where I could find a "robust" http server written in Zig ?
You can take a look here: https://github.com/catdevnull/awesome-zig
Would it be even better if put behind nginx or this has nothing to do with performances ?
Nginx is just a proxy to your HTTP server, I think it can be used to parallelize things but at the end of the day you will be bottlenecked by the performance of your HTTP server.
Orhun's Blog
FOSS • Linux • Programming
https://blog.orhun.dev/zig-bits-04/