顯示具有 Web Service 標籤的文章。 顯示所有文章
顯示具有 Web Service 標籤的文章。 顯示所有文章
2019-07-19 16:46

WCF IP Filter

<!-- Web.config -->
<system.serviceModel>
  <extensions>
    <behaviorExtensions>
      <add name="ipFilter" type="XXX.XXX.IpFilterElement, XXX.XXX" />
    </behaviorExtensions>
  </extensions>

  <!-- .... -->

  <behaviors>
    <serviceBehaviors>
      <behavior>
        <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="true" />
        <ipFilter allow="192.168.1.0/24, 127.0.0.1" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Security.Authentication;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using JustWin.API.Extensions;


public class IpFilterElement : BehaviorExtensionElement
{
    [ConfigurationProperty("allow", IsRequired = true)]
    public virtual string Allow
    {
        get { return this["allow"] as string; }
        set { this["allow"] = value; }
    }

    public override Type BehaviorType
    {
        get { return typeof(IpFilterBehaviour); }
    }

    protected override object CreateBehavior()
    {
        return new IpFilterBehaviour(Allow);
    }
}




public class IpFilterBehaviour : IDispatchMessageInspector, IServiceBehavior
{
    private readonly List<IPAddressRange> _allowList;

    public IpFilterBehaviour(string allow)
    {
        _allowList = allow.Split(',').Select(x => new IPAddressRange(x)).ToList();
    }


    void IServiceBehavior.Validate(ServiceDescription service, ServiceHostBase host)
    {
    }

    void IServiceBehavior.AddBindingParameters(ServiceDescription service, ServiceHostBase host, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
    {
    }

    void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription service, ServiceHostBase host)
    {
        foreach (ChannelDispatcher dispatcher in host.ChannelDispatchers)
        foreach (EndpointDispatcher endpoint in dispatcher.Endpoints)
        {
            endpoint.DispatchRuntime.MessageInspectors.Add(this);
        }
    }



    object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        var remoteEndpoint = request.Properties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;

        var address = IPAddress.Parse(remoteEndpoint.Address);
        if(_allowList.Any(x => x.IsMatch(address))) { return null; }

        request = null;
        return new AuthenticationException($"IP address ({remoteEndpoint.Address}) is not allowed.");
    }


    void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState)
    {
        var ex = correlationState as Exception;
        if (ex == null) { return; }

        MessageFault messageFault = MessageFault.CreateFault(
            new FaultCode("Sender"),
            new FaultReason(ex.Message),
            ex,
            new NetDataContractSerializer()
        );

        reply = Message.CreateMessage(reply.Version, messageFault, null);
    }

}







public class IPAddressRange
{
    private readonly byte[] _rangeAddress;
    private readonly byte[] _rangeMask;

    public IPAddressRange(string ipAndMask)
    {
        string[] split = (ipAndMask + "/128").Split('/');

        var ip = IPAddress.Parse(split[0].Trim());

        int maskLength = int.Parse(split[1].Trim());
        if (ip.AddressFamily == AddressFamily.InterNetwork) { maskLength += 96; }

        _rangeMask = createMask(maskLength);

        _rangeAddress = ip.MapToIPv6().GetAddressBytes()
            .Select((x, i) => x & _rangeMask[i])
            .Select(x => (byte)x)
            .ToArray();
    }


    public bool IsMatch(IPAddress ip)
    {
        byte[] address = ip.MapToIPv6().GetAddressBytes();

        for (int i = 0; i < 16; i++)
        {
            if ((address[i] & _rangeMask[i]) != _rangeAddress[i]) { return false; }
        }

        return true;
    }



    private byte[] createMask(int length)
    {
        var mask = new byte[16];

        for (int i = 0; i < 16; i++)
        {
            mask[i] = 0xff;
            if (length > -8) { length -= 8; }
            if (length < 0) { mask[i] = (byte)(mask[i] << -length); }
        }
        return mask;
    }
}
2015-02-10 15:52

JAX-WS RI + Spring 4 自動 Exporting Service

Spring 4 的部分我就不多說了,請自行配置 jar 檔,這裡我用 jaxws-ri-2.2.10.zipjaxws-spring-1.9.jar 進行配置。


AccountService.java
package test.jaxws.webservice;

import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService
public interface AccountService {

    @WebMethod
    void insertAccount(String acc);

    @WebMethod
    String[] getAccounts(String role);
}
先來定義一個 interface,這主要是給 Client 用的。


AccountServiceImpl.java
package test.jaxws.webservice;

import javax.jws.WebMethod;
import javax.jws.WebService;
import org.springframework.stereotype.Component;

@Component
@WebService(serviceName="AccountService")
public class AccountServiceImpl implements AccountService {

    @Override
    @WebMethod
    public void insertAccount(String acc) {
    }

    @Override
    @WebMethod
    public String[] getAccounts(String role) {
        return new String[] {"tom", "jax"};
    }
}
接者是 Web Service 的主體,@Component 是為了讓 Spring component-scan 自動建立 Bean,@WebService 則是用定義要 Exporting 到 JAX-WS 的 serviceName,由於是透過 Spring 去管理 bean 所以可以用 @Autowired 引用其他資源。


JaxWsRtServletServiceExporter.java
package test.jaxws.webservice;

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.jws.WebService;
import javax.xml.ws.WebServiceProvider;

import org.jvnet.jax_ws_commons.spring.SpringService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.CannotLoadBeanClassException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import com.sun.xml.ws.transport.http.servlet.SpringBinding;

public class JaxWsRtServletServiceExporter implements BeanFactoryAware, InitializingBean {

    private ListableBeanFactory beanFactory;
    private String basePath;

    public void setBasePath(String basePath) {
        this.basePath = basePath;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        if (!(beanFactory instanceof ListableBeanFactory)) {
            throw new IllegalStateException(getClass().getSimpleName() + " requires a ListableBeanFactory");
        }
        this.beanFactory = (ListableBeanFactory) beanFactory;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (this.beanFactory instanceof SingletonBeanRegistry == false) { return; }

        SpringService service = new SpringService();

        Set<String> beanNames = new LinkedHashSet<String>(this.beanFactory.getBeanDefinitionCount());
        beanNames.addAll(Arrays.asList(this.beanFactory.getBeanDefinitionNames()));

        if (this.beanFactory instanceof ConfigurableBeanFactory) {
            beanNames.addAll(Arrays.asList(((ConfigurableBeanFactory) this.beanFactory).getSingletonNames()));
        }

        SingletonBeanRegistry factory = (SingletonBeanRegistry)this.beanFactory;

        for (String beanName : beanNames) {
            try {
                Class<?> type = this.beanFactory.getType(beanName);
                if (type == null || type.isInterface()) { continue; }

                WebService wsAnnotation = type.getAnnotation(WebService.class);
                WebServiceProvider wsProviderAnnotation = type.getAnnotation(WebServiceProvider.class);
                if (wsAnnotation == null && wsProviderAnnotation == null) { continue; }


                String serviceName;
                if (wsAnnotation != null) {
                    serviceName = wsAnnotation.serviceName();
                } else {
                    serviceName = wsProviderAnnotation.serviceName();
                }

                service.setImpl(type);
                service.setBean(this.beanFactory.getBean(beanName));

                SpringBinding springBinding = new SpringBinding();
                springBinding.setBeanName(serviceName + "Binding");
                springBinding.setUrl(this.basePath + "/" + serviceName);
                springBinding.setService(service.getObject());

                factory.registerSingleton(serviceName + "Binding", springBinding);
            }
            catch (CannotLoadBeanClassException ex) {
                // ignore beans where the class is not resolvable
            }
        }
    }
}
為了可以自動 Exporting Service 到 JaxWs RI Servlet 需要建立一個 Exporter,這裡將具有 @WebService 或 @WebServiceProvider 的 Bean 包裝成 SpringBinding 再註冊到 Spring BeanFactory 裡,這樣 WSSpringServlet 就會取到這邊註冊 SpringBinding Bean 進行 Exporting。


web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns="http://java.sun.com/xml/ns/javaee" 
 xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
 version="3.0"
>
 <display-name>test-jaxws-rt</display-name>

 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/jaxws.xml</param-value>
 </context-param>

 <context-param>
     <param-name>webAppRootKey</param-name>
     <param-value>test-jaxws-rt.root</param-value>
   </context-param> 
 
 <servlet>
  <servlet-name>dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>/WEB-INF/dispatcher.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>/</url-pattern>
 </servlet-mapping>

    <servlet>
        <servlet-name>jaxws-servlet</servlet-name>
        <servlet-class>com.sun.xml.ws.transport.http.servlet.WSSpringServlet</servlet-class>
  <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>jaxws-servlet</servlet-name>
        <url-pattern>/service/*</url-pattern>
    </servlet-mapping>
</web-app>
在 web.xml 同時註冊兩個 servlet,分別是 Spring MVC 的 DispatcherServlet 跟 JAX-WS 的 WSSpringServlet,這邊主要是透過 ContextLoaderListener 去啟用全域的 ApplicationContext 去管理所有的 Bean。


jaxws.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-4.0.xsd"
>
    <context:component-scan base-package="test.jaxws.webservice" />

    <bean id="jaxWsRtServletServiceExporter" class="test.jaxws.webservice.JaxWsRtServletServiceExporter">
        <property name="basePath" value="/service"/>
    </bean>
</beans>
jaxws.xml 是給 ContextLoaderListener 的設定,這裡用 component-scan 去找出 test.jaxws.webservice 下有 @Component 的 Bean,以及用 JaxWsRtServletServiceExporter 去轉換所有的 @WebService 到 SpringBinding。


dispatcher.xml
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-4.0.xsd"
>
    <context:component-scan base-package="test.jaxws.controller" />

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean" scope="request">
        <property name="serviceInterface" value="test.jaxws.webservice.AccountService"/>
        <property name="wsdlDocumentUrl" value="http://localhost:8090/test-jaxws-rt/service/AccountService?wsdl"/>
        <property name="namespaceUri" value="http://webservice.jaxws.test/"/>
        <property name="serviceName" value="AccountService"/>
        <property name="portName" value="AccountServiceImplPort"/>
    </bean>
</beans>
在這裡用 JaxWsPortProxyFactoryBean 去建立 WebService Client,因為是同一個 Web Content 所以用 scope="request" 延遲建立。


HomeController.java
package test.jaxws.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.ModelAndView;

import test.jaxws.webservice.AccountService;

@Controller
public class HomeController {

    @Autowired
    private WebApplicationContext appContent;

    @RequestMapping("/")
    public ModelAndView index() {
        AccountService service = appContent.getBean("accountWebService", AccountService.class);
        String[] users = service.getAccounts("user");

        ModelAndView model = new ModelAndView("index");
        model.addObject("users", users);

        return model;
    }
}
接下來就是連接的 Client,這邊用 WebApplicationContext 延遲取得 AccountService,在 http://localhost:8080/test-jaxws-rt/service/AccountService 就可以看到 service 的主頁面。


參考資料:
JAX-WS + Spring integration example
22. Remoting and web services using Spring
2012-02-01 15:18

[VBA] Office Excel 2003 連接 Web Service

參考來源:Excel 透過 VBA 呼叫 Web Service

首先需要加入 Soap Library
在『Visual Basic編輯器』中:工具 -> 設定引用項目 -> 勾選『Microsoft Office Soap Type Library v3.0』

'連接 EpgSoap
Set EpgSoap = New SoapClient30
EpgSoap.MSSoapInit ("http://webservices.daehosting.com/services/isbnservice.wso?WSDL")

'請求 EpgSoap
MsgBox EpgSoap.IsValidISBN10("986-7889-18-5")

'關閉 EpgSoap
Set EpgSoap = Nothing
2012-02-01 14:05

[PHP] 用 SoapServer 建立 Microsoft Office Research Service

這裡我用 Webservice Helper 來處理 WSDL 的問題,所以範例也是 Base 在 Webservice Helper 上面,在開始前請檢查是否有 php_soapphp_xsl 這兩個套件。


為了相容於 Research Service 的名稱空間,在輸出入的定義上需要在包一層資料類型,所以需要以下的類型定義:
StatusResponse.class.php
<?php
/**
 * Return object to Status method
 */
class StatusResponse {
    /** @var string */
    public $StatusResult;
}

Registration.class.php
<?php
/**
 * Input object to Registration method
 */
class Registration {
    /** @var string */
    public $registrationXml;
}

RegistrationResponse.class.php
<?php
/**
 * Return object to Registration method
 */
class RegistrationResponse {
    /** @var string */
    public $RegistrationResult;
}

Query.class.php
<?php
/**
 * Input object to Query method
 */
class Query {
    /** @var string */
    public $queryXml;
}

QueryResponse.class.php
<?php
/**
 * Return object to Query method
 */
class QueryResponse {
    /** @var string */
    public $QueryResult;
}


接著是建立 Web Service 的 Method,主要定義 Registration 跟 Query 這兩個 Method 就可以了,Registration 是用在新增 Research Service 到 Research Pane 時會呼叫的 Method,主要是提供 Research Pane 所需要的 Service 資訊。
而 Query 則是真正再處理資料查詢的 Method,而 QueryResponse 中的 domain 及 QueryId 必需與 QueryXml 中所給的相同,不然 Client 端會無法辨識回傳結果。
<?php
/**
 * Microsoft Office Research Service
 *
 */

class MsOfficeResearch {

    /**
     * Entry point to test if server is alive. Will return 'ONLINE' or 'OFFLINE'
     * @param void
     * @return StatusResponse
     */
    function Status() {
        $result = new StatusResponse;
        $result->StatusResult = 'ONLINE';
        return $result;
    }


    /**
     * Basic registration entry point
     * @param Registration $registrationXml
     * @return RegistrationResponse
     */
    public function Registration($registrationXml) {
//      debugDump('registrationXml: '.$request->registrationXml);

        $dom = new DOMDocument();

        $proUpdate = $dom->createElementNS("urn:Microsoft.Search.Registration.Response",'ProviderUpdate');
        $proUpdate->appendChild( new DOMElement('Status',"SUCCESS") );

        $providers = $proUpdate->appendChild( new DOMElement('Providers') );
        $provider = $providers->appendChild( new DOMElement('Provider') );
        $provider->appendChild( new DOMElement('Id',"{62E1D68D-E1C4-4CC5-9D0C-D4B7999C4B77}") );
        $provider->appendChild( new DOMElement('Name',"Research Service") );
        $provider->appendChild( new DOMElement('QueryPath',"http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']) );
        $provider->appendChild( new DOMElement('RegistrationPath',"http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']) );
        $provider->appendChild( new DOMElement('Type',"SOAP") );
        $provider->appendChild( new DOMElement('Revision',"1") );

        $services = $provider->appendChild( new DOMElement('Services') );
        $service = $services->appendChild( new DOMElement('Service') );
        $service->appendChild( new DOMElement('Id',"{0297CD20-047F-4256-0104-000004040001}") );
        $service->appendChild( new DOMElement('Name',"Research Service Name") );
        $service->appendChild( new DOMElement('Description',"Research Service Description") );
        $service->appendChild( new DOMElement('Copyright',"") );
        $service->appendChild( new DOMElement('Display',"On") );
        $service->appendChild( new DOMElement('Category',"RESEARCH_GENERAL") );

        $response = new RegistrationResponse;
        $response->RegistrationResult = $dom->saveXML($proUpdate);
        return $response;
    }



    /**
     * Basic entrypoint for Query
     * @param Query $queryXml
     * @return QueryResponse
     */
    function Query($queryXml) {
        if(is_object($queryXml)){ $queryXml = $queryXml->queryXml; }
//      debugDump('queryXml: '.$queryXml);

        /*解析請求的 XML*/
        $dom = new DOMDocument();
        $dom->loadXML($queryXml);
        $domain = $dom->getElementsByTagName('Query')->item(0)->getAttribute('domain');
        $queryId = $dom->getElementsByTagName('QueryId')->item(0)->nodeValue;

        /*建立返回的結構*/
        $packet = $dom->createElementNS("urn:Microsoft.Search.Response",'ResponsePacket');
        $packet->setAttribute('revision',"1");
        $response = $packet->appendChild( new DOMElement('Response') );
        $response->setAttribute('domain', $domain );
        $response->appendChild( new DOMElement('QueryId', $queryId) );
        $range = $response->appendChild( new DOMElement('Range') );
        $results = $range->appendChild( new DOMElement('Results') );
        $content = $results->appendChild( new DOMElement('Content',"","urn:Microsoft.Search.Response.Content") );

        /*請求查詢*/
        $status = "ERROR_NO_RESULTS_FOUND";
        $queryText = trim( $dom->getElementsByTagName('QueryText')->item(0)->nodeValue );
        if(!empty($queryText)){
//          debugDump($queryText);
            $line = $content->appendChild( new DOMElement('P') );
            $line->nodeValue = htmlspecialchars($queryText, ENT_QUOTES);
            $status = "SUCCESS";
        }

        $response->appendChild( new DOMElement('Status',$status) );

        /*設定回傳結構*/
        $response = new QueryResponse;
        $response->QueryResult = $dom->saveXML($packet);
        return $response;
    }

}


關於資料交換的 XML 格式如下:
Registration Response XML
<ProviderUpdate xmlns='urn:Microsoft.Search.Registration.Response'>
    <Status>SUCCESS</Status>
    <Providers><Provider>
        <Id>{88686849-2DD9-474d-9300-778E3336FA77}</Id>
        <Name>EpgTools</Name>
        <QueryPath>http://localhost/service.php</QueryPath>
        <RegistrationPath>http://localhost/service.php</RegistrationPath>
        <Type>SOAP</Type>
        <Revision>1</Revision>
        <Services><Service>
            <Id>63d351db-d12e-448b-8541-9f794e1ec977</Id>
            <Name>Research Service Name</Name>
            <Data>1031/1033/4</Data>
            <Description>Research Service Description</Description>
            <AboutPath>helpId:553713956</AboutPath>
            <Copyright>All content Copyright (c) 2003.</Copyright>
            <Display>On</Display>
            <Category>RESEARCH_GENERAL</Category>
            <OptionsPath></OptionsPath>
            <Parental>Unsupported</Parental>
        </Service></Services>
    </Provider></Providers>
</ProviderUpdate>

Query XML
<QueryPacket xmlns="urn:Microsoft.Search.Query" revision="1" build="(11.0.5606)">
    <Query domain="{6E3B8AA1-5131-403E-AEF3-E7AFC2E88557}">
        <QueryId>{5A4FD162-DB71-45BC-8721-F059D28947B3}</QueryId>
        <OriginatorId>{F6FF7BE0-F39C-4ddc-A7D0-09A4C6C647A5}</OriginatorId>
        <SupportedFormats>
            <Format revision="1">urn:Microsoft.Search.Response.Document:Document</Format>
            <Format revision="1">urn:Microsoft.Search.Response.Content:Content</Format>
            <Format revision="1">urn:Microsoft.Search.Response.Form:Form</Format>
        </SupportedFormats>
        <Context>
            <QueryText type="STRING" language="zh-tw">test</QueryText>
            <LanguagePreference>zh-tw</LanguagePreference>
            <Requery></Requery>
        </Context>
        <Range id="result"></Range>
        <OfficeContext xmlns="urn:Microsoft.Search.Query.Office.Context" revision="1">
            <UserPreferences>
                <ParentalControl>false</ParentalControl>
            </UserPreferences>
            <ServiceData>EWATW</ServiceData>
            <ApplicationContext>
                <Name>Microsoft Office</Name>
                <Version>(11.0.5606)</Version>
                <SystemInformation>
                    <SkuLanguage>zh-tw</SkuLanguage>
                    <LanguagePack>zh-tw</LanguagePack>
                    <InterfaceLanguage>zh-tw</InterfaceLanguage>
                    <Location>TW</Location>
                </SystemInformation>
            </ApplicationContext>
            <QueryLanguage>zh-tw</QueryLanguage>
            <KeyboardLanguage>zh-tw</KeyboardLanguage>
        </OfficeContext>
        <Keywords xmlns="urn:Microsoft.Search.Query.Office.Keywords" revision="1">
            <QueryText>test</QueryText>
            <Keyword>
                <Word>test</Word>
            </Keyword>
        </Keywords>
    </Query>
</QueryPacket>

Query Response XML
<ResponsePacket xmlns="urn:Microsoft.Search.Response" revision="1">
    <Response domain="{6e3b8aa1-5131-403e-aef3-e7afc2e88557}">
        <QueryId>{5A4FD162-DB71-45BC-8721-F059D28947B3}</QueryId>
        <Range><Results>
            <Content xmlns="urn:Microsoft.Search.Response.Content">
                <any />
            </Content>
        </Results></Range>
        <Status>SUCCESS</Status>
    </Response>
</ResponsePacket>


範例下載:
SearchService.zip


參考資料:
Serveur SOAP en PHP 5 pour "Microsoft Office Research Service"
The Definitive Hello World Custom Research Service Tutorial
Microsoft.Search.Response.Content Schema Documentation
2010-11-03 06:30

[PHP] SOAP - Webservice Helper

Webservice Helper 這個套件真是一個方便的好工具
它必須 Base 在 PHP SOAP 原生套件的上面
能夠透過 PHP 裡的註解自動產生 WSDL File

詳細的用法在他的範例中都介紹得很清楚