[dreamwidth/dreamwidth] 221201: Plack/Starman Support (#3512)
Branch: refs/heads/main Home: https://github.com/dreamwidth/dreamwidth Commit: 22120106eb6013b4857e3d8f233d0b34dfaad9bd https://github.com/dreamwidth/dreamwidth/commit/22120106eb6013b4857e3d8f233d0b34dfaad9bd Author: Mark Smith mark@dreamwidth.org Date: 2026-02-01 (Sun, 01 Feb 2026)
Changed paths: M .devcontainer/config/etc/apache2/ports.conf M .devcontainer/devcontainer.json A .devcontainer/plack/Dockerfile A .devcontainer/plack/devcontainer.json A .devcontainer/setup.sh M .devcontainer/start.sh M .gitignore R Build.PL M CLAUDE.md A app.psgi M bin/checkconfig.pl A bin/starman M cgi-bin/Apache/BML.pm M cgi-bin/Apache/LiveJournal.pm M cgi-bin/DBI/Role.pm A cgi-bin/DW/BML.pm M cgi-bin/DW/Controller/Create.pm A cgi-bin/DW/Controller/Journal.pm M cgi-bin/DW/Controller/Login.pm A cgi-bin/DW/Controller/Userpic.pm M cgi-bin/DW/Request.pm M cgi-bin/DW/Request/Apache2.pm M cgi-bin/DW/Request/Base.pm A cgi-bin/DW/Request/Plack.pm M cgi-bin/DW/Request/Standard.pm M cgi-bin/DW/Widget/AccountStatistics.pm M cgi-bin/LJ/Global/Defaults.pm M cgi-bin/LJ/S2.pm M cgi-bin/LJ/User/Account.pm M cgi-bin/LJ/Web.pm A cgi-bin/Plack/Middleware/DW/Auth.pm A cgi-bin/Plack/Middleware/DW/ConcatRes.pm A cgi-bin/Plack/Middleware/DW/Dev.pm A cgi-bin/Plack/Middleware/DW/Redirects.pm A cgi-bin/Plack/Middleware/DW/RequestWrapper.pm A cgi-bin/Plack/Middleware/DW/Sysban.pm A cgi-bin/Plack/Middleware/DW/UniqCookie.pm A cgi-bin/Plack/Middleware/DW/XForwardedFor.pm M cgi-bin/ljlib.pl M cgi-bin/modperl_subs.pl A doc/PLACK.md M doc/dependencies-cpanm M doc/dependencies-system M src/s2/S2.pm A t/plack-app.t A t/plack-auth.t A t/plack-bml.t A t/plack-controller.t A t/plack-integration.t A t/plack-middleware.t A t/plack-static.t A t/plack-sysban.t M views/login.tt
Log Message:
Plack/Starman Support (#3512)
- Add missing use LJ::Memories in AccountStatistics widget
The widget calls LJ::Memories::count() but relied on modperl_subs.pl to load the module at startup rather than declaring its own dependency.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Use path-based journal URLs in dev container
Override $SUBDOMAIN_RULES for dev containers to use path-based format (/~username) instead of subdomain-based format (username.domain). Fix journal_base() to construct URLs from the request host when the rule has an empty domain.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
Plack testing with devcontainer
Fix Plack request lifecycle and integration tests
DW::Request->get now always creates a fresh request when plack_env is provided, fixing the issue where LJ::start_request's internal reset/get cycle would leave DW::Request in a stale state. Also fixes DW::Request::Plack->status to work as a getter, adds a default 404 for unmatched routes in app.psgi, and rewrites the integration test to properly monkey-patch after module loading. Includes tidyall formatting.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Plack: render Login controller through full stack, remove dead Procnotify call
Add missing methods to DW::Request::Plack (pnote, note, content, content_type getter/setter, no_cache, get_remote_ip) so controllers can render through the Plack stack. Route all requests through DW::Routing in app.psgi instead of only /api/ paths, and use the routing return value to set HTTP 200 status for handled requests.
Remove the dead LJ::Procnotify::check() call from RequestWrapper middleware — the module was deleted but the call was left behind.
New test t/plack-controller.t validates GET /login renders through the real routing + controller + template pipeline via Plack::Test.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Add UniqCookie middleware and fix is_web_context for Plack
Add Plack::Middleware::DW::UniqCookie that calls LJ::UniqCookie->ensure_cookie_value to generate and set the unique tracking cookie on every request, matching Apache behavior.
This required two supporting changes:
LJ::is_web_context() now returns true when a DW::Request is active, not just under mod_perl. Many modules gate web-only behavior on this check, so Plack requests were silently skipping cookie, auth, and other web-context logic.
DW::Request::Plack now implements err_header_out, header_out_add, and err_header_out_add so that add_cookie (which appends Set-Cookie headers) works correctly.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Add static content serving middleware for Plack
Add DW::ConcatRes middleware to handle concatenated CSS/JS requests (??file1.css,file2.css URLs), ported from Apache::LiveJournal. Wire in Plack::Middleware::Static for plain static files from $LJ::HTDOCS, matching Apache's DocumentRoot behavior.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Implement redirect.dat support in Plack redirects middleware
Load redirect.dat and redirect-local.dat at startup and return 301 for matching paths, preserving query strings. Matches the existing Apache behavior in Apache::LiveJournal::trans().
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Add Sysban middleware for Plack and clean up app.psgi TODOs
Port sysban blocking from Apache::LiveJournal::trans() to a Plack middleware: IP bans, uniq cookie bans, tempbans, and noanon_ip bans for anonymous users. Also remove stale TODOs from app.psgi (srand preforking is a non-issue in modern Perl, BML language setup is irrelevant to Plack but noted in RequestWrapper for future BML port).
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Implement Auth middleware for Plack
Resolve authenticated user from session cookies via LJ::Session->session_from_cookies() and set the remote user for the request. Bypasses LJ::get_remote() directly since it depends on BML::get_request(), but subsequent get_remote() calls hit the cache. Supports dev server ?as=username impersonation.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Route embed module domain requests to embedcontent handler
When the request host matches $LJ::EMBED_MODULE_DOMAIN, force routing to /journal/embedcontent regardless of path, matching the Apache trans() behavior. Removes the last placeholder comment from app.psgi.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Implement BML rendering under Plack via DW::BML
Add DW::BML module that renders .bml pages using DW::Request instead of Apache APIs, allowing BML pages to work under Plack. The module reuses the core BML engine (bml_decode, bml_block, config loading, scheme/look system) from Apache::BML and only replaces the handler and request adapter layers.
Includes a RequestAdapter that makes DW::Request look like an Apache request object for BML's public API functions (BML::get_request(), etc.), BML fallback routing in app.psgi after DW::Routing, and tests.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Add homepage integration tests verifying tropo-red site scheme
Tests that GET /index returns 200, renders with the tropo-red body class, includes tropo-red.css, and has text/html content type. Restructured integration tests so real-routing tests run before the monkey-patch.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Add Starman dependency and dev server startup script
Add Starman to dependencies-cpanm and create bin/dev-plack-server for running Dreamwidth under Plack/Starman with a single worker in dev mode. Listens on 0.0.0.0:8080 by default, configurable via --port and --host.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Fix static file serving to search all htdocs directories
Static middleware now iterates over LJ::get_all_directories('htdocs') so files from extensions (e.g. dw-nonfree) are served alongside base htdocs. Uses pass_through so each layer falls through to the next when a file isn't found in that directory.
Fixes 404s for nonfree static assets like /img/tropo-red/dw_logo.png.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Provide BML package functions for Plack and initialize language getter
DW::BML now defines all BML::* package functions (ml, set_language, ehtml, get_request, cookies, etc.) and Apache::BML::* stubs at load time so they're available in any Plack web context. Previously these were only defined by Apache::BML which can't be loaded without Apache2::* modules.
RequestWrapper now calls BML::set_language with LJ::Lang::get_text on every request so translation strings resolve correctly for all pages.
Also fixes infinite recursion in the request adapter's overloaded hash classes by using array-based objects to avoid re-triggering %{} overload.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
Fix login error display, URL doubling, and BML scope bridging
Display login errors in login.tt using Foundation alert-box pattern
- Set ml_scope early in Login controller so LJ::Lang::ml resolves relative string codes before template rendering
- Bridge BML and TT scope mechanisms: BML::ml falls back to $r->note('ml_scope') when $BML::ML_SCOPE is empty
- Fix DW::Request::Plack::uri to return path only (matching Apache behavior) instead of full URL, which caused doubled URLs in every LJ::create_url call
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Fix cookie domain issues for dev container under Plack
The Redirects middleware used local $LJ::DOMAIN_WEB = "www.$LJ::DOMAIN"
which leaked "www." into downstream cookie-setting code when $DOMAIN is
empty (dev container). Changed to a lexical variable scoped to the
redirect checks only.
Also set $COOKIE_DOMAIN to empty string in dev container defaults to prevent it defaulting to "." (just a dot), which browsers reject.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Update CLAUDE.md with Plack dev server and workflow notes
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Preserve cookies on Plack redirects and handle controller redirect responses
DW::Request::Plack::redirect was creating a new Plack::Response, discarding any cookies/headers already set on the request (e.g. login session cookies). Now builds the redirect from the existing response object. Also handle controller redirect responses (arrayrefs) in app.psgi dispatch.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
Load real Apache::BML engine under Plack, fix BML strings and dev server usability
Load Apache::BML by stubbing Apache2/APR modules so the BML engine functions (bml_decode, load_conffile, parsein, etc.) are available under Plack without duplicating them. Fake Apache2::Const provides the constants Apache::BML needs.
Remove no-op stubs for BML::register_hook, set_config, register_language, etc. that were preventing BML config files (like BMLInit.pm) from registering the ml_getter hook. This fixes missing translation strings on BML pages.
Keep .bml in the language scope so LJ::Lang::get_text can match scope to .bml.text files, fixing [missing string] on BML pages.
Fix undef warnings in BML::decide_language.
Fix check_referer for dev servers with empty $LJ::DOMAIN by comparing referer host against the request Host header.
Auto-validate email on account creation in dev servers so new accounts can post immediately without email confirmation.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Fix grants
This supports both localhost (domain socket) and 127.0.0.1 (TCP socket) connections, which get triggered depending on how you access the database.
- Fix missing standard CSS/JS resources and LJ::Memories under Plack
Under Plack, start_request called DW::Request->reset before the request was created, so the resource registration block (lj_base.css, esn.css, jquery UI, etc.) was skipped. Extract registration into LJ::register_standard_resources() and call it from the Plack middleware after the DW::Request is created.
Also add explicit use LJ::Memories in AccountStatistics widget, which was previously only loaded via modperl_subs.pl.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Add DW::Controller::Userpic to serve userpics via DW::Routing
Replaces the Apache::LiveJournal userpic_trans/userpic_content handlers with a proper controller that works under both Plack and mod_perl.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Add DW::Controller::Journal for journal rendering under Plack
Extract journal viewing pipeline from Apache::LiveJournal into a shared DW::Controller::Journal module. Journals are now accessible under Plack via path-based URLs (/~username/ and /users/username/).
- Create DW::Controller::Journal with determine_view() and render() methods
- Add journal path-based routing in app.psgi before BML fallback
- Override $SUBDOMAIN_RULES for dev container to use path-based URLs
- Fix LJ::User::journal_base to construct /~user URLs in dev container
- Add OK/NOT_FOUND/DECLINED/status_line stubs to DW::BML::RequestAdapter
- Load Apache2::Response in modperl_subs.pl for headers_out in trans phase
- Fix S2 check_depth undef warnings by guarding before hash interpolation
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Load DW::Request::Apache2 at startup to bootstrap mod_perl XS methods
DW::Request no longer loads DW::Request::Apache2 at compile time (to support Plack), so Apache2::RequestRec XS methods like headers_out were never bootstrapped. Load DW::Request::Apache2 explicitly in modperl_subs.pl so all Apache2 XS methods are available during the PerlInitHandler/PerlTransHandler phases.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Fix redirect loop for path-based journal URLs under Apache
When SUBDOMAIN_RULES uses path-based URLs in the dev container, journal_base returns http://host/~user which matches the incoming /~user/ path, causing an infinite redirect. Detect this case and dispatch directly via $determine_view instead of redirecting.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Fix S2 stylesheet loading and warnings under Plack path-based URLs
The HTMLCleaner was stripping S2 stylesheet tags because valid_stylesheet_url didn't recognize /~user/res/N/stylesheet paths. Also adds set_last_modified/meets_conditions to DW::Request::Plack and fixes various uninitialized value warnings in the journal controller.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Update web runner name
Default to 3 workers and call it Starman! Because it is!
- Fix shop links in dev container by setting SHOPROOT to /shop
When SHOPROOT was empty string, links like "$SHOPROOT/account" lost their /shop prefix and resolved to just /account, which doesn't route to the shop controllers.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Fix Auth middleware to always mark auth resolution and add safety docs
Always call set_remote(undef) when no authenticated user is found, so LJ::get_remote() won't re-enter session resolution via Login.pm (which crashes on null owner). Add safety comments to LJ_IS_DEV_SERVER env var and ?as= impersonation block. Fix plack-auth.t mocks to properly isolate test state.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Update Plack docs, fix CLAUDE.md startup command, remove Build.PL
Rewrite doc/PLACK.md to reflect current implementation: full middleware stack, routing pipeline, test suite, and security notes. Update CLAUDE.md to reference doc/PLACK.md and fix bin/starman command. Remove unused Build.PL.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Switch devcontainer to Starman on port 80, Apache available on 8081
Starman is now the primary web server in the devcontainer. Apache config is kept on port 8081 but not started by default (run apache2ctl start manually if needed). Added --log option to bin/starman for access and error logs. Removed spammy debug logging from app.psgi.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Add libgd-dev
Need this to build GD... sometimes? Docker caching memes.
- Split devcontainer into setup.sh (one-time) and start.sh (every restart)
postCreateCommand runs setup.sh for DB init, schema loading, and static compilation. postStartCommand runs start.sh for mysql, memcached, and Starman on every container start. Added --daemonize flag to bin/starman so the process survives the parent shell exiting.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
- Remove macos/brew
This was a brief experiment, but no longer needed now that devcontainer is working!
Extraneous, was part of a spurious misfire
Revert unnecessary changes to Utils.pm, Auth.pm; remove DBIx::Class dep
Revert LJ::Utils namespace refactor and LJ::Auth irand import — both were interim experiments that aren't needed. Remove unused DBIx::Class from dependencies-cpanm.
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
