added some clarification docs

This commit is contained in:
Yessiest 2024-07-08 16:34:04 +04:00
parent 6566c2af5c
commit 3f7bde8d30
1 changed files with 129 additions and 0 deletions

129
STRUCTURE.md Normal file
View File

@ -0,0 +1,129 @@
# Landline library and application structure
## File layout
- `/landline/lib/*.rb` - core of the library
- `/landline/lib/dsl/` - module namespaces for use by executable contexts
- `/landline/lib/path/` - nodes that extend the Path class (traversable
node/setup execution context)
- `/landline/lib/probe/` - nodes that extend the Probe class (executable
nodes/per-request execution context)
- `/landline/lib/template/` - template engine adapters
- `/landline/lib/util/` - utility classes and modules
- `/landline/lib/pattern_matching/` - classes that implement path pattern
matching (i.e. globs, regex and static traversal paths)
## Architecture overview
The following chapter defines information that pertains to the overall
structure and interaction of landline's core abstractions - Nodes, Paths
and Probes.
For context: Node is an element of the path tree, and is another name for a
Probe (which is a leaf of the tree) or a path (which is an internal vertex
of the tree)
```plaintext
Path +-> Path +-> Probe
| |
| +-> Probe
+-> Probe
|
|-> Path -> Probe
```
### Execution contexts
In Sinatra, which this library takes most of its influence from, every
request creates its own instance of the Sinatra application. This seemed
like an excessive measure to solve a trivial problem of race conditions on a
shared execution context, so to solve that, Landline introduces per-request
execution contexts, which have a life time that spans the duration of
request processing.
In short, there are two distinct execution contexts:
- Per-request execution context, which is used by blocks that process the
request and which is bound to that given request
- Setup execution context, in which the Landline node tree is constructed
For every Path-like node, the supplied block is executed in the setup
context bound to that path.
For every Probe-like node, the supplied block is executed in the per-request
context of the request that is passed to the probe.
These execution contexts also affect the receivers of the instance variable
write and read operations. Additionally, the two types have different
sets of available methods, and every DSL method has a description attached
which describes in which context that method can be used (this is due to the
limitations of the YARD parser and due to the need of showing method
descriptions in the IDE)
### Request traversal
Requests in Landline traverse through a series of paths that determine
which route the request would take. All nodes are organized in a tree
structure, where the root of the tree is a Server object that handles
things likes localized jumps, request/response conversion and response
finalization, and root-level error/`die()` handling.
Every path-like node can be supplied with callbacks that perform
the following functions:
- filters - callbacks that filter incoming request objects
- preprocessors - callbacks that don't usually affect the routing decision
but can modify requests before they come through
- postprocessors - callbacks that execute either when a request exits a
given path after not finding any suitable probe-like node or when a
response to a request is being finalized
- pipeline - single callback that is injected in the execution stack that
can be used to catch throws and exceptions
(see `/examples/logging.ru`, `/examples/norxondor_gorgonax/config.ru`)
The execution contexts for every callback (except pipeline) are shared with
those of the probe-like execution contexts, meaning that an instance
variable written to in a filter or a preprocessor can be accessed from the
probe node or a postprocessor callback.
Once all preprocessors and filters are finished successfully, the request
is passed to all subsequent graph elements (either other Paths or Probes)
to check whether a given Node accepts that element.
If the subtree of the path contains at least one probe that accepts the
request, that probe finishes processing the request and either exits
the tree immediately by throwing the `:finish` signal with a response
or by returning a valid (not nil) response from the `process` method,
in which case the response ascends from the probe back to the root.
If none of the subsequent elements of a path accepted the request, the
path executes `die(404)` if `bounce` is not enabled, and if `bounce` is
enabled, the request exits from the path and proceeds to sibling
(adjacent) paths. (see `/examples/dirbounce.ru`)
A probe can finish the request by either throwing a response, by executing a
jump to another path, by explicitly dying with a status code or by throwing
an exception (which effectively causes `die(500)`)
(see `/examples/errorpages.ru`, `/examples/jumps.ru`)
If a jump is executed, the path of the request is rewritten locally, and the
request is fed through the server again for processing. This is effectively
a localized redirect which retains accumulated request processing
information (i.e. instance variables)
(see `/examples/jumps.ru`)
The tree can be changed at runtime, but it's generally not advisable to
depend on that fact. If, eventually, some solution will be found to
optimizing trees into linear routing paths, and if that solution is proven
to improve performance, this statement might become false.
```plaintext
+--------------------------------------+
v |
run (obj) -> App#call -> Server#call +-> Path#go -> ... -> Probe#go +
| | (if app acts as a wrapper for another
<-----------------------+ | Rack server and if response is 404
(returns back to Rack server | with x-cascade header set)
if no jumps occured) +-> passthrough#call
```