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.
| 🤸♀️ wonder-slim | Simplify class structure for ERExtensions' dynamic element patches | Jun 27 |
| 💋 parslips | Move Inline Binding Shortcuts preferences to the Zombies category | Jun 27 |
| 💋 parslips | Reload tag aliases when a parsley-tag-aliases.properties changes | Jun 27 |
| 🌿 parsley | Add a note on parsley directly registering wonder elements | Jun 27 |
| 🌿 parsley | Removed the "not in 5.4" tag comments. No one cares. | Jun 27 |
| 💋 parslips | Bump wonder-slim project template to 8.0.3 | Jun 26 |
| 🤸♀️ wonder-slim | Update parsley v1.6.0 -> v1.6.1-SNAPSHOT | Jun 26 |
| ⚙️️ wonder-slim-deployment | Update wonder-slim v8.0.2 -> v8.0.3 | Jun 26 |
| 👨⚕️ examiner | Update wonder-slim v8.0.2 -> v8.0.3, parsley v1.5.0 -> v1.6.0 | Jun 26 |
| 💋 parslips | Add changelog entry for the shared tag registry | Jun 26 |
| 🤸♀️ wonder-slim | 8.0.3 | Jun 26 |
| 🌿 parsley | 1.6.0 | Jun 26 |
| 🌿 parsley | 1.5.0 | Jun 18 |
| 🤸♀️ wonder-slim | 8.0.2 | Jun 18 |
| 🦡 vermilingua | 1.1.5 | Jun 15 |
| 🤸♀️ wonder-slim | 8.0.1 | Jun 1 |
| 🌿 parsley | 1.4.2 | Jun 1 |
| 🦡 vermilingua | 1.1.4 | Apr 24 |
| 🚀 ng-objects | 0.1.1 | Apr 22 |
| 🦡 vermilingua | 1.1.3 | Apr 15 |
| 🌿 parsley | Switch components bypass tag-alias resolution (runtime dynamicElementWithName) | Jun 27 |
| 🧩 apiext-format | Root <wo class> uses the simple class name (Obj-C relic) — reconcile with FQN class references | Jun 25 |
| 🧩 apiext-format | Value sets — allowed values for a binding (literal enums and dynamic sources) | Jun 25 |
| 🧩 apiext-format | Declare the primary HTML element an element renders as | Jun 25 |
| 🧩 apiext-format | Review, document, and possibly redesign the binding-validation syntax | Jun 25 |
| 🧩 apiext-format | Element/component use scope (access control / visibility) | Jun 25 |
| 🤸♀️ wonder-slim | bfcache: stale page on back/forward after an ajax update | Jun 22 |
| 🦡 vermilingua | Write a file to the WOA containing some info about the build | Jun 22 |
| 🤖 modulo | Audit and complete the set of adaptor headers modulo injects | Jun 19 |
| 🦡 vermilingua | Non-clean rebuilds leave deleted/renamed resources in the .woa | Jun 15 |