Architecture¶
Libyimmo has a simple, layered, architecture which compartmentalizes functionality into the following three general domains:
the transport (e.g. TCP) — handled by the server
the protocol (e.g. HTTP/WS) — handled by Protocols
the application — handled by user code
The aim is to keep the code for each layer as intelligible as possible by limiting the scope of concerns — allowing the layer below to handle technical intracacies and building on top of it to provide a higher level of abstraction to the layer above.
So, for instance, the protocol doesn’t have to worry about checking EAGAIN
on send()
, fiddling with ioctl
, handling SIGPIPE
, etc. The server
doesn’t have to concern itself with user sessions, transactions, encoding, etc.
The user code doesn’t have to worry about validating input, transfer encoding,
protocol versions, etc.
info
For a diagrammatic for of the overall flow of messages and data see Core Event Flow.
Interaction¶
Each layer implements a callback interface, defined by the layer below, and exposes a set of API functions which the layer above can invoke to pass information down.
Information moves bottom-to-top by way of callbacks.
Information moves top-to-bottom by way of API functions.
No layer should know anything about the technical details of the layer above it, outside of the interface it defines. So, for example, the TCP server should never need to know about or be concerned with protocol details — e.g. setting HTTP headers, checking protocol flags, etc.
Data¶
Most of the entity types in libyimmo have a data
or user_data
field
associated with them. These allow a given layer in the architecture to provide
a place for the layer above it to persist arbitrary data. At a high-level, data
can be associated with a server or with a particular connection.
Generally speaking:
The
_create
or_init
function for one layer accepts adata
parameter.The
data
parameter is passed to the layer above during callback invocations.The layer above stashes its layer-specific persistent data in that pointer. The layer-specific persistent data usually also has a
data
field, populated by that layer’s_create
or_init
function.
etc, etc, etc. You get the gist. Each layer optionally provides the one above it with a place to store data at init time and makes that data available as a callback parameter.
Example: HTTP Data¶
The exact details vary by protocol, but here’s a quick look at how this works with the HTTP protocol, which should help the illustrate the gist of the thing:
Transport-Level:
ymo_proto
provides adata
field for protocol implementations to store data that will persist for the lifetime of the server.ymo_conn
provides auser_data
field that will persist for the lifetime of the connection.When the server invokes protocol callbacks, it passes the proto data field and, where applicable, the connection
user_data
field.
Protocol-Level:
ymo_proto_http_create()
populates theymo_proto
data
field with its own, protocol-specific, data structure (ymo_http_proto_data
) in order to store things like the users’s HTTP header and body callbacks, upgrade handlers, etc.The
ymo_http_proto_data
struct, has its owndata
field, which is populated with the value passed by the user when they invokeymo_proto_http_create()
orymo_http_simple_init()
.
It populates the
ymo_conn
data
field with aymo_http_session
, which is used to track things like HTTP exchange state, client capabilities, etc.the
ymo_http_session
type has auser_data
field which is optionally populated by user code when itsymo_http_session_init_cb_t
is invoked.
When the protocol invokes user callbacks, it passes the HTTP proto data field and, where applicable, the HTTP session
user_data
field.