WO has an interesting way of handling static resources. Traditionally, resources like images, CSS-files etc. are put into a project's WebServerResources folder, and on application deployment that folder gets copied to the webserver and served from there, bypassing the application entirely.
This "split install" made sense a while back since it freed the application from serving static resources, saving CPU cycles and bandwidth, more valuable resources at the time. Today however, serving static stuff is a comparatively lightweight task making split installs something of an unnecessary hassle.
So, for a while I've been skipping the split install and serving my webserver resources from my applications rather than the web server.
Serving static resources from WO is fast. Fast enough that for my purposes, I don't even care exactly how fast, but for reassurance I did some quick and dirty benchmarking. The test is done on a ~230Kb resource on my own private website, hosted on a Hetzner CAX31 with -Xmx512M (not chosen specifically, just happens to be the heap size I've got set, should be fine with just about any value). Did the benchmarking locally using ab, speaking directly to a single instance of the application in production mode (meaning caching is enabled).
# Single request thread, 10.000 requests
# Completes in 6 seconds, ~0.6ms per request
# Transfer rate 375MB/sec
ab -n 10000 http://localhost:2001/Apps/WebObjects/Hugi.woa/res/app/ZillaSlab-Light.ttf
# 32 request threads, 10.000 requests
# Completes in 1.3 seconds, avg. ~4ms per request/~0.13ms across all concurrent requests
# Transfer rate 1703MB/sec
ab -c 32 -n 10000 http://localhost:2001/Apps/WebObjects/Hugi.woa/res/app/ZillaSlab-Light.ttf
This purely measures WO's request handling performance (no SSL, no network latency no mod_WebObjects etc). And yes, I'm fetching the same resource repeatedly so probably getting some performance benefits from that. But this is fast and would still be fast at even half the speed. And note I'm not using ab with -k (HTTP keep-alive) meaning each request initiates a new connection. Using keep-alive roughly doubles the performance in both cases.
I've been doing something like this through a private framework for a while, but I recently started cleaning it up, making it more generic and added it to wonder-slim. The implementation can be found in ERXAppBasedResourceManager and ERXAppBasedResourceRequestHandler. Same code/method should work in any WO/Wonder app, it's simple and just means static resources are always served from your app using URLs that follow a simple format:
## URL format
.../App.woa/res/[frameworkName]/[resourceName]
## Example
.../App.woa/res/app/main.css
Even if already used in production, wonder-slim's implementation still needs some work:
languages parameter to resource URLs to handle those.version query parameter to resource URLs, forcing clients to update potentially cached resources on the client side. This is useful so I'd like to add something like that.I've deleted most of the code in wonder-slim for supporting traditional split installs, most or all of it for generating URLs during development. It's a lot of logic to maintain and slim is all about reducing complexity and development effort by having a "single, proper, well understood and maintained way" to do stuff. And I think serving resources through the application is most definitely the single proper way.
Static resources are just as much a part of the application as anything dynamic.
| 🌶 cayenne | CAY-2905 Upgrade Gradle to 8.14 | Nov 5 |
| 🤸♀️ wonder-slim | Add some common image mimetypes | Nov 4 |
| ⚙️️ wonder-slim-deployment | Remove unused import | Nov 4 |
| 🤸♀️ wonder-slim | Added ERXErrorPage | Nov 3 |
| 🤸♀️ wonder-slim | Comments cleanup in app-based WebServerResource management | Nov 3 |
| 🤸♀️ wonder-slim | Notification parameter unused, replace with _ | Nov 3 |
| 🤸♀️ wonder-slim | Initialize ERXShutdownHook from within | Nov 3 |
| 🤸♀️ wonder-slim | Made XXLifecycleListenerHack sound a little more innocent | Nov 3 |
| 🤸♀️ wonder-slim | Move ERXApp._cachedApplicationName to top with other fields | Nov 2 |
| 🤸♀️ wonder-slim | Start cleanup in ERXApp.name() | Nov 2 |