Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# AWS Lambda Runtime for Zig
![Zig v0.15](https://img.shields.io/badge/Zig-v0.15-black?logo=zig&logoColor=F7A41D "Zig v0.15")
![Zig v0.16](https://img.shields.io/badge/Zig-v0.16-black?logo=zig&logoColor=F7A41D "Zig v0.16")
[![MIT License](https://img.shields.io/github/license/by-nir/aws-lambda-zig)](/LICENSE)

Write _AWS Lambda_ functions in the Zig programming language to achieve blazing fast invocations and cold starts!
Expand Down Expand Up @@ -422,4 +422,4 @@ by the use of this software, part of it, or its derivatives. See [LICENSE](/LICE
for the complete terms of use.

**_AWS Lambda Runtime for Zig_ is not an official _Amazon Web Services_ software,
nor is it affiliated with _Amazon Web Services, Inc_.**
nor is it affiliated with _Amazon Web Services, Inc_.**
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
.version = "0.4.0",
.fingerprint = 0xdc70f8287a69c300,

.minimum_zig_version = "0.15.0",
.minimum_zig_version = "0.16.0-dev.2821+3edaef9e0",

.dependencies = .{},

Expand Down
2 changes: 1 addition & 1 deletion demo/debug.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub fn main() void {
}

fn handler(ctx: lambda.Context, event: []const u8) ![]const u8 {
var str: std.io.Writer.Allocating = try .initCapacity(ctx.arena, 1024);
var str: std.Io.Writer.Allocating = try .initCapacity(ctx.arena, 1024);

const cfg = ctx.config;
try str.writer.print(
Expand Down
15 changes: 11 additions & 4 deletions demo/stream.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@ pub fn main() void {
}

/// 0.5 seconds (in nanoseconds)
const HALF_SEC = 0.5 * std.time.ns_per_s;
const HALF_SEC = std.time.ns_per_s / 2;

fn handler(_: lambda.Context, _: []const u8, stream: lambda.Stream) !void {
// Start a textual event stream with a prelude body.
// Use `stream.open("text/event-stream")` instead when a prelude body is not needed.
const writer = try stream.openPrint("text/event-stream", "Loading {d} messages...\n\n", .{3});

// Wait for half a second.
std.Thread.sleep(HALF_SEC);
try sleepHalfSecond();

// Append multiple to the stream’s buffer without publishing to the client.
try writer.writeAll("id: 0\n");
try writer.print("data: This is message number {d}\n\n", .{1});

// Send the buffered response to the client.
try stream.publish();
std.Thread.sleep(HALF_SEC);
try sleepHalfSecond();

try writer.writeAll("id: 1\ndata: This is message number 2\n\n");
try stream.publish();
std.Thread.sleep(HALF_SEC);
try sleepHalfSecond();

// One last message to the client...
try writer.print("id: {d}\ndata: This is message number {d}\n\n", .{ 2, 3 });
Expand All @@ -46,3 +46,10 @@ fn handler(_: lambda.Context, _: []const u8, stream: lambda.Stream) !void {
fn doSomeCleanup() void {
// Some cleanup work...
}

fn sleepHalfSecond() !void {
try std.Io.Clock.Duration.sleep(.{
.clock = .awake,
.raw = .fromNanoseconds(HALF_SEC),
}, std.Options.debug_io);
}
8 changes: 4 additions & 4 deletions demo/url_buffer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ fn homePage(ctx: lambda.Context) ![]const u8 {
/// The `url.Request` contains both the HTTP request and additional AWS metadata.
fn ipAddrPage(ctx: lambda.Context, req: lambda.url.Request) ![]const u8 {
// Generate dynamic HTML content, note the usage of an arena allocator.
var html: std.io.Writer.Allocating = .init(ctx.arena);
var html: std.Io.Writer.Allocating = .init(ctx.arena);

try html.writer.writeAll(global_nav);
if (req.request_context.http.source_ip) |addr| {
Expand All @@ -110,7 +110,7 @@ fn ipAddrPage(ctx: lambda.Context, req: lambda.url.Request) ![]const u8 {

/// Use a parsed query parameter provided by the decoded request to greet the user.
fn greetPage(ctx: lambda.Context, req: lambda.url.Request) ![]const u8 {
var html: std.io.Writer.Allocating = .init(ctx.arena);
var html: std.Io.Writer.Allocating = .init(ctx.arena);

try html.writer.writeAll(global_nav);

Expand Down Expand Up @@ -165,7 +165,7 @@ fn storagePage(ctx: lambda.Context, req: lambda.url.Request) ![]const u8 {
}

// Encode the cookie value
var new_cookie: std.io.Writer.Allocating = .init(ctx.arena);
var new_cookie: std.Io.Writer.Allocating = .init(ctx.arena);
errdefer new_cookie.deinit();
try new_cookie.writer.writeAll("store=");
try std.base64.standard.Encoder.encodeWriter(&new_cookie.writer, value);
Expand All @@ -176,7 +176,7 @@ fn storagePage(ctx: lambda.Context, req: lambda.url.Request) ![]const u8 {
}

// Render a form to display and update the stored value.
var html: std.io.Writer.Allocating = .init(ctx.arena);
var html: std.Io.Writer.Allocating = .init(ctx.arena);
try html.writer.writeAll(global_nav);
try html.writer.print(
\\<form>
Expand Down
23 changes: 17 additions & 6 deletions demo/url_stream.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub fn main() void {
}

/// 0.5 seconds (in nanoseconds)
const HALF_SEC = 0.5 * std.time.ns_per_s;
const HALF_SEC = std.time.ns_per_s / 2;

fn handler(ctx: lambda.Context, event: []const u8, stream: lambda.Stream) !void {
// Decode the Lambda URLs event.
Expand Down Expand Up @@ -41,28 +41,28 @@ fn handler(ctx: lambda.Context, event: []const u8, stream: lambda.Stream) !void
});

// Wait for half a second.
std.Thread.sleep(HALF_SEC);
try sleepHalfSecond();

// Append multiple to the stream’s buffer without publishing to the client.
try writer.writeAll("<h2>Update #1</h2>");
try writer.print("<p>Current epoch: <time>{d}</time></p>", .{std.time.timestamp()});
try writer.print("<p>Current epoch: <time>{d}</time></p>", .{currentEpochSeconds()});

// Publish the buffered data to the client.
try stream.publish();
std.Thread.sleep(HALF_SEC);
try sleepHalfSecond();

try writer.writeAll(
\\<h2>Update #2</h2>
\\<p>Current epoch: 🕰️</p>
);
try stream.publish();
std.Thread.sleep(HALF_SEC);
try sleepHalfSecond();

// One last message to the client...
try writer.print(
\\<h2>Update #{d}</h2>
\\<p>Current epoch: <time>{d}</time></p>
, .{ 3, std.time.timestamp() });
, .{ 3, currentEpochSeconds() });
try stream.publish();

// We can optionally let the runtime know we have finished the response.
Expand All @@ -76,3 +76,14 @@ fn handler(ctx: lambda.Context, event: []const u8, stream: lambda.Stream) !void
fn doSomeCleanup() void {
// Some cleanup work...
}

fn sleepHalfSecond() !void {
try std.Io.Clock.Duration.sleep(.{
.clock = .awake,
.raw = .fromNanoseconds(HALF_SEC),
}, std.Options.debug_io);
}

fn currentEpochSeconds() i64 {
return std.Io.Clock.real.now(std.Options.debug_io).toSeconds();
}
8 changes: 4 additions & 4 deletions src/event/url.zig
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub const Response = struct {
pub const internal_server_error = "{\"statusCode\":500,body:\"Internal Server Error\"}";

pub fn encode(self: Response, gpa: Allocator) ![]const u8 {
var buffer: std.io.Writer.Allocating = .init(gpa);
var buffer: std.Io.Writer.Allocating = .init(gpa);
errdefer buffer.deinit();

try buffer.writer.writeByte('{');
Expand Down Expand Up @@ -156,7 +156,7 @@ test Response {
/// .body = .{ .textual = "<h1>Incoming...</h1>" },
/// });
/// ```
pub fn openStream(ctx: hdl.Context, stream: hdl.Stream, response: Response) !*std.io.Writer {
pub fn openStream(ctx: hdl.Context, stream: hdl.Stream, response: Response) !*std.Io.Writer {
// https://github.com/awslabs/aws-lambda-rust-runtime/blob/main/lambda-runtime/src/requests.rs
// https://aws.amazon.com/blogs/compute/using-response-streaming-with-aws-lambda-web-adapter-to-optimize-performance
const writer = try stream.openPrint(INTEGRATION_CONTENT_TYPE, "{f}", .{StreamingResponse{
Expand All @@ -183,12 +183,12 @@ const StreamingResponse = struct {
arena: Allocator,
response: Response,

pub fn format(self: @This(), writer: *std.io.Writer) !void {
pub fn format(self: @This(), writer: *std.Io.Writer) !void {
var response = self.response;
response.body = .{ .textual = "" };

const prelude = response.encode(self.arena) catch {
return std.io.Writer.Error.WriteFailed;
return std.Io.Writer.Error.WriteFailed;
};
try writer.writeAll(prelude);
}
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/api.zig
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ fn sendRequest(arena: Allocator, client: *Client, path: []const u8, payload: ?[]
}

fn formatError(arena: Allocator, err_category: []const u8, err: ErrorRequest) ![]const u8 {
var buffer: std.io.Writer.Allocating = .init(arena);
var buffer: std.Io.Writer.Allocating = .init(arena);

try buffer.writer.print(
\\{{"errorType":"{s}.
Expand Down
6 changes: 3 additions & 3 deletions src/runtime/context.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ pub const Context = struct {
config: ConfigMeta = .{},
/// Request metadata of the invocation.
request: RequestMeta = .{},

io: *const std.Io,
_force_destroy: *bool,
_kv: *const std.process.EnvMap = undefined,
_kv: *const std.process.Environ.Map = undefined,

/// Return the environmant value associated with a key.
pub fn env(self: Context, key: []const u8) ?[]const u8 {
Expand Down Expand Up @@ -102,7 +102,7 @@ pub const Context = struct {

/// Returns the URL (host and port) of the Runtime API.
/// https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html
pub fn loadMeta(ctx: *Context, env: *const std.process.EnvMap) void {
pub fn loadMeta(ctx: *Context, env: *const std.process.Environ.Map) void {
const cfg = &ctx.config;
ctx._kv = env;

Expand Down
4 changes: 2 additions & 2 deletions src/runtime/handle.zig
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ pub const Stream = struct {
};

/// Start streaming a response of a specified content type.
pub fn open(self: @This(), content_type: []const u8) !*std.io.Writer {
pub fn open(self: @This(), content_type: []const u8) !*std.Io.Writer {
return self.openPrint(content_type, "", {});
}

Expand All @@ -111,7 +111,7 @@ pub const Stream = struct {
content_type: []const u8,
comptime prelude_fmt: []const u8,
prelude_args: anytype,
) !*std.io.Writer {
) !*std.Io.Writer {
if (self.state.* != .pending) return error.ReopeningStream;

std.debug.assert(content_type.len > 0);
Expand Down
27 changes: 22 additions & 5 deletions src/runtime/serve.zig
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ pub const Options = struct {};
pub const Server = struct {
gpa: std.heap.GeneralPurposeAllocator(.{}),
arena: std.heap.ArenaAllocator,
threaded: std.Io.Threaded,
io: std.Io,
http: HttpClient,
env: std.process.EnvMap,
env: std.process.Environ.Map,
request_id: []const u8 = "",

pub fn init(self: *Server, _: Options) !void {
Expand All @@ -48,7 +50,20 @@ pub const Server = struct {
return initFailed(arena_alloc, null, error.MissingRuntimeOrigin, "Missing the runtime’s API origin URL");
};

self.http = HttpClient.init(gpa_alloc, api_origin) catch |err| {
const process_environ: std.process.Environ = if (@import("builtin").link_libc)
.{ .block = std.c.environ }
else
.empty;

// Initialize threaded IO - owned by Server
self.threaded = std.Io.Threaded.init(gpa_alloc, .{
.environ = process_environ,
});
errdefer self.threaded.deinit();

self.io = self.threaded.io();

HttpClient.init(&self.http, gpa_alloc, api_origin, self.io) catch |err| {
return initFailed(arena_alloc, null, err, "Creating a HTTP client failed");
};
}
Expand All @@ -57,6 +72,7 @@ pub const Server = struct {
self.http.deinit();
self.env.deinit();
self.arena.deinit();
self.threaded.deinit();

switch (self.gpa.deinit()) {
.ok => {},
Expand Down Expand Up @@ -98,6 +114,7 @@ pub const Server = struct {
.gpa = self.gpa.allocator(),
.arena = self.arena.allocator(),
._force_destroy = &force_terminate,
.io = &self.io,
};
ctx.loadMeta(&context, &self.env);

Expand Down Expand Up @@ -134,8 +151,8 @@ pub const Server = struct {
}

pub fn respondFailure(self: *Server, err: anyerror, trace: ?*std.builtin.StackTrace) !void {
if (trace) |t| {
log.err("The handler returned an error `{s}`.{f}", .{ @errorName(err), t });
if (trace) |_| {
log.err("The handler returned an error `{s}`.", .{@errorName(err)});
} else {
log.err("The handler returned an error `{s}`.", .{@errorName(err)});
}
Expand Down Expand Up @@ -203,7 +220,7 @@ pub const Server = struct {
body: std.http.BodyWriter,
arena: std.mem.Allocator,

pub fn writer(self: *Stream) *std.io.Writer {
pub fn writer(self: *Stream) *std.Io.Writer {
return &self.body.writer;
}

Expand Down
13 changes: 6 additions & 7 deletions src/utils/Http.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
const std = @import("std");
const testing = std.testing;
const Client = std.http.Client;
const Io = std.Io;
const Threaded = std.Io.Threaded;
const Allocator = std.mem.Allocator;
const builtin = @import("builtin");
const package_version = @import("meta.zig").package_version;
Expand All @@ -20,9 +22,9 @@ const USER_AGENT = std.fmt.comptimePrint(
client: Client,
uri: std.Uri,

pub fn init(gpa: Allocator, origin: []const u8) !Self {
pub fn init(self: *Self, gpa: Allocator, origin: []const u8, io: Io) !void {
const idx = std.mem.indexOfScalar(u8, origin, ':');
const uri = std.Uri{
self.uri = std.Uri{
.path = .{ .percent_encoded = "" },
.scheme = "http",
.host = .{ .raw = if (idx) |i| origin[0..i] else origin },
Expand All @@ -32,10 +34,7 @@ pub fn init(gpa: Allocator, origin: []const u8) !Self {
null,
};

return .{
.client = Client{ .allocator = gpa },
.uri = uri,
};
self.client = Client{ .allocator = gpa, .io = io };
}

pub fn deinit(self: *Self) void {
Expand Down Expand Up @@ -137,7 +136,7 @@ fn parseResponse(arena: Allocator, res: *Client.Response) !Result {
var transfer_buffer: [64]u8 = undefined;
const reader = res.reader(&transfer_buffer);

var buffer: std.io.Writer.Allocating = .init(arena);
var buffer: std.Io.Writer.Allocating = .init(arena);
_ = reader.streamRemaining(&buffer.writer) catch |err| switch (err) {
error.ReadFailed => return res.bodyErr().?,
else => |e| return e,
Expand Down
26 changes: 6 additions & 20 deletions src/utils/environ.zig
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
const std = @import("std");
const Allocator = std.mem.Allocator;

pub fn load(env: *std.process.EnvMap, gpa: Allocator) !void {
env.* = std.process.EnvMap.init(gpa);
if (@import("builtin").link_libc) {
var ptr = std.c.environ;
while (ptr[0]) |line| : (ptr += 1) try parseAndPutVar(env, line);
} else {
for (std.os.environ) |line| try parseAndPutVar(env, line);
}
}

/// Based on std.process.getEnvMap
fn parseAndPutVar(map: *std.process.EnvMap, line: [*]u8) !void {
var line_i: usize = 0;
while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
const key = line[0..line_i];

var end_i: usize = line_i;
while (line[end_i] != 0) : (end_i += 1) {}
const value = line[line_i + 1 .. end_i];
pub fn load(env: *std.process.Environ.Map, gpa: Allocator) !void {
const process_environ: std.process.Environ = if (@import("builtin").link_libc)
.{ .block = std.c.environ }
else
.empty;

try map.putMove(key, value);
env.* = try process_environ.createMap(gpa);
}