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

The core handles connection management and TCP I/O and provides the protocol with a stream of raw data. The protocol parses raw data into protocol-level entities, handling things like framing, encoding, etc, and passes those to the application.

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

ymo_proto_t and ymo_conn_t have data fields populated by specific protocol implementations. Usually, the protocol itself occupies the ymo_proto_t data field and the ymo_conn_t data field is leveraged by the protocl to store protocol-specific session information.

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 a data 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 a data field for protocol implementations to store data that will persist for the lifetime of the server.

  • ymo_conn provides a user_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 the ymo_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.

  • It populates the ymo_conn data field with a ymo_http_session, which is used to track things like HTTP exchange state, client capabilities, etc.

  • When the protocol invokes user callbacks, it passes the HTTP proto data field and, where applicable, the HTTP session user_data field.