http://www.silvery-lunar.com/simple/index.php?t295.html
Handler的基本概念
J2EE Web 服務中的Handler技術特點非常像Servlet技術中的Filter。我們知道,在Servlet中,當一個HTTP到達服務端時,往往要經過多個Filter對請求進行過濾,然后才到達提供服務的Servlet,這些Filter的功能往往是對請求進行統一編碼,對用戶進行認證,把用戶的訪問寫入系統日志等。相應的,Web服務中的Handler通常也提供一下的功能:
對客戶端進行認證、授權; 把用戶的訪問寫入系統日志; 對請求的SOAP消息進行加密,解密; 為Web Services對象做緩存。 SOAP消息Handler能夠訪問代表RPC請求或者響應的SOAP消息。在JAX-RPC技術中,SOAP消息Handler可以部署在服務端,也可以在客戶端使用。
下面我們來看一個典型的SOAP消息Handler處理順序: 某個在線支付服務需要防止非授權的用戶訪問或者撰改服務端和客戶端傳輸的信息,從而使用消息摘要(Message Digest)的方法對請求和響應的SOAP消息進行加密。當客戶端發送SOAP消⑹保?突Ф說?andler把請求消息中的某些敏感的信息(如信用卡密碼)進行加密,然后把加密后的SOAP消息傳輸到服務端;服務端的SOAP消息Handler截取客戶端的請求,把請求的SOAP 消息進行解密,然后把解密后的SOAP消息派發到目標的Web服務端點。
Apache axis是我們當前開發Web服務的較好的選擇,使用axisWeb服務開發工具,可以使用Handler來對服務端的請求和響應進行處理。典型的情況下,請求傳遞如圖1所示。
圖1 SOAP消息的傳遞順序
在圖中,軸心點(pivot point)是Apache與提供程序功能相當的部分,通過它來和目標的Web服務進行交互,它通常稱為Provider。axis中常用的Provider有Java:RPC,java:MSG,java:EJB。一個Web服務可以部署一個或者多個Handler。
Apache axis中的Handler體系結構和JAX-RPC 1.0(JSR101)中的體系結構稍有不同,需要聲明的是,本文的代碼在axis中開發,故需要在axis環境下運行。
在axis環境下,SOAP消息Handler必須實現org.apache.axis.Handler接口(在JAX-RPC 1.0規范中,SOAP消息Handler必須實現javax.xml.rpc.handler.Handler接口),org.apache.axis.Handler接口的部分代碼如下:
例程1 org.apache.axis.Handle的部分代碼
public interface Handler extends Serializable { ? public void init(); ? public void cleanup(); ? public void invoke(MessageContext msgContext) throws AxisFault ;
? public void onFault(MessageContext msgContext); ? public void setOption(String name, Object value); ? ? public Object getOption(String name); ? ? public void setName(String name); ? ? public String getName(); ? ? public Element getDeploymentData(Document doc); ? public void generateWSDL(MessageContext msgContext) throws AxisFault; ? … }
為了提供開發的方便,在編寫Handler時,只要繼承org.apache.axis.handlers. BasicHandler即可,BasicHandler是Handler的一個模板,我們看它的部分代碼:
例程2 BasicHandler的部分代碼
public abstract class BasicHandler implements Handler { ? protected static Log log = ? ? LogFactory.getLog(BasicHandler.class.getName()); ? protected Hashtable options; ? protected String name; ? //這個方法必須在Handler中實現。 public abstract void invoke(MessageContext msgContext) throws AxisFault; public void setOption(String name, Object value) { ? ? if ( options == null ) initHashtable(); ? ? options.put( name, value ); ? } … }
BasicHandler中的(MessageContext msgContext)方法是Handler實現類必須實現的方法,它通過MessageContext來獲得請求或者響應的SOAPMessage對象,然后對SOAPMessage進行處理。
在介紹Handler的開發之前,我們先來看一下目標Web服務的端點實現類的代碼,如例程3所示。
例程3 目標Web服務的端點實現類
package com.hellking.webservice; public class HandleredService { //一個簡單的Web服務 public String publicMethod(String name) { return "Hello!"+name; } } //另一個Web服務端點: package com.hellking.webservice; public class OrderService { ? ? //web服務方法:獲得客戶端的訂單信息,并且對訂單信息進行對應的處理, 通常情況是把訂單的信息寫入數據庫,然后可客戶端返回確認信息。 public String orderProduct(String name,String address,String item,int quantity,Card card) { String cardId=card.getCardId(); String cardType=card.getCardType(); String password=card.getPassword(); String rderInfo="name="+name+",address="+address+",item="+item+",quantity="+quantity+" ,cardId="+cardId+",cardType="+cardType+",password="+password; System.out.println("這里是客戶端發送來的信息:"); System.out.println(orderInfo); return orderInfo; } }
下面我們分不同情況討論Handler的使用實例。
使用Handler為系統做日志
Handler為系統做日志是一種比較常見而且簡單的使用方式。和Servlet中的Filter一樣,我們可以使用Handler來把用戶的訪問寫入系統日志。下面我們來看日志Handler的具體代碼,如例程4所示。
例程4 LogHandler的代碼
package com.hellking.webservice;
import java.io.FileOutputStream; import java.io.PrintWriter; import java.util.Date;
import org.apache.axis.AxisFault; import org.apache.axis.Handler; import org.apache.axis.MessageContext; import org.apache.axis.handlers.BasicHandler;
public class LogHandler extends BasicHandler {
? /**invoke,每一個handler都必須實現的方法。 */ ? public void invoke(MessageContext msgContext) throws AxisFault ? { ? ? //每當web服務被調用,都記錄到log中。 ? ? try { ? ? ? ? Handler handler = msgContext.getService(); ? ? ? ? String filename = (String)getOption("filename"); ? ? ? ? if ((filename == null) || (filename.equals(""))) ? ? ? ? ? throw new AxisFault("Server.NoLogFile", ? ? ? ? ? ? ? ? ? ? ? "No log file configured for the LogHandler!", ? ? ? ? ? ? ? ? ? ? ? ? null, null); ? ? ? ? FileOutputStream fos = new FileOutputStream(filename, true); ? ? ? ? ? ? ? ? PrintWriter writer = new PrintWriter(fos); ? ? ? ? ? ? ? ? Integer counter = (Integer)handler.getOption("accesses"); ? ? ? ? if (counter == null) ? ? ? ? ? counter = new Integer(0); ? ? ? ? ? ? ? ? counter = new Integer(counter.intValue() + 1); ? ? ? ? ? ? ? ? Date date = new Date(); ? ? ? ? msgContext.getMessage().writeTo(System.out); ? ? ? ? ? ? ? String result = "在"+date + ": Web 服務 " + ? ? ? ? ? ? ? ? ? msgContext.getTargetService() + ? ? ? ? ? ? ? ? ? " 被調用,現在已經共調用了 " + counter + " 次."; ? ? ? ? handler.setOption("accesses", counter); ? ? ? ? ? ? ? ? writer.println(result); ? ? ? ? ? ? ? ? writer.close(); ? ? } catch (Exception e) { ? ? ? ? throw AxisFault.makeFault(e); ? ? } ? } }
前面我們說過,Handler實現類必須實現invoke方法,invoke方法是Handler處理其業務的入口點。LogHandler的主要功能是把客戶端訪問的Web服務的名稱和訪問時間、訪問的次數記錄到一個日志文件中。
下面部署這個前面開發的Web服務對像,然后為Web服務指定Handler。編輯Axis_Home/WEB-INF/ server-config.wsdd文件,在其中加入以下的內容:
<service name="HandleredService" provider="java:RPC"> <parameter name="allowedMethods" value="*"/> <parameter name="className" value="com.hellking.webservice.HandleredService"/> <parameter name="allowedRoles" value="chen"/> <beanMapping languageSpecificType="java:com.hellking.webservice.Card" qname="card:card" xmlns:card="card"/> <requestFlow> <handler name="logging" type="java:com.hellking.webservice.LogHandler"> <parameter name="filename" value="c:\\MyService.log"/> </handler> </requestFlow> </service>
… </globalConfiguration> … <handler name="logging" type="java:com.hellking.webservice.LogHandler"> <parameter name="filename" value="c:\\MyService.log"/> </handler> … <service name="HandleredService" provider="java:RPC"> … <requestFlow> <handler type="logging"/> ? …<!--在這里可以指定多個Handler--> </requestFlow> </service>
http://127.0.0.1:8080/handler/services/HandleredService?wsdl&method=publicMethod&name=chen
注意:這個URL需要根據具體情況改變。
在Sun Jul 06 22:42:03 CST 2003: Web 服務 HandleredService 被調用,現在已經共調用了 1 次. 在Sun Jul 06 22:42:06 CST 2003: Web 服務 HandleredService 被調用,現在已經共調用了 2 次. 在Sun Jul 06 22:42:13 CST 2003: Web 服務 HandleredService 被調用,現在已經共調用了 3 次.
使用Handler對用戶的訪問認證
使用Handler為用戶訪問認證也是它的典型使用,通過它,可以減少在Web服務端代碼中認證的麻煩,同時可以在部署描述符中靈活改變用戶的訪問權限。
對用戶認證的Handler代碼如下:
例程5 認證的Handler
package com.hellking.webservice; import….
//此handler的目的是對用戶認證,只有認證的用戶才能訪問目標服務。 public class AuthenticationHandler extends BasicHandler { /**invoke,每一個handler都必須實現的方法。 */ public void invoke(MessageContext msgContext)throws AxisFault { ? ? SecurityProvider provider = (SecurityProvider)msgContext.getProperty("securityProvider"); if(provider==null) { ? provider= new SimpleSecurityProvider(); ? ? ? ? msgContext.setProperty("securityProvider", provider); ? ? ? } ? ? if(provider!=null) ? ? { ? ? ? ? ? ? String userId=msgContext.getUsername(); ? ? ? String password=msgContext.getPassword(); ? ? ? ? ? ? //對用戶進行認證,如果authUser==null,表示沒有通過認證, 拋出Server.Unauthenticated異常。 ? ? ? ? org.apache.axis.security.AuthenticatedUser authUser = provider.authenticate(msgContext); ? ? ? ? if(authUser==null) ? ? ? ? throw new AxisFault("Server.Unauthenticated", Messages.getMessage("cantAuth01", userId), null,null); ? ? ? ? //用戶通過認證,把用戶的設置成認證了的用戶。 ? ? ? ? msgContext.setProperty("authenticatedUser", authUser); ? ? } ? } }
在AuthenticationHandler代碼里,它從MessageContext中獲得用戶信息,然后進行認證,如果認證成功,那么就使用msgContext.setProperty("authenticatedUser", authUser)方法把用戶設置成認證了的用戶,如果認證不成功,那么就拋出Server.Unauthenticated異常。
部署這個Handler,同樣,在server-config里加入以下的內容:
<handler name="authen" type="java:com.hellking.webservice.AuthenticationHandler"/> … <service name="HandleredService" provider="java:RPC"> <parameter name="allowedRoles" value="chen"/> … </service>
WEB-INF/users.lst文件中加入以下用戶:
hellking hellking chen chen
http://127.0.0.1:8080/handler/services/HandleredService?wsdl&method=publicMethod&name=chen
將會提示輸入用戶名和密碼,如圖2所示。
圖2 訪問web服務時的驗證
如果客戶端是應用程序,那么可以這樣在客戶端設置用戶名和密碼:
例程6 在客戶端設置用戶名和密碼
http://127.0.0.1:808 String endpointURL = "http://127.0.0.1:8080/handler/services/HandleredService?wsdl"; ? ? ? ? ? ? ? ? Service service = new Service(); ? ? ? ? Call ? call ? = (Call) service.createCall(); ? ? ? ? call.setTargetEndpointAddress( new java.net.URL(endpointURL) ); ? ? ? ? call.setOperationName( new QName("HandleredService", "orderProduct") );//設置操作的名稱。 ? ? ? ? //由于需要認證,故需要設置調用的用戶名和密碼。 ? ? ? ? call.getMessageContext().setUsername("chen"); ? ? ? ? call.getMessageContext().setPassword("chen");??????
|