SOAP Headers are used in many WS-* standards (for an example WS-Security) to transmit information related to the QOS aspects of the service. So only the SOAP engines which process these QOS parameters are supposed to see these headers. Web Service Developer or Consumer need to care only what is in the SOAP payload (which is what inside the SOAP body element) and not SOAP headers.

But that is theory, In practice we see many places where people uses SOAP headers to transmit what they should have easily sent through SOAP Body. And many SOAP stacks also provide APIs to allow people to send/receive custom headers and standards like WSDL too provide ways to describe services which accept custom headers. So I thought it might worth to discuss how you describe custom headers in a service from a WSDL and how you can consume and serve in real world with SOAP stacks like Axis2/C and WSF/PHP.

In a WSDL 1.1 you first declare your headers in the binding section itself which is the section that is used to provide the messaging and transport protocol information.

  <binding name="RetHeaderBinding" type="tns:RetHeaderPortType">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
    <operation name="echoString">
      <soap:operation soapAction="http://soapinterop.org/" style="document"/>
      <input>
        <soap:body use="literal"/>
        <soap:header message="tns:Header1" part="Header1" use="literal"/>
        <soap:header message="tns:Header2" part="Header2" use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
        <soap:header message="tns:Header1" part="Header1" use="literal"/>
        <soap:header message="tns:Header2" part="Header2" use="literal"/>
      </output>
    </operation>
  </binding>

There you can see we have Header1 and Header2 in both in input and output of the operation. It refers to the message section with the specific part name that represent the header. Here is how message section will look like.

  <message name="Header1">
    <part name="Header1" element="types:Header1"/>
  </message>
  <message name="Header2">
    <part name="Header2" element="types:Header2"/>
  </message>

In this section it refers to the schema element that actually describe the element structure/schema. Here I put a string and an int in each header.

  <s:element name="Header1">
      <s:complexType>
        <s:sequence>
          <s:element name="string" type="s:string"/>
          <s:element name="int" type="s:int"/>
        </s:sequence>
      </s:complexType>
  </s:element>
  <s:element name="Header2">
      <s:complexType>
        <s:sequence>
          <s:element name="int" type="s:int"/>
          <s:element name="string" type="s:string"/>
        </s:sequence>
      </s:complexType>
  </s:element>

Here is a valid SOAP message for the WSDL description.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Header>
      <ns1:Header1 xmlns:ns1="http://soapinterop.org/xsd">
         <ns1:string>test1</ns1:string>
         <ns1:int>5</ns1:int>
      </ns1:Header1>
      <ns1:Header2 xmlns:ns1="http://soapinterop.org/xsd">
         <ns1:int>8</ns1:int>
         <ns1:string>test2</ns1:string>
      </ns1:Header2>
   </soapenv:Header>
   <soapenv:Body>
      My payload
   </soapenv:Body>
</soapenv:Envelope>

If you are using Apache Axis2/C WSDL2C Codegen tool (which is shipped with Axis2/Java) you will be able to directly use the API provided with the generated code to deal with custom headers. Here is how the client API is generated for the above WSDL.

         /**
          * auto generated method signature
          * for "echoString|http://ws.dimuthu.org/blog/headers" operation.
          *
          * @param _echoStringParam
          * @param _header1
          * @param _header2
          *
          * @param dp_header10 - output header
          * @param dp_header21 - output header
          * @return adb_echoStringReturn_t*
          */
         adb_echoStringReturn_t*
         axis2_stub_op_RetHeaderService_echoString( axis2_stub_t *stub, const axutil_env_t *env,
                                              adb_echoStringParam_t* _echoStringParam,
                                              adb_Header1E0_t* _header1,
                                              adb_Header2E1_t* _header2,
                                              adb_Header1E0_t** dp_header10 /* output header double ptr*/,
                                              adb_Header2E1_t** dp_header21 /* output header double ptr*/)

Note that the first parameter is the environment variable passed in every function in Axis2/c. The second parameters is the payload. Input headers (input headers for server side) are in 3rd and 4th parameters, you can build them similar to how you build payload object. 5th and 6th are for the output headers which you only need to provide some pointers and the generated code will build you the objects from the response data.

You service will also have a similar API. There you have to create adb objects for output headers (like you build output payload) and assign the pointer to these output header double pointers.

             adb_Header2_t* arg_Header1 = adb_Header1_create(env);
             adb_Header1_set_string(arg_Header1, env, "I m response Header 1");
             adb_Header1_set_int(arg_Header1, env, 1);
             adb_Header10_t* _header1 = adb_Header10_create(env);
             adb_Header10_set_Header1(_header1, env, arg_Header1);
             *dp_header10 = _header1;

Now how about PHP, can you provide the same API form the PHP as well. Yes, PHP has references similar to C pointers. WSF/PHP already have the custom headers support in the SVN and to be released with the 2.0.
Here is the generated API for the service business logic for the above WSDL.

/**
 * Service function echoString
 * @param string $input
 * @param object of Header1 $header_in0 input header
 * @param object of Header2 $header_in1 input header
 * @param reference object of Header1 $header_out0 output header
 * @param reference object of Header2 $header_out1 output header
 * @return string
 */
function echoString($input, $header_in0, $header_in1, &$header_out0, &$header_out1) {

...
}

These tool provides APIs to consume and provide data through custom headers in same easiness you send the data in the payload. Anyway it is really recommended to not to use custom SOAP headers in your Service and try to avoid them as much as possible. Whenever you think there is valid reason to put a custom header (it is relevant to QOS aspects), just try to integrate the header processing logic to the SOAP engine. In Axis2 C or Java you can write Axis2 modules for that. That will really speed up the processing of your service and give consumers a well-designed API.

In a valid XML you can only have text and you can not have binary characters. SOAP which in fact an XML is also having this issue. So it is not staright forward to send binary data in SOAP. But with WSO2 WSF/PHP 1.3.2 It is really easy to send binaries. Check the online WSF/PHP Mtom Sample sources to understand the few lines that needed to send and serve binaries.

To understand what is happening inside WSF/PHP to send your message we will first take a simple demo. To understand the messages I will be sending text data rather than binary so you can clearly see how your data goes through the wire.

Here is my code to send the binary. (in fact a test in this percular case)

<?php
$requestPayloadString = <<<XML
<ns1:upload xmlns:ns1="http://php.axis2.org/samples/mtom">
  <ns1:fileName>test.txt</ns1:fileName>
  <ns1:image xmlmime:contentType="text/txt" xmlns:xmlmime="http://www.w3.org/2004/06/xmlmime">

    <xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="cid:myid1"></xop:Include>
  </ns1:image>
</ns1:upload>
XML;

try {
    $f = file_get_contents("test.txt");

    $requestMessage = new WSMessage($requestPayloadString,
        array("to" => "http://localhost/my_mtom_service.php",
        "attachments" => array("myid1" => $f)));

    $client = new WSClient(array("useMTOM" => "FALSE"));

    $responseMessage = $client->request($requestMessage);

    echo $responseMessage->str;

} catch (Exception $e) {
    if ($e instanceof WSFault) {

        printf("Soap Fault: %s\\n", $e->Reason);
    } else {

        printf("Message = %s\\n",$e->getMessage());
    }
}

?>

And test.txt has the following text

TEXT as BINARY

In here the ‘xop:include’ element is where you going to send either the binary data or some reference to binary data.
“useMTOM” => FALSE
Note that in there in the “useMTOM” option for the WSClient I have given the value FALSE. That mean don’t use the MTOM(Message Transmission Optimization Mechanism) to serialize the message.There the generated soap message (over HTTP) will look like this,

POST /samples/mtom/mtom_upload_service.php HTTP/1.1
User-Agent: Axis2C/1.5.0
Content-Length: 404
Content-Type: application/soap+xml;charset=UTF-8
Host: 127.0.0.1:8080

<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
   <soapenv:Header/>
   <soapenv:Body>
      <ns1:upload xmlns:ns1="http://php.axis2.org/samples/mtom">
         <ns1:fileName>test.jpg</ns1:fileName>
         <ns1:image xmlmime:contentType="text/txt" xmlns:xmlmime="http://www.w3.org/2004/06/xmlmime">VEVYVCBhcyBCSU5BUlkK</ns1:image>
      </ns1:upload>
   </soapenv:Body>
</soapenv:Envelope>

The value “VEVYVCBhcyBCSU5BUlkK” is base64 encoded value for the “TEXT as BINARY” text.

<?php
echo base64_encode("TEXT as BINARY");
?>

In fact it is a standard that map each 6 bit of your binary file to an 8 bit (valid character to send through XML), so you can send the encoded value in the soap message. But no doubt you see the drawback. The encoded value is 4/3rd of the original data size. So this is not the most optimized way to send binary over SOAP.
“useMTOM” => TRUE
Setting this option TRUE will generate the following soap message over http.

POST /samples/mtom/mtom_upload_service.php HTTP/1.1
User-Agent: Axis2C/1.5.0
Transfer-Encoding: chunked
Content-Type: multipart/related; boundary=MIMEBoundary594038a6-6d95-1dd1-2b72-00197e732d4d; type="application/xop+xml"; start="<0.594038e2-6d95-1dd1-2b73-00197e732d4d@apache.org>"; start-info="application/soap+xml"; charset="UTF-8"
Host: 127.0.0.1:8080

32
--MIMEBoundary594038a6-6d95-1dd1-2b72-00197e732d4d
2

b2
content-transfer-encoding: binary
content-id: <0.594038e2-6d95-1dd1-2b73-00197e732d4d@apache.org>content-type: application/xop+xml;charset=UTF-8;type="application/soap+xml";21ff
   <soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
      <soapenv:Header/>
      <soapenv:Body>
         <ns1:upload xmlns:ns1="http://php.axis2.org/samples/mtom">
            <ns1:fileName>test.jpg</ns1:fileName>
            <ns1:image xmlmime:contentType="text/txt" xmlns:xmlmime="http://www.w3.org/2004/06/xmlmime">
               <xop:Include href="cid:1.594037a2-6d95-1dd1-2b71-00197e732d4d@apache.org" xmlns:xop="http://www.w3.org/2004/08/xop/include"/>
            </ns1:image>
         </ns1:upload>
      </soapenv:Body>
   </soapenv:Envelope>32--MIMEBoundary594038a6-6d95-1dd1-2b72-00197e732d4d27econtent-transfer-encoding: binarycontent-id:
   <1.594037a2-6d95-1dd1-2b71-00197e732d4d@apache.org>content-type: text/txtfTEXT as BINARY234--MIMEBoundary594038a6-6d95-1dd1-2b72-00197e732d4d--0

Note that in the last line you see the data you send as it is. Here the binary is sent as an attachment and the element which suppose to keep the binary data refers to the attachment using the content id (check “href” attribute). It is obvious for a large binary data this is really optimized way of sending binaries.

“useMTOM” => “SWA”

WSF/PHP supports 1.3.2 supports SWA (Soap with Attachment) as well. you can enable this by setting “useMTOM” => “swa”. It is also send the binary as an attachment.  But it is not a recommended approach since you can’t secure the message when using SWA, because it can not be represented in the XML infoset. Here is the message generated enabling the “SWA”,

POST /samples/mtom/mtom_upload_service.php HTTP/1.1
User-Agent: Axis2C/1.5.0
Transfer-Encoding: chunked
Content-Type: multipart/related; boundary=MIMEBoundarybc636bdc-6d96-1dd1-31c0-00197e732d4d; type="application/xop+xml"; start="<0.bc636c40-6d96-1dd1-31c1-00197e732d4d@apache.org>"; start-info="application/soap+xml"; charset="UTF-8"
Host: 127.0.0.1:8080

32
--MIMEBoundarybc636bdc-6d96-1dd1-31c0-00197e732d4d
2

b2
content-transfer-encoding: binary
content-id: <0.bc636c40-6d96-1dd1-31c1-00197e732d4d@apache.org>content-type: application/xop+xml;charset=UTF-8;type="application/soap+xml";21b3
   <soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
      <soapenv:Header/>
      <soapenv:Body>
         <ns1:upload xmlns:ns1="http://php.axis2.org/samples/mtom">
            <ns1:fileName>test.jpg</ns1:fileName>
            <ns1:image xmlmime:contentType="text/txt" xmlns:xmlmime="http://www.w3.org/2004/06/xmlmime">1.bc6368a8-6d96-1dd1-31bf-00197e732d4d@apache.org</ns1:image>
         </ns1:upload>
      </soapenv:Body>
   </soapenv:Envelope>

32
--MIMEBoundarybc636bdc-6d96-1dd1-31c0-00197e732d4d
2

7e
content-transfer-encoding: binary
content-id:
   <1.bc6368a8-6d96-1dd1-31bf-00197e732d4d@apache.org>content-type: text/txtfTEXT as BINARY234--MIMEBoundarybc636bdc-6d96-1dd1-31c0-00197e732d4d--0

In a WSDL, XML Schema is the section where it define the message format for each operations, which eventually become the real API that users are interested. And it is the most tricky part of the WSDL. Nowadays there are many tools that you can design and use WSDLs without any needs in knowing the meaning of a single line of the WSDL. But there are situations that you may find it is better you have some knowledge in XML Schema section and in WSDL overall.
For this post I m taking a simple example of use of nillable=”true” and minOccurs=”0″. Take the following example.

<xs:element name="myelements">
  <xs:complexType>
    <xs:sequence>
     <xs:element name="nonboth" type="xs:string"/>
      <xs:element minOccurs="0" name="minzero" type="xs:int"/>
      <xs:element name="nilint" nillable="true" type="xs:int"/>
      <xs:element name="nilstring" nillable="true" type="xs:string"/>
      <xs:element minOccurs="0" name="minzeronil" nillable="true" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
</xs:element>

Just ignore the meaning of what nillable and minOccurs attributes for now. You can safely say the following XML is valid for the above Schema.

<myelements>
  <nonboth>i can't be either nil nor skipped<nonboth>
  <minzero>3<minzero>
  <nilint><nilint>
  <nilstring>i can have null, but i cant skipeed</nilstring>
  <minzeronil>i can be skipped and have the nil value<minzeronil>
</myelements>

Take the first element ‘nonboth’ in the schema, It has not any minOccurs or nillable attribute. By default minOccurs equal to 1 and nillable equal to false. That mean it can’t have nil value nor it can not be removed from the xml.

Is that making an element nil and removing the element from the XML is same? No. Take the second element in the schema ‘minzerostring’. There you have minOccurs =”0″ but there are no nillable=”true”, mean it is non-nillable. The idea is whenever you don’t want that element in your xml, you can’t have the element keeping empty like

  <minzero xsi:nil="true"><minzero>

But you can remove the whole element from the XML (since it is minOccurs=0).

The opposite of the above scenario is nillable=”true” but minOccurrs != 0. Check the ‘nilint’ element in the schema. There you can’t skip the element ‘nilint’, you have to have the element <nilint/> but it can hold a nil value.

  <nilint xsi:nil="true"></nilint>

or simply

  <nilint xsi:nil="true"/>

Note that the correct way to declare the nil element is,

  <nilint xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>

You can understand why when we look at the third element ‘nilstring’. Say you set message the following element

  <nilstring></nilstring>

You can say that this is not nil, this is an empty string. In fact an empty string is nil in some other language, But if we take XML Schema as a language, then for someone to be nil, it have to have the xsi:nil attribute set to “true” or “1″.

So going back to the ‘minzero’ which is non-nillable, by theory you should be able to write the following xml,

  <minzero/>

Since you don’t have that xsi:nil=”1″ this is not a nil value, so the condition nillable=”false” condition is preserved. But unlike for string when you set an empty element for an integer, it doesn’t sound correct. So in practice whenever some schema says non-nillable you should set some valid value.

The last one is ‘minzeronil’ element which is both nillable=”true” and minOccurs=”0″. Whenever you don’t need to set a value for this element, you have the choice of either skipping the element or setting the value of the element to nil. It is obvious rather than setting a nil value it is better you just skip the element to make the XML shorter. This is really needed specially in web services where you need the payload to be minimum as much as possible.

Say you have to prepare the XML and you don’t have valid values for any of the element. So this can be the optimum XML you can create.

<myelements xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
  <nonboth><nonboth>
  <nilint xsi:nil="1"/>
  <nilstring xsi:nil="1">
</myelements>

Read this nice article in developer works on nillable=”true” and minOccurs=”0″ for more.

With WSF/PHP 1.3.2 you can use following basic features in WS-Security.

Feature Purpose Array based Security Policy Options ($sec_policies) Security Token Options ($sec_token_options)
UsernameToken Authentication array(“useUsernameToken” => TRUE) array(“user” => “your_username”,
“password” => “your_password”,
“passwordType” => “Digest”); //Digest/Plain
Timestamp Avoid Interception,Replay Attack (use with signing) array(“includeTimeStamp” => TRUE); array(“ttl” => 100)
Signing Non-Repudiation, Verify Server/Clients identity array(“sign” => TRUE,
“algorithmSuite” => “Basic256Rsa15″,
“securityTokenReference” => “KeyIdentifier”)
array(“privateKey” => $pvt_key,
certificate” => $cert)
Encryption privacy array(“encrypt” => TRUE,
“algorithmSuite” => “Basic256Rsa15″,
“securityTokenReference” => “IssuerSerial”);
array(“privateKey” => $pvt_key,
“receiverCertificate” => $pub_key))

You can build the WSPolicy and WSSecurityToken with an any mix of above features. For some scenarios you may only need timestamp with signing where as some other critical scenarios you want signing, encryption, username token and timestamp.

Here is how you build the WSSPolicy and WSSecurityToken classes using the above mentioned $sec_policies and $sec_token_options.

$policy = new WSPolicy(array("security"=> $sec_policies));

$sec_token = new WSSecurityToken($sec_token_options);

$svr = new WSService(array("policy" => $policy,
                           "securityToken" => $sec_token,
                            "actions" => $your_actions,
                           "operations" => $your_operations));

$svr->reply();

Similarly you can use the WSPolicy and WSSecurity with WSClient for the client side security. See the samples WS-Security demos and WS-Security sources.

This blog is about some of the security features shipped with WSF/PHP 1.3.2. With the next release of WSF/PHP you will have more features related to WS-Security like WS-SecureConversations, WS-Trust and use of KeyStores for encryption and signing.

If you have a shared hosting environment you will find it is not straight forward to install WSO2 WSF/PHP + PHP DataServices Library since you have very limited authority on your environment. Here in this post, I’m explaining the steps I followed in setting up my custom PHP, WSF/PHP Extension and DataServices library for http://ws.dimuthu.org (This is a shared hosting environment from dreamhost based on LAMP stack) which I’m using to expose my blog as a WebService.

Prerequisites:

  1. You should be able to run PHP using mod_fastcgi (not using mod_php) in Apache. That allows you to configure your own php environment.
  2. The server configurations managed by the hosting service should allow you to handle .php extension from your own  CGI executable (here it is php.cgi), If not, you can still try with setting some other extension (say .phq or .qhq, anything not .php) to be handled by your php-cgi. Anyway if your hosting service allow you to run CGI then most probably you have this permission.
  3. You should have the access to the shell (using SSH) and should be able to use development libraries(libxml, libxsl, libopenssl) + build tools (make)
  4. There can be some other restrictions that your hosting service has put on you, that I have not experienced with my hosting service. So please check your hosting service support and documentation for possibility of building your own PHP environment.

Install PHP:

I have extracted out most of the steps from dreamhost documentation on custom php.ini.

  1. Download PHP and compile. You should at least enable –enable-fastcgi –enable-force-cgi-redirect in the configuration.
    ./configure --enable-fastcgi --enable-force-cgi-redirect --with-xsl --with-openssl --prefix=`pwd`/dist
    make
    make install

    Note: from here on, php source directory is referred as <php_source_directory>
    I have installed the php to <php_source_directory>/dist since we mostly don’t have permission to install it in to default location. (/usr/local). From here on I will be referring php installation directory as <php_install_dir>

  2. create a directory called ‘cgi-bin’ to your web document root directory(<document_root_directory>) and copy the <php_source_directory>/dist/bin/php-cgi in to that. (before php version 5.2.1 there is no php-cgi generated, you can just copy the php to the cgi-bin directory). And rename the php-cgi to php.cgi.
    cp <php_install_direcory>/bin/php-cgi <document_root_directory>/cgi-bin/php.cgi
  3. copy <php_source_direcory/php.ini-dist to the cgi-bin directory and rename it as php.ini, this can be used to configure your php environment.
    cp <php_source_directory/php.ini-dist <document_root_directory>/cgi-bin/php.ini
  4. Create .htaccess file in the cgi-bin directory to allow the access to php-cgi and php.ini files.
    cat << EOF ><document_root_directory>/cgi-bin/.htaccess
    Options +ExecCGI -Indexes +FollowSymLinks
    <FilesMatch "php(.?)\\.(cgi|ini)$">
    Order Deny,Allow
    Deny from All
    Allow from env=REDIRECT_STATUS
    </FilesMatch>
    
    EOF
  5. Create .htaccess file in the root directory redirect all the requests for .php extensions to go through our php-cgi.
    cat << EOF > <document_root_directory>/.htaccess
    AddHandler php-cgi .php
    Action php-cgi /cgi-bin/php.cgi
    EOF

    If your hosting service doesn’t allow to handle .php just rename the end of the first line to some other extension.

  6. Check the necessary permissions levels in files.
    chmod 644 <document_root_directory>/.htaccess
    chmod 755 <document_root_directory>/cgi-bin
    chmod 644 <document_root_directory>/cgi-bin/.htaccess
    chmod 644 <document_root_directory>/cgi-bin/php.ini
    chmod 755 <document_root_directory>/cgi-bin/php.cgi

That’s it. Now your .php files should be run on your own php environment. Just to check whether it is working or not create a phpinfo() file.

cat <<EOF > phpinfo.php
<?php
phpinfo();
?>
EOF

And go to the http://yourdoman.com/phpinfo.php and verify that it is your custom php environment .

Installing WSF/PHP

  1. Download WSF/PHP latest version and extract it to any of your preferred directory(<wsf_php_source_directory>).
  2. Set the PATH environment variable to search for your custom php installation first.
  3. export PATH=<php_install_dir>/bin:$PATH
  4. From the same shell go to the <wsf_php_source_directory> and compile it.
    ./configure
    make
    make install
  5. Now check where you have installed wsf/php. it is in fact the extension dir shown by the php-config command.
    php-config --extension-dir

    I will refer this directory as <php_extension_directory>

  6. Open the <document_root_directory>/cgi-bin/php.ini an set the extension directory, enable wsf extension and set the include path to the wsf scripts directory.cat <<EOF >> <wsf_php_source_directory>/cgi-bin/php.ini
    cat <<EOF >> <wsf_php_source_directory>/cgi-bin/php.ini
    extension_dir=<php_extension_directory>
    extension=wsf.so
    include_path=".:<wsf_php_source_directory>/scripts"
    
    EOF

    If you found that you can only put relative directories in your php.ini (that happens when you don’t even have the read access to your root directory ‘/’), follow the workaround mentioned in here, http://phpwebservices.blogspot.com/2008/08/installing-wsfphp-in-third-party.html

That’s all. Just check the phpinfo (created earlier section) whether your configurations and the wsf extension are loaded correctly. In order to test the functionality copy the wsf/php samples to the web document root and access them from the browser. Go to each sample and verify they function as expected.

cp /samples <wsf_php_source_directory>/ -R

And go to http://yourdomain.com/samples and click on each samples. If this not working most probably your localhost to ip mapping is wrong, most probably this problem is there in your shared host. Just go to the samples directory and change the endpoint to the domain name in place of ‘localhost’ in each client and try it again.

Installing PHP DataServices:

  1. Download and unpack PHP DataServices. <php_data_services_directory>
  2. Update the include_path entry in php.ini to DataServices libraries directory as well. So the <wsf_php_source_directory>/cgi-bin/php.ini  file now has the following entry.
    include_path=".:<wsf_php_source_directory>/scripts:<php_data_services_directory>/lib"

That’ all for the DataService installation. Please read <php_data_services_directory>/README to run the samples. Anyway if you are sure you did followed these simple steps correctly, go on with your DataService  development. Specially if you have wordpress database, you may start straight away with the making wordpress a DataService.

When you are developing a Web Service, you have to think about the security aspects of your service seriously. When it comes to security in web services you have two basic choices.

  1. Transport level security – Just SOAP over HTTPS
  2. Message level security – WS-Security

See my previous blog comparing Transport level and Message level security.

If you are satisfied with the security provided by using just ‘SOAP over HTTPS’, you can get the work done by configuring your server (Apache or IIS) to enable ssl. See http://www.onlamp.com/pub/a/onlamp/2008/03/04/step-by-step-configuring-ssl-under-apache.html for an step by step guide for configure SSL in your Apache server.

If you want message level security for your application, just use WS-Security. With WSF/PHP it is even easier to implement than SOAP over HTTPS method, because you can provide the certificates programatically in PHP and no need to do further configuration.

WSF/PHP provides you two classes in line with WSService to implement an API to provide WS-Security.

  1. WSPolicy -Let you provide rules that the engine need to follow in securing the message. E.g.
    $policy = new WSPolicy(array("security"=> array("encrypt" => TRUE,
                        "algorithmSuite" => "Basic256Rsa15",
                        "securityTokenReference" => "IssuerSerial")));

    In fact you can load policies from an xml which adheres to the WS-SecurityPolicy specification.

  2. WSSecurityToken – Keeps the security tokens like certificates, keys, username, passwords which would be used when applying the rules specified in the policy. E.g.
    $sec_token = new WSSecurityToken(array("privateKey" => $pvt_key,
                                           "receiverCertificate" => $pub_key));

You can see the WS-Security in action on live from http://labs.wso2.org/wsf/php/samples/security/ and security demo ource codes.

Transport Level Security Message Level Security
Secures point to point communication.

E.g: Your browser to Apache server

Secures end to end to end communication.

E.g. Sales Order Request application to Database updating application

Not transparent thorough multiple transport protocols, Transparent through any number of transports since it is handled at an above layer
Cannot specify different part of the message to secured idifferently Can specify which part to sign, which part to encrypt in the message, Specially useful when you have a large message and you really want to secure a small portion.
Relatively easy to attack. Relatively difficult to attack. Since the unsecured path in the message flow is minimum,
In Web Services we found this is followed by transmitting SOAP over HTTPS. In Web Services you can follow message level security by adhering to WS-Security specification.

You can find a descriptive post about the Transport Level security vs Message Level Security on http://www.xyzws.com/scdjws.do?cat=scdjws&smenu=WSGEN&article=4.

With PHP DataServices it is just a matter of putting a little configuration php file to make your database available as a web service. I only needed few minutes to make a simple web service from my blog after figuring out my wordpress database structure, http://wpbits.wordpress.com/2007/08/08/a-look-inside-the-wordpress-database/. In this guide, I m exposing the title, date and the content of each of my blog for my service, But you can extend this more the way you prefer.

  1. Download and install WSF/PHP 1.3.2 and PHP Data Services Library. If WSF/PHP 2.0 released by the time you are reading this, (it is to be released in this week), you only need to install WSF/PHP, since the DataServices library is packed with WSF/PHP from 2.0.
  2. Drop the following file (Say WordpressService.php) in to any of your web server document directories.
    <?php
    
    // Make sure you put the DataService.php in your include path
    require_once("wso2/DataServices/DataService.php");
    
    // database configuraitons
    // you have to set your database configurations in here..
    // These entries can be copy and past from the wp-config.php in your wordpress installation
    
    $config = array(
    "db" => "mysql",
    "username"=>DB_USER,
    "password"=> DB_PASSWORD,
    "dbname"=>DB_NAME,
    "dbhost"=>DB_HOST);
    // output format, plese check the API from http://wso2.org/wiki/display/wsfphp/API+for+Data+Services+Revised
    $outputFormat = array("resultElement" => "Posts",
    "rowElement" => "post",
    "elements" => array( "title" => "post_title",
    "content" => "post_content",
    "date" => "post_date"));
    
    // sql statment to execute, note that I assume the table prefix is wp_ (so the table name is wp_posts)
    // just check $table_prefix variable in the wp-config.php of your wordpress installation
    $sql="select post_title, post_content, post_date from wp_posts where post_status='published'";
    
    // operations is consist of inputFormat (optional), outputFormat(required), sql(sql), input_mapping(optional)
    $operations = array("getPosts"=>array("outputFormat"=>$outputFormat, "sql"=>$sql));
    $my_data_service = new DataService(array("config"=>$config,"operations"=>$operations));
    
    $my_data_service->reply();
    ?>
  3. It is all. Just access the above file from a web browser, you see your service is hosted. Here is the endpoint for my service. http://ws.dimuthu.org/blog/WordpressService.php. Since I m using WSF/PHP latest svn, I m able to retrieve the wsdl automatically from http://ws.dimuthu.org/blog/WordpressService.php?wsdl. Please wait for WSF/PHP 2.0 release for ?wsdl feature.
  4. To verify whether your service deployed correctly, you may need to write a simple test client. Yea I too wrote one. Since I have the wsdl generated, I just needed to generate the code for the client from the wsdl using wsdl2php tool. There is an online version of the tool in the wsf/php demo site. Here is the generated code for my client, http://labs.wso2.org/wsf/php/wsdl2phptool.php?wsdl_url=http%3A%2F%2Fws.dimuthu.org%2Fblog%2FWordpressService.php%3Fwsdl. I added the following code to the TODO section in handling response,
        if(is_array($response->post)) {
            foreach($response->post as $post) {
    
                echo "<h2>".$post->title . " - ".$post->date."</h2>";
                echo "<p>";
                echo $post->content;
                echo "</p>";
    
                echo "<hr/>";
            }
        }

    Check my Web Service client for the above service here, http://ws.dimuthu.org/blog/WordpressClient.php.

Now I can call for my blog from any web service enabled platform.

We use the term ‘DataService’ when we expose a database as a web service. WSO2 has a separate product for the DataService space which was earlier shipped bundled with WSAS. There you write a configuration xml explaining what data to be exposed in your database and drop it in the WSAS java app server and you get the DataServices deployed without much effort. Couple of months ago we published PHP DataServices library that allow you do deploy Your DataService within a popular LAMP or WAMP stack. With the WSF/PHP 2.0.0 release, you can have the PHP DataService library bundled with the release, hopefully within next couple of weeks.

One of the main problem DataService developers face is how to design an elegant web service API that will provide the requested information from a row database straightaway. Because when we normalize a database, the very closely related data are spread in different tables just keeping the association with foreign keys. So in order to retrieve the useful information database developers have to write multiple queries to the database. Is that same with DataServices?, Do you have to write multiple web service requests to return some useful data?

No. You don’t. You have Nested Queries to save you. With nested queries you can configure your DataServices, so all the relevent information can be extracted and send as a single SOAP message. I will take a demonstration to explain this.

Say you are working for a company that manufacture or sell electrical equipments, say TVs. And your boss is asking you to prepare a whole list of customers who ordered good from them. He want order information (like order date, status of the order) plus the name of the customers in the list.

Here is the table structure that you have.
Orders

ORDERNUMBER ORDERDATE STATUS CUSTOMERID

Customers

CUSTOMERID CUSTOMERNAME

From this you have to find a list of orders with order date, status (available in Orders table) and ordered customer information available in Customers table. As you may seen you have to query both the tables to get the required data.

First you have to decide a service operation that you going to expose as a web service. Operation is uniquely identified by its name. We will give our operation the name “getOrders”. In fact in our PHP Data Service API we treat a Operation same as a query. In general operation/query has following properties associated with it. Note for the complete API please check this Oxygen Tank Page.
Properties of Data Service Operation/Query

inputFormat This is the map of input element or input parameter to the corresponding type. For our operation there is no input. So we are not setting it here.
outputFormat This is a little tricky option. You have to set several configuration under this.

resultElement The name of the wrapper response element. We will give the name “Orders” here.
rowElement The name we give to wrap the each row of the result. If we give this value something like “Order“, then the result xml will be like,

<orders>
  <order>
    order 1 details
  </order>
  <order>
    next order details
  </order>

  <order>
    etc
  </order>
</orders>
elements The name of elements that is going to be in the response XML map to the real names of columns in the actual query result.
For an example if I give this the following value.

array("Order_number" => "ORDERNUMBER",
      "Order_date" => "ORDERDATE",
      "Status" => "STATUS");

The portion of the response will be like this,

<order>
  <Order_number>
    3
  </Order_number>
  <Order_date>
    2008-06-09
  </Order_date>

  <Status>
    Shipped
  </Status>
</order>
queries Array of queries, (The correct term is nested queries). So here you can set the response to be the result of another query. In fact in order to retrieve the customer name for the corresponding order we are going to write a nested query. Just think a operation that take the customerId as a parameter and return the customer data.

getCustomer(customerId)

You have to build this operation/query from the stat as we are just building the ‘getOrders’ query

attributes, texts, elementsOrder Some other configuration options. we are not using it in here, but by the name you can have an idea what they are for.
sql The standard sql query corresponding to the operation. In our case it is

SELECT o.ORDERNUMBER,o.ORDERDATE, o.STATUS,o.CUSTOMERNUMBER FROM ds.Orders o
inputMapping Finally this describe how the names of the input elements are mapped to the names of input parameters. Since ‘getOrders’ operation doesn’t have input parameters, this is not going to be used in this particular situation.

So as I described in the queries section you have to write the whole thing for the getCustomer operation as well.

$getCustomer = array(
    "inputFormat" => array("customerNumber" => "INTEGER"),
    "outputFormat" => array(

            "resultElement" => "Customers",
            "rowElement" => "Customer",
            "elements" => array("name" => "CUSTOMERNAME")
            ),
    "sql" => "select c.CUSTOMERNAME from ds.Customers c where c.CUSTOMERNUMBER = ?",
    "inputMapping" => array("CUSTOMERNUMBER" => "customerNumber")

    );

And here is the getOrders operation/query that actually use the getCustomer as a part of it.

$getOrders = array(
    "outputFormat" => array(
        "resultElement" => "Orders",
        "rowElement" => "Order",
        "elements" =>  array(

            "Order_number" => "ORDERNUMBER",
            "Order_date" => "ORDERDATE",
            "Status" => "STATUS"),
         "queries" =>  array($getCustomer)
         ),
    "sql" => "select c.CUSTOMERNAME from ds.Customers c where c.CUSTOMERNUMBER = ?"
    );

Then we will to define the operations array which we are going feed to the DataService class. Note that now we have two operations getOrders and getCustomer. From that I used getCustomer to help the building of getOrders. And I don’t want to publish getCustomer as a separate operation. Simply the boss doesn’t ask for that operation :) . So my operation map is like this,

$operations = array("getOrders" => $getOrders);

This operation map will be given as a constructor argument of DataService class. If you are familiar with WSF/PHP WSService class, there is no much different between these two classes. You can give all the options available in the WSService class to the DataService class. Say you want to encrypt the messages, just use the “policy” and the “securityToken” options. So with DataService class you have the luxury of using WS-Security as well as WS-Reliable Messaging for free.

To finish this post I will put the complete code for the above scenario. You can check the same service option in action in the WSF/PHP Demo site (Try the demo4).

<?php
// include the DataService library.
// NOTE: this path is valid only from wsf/php 2.0.0
require_once("wso2/DataServices/DataService.php");

// set the configurations
$config = array("db" => "mysql",
   "username" => "dimuthu",
   "password" => "thiswillnotbeasecretanymore",
   "dbname" => "ds",
   "dbhost" => "localhost");

// getCustomer Query - this used by the getOrders query/operation
$getCustomer = array(
    "inputFormat" => array("customerNumber" => "INTEGER"),
    "outputFormat" => array(

            "resultElement" => "Customers",
            "rowElement" => "Customer",
            "elements" => array("name" => "CUSTOMERNAME")
            ),
    "sql" => "select c.CUSTOMERNAME from ds.Customers c where c.CUSTOMERNUMBER = ?",
    "inputMapping" => array("CUSTOMERNUMBER" => "customerNumber")

    );

// The operation/query getOrders - Read the complete blog to see what 
// each of these option stand for
$getOrders = array(
    "outputFormat" => array(
        "resultElement" => "Orders",
        "rowElement" => "Order",
        "elements" =>  array(

            "Order_number" => "ORDERNUMBER",
            "Order_date" => "ORDERDATE",
            "Status" => "STATUS"),
         "queries" =>  array($getCustomer)
         ),
    "sql" => "select c.CUSTOMERNAME from ds.Customers c where c.CUSTOMERNUMBER = ?"
    );

// operation array
$operations = array("getOrders" => $getOrders);

// create the DataService object and reply
$ds = new DataService(array("operations" => $operations,
          "config" => $config,
          "serviceName" => NextedQuerySample));

$ds->reply();

?>

© 2007 Dimuthu’s Blog | iKon Wordpress Theme by Windows Vista Administration | Powered by Wordpress