August 14, 2014
OSB and JSON Proxies: Gathering Statistic
How to collect OSB per-operation statistics for a JSON proxy. Download the full example.
(Special thanks to Saeed Awan for the reference implementation.)
In one of my previous posts, I demonstrated how to implement a simple pass-through JSON proxy. The implementation though has one important deficiency - there are no statistics visible in the OSB console.
We do not know what operations were called, what their execution time was or how many of them have failed.
This is as expected. OSB statistics are based on WSDL operations, and our JSON service has no WSDL.
The operations and performance folks, though, would really like to have these data. Let’s help them.
Since OSB can only read the stats from a WSDL-based proxy, we need to inject such a proxy into our JSON service execution path.
The original JSON service consisted of two services only – an entry proxy (text-based) and a business service (also text-based). Now we insert yet another proxy in between, a WSDL-based one, and that proxy is going to be the one that publishes the statistics:
Forging a WSDL
OSB only understands operations from WSDL. But JSON has operations too, and so we can create a fake WSDL that contains one SOAP operation per real JSON operation.
For instance, the example JSON service from the first post has two operations, AddUser and UpdateUser. We can create a WSDL that exposes the same operations, for example:
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://jsondirect/service" name="JSONDirect" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:jsondirect="http://jsondirect" targetnamespace="http://jsondirect/service" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> <wsdl:types> <xsd:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://jsondirect" attributeformdefault="unqualified" elementformdefault="qualified" targetnamespace="http://jsondirect"> <xs:element name="Dummy"> <xs:complextype> <xs:sequence> <xs:element type="xs:string" name="Data"></xs:element> </xs:sequence> </xs:complextype> </xs:element> </xsd:schema> </wsdl:types> <wsdl:message name="Request"> <wsdl:part name="parameters" element="jsondirect:Dummy"></wsdl:part> </wsdl:message> <wsdl:message name="Response"> <wsdl:part name="parameters" element="jsondirect:Dummy"></wsdl:part> </wsdl:message> <wsdl:porttype name="JSONDirectPort"> <wsdl:operation name="AddUser"> <wsdl:input message="tns:Request"></wsdl:input> <wsdl:output message="tns:Response"></wsdl:output> </wsdl:operation> <wsdl:operation name="UpdateUser"> <wsdl:input message="tns:Request"></wsdl:input> <wsdl:output message="tns:Response"></wsdl:output> </wsdl:operation> </wsdl:porttype> <wsdl:binding type="tns:JSONDirectPort" name="JSONDirectBinding"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"></soap:binding> <wsdl:operation name="AddUser"> <soap:operation soapaction="AddUser"></soap:operation> <wsdl:input> <soap:body use="literal"></soap:body> </wsdl:input> <wsdl:output> <soap:body use="literal"></soap:body> </wsdl:output> </wsdl:operation> <wsdl:operation name="UpdateUser"> <soap:operation soapaction="UpdateUser"></soap:operation> <wsdl:input> <soap:body use="literal"></soap:body> </wsdl:input> <wsdl:output> <soap:body use="literal"></soap:body> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="JSONDirect"> <wsdl:port binding="tns:JSONDirectBinding" name="JSONDirectPort"> <soap:address location="http://localhost:7001/JSONDirect"></soap:address> </wsdl:port> </wsdl:service> </wsdl:definitions>
The payloads defined in this WSDL are never going to be used. The whole purpose of it is to define the operations.
Updating the Entry Proxy
The entry proxy does not communicate directly with the business service anymore, and so it should save some of the values the business services needs in the user headers.
Let’s review this step by step:
Save the relative-URI into the $operationName variable.
Save the http-method into the $httpMethod variable.
Pass the headers to the statistics proxy: $operationName as JSONDirectOperation and $httpMethod as JSONDirectMethod.
On the way out, copy the result content-type to the caller. We’re pretty sure it will be application/json, but we trust the backend system to control it.
The proxy needs an error handler, otherwise the HTTP 500 response body will be replaced with an OSB-generated XML fault.
Even in the error handler, we copy the Content-Type header back to the caller.
When we get into the error handler, respond to the caller with the error.
Statistics Proxy Operation Selection
To increment the statistics counters, the statistics proxy needs to know the current operation name.
The default operation selection logic for a proxy is SOAPAction header. Being a local proxy, the statistics proxy never gets this header.
We cannot use the second most common choice, the SOAP body type, too. Our body will contain a JSON payload, and not the XML the proxy expects.
Instead, we tell the proxy to select the operation based on a transport header.
Remember that in the entry proxy, we have placed the operation name into the JSONDirectOperation user header. This header is delivered to the statistics proxy, and now we use it:
The selection type is transport header.
The header name is JSONDirectOperation.
The header values correspond to the operation names.
Implementing the Statistics Proxy Pipeline
We will define our statistics proxy as WSDL-based, and yet the payload traversing it back and forth is not SOAP, but JSON. Any action that requires the body content may immediately fail, even if it is as simple as logging. For safety, we do not touch the body at all:
The statistics proxy routes to the business service, just like the entry proxy was doing in the original example.
Most of the steps are migrated into this proxy directly from the original entry proxy.
Set $operationName from the JSONDirectOperation user header.
Set $httpMethod from the JSONDirectMethod user header.
Insert $operationName as the relative-URI into $outbound.
Insert $httpMethod as the http-method into $outbound.
Pass all headers to the business service.
On the way back, pass all headers from the business service to the entry proxy.
Provide a basic error handler. Otherwise, any HTTP 500 will lose its JSON payload and get a SOAP fault instead.
I ran a few requests via both AddUser and UpdateUser, and in a couple of minutes I got this nice report:
Note that the UpdateUser operation was intentionally implemented as always to fail, and the 3 failures out of the 3 calls in the report reflect that.
A Potentially Safer Design
The statistics proxy does work as intended.
Having said that, I still have to warn you that this design is a hack. You’re not supposed to pass a non-XML payload as $body via a SOAP proxy!
It works now, and it may work in the foreseeable future, but it may also break in the next OSB release because the OSB runtime may use the payload in some currently unpredictable way.
To protect against this risk, a safer approach can be taken:
In the entry proxy, place the whole JSON payload into a user header JSONBody.
Before routing to the statistics proxy, form a XML body with the payload valid for the operation we’re performing.
The statistics proxy will only see the XML payload, and so will never ever fail because of an unexpected JSON payload in its $body.
Place yet another proxy after the statistics proxy. That proxy will be a text one. It will extract the JSON payload from the JSONBody user header and place it back into $body.
Now call the business service.
Despite requiring a bit more work to implement, this design is more robust and is guaranteed not to break after an OSB upgrade.
I'm building SOA enterprise systems for clients large and small for almost 20 years. Most of that time I've been working with BEA (later Oracle) Weblogic platform, including OSB and other SOA systems.
Feel free to contact me if you have a SOA project to design and implement. See my profile on LinkedIn.
I live in Toronto, Ontario, Canada. Email me at firstname.lastname@example.org