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
_createor_initfunction for one layer accepts adataparameter.The
dataparameter 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
datafield, populated by that layer’s_createor_initfunction.
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_protoprovides adatafield for protocol implementations to store data that will persist for the lifetime of the server.ymo_connprovides auser_datafield 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_datafield.
Protocol-Level:
ymo_proto_http_create()populates theymo_protodatafield 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_datastruct, has its owndatafield, which is populated with the value passed by the user when they invokeymo_proto_http_create()orymo_http_simple_init().
It populates the
ymo_conndatafield with aymo_http_session, which is used to track things like HTTP exchange state, client capabilities, etc.the
ymo_http_sessiontype has auser_datafield which is optionally populated by user code when itsymo_http_session_init_cb_tis invoked.
When the protocol invokes user callbacks, it passes the HTTP proto data field and, where applicable, the HTTP session
user_datafield.