2012年9月26日 星期三

處理 SOAP Header

處理 SOAP Header

This document is provided as is. You are welcomed to use it for non-commercial purpose.

Written by: 國立中興大學資管系呂瑞麟

請勿轉貼


目錄

  1. 處理 SOAP Header
    1. 首先建立一個沒有 SOAP Header 的 web service 以及 client
  2. SOAP Header 的處理流程
  3. 開發 .NET client 來處理 SOAP Header
    1. 開發一個回傳 SOAP Header 的 service 以及使用 SOAP Header 的 .NET client
    2. 開發 .NET client 來處理 AXIS service 傳來的 SOAP Header



處理 SOAP Header

我們在之前的說明中,著重在如何開發 Axis 的服務以及客戶端程式。 可是在實務上,我們需要更多的功能:例如,SOAP 封包內如果包含機密 內容而不希望被惡意人士擷取、或者我們希望能夠控制只允許某些特定人士 才能使用服務等等。為了能夠達成以上目標,我們必須能夠善用 SOAP Header。 參考資料:

要了解整個 SOAP Header 的處理流程,我們有必要從沒有 SOAP Header 的訊息處理開始,並借重 TCP Monitor 的功能,詳細解讀、剖析 SOAP Header 的變化。我們依照下列步驟來說明:
  1. 首先建立一個沒有 SOAP Header 的 service 以及 client,該 service 會回傳 一個代表時間的字串。由於這些程式跟之前的說明類似,我們就不細說。
    1. AXIS service 的程式碼:請注意,由於這個範例使用的 service 名稱也是 TimeService,如果你之前已經依照我們的範例安裝發布了 TimeService, 請你一定要記得先解除之前 TimeService 的佈置。
      import java.util.*;
      
      public class ServerTime {
        public String getTime() {
          Calendar now = Calendar.getInstance();
          String result = String.valueOf(now.get(Calendar.YEAR)) + "-" +
                          String.valueOf(now.get(Calendar.MONTH)) + "-" +
                          String.valueOf(now.get(Calendar.DATE)) + " " +
                          String.valueOf(now.get(Calendar.HOUR)) + ":" +
                          String.valueOf(now.get(Calendar.MINUTE)) + ":" +
                          String.valueOf(now.get(Calendar.SECOND));
          return result;
        }
      }
      
    2. AXIS client 的程式碼,這個程式還沒有加入任何 SOAP Header 的資訊。 port 由使用者決定,如此一來,我們可以容易的使用 TCPMonitor(在我們 的範例中,我們使用 port 1234)、或者真正的 service(Tomcat 的 port 8080)。
      import org.apache.axis.client.*;
      import org.apache.axis.encoding.XMLType;
      import org.apache.axis.utils.Options;
      import javax.xml.rpc.ParameterMode;
      import javax.xml.namespace.QName;
      
      public class HeaderClient {
        public static void main(String[] args) throws Exception {
          if(args.length != 1) {
            System.out.println("Usage: java HeaderClient <port number>");
            System.exit(1);
          }
          String host = "http://localhost:" + args[0];
          String servicepath = "/axis/services";
          String endpoint = host + servicepath;
      
          String ret = null;
          Service service = new Service();
          Call call = (Call) service.createCall();
          call.setTargetEndpointAddress(new java.net.URL (endpoint));
          call.setOperationName(new QName("TimeService", "getTime"));
          call.setReturnType(XMLType.XSD_STRING);
          ret = (String) call.invoke((Object[])null);
      
          System.out.println("伺服器的時間 : " + ret);
        }
      }
      
      如果 TCPMonitor 的 port 值設定為 1234,則我們需要以下列方式執行 client 程式:
      java HeaderClient 1234
      
  2. 觀察 TCPMonitor 的 SOAP 訊息, 下面的訊息是由 AXIS client 端傳給 service 端的 SOAP 訊息。
    POST /axis/services HTTP/1.0
    Content-Type: text/xml; charset=utf-8
    Accept: application/soap+xml, application/dime, multipart/related, text/*
    User-Agent: Axis/1.4
    Host: 127.0.0.1:1234
    Cache-Control: no-cache
    Pragma: no-cache
    SOAPAction: ""
    Content-Length: 365
    
    <?xml version="1.0" encoding="UTF-8"?>
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
                      xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <soapenv:Body>
        <ns1:getTime soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
                     xmlns:ns1="TimeService"/>
      </soapenv:Body>
    </soapenv:Envelope>
    
    下面的訊息是由 service 端回給 client 端的 SOAP 訊息。
    HTTP/1.1 200 OK
    Server: Apache-Coyote/1.1
    Content-Type: text/xml;charset=utf-8
    Date: Sat, 26 Aug 2006 05:19:31 GMT
    Connection: close
    
    <?xml version="1.0" encoding="utf-8"?>
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
                      xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <soapenv:Body>
        <getTimeResponse xmlns="TimeService">
          <getTimeReturn>2006-7-26 1:19:31</getTimeReturn>
        </getTimeResponse>
      </soapenv:Body>
    </soapenv:Envelope>
    
    從以上兩個 SOAP 訊息中,我們可以清楚的看出 <soapenv:Envelope> 只包含了 <soapenv:Body> 的子元素。
  3. 假設我們寫了一個 ASP.NET 的 client(其程式如下),並且在 URL 中輸入 http://localhost:1234/axis/services/TimeService
    <%@Page Language="VB" %>
    <script runat=server>
    Sub Ws_Click(ByVal sender As Object, ByVal e As System.EventArgs)
      ' 從表格中取得 URL 的資料
      Dim URL as String = wsurl.Text
      Dim service as New ServerTimeService()
      service.Url = URL
      Dim Answer As String = service.getTime()
      Label1.Text = Answer
    End Sub
    </script>
    <html>
    <body>
    <h2 align="center">ASP SOAP Client</h2>
    <hr/>
    <form runat="server" id="form1">
    服務的 URL:<asp:TextBox runat="server" ID="wsurl" Width="360px" />
    <asp:Button runat="server" ID="client" OnClick="Ws_Click" Text="執行" />
    </form>
    <font color="gray" size="5">
    <asp:Label id="Label1" runat="server" />
    </font>
    <p/><p/>
    </body>
    </html>
    
    下面的訊息是由 ASP.NET client 端傳給 service 端的 SOAP 訊息。
    POST /axis/services/TimeService HTTP/1.1
    User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 2.0.50727.42)
    Content-Type: text/xml; charset=utf-8
    SOAPAction: ""
    Host: 127.0.0.1:1234
    Content-Length: 289
    Expect: 100-continue
    Connection: Keep-Alive
    
    <?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                   xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <soap:Body>
        <getTime xmlns="http://DefaultNamespace" />
      </soap:Body>
    </soap:Envelope>
    
    下面的訊息是由 service 端回給 ASP.NET client 端的 SOAP 訊息。
    HTTP/1.1 100 Continue
    
    HTTP/1.1 200 OK
    Server: Apache-Coyote/1.1
    Content-Type: text/xml;charset=utf-8
    Transfer-Encoding: chunked
    Date: Sat, 26 Aug 2006 05:50:34 GMT
    
    178
    <?xml version="1.0" encoding="utf-8"?>
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
                      xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <soapenv:Body>
        <getTimeResponse xmlns="http://DefaultNamespace">
          <getTimeReturn>2006-7-26 1:50:34</getTimeReturn>
        </getTimeResponse>
      </soapenv:Body>
    </soapenv:Envelope>
    0
    
    從以上兩個 SOAP 訊息中,我們可以清楚的看出 <soapenv:Envelope> 也都只包含了 <soapenv:Body> 的子元素。另外,值得注意的是:<soapenv:Body> 的子元素(不論是 <getTime> 或者是 <getTimeResponse>),它們 的 namespace 的定義方式在 Axis 和 .NET 不同,這也是造成互通性差的一個 主要原因(唉,廠商的 implementation 為什麼不能一致呢?)

SOAP Header 的處理流程

在本文的一開始,我們說明了使用 SOAP Header 的使用時機,可是它是 如何被使用的,卻還沒說明。一般來說,SOAP Header 的使用都是成雙成對的; 也就是說,當 service 端收到 SOAP 訊息的時候,它會先檢查該訊息是否 包含所需要的資訊(例如使用者帳號和密碼);如果該資訊不存在,或者資訊 比對之後不符合要求,service 端就可以將該 SOAP 訊息的要求退回。 由於這些檢查用的資料與 <soapenv:Body> 內的內容沒有直接關係, 因此這一類的資訊大多放在 <soapenv:Header> 內(也就是 SOAP Header)。 從剛剛的說明,我們知道客戶端程式在要求 service 端的服務,而且該服務 要求某些特定的檢查資訊,那麼客戶端程式在送出要求以前,需要自行加入 所需要的 <soapenv:Header>。假設 service 端希望收到的 SOAP Header 如下所示:
  <soapenv:Header>
    <cp:MessageHeader cp:id="2.16.886.101.999999999.2097156.2.126.1"
                      soapenv:actor="http://schemas.xmlsoap.org/soap/actor/next"
                      soapenv:mustUnderstand="1" 
                      xmlns:cp="http://www.gov.tw/CP/envelope">
      <cp:From>2.16.886.101.999999999aaa</cp:From>
      <cp:To>2.16.886.101.999999999</cp:To>
    </cp:MessageHeader>
  </soapenv:Header>

  1. 讓我們寫一個傳遞包含 SOAP Header 的 AXIS client,程式碼如下: 其中跟 SOAP Header 有關的程式碼以暗紅色呈現,並且將這些敘述從第 01 行 標示到第 08 行。
    import org.apache.axis.client.*;
    import org.apache.axis.encoding.XMLType;
    import org.apache.axis.utils.Options;
    import javax.xml.rpc.ParameterMode;
    import javax.xml.namespace.QName;
    import javax.xml.soap.SOAPElement;
    import org.apache.axis.message.*;
    
    public class HeaderClient {
      public static void main(String[] args) throws Exception {
        if(args.length != 1) {
          System.out.println("Usage: java HeaderClient ");
          System.exit(1);
        }
        String host = "http://localhost:" + args[0];
        String servicepath = "/axis/services";
        String endpoint = host + servicepath;
    
        // 產生 SOAP Header
        // 加上 setPrefix("cp") 的效果為產生如下的標籤
        // <cp:MessageHeader xmlns:cp="http://www.gov.tw/CP/envelope">
    01    SOAPHeaderElement cpHeader = new SOAPHeaderElement(
    01                                 "http://www.gov.tw/CP/envelope",
    01                                 "MessageHeader");
    02    cpHeader.setPrefix("cp");
    03    cpHeader.setMustUnderstand(true);
    04    cpHeader.addAttribute("cp", "http://www.gov.tw/CP/envelope", "id", 
    04                          "2.16.886.101.999999999.2097156.2.126.1");
    05    SOAPElement ele = cpHeader.addChildElement("From");
    06    ele.addTextNode("2.16.886.101.999999999aaa");
    07    ele = cpHeader.addChildElement("To");
    08    ele.addTextNode("2.16.886.101.999999999");
    
        String ret = null;
        Service service = new Service();
        Call call = (Call) service.createCall();
        call.setTargetEndpointAddress(new java.net.URL (endpoint));
        call.setOperationName(new QName("TimeService", "getTime"));
        call.setReturnType(XMLType.XSD_STRING);
    
        // 為 service 設定 Header
        call.addHeader(cpHeader);
    
        ret = (String) call.invoke((Object[])null);
    
        System.out.println("伺服器的時間 : " + ret);
      }
    }
    
    程式第 01 行用來產生一個名為 MessageHeader 的 <soapenv:Header> 子元素, 而且指定 MessageHeader 的 namespace 是 http://www.gov.tw/CP/envelope。 程式第 02 行用來指定 namespace 的名稱為 cp;程式第 03 行幫 MessageHeader 加上一個 soapenv:mustUnderstand="1" 的屬性;程式第 04 行幫 MessageHeader 加上一個 cp:id 的屬性名稱,而且其值為 2.16.886.101.999999999.2097156.2.126.1。 特別說明一下 soapenv:mustUnderstand="1":因為值為 1,這強迫 service 端 一定要處理該屬性,並且將其值改成 0 即可交由 service 端繼續處理 SOAP Body 的內容。 程式第 05 和 06 行幫 MessageHeader 新增一個子元素,該子元素的名稱 為 From,然後再為 From 元素加上一個文字節點 2.16.886.101.999999999aaa。 類似第 05 和 06 行,第 07 和 08 行幫 MessageHeader 新增一個 To 子元素, 並為它加上一個文字節點 2.16.886.101.999999999。
  2. 由 TCPMonitor 所觀察到的 SOAP 訊息如下所示,我們可以清楚看出 已經新增了 Header 的訊息,該部分以綠色字體顯示出來。
    POST /axis/services HTTP/1.0
    Content-Type: text/xml; charset=utf-8
    Accept: application/soap+xml, application/dime, multipart/related, text/*
    User-Agent: Axis/1.4
    Host: 127.0.0.1:1234
    Cache-Control: no-cache
    Pragma: no-cache
    SOAPAction: ""
    Content-Length: 690
    
    <?xml version="1.0" encoding="UTF-8"?>
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" x
                      mlns:xsd="http://www.w3.org/2001/XMLSchema" 
                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <soapenv:Header>
        <cp:MessageHeader cp:id="2.16.886.101.999999999.2097156.2.126.1"
                          soapenv:actor="http://schemas.xmlsoap.org/soap/actor/next"
                          soapenv:mustUnderstand="1" 
                          xmlns:cp="http://www.gov.tw/CP/envelope">
          <cp:From>2.16.886.101.999999999aaa</cp:From>
          <cp:To>2.16.886.101.999999999</cp:To>
        </cp:MessageHeader>
      </soapenv:Header>
      <soapenv:Body>
        <ns1:getTime soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
                     xmlns:ns1="TimeService"/>
      </soapenv:Body>
    </soapenv:Envelope>
    
    由於我們的 AXIS service 還沒建立相對的 SOAP Header 的處理器(稱之為 Handler),所以我們可以觀察到如下的錯誤訊息:
    HTTP/1.1 500 Internal Server Error
    Server: Apache-Coyote/1.1
    Content-Type: text/xml;charset=utf-8
    Date: Sat, 26 Aug 2006 08:47:28 GMT
    Connection: close
    
    <?xml version="1.0" encoding="utf-8"?>
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
                      xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <soapenv:Body>
        <soapenv:Fault>
          <faultcode>soapenv:MustUnderstand</faultcode>
          <faultstring>Did not understand "MustUnderstand" header(s):{http://www.gov.tw/CP/envelope}MessageHeader</faultstring>
          <detail>
            <ns1:hostname xmlns:ns1="http://xml.apache.org/axis/">xml</ns1:hostname>
          </detail>
        </soapenv:Fault>
      </soapenv:Body>
    </soapenv:Envelope>
    
  3. 依照 Axis 的架構設計,處理 SOAP Header 是由 Handler 來處理; 嚴格的說,client 端送過來的 SOAP 訊息會先交給 Handler 來處理(如果 有的話),等到 Handler 處理完了之後,才會交給 service 物件來處理 SOAP Body 的內容。因此,我們需要先設計 Handler。
    import org.apache.axis.AxisFault;
    import org.apache.axis.Message;
    import org.apache.axis.MessageContext;
    import org.apache.axis.handlers.BasicHandler;
    import javax.xml.soap.SOAPException;
    import javax.xml.soap.SOAPHeader;
    import org.apache.axis.message.SOAPHeaderElement;
    import org.apache.axis.message.SOAPEnvelope;
    import org.apache.axis.message.MessageElement;
    import javax.xml.namespace.QName;
    import java.util.Iterator;
    
    public class CPHeaderHandler extends BasicHandler {
      private String from, to;
    
      // 在執行 service 之前,會被處理的 handler
      // invoke() 會被自動呼叫,而且 SOAP 訊息可以經由 msgContext 取得
      public void invoke(MessageContext msgContext) throws AxisFault {
        boolean processedHeader = false;
        try {
          // 取得 Request 的 SOAP 訊息
          Message msg = msgContext.getRequestMessage();
          SOAPEnvelope envelope = msg.getSOAPEnvelope();
          SOAPHeader header = envelope.getHeader();
          Iterator it = header.examineAllHeaderElements();
          SOAPHeaderElement hel;
      
          while (it.hasNext()) {
            hel = (SOAPHeaderElement) it.next();
            String headerName = hel.getNodeName();
            if(headerName.equals("cp:MessageHeader")) {
              // 對於 mustUnderstand 設為 true 的 Header,必須
              // 利用下列的方式把它設為"已經處理了",否則 service
              // 會回傳 Did not understand "MustUnderstand"
              hel.setProcessed(true);
              checkFromTo(hel);
              processedHeader = true;
            }
          }
        } catch (SOAPException e) {
          throw new AxisFault("無法處理 SOAP Header.", e);
        }
    
        if(!processedHeader)
          throw new AxisFault("接收 SOAP Header 失敗");
      }
    
      private void checkFromTo(SOAPHeaderElement hel) throws AxisFault {
        MessageElement fele = hel.getChildElement(new QName(
                              "http://www.gov.tw/CP/envelope/", "From", "cp"));
        if(fele == null)
          throw new AxisFault("找不到 cp:From 標籤.");
    
        MessageElement tele = hel.getChildElement(new QName(
                              "http://www.gov.tw/CP/envelope/", "To", "cp"));
        if(tele == null)
          throw new AxisFault("找不到 cp:To 標籤.");
    
        from = fele.getValue();
        to = tele.getValue();
    
    //    System.out.println("From: " + from + "; To: " + to);
      }
    }
    
  4. Handler 設計完成後,我們必須在註冊 service 的時候,也將該 Handler 一起註冊。本範例所需要的 WSDD 檔如下所示:
    <deployment xmlns="http://xml.apache.org/axis/wsdd/"
        xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
      <handler name="CPInHandler" type="java:CPHeaderHandler"/>
      <service name="TimeService" provider="java:RPC"
                  style="wrapped" use="literal">
        <requestFlow>
          <handler type="CPInHandler"/>
        </requestFlow>
        <parameter name="className" value="ServerTime" />
        <parameter name="allowedMethods" value="*" />
      </service>
    </deployment>
    
    跟以前不一樣的地方,我們以綠色字體呈現出來。首先,我們先把需要註冊 的 Handler 名稱以及其相對應的 Handler 類別定義出來;如第一個綠色部分 就定義了一個名為 CPInHandler 的 Handler,而該 Handler 的類別是 以 Java 開發的 CPHeaderHandler。有了 Handler 之後,我們還需要定義 Handler 究竟是要對送進來的 SOAP 訊息進行處理,還是對送出去的 SOAP 訊息進行處理;如果是對送進來的 SOAP 訊息進行處理,我們需要定義 <requestFlow>;反之,如果是對送出去的 SOAP 訊息進行處理,我們需要定義 <responseFlow>。
  5. 如果我們要求 service 也會回傳 SOAP Header 給 client,那麼我們 需要一個 service 回傳 Header 的 Handler:
    //
    // 非常重要的一項觀察:不要試圖修改 Request 的內容並用 Request
    // 當作 Response,這會造成資料長度的限制。例如,如果 Request
    // 的長度是 691,則利用 Request 來當作 Response 時,期回傳的
    // 長度最高為 691。
    //
    // 另外一項還蠻奇怪的地方就是 AXIS 1.4 版,似乎 SOAPEnvelop 的
    // setHeader() 和 setBody() 這兩個方法無法使用,而必須將 Element
    // 一個一個加上去。
    // 
    import org.apache.axis.*;
    import org.apache.axis.message.*;
    import org.apache.axis.handlers.BasicHandler;
    import javax.xml.soap.SOAPException;
    import javax.xml.namespace.QName;
    import java.util.*;
    import java.io.*;
    import org.w3c.dom.*;
    import javax.xml.soap.SOAPElement;
    
    public class CPOutHandler extends BasicHandler {
      private Message imsg, omsg;
      private SOAPEnvelope ienvelope, oenvelope;
      private SOAPHeader iheader, oheader;
      private SOAPBody ibody, obody;
    
      public void invoke(MessageContext msgContext) throws AxisFault {
    
        System.out.println("CPOutHandler started");
    
        boolean processedHeader = false;
        try {
          // 取得 Request 和 Response 的 SOAP 訊息
          // Request 的訊息包含我們需要的 cp:From 和 cp:To (SOAPHeader)
          // Response 有我們需要的 service 處理完的結果 (SOAPBody)
          imsg = msgContext.getRequestMessage();
          omsg = msgContext.getResponseMessage();
          SOAPEnvelope ienvelope = imsg.getSOAPEnvelope();
          SOAPEnvelope oenvelope = omsg.getSOAPEnvelope();
    
          // 複製 Request 的 Header 到 Response 並進行修改
          iheader = (SOAPHeader) ienvelope.getHeader();
          oheader = (SOAPHeader) oenvelope.getHeader();
          Iterator it = iheader.examineAllHeaderElements();
          SOAPHeaderElement hel;
      
          while (it.hasNext()) {
            hel = (SOAPHeaderElement) it.next();
            oheader.addChildElement(hel);
            String headerName = hel.getNodeName();
            if(headerName.equals("cp:MessageHeader")) {
              swapFromTo(hel);
              processedHeader = true;
            }
          }
        } catch (SOAPException e) {
          throw new AxisFault("無法處理 Request SOAP Header.", e);
        } catch (Exception e) {
          throw new AxisFault("其他錯誤");
        }
    
        if(!processedHeader)
          throw new AxisFault("接收 Request SOAP Header 失敗");
    
        // 將處理過的 SOAP Header 放入 Response 的 Message
        // 記得要將 service 回傳的 SOAP Body 保留在 Response 的
        // Message 之中,否則 client 會收到錯誤的結果
        try {
          msgContext.setResponseMessage(omsg);
        } catch (Exception e) {
          throw new AxisFault("無法產生 Response Message", e);
        }
      }
    
      private void swapFromTo(SOAPHeaderElement hel) throws AxisFault {
        MessageElement fele = hel.getChildElement(new QName(
                              "http://www.gov.tw/CP/envelope/", "From", "cp"));
        if(fele == null)
          throw new AxisFault("找不到 cp:From 標籤.");
    
        MessageElement tele = hel.getChildElement(new QName(
                              "http://www.gov.tw/CP/envelope/", "To", "cp"));
        if(tele == null)
          throw new AxisFault("找不到 cp:To 標籤.");
    
        String from = fele.getValue();
        String to = tele.getValue();
    
        System.out.println("Original From: " + from + "; To: " + to);
    
        fele.setValue(to);
        tele.setValue(from);
      }
    }
    
    我們也需要修改 wsdd,使得 service 回傳資料以後,可以呼叫 CPOutHandler.java 並由它來加上 SOAP Header。
    <deployment xmlns="http://xml.apache.org/axis/wsdd/"
        xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
      <handler name="CPInHandler" type="java:CPHeaderHandler"/>
      <handler name="CPOutHandler" type="java:CPOutHandler"/>
      <service name="TimeService" provider="java:RPC"
                  style="wrapped" use="literal">
        <requestFlow>
          <handler type="CPInHandler"/>
        </requestFlow>
        <responseFlow>
          <handler type="CPOutHandler"/>
        </responseFlow>
        <parameter name="className" value="ServerTime" />
        <parameter name="allowedMethods" value="*" />
      </service>
    </deployment>
    
    仔細觀察 service 回傳的資料中,已經包含了 SOAP Header。
    HTTP/1.1 200 OK
    Server: Apache-Coyote/1.1
    Content-Type: text/xml;charset=utf-8
    Date: Sun, 27 Aug 2006 17:08:42 GMT
    Connection: close
    
    <?xml version="1.0" encoding="utf-8"?>
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
                      xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <soapenv:Header>
        <cp:MessageHeader cp:id="2.16.886.101.999999999.2097156.2.126.1" 
                        soapenv:actor="http://schemas.xmlsoap.org/soap/actor/next" 
                        soapenv:mustUnderstand="1" 
                        xmlns:cp="http://www.gov.tw/CP/envelope/">
          <cp:From>2.16.886.101.999999999</cp:From>
          <cp:To>2.16.886.101.999999999aaa</cp:To>
        </cp:MessageHeader>
      </soapenv:Header>
      <soapenv:Body>
        <getTimeResponse xmlns="TimeService">
          <getTimeReturn>2006-7-28 1:8:42</getTimeReturn>
        </getTimeResponse>
      </soapenv:Body>
    </soapenv:Envelope>
    
  6. 我們也必須修改 AXIS client 使得它需要先使用 Handler 來處理回傳的 SOAP Header 之後,才能處理 SOAP Body 的內容。 新的 AXIS client 稱為 NewHeaderClient.java。
    import org.apache.axis.client.*;
    import org.apache.axis.encoding.XMLType;
    import org.apache.axis.utils.Options;
    import javax.xml.rpc.ParameterMode;
    import javax.xml.namespace.QName;
    import javax.xml.soap.SOAPElement;
    import org.apache.axis.message.*;
    
    public class NewHeaderClient {
      public static void main(String[] args) throws Exception {
        if(args.length != 1) {
          System.out.println("Usage: java HeaderClient ");
          System.exit(1);
        }
        String host = "http://localhost:" + args[0];
        String servicepath = "/axis/services";
        String endpoint = host + servicepath;
    
        // 產生 SOAP Header
        SOAPHeaderElement cpHeader = new SOAPHeaderElement("http://www.gov.tw/CP/envelope/",
                                                           "MessageHeader");
        cpHeader.setPrefix("cp");
        cpHeader.setMustUnderstand(true);
        cpHeader.addAttribute("cp", "http://www.gov.tw/CP/envelope/", "id", 
                              "2.16.886.101.999999999.2097156.2.126.1");
        SOAPElement ele = cpHeader.addChildElement("From");
        ele.addTextNode("2.16.886.101.999999999aaa");
        ele = cpHeader.addChildElement("To");
        ele.addTextNode("2.16.886.101.999999999");
    
        String ret = null;
        Service service = new Service();
        Call call = (Call) service.createCall();
        call.setTargetEndpointAddress(new java.net.URL (endpoint));
        call.setOperationName(new QName("TimeService", "getTime"));
        call.setReturnType(XMLType.XSD_STRING);
    
        // 設定 Header
        call.addHeader(cpHeader);
    
        // 為 client 設定 Handlers
        call.setClientHandlers(null, new InHandler());
    
        ret = (String) call.invoke((Object[])null);
    
        System.out.println("伺服器的時間 : " + ret);
      }
    }
    
  7. client 的 Handler:InHandler.java
    import org.apache.axis.AxisFault;
    import org.apache.axis.Message;
    import org.apache.axis.MessageContext;
    import org.apache.axis.handlers.BasicHandler;
    import javax.xml.soap.SOAPException;
    import javax.xml.soap.SOAPHeader;
    import org.apache.axis.message.SOAPHeaderElement;
    import org.apache.axis.message.SOAPEnvelope;
    import org.apache.axis.message.MessageElement;
    import javax.xml.namespace.QName;
    import java.util.Iterator;
    
    public class InHandler extends BasicHandler {
      public void invoke(MessageContext msgContext) throws AxisFault {
        boolean processedHeader = false;
        try {
          // 取得 Request 的 SOAP 訊息
          Message msg = msgContext.getResponseMessage();
          SOAPEnvelope envelope = msg.getSOAPEnvelope();
          SOAPHeader header = envelope.getHeader();
          Iterator it = header.examineAllHeaderElements();
          SOAPHeaderElement hel;
      
          while (it.hasNext()) {
            hel = (SOAPHeaderElement) it.next();
            String headerName = hel.getNodeName();
            if(headerName.equals("cp:MessageHeader")) {
              hel.setProcessed(true);
              checkFromTo(hel);
              processedHeader = true;
            }
          }
        } catch (SOAPException e) {
          throw new AxisFault("無法處理 SOAP Header.", e);
        }
    
        if(!processedHeader)
          throw new AxisFault("接收 SOAP Header 失敗");
      }
    
      private void checkFromTo(SOAPHeaderElement hel) throws AxisFault {
        MessageElement fele = hel.getChildElement(new QName(
                              "http://www.gov.tw/CP/envelope/", "From", "cp"));
        if(fele == null)
          throw new AxisFault("找不到 cp:From 標籤.");
    
        MessageElement tele = hel.getChildElement(new QName(
                              "http://www.gov.tw/CP/envelope/", "To", "cp"));
        if(tele == null)
          throw new AxisFault("找不到 cp:To 標籤.");
    
        String from = fele.getValue();
        String to = tele.getValue();
      }
    }
    




開發 .NET client 來處理 SOAP Header

  1. 開發一個回傳 SOAP Header 的 service 以及使用 SOAP Header 的 .NET client 參考資料: 首先,我們要定義回傳的 SOAP Header 的樣子。定義 SOAP Header 可以利用 微軟的 Web Services Enhancements (WSE) 2.0 Service Pack 3 for Microsoft .NET (最新版的是 WSE 3.0,但是為了專案的需求,我們安裝的是 WSE 2.0 SP3)。 安裝 WSE 2.0 SP3 的時候,我們選擇的安裝選項是 Administrator 因為我們的環境只有 .NET Framework 2.0 以及它的 SDK。我們建議:安裝完了 WSE 之後,如果你依照預設的方式安裝的話,請將 C:\Program Files\Microsoft WSE\v2.0\Microsoft.Web.Services2.dll 複製一份到 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727 目錄之下。 WSE 的套件包含 SoapHeader 的類別,讓我們利用。假設,我們要從 service 回傳給 client 的 SOAP Header 如下所示:
    <soap:Header xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
        <MyHeader xmlns="http://tempuri.org">
           <Created>dateTime</Created>
           <Expires>long</Expires>
        </MyHeader>
    </soap:Header>
    
    那麼我們需要定義一個對應的 SoapHeader 的類別,其定義的程式碼如下:
    Imports System
    Imports System.Web.Services
    Imports System.Web.Services.Protocols
    
    ' Define a SOAP header by deriving from the SoapHeader class.
    Public Class MyHeader
        Inherits SoapHeader
        Public Created As DateTime
        Public Expires As Long
    End Class
    
    假設我們安裝 service 的位置在 c:\Inetpub\wwwroot\asmx 並已經 建好了 c:\Inetpub\wwwroot\asmx\bin 的目錄,則我們 將上面這個程式碼的檔案名稱命名為 MyHeader.vb 並放置在 c:\Inetpub\wwwroot\asmx。然後,我們需要產生 MyHeader 的 dll 檔:
    vbc /out:bin\MyHeader.dll /t:library /r:System.Web.Services.dll /r:System.Xml.dll MyHeader.vb
    
    我們 HelloWorld 的 service 定義如下:(假設這個 service 的檔名為 1st.asmx)
    <%@ WebService Language="VB" Class="MyWebService" %>
    Imports System
    Imports System.Web.Services
    Imports System.Web.Services.Protocols
    
    ' 由於我們定義的 SOAP Header 的 namespace 是 http://tempuri.org
    ' 所以我們需要下面這一行的宣告
     _
    Public Class MyWebService
        Inherits System.Web.Services.WebService
    
        ' Add a member variable of the type deriving from SoapHeader.
        Public timeStamp As MyHeader
    
        ' Apply a SoapHeader attribute.
        ' 下面的宣告,是告訴 ASP.NET 引擎在執行 HelloWorld 這個方法
        ' 的時候,要將名稱為 timeStamp 的 SOAP Header 回傳給 client
        ' (也就是 Out 的用法)
         _
        Public Function HelloWorld() As String
    
            If (timeStamp Is Nothing) Then
                timeStamp = New MyHeader
            End If
    
            timeStamp.Expires = 60000
            timeStamp.Created = DateTime.UtcNow
    
            Return "Hello World"
        End Function
    End Class
    
    在開發 client 程式之前,仍然跟以前所寫的步驟一樣,我們需要利用 wsdl.exe 來產生 proxy 的原始碼,然後利用 VB 的 compiler 來產生所需要的 proxy 執行檔,執行的步驟如下:(請在 SDK 命令提示字元的視窗下執行)
    // 這個指令產生 MyWebService.vb 的原始碼
    wsdl /l:vb /protocol:SOAP http://localhost/asmx/1st.asmx?WSDL
    
    // 這個指令將 proxy 的原始碼 MyWebService.vb compile 成 MyWebService.dll
    vbc /out:bin\MyWebService.dll /t:library /r:System.Web.Services.dll /r:System.Xml.dll MyWebService.vb
    
    最後,把 client 的程式開發出來,其原始碼如下:
    <%@ Page Language="VB" %>
    <asp:Label id="ReturnValue" runat="server" /><br/>
    <asp:Label id="hdr1" runat="server" /><br/>
    <asp:Label id="hdr2" runat="server" />
    <script runat=server language=VB>
    
     Sub Page_Load(o As Object, e As EventArgs)
      ' Create a new instance of the proxy class.
      Dim proxy As MyWebService = New MyWebService()
      
      ' Call the method on the proxy class that communicates
      ' with your XML Web service method.
      Dim results as String = proxy.HelloWorld()
      Dim header As MyHeader = proxy.MyHeaderValue
    
      ' Display the results of the method in a label.
      ReturnValue.Text = results
      hdr1.Text = header.Expires
      hdr2.Text = header.Created
    
     End Sub
    </script>
    
    記得要將 Microsoft.Web.Services2.dll 複製到 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727
    vbc /t:library /r:System.Web.Services.dll,"C:\Program Files\Microsoft WSE\v2.0\Microsoft.Web.Services2.dll" PrintServiceClient.cs
    
  2. 開發 .NET client 來處理 AXIS service 傳來的 SOAP Header在這個範例中,我們開發一個 AXIS 的 service,這個 service 就是之前的 TimeService(類別是 ServerTime),而且我們也開發了一個 OutputHandler 讓他能夠自動為 service 加上 SOAP Header。然後,我們開發了一個 .NET 的 client 來接收這個 Header。類似 AXIS 的 Handlers,.NET 利用 Filters 來先行處理 SOAP Header。
    1. 開發 ServerTime.java(請取用之前的範例)。
    2. 開發 CPOHandler.java:很明顯的,這個程式是修改 CPOutputHandler.java 而來。
      import org.apache.axis.*;
      import org.apache.axis.message.*;
      import org.apache.axis.handlers.BasicHandler;
      import javax.xml.soap.SOAPException;
      import javax.xml.namespace.QName;
      import java.util.*;
      import java.io.*;
      import org.w3c.dom.*;
      import javax.xml.soap.SOAPElement;
      
      public class CPOHandler extends BasicHandler {
        private Message omsg;
        private SOAPEnvelope oenvelope;
        private SOAPHeader oheader;
      
        public void invoke(MessageContext msgContext) throws AxisFault {
      
          System.out.println("CPOHandler started");
      
          try {
            SOAPHeaderElement cpHeader = new SOAPHeaderElement(
                                         "http://www.gov.tw/CP/envelope",
                                         "MessageHeader");
            cpHeader.setPrefix("cp");
            cpHeader.setMustUnderstand(true);
            cpHeader.addAttribute("cp", "http://www.gov.tw/CP/envelope", "id", 
                                  "2.16.886.101.999999999.2097156.2.126.1");
            SOAPElement ele = cpHeader.addChildElement("From");
            ele.addTextNode("2.16.886.101.999999999aaa");
            ele = cpHeader.addChildElement("To");
            ele.addTextNode("2.16.886.101.999999999");
      
            omsg = msgContext.getResponseMessage();
            SOAPEnvelope oenvelope = omsg.getSOAPEnvelope();
            oheader = (SOAPHeader) oenvelope.getHeader();
            oheader.addChildElement(cpHeader);
          } catch (SOAPException e) {
            throw new AxisFault("無法處理 Response SOAP Header.", e);
          } catch (Exception e) {
            throw new AxisFault("其他錯誤");
          }
      
          try {
            msgContext.setResponseMessage(omsg);
          } catch (Exception e) {
            throw new AxisFault("無法產生 Response Message", e);
          }
        }
      }
      
    3. 利用 wsdd 來佈置服務:
      <deployment xmlns="http://xml.apache.org/axis/wsdd/"
          xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
        <handler name="CPOutHandler" type="java:CPOHandler"/>
        <service name="TimeService" provider="java:RPC" style="wrapped" use="literal">
          <responseFlow>
            <handler type="CPOutHandler"/>
          </responseFlow>
          <parameter name="className" value="ServerTime" />
          <parameter name="allowedMethods" value="*" />
        </service>
      </deployment>
      
    4. 服務佈置完了以後,利用 wsdl 來產生 proxy 的原始碼。跟之前的步驟 不一樣的地方在於要處理 SOAP Header 必須藉助 Microsoft Web Services Enhancement (WSE 2.0 SP3),而要使得 proxy 能用到 WSE 的功能,proxy 不能再繼承原來 的 System.Web.Services.Protocols.SoapHttpClientProtocol 類別, 而必須繼承 WSE 2.0 的 Microsoft.Web.Services2.WebServicesClientProtocol。 因此,在執行完 wsdl.exe 的指令之後,我們需要修改 proxy 的原始碼。 以我們的範例來說,我們需要更改:
      • 將 Imports System.Web.Services 改成 Microsoft.Web.Services2
      • 移除 Imports System.Web.Services.Protocols
      • 修改 proxy 類別的父類別,以 ServerTimeService 為例,將
        Partial Public Class ServerTimeService
                Inherits System.Web.Services.Protocols.SoapHttpClientProtocol
        
        改成
        Partial Public Class ServerTimeService
                Inherits Microsoft.Web.Services2.WebServicesClientProtocol
        
    5. 跟以前一樣,利用 vbc 來產生 .dll 檔,記得要將 Microsoft.Web.Services2.dll 包進來,指令如下:
      vbc /t:library /r:System.Xml.dll /r:Microsoft.Web.Services2.dll ServerTimeService.vb
      
    6. 開發 InputFilter:雖然 WSE 提供了 Trace 的過慮器,但是在這次的 測試過程中,我個人還是比較喜歡把一些訊息寫到檔案比較容易 debug。
      imports System
      imports System.Web
      imports System.Xml
      imports Microsoft.Web.Services2
      imports System.IO
      
      Namespace Filter
        public class CPInputFilter 
               Inherits SoapInputFilter
          Public Overrides Sub ProcessMessage(envelope As SoapEnvelope)
            ' 利用 File 來決定是否被執行,以及其他執行資訊
            Dim sw As StreamWriter = new StreamWriter("filter.log")
            sw.WriteLine("Original SOAP Envelope: " & envelope.OuterXml & vbCrLf)
            sw.flush()
      
            ' 處理 mustUnderstand 的方法之ㄧ:可以在讀取所需的資料後,把
            ' 整個 SOAP Header 刪除掉
      '      envelope.RemoveHeader()
      '      sw.WriteLine("Update SOAP Envelope: " & envelope.OuterXml)
      '      sw.flush()
      
            ' 處理 mustUnderstand 的方法之ㄧ:
            ' 讀取資料,並利用 mustUnderstand 的屬性值設為 "0"
            Dim soapHeader As XmlElement = envelope.Header
      
            if soapHeader isnot nothing then
              sw.WriteLine("SOAP Header is not empty" & vbCrLf)
              sw.flush
            end if
      
            sw.WriteLine("Prefix: " & soapHeader.Prefix & vbCrLf)
      
            Dim msg As XmlNode = soapHeader.FirstChild
            sw.WriteLine("1st Child " & msg.InnerXml & vbCrLf)
            sw.WriteLine("1st Child's Name " & msg.Name & vbCrLf)
            sw.flush()
      
            if (msg.Name = "cp:MessageHeader") then
              msg.Attributes.ItemOf("soapenv:mustUnderstand").Value = "0"
            end if
              
            sw.close()
          End Sub
        End Class
      End Namespace
      
    7. 開發 .NET client:這是一個 console 的程式。
      Imports System
      Imports Microsoft.Web.Services2
      
      Public Class Test
        Public Shared Sub Main()
      
      '==============================================================
          ' 下面的程式碼在於了解整個 Pipeline 內的 Filters 的順序
          ' 以及手動的方式加入 Filter 的方式
      '    Dim pline As New PipeLine()
      '    Dim i as Integer
      
           ' 將 Filters 的順序列印出來
      '    Console.writeline("Before:")
      '    For i=0 to pline.InputFilters.Count - 1
      '      Console.Writeline(pline.InputFilters.Item(i).GetType.ToString)
      '    Next
      
      '    For i=0 to pline.InputFilters.Count - 2
      '      pline.InputFilters.RemoveAt(0)
      '   Next
      
          ' 將 Filter 加到指定的位置,使用 Add() 會加到最後
      '    pline.InputFilters.Insert(0, new CPInputFilter())
      
           ' 將修改後的 Filters 順序列印出來
      '    Console.writeline("After:")
      '    For i=0 to pline.InputFilters.Count - 1
      '      Console.Writeline(pline.InputFilters.Item(i).GetType.ToString)
      '    Next
      
      '=======================================================================
      
          Dim service as New ServerTimeService()
      
          ' 呼叫服務的 getTime()
          Dim Answer As String = service.getTime()
      
          Console.WriteLine(Answer)
        End Sub
      End Class
      
    8. 你必須告訴 .NET 的平台,這個 client 在呼叫 service 之後,回傳 的 SOAP Header 需要讓 CPInputFilter.vb 來處理,因此,我們必須為它 產生一個設定檔。如果剛剛 client 的程式名稱為 cpclient.exe 的話, 那麼這個設定檔的名稱必須是 cpclient.exe.config,而且這個 設定檔必需跟 cpclient.exe 放在同一個目錄之下。
      <?xml version="1.0" encoding="utf-8"?>
      <configuration>
        <configSections>
          <section name="microsoft.web.services2" type="Microsoft.Web.Services2.Configuration.WebServicesConfiguration, Microsoft.Web.Services2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        </configSections>
        <system.web>
          <webServices>
            <soapExtensionTypes>
              <add type="Microsoft.Web.Services2.WebServicesExtension, Microsoft.Web.Services2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" priority="1" group="0" />
            </soapExtensionTypes>
          </webServices>
        </system.web>
        <microsoft.web.services2>
          <diagnostics />
          <filters>
            <input>
              <add type="Filter.CPInputFilter, CPInputFilter" />
            </input>
          </filters>
        </microsoft.web.services2>
      </configuration>
      
      這個設定檔以及它的內容的說明如下:
      • .NET 的 client(console 的程式)在執行以前,會先檢查再同一個 目錄之下,是不是有一個跟它同樣名稱但是副檔名卻是 .config 的設定檔, 在這個範例就是 cpclient.exe.config。如果沒有,就依照預設的情形執行; 如果有,他就會依照設定檔設定的狀況執行。
      • 這個 cpclient.exe.config 的設定檔,一般在微軟的文件中稱之為 app.config,其內容說明如下:
        • 所有要使用 WSE 2.0 來設定 Filters 的設定檔,都必須在 <configuration> 之下加入
            <configSections>
              <section name="microsoft.web.services2" type="Microsoft.Web.Services2.Configuration.WebServicesConfiguration, Microsoft.Web.Services2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
            </configSections>
            
          這一段的設定,基本上照抄即可。唯一需要留意的是 name 的屬性。 name 的屬性值可以不使用如範例中的 microsoft.web.services2 而可以使用你所喜歡的名稱,如 my.webservice,只是這樣子一來, 加入 filters 的標籤名稱也要更改,這個我們等下再說明。
        • 由於我們會使用 soapExtention,所以在 <system.web> 之內也要加入
            <system.web>
              <webServices>
                <soapExtensionTypes>
                  <add type="Microsoft.Web.Services2.WebServicesExtension, Microsoft.Web.Services2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" priority="1" group="0" />
                </soapExtensionTypes>
              </webServices>
            </system.web>
            
        • 由於我們希望 .NET client 在收到 service 傳回來的 SOAP 訊息以前 先由 InputFilter 來處理,因此,我們必須明確的定義出來:
            <microsoft.web.services2>
              <diagnostics />
              <filters>
                <input>
                  <add type="Filter.CPInputFilter, CPInputFilter" />
                </input>
              </filters>
            </microsoft.web.services2>
            
          定義 <microsoft.web.services2> 標籤是因為剛才我們定義一個叫做 microsoft.web.services2 的 section,而 這個 section 需要加入一個我們自己開發的 filter。由於這個 filter 屬於 inputfilter,所以我們就有綠色的那一段設定。如果我們也需要 outputfilter, 那麼我們就要增加以下的設定在 <filters> 的標籤內。
                <output>
                  <add type="namespace.classname, dllname" />
                </output>
            
          至於 <add> 標籤內,type 屬性值的設定規則是 namespace.classname, dllname;以 CPInputfilter 為例,請仔細 看它的原始碼,CPInputfilter 的 Namespace(也就是類似 Java 的 package) 定義為 Filter,而類別名稱為 CPInputfilter,所以 type 屬性值的第一個 部分就是 Filter.CPInputFilter。由於 CPInputfilter.vb 被 compile 之後會產生 CPInputfilter.dll 的檔案,所以 type 屬性值的第二個 部分就是 CPInputFilter。經過這樣的設定,.NET 平台就知道,每一次執行 cpclient.exe 的時候, 會先執行一個名為 Filter.CPInputFilter 的 InputFilter。 最後,要注意的地方是 section 的名稱。如果我們之前把 section 的名稱改成 my.webservice,那麼我們 <filters> 的父標籤的名稱也必須 從 microsoft.web.services2 改成 my.webservice
    9. WSE 2.0 也提供了一個產生 app.config 的工具,你可以執行 Microsoft WSE 2.0 的 Configuration Editor,然後你會看到如下的 畫面:
      請勾選如畫面的兩個選項,然後再選擇 Customized filters 的畫面,並新增 Filter.CPInputFilter, CPInputfilter,最後,在選擇 〔File〕--> 〔Save〕 把檔案儲存下來即可。
    10. 執行後的畫面:
    11. 這是執行 TCP Monitor 的觀察畫面,請注意收到的 SOAP Header 中, 其 soapenv:mustUnderstand 的值是 1,也就是 mustUnderstand 的屬性為 true 的情形,如果沒經過 CPInputFilter 的處理,client 會產生 類似 MessageHeader is not understood 的錯誤訊息。
    12. 剛剛的 .NET client 是 console 的程式,如果要改成 ASP.NET 的 client 呢?為了不跟現在的測試程式重疊,我們採用了以下幾個步驟:
      • 新建一個目錄 c:\Inetpub\wwwroot\soap
      • 把剛才所建立的所有程式複製到 c:\Inetpub\wwwroot\soap,你應該至少有:
        • CPInputFilter.vb
        • cpclient.exe.config
        • ServerTimeService.vb 和 ServerTimeService.dll
      • 把 Microsoft.Web.Services2.dll 複製到 c:\Inetpub\wwwroot\bin
      • 把 cpclient.exe.config 重新命名為 web.config
      • 把 ServerTimeService.dll 複製(或者移到) c:\Inetpub\wwwroot\bin。你必須確定 ServerTimeService.dll 並不會影響其他的程式執行。
      • 修改 CPInputFilter.vb 程式,刪除所有寫資料到檔案的敘述。這個動作是 確保我們不必開放"寫"的權限給網頁的使用者,以避免可能的安全漏洞。修改後, 請重新 compile 產生 .dll 檔,並把這個 dll 檔移到 c:\Inetpub\wwwroot\bin
        imports System
        imports System.Web
        imports System.Xml
        imports Microsoft.Web.Services2
        
        Namespace Filter
          public class CPInputFilter 
                 Inherits SoapInputFilter
            Public Overrides Sub ProcessMessage(envelope As SoapEnvelope)
              Dim soapHeader As XmlElement = envelope.Header
        
              Dim msg As XmlNode = soapHeader.FirstChild
              if msg.Name = "cp:MessageHeader" then
                msg.Attributes.ItemOf("soapenv:mustUnderstand").Value = "0"
              end if
            End Sub
          End Class
        End Namespace
          
      • 開發 ASP.NET 的 client 程式:
        <%@ Page Language="VB" %>
        <asp:Label id="datelabel" runat="server" /><br/>
        <asp:Label id="timelabel" runat="server" /><br/>
        <script runat="server">
         Sub Page_Load(o As Object, e As EventArgs)
          ' Create a new instance of the proxy class.
          Dim proxy As ServerTimeService = New ServerTimeService()
          
          ' Call the method on the proxy class that communicates
          ' with your XML Web service method.
          Dim results as String = proxy.getTime()
        
          ' Display the results of the method in labels.
          Dim arr = split(results, " ")
          datelabel.Text = "日期 " & arr(0)
          timelabel.Text = "時間 " & arr(1)
         End Sub
        </script>
          
    13. 執行後的畫面:









Written by: 國立中興大學資管系呂瑞麟







沒有留言:

張貼留言