June 20, 2014

How To Build a JSON Pass-Through Proxy in OSB

This is a step-by-step guide on how to implement a pass-through JSON proxy in OSB. Download the full example.

See other posts about OSB & JSON:
Why JSON Does Help Direct Proxy Performance
OSB and JSON Proxies: Gathering Statistics
JSON Proxies: Inspecting & Modifying The Payload

JSON is cool, but OSB doesn’t recognize it as a first-class data format. OSB cannot validate it, cannot transform it, and cannot even add the smallest security token to the JSON payload.

Consumers though have begun to demand support for JSON, because tests show that the serialization overhead for JSON is several times smaller than for XML.

What it takes to support JSON in OSB?

A JSON Proxy, Step by Step

Let’s walk through the process of creating a simple OSB JSON proxy.

Our proxy will pass the payload through OSB to a JSON business service without being inspected or modified.

While this type of implementation is very limited, it is a good first step in supporting JSON in your OSB deployments, and lets you answer “Yes, but” instead of a blunt “No” when being offered a budget.

Service Description

Pure REST Services

A typical REST service has a separate URL per object. Using different HTTP methods, the caller may create, update or delete that object.

For example, for the URL http://usermanagement/User/100500:

  • GET - retrieves the current user information with id 100500

  • PUT - creates the user with id 100500

  • POST - updates the user with id 100500

  • DELETE - removes the user with id 100500

Operation-Based REST

Some JSON services use a WSDL-influenced operation-based approach, where each URL is an operation, and the payload is passed to the operation with a POST method. This type of implementation is easier to implement with some of the existing tools, including OSB.

For example, the same HTTP POST method with the appropriate JSON payload is used for:

A Service I’m Going to Use

For the sake of this post, I will use a mixed approach service, which lets me demonstrate the handling of both the URI and the HTTP method parts.

OperationURLHTTP Method
Add user http://host:port/AddUser PUT
Update user http://host:port/UpdateUser POST

(For those interested in how I built a mock of the backend service, see the Addendum.)

Business Service

Let’s build the business service. I called mine JSONDirect.

2014-06-18_19-14-05

It must have a type of “Messaging Service”.

2014-06-18_19-15-55

It should have the request and response types set to “Text”.

2014-06-18_19-16-37

I target the business service to the mock proxy by providing the URL.

No more configuration is required, though for a real production service you may want to update the HTTP transport tab to set the authentication method and a service account.

Entry Proxy

Entry proxy is responsible for capturing the call operation (relative URI), its method (HTTP method) and the payload, and to pass these unmodified to the business service.

The relative URI and the method should be updated in the $outbound variable to modify the behaviour of the business service accordingly.

Strictly speaking, you may also need to pass the query parameters value. Some REST services may use the query parameters; mine doesn’t and so I ignore it.

2014-06-18_19-14-05

The service type is set to the already familiar “Messaging Service”.

2014-06-18_19-15-55

And the request and response types are set to “Text”, too.

JSONP-URI

The entry proxy is listening on /JSONDirect URI.

JSONP-Proxy

The proxy implementation retrieves the consumer-provided operation (property relative-URI) and method (property http-method).

It then injects the values into the outbound request for the business service to consume.

(This implementation contains a bug.)

JSONP-OperationName

(1) Read the relative URI into variable $operationName.

The relative URI is the part of the URL after /JSONDirect/.

JSONP-HTTPM

(2) Read the HTTP method into variable $httpMethod

JSONP-Routing

(3) Route the request to the JSONDirect business service.

JSON-InsertURI

(4) Within the routing node, inject the relative URI ($operationName) into the outbound request.

The business service will append this value to the URL configured for it, reconstructing the same URI that was used by the consumer.

http://localhost:8001/JSONDirect + / + AddUser

JSONP-InsertMethod

(5) Within the same routing node, inject the HTTP method, making the business service use it instead of the default POST.

The entry proxy implementation is now complete.

Testing

Time to test our proxy. We’re going to validate 3 scenarios:

  1. AddUser call with HTTP PUT (expected outcome: success with HTTP 200)
  2. AddUser call with HTTP POST (expected outcome: a system error due to the invalid method, HTTP 500)
  3. UpdateUser call with HTTP POST (expected outcome: a business error as designed, HTTP 500).

AddUser PUT

Request:

PUT http://localhost:8001/JSONDirect/AddUser HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/json
Content-Length: 71
Host: localhost:8001
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

{
    "FirstName":"John",
    "LastName":"Doe",
    "PhoneNumber":"4162221234"
}

Response:

HTTP/1.1 200 OK
Date: Fri, 20 Jun 2014 03:48:22 GMT
Content-Length: 42
Content-Type: text/plain; charset=utf-8
X-Powered-By: Servlet/2.5 JSP/2.1

{ 'Result':'Operation AddUser succeeded' }

All OK. (Not really! See below.)

AddUser POST

The request is the same, except for the POST method:

POST http://localhost:8001/JSONDirect/AddUser HTTP/1.1
...

Response:

HTTP/1.1 500 Internal Server Error
Date: Fri, 20 Jun 2014 03:49:38 GMT
Content-Length: 137
Content-Type: text/plain; charset=utf-8
X-Powered-By: Servlet/2.5 JSP/2.1

{ 'Result':'Operation AddUser failed: Not HTTP PUT Only HTTP PUT is allowed for AddUser method' }

The result is as expected: the backend service doesn’t accept POST, and the error is propagated to the caller.

UpdateUser POST

Request (note the /UpdateUser component of the URL):

POST http://localhost:8001/JSONDirect/UpdateUser HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/json
Content-Length: 71
Host: localhost:8001
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

{
    "FirstName":"John",
    "LastName":"Doe",
    "PhoneNumber":"4162221234"
}

Response:

HTTP/1.1 500 Internal Server Error
Date: Fri, 20 Jun 2014 03:52:16 GMT
Content-Length: 42
Content-Type: text/plain; charset=utf-8
X-Powered-By: Servlet/2.5 JSP/2.1

{ 'Result':'Operation UpdateUser failed' }

We’ve got HTTP 500 with the preconfigured error message.

I’ve Got a Bug!

Have you noticed the bug in my implementation?

The content type is returned as text/plain.

The service mock doesn’t have this issue. It sends back application/json, but the entry proxy loses it.

JSONP-ContentTypeBack

The solution is to either pass the Content-Type from the backend service to the consumer, or simply hardcode it to “application/json”.

The response with HTTP 500 (i.e. UpdateUser one) will go back as a fault, so you have to update the Content-Type header in both the response flow and the error handler.

That’s it!

Xgxj8Ta

Not rocket science, as you can see, though as usual for OSB, you have to learn a few gotchas.

Happy JSON’ing!

Addendum: Backend Service Mock

Note: it is much easier to implement mocks with MockMotor.
It is free for a single user.

I’m not going to use an existing service, but build one specifically for this post. This lets me add special features to demonstrate and test the behaviour of the JSON proxy.

2014-06-18_19-19-59

I call my service JSONEcho, and it is implemented as an OSB proxy service.

First, our proxy must have the type “Messaging Service”. “Any SOAP Service” or “Any XML Service” won’t do anymore.

2014-06-18_19-20-28

Then, on the next tab, make sure both the request and response have a type of text.

2014-06-18_19-27-12

On the transport configuration tab, we enter the usual values, most notably the URI the service is listening on.

There is nothing special on any of the other tabs. Configure them as usual for any HTTP entry proxy.

JSONEchoMock

The implementation for JSONEcho is pretty simple:

  • Get the operation name.

  • For AddUser and HTTP PUT, return a success message in JSON format.

  • For UpdateUser, return a failure message in JSON format, and HTTP 500.

  • For all responses, set the Content-Type header to “application/json”.

JSONEcho-OperationName

(1) The mock extracts the operation name.

The operation name is passed as the part after the base URL.

JSONEcho-If

(2) Based on the operation name, one of the two code branches is executed.

JSONEcho-IfPut

(3) For AddUser, the service demands the use of the HTTP PUT operation.

(5) If the method is not PUT, raise an error.

JSONEcho-SuccessResult

(4) For the AddUser operation, the body content is replaced with a success message.

JSONEcho-FailedOperation

(6) For any other operation (including UpdateUser), the body content is replaced with a failure message.

JSONEcho-HTTP500

(7) Also, for the failed operation set the HTTP status code to 500.

2014-06-18_19-25-52

(8) Finally, add “Content-Type: application/json” header, as befits a well-written JSON service.

Testing the Mock Service

Let’s test it.

First let’s perform an AddUser JSON request with a PUT method, as required by our service:

PUT http://localhost:8001/JSONEcho/AddUser HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/json
Content-Length: 71
Host: localhost:8001
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

{
    "FirstName":"John",
    "LastName":"Doe",
    "PhoneNumber":"4162221234"
}

The response contains the success message, the HTTP status is 200, and the the content type is set to “application/json”. All as expected.

HTTP/1.1 200 OK
Date: Thu, 19 Jun 2014 01:50:15 GMT
Content-Length: 42
Content-Type: application/json; charset=utf-8
X-Powered-By: Servlet/2.5 JSP/2.1

{ 'Result':'Operation AddUser succeeded' }

The same AddUser request with a POST method gives us an error (as designed):

HTTP/1.1 500 Internal Server Error
Date: Thu, 19 Jun 2014 20:51:40 GMT
Content-Length: 137
Content-Type: application/json; charset=utf-8
X-Powered-By: Servlet/2.5 JSP/2.1

{ 'Result':'Operation AddUser failed: Not HTTP PUT Only HTTP PUT is allowed for AddUser method' }

Now let’s test the UpdateUser operation. Note the URL has “UpdateUser” as the last component now.

POST http://localhost:8001/JSONEcho/UpdateUser HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/json
Content-Length: 71
Host: localhost:8001
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

{
    "FirstName":"John",
    "LastName":"Doe",
    "PhoneNumber":"4162221234"
}

The response has the failure message (as designed), HTTP status 500 and the correct content type.

TTP/1.1 500 Internal Server Error
Date: Thu, 19 Jun 2014 20:57:48 GMT
Content-Length: 42
Content-Type: application/json; charset=utf-8
X-Powered-By: Servlet/2.5 JSP/2.1

{ 'Result':'Operation UpdateUser failed' }

Good, our service is ready.

Vladimir Dyuzhev, author of GenericParallel

About Me

My name is Vladimir Dyuzhev, and I'm the author of GenericParallel, an OSB proxy service for making parallel calls effortlessly and MockMotor, a powerful mock server.

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.  canada   Email me at info@genericparallel.com