PHP SOAP server and .NET Client

On a previous post, it was the other way around a .NET SOAP server and a PHP client and this one is a PHP SOAP server talking to a .NET client. I am using a similar PHP SOAP Server output as before, but having to alter the return type to a complexType instead of a normal PHP SOAP server type.

The basic WSDL generation is very similar to the previous SOAP post, apart from parameter passed the Zend_Soap_AutoDiscover which is “Zend_Soap_Wsdl_Strategy_ArrayOfTypeComplex”, which creates the start of the newer return complexTypes.

if(isset($_GET['wsdl'])) {
    $autodiscover = new Zend_Soap_AutoDiscover('Zend_Soap_Wsdl_Strategy_ArrayOfTypeComplex');
    $autodiscover->setClass('QuoteOfTheDay');
    $autodiscover->handle();

here is the new return type for the function to return, it is a class that has public variable that is a string (you define as “” which sets up a string in PHP)

// the return class type
class theQuote {
// have a return value of type string
/** @var string */
  public $getTheQuote="";
}

and then just alter the phpDoc notation for the auto discovery with the Zend SOAP to create a return of theQuote class as above and alter the return variable to the class., below I have included the new WSDL output generated.

  /* phpdoc notation to return a complex type (a class) */
  /**
  * @return theQuote
  */
 function getQuote($quote) {
    $theQuoteR = new theQuote();
    /* just encase the string is in uppercase*/
    $symbol = strtolower($quote);
    /* if there is a quote for the day requested */
    if (isset($this->quotes[$quote])) {
      $theQuoteR->getTheQuote=$this->quotes[$quote];
      return $theQuoteR;
    } else {
      /* else error with default response*/
      $theQuoteR->getTheQuote=$this->quotes["monday"];
      return $theQuoteR;
    }
  }

here is the full source code for the PHP SOAP server

<?php
/* setup the including path for the zend library framework */
ini_set('include_path', '/usr/share/php/libzend-framework-php/');
 
//****************************************************
// Zend Framework 1.8
include_once 'Zend/Loader/Autoloader.php';
require_once "Zend/Soap/Server.php";
require_once "Zend/Soap/AutoDiscover.php";
$loader = Zend_Loader_Autoloader::getInstance();
$loader->setFallbackAutoloader(true);
$loader->suppressNotFoundWarnings(false);
//****************************************************
 
if(isset($_GET['wsdl'])) {
    $autodiscover = new Zend_Soap_AutoDiscover('Zend_Soap_Wsdl_Strategy_ArrayOfTypeComplex');
    $autodiscover->setClass('QuoteOfTheDay');
    $autodiscover->handle();
} else {
    $soap = new Zend_Soap_Server("http://localhost/projects/webservice/zend_soap_server_net.php?wsdl"); // this current file here
    $soap->setClass('QuoteOfTheDay');
    $soap->handle();
}
 
// the return class type
class theQuote {
// have a return value of type string
/** @var string */
  public $getTheQuote="";
}
 
class QuoteOfTheDay {
 
  /* the quotes to be used from within the function getQuote */
  private $quotes = array("monday" => "Monday's child is fair of face", "tuesday"=>"Tuesday's child is full of grace","wednesday" =>"Wednesday's child is full of woe",
  "thursday" =>"Thursday's child has far to go", "friday" => "Friday's child is loving and giving", "saturday" =>"Saturday's child works hard for a living",
  "sunday" =>"But the child who is born on the Sabbath Day - Is bonny and blithe and good and gay");  
 
  /* phpdoc notation to return a complex type (a class) */
  /**
  * @return theQuote
  */
 function getQuote($quote) {
    $theQuoteR = new theQuote();
    /* just encase the string is in uppercase*/
    $symbol = strtolower($quote);
    /* if there is a quote for the day requested */
    if (isset($this->quotes[$quote])) {
      $theQuoteR->getTheQuote=$this->quotes[$quote];
      return $theQuoteR;
    } else {
      /* else error with default response*/
      $theQuoteR->getTheQuote=$this->quotes["monday"];
      return $theQuoteR;
    }
  }
}
 
?>

Here is the newer WSDL output (snipped) from the new PHP SOAP server that creates a complexType return type for the .NET environment to be able to use.

<types>
-
<xsd:schema targetNamespace="http://localhost/projects/webservice/zend_soap_server_net.php">
-
<xsd:complexType name="theQuote">
-
<xsd:all>
<xsd:element name="getTheQuote" type="xsd:string"/>
</xsd:all>
</xsd:complexType>
</xsd:schema>
</types>
-
<portType name="QuoteOfTheDayPort">
-
<operation name="getQuote">
<documentation>@return theQuote</documentation>
<input message="tns:getQuoteIn"/>
<output message="tns:getQuoteOut"/>
</operation>
</portType>

Here is the .NET client

Since we have a new WSDL generation, will need to generate a newer DLL that will allow the .NET to use the class structure. Please note I am using mono as my .NET environment, so if you are using .NET in Windows then you could either include the WSDL within the Visual Studio or use very similar commands as below.

wsdl2 http://localhost/projects/webservice/zend_soap_server_net.php?wsdl
gmcs QuoteOfTheDayService.cs /t:library /r:System.Web.Services

which will generate the QuoteOfTheDayService.dll and with the code below

using System;
 
namespace codingfriendssoap
{
    class Test_Php_Soap
    {
 
static public void Main()
{
  QuoteOfTheDayService quoteService = new QuoteOfTheDayService();
 
  Console.WriteLine("mondays quote " + quoteService.getQuote("monday").getTheQuote);
}
    }
}

to compile that you once again I am using mono so these are the commands that I am using, but with .NET framework in Windows the commands may be different to compile the program e.g. (mcc)

gmcs web_service_client_php.cs /r:QuoteOfTheDayService.dll

the /r includes the dll generated of the above generated PHP SOAP server this is the output

mondays quote Monday's child is fair of face

here is the XML response from the PHP SOAP server, which includes getTheQuote return wrapped around the theQuote class.

<SOAP-ENV:Body><ns1:getQuoteResponse><return xsi:type="ns1:theQuote"><getTheQuote xsi:type="xsd:string">Monday's child is fair of face</getTheQuote></return></ns1:getQuoteResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>

Web service – the consumer (client)

As from the web service server from the previous post you can now link to that web service. You will need to do a couple of things to be able to get the client to “talk” to the web service.

To start with on the server part of the tutorial you will need to create the dll (dynamically linked library) for the FirstWebService of the server, this is because this is what the client will use to “talk” to the server. To create this dll file you will need to compile up the FirstWebService, pull information from the server about the service and then just compile into a library

I am using mono, so if you are using .net within Windows then there is a similar command (may be just wsdl instead of wsdl2)

wsdl2 http://localhost/csharp/web_service.asmx?WSDL
 
--- output from the above command
Web Services Description Language Utility
Mono Framework v2.0.50727.1433
Writing file 'FirstWebService.cs'

as you can see it has created a file called FirstWebService.cs, a csharp source file of the WSDL (Web Services Description Language). To compile this into a FirstWebService.dll within the mono environment you just need to

gmcs /t:library FirstWebService.cs -r:System.Web.Services

the /t:library means to create a .dll file, if you do not pass in the “-r:System.Web.Services” it will complain with the below error.

FirstWebService.cs(21,51): error CS0234: The type or namespace name `Services' does not exist in the namespace `System.Web'. Are you missing an assembly reference?
Compilation failed: 1 error(s), 0 warnings

if you place the FirstWebService.dll within a bin directory within the directory where you are hosting the client from (you may need to create the bin directory for the dll)

Now it is the consumer (the client)

Since we have a FirstWebService.dll within the bin, this means that we try to compile up the client (on-the-fly) it knows how to create the class of FirstWebService. So to call the function of “Add” on the web service, we just need to create a new class of type FirstWebService and then call that function (and the rest is done behind the scenes)

    FirstWebService myFirstWebService = new FirstWebService();
    myFirstWebService.Add(4,5);

that is about it, of course it is nicer to have a web page to post some values to the server from the client, so here is the client source code, if you save this as web_service_consumer.aspx (aspx is a web page extension) and then just goto that web page hosted on the apache environment.

<%@ Page Language="C#" %>
<script runat="server">
// on the asp:Button onclick call this method
void runWebService_Click(Object sender, EventArgs e)
{
    FirstWebService myFirstWebService = new FirstWebService();
    // call the add method from the webservice
    // pass in the 2 values from the page and convert to integer values
    resultLabel.Text = myFirstWebService.Add(
		  Int32.Parse(number1.Text),
                  Int32.Parse(number2.Text)).ToString();
}
</script>
<html>
<head>
<title>ASP Web service consumer</title>
</head>
<body>
<form runat="server">
      First Number to Add : <asp:TextBox id="number1" runat="server">0</asp:TextBox>
<br/>
      Second Number To Add :
      <asp:TextBox id="number2" runat="server">0</asp:TextBox>
<br/>
      THE WEB SERVICE RESULTS!!!
<br/>
      Adding result : <asp:Label id="resultLabel" runat="server">Result</asp:Label>
<br/>
      <asp:Button id="runService" onclick="runWebService_Click" runat="server" Text="Run the Service"></asp:Button>
</form>
</body>
</html>

when you goto that page and see a error like

The type or namespace name `FirstWebService' could not be found. Are you missing a using directive or an assembly reference?
 
Source Error:
 
Line 4: void runWebService_Click(Object sender, EventArgs e)
Line 5: {
Line 6:     FirstWebService myFirstWebService = new FirstWebService();

that is because you have not created the bin directory and placed the FirstWebService.dll into that directory as describe at the top of this page.

SOAP – the client with requested and responses

From the SOAP server page here, this is the client that can connect to the PHP server page, the client uses the WSDL (Web Services Description Language) part of the server to find out the functions that are available.

To connect to the server WSDL, you create a new Soap Client with the WSDL as the URI link

  $client = new SoapClient("http://localhost/projects/webservice/zend_soap_server.php?wsdl")

in this example I am passing in some variables for debugging

    array(
      "trace"      => 1,		// enable trace to view what is happening
      "exceptions" => 0,		// disable exceptions
      "cache_wsdl" => 0) 		// disable any caching on the wsdl, encase you alter the wsdl server

so to only get a response from the server you just need to call the function on the server like it is running within the local PHP environment

  echo $client->getQuote("monday");

What is happening behind the scenes, is that the client has created a XML request and send that to the server, the server processes the request and responds with a XML response to the client, the client then pulls out the result from the XML response, here is how with using the soap client trace debugging out

  // display what was sent to the server (the request)
  echo "<p>Request :".htmlspecialchars($client->__getLastRequest()) ."</p>";
  // display the response from the server
  echo "<p>Response:".htmlspecialchars($client->__getLastResponse())."</p>";

here is the full source code, save as zend_soap_client.php (it is not using any zend framework, but the server is)

<?php
  // create a connection to the local host zend soap server, pull back the wsdl to get the functions names
  // and also the parameters and return values
  $client = new SoapClient("http://localhost/projects/webservice/zend_soap_server.php?wsdl",
    array(
      "trace"      => 1,		// enable trace to view what is happening
      "exceptions" => 0,		// disable exceptions
      "cache_wsdl" => 0) 		// disable any caching on the wsdl, encase you alter the wsdl server
  );
 
  // get a response from the WSDL zend server function getQuote for the day monday
  echo $client->getQuote("monday");
 
  // display what was sent to the server (the request)
  echo "<p>Request :".htmlspecialchars($client->__getLastRequest()) ."</p>";
  // display the response from the server
  echo "<p>Response:".htmlspecialchars($client->__getLastResponse())."</p>";
?>

and here is the output on the web page

Monday's child is fair of face
 
Request :<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:ns1="http://localhost/projects/webservice/zend_soap_server.php" xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body><ns1:getQuote><quote xsi:type="xsd:string">monday</quote></ns1:getQuote></SOAP-ENV:Body></SOAP-ENV:Envelope>
 
Response:<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:ns1="http://localhost/projects/webservice/zend_soap_server.php" xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body><ns1:getQuoteResponse><return xsi:type="xsd:string">Monday's child is fair of face</return></ns1:getQuoteResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>

RMI – Client and run the Library

This is the last part in the build up to the RMI (Remote Method Invocation) this is the clients part, the previous part was the RMI library / server. Where the server will listen for a clients request.

So to start with we need to be able to call the remote server, so we need to be able to have permission from the RMI java security package.

System.setSecurityManager(new RMISecurityManager());

next we need to build up the RMI server request URL (where the server is and also the remote class that it is listening on and as taken from here we are listening on the “RemoteBook” class) (we are listening on the localhost as the server)

String remoteClass = "RemoteBook";
String rmiURL = "rmi://localhost/" + remoteClass;

since all the client needs to know is what interface has been implemented within the server (the borrowable interface) then we just need to create a new object based on that that then links to the servers listening class rmiURL (as taken from above)

Borrowable remoteBook = (Borrowable)Naming.lookup(rmiURL);

and then just call the functions that will have been implemented on the server (the server has used the Book.java which implements the Borrowable interface) because now they are linked.

The java Naming object is a RMI object that allows for the server to bind’s its class object to a URL and the client to link its interface object to the servers class object basically.

here is the client code in full

import java.util.*;
import java.rmi.*;
 
public class RMIClient {
	String remoteClass = "RemoteBook";
	String libraryCardNumber = "genuxCard";
 
	public RMIClient()
	{
		if (System.getSecurityManager()== null)
			System.setSecurityManager(new RMISecurityManager());
 
	}
 
	public boolean useLibrary()
	{
		boolean result = true;
		try {
			String rmiURL = "rmi://localhost/" + remoteClass;
			Borrowable remoteBook = (Borrowable)Naming.lookup(rmiURL);
			System.out.println("The book is checked out "+remoteBook.isCheckedOut());
 
			boolean outResult = remoteBook.checkOut(libraryCardNumber,new Date());
 
			if (outResult==true)
				System.out.println("Book checked out successfully");
			else
				result = false;
 
			if (remoteBook.isCheckedOut())
			{
				System.out.println("libraray Card number that has checked out the book is :"+remoteBook.checkedOutCardNumber());
			}
 
			boolean inResult = remoteBook.checkIn(libraryCardNumber, new Date());
 
			if (inResult==true)
				System.out.println("Book checked in");
			else
				result = false;
		}
		catch (Exception e)
		{
			System.out.println("RMI Error: "+e.getMessage());
			result = false;
		}
		return result;
	}
 
	public static void main(String[] args) {
		RMIClient RMIC = new RMIClient();
		boolean result = RMIC.useLibrary();
		if (result == false)
			System.out.println("Error using library");
 
	}
 
}

and if you save that as RMIClient.java, compile up the java files to create the class files

This is the policy file, if you save this as RMIpolicyfile.policy, it basically allows all permissions within the java security object, not the best security for servers but this will server as a local host test. (alter the where/your/class/files/are to where you are building up the java files into there class files, within eclipse it has a /src directory and a /bin so you would link to the /bin directory)

grant codeBase "file:/where/your/class/files/are" {
    permission java.security.AllPermission;
};

and now that you have the RMI server (RMIFactory) and the RMI client (RMIClient) all we need to do is to register the java RMI to allow connections on the linux command line I do

rmiregistry &

the “&” allows it to run in the back ground, then to run the server

java -Djava.security.policy=RMIpolicyfile.policy RMIFactory &
console output : Remote book object has been started

which this uses the security policy file from above and also runs again in the background (“&”) and now to just run the client

java -Djava.security.policy=RMIpolicyfile.policy RMIClient

once again since the client is requesting the RMI security, you need to tell it to use the RMI policy file from above, and the output would be

The book is checked out false
Book checked out successfully
libraray Card number that has checked out the book is :genuxCard Date taken out on Thu Apr 08 11:49:36 BST 2010
Book checked in

TCP Client

As from the TCP Server, this is the client that will connect to the server and output the response from the server.

Since we are the client, we need to know where the server is to connect to, other wise we would not connect to anywhere!!!.. since within this tutorial I am using the local host for running the server as well (you could alter this to where you want to run the server on)

InetAddress theHost = InetAddress.getLocalHost();
// get the local hostname as well
String theHostName = theHost.getHostName();

and with this information you can create a new Socket to connect to the server on the port 9999 (which is the same port number that the server is listening on)

// open the socket connection with the server on port 9999
Socket theSocket = new Socket(theHostName, 9999);

to gain the server data, I am using the ObjectInputStream, with passing the socket from above connection to the server, then just read a line from the ObjectInputStream into a String local variable.

ObjectInputStream ois = new ObjectInputStream(theSocket.getInputStream());
String response = (String)ois.readLine();
// just output the response.
System.out.println("Response was : "+response);

and that is it, all very simple since most of the “interesting” stuff over the networking connections happening under the hood of java, which is just great.

Here is the full source code.

import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
 
public class TCPClient {
	InetAddress theHost;
	Socket theSocket;
	int port = 9999;
 
	public TCPClient() {
		try {
			// the local host IP address - where you want to connect to.. 
			theHost = InetAddress.getLocalHost();
			// get the local hostname as well
			String theHostName = theHost.getHostName();
			// open the socket connection with the server on port 9999
			theSocket = new Socket(theHostName, port);
 
			System.out.println("Server connection opened, going to listen");		
 
			ObjectInputStream ois = new ObjectInputStream(theSocket.getInputStream());
			String response = (String)ois.readLine();
 
			System.out.println("Response was : "+response);
			theSocket.close();
		} catch (UnknownHostException ExecHost)
		{
			System.out.println("Unknown Host Error");
		}
		catch (IOException ExceIO)
		{
			System.out.println("Error creatin socket : "+ExceIO.getMessage());
		}
	}
 
	public static void main(String[] args) {
		new TCPClient();
	}
 
 
}

If you save as TCPClient.java and then to compile and run (of course you need to be running in another console the TCPServer since the client will have nothing to connect to !!!)

javac TCPClient.java
java TCPClient

and the output would be

Server connection opened, going to listen
Response was : Hi from the server

you could use wireshark to watch what is happening.