blob: 6e1b1ee537da90139102c8b90c2a8cacb695781e [file] [log] [blame] [raw]
/*
Copyright: Boaz Segev, 2016-2017
License: MIT
Feel free to copy, use and enjoy according to the license provided.
*/
#ifndef H_FACIL_H
/**
"facil.h" is the main header for the facil.io server platform.
*/
#ifdef __cplusplus
extern "C" {
#endif
#define H_FACIL_H
#define FACIL_VERSION_MAJOR 0
#define FACIL_VERSION_MINOR 4
#define FACIL_VERSION_PATCH 2
#ifndef FACIL_PRINT_STATE
/**
When FACIL_PRINT_STATE is set to 1, facil.io will print out common messages
regarding the server state (start / finish / listen messages).
*/
#define FACIL_PRINT_STATE 1
#endif
/* *****************************************************************************
Required facil libraries
***************************************************************************** */
#include "defer.h"
#include "sock.h"
/* *****************************************************************************
Core object types
***************************************************************************** */
typedef struct FacilIOProtocol protocol_s;
/**************************************************************************/ /**
* The Protocol
The Protocol struct defines the callbacks used for the connection and sets it's
behaviour. The Protocol struct is part of facil.io's core design.
For concurrency reasons, a protocol instance SHOULD be unique to each
connections. Different connections shouldn't share a single protocol object
(callbacks and data can obviously be shared).
All the callbacks recieve a unique connection ID (a localized UUID) that can be
converted to the original file descriptor when in need.
This allows facil.io to prevent old connection handles from sending data
to new connections after a file descriptor is "recycled" by the OS.
*/
struct FacilIOProtocol {
/**
* A string to identify the protocol's service (i.e. "http").
*
* The string should be a global constant, only a pointer comparison will be
* used (not `strcmp`).
*/
const char *service;
/** called when a data is available, but will not run concurrently */
void (*on_data)(intptr_t uuid, protocol_s *protocol);
/** called when the socket is ready to be written to. */
void (*on_ready)(intptr_t uuid, protocol_s *protocol);
/** called when the server is shutting down,
* but before closing the connection. */
void (*on_shutdown)(intptr_t uuid, protocol_s *protocol);
/** called when the connection was closed, but will not run concurrently */
void (*on_close)(protocol_s *protocol);
/** called when a connection's timeout was reached */
void (*ping)(intptr_t uuid, protocol_s *protocol);
/** private metadata used by facil. */
size_t rsv;
};
/**************************************************************************/ /**
* Listening to Incoming Connections
Listenning to incoming connections is pretty straight forward.
After a new connection is accepted, the `on_open` callback is called. `on_open`
should allocate the new connection's protocol and retuen it's address.
The protocol's `on_close` callback is expected to handle the cleanup.
These settings will be used to setup listening sockets.
i.e.
```c
// A callback to be called whenever data is available on the socket
static void echo_on_data(intptr_t uuid,
protocol_s *prt
) {
(void)prt; // we can ignore the unused argument
// echo buffer
char buffer[1024] = {'E', 'c', 'h', 'o', ':', ' '};
ssize_t len;
// Read to the buffer, starting after the "Echo: "
while ((len = sock_read(uuid, buffer + 6, 1018)) > 0) {
// Write back the message
sock_write(uuid, buffer, len + 6);
// Handle goodbye
if ((buffer[6] | 32) == 'b' && (buffer[7] | 32) == 'y' &&
(buffer[8] | 32) == 'e') {
sock_write(uuid, "Goodbye.\n", 9);
sock_close(uuid);
return;
}
}
}
// A callback called whenever a timeout is reach
static void echo_ping(intptr_t uuid, protocol_s *prt) {
(void)prt; // we can ignore the unused argument
sock_write(uuid, "Server: Are you there?\n", 23);
}
// A callback called if the server is shutting down...
// ... while the connection is still open
static void echo_on_shutdown(intptr_t uuid, protocol_s *prt) {
(void)prt; // we can ignore the unused argument
sock_write(uuid, "Echo server shutting down\nGoodbye.\n", 35);
}
// A callback called for new connections
static protocol_s *echo_on_open(intptr_t uuid, void *udata) {
(void)udata; // ignore this
// Protocol objects MUST always be dynamically allocated.
protocol_s *echo_proto = malloc(sizeof(*echo_proto));
*echo_proto = (protocol_s){
.service = "echo",
.on_data = echo_on_data,
.on_shutdown = echo_on_shutdown,
.on_close = (void (*)(protocol_s *))free, // simply free when done
.ping = echo_ping};
sock_write(uuid, "Echo Service: Welcome\n", 22);
facil_set_timeout(uuid, 5);
return echo_proto;
}
int main() {
// Setup a listening socket
if (facil_listen(.port = "8888", .on_open = echo_on_open))
perror("No listening socket available on port 8888"), exit(-1);
// Run the server and hang until a stop signal is received.
facil_run(.threads = 4, .processes = 1);
}
```
*/
struct facil_listen_args {
/**
* Called whenever a new connection is accepted.
* Should return a pointer to the connection's protocol
*/
protocol_s *(*on_open)(intptr_t fduuid, void *udata);
/** The network service / port. Defaults to "3000". */
const char *port;
/** The socket binding address. Defaults to the recommended NULL. */
const char *address;
/** Opaque user data. */
void *udata;
/** Opaque user data for `set_rw_hooks`. */
void *rw_udata;
/** (optional)
* Called before `on_open`, allowing Read/Write hook initialization.
* Should return a pointer to the new RW hooks or NULL (to use default hooks).
*
* This allows a seperation between the transport layer (i.e. TLS) and the
* protocol (i.e. HTTP).
*/
sock_rw_hook_s *(*set_rw_hooks)(intptr_t fduuid, void *rw_udata);
/**
* Called when the server starts, allowing for further initialization, such as
* timed event scheduling.
*
* This will be called seperately for every process. */
void (*on_start)(void *udata);
/**
* Called when the server is done, usable for cleanup.
*
* This will be called seperately for every process. */
void (*on_finish)(void *udata);
};
/** Schedule a network service on a listening socket. */
int facil_listen(struct facil_listen_args args);
/**
* Schedule a network service on a listening socket.
*
* See the `struct facil_listen_args` details for any possible named arguments.
*/
#define facil_listen(...) facil_listen((struct facil_listen_args){__VA_ARGS__})
/* *****************************************************************************
Connecting to remote servers as a client
***************************************************************************** */
/**
Named arguments for the `server_connect` function, that allows non-blocking
connections to be established.
*/
struct facil_connect_args {
/** The address of the server we are connecting to. */
char *address;
/** The port on the server we are connecting to. */
char *port;
/**
* The `on_connect` callback should return a pointer to a protocol object
* that will handle any connection related events.
*/
protocol_s *(*on_connect)(intptr_t uuid, void *udata);
/**
* The `on_fail` is called when a socket fails to connect.
*/
void (*on_fail)(void *udata);
/** Opaque user data. */
void *udata;
/** Opaque user data for `set_rw_hooks`. */
void *rw_udata;
/** (optional)
* Called before `on_connect`, allowing Read/Write hook initialization.
* Should return a pointer to the new RW hooks or NULL (to use default hooks).
*
* This allows a seperation between the transport layer (i.e. TLS) and the
* protocol (i.e. HTTP).
*/
sock_rw_hook_s *(*set_rw_hooks)(intptr_t fduuid, void *udata);
};
/**
Creates a client connection (in addition or instead of the server).
See the `struct facil_listen_args` details for any possible named arguments.
* `.address` should be the address of the server.
* `.port` the server's port.
* `.udata`opaque user data.
* `.on_connect` called once a connection was established.
Should return a pointer to a `protocol_s` object, to handle connection
callbacks.
* `.on_fail` called if a connection failed to establish.
(experimental: untested)
*/
intptr_t facil_connect(struct facil_connect_args);
#define facil_connect(...) \
facil_connect((struct facil_connect_args){__VA_ARGS__})
/* *****************************************************************************
Core API
***************************************************************************** */
struct facil_run_args {
/** The number of threads to run in the thread pool. Has "smart" defaults. */
uint16_t threads;
/** The number of processes to run (including this one). "smart" defaults. */
uint16_t processes;
/** called if the event loop in cycled with no pending events. */
void (*on_idle)(void);
/** called when the server is done, to clean up any leftovers. */
void (*on_finish)(void);
};
/**
* Starts the facil.io event loop. This function will return after facil.io is
* done (after shutdown).
*
* See the `struct facil_run_args` details for any possible named arguments.
*/
void facil_run(struct facil_run_args args);
#define facil_run(...) facil_run((struct facil_run_args){__VA_ARGS__})
/**
* Attaches (or updates) a protocol object to a socket UUID.
*
* The new protocol object can be NULL, which will practically "hijack" the
* socket away from facil.
*
* The old protocol's `on_close` will be scheduled, if they both exist.
*
* Returns -1 on error and 0 on success.
*/
int facil_attach(intptr_t uuid, protocol_s *protocol);
/** Sets a timeout for a specific connection (if active). */
void facil_set_timeout(intptr_t uuid, uint8_t timeout);
/** Gets a timeout for a specific connection. Returns 0 if none. */
uint8_t facil_get_timeout(intptr_t uuid);
/* *****************************************************************************
Helper API
***************************************************************************** */
/**
Returns the last time the server reviewed any pending IO events.
*/
time_t facil_last_tick(void);
/** Counts all the connections of a specific type `service`. */
size_t facil_count(void *service);
/**
* Creates a system timer (at the cost of 1 file descriptor).
*
* The task will repeat `repetitions` times. If `repetitions` is set to 0, task
* will repeat forever.
*
* Returns -1 on error or the new file descriptor on succeess.
*/
int facil_run_every(size_t milliseconds, size_t repetitions,
void (*task)(void *), void *arg, void (*on_finish)(void *));
/**
* This is used to lock the protocol againste concurrency collisions and
* concurent memory deallocation.
*
* However, there are three levels of protection that allow non-coliding tasks
* to protect the protocol object from being deallocated while in use:
*
* * `FIO_PR_LOCK_TASK` - a task lock locks might change data owned by the
* protocol object. This task is used for tasks such as `on_data` and
* (usually) `facil_defer`.
*
* * `FIO_PR_LOCK_WRITE` - a lock that promises only to use static data (data
* that tasks never changes) in order to write to the underlying socket.
* This lock is used for tasks such as `on_ready` and `ping`
*
* * `FIO_PR_LOCK_STATE` - a lock that promises only to retrive static data
* (data that tasks never changes), performing no actions. This usually
* isn't used for client side code (used internally by facil) and is only
* meant for very short locks.
*/
enum facil_protocol_lock_e {
FIO_PR_LOCK_TASK = 0,
FIO_PR_LOCK_WRITE = 1,
FIO_PR_LOCK_STATE = 2,
};
/** Named arguments for the `facil_defer` function. */
struct facil_defer_args_s {
/** The socket (UUID) that will perform the task. This is required.*/
intptr_t uuid;
/** The type of task to be performed. Defaults to `FIO_PR_LOCK_TASK` but could
* also be seto to `FIO_PR_LOCK_WRITE`. */
enum facil_protocol_lock_e task_type;
/** The task (function) to be performed. This is required. */
void (*task)(intptr_t uuid, protocol_s *, void *arg);
/** An opaque user data that will be passed along to the task. */
void *arg;
/** A fallback task, in case the connection was lost. Good for cleanup. */
void (*fallback)(intptr_t uuid, void *arg);
};
/**
* Schedules a protected connection task. The task will run within the
* connection's lock.
*
* If an error ocuurs or the connection is closed before the task can run, the
* `fallback` task wil be called instead, allowing for resource cleanup.
*/
void facil_defer(struct facil_defer_args_s args);
#define facil_defer(...) facil_defer((struct facil_defer_args_s){__VA_ARGS__})
/** Named arguments for the `facil_defer` function. */
struct facil_each_args_s {
/** The socket (UUID) that originates the task or -1 if none (0 is a valid
* UUID). This socket will be EXCLUDED from performing the task.*/
intptr_t origin;
/** The target type of protocol that should perform the task. This is
* required. */
const void *service;
/** The type of task to be performed. Defaults to `FIO_PR_LOCK_TASK` but could
* also be seto to `FIO_PR_LOCK_WRITE`. */
enum facil_protocol_lock_e task_type;
/** The task (function) to be performed. This is required. */
void (*task)(intptr_t uuid, protocol_s *, void *arg);
/** An opaque user data that will be passed along to the task. */
void *arg;
/** An on_complete callback. Good for cleanup. */
void (*on_complete)(intptr_t uuid, void *arg);
};
/**
* Schedules a protected connection task for each `service` connection.
* The tasks will run within each of the connection's locks.
*
* Once all the tasks were performed, the `on_complete` callback will be called.
*
* Returns -1 on error. `on_complete` is always called (even on error).
*/
int facil_each(struct facil_each_args_s args);
#define facil_each(...) facil_each((struct facil_each_args_s){__VA_ARGS__})
/* *****************************************************************************
Lower Level API - for special circumstances, use with care under .
***************************************************************************** */
/**
* This function allows out-of-task access to a connection's `protocol_s` object
* by attempting to lock it.
*
* CAREFUL: mostly, the protocol object will be locked and a pointer will be
* sent to the connection event's callback. However, if you need access to the
* protocol object from outside a running connection task, you might need to
* lock the protocol to prevent it from being closed in the background.
*
* facil.io uses three different locks:
*
* * FIO_PR_LOCK_TASK locks the protocol from normal tasks (i.e. `on_data`,
* `facil_defer`, `facil_every`).
*
* * FIO_PR_LOCK_WRITE locks the protocol for high priority `sock_write`
* oriented tasks (i.e. `ping`, `on_ready`).
*
* * FIO_PR_LOCK_STATE locks the protocol for quick operations that need to copy
* data from the protoccol's data stracture.
*
* IMPORTANT: Remember to call `facil_protocol_unlock` using the same lock type.
*
* Returns NULL on error (lock busy == EWOULDBLOCK, connection invalid == EBADF)
* and a pointer to a protocol object on success.
*
* On error, consider calling `facil_defer` or `defer` instead of busy waiting.
* Busy waiting SHOULD be avoided whenever possible.
*/
protocol_s *facil_protocol_try_lock(intptr_t uuid, enum facil_protocol_lock_e);
/** Don't unlock what you don't own... see `facil_protocol_try_lock` for
* details. */
void facil_protocol_unlock(protocol_s *pr, enum facil_protocol_lock_e);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* H_FACIL_H */