.__ .___ _______ ____ ____________ |__| ____ ______ __| _/ ____ ___ __ \_ __ \ / _ \ / ___/\____ \ | |_/ __ \ / ___/ / __ | _/ __ \ \ \/ / | | \/( <_> ) \___ \ | |_> >| |\ ___/ \___ \ / /_/ | \ ___/ \ / |__| \____/ /____ >| __/ |__| \___ >/____ > /\ \____ | \___ > \_/ \/ |__| \/ \/ \/ \/ \/
root@romancentral~ $ ./loadBlog.sh
srv-acheron: The backend for this website | February 2nd, 2025
Hi there and welcome to my very first blog post on this site (which I have admittedly postponed for a while, excusez-moi…) Anyway, lets get started. This website is powered by a backend written in C++, based on drogon framework and using folly library and others. Now, what does this backend accomplish ?
📌 supplies matrix presence and last online status on every page via websocket
📌 supplies current or last playback on my self-hosted navidrome server via websocket and commits those to pgsql
📌 logs users accessing all those endpoints to pgsql
📌 supplies an endpoint to access the name, album, artist, playcount and date of last playback for the 50 most-played songs
📌 supplies endpoints not exposed publicly for synapse and navidrome
Now, the entire design of srv-acheron plays into the event-driven nature of drogon and the REST-API framework it provides. Furthermore, this app makes extensive use of facebooks folly library, which provides event queues, shared mutexes and threadsafe hashmaps. The basic layout is simple:
📌 Global initialization
- Drogon.cpp initializes necessary variables, especially the mutexes, event queues and atomic hashmaps made globally available via CacheAccess.h, contains main() which sets environment variables, sleeps for 60 seconds and then launches the framework.
📌 Controller-based layout
- All controllers, be it websocket or REST API endpoint, are defined by separate .h and .cpp files, with the header files defining route mapping and integrating the controller into the larger framework, while the .cpp files merely define the functions outlined in the header files.
📌 Plugin-based adaptable configuration
- The plugin defined by secretVariablesInit reads the plugin section from config.json as to provide config parameters such as api keys or usernames.
📌 Event-driven architecture
- each websocket controller has an attached event collector thread not tied into the framework, which uses efficient blocking through a combination of short-adaptive spinning and then blocking with futexes via follys MPMCQueue. Upon a new event being passed - which is done via shared pointer to a JSON::Value object as to avoid unnecessary copies -, the thread performs a shared-mutex-protected write operation on the global variable containing a reference to the last event passed, after releasing the old pointer via reset (which at this point is the last existing reference to the previous event, thus freeing the memory allocated to the previous event json). After that, the thread obtains a const reference to the current Json::Value object and passes it to the PubSub service of the websocket controller, which prompts all framework worker threads to asynchronously push the JSON update to their respective websocket clients. This is identical for both matrix status and current playback.
📌 Coroutine based tasks
- While websockets do not yet have coroutine-based handler functions, the regular REST-API-endpoints absolutely do - the bestOfSongs endpoint makes use of this, obtaining a db client and executing a coroutine-based SQL query while co_awaiting the results; after that, the results are parsed into JSON. Similarly, the internal endpoints also use coroutine-based handler functions.
📌 low-overhead, move-semantics-aware design
- This API makes use of move semantics and smart pointers to minimize copy operations whereever possible.
📌 no polling, minimal use of external data
-
Events are passed to srv-acheron by means of http request. In the “altered_code” subdirectory of the repository, you can find three files, the py file being from synapse and the go files hailing from the navidrome repository. Each has been pulled from the respective software version current as of today and altered in such a manner as to pass the desired type of events to the srv-acheron app. Should you wish to replicate this, please pay attention to routing, docker networking and iptables configuration.
-
While synapse will pass all presence events to the API (which will in turn decide whether the update is relevant based on the presence of the designated matrix username as key), navidrome will only pass a song being played in the case of a lastFM scrobble. Therefore, to use the jukebox update capability, the relevant user needs to activate last.fm scrobbling in navidrome. All scrobbles from all last.fm scrobbling users will be passed to the API, which, again, will decide based on the configuration whether the event is placed in the queue.
-
external sources are used sparingly on this site in general, with only some .css or font files being included. srv-agora itself only pulls data from one external source: lastFM, which is why a lastFM API Key is required should you wish to use this without altering code. Data queried from lastFM is cached in the form of a struct containing the album URL and album cover URL, based on a hash representation of unique album-artist combinations.
Frequent updates to the code will still be made at this point, so keep tuned, and don’t forget to take a look at my other current projects. Feel free to contact me about any ideas or questions you may have!