Tuesday, December 22, 2009

From HTTP to ESB - JBossESB 4.7's New Servlet based HTTP Gateway

One of the primary functions performed by JBossESB is the routing of messages between services. (Remember that, on an ESB, everything is either a message or a service.) The messages that JBoss ESB handles conform to org.jboss.soa.esb.message.Message. Service endpoints that can process messages in this format are described as being "ESB-aware." This is all well and good for services and applications that were designed and written with this message format in mind, but what about other services, including legacy applications, that communicate through other non-ESB aware data formats?

JBossESB handles connecting services that communicate in non-ESB aware message formats through its gateway listeners. These listeners route incoming messages from outside the ESB to ESB services. The ESB's set of gateway listeners (generally referred to as "gateways") support a variety of message formats such as files, FTP, and JMS. These gateways accept messages in a non-ESB aware format onto the ESB and then convert them (actually, they wrap the message payload) into ESB-aware messages before sending them to their service's action pipeline.

In this post, we'll take a look at JBossESB 4.7's new HTTP gateway ("http-gateway" ). [1]

You use an http-gateway to "expose" an ESB-unaware endpoint on the ESB. In other words, an http-gateway enables you to communicate to ESB services through HTTP endpoints, in the familiar format used by Java servlets. This servlet-based approach is made possible by the http-gateway leveraging the JBossESB Server or JBoss Application Server's HTTP container, just as any servlet would. This means that many of the HTTP configuration properties such as port addresses are already handled for you. You don't have to create the entire HTTP configuration for your http-gateway, as many configurations work right out-of-the-box. This is in keeping with other features of JBossESB, such as its rich set of out-of-the-box actions. [2]

A Very Basic Example

Let's take a quick look at a very basic http-gateway configuration. We'll take a longer look at variations of http-gateway usage later on in this post. To begin, let's start small. Here's a service definition taken from the JBossESB user guide [3] that you would create in your JBossESB application's jboss-esb.xml file:
1:  <service category="Vehicles" name="Cars" description="" invmScope="GLOBAL">
2: <listeners>
3: <http-gateway name="Http" />
4: </listeners>
5: <actions mep="RequestResponse">
6: <!-- Service Actions.... -->
7: </actions>
8: </service>
Like a lot of things with JBossESB, you can do a lot with a little. Let's examine this example line-by-line:
  • Line 1: Service name and category are used to differentiate the services. Every service has a category and name attribute. JBossESB uses these attributes to register a service's listeners (gateway listeners or ESB-aware listeners) endpoints in a service registry. The InVM transport enables services to communicate using the same JVM and without having to go through a second non-gateway provider. This makes things more efficient as the services don't have use any networking protocols between JVM instances. The "GLOBAL" value indicates that the this service can be invoked using the InVM transport using the same JVM.
  • Line 3: Here's the definition of the http gateway.
  • Line 5: Remember that since the gateway is servlet based, it requires a request/response message exchange pattern (or "mep" for short).
  • Line 6: And, here's where the pipeline of actions would be defined.
Simple, right? Before we move on, did you notice what's not here? Dude, where's my service provider?

Since a provider is not defined, the service uses a the ESB's default HTTP provider. And, since there is no bus reference ID (the busrefid element in a provider definition and then referenced elsewhere in jboss-esb.xml files), the ESB builds the HTTP endpoint that it exposes out of these fields:
1:     http://<host>:<port>/<.esbname>/http/Vehicles/Cars
The host and port number come from the ESB server. The "<.esbname>" token is the actual name of the deployed .esb archive, minus the .esb extension. Note that while this string is an HTTP URL, it's also an endpoint. The prefix of "http" is actually a namespace that is used for all the http-gateway endpoints.

It's also important to note that the http-gateway listener captures all incoming HTTP requests that match the servlet mapping pattern expressed in the HTTP endpoint. You can make use of this mapping with the gateway's "urlPattern" property. For example, if we modify the service and gateway definition that we just looked at to include a urlPattern:
1:  <service category="Vehicles" name="Cars" description="" invmScope="GLOBAL">
2: <listeners>
3: <http-gateway name="Http" urlPattern="esb-cars/*" />
4: </listeners>
5: <actions mep="RequestResponse">
6: <!-- Service Aactions.... -->
7: </actions>
8: </service>
Then, the service's exposed HTTP endpoint would capture incoming HTTP requests at this address:
1:    http://<host>:<port>/<.esbname>/http/esb-cars/*

Creating a Message From a Request

How does the http-gateway actually convert an incoming HTTP request into an ESB message? It depends on the request's MIME type. What controls this is the “core:org.jboss.soa.esb.mime.text.types” configuration property. This property is defined in your server's jbossesb-properties.xml as a semi-colon separated list of character MIME types. The default value of the property includes wildcard support and is set equal to this set:

text/*
application/xml
application/*-xml

The http-gateway uses this property to determine whether the request payload should be decoded into a Java String before it's given to the service, or if the payload should stay as a Byte array, and have the Service perform the decoding itself in an action. The http-gateway can also over-ride the default MIME type with the "payloadAs" attribute. This attribute enables you to have the gateway treat the payload as either “BYTES” or “STRING.” For text payloads, the gateway uses the same character encoding from the request.

What about the other information in the incoming HTTP request?

The request parameters are contained in the RequestParameterMap (a java.util.Map). These request query string parameters are stored as message properties on the ESB message created by the gateway. Full access to the URL arguments/query string is supported in that you can access the parameters in your service's actions by:
1:  Map params = (Map) message.getProperties().getProperty(“RequestParameterMap”);
The RequestInfoMap is another java.util.Map that contains request information such as authType, characterEncoding, HTTP Method, remoteHost, contentLength, etc.. You can access the parameters in your service's actions by:
1:  Map params = (Map) message.getProperties().getProperty(“RequestInfoMap”);
Let's switch gears now and look at a more extensive example.

The http_gateway Quickstart Example

JBossESB release 4.7 includes the aptly named "http_gateway" quickstart example application. (As I always tell people, the quickstarts included with JBossESB are both a great learning tool and a great starting point for developing your own ESB applications.) The http_gateway quickstart demonstrates multiple ways of using the gateway to pass HTTP requests onto ESB services:
  • Basic authentication
  • The default http bus
  • Mapping ESB exceptions to HTTP error codes globally for an http-provider...
  • ...and how to over-ride that global setting
  • Setting up an asynchronous response that changes the default response code
  • Message-Level Security
We'll take these one at a time.

Basic Authentication

The quickstart uses the "java:/jaas/JBossWS" security domain, which is already installed in JBoss ESB/App Server out-of-the-box configuration. The login username is "kermit" and the password is "thefrog". You can see this username/password combination defined in the "server/[server profile]/conf/props/jbossws-roles.properties" file. Let's take a look at the security setting in the quickstart's jboss-esb.xml file:
1:  <globals>
2: <!-- Security setting for all http-providers and all EBWSs in this jboss-esb.xml file.-->
3: <war-security method="BASIC" domain="JBossWS" />
4: </globals>
This "global" section is new with the http-gateway in JBossESB 4.7. Like the comment says, this defines the global security setting for all the http and EBWS providers in this application. The other supported settings for the war-security method are: DIGEST [4] and CLIENT-CERT.

And here's the http-provider definition:
1:  <http-provider name="http">
2: <http-bus busid="secureFriends">
3: <!-- Only users in the "friend" role are allowed
4: access via the "GET" method. Unspecified
5: methods are not protected (i.e. are allowed)... -->
6: <allowed-roles>
7: <role name="friend" />
8: </allowed-roles>
9: <protected-methods>
10: <method name="GET" />
11: <method name="POST" />
12: </protected-methods>
13: </http-bus>
14: <!-- Global exception mappings file... -->
15: <exception mappingsFile="/http-exception-mappings.properties" />
16: </http-provider>
In this file fragment we see:
  • Line 2: Here's where the http-bus definition starts. Note the name chosen for the busid ("secureFriends"). We'll reference this in the definition of the http-gateway in a moment.
  • Line 5: The set of allowed roles starts here.
  • Line 8: And the set of HTTP methods that user in the allowed roles can use is defined here. Note that the http-gateway supports using these HTTP methods: GET, PUT, POST and DELETE
  • Line 14: Here's another global (global to the http-provider, that is) definition. We'll come back to the exception mappingsFile in a bit. This definition enables you to define status codes to be returned for exceptions.
Now, let's look at the actual http-gateway definition:
1:  <service category="Sales" name="List" description="" invmScope="GLOBAL">
2: <listeners>
3: <!-- Receives: http://<host>:<port>/Quickstart_http_gateway/http/sales/* but will be forced to
4: authenticate because the "sales" bus has basic auth configured (above)... -->
5: <http-gateway name="sales" busidref="secureFriends" urlPattern="sales/*" />
6: </listeners>
7: <actions mep="RequestResponse">
8: <action name="print" class="org.jboss.soa.esb.samples.quickstart.httpgateway.MyAction"/>
9: </actions>
10: </service>
Here's where it all ties together:
  • Line 1: The service definition begins here. Note the "GLOBAL InVM scope that we talked about earlier.
  • Line 5: The http-gateway definition is here. Note the reference to the busidref that we defined in the http-bus. Also note the urlPattern. We'll use this pattern when we run the quickstart.
  • Line 6: The message exchange pattern (mep) is RequestResponse as, remember we're dealing with HTTP.
  • Line 7: Our customer action. This class prints out informatin from the request.
OK - let's run the quickstart and see the results.

Where's the client program that we run? Well, this quickstart is different from most of the other quickstarts in that you don't initial the test by generating a message. Remember how the http-provider we defined specified the GET and POST HTTP verbs? To run this part of the quickstart, you can just fire up your favorite browser and send an HTTP GET request by entering this URL:

http://localhost:8080/Quickstart_http_gateway/http/sales/a/b/c

For the sake of illustration, we'll use the lynx (text) browser.
1:  lynx -dump -source -auth=kermit:thefrog http://localhost:8080/Quickstart_http_gateway/http/sales/a/b/c
2: Service: Sales:List
3:
4: ------------Http Request Info (XStream Encoded)-------------------
5: <org.jboss.soa.esb.http.HttpRequest>
6: <authType>BASIC</authType>
7: <contextPath>/Quickstart_http_gateway</contextPath>
8: <localAddr>127.0.0.1</localAddr>
9: <localName>localhost.localdomain</localName>
10: <method>GET</method>
11: <pathInfo>/a/b/c</pathInfo>
12: <protocol>HTTP/1.0</protocol>
13: <remoteAddr>127.0.0.1</remoteAddr>
14: <remoteHost>127.0.0.1</remoteHost>
15: <remoteUser>kermit</remoteUser>
16: <contentLength>-1</contentLength>
17: <requestURI>/Quickstart_http_gateway/http/sales/a/b/c</requestURI>
18: <scheme>http</scheme>
19: <serverName>localhost</serverName>
20: <requestPath>/http/sales</requestPath>
21: <pathInfoTokens>
22: <string>a</string>
23: <string>b</string>
24: <string>c</string>
25: </pathInfoTokens>
26: <queryParams/>
27: <headers>
28: <org.jboss.soa.esb.http.HttpHeader>
29: <name>host</name>
30: <value>localhost:8080</value>
31: </org.jboss.soa.esb.http.HttpHeader>
32: <org.jboss.soa.esb.http.HttpHeader>
33: <name>accept</name>
34: <value>text/html, text/plain, audio/mod, image/*, application/msword, application/pdf, application/postscript, application/x-java-jnlp-file, text/sgml, video/mpeg, */*;q=0.01</value>
35: </org.jboss.soa.esb.http.HttpHeader>
36: <org.jboss.soa.esb.http.HttpHeader>
37: <name>accept-language</name>
38: <value>en</value>
39: </org.jboss.soa.esb.http.HttpHeader>
40: <org.jboss.soa.esb.http.HttpHeader>
41: <name>user-agent</name>
42: <value>Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.8e-fips-rhel5</value>
43: </org.jboss.soa.esb.http.HttpHeader>
44: <org.jboss.soa.esb.http.HttpHeader>
45: <name>authorization</name>
46: <value>Basic a2VybWl0OnRoZWZyb2c=</value>
47: </org.jboss.soa.esb.http.HttpHeader>
48: </headers>

The Default HTTP Bus in Action

In the next part of the QuickStart we'll take another look a service definition where the definition doesn't reference a bus definition (busidref) and instead uses the "default" http bus. The definition of this service looks like this:
1:  <service category="Index" name="List" description="" invmScope="GLOBAL">
2: <listeners>
3: <!-- Receives: http://<host>:<port>/Quickstart_http_gateway/http/index/*
4: Uses the default http bus configuration... -->
5: <http-gateway name="Index" urlPattern="index/*" />
6: </listeners>
7: <actions mep="RequestResponse">
8: <action name="print" class="org.jboss.soa.esb.samples.quickstart.httpgateway.MyAction"/>
9: </actions>
10: </service>

Well, there's not a great deal to look at here since the default http bus is being used. To access this service, you point your browser at this URL:

http://localhost:8080/Quickstart_http_gateway/http/index/XXXX/yyy?a=1,b=2

Note that we discussed earlier in this post and that this URL shows, we're able to process request parameters in the URL. Here's what lynx shows us:
1:  lynx -dump -source http://localhost:8080/Quickstart_http_gateway/http/index/XXXX/yyy?a=1,b=2
2: Service: Index:List
3:
4: ------------Http Request Info (XStream Encoded)-------------------
5: <org.jboss.soa.esb.http.HttpRequest>
6: <contextPath>/Quickstart_http_gateway</contextPath>
7: <localAddr>127.0.0.1</localAddr>
8: <localName>localhost.localdomain</localName>
9: <method>GET</method>
10: <pathInfo>/XXXX/yyy</pathInfo>
11: <protocol>HTTP/1.0</protocol>
12: <queryString>a=1,b=2</queryString>
13: <remoteAddr>127.0.0.1</remoteAddr>
14: <remoteHost>127.0.0.1</remoteHost>
15: <contentLength>-1</contentLength>
16: <requestURI>/Quickstart_http_gateway/http/index/XXXX/yyy</requestURI>
17: <scheme>http</scheme>
18: <serverName>localhost</serverName>
19: <requestPath>/http/index</requestPath>
20: <pathInfoTokens>
21: <string>XXXX</string>
22: <string>yyy</string>
23: </pathInfoTokens>
24: <queryParams>
25: <entry>
26: <string>a</string>
27: <string-array>
28: <string>1,b=2</string>
29: </string-array>
30: </entry>
31: </queryParams>
32: <headers>
33: <org.jboss.soa.esb.http.HttpHeader>
34: <name>host</name>
35: <value>localhost:8080</value>
36: </org.jboss.soa.esb.http.HttpHeader>
37: <org.jboss.soa.esb.http.HttpHeader>
38: <name>accept</name>
39: <value>text/html, text/plain, audio/mod, image/*, application/msword, application/pdf, application/postscript, application/x-java-jnlp-file, text/sgml, video/mpeg, */*;q=0.01</value>
40: </org.jboss.soa.esb.http.HttpHeader>
41: <org.jboss.soa.esb.http.HttpHeader>
42: <name>accept-language</name>
43: <value>en</value>
44: </org.jboss.soa.esb.http.HttpHeader>
45: <org.jboss.soa.esb.http.HttpHeader>
46: <name>user-agent</name>
47: <value>Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.8e-fips-rhel5</value>
48: </org.jboss.soa.esb.http.HttpHeader>
49: </headers>
Take special note of these lines:
1:   <queryParams>
2: <entry>
3: <string>a</string>
4: <string-array>
5: <string>1,b=2</string>
6: </string-array>
7: </entry>
8: </queryParams>
Let's change the URL a bit and make sure that the query parameters can really be accessed by the service and its actions:
1:  lynx -dump -source http://localhost:8080/Quickstart_http_gateway/http/index/XXXX/yyy?param1=hello,param2=world
2:
3: <queryParams>
4: <entry>
5: <string>param1</string>
6: <string-array>
7: <string>hello,param2=world</string>
8: </string-array>
9: </entry>
10: </queryParams>
Yup! It's them!

A Global Exception

In the description of the basic authentication example that we discussed earlier, we skipped over the definition of a global exceptions mapping for the http-provider:
1:  <!-- Global exception mappings file... -->
2: <<exception mappingsFile="/http-exception-mappings.properties" />
As its name indicates, this feature enables you to define a status code that will be returned for all requests that are received by a service. Let's take a look at the http-exception-mappings.properties file:
1:  org.jboss.soa.esb.services.security.SecurityServiceException=401
2: org.jboss.soa.esb.samples.quickstart.httpgateway.MyActionException=502
What this file indicates is that all requests that raise an exception of type org.jboss.soa.esb.services.security.SecurityServiceException will result in an HTTP return status code of 401 being returned to the client that sent in the request, and all requests that raise an exception of type org.jboss.soa.esb.samples.quickstart.httpgateway.MyActionException will result in an HTTP return sttaus code of 502 being returned. To illustrate how this, we'll invoke this service:
1:  <service category="Exceptions" name="Exception1" description="" invmScope="GLOBAL">
2: <listeners>
3: <http-gateway name="Exception2" />
4: </listeners>
5: <actions mep="RequestResponse">
6: <!-- Uses the globally defined exception mappings defined on the <http-provider>... -->
7: <action name="print" class="org.jboss.soa.esb.samples.quickstart.httpgateway.MyExceptionAction"/>
8: </actions>
9: </service>
The MyExceptionAction class is minimal - just enough code to throw an exception:
1:  public class MyExceptionAction extends AbstractActionPipelineProcessor {
2:
3: public MyExceptionAction(ConfigTree config) {
4: }
5:
6: public Message process(Message message) throws ActionProcessingException {
7: throw new MyActionException("MyActionException!!!!");
8: }
9:
To access this service, you point your browser at this URL: http://localhost:8080/Quickstart_http_gateway/http/Exceptions/Exception1
1:  lynx -dump -source http://localhost:8080/Quickstart_http_gateway/http/Exceptions/Exception1 -error_file=error.txt
2: org.jboss.soa.esb.couriers.FaultMessageException: org.jboss.soa.esb.samples.quickstart.httpgateway.MyActionException: MyActionException!!!!
3: at org.jboss.soa.esb.listeners.message.errors.Factory.createExceptionFromFault(Factory.java:50)
4: at org.jboss.internal.soa.esb.couriers.TwoWayCourierImpl.pickup(TwoWayCourierImpl.java:207)
5: at org.jboss.soa.esb.client.ServiceInvoker$EPRInvoker.attemptDelivery(ServiceInvoker.java:675)
6: at org.jboss.soa.esb.client.ServiceInvoker$EPRInvoker.access$200(ServiceInvoker.java:569)
7: at org.jboss.soa.esb.client.ServiceInvoker.post(ServiceInvoker.java:359)
8: at org.jboss.soa.esb.client.ServiceInvoker.deliverSync(ServiceInvoker.java:219)
9: at org.jboss.soa.esb.listeners.gateway.http.HttpGatewayServlet.service(HttpGatewayServlet.java:187)
10: at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
11: at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
12: at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
13: at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
14: at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
15: at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
16: at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
17: at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
18: at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:182)
19: at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:432)
20: at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
21: at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
22: at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
23: at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:157)
24: at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
25: at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:262)
26: at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
27: at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
28: at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:446)
29: at java.lang.Thread.run(Thread.java:636)
30: Caused by: org.jboss.soa.esb.samples.quickstart.httpgateway.MyActionException: MyActionException!!!!
31: at org.jboss.soa.esb.samples.quickstart.httpgateway.MyExceptionAction.process(MyExceptionAction.java:34)
32: at org.jboss.soa.esb.listeners.message.ActionProcessingPipeline.processPipeline(ActionProcessingPipeline.java:634)
33: at org.jboss.soa.esb.listeners.message.ActionProcessingPipeline.processPipeline(ActionProcessingPipeline.java:586)
34: at org.jboss.soa.esb.listeners.message.ActionProcessingPipeline.process(ActionProcessingPipeline.java:420)
35: at org.jboss.soa.esb.listeners.message.MessageAwareListener$TransactionalRunner.run(MessageAwareListener.java:540)
36: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
37: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
38: ... 1 more
And - here's the return code that we wanted:
1:  cat error.txt
2: URL=http://localhost:8080/Quickstart_http_gateway/http/Exceptions/Exception1 (GET)
3: STATUS=HTTP/1.1 502 Bad Gateway

Over-Riding a Global Exception

It's good to be able to handle all exceptions at a global level. However, there may be times when you will want to receive a different return status code. To do this, you can over-ride that global setting. Here's a service definition to over-ride the global variable that we just discussed:
1:  <service category="Exceptions" name="Exception2" description="" invmScope="GLOBAL">
2: <listeners>
3: <http-gateway name="Exception1">
4: <!-- Override the exception mappings defined on the <http-provider>... -->
5: <exception>
6: <mapping class="org.jboss.soa.esb.samples.quickstart.httpgateway.MyActionException" status="503" />
7: </exception>
8: </http-gateway>
9: </listeners>
10: <actions mep="RequestResponse">
11: <!-- Uses the override exception mappings defined on the <http-gateway>... -->
12: <action name="print" class="org.jboss.soa.esb.samples.quickstart.httpgateway.MyExceptionAction"/>
13: </actions>
14: </service>
Line 6 is the one that we're interested in. Here's where we map the class that throws the exception to the code that is returned. And, here's what happens:
1:  lynx -dump -source http://localhost:8080/Quickstart_http_gateway/http/Exceptions/Exception2 -error_file=error.txt
2: org.jboss.soa.esb.couriers.FaultMessageException: org.jboss.soa.esb.samples.quickstart.httpgateway.MyActionException: MyActionException!!!!
3: at org.jboss.soa.esb.listeners.message.errors.Factory.createExceptionFromFault(Factory.java:50)
4: at org.jboss.internal.soa.esb.couriers.TwoWayCourierImpl.pickup(TwoWayCourierImpl.java:207)
5: at org.jboss.soa.esb.client.ServiceInvoker$EPRInvoker.attemptDelivery(ServiceInvoker.java:675)
6: at org.jboss.soa.esb.client.ServiceInvoker$EPRInvoker.access$200(ServiceInvoker.java:569)
7: at org.jboss.soa.esb.client.ServiceInvoker.post(ServiceInvoker.java:359)
8: at org.jboss.soa.esb.client.ServiceInvoker.deliverSync(ServiceInvoker.java:219)
9: at org.jboss.soa.esb.listeners.gateway.http.HttpGatewayServlet.service(HttpGatewayServlet.java:187)
10: at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
11: at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
12: at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
13: at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
14: at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
15: at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
16: at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
17: at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
18: at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:182)
19: at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:432)
20: at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
21: at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
22: at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
23: at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:157)
24: at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
25: at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:262)
26: at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
27: at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
28: at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:446)
29: at java.lang.Thread.run(Thread.java:636)
30: Caused by: org.jboss.soa.esb.samples.quickstart.httpgateway.MyActionException: MyActionException!!!!
31: at org.jboss.soa.esb.samples.quickstart.httpgateway.MyExceptionAction.process(MyExceptionAction.java:34)
32: at org.jboss.soa.esb.listeners.message.ActionProcessingPipeline.processPipeline(ActionProcessingPipeline.java:634)
33: at org.jboss.soa.esb.listeners.message.ActionProcessingPipeline.processPipeline(ActionProcessingPipeline.java:586)
34: at org.jboss.soa.esb.listeners.message.ActionProcessingPipeline.process(ActionProcessingPipeline.java:420)
35: at org.jboss.soa.esb.listeners.message.MessageAwareListener$TransactionalRunner.run(MessageAwareListener.java:540)
36: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
37: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
38: ... 1 more
39:
40: cat error.txt
41: URL=http://localhost:8080/Quickstart_http_gateway/http/Exceptions/Exception2 (GET)
42: STATUS=HTTP/1.1 503 Service Unavailable

An Asynchronous Response

So far, we've only seen synchronous responses to our requests. It's also possible to have the invoked service respond asynchronously. Here's the quickstart's service definition that illustrates this:

1:  <service category="Async" name="List" description="" invmScope="GLOBAL">
2: <listeners>
3: <!-- Receives: http://<host>:<port>/Quickstart_http_gateway/http/Async/List
4: Uses the default http bus configuration... -->
5: <http-gateway name="Async">
6: <asyncResponse statusCode="202">
7: <payload classpathResource="/readme.txt" contentType="text/plain" characterEncoding="UTF-8" />
8: </asyncResponse>
9: </http-gateway>
10: </listeners>
11: <actions mep="RequestResponse">
12: <action name="print" class="org.jboss.soa.esb.samples.quickstart.httpgateway.MyAction"/>
13: </actions>
14: </service>

  • Line 5: This is where the asynchronous response definition starts. Note that the return code is 202.
  • Line 6: And. we're also able to specify a file, the contents of which will be returned as a static payload in the response along with the return code.
1:  lynx -dump -source http://localhost:8080/Quickstart_http_gateway/http/Async/List -error_file=error.txt
2: Overview:
3: =========
4: The purpose of the http_gateway quickstart sample is to demonstarte how to
5: configue a http gateway to pass the http request into ESB service.
6:
7:
8: Running this quickstart:
9: ========================
10: Please refer to 'ant help-quickstarts' for prerequisites about the quickstarts
11: and a more detailed descripton of the different ways to run the quickstarts.
12:
13: To Run '.esb' archive mode:
14: ===========================
15: 1. In a command terminal window in this folder ("Window1"), type 'ant deploy'.
16: 2. Open the web brower and send http requests to the following addresses. Be sure to
17: check the Server console after executing each request:
18: a) http://localhost:8080/Quickstart_http_gateway/http/sales/a/b/c - Will be routed to the Sales:List
19: service. Will return some details about the request. This Service's <http-gateway> references a
20: "secureFriends" bus, which contains login configurations. The login username is "kermit" and the
21: password is "thefrog". It usee the "java:/jaas/JBossWS" security domain, which is already installed
22: in your JBoss ESB/App Server.
23: b) http://localhost:8080/Quickstart_http_gateway/http/index/XXXX/yyy?a=1,b=2 - Will be routed to the Index:List
24: service. Will return some details about the request. This Service's <http-listener> does not refer
25: to any bus and so simply uses the "default" http bus.
26: c) http://localhost:8080/Quickstart_http_gateway/http/Exceptions/Exception1 - Will be routed to the
27: Exceptions:Exception1 service. This service throws a "MyActionException", resulting in a
28: 502 (Bad Gateway) status being returned in accordance with the Exception to HTTP status code
29: mappings defined globally on the <http-provider>.
30: d) http://localhost:8080/Quickstart_http_gateway/http/Exceptions/Exception2 - Will be routed to the
31: Exceptions:Exception2 service. This service also throws a "MyActionException", but the <http-gateway>
32: on this service overrides the globally defined Exception to HTTP status code mappings defined globally
33: on the <http-provider> to return a 503 (Service Unavailable) status for the "MyActionException" exception.
34: e) http://localhost:8080/Quickstart_http_gateway/http/Async/List - Will be routed to the
35: Async:List service, which responds asynchronously with a 202 (Accepted) response code and a static
36: payload of this readme.txt.
37: f) http://localhost:8080/Quickstart_http_gateway/http/soap/ - Will be routed to the
38: Soap:List service, which requires message level seurity. An 401 error will occur. Now
39: switch to the command line and run "ant soap". This POSTs a SOAP message, with login credentials
40: to the gateway. The service should respond successfully this time!!
41: 3. In this folder ("Window1"), type 'ant undeploy'.
42:
43: cat error.txt
44: URL=http://localhost:8080/Quickstart_http_gateway/http/Async/List (GET)
45: STATUS=HTTP/1.1 202 Accepted

SOAP and Security

The quickstart finishes where it began; with an example of some basic security. Here's the service definition:
1:  <service category="Soap" name="List" description="" invmScope="GLOBAL">
2: <listeners>
3: <!-- Receives: http://<host>:<port>/Quickstart_http_gateway/http/soap/*
4: Execute "ant soap" on command line.... -->
5: <http-gateway name="soap" busidref="secureFriends" urlPattern="soap/*">
6: <exception>
7: <mapping class="org.jboss.soa.esb.services.security.SecurityServiceException" status="401" />
8: </exception>
9: </http-gateway>
10: </listeners>
11: <actions mep="RequestResponse">
12: <action name="print" class="org.jboss.soa.esb.samples.quickstart.httpgateway.MyAction"/>
13: </actions>
14: </service>

  • Line 5: Here's the http-gateway definition. Note that we're using the "secureFriends" busidref again. (This means that kermit will have to login again.)
  • Line 7: And, here's the exception that we'll throw, with a return status code of 401.
Sure enough, when we send a request, we get that exception and return code:
1:  lynx -dump -source http://localhost:8080/Quickstart_http_gateway/http/soap -error_file=error.txt
2: HTTP: Access authorization required.
3: Use the -auth=id:pw parameter.
4:
5: Looking up localhost:8080
6: Making HTTP connection to localhost:8080
7: Sending HTTP request.
8: HTTP request sent; waiting for response.
9: Alert!: Access without authorization denied -- retrying
10:
11: lynx: Can't access startfile http://localhost:8080/Quickstart_http_gateway/http/soap
12:
13: cat error.txt
14: URL=http://localhost:8080/Quickstart_http_gateway/http/soap (GET)
15: STATUS=HTTP/1.1 401 Unauthorized
Now, we could just provide login credentials manually through the browser UI, but we want our client code to be able to do this programatically. The answer to avoiding this exception, is ot have request that we send has to include login credentials. The quickstart's build.xml file provides an ant target to do this for you by invoking the JBossESB HttpRouter action (org.jboss.soa.esb.actions.routing.http.HttpRouter). This action is one of the JBossESB's large set of "out-of-the-box" actions.
1:  <target name="soap" depends="compile" description="sends a SOAP HTTP request to the http gateway">
2: <echo>Http Client</echo>
3: <java fork="yes" classname="org.jboss.soa.esb.actions.routing.http.HttpRouter" failonerror="true">
4: <arg value="endpointUrl=http://localhost:8080/Quickstart_http_gateway/http/soap/"/>
5: <!-- The EBWS is secured with container level security -->
6: <arg value="configurators=HttpProtocol,AuthBASIC"/>
7: <arg value="method=POST"/>
8: <arg value="auth-username=kermit"/>
9: <arg value="auth-password=thefrog"/>
10: <arg value="authscope-host=localhost"/>
11: <arg value="authscope-port=8080"/>
12: <!-- The actual payload to POST -->
13: <arg value="payload=soap-login.xml"/>
14: <classpath refid="exec-classpath"/>
15: </java>
16: </target>
The actual message payload that is sent is defined in the soap-login.xml file is:
1:  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
2: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3: xmlns:xsd="http://www.w3.org/2001/XMLSchema">
4:
5: <soap:Header>
6: </soap:Header>
7:
8: <soap:Body>
9: </soap:Body>
10:
11: </soap:Envelope>
And, the successful results look like this:
1:  soap:
2: [echo] Http Client
3: [java] Request payload:
4: [java] <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
5: [java] xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
6: [java] xmlns:xsd="http://www.w3.org/2001/XMLSchema">
7: [java]
8: [java] <soap:Header>
9: [java] </soap:Header>
10: [java]
11: [java] <soap:Body>
12: [java] </soap:Body>
13: [java]
14: [java] </soap:Envelope>
15: [java]
16: [java] --------------------------
17: [java]
18: [java]
19: [java] Response Status Code: 200
20: [java] Response payload:
21: [java] Service: Soap:List
22: [java]
23: [java] ------------Http Request Info (XStream Encoded)-------------------
24: [java] <org.jboss.soa.esb.http.HttpRequest>
25: [java] <authType>BASIC</authType>
26: [java] <characterEncoding>UTF-8</characterEncoding>
27: [java] <contentType>text/xml;charset=UTF-8</contentType>
28: [java] <contextPath>/Quickstart_http_gateway</contextPath>
29: [java] <localAddr>127.0.0.1</localAddr>
30: [java] <localName>localhost.localdomain</localName>
31: [java] <method>POST</method>
32: [java] <pathInfo>/</pathInfo>
33: [java] <protocol>HTTP/1.1</protocol>
34: [java] <remoteAddr>127.0.0.1</remoteAddr>
35: [java] <remoteHost>127.0.0.1</remoteHost>
36: [java] <remoteUser>kermit</remoteUser>
37: [java] <contentLength>268</contentLength>
38: [java] <requestURI>/Quickstart_http_gateway/http/soap/</requestURI>
39: [java] <scheme>http</scheme>
40: [java] <serverName>localhost</serverName>
41: [java] <requestPath>/http/soap</requestPath>
42: [java] <pathInfoTokens/>
43: [java] <queryParams/>
44: [java] <headers>
45: [java] <org.jboss.soa.esb.http.HttpHeader>
46: [java] <name>content-type</name>
47: [java] <value>text/xml;charset=UTF-8</value>
48: [java] </org.jboss.soa.esb.http.HttpHeader>
49: [java] <org.jboss.soa.esb.http.HttpHeader>
50: [java] <name>user-agent</name>
51: [java] <value>Jakarta Commons-HttpClient/3.0.1</value>
52: [java] </org.jboss.soa.esb.http.HttpHeader>
53: [java] <org.jboss.soa.esb.http.HttpHeader>
54: [java] <name>content-length</name>
55: [java] <value>268</value>
56: [java] </org.jboss.soa.esb.http.HttpHeader>
57: [java] <org.jboss.soa.esb.http.HttpHeader>
58: [java] <name>authorization</name>
59: [java] <value>Basic a2VybWl0OnRoZWZyb2c=</value>
60: [java] </org.jboss.soa.esb.http.HttpHeader>
61: [java] <org.jboss.soa.esb.http.HttpHeader>
62: [java] <name>host</name>
63: [java] <value>localhost:8080</value>
64: [java] </org.jboss.soa.esb.http.HttpHeader>
65: [java] </headers>
66: [java] </org.jboss.soa.esb.http.HttpRequest>
67: [java] --------------------------
68: [java]

Closing Thoughts

The JBossESB's new http-gateway provides great flexibility in integrating HTTP applications across the ESB. The http_gateway quickstart illustrates some of this flexibility, and, incidentally, shows just how much you can do without writing a lot of custom code. Did you notice while eading this post, that we didn't have to write support routines to handle authentication, exception handling, and dealing with URL patterns? The http-gateway and the JBossESB did the heavy lifting for us.

Acknowledgements

Thanks to the JBossESB community and its contributors (see http://anonsvn.jboss.org/repos/labs/labs/jbossesb/trunk/Contributors.txt) - and to Kevin Conner for his timely review comments!

References

[1] http://www.jboss.org/community/wiki/HTTPGateway
[2] http://jboss-soa-p.blogspot.com/2009/09/works-great-right-out-of-box.html
[3] http://www.jboss.org/jbossesb/docs/4.7/manuals/html/ProgrammersGuide.html
[4] http://en.wikipedia.org/wiki/Digest_access_authentication


3 comments:

Unknown said...

First off, thanks for a great post!
One note, I was unable to get the request params using the method mentioned:

Map params = (Map) message.getProperties().getProperty(“RequestParameterMap”);

That property did not exist. Instead I had to use the following code:

HttpRequest request = HttpRequest.getRequest(message);
Map params =request.getQueryParams();



Am I missing something? Using SOA-P 5.0 beta which contains JBossESB4.7 I believe.

Hans said...

Good post!
One note, I do hope some day authentication by CLIENT-CERT will be explained also. I am guessing when it will be explained it will not be what I need. There is a difference in pulling a client certificate out of a token within a message and getting the certificate information of the connection. The last one is what I need because the XML message standard I have to use is without WS-Security implementation.

Lucky for me there is a Jira: http://jira.jboss.org/jira/browse/JBESB-2871

Please vote if you can :)

Unknown said...

I would like to send greetings to all readers. After this, I recognize the content so interesting about this article. For me personally I liked all the information. I would like to know of cases like this more often. Cheap Boston Red Sox Tickets