An integration that reads a virtual machine inventory from vCenter worked on one instance and failed on the other. The error named the Script mediator, so the first assumption was a defect in the JavaScript. It was not. The response was simply too large for WSO2 Enterprise Integrator 6.6 to build, and the Script mediator happened to be the first thing that asked for it. This post describes how we found the actual cause, the three plausible theories we ruled out along the way, and the fix: making the response smaller instead of trying to raise a limit.
The symptom
ERROR {org.apache.synapse.mediators.bsf.ScriptMediator} - The script engine
returned an error executing the inlined js script function mediate
javax.script.ScriptException: Invalid JSON payload in <Unknown source> at line number 1
Everything in that message points at the script. But the Script mediator attaches the message payload to the script engine before the first statement of the script runs. The exception is raised while the payload is being built, not while the script is executing. A try/catch inside the script cannot catch it, and making the JavaScript more defensive changes nothing. We tried that first, and it did not work.
The body was also not malformed. The same request with curl, using the same session, returned well-formed JSON that parsed cleanly. That proves only that the body is valid JSON. It says nothing about whether EI can build it.
Finding the boundary
Measuring the response size for different requests against the failing instance gave a clear bracket:
| Request | Body size | Result |
|---|---|---|
| VMs on a single host | a few kilobytes | builds |
| VMs filtered to powered off | a little over 200 KB | builds |
| All VMs, unfiltered | a little over 300 KB | fails |
The unfiltered call on the smaller instance returned about half that and had always worked. So the limit lies somewhere between the largest response that built and the smallest that failed, a range that takes in 256 KB, which made a buffer boundary the obvious suspect. We did not pursue the exact parameter, because once the response was split it no longer mattered.
Three theories that each cost a run
An empty body
A 200 response with a zero-length body produces the same exception. vCenter replies with chunked transfer encoding and no Content-Length, so this was plausible. Measuring the body ruled it out.
Guarding the build with count()
The natural guard looks like this:
<filter xpath="count($body/*) > 0">
It breaks every call that was working. EI builds application/json lazily through its JSON stream builder, and an XPath count() in a filter does not expand the lazy element, so it counts zero on good responses too. A run that had read the full inventory read none, with no error. We would not use this guard on a JSON payload.
Compression
There is a proxy in front of vCenter, and every response carries vary: Accept-Encoding. With gzip requested, the large body shrinks to about a tenth of its size, so compression looked like a candidate: perhaps EI was asking for gzip and failing to decode it. But the small single-host response is compressed as well. If decompression were the problem, every call would fail, not only the large one. Ruled out.
The fix: partition the request
Rather than tune a limit we could not identify, we made each response smaller. For a VM listing, the natural partition is power state. Every VM has exactly one, so the union of the slices is the complete set, with nothing missed and nothing counted twice:
/api/vcenter/vm?power_states=POWERED_ON
/api/vcenter/vm?power_states=POWERED_OFF
/api/vcenter/vm?power_states=SUSPENDED
The partition can be checked arithmetically. The byte sizes of the three slices summed to exactly one byte less than the unfiltered call. The missing byte is the comma that joined the arrays. The VM counts summed to the full estate.
Two details matter in the implementation:
- Collect all slices and process them once. Comparing each slice separately against the list of VMs already placed on hosts marked VMs from an earlier slice as unplaced.
- An empty slice, typically
SUSPENDED, returns[]. That is a normal result, not a failure.
If a slice ever grows too large again, the next partition is by datacenter, because every VM belongs to exactly one.
Finding which call failed
The loop that drove these calls logged before each call, so the last log line named the call that had started, not the one whose response failed. That produced two wrong diagnoses, including accusing a host whose response turned out to be clean.
The fix was a temporary log line placed after the call and before anything touched the payload:
<log level="custom">
<property name="STAGE" value="GET-PRE-BUILD"/>
<property name="URL" expression="concat($ctx:HOST, $ctx:PATH)"/>
<property name="HTTP_SC" expression="$ctx:HTTP_SC"/>
</log>
Then pair the last such line with the first error:
awk '/GET-PRE-BUILD/{last=$0} /ScriptMediator/{print last; print $0; exit}' wso2carbon.log
That named the failing URL on the first run. It is an instrument for diagnosis, not production logging, and it came out once the URL was known.
Conclusion
When an EI error names a mediator, ask whether the failure happened inside that mediator or in the payload it requested. A Script mediator that fails before its first statement is a build failure, and a build failure on a large, well-formed response is a size problem. The robust answer is to make the response smaller in a way you can verify, so the fix does not depend on a limit nobody has documented.
Our WSO2 course covers integrating large REST APIs and diagnosing failures like this one, taught by practitioners who do this work for a living.