Core building blocks¶
There are several core building blocks used in many places of the server. These are described here.
Handlers¶
A very important building block that gets reused in many places is the AsyncHandler
.
The idea is that a handler has 2 important functions.
canHandle
determines if this class is capable of correctly handling the request,
and throws an error if it can not.
For example, a class that converts JSON-LD to turtle can handle all requests containing JSON-LD data,
but does not know what to do with a request that contains a JPEG.
The second function is handle
where the class executes on the input data and returns the result.
If an error gets thrown here it means there is an issue with the input.
For example, if the input data claims to be JSON-LD but is actually not.
The power of using this interface really shines when using certain utility classes.
The one we use the most is the WaterfallHandler
,
which takes as input a list of handlers of the same type.
The input and output of a WaterfallHandler
is the same as those of its inputs,
meaning it can be used in the same places.
When doing a canHandle
call, it will iterate over all its input handlers
to find the first one where the canHandle
call succeeds,
and when calling handle
it will return the result of that specific handler.
This allows us to chain together many handlers that each have their specific niche,
such as handler that each support a specific HTTP method (GET/PUT/POST/etc.),
or handlers that only take requests targeting a specific subset of URLs.
To the parent class it will look like it has a handler that supports all methods,
while in practice it will be a WaterfallHandler
containing all these separate handlers.
Some other utility classes are the ParallelHandler
that runs all handlers simultaneously,
and the SequenceHandler
that runs all of them one after the other.
Since multiple handlers are executed here, these only work for handlers that have no output.
Streams¶
Almost all data is handled in a streaming fashion.
This allows us to work with very large resources without having to fully load them in memory,
a client could be reading data that is being returned by the server while the server is still reading the file.
Internally this means we are mostly handling data as Readable
objects.
We actually use Guarded<Readable>
which is an internal format we created to help us with error handling.
Such streams can be created using utility functions such as guardStream
and guardedStreamFrom
.
Similarly, we have a pipeSafely
to pipe streams in such a way that also helps with errors.