Spring HTTP Invoker

As you saw from the previous section, JAXRPC is quite a complex way to expose services over HTTP. Not only that, the use of XML adds a certain amount of overhead, because incoming requests and outgoing responses have to be converted to and from SOAP. In many cases, all you need is a simple mechanism for two Java applications to communicate with each other using HTTP as the transport.

In this case, JAXRPC may be overkill, because both sides are using Java and using XML as a communication fabric adds unnecessary complexity. Likewise, RMI is unsuitable for this scenario, because it cannot be used over HTTP. Thankfully, quite a few solutions provide simple mechanisms for exposing services over HTTP. Spring provides support for three such services on top of the support offered by JAXRPC: Hessian, Burlap, and Spring HTTP Invokers. Hessian and Burlap are covered in more detail later in the chapter; in this section, you learn how to use Spring's native HTTP Invoker remoting architecture to build distributed components in a 100-percent Spring environment.

From a user perspective, Spring HTTP Invokers are deceptively simple and provide a powerful mechanism for remote component communication. On the server side, services are exposed on top of Spring's comprehensive web framework, using a 100-percent Spring-native configuration mechanism. Unlike JAXRPC, you are in full control of the creation semantics for your service objects and thus you can configure them using Spring DI.

On the client side, Spring provides proxy capabilities that allow you to access HTTP Invoker– exposed services via a business interface. Internally, Spring uses the built-in capabilities of the JDK for HTTP communication, although you can configure it to use Jakarta Commons HttpClient (http://jakarta.apache.org/commons/httpclient), which provides a more complete HTTP implementation. By using HttpClient on the client and standard servlet security on the server, you can secure your HTTP Invoker services using HTTP Basic authentication.

In this section, we show you how to build services using the HTTP Invoker architecture and how to access these services using Spring-generated proxies. You also see how Spring handles the transmission of complex types and how you can provide basic security using HTTP Basic authentication.

Exposing Simple Services

To build and deploy a service using the HTTP Invoker architecture, you need to follow four distinct steps. First you must create the service interface. Spring does not place any special constraints on the service interface, so feel free to use one of your normal business interfaces. However, you may wish to restrict the operations that are exported in your service, in which case you can create a specific interface for your service. For the example in this section, we use the standard HelloWorld interface from Listing 16-1.

The second step you need to take is to build the actual service implementation. Again, Spring places no special constraints on the implementation class such as those specified by RMI. Indeed, with Spring HTTP Invokers, it is generally easier to use one of your standard service classes. If you need to restrict the exposure of operations on your service class, do this in the service interface. For the example in this section, we use the SimpleHelloWorld implementation of HelloWorld shown in Listing 16-2.

The third step is to configure an instance of HttpInvokerServiceExporter in your ApplicationContext. Behind the scenes, HttpInvokerServiceExporter is implemented as a Spring Controller that is capable of receiving an HTTP request and returning an arbitrary response. Also, when configuring your HttpInvokerServiceExporter, you must specify a mapping for the service to a URL. We discuss a lot of different mechanisms for mapping beans to a URL in Chapter 17, but for the examples in this section, we use BeanNameUrlHandlerMapping, which uses the name of the bean as the URL mapping.

The fourth step you need to follow is to configure an instance of Spring's DispatcherServlet in your web application deployment descriptor. DispatcherServlet is responsible for routing incoming HTTP requests to the appropriate Controllers based on their URL mappings. If you are unclear about the basic concept behind the MVC pattern in a web application, you might want to skip ahead to Chapter 17 for a rundown of MVC and the roles played by Spring's DispatcherServlet class and Controller interface.

Creating the Service Interface and Service Implementation

As we mentioned, Spring places no special constraints on your service interface or service implementation when you are using the HTTP Invoker stack. In most cases, you simply use standard business interfaces along with some arbitrary implementation, although in some cases, you may choose to restrict the operations that are exposed remotely using a specific service interface.

For the example in this section, we are going to use the HelloWorld interface and SimpleHelloWorld class from 16-2, respectively. Because we have already shown and discussed these, we will not cover them again here.

Exporting an HTTP Invoker Service

Configuring the HttpInvokerServiceExporter is much like configuring the RmiServiceExporter, although HttpInvokerServiceExporter only requires two properties: the service bean and the service interface. Listing 16-28 shows a sample configuration that configures an HttpInvokerServiceExporter for the helloWorldService bean that we configured in Listing 16-14.

Listing 16-28: Exporting an HTTP Invoker Service

image from book
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean   />          <bean name="/helloWorld"         >         <property name="service">             <ref bean="helloWorldService"/>         </property>         <property name="serviceInterface">             <value>com.apress.prospring.ch16.remoting.HelloWorld</value>         </property>     </bean> </beans>
image from book

This configuration is fairly straightforward, but it has two points of note. First, notice that the bean name for the HttpInvokerServiceExporter is specified as /helloWorld. When processing incoming requests, Spring's DispatcherServlet uses any defined HandlerMapping beans to find a Controller to which the request should be routed. In this case, we define a BeanNameUrlHandlerMapping bean as our only HandlerMapping implementation. As its name implies, BeanNameUrlHandlerMapping attempts to map the URL of the incoming request to a bean based on the bean's name. So in this case, if a request comes in whose URL (after taking out the host name, context path, and servlet mapping) is /helloWorld, BeanNameUrlHandlerMapping routes it to the /helloWorld bean.

The second point of note here is that when we set the service property of the HttpInvokerServiceExporter, we point to a bean that is contained in a separate configuration file. When you create a web application using Spring, it is traditional to separate your bean definitions into at least two ApplicationContexts. You place all non-web-specific bean definitions, such as service objects and data access objects, in one ApplicationContext and all web- specific beans, such as Controllers and HandlerMappings, in another. The ApplicationContext that contains your business interfaces is loaded by the ContextLoaderServlet, whereas the ApplicationContext containing the web components is loaded by the DispatcherServlet. Spring ensures that beans in the web-specific ApplicationContext can access beans in the business ApplicationContext but not vice versa. The reason for this separation is simple— it increases reusability. Because the business ApplicationContext does not contain any web- specific bean declarations, you can reuse it in other applications.

In our example, we reuse the helloWorldBean definition that is shown in Listing 16-14. This single bean definition constitutes the business ApplicationContext and it is loaded by the ContextLoaderServlet. The defaultHandlerMapping and /helloWorld beans shown in Listing 16-28 make up our web ApplicationContext, which is loaded by the DispatcherServlet. The name and location of the web ApplicationContext file is very important. You must place the file in the WEB-INF directory of your web application and you must name it <servlet_name>-servlet.xml where <servlet_name> is the name given to the DispatcherServlet in your web application deployment descriptor.

Configuring Spring's DispatcherServlet

Configuring the DispatcherServlet is really simple; it is just a standard servlet configuration. The only point to note is that you must remember to configure the ContextLoaderServlet, or ContextLoaderListener if it is compatible with your container, if you split your bean definitions across multiple files as we did. Listing 16-29 shows the DispatcherServlet configuration for this example.

Listing 16-29: Configuring DispatcherServlet

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"                          "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app>     <context-param>         <param-name>contextConfigLocation</param-name>         <param-value>/WEB-INF/applicationContext.xml</param-value>     </context-param>          <servlet>         <servlet-name>context</servlet-name>         <servlet-class>               org.springframework.web.context.ContextLoaderServlet         </servlet-class>         <load-on-startup>1</load-on-startup>     </servlet>              <servlet>         <servlet-name>httpinvoker</servlet-name>         <servlet-class>               org.springframework.web.servlet.DispatcherServlet         </servlet-class>         <load-on-startup>3</load-on-startup>     </servlet>              <servlet-mapping>         <servlet-name>httpinvoker</servlet-name>         <url-pattern>/http/*</url-pattern>     </servlet-mapping>      </web-app>
image from book

This configuration should be self-explanatory, so we do not go into any detail here. The only thing to be aware of is that because we named the DispatcherServlet httpinvoker, the ApplicatonContext configuration file associated with this servlet must be named httpinvoker-servlet.xml.

Unfortunately, unlike Axis, the HttpInvokerServiceExporter does not provide a nice interface to test whether or not the service is deployed correctly. However, you can test to see whether the servlet is configured correctly by pointing your browser at the service URL, in this case http://localhost:8080/remoting/http/helloWorld. If you get an error screen that indicates that a java.io.EOFException has been thrown, then the service is being invoked as you wanted it to be but no data is available. If you get another error, then it is likely that Spring cannot find your ApplicationContext configuration files, that they contain errors, or that the URL mapping is incorrectly defined. Check all the configuration files for mistakes and make sure that they are all correctly named and sit in the correct places in your web application, then redeploy.

Accessing an HTTP Invoker Service Using Proxies

As with both RMI and JAXRPC, accessing an HTTP Invoker service is done using proxies that hide all the messy details from your application, which allows you to code purely to business interfaces. Just like the FactoryBeans provided for RMI and JAXRPC proxies, Spring provides the HttpInvokerProxyFactoryBean that creates a proxy for your HTTP Invoker service. Listing 16-30 shows a sample configuration that uses HttpInvokerProxyFactoryBean to create a proxy to the service created in the last section.

Listing 16-30: Creating an HTTP Invoker Proxy

image from book
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean         >         <property name="serviceUrl">             <value>http://localhost:8080/remoting/http/helloWorld</value>         </property>         <property name="serviceInterface">             <value>com.apress.prospring.ch16.remoting.HelloWorld</value>         </property>     </bean> </beans> 
image from book

This configuration is fairly self-explanatory and demonstrates the ease with which you can configure a proxy for HTTP Invoker services. You can use this proxy in your application just like you would use any instance of HelloWorld, as shown in Listing 16-31.

Listing 16-31: Using an HTTP Invoker Proxy

image from book
package com.apress.prospring.ch16.remoting.http;      import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext;      import com.apress.prospring.ch16.remoting.HelloWorld;      public class HelloWorldClient {          public static void main(String[] args) {         ApplicationContext ctx = new FileSystemXmlApplicationContext(                                          "./ch16/src/conf/http/helloWorld.xml");                   HelloWorld helloWorld = (HelloWorld)ctx.getBean("helloWorldService");              System.out.println(helloWorld.getMessage());     } }
image from book

As with earlier examples of the HelloWorld service, running this class results in a short delay before the message "Hello World" is printed to the console.

Using Arbitrary Objects in HTTP Invoker Services

In the previous example, the return type of the getMessage() method was a Java String that implements the Serializable interface and thus is transmittable by HTTP Invoker, which builds on top of Java serialization as a mechanism for transmitting objects across the wire. Recall from the discussion of JAXRPC that you need substantial effort to use a complex Java type as a return type or an argument of a remote method. With HTTP Invokers, you need very little effort to make your own types transmittable—all you need to do is implement Serializable for any classes you wish to use in your service.

As an example of this, consider the MessageService interface we showed you earlier in Listing 16-21 that the MessageBean class implements the Serializable interface and as such, it is perfectly acceptable to use in an HTTP Invoker service. Listing 16-32 shows an HttpInvokerServiceExporter that configures a new HTTP Invoker service at the URL /messageService.

Listing 16-32: Configuring the /messageService HTTP Invoker Service

image from book
<bean name="/messageService"         >         <property name="service">             <ref bean="messageService"/>         </property>         <property name="serviceInterface">             <value>com.apress.prospring.ch16.remoting.MessageService</value>         </property> </bean>
image from book

Notice that in this example we point the HttpInvokerServiceExporter to the messageService that needs to be added to the business ApplicationContext, as shown in Listing 16-33.

Listing 16-33: Adding the messageService Bean to the ApplicationContext

image from book
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean               />     <bean               /> </beans>
image from book

Mapping this service to a URL requires that you configure a BeanNameUrlHandlerMapping definition in the web ApplicationContext, as shown earlier in Listing 16-28.

Because a DispatcherServlet is already configured for the ApplicationContext containing the HTTP Invoker services, you do not need to make any additional modifications to the deployment descriptor for the web application.

As with the previous HTTP Invoker service, you can test that this service has been deployed correctly by pointing your browser at the correct location, but to test it fully, you need to build a client. As you saw in the previous section, this is much simplified by the use of proxies. Listing 16-34 shows a proxy configuration for accessing the MessageService.

Listing 16-34: HTTP Invoker Proxy for MessageService

image from book
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean   >         <property name="serviceUrl">             <value>http://localhost:8080/remoting/http/messageService</value>         </property>         <property name="serviceInterface">             <value>com.apress.prospring.ch16.remoting.MessageService</value>         </property>     </bean> </beans>
image from book

You should be more than familiar with code needed to use this proxy, but we included it in Listing 16-35 for the sake of completeness.

Listing 16-35: Accessing the MessageService

image from book
package com.apress.prospring.ch16.remoting.http;      import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext;      import com.apress.prospring.ch16.remoting.MessageService;      public class MessageServiceClient {          public static void main(String[] args) {         ApplicationContext ctx = new FileSystemXmlApplicationContext(                 "./ch16/src/conf/http/messageService.xml");              MessageService messageService = (MessageService) ctx                 .getBean("messageService");         System.out.println(messageService.getMessage());     } }
image from book

Running this class results in the following output:

Message: Hello World! Sender: Rob Harrop

Using HTTP Basic Authentication

One of the best features of the HTTP Invoker architecture is that it can use built-in servlet container security services to provide secure services. In this example, you learn how to create a secure version of the MessageService we created in the previous section that requires users to authenticate using HTTP Basic authentication.

For the purposes of this example, we reuse most of the server-side configuration from the previous example. The only modifications are to the web ApplicationContext file to add a new URL mapping and to the web application deployment descriptor to configure the security. The bulk of the work in this example is on the client side, where we create an authentication-aware proxy.

Configuring a URL Mapping for the Secure Service

The new secure service requires a new URL mapping that can be mapped as a secured resource in the web application deployment descriptor. Because we use BeanNameUrlHandlerMapping, this involves creating a new bean definition with the new URL, as shown in Listing 16-36.

Listing 16-36: URL Mapping for Secure Service

image from book
<bean name="/messageServiceSecure"         >         <property name="service">             <ref bean="messageService"/>         </property>         <property name="serviceInterface">             <value>com.apress.prospring.ch16.remoting.MessageService</value>         </property>     </bean>
image from book

Remember to add this declaration to the web ApplicationContext, not the business ApplicationContext.

Configuring Container Security

Most of you are already familiar with the details of container security—it is a fairly well-known part of the servlet specification—so we do not go into a great amount of detail on this topic. To secure a service, you need to configure the web application to use HTTP Basic authentication, map a security role into your application, and then mark the service URL as secure. Listing 16-37 shows a configuration that achieves these three points.

Listing 16-37: Configuring Container Security

image from book
    <security-constraint>         <web-resource-collection>             <web-resource-name>Secure HTTP Services</web-resource-name>             <url-pattern>/http/messageServiceSecure</url-pattern>         </web-resource-collection>         <auth-constraint>             <role-name>manager</role-name>         </auth-constraint>     </security-constraint>              <login-config>         <auth-method>BASIC</auth-method>         <realm-name>remoting</realm-name>     </login-config>          <security-role>         <role-name>manager</role-name>     </security-role> 
image from book

In this configuration, we define a security role named manager. How these roles are obtained is container specific. For testing, we used Tomcat, which allows you to specify users and roles in a simple XML file called tomcat-users.xml.

The <login-config> element configures the servlet container to use HTTP Basic authentication, and the <security-constraint> element restricts access to the /http/messageServiceSecure URL members of the manager role only. If you deploy this example to your servlet container, you can check that the security is active by pointing your browser at http://localhost:8080/remoting/http/messageServiceSecure, which should prompt you for a user name and password.

Adding Authentication Capabilities to a Proxy

Deploying a secure HTTP service is the easy part; accessing it is not quite so simple. From our initial discussion of the HttpInvokerProxyFactoryBean¸ recall that we mentioned that internally, this bean can use either built-in JDK HTTP support or the Jakarta Commons HttpClient project. The built-in support for HTTP in the JDK does not include support for HTTP Basic authentication, meaning that you need to use HttpClient if you want to use HTTP Basic authentication to access a secure service.

Configuring HttpInvokerProxyFactoryBean to use HttpClient is simply a matter of setting the httpInvokerRequestExecutor property to an instance of CommonsHttpInvokerRequestExecutor. This is where it gets complex. CommonsHttpInvokerRequestExecutor does not allow you to set user names and passwords as properties, but it does allow you to pass in an instance of

the HttpClient class, which is core to the HttpClient project. However, it is not possible to configure HttpClient with credentials for HTTP Basic authentication using Spring DI. The workaround for this is to create a FactoryBean implementation that can return an appropriately configured implementation of HttpClient, as shown in Listing 16-38.

Listing 16-38: The HttpClientFactoryBean Class

image from book
package com.apress.prospring.ch16.remoting.http;      import org.apache.commons.httpclient.Credentials; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean;      public class HttpClientFactoryBean implements FactoryBean, InitializingBean {          private HttpClient httpClient;     private String username;     private String password;     private String authenticationHost;     private String authenticationRealm;          public Object getObject() throws Exception {         return httpClient;     }          public Class getObjectType() {         return HttpClient.class;     }          public boolean isSingleton() {         return true;     }          public void setPassword(String password) {         this.password = password;     }          public void setUsername(String username) {         this.username = username;     }          public void setAuthenticationHost(String authenticationHost) {         this.authenticationHost = authenticationHost;     }          public void setAuthenticationRealm(String authenticationRealm) {         this.authenticationRealm = authenticationRealm;     }          public void afterPropertiesSet() throws Exception {         if ((username == null) || (password == null)) {             throw new IllegalArgumentException(                     "You must set the username, password");         }              httpClient = new HttpClient();         httpClient.getState().setAuthenticationPreemptive(true);              Credentials credentials = new UsernamePasswordCredentials(username,                 password);         httpClient.getState().setCredentials(authenticationRealm,                 authenticationHost, credentials);     } }
image from book

Most of the code in the HttpClientFactoryBean class is self-explanatory; the important part is the afterProperties() method. The afterProperties() method ensures that values for the username and password properties have been supplied before it moves on to create an instance of HttpClient, which is stored in the httpClient field. The call to setAuthenticationPreemptive() instructs the HttpClient that it should send credentials to the server in advance of any requests from the server in an attempt to authenticate preemptively.

In the final part of afterProperties(), an instance of UsernamePasswordCredentials is created and assigned to the HttpClient, along with the values of the authenticationRealm and authenticationHost properties. Both of these properties can be left null, but specifying them allows you to restrict the supplied credentials to a set host or authentication realm.

By using the HttpClientFactoryBean, it is now possible to configure an instance of CommonsHttpInvokerRequestExecutor with a correctly configured instance of HttpClient. In turn, you can use this CommonsHttpInvokerRequestExecutor bean to configure an instance of HttpInvokerProxyFactoryBean so that it can take advantage of HTTP Basic authentication. This is shown in Listing 16-39.

Listing 16-39: Configuring HttpInvokerProxyFactoryBean for HTTP Basic Authentication

image from book
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean         >         <property name="serviceUrl">             <value>http://localhost:8080/remoting/http/messageServiceSecure</value>         </property>         <property name="serviceInterface">             <value>com.apress.prospring.ch16.remoting.MessageService</value>         </property>         <property  name="httpInvokerRequestExecutor">             <ref local="requestExecutor"/>         </property>     </bean>              <bean   >         <property name="httpClient">             <bean                >                 <property name="username">                     <value>tomcat</value>                 </property>                     <property name="password">                     <value>tomcat</value>                 </property>             </bean>         </property>     </bean> </beans>
image from book

You can use the resulting proxy created by this configuration just like any proxy, as shown in Listing 16-40.

Listing 16-40: The MessageServiceSecureClient Class

image from book
package com.apress.prospring.ch16.remoting.http;      import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext;      import com.apress.prospring.ch16.remoting.MessageService;      public class MessageServiceSecureClient {          public static void main(String[] args) {         ApplicationContext ctx = new FileSystemXmlApplicationContext(                 "./ch16/src/conf/http/messageServiceSecure.xml");              MessageService messageService = (MessageService) ctx                 .getBean("messageService");         System.out.println(messageService.getMessage());     } }
image from book

If you run this example, as long as you configured validation credentials in the ApplicationContext, then you will see output similar to that from the example in Listing 16-35. You should experiment with different sets of credentials to see the security in action.



Pro Spring
Pro Spring
ISBN: 1590594614
EAN: 2147483647
Year: 2006
Pages: 189

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net