DazScript Server is a DAZ Studio pane plugin (.dll / .dylib) that embeds
an HTTP server inside the DAZ Studio process. External clients send DazScript
code over HTTP; the plugin executes it on DAZ Studio's main Qt thread and
returns a JSON response.
graph TD
Client["External Client<br/>(Python / JS / PS1)"]
subgraph Plugin["DazScript Server Plugin"]
SLT["ServerListenThread<br/>(httplib, std::thread)"]
Pane["DzScriptServerPane<br/>(Qt main thread)"]
subgraph Services["Services"]
Auth["AuthenticationService"]
Rate["RateLimiterService"]
WL["IPWhitelistService"]
Met["MetricsCollector"]
end
subgraph Exec["Execution"]
RV["RequestValidator"]
RP["RequestProcessor"]
ARM["AsyncRequestManager"]
end
subgraph Infra["Infrastructure"]
SR["SecureRandom"]
JB["JsonBuilder"]
SS["ServerSettings / ServerConfig"]
end
end
DAZ["DAZ Studio<br/>(DzScript engine, scene graph)"]
Client -->|HTTP POST /execute| SLT
SLT -->|sync + DS4 async submit:<br/>BlockingQueuedConnection| Pane
SLT -->|DS6 async: validate + enqueue| ARM
Pane --> Auth
Pane --> Rate
Pane --> WL
Pane --> Met
Pane --> RV
RV --> RP
RP -->|Main thread| DAZ
RP --> ARM
ARM -->|QueuedConnection| Pane
Auth --> SR
JB -.-> Pane
SS -.-> Services
SS -.-> Exec
sequenceDiagram
participant C as Client
participant H as HTTP thread (std::thread)
participant M as Main Qt thread
participant D as DzScript engine
C->>H: POST /execute (JSON body)
Note over H: Parse body, validate auth header
H->>M: emit signal (BlockingQueuedConnection)
Note over M: handleExecuteRequest()
M->>M: Check concurrent limit
M->>M: Check IP whitelist
M->>M: Check rate limit
M->>M: Validate body & token
M->>D: DzScript::execute()
D-->>M: result / error
M-->>H: response struct
H-->>C: HTTP 200 JSON
Rules that must never be broken:
DzScript,QScriptEngine, and all DAZ API calls on the main thread only.- Synchronous execution crosses from HTTP workers with
Qt::BlockingQueuedConnection. On Studio 6, async script submission uses only local, reentrant Qt Core values and mutex-protected services so it can enqueue while the main thread is busy. Studio 4 retains the blocking crossing for submission because its JSON parser usesQScriptEngine. DzScriptobjects must be created and destroyed on the main thread.killRender()must be invoked via a signal on the main thread, not from the HTTP thread.- HTTP workers must not read GUI-owned mutable settings; async handlers receive immutable snapshots captured before the server starts.
stateDiagram-v2
[*] --> queued : POST /execute/async
queued --> running : dispatcher picks up request
queued --> cancelled : DELETE /requests/:id
running --> completed : script returns
running --> failed : script throws
running --> cancelled : cancel flag + killRender()
completed --> [*] : TTL 1h cleanup
failed --> [*] : TTL 1h cleanup
cancelled --> [*] : TTL 1h cleanup
AsyncRequestManager owns the queue and the map of in-flight requests.
A cleanup timer fires every 5 minutes and purges entries older than 1 hour.
flowchart TD
A[HTTP handler receives request] --> B{Concurrent limit?}
B -->|exceeded| R429a[HTTP 429]
B -->|ok| C{IP whitelisted?}
C -->|blocked| R403[HTTP 403]
C -->|ok| D{Rate limited?}
D -->|exceeded| R429b[HTTP 429]
D -->|ok| E{Body size OK?}
E -->|too large| R413[HTTP 413]
E -->|ok| F{Token valid?}
F -->|invalid| R401[HTTP 401]
F -->|ok| G{Input valid?}
G -->|invalid| R400[HTTP 400]
G -->|ok| H[Wrap in IIFE, inject args]
H --> I[Capture print output]
I --> J[DzScript::execute]
J --> K[Update metrics, write log]
K --> L[HTTP 200 JSON response]
- Generates 128-bit tokens via
SecureRandom(CryptoAPI on Windows,/dev/urandomon Unix/macOS) - Stores token to
~/.daz3d/dazscriptserver_token.txtwithchmod 600 - Validates
X-API-TokenandAuthorization: Bearerheaders - Thread-safe: token loaded once at startup, read-only thereafter
- Per-IP sliding-window counter (default: 60 req / 60 s)
QMutex-protected map; stale entries cleaned up every 100 requests
- Exact-match list, comma-separated in settings
QMutex-protected; applied after concurrent limit, before rate limit
- Monotonic counters for total, successful, and failed requests, plus auth failures
- Persisted via QSettings across DAZ Studio restarts
- Provides uptime (seconds since server start) and calculated success rate
- Thread-safe queue (
QMutex) for incoming async work items - Map of
request_id → AsyncRequestfor status and result retrieval - TTL cleanup timer (Qt timer, fires on main thread)
- Long-poll support: waiting
GET /requests/:id/result?wait=truecalls block inQWaitConditionuntil the result is available or the timeout fires
Client JSON body
└─ "args": { "key": "value" }
│
▼
QScriptEngine::evaluate("var __args = JSON.parse('" + escaped_args + "');")
│
▼
Script wrapping:
(function(){
// user script
}).call(null, __args)
│
▼
Inside DazScript: getArguments()[0] → { key: "value" }
| Platform | Location |
|---|---|
| Windows | HKEY_CURRENT_USER\Software\DAZ 3D\DazScriptServer |
| macOS | ~/Library/Preferences/com.daz3d.DazScriptServer.plist |
| Linux | ~/.config/DAZ 3D/DazScriptServer.conf |
All settings are accessed through ServerSettings, which wraps QSettings
and provides typed getters with range clamping from ServerConfig constants.
CMakeLists.txt — top-level: sets plugin type, platform flags
src/CMakeLists.txt — source list, DAZ SDK include/link, MSVC /MD flag
include/common_version.h — DZSRV_VERSION_STR, bumped per release
Platform-specific notes:
- Windows MSVC:
/MD /U_DEBUG— force multi-threaded DLL CRT, suppress DAZ SDK debug macros that conflict with Release builds - Windows link:
ws2_32(Winsock),advapi32(CryptoAPI) - macOS/Linux:
SecureRandomuses/dev/urandom;chmod 600via POSIXchmod(2)
Qt 4.8 (the SDK's Qt) has QHttp but it is deprecated and removed in later
Qt versions. cpp-httplib is header-only, zero-dependency, and well-maintained.
It runs on a dedicated thread managed by ServerListenThread.
The DAZ Studio SDK explicitly requires all scene-graph and script-engine
operations on the main thread. Synchronous /execute therefore uses
BlockingQueuedConnection and waits for the result. On Studio 6, async script
endpoints validate request value data on the HTTP worker and submit directly to
the mutex-protected AsyncRequestManager; blocking on the main thread here
would prevent submission and queued cancellation while a prior script is
running. Studio 4 keeps submission on the main thread because its JSON fallback
uses QScriptEngine. In both builds the manager posts execution to the main
thread, where DzScript is loaded and run.
Persistent script storage would require a file-system schema and migration logic. Session-only storage keeps the registry simple: clients re-register on HTTP 404 (which happens after a DAZ Studio restart). The tradeoff is that clients need a small registration step at startup.