282 lines
7.7 KiB
Markdown
282 lines
7.7 KiB
Markdown
# zerve
|
|
|
|
A simple framework for writing web services in zig.
|
|
|
|
* [Create a simple Web App](#create-a-simple-web-app)
|
|
* [Types](#types)
|
|
* [Route](#route)
|
|
* [Handler Functions](#handler-functions)
|
|
* [Request](#request)
|
|
* [Get Query Params](#get-query-params)
|
|
* [Get Header Value by Key](#get-value-of-request-header-by-key)
|
|
* [Response](#response)
|
|
* [Header](#header)
|
|
* [Cookies](#cookies)
|
|
* [Read Cookie from Request](#read-cookie-from-request)
|
|
* [Add Cookie to Response](#add-cookie-to-response)
|
|
* [Method](#method)
|
|
* [HTTP-Version](#http-version)
|
|
* [Namespaces](#namespaces)
|
|
* [Server](#server)
|
|
|
|
# Create a simple web app
|
|
|
|
```zig
|
|
const zrv = @import("zerve"); // Or set the path to zerve.zig e.g. @import("zerve-main/src/zerve.zig");
|
|
const Request = zrv.Request;
|
|
const Response = zrv.Response;
|
|
const Server = zrv.Server;
|
|
const Route = zrv.Route;
|
|
const allocator = std.heap.page_allocator; // Choose any allocator you want!
|
|
|
|
fn index(req: *Request) Response {
|
|
_=req;
|
|
return Response.write("hello!");
|
|
}
|
|
|
|
fn about(req: *Request) Response {
|
|
_=req;
|
|
return Response.write("about site");
|
|
}
|
|
|
|
fn writeJson(req: *Request) Response {
|
|
_=req;
|
|
return Response.json("[1, 2, 3, 4]");
|
|
}
|
|
|
|
pub fn main() !void {
|
|
const rt = [_]Route{.{"/", index}, .{"/about", about}, .{"/json", writeJson}};
|
|
|
|
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)
|
|
}
|
|
```
|
|
|
|
# Types
|
|
|
|
## Route
|
|
|
|
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};
|
|
```
|
|
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}};
|
|
```
|
|
|
|
## Handler Functions
|
|
|
|
Every Request is handled by a handler function. It has to be of this type: `fn(req: *Request) Response`
|
|
|
|
Example:
|
|
```zig
|
|
fn hello(req: *Request) Response {
|
|
_ = req;
|
|
return Response.write("hello"); // `Server` will return a Reponse with body "hello". You will see "hello" on your browser.
|
|
}
|
|
```
|
|
|
|
## Request
|
|
|
|
This represents the Request sent by the client.
|
|
```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,
|
|
/// The Request URI
|
|
uri: []const u8,
|
|
/// Represents the request body sent by the client
|
|
body: []const u8,
|
|
};
|
|
```
|
|
|
|
### 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: Request) 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.?);
|
|
|
|
}
|
|
```
|
|
|
|
### Get value of Request header by key
|
|
|
|
You can get the header value of any sent header by the client with the `header`method of `Request`.
|
|
|
|
Example:
|
|
|
|
```zig
|
|
fn index(req: *Request) Response {
|
|
|
|
// Get value of the 'Content-Type' header
|
|
|
|
const h = req.header("Content-Type"); // This will return an optional
|
|
|
|
if (h == null) return Response.write("Header not found!") else return Response.write(h.?);
|
|
|
|
}
|
|
```
|
|
|
|
## Response
|
|
|
|
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.
|
|
pub fn write(s: []const u8) Response
|
|
|
|
/// Send a response with json content.
|
|
pub fn json(j: []const u8) Response
|
|
|
|
/// Send a response with status not found.
|
|
pub fn notfound(s: []const u8) Response
|
|
|
|
/// Send a response with status forbidden.
|
|
pub fn forbidden(s: []u8) Response
|
|
};
|
|
```
|
|
|
|
## Header
|
|
|
|
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,
|
|
};
|
|
```
|
|
|
|
## Cookies
|
|
|
|
### Read Cookie from Request
|
|
|
|
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: *Request) Response {
|
|
|
|
// 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 Response.write("cookie-test");
|
|
}
|
|
```
|
|
|
|
### Add Cookie to Response
|
|
|
|
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(_: *Request) 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 = Response.Cookie{.name="User", .value="James", .maxAge=60*60*24};
|
|
|
|
var res = Response.write("Set Cookie!");
|
|
// add cookie to the `cookies` field which is a slice of `Response.Cookie`
|
|
res.cookies = &[_]Response.Cookie{.{cookie}};
|
|
|
|
return res;
|
|
}
|
|
```
|
|
|
|
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,
|
|
```
|
|
|
|
## Method
|
|
|
|
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 {...}
|
|
};
|
|
```
|
|
|
|
## HTTP-Version
|
|
|
|
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 {...}
|
|
|
|
};
|
|
```
|
|
|
|
# Namespaces
|
|
|
|
## Server
|
|
|
|
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 {...}
|
|
```
|