2023-04-24 14:04:27 +02:00
# zerve
A simple framework for writing web services in zig.
2023-05-04 11:07:33 +02:00
* [Create a simple Web App ](#create-a-simple-web-app )
2023-05-04 11:08:50 +02:00
* [Types ](#types )
* [Route ](#route )
2023-05-04 11:11:37 +02:00
* [Handler Functions ](#handler-functions )
* [Request ](#request )
* [Response ](#response )
* [Header ](#header )
2023-05-19 12:33:21 +02:00
* [Cookies ](#cookies )
2023-05-19 14:45:49 +02:00
* [Read Cookie from Request ](#read-cookie-from-request )
* [Add Cookie to Response ](#add-cookie-to-response )
2023-05-04 11:11:37 +02:00
* [Method ](#method )
* [HTTP-Version ](#http-version )
* [Namespaces ](#namespaces )
* [Server ](#server )
2023-04-24 17:07:48 +02:00
2023-05-19 14:45:49 +02:00
# Create a simple web app
2023-04-24 14:04:27 +02:00
```zig
2023-04-25 23:21:52 +02:00
const zrv = @import ("zerve"); // Or set the path to zerve.zig e.g. @import ("zerve-main/src/zerve.zig");
2023-04-24 14:13:48 +02:00
const Request = zrv.Request;
const Response = zrv.Response;
const Server = zrv.Server;
2023-04-24 14:44:58 +02:00
const Route = zrv.Route;
2023-04-24 17:07:48 +02:00
const allocator = std.heap.page_allocator; // Choose any allocator you want!
2023-04-24 14:04:27 +02:00
2023-05-02 21:48:18 +02:00
fn index(req: *Request) Response {
2023-04-24 14:04:27 +02:00
_=req;
2023-05-01 10:10:17 +02:00
return Response.write("hello!");
2023-04-24 14:04:27 +02:00
}
2023-05-02 21:48:18 +02:00
fn about(req: *Request) Response {
2023-04-24 14:04:27 +02:00
_=req;
2023-05-01 10:10:17 +02:00
return Response.write("about site");
2023-04-24 14:04:27 +02:00
}
2023-05-02 21:48:18 +02:00
fn writeJson(req: *Request) Response {
2023-04-24 14:04:27 +02:00
_=req;
2023-05-04 14:03:49 +02:00
return Response.json("[1, 2, 3, 4]");
2023-04-24 14:04:27 +02:00
}
pub fn main() !void {
const rt = [_]Route{.{"/", index}, .{"/about", about}, .{"/json", writeJson}};
2023-04-24 14:21:09 +02:00
try Server.listen("0.0.0.0", 8080, & rt, allocator); // listens to http://localhost:8080
// http://localhost:8080/ "hello!"
// http://localhost:8080/about "about site"
// http://localhost:8080/json "[1, 2, 3, 4]" (JSON-Response)
2023-04-24 14:04:27 +02:00
}
2023-04-24 17:07:48 +02:00
```
2023-05-19 14:45:49 +02:00
# Types
2023-04-24 17:07:48 +02:00
2023-05-19 14:45:49 +02:00
## Route
2023-04-24 14:04:27 +02:00
2023-04-24 17:07:48 +02:00
To write a web service with **zerve** you have to configure one or more Routes. They are being set by creating an Array of `Route` .
Example:
```zig
const rt = [_]Route{.{"/hello", helloFunction}, "/about", aboutFunction};
2023-04-24 14:13:48 +02:00
```
2023-04-24 17:07:48 +02:00
You can also set only one path and link it to a handler function, but since `Server.listen()` takes an Array of `Route` as one of it's arguments,
you have do declare it as an Array as well:
```zig
const rt = [_]Route{.{"/hello", helloFunction}};
```
2023-05-19 14:45:49 +02:00
## Handler Functions
2023-04-24 17:07:48 +02:00
2023-05-02 21:48:18 +02:00
Every Request is handled by a handler function. It has to be of this type: `fn(req: *Request) Response`
2023-04-24 17:07:48 +02:00
Example:
```zig
2023-05-02 21:48:18 +02:00
fn hello(req: *Request) Response {
2023-04-24 17:07:48 +02:00
_ = req;
2023-05-01 10:10:17 +02:00
return Response.write("hello"); // `Server` will return a Reponse with body "hello". You will see "hello" on your browser.
2023-04-24 17:07:48 +02:00
}
```
2023-05-19 14:45:49 +02:00
## Request
2023-04-24 17:07:48 +02:00
2023-05-19 14:45:49 +02:00
This represents the Request sent by the client.
2023-04-24 17:07:48 +02:00
```zig
pub const Request = struct {
/// The Request Method, e.g. "GET"
method: Method,
/// HTTP-Version of the Request sent by the client
httpVersion: HTTP_Version,
/// Represents the request headers sent by the client
headers: []const Header,
2023-04-24 22:21:47 +02:00
/// The Request URI
2023-04-24 17:07:48 +02:00
uri: []const u8,
/// Represents the request body sent by the client
body: []const u8,
};
```
2023-05-19 14:45:49 +02:00
### Get Query Params
**zerve** lets you easily extract query params no matter if `Request` method is `Get` or `POST` .
This can be done by using the `getQuery` method of `Request` .
Example:
```zig
fn index(req: zrv.Request) zrv.Response {
// Assuming that a query string has been sent by the client containing the requested param,
// e.g. `?user=james`
const user = req.getQuery("user"); // This will return an optional
if (user == null) return Response.write("") else return Response.write(user);
}
```
## Response
2023-04-24 17:07:48 +02:00
A Response that is sent ny the server. Every handler function has to return a `Response` .
```zig
pub const Response = struct {
httpVersion: HTTP_Version = HTTP_Version.HTTP1_1,
/// Response status, default is "200 OK"
status: stat.Status = stat.Status.OK,
/// Response eaders sent by the server
headers: []const Header = & [_]Header{.{ .key = "Content-Type", .value = "text/html; charset=utf-8" }},
/// Response body sent by the server
body: []const u8 = "",
/// Write a simple response.
2023-05-19 12:33:21 +02:00
pub fn write(s: []const u8) Response {
2023-04-24 17:07:48 +02:00
return Response{ .body = s };
}
/// Send a response with json content.
pub fn json(j: []const u8) Response {
return Response{ .headers = & [_]Header{.{ .key = "Content-Type", .value = "application/json" }}, .body = j };
}
/// Send a response with status not found.
pub fn notfound(s: []const u8) Response {
return Response{ .status = stat.Status.NOT_FOUND, .body = s };
}
/// Send a response with status forbidden.
pub fn forbidden(s: []u8) Response {
return Response{ .status = stat.Status.FORBIDDEN, .body = s };
}
};
```
2023-05-19 14:45:49 +02:00
## Header
2023-04-24 17:07:48 +02:00
Every Request or Response has Headers represented by an Array of Headers. Every Header has a key and a value.
```zig
pub const Header = struct {
key: []const u8,
value: []const u8,
};
```
2023-05-19 14:45:49 +02:00
## Cookies
### Read Cookie from Request
2023-05-19 12:33:21 +02:00
To read the Cookie of a request by key, `Request` has a `cookie` -method.
It returns an optional and fetches the value of a `Request.Cookie` .
Get Request Cookie value by key:
```zig
fn index(req: *zrv.Request) zrv.Response {
2023-05-19 12:36:31 +02:00
2023-05-19 12:33:21 +02:00
// Fetches the cookie value by cookie name.
// The `cookie` method will return an optional and will be `null`
// in case that the cookie does not exist.
const cookie = if (req.cookie("password")) |password| password else "";
return zrv.Response.write("cookie-test");
}
```
2023-05-19 14:45:49 +02:00
### Add Cookie to Response
2023-05-19 12:33:21 +02:00
To send a cookie in your `Response` just add a `Response.Cookie` to the `cookies` field.
The `cookies` field is a slice of `Response.Cookie` .
```zig
fn index(_: *zrv.Request) zrv.Response {
// Define a cookie with name and value.
// It will live for 24 hours, since `maxAge` represents
// lifetime in seconds.
// See all field of the `Response.Cookie` struct below.
const cookie = zrv.Response.Cookie{.name="User", .value="James", .maxAge=60*60*24};
var res = zrv.Response.write("Set Cookie!");
// add cookie to the `cookies` field which is a slice of `Response.Cookie`
res.cookies = &[_]zrv.Response.Cookie{.{cookie}};
}
```
This are the fields of `Response.Cookie` :
```zig
name: []const u8,
value: []const u8,
path: []const u8 = "/",
domain: []const u8 = "",
/// Indicates the number of seconds until the cookie expires.
maxAge: i64 = 0,
secure: bool = true,
httpOnly: bool = true,
sameSite: SameSite = .lax,
```
2023-05-19 14:45:49 +02:00
## Method
2023-04-24 17:07:48 +02:00
Represents the http method of a Request or a Response.
```zig
pub const Method = enum {
GET,
POST,
PUT,
HEAD,
DELETE,
CONNECT,
OPTIONS,
TRACE,
PATCH,
UNKNOWN,
/// Turns the HTTP_method into a u8-Slice.
pub fn stringify(m: Method) []const u8 {...}
};
```
2023-05-19 14:45:49 +02:00
## HTTP-Version
2023-04-24 17:07:48 +02:00
The HTTP-Version of a Request or a Response.
```zig
pub const HTTP_Version = enum {
HTTP1_1,
HTTP2,
/// Parses from `[]u8`
pub fn parse(s: []const u8) HTTP_Version {...}
/// Stringifies `HTTP_Version`
pub fn stringify(version: HTTP_Version) []const u8 {...}
};
```
2023-05-19 14:45:49 +02:00
# Namespaces
2023-04-24 17:07:48 +02:00
2023-05-19 14:45:49 +02:00
## Server
2023-04-24 17:07:48 +02:00
Server is a namespace to configure IP and Port the app will listen to by calling `Server.listen()` , as well as the routing paths (`[]Route`) it shall handle.
You can also choose an allocator that the app will use for dynamic memory allocation.
```zig
pub fn listen(ip: []const u8, port: u16, rt: []const Route, allocator: std.mem.Allocator) !void {...}
2023-04-24 17:08:36 +02:00
```