1
0
Fork 0
mirror of https://github.com/zigzap/zap.git synced 2025-10-20 15:14:08 +00:00
Find a file
2023-01-13 17:33:23 +01:00
examples fixed hello_json example 2023-01-13 16:30:34 +01:00
src don't pull this 2023-01-13 17:28:24 +01:00
.gitignore initial commit after adding facilio dep 2023-01-11 11:57:38 +01:00
.gitmodules moved deps into src 2023-01-11 12:17:27 +01:00
build.zig updated build.zig to make it more generic 2023-01-13 17:33:23 +01:00
LICENSE added LICENSE 2023-01-11 23:38:59 +01:00
README.md update readme with json example 2023-01-13 16:33:05 +01:00
shell.nix added shell.nix for my convenience 2023-01-12 18:08:01 +01:00

zap - blazingly fast backends

Zap is intended to become the zig replacement for the kind of REST APIs I used to write in python with Flask and mongodb, etc.

What I need for that is a blazingly fast, robust HTTP server that I can use with zig. While facil.io supports TLS, I don't mind HTTPS. In production, I use nginx as a reverse proxy anyway.

Zap wraps and patches facil.io - the C web application framework.

ZAP IS SUPER ALPHA

Here's what works:

  • Super easy build process: zag's build.zig fetches git sub-modules, applies a patch to facil.io's logging for microsecond precision, builds and optionally runs everything.
    • tested on Linux and macOS (arm, M1)
  • hello: welcomes you with some static HTML
  • routes: a super easy example dispatching on the HTTP path
  • serve: the traditional static web server with optional dynamic request handling
  • hello_json: serves you json dependent on HTTP path

I'll continue wrapping more of facil.io's functionality and adding stuff to zap to a point where I can use it as the JSON REST API backend for my current research project which is likely to need to go live in the summer of 2023.

Getting started

$ git clone https://github.com/renerocksai/zap.git
$ cd zap 
$ zig build run-hello
$ # open http://localhost:3000 in your browser

... and open http://localhost:3000 in your browser.

Examples

You build and run the examples via:

$ zig build [EXAMPLE]
$ ./zig-out/bin/[EXAMPLE]

... where [EXAMPLE] is one of hello, routes, or serve.

Example: building and running the hello example:

$ zig build hello 
$ ./zig-out/bin/hello

To just run an example, like routes, without generating an executable, run:

$ zig build run-[EXAMPLE]

Example: building and running the routes example:

$ zig build run-routes

hello

const std = @import("std");
const zap = @import("zap");

fn on_request(r: zap.SimpleRequest) void {
    if (r.path) |the_path| {
        std.debug.print("PATH: {s}\n", .{the_path});
    }

    if (r.query) |the_query| {
        std.debug.print("QUERY: {s}\n", .{the_query});
    }
    _ = r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>");
}

pub fn main() !void {
    var listener = zap.SimpleHttpListener.init(.{
        .port = 3000,
        .on_request = on_request,
        .log = false,
    });
    try listener.listen();

    std.debug.print("Listening on 0.0.0.0:3000\n", .{});

    // start worker threads
    zap.start(.{
        .threads = 2,
        .workers = 2,
    });
}

serve

const std = @import("std");
const zap = @import("zap");

fn on_request(r: zap.SimpleRequest) void {
    // TODO: send 404 response
    _ = r.sendBody("<html><body><h1>404 - File not found</h1></body></html>");
}

pub fn main() !void {
    var listener = zap.SimpleHttpListener.init(.{
        .port = 3000,
        .on_request = on_request,
        .public_folder = std.mem.span("examples/serve"),
        .log = true,
    });
    try listener.listen();

    std.debug.print("Listening on 0.0.0.0:3000\n", .{});

    // start worker threads
    zap.start(.{
        .threads = 2,
        .workers = 2,
    });
}

routes

const std = @import("std");
const zap = @import("zap");

fn dispatch_routes(r: zap.SimpleRequest) void {
    // dispatch
    if (r.path) |the_path| {
        if (routes.get(the_path)) |foo| {
            foo(r);
        }
    }
    // or default: present menu
    _ = r.sendBody(
        \\ <html>
        \\   <body>
        \\     <p><a href="/static">static</a></p>
        \\     <p><a href="/dynamic">dynamic</a></p>
        \\   </body>
        \\ </html>
    );
}

fn static_site(r: zap.SimpleRequest) void {
    _ = r.sendBody("<html><body><h1>Hello from STATIC ZAP!</h1></body></html>");
}

var dynamic_counter: i32 = 0;
fn dynamic_site(r: zap.SimpleRequest) void {
    dynamic_counter += 1;
    var buf: [128]u8 = undefined;
    const filled_buf = std.fmt.bufPrintZ(
        &buf,
        "<html><body><h1>Hello # {d} from DYNAMIC ZAP!!!</h1></body></html>",
        .{dynamic_counter},
    ) catch "ERROR";
    _ = r.sendBody(std.mem.span(filled_buf));
}

fn setup_routes(a: std.mem.Allocator) !void {
    routes = std.StringHashMap(zap.SimpleHttpRequestFn).init(a);
    try routes.put("/static", static_site);
    try routes.put("/dynamic", dynamic_site);
}

var routes: std.StringHashMap(zap.SimpleHttpRequestFn) = undefined;

pub fn main() !void {
    try setup_routes(std.heap.page_allocator);
    var listener = zap.SimpleHttpListener.init(.{
        .port = 3000,
        .on_request = dispatch_routes,
        .log = true,
    });
    try listener.listen();

    std.debug.print("Listening on 0.0.0.0:3000\n", .{});

    zap.start(.{
        .threads = 2,
        .workers = 2,
    });
}

hello_json

const std = @import("std");
const zap = @import("zap");

const User = struct {
    first_name: ?[]const u8 = null,
    last_name: ?[]const u8 = null,
};

fn on_request(r: zap.SimpleRequest) void {
    if (!std.mem.eql(u8, r.method.?, "GET"))
        return;

    // /user/n
    if (r.path) |the_path| {
        if (the_path.len < 7 or !std.mem.startsWith(u8, the_path, "/user/"))
            return;

        const user_id: usize = @intCast(usize, the_path[6] - 0x30);
        const user = users.get(user_id);

        var json_to_send: []const u8 = undefined;
        if (stringify(user, .{})) |json| {
            json_to_send = json;
        } else {
            json_to_send = "null";
        }
        std.debug.print("<< json: {s}\n", .{json_to_send});
        r.setContentType(.JSON);
        _ = r.sendBody(json_to_send);
    }
}

const UserMap = std.AutoHashMap(usize, User);

var users: UserMap = undefined;
fn setupUserData(a: std.mem.Allocator) !void {
    users = UserMap.init(a);
    try users.put(1, .{ .first_name = "renerocksai" });
    try users.put(2, .{ .first_name = "Your", .last_name = "Mom" });
}

fn stringify(value: anytype, options: std.json.StringifyOptions) ?[]const u8 {
    var buf: [100]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buf);
    var string = std.ArrayList(u8).init(fba.allocator());
    if (std.json.stringify(value, options, string.writer())) {
        return string.items;
    } else |_| { // error
        return null;
    }
}

pub fn main() !void {
    var a = std.heap.page_allocator;
    try setupUserData(a);
    var listener = zap.SimpleHttpListener.init(.{
        .port = 3000,
        .on_request = on_request,
        .log = false,
    });
    try listener.listen();

    std.debug.print("Listening on 0.0.0.0:3000\n", .{});

    // start worker threads
    zap.start(.{
        .threads = 2,
        .workers = 2,
    });
}