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.

Web service

An web service is basically like a SOAP server in that it “talks” from the client-server in XML with the client requesting the function on the server and obtaining a result.

I am using mono to compile and run the web service since I am running apache on Linux (kubuntu) (here is how I setup the mono apache module within k/ubuntu).

To start with, you need to create a web service instance and what language you are going to be using (since using mono then c#) and also the class that you want to “expose” to the web service itself.

<%@ WebService language="C#" class="FirstWebService" %>

after that you then need to add the attribute to the class to tell the compiler that the class is going to be a webservice and what the base directory is going to be (I created a new directory within the apache hosting directory), since we are writing a WebService we need to inherit from a System.Web.Services.WebService

[WebService(Namespace="http://localhost/csharp/")]
public class FirstWebService : WebService

and then just within the class structure you only need to tell the function with a attribute heading of [WebMethod] that it is going to be “exposed” to the web service, if you do not put that in, it will be “exposed” to the web service and thus the client cannot access that method.

    [WebMethod]
    public int Add(int a, int b)

and that is about it, the mono (and of course .net base framework) will create the rest of the WSDL and additional parts for a WebService.

Here is the full web service code in full, save this as web_service.asmx (the asmx means a web service extension)

<%@ WebService language="C#" class="FirstWebService" %>
 
using System;
using System.Web.Services;
 
// expose as the web service
[WebService(Namespace="http://localhost/csharp/")]
public class FirstWebService : WebService
{
    // expose as a web method
    [WebMethod]
    public int Add(int a, int b)
    {
        return TheAddingMethod(a,b);
    }
 
    // this one will not be exposed since it does not have the [WebMethod] attribute
    public int TheAddingMethod(int a, int b)
    {
	// but since it is part of the class you can still call class methods etc.
	return a+b;
    }
}

and when you goto the web URL for the webservice you should see something similar to this

The FirstWebService URL
The FirstWebService URL

if you click on the left menu “add” and then “test form” to test the webservice, it will bring up a window similar to the below, I have done a full test with adding 4 + 5 = 9

Testing the first web service
Testing the first web service

Mono – web development on Linux

It is really easy to get some web c# (csharp) (asp.net) development within a Linux setup, since I use kubuntu which is derived from ubuntu. All you need to do is to

this installs the mono module for the apache2

aptitude install libapache2-mod-mono

to enable the module to work within apache2 you have to update the mods-enabled directory and to do this, just use the

a2enmod mod_mono_auto

which stands for apache2 (a2) enable (en) mod (module) and then the module name, you will need to restart apache2 for it to work

/etc/init.d/apache2 restart

that is it., then if you look within the /etc/apache2/mods-enabled you will notice the

mod_mono_auto.conf
mod_mono_auto.load

of course you could symlink them yourself, but it is just easier to use the a2enmod script.

Within the .conf file it tells apache what extensions to “listen” to direct to the module mono instead of either php module or just a static html web page.

To test the setup, just create a directory within your hosting directory this is normally /var/www/ or just place a file in that base directory /var/www it is up to you, but within that file you could create a basic test file like.

<%@ Page Language="C#" %>
<html>
  <head>
    <title>Apache2 Mod mono test page</title>
  </head>
  <body>
        <form id="form1" runat="server">
          <asp:label id="lbl1" runat="server">Apache2 Mod mono test page</asp:label>
        </form>
  </body>
</html>

the asp:label will be the main test, since that part of the csharp library as such. you should see something like

Apache2 Mod mono test 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>

SOAP – Server

SOAP is the acronym for Simple Object Access Protocol, it uses the web and XML for a client to find out what functions are available on a server using the WSDL (Web Server Description Language) (this is also in XML format) and once the client knows what functions are available and also what parameters can be passed, the client is then able to call the function and gain the response from the server.

I am using the Zend Framework to auto generate the WSDL file since there does not appear to be at present a function within the PHP standard framework to create one, I have included below how to install the Zend framework. Once you have installed the framework you need to either alter the php.ini file to tell it where the framework is or within the .php file for your server include details on where the framework is within that and include the relevant files.

Below I have told the php runtime within the .php file where the zend library framework is by using ini_setup include path settings.

/* setup the including path for the zend library framework */
ini_set('include_path', '/usr/share/php/libzend-framework-php/');
/* and include the zend soap files */
require_once 'Zend/Soap/AutoDiscover.php';
require_once "Zend/Soap/Server.php";

with including the auto discover (the WSDL generator part of the zend framework that we are wanting) and the server php files.

For the auto discover to work you need to use the php doc notation so that the WSDL can be generated correctly, so for example if you was using a function that was taking in a string and returning a string as in the main example below, you would use something like this (where the php doc is /** start)

  /**
   * @param string $quote
   * @return string
  */
  function getQuote($quote) {

To generate WSDL file I am using the parameter within the URL (?wsdl) and thus if that is present auto generate the WSDL file else act as the server, so

/* if the client is requesting the WSDL file (the web service definition language) */
if(isset($_GET['wsdl'])) {
   // use the zend soap autodiscover function to create the wsdl file from using the phpdoc info from the class QuoteOfTheDay */
    $autodiscover = new Zend_Soap_AutoDiscover();
    $autodiscover->setClass('QuoteOfTheDay');
    $autodiscover->handle();

with using the Zend_Soap_AutoDiscover class and then setting up the class that you want to expose to the WSDL generator / server and then output (handle) the WSDL request.

Here is the full code

<?php
 
/* setup the including path for the zend library framework */
ini_set('include_path', '/usr/share/php/libzend-framework-php/');
/* and include the zend soap files */
require_once 'Zend/Soap/AutoDiscover.php';
require_once "Zend/Soap/Server.php";
 
/* this is the class to be *expose* to the SOAP interface */
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");  
 
/* you have to use the phpDoc format for the zend soap auto discover to pick up the parameters and return types */
  /**
   * @param string $quote
   * @return string
  */
  function getQuote($quote) {
    /* just encase the string is in uppercase*/
    $symbol = strtolower($quote);
    /* if there is a quote for the day requested */
    if (isset($this->quotes[$quote])) {
      return $this->quotes[$quote];
    } else {
      /* else error */
      throw new SoapFault("Server","Unknown Symbol '$quote'.");
    }
  }
}
 
/* if the client is requesting the WSDL file (the web service definition language) */
if(isset($_GET['wsdl'])) {
   // use the zend soap autodiscover function to create the wsdl file from using the phpdoc info from the class QuoteOfTheDay */
    $autodiscover = new Zend_Soap_AutoDiscover();
    $autodiscover->setClass('QuoteOfTheDay');
    $autodiscover->handle();
} else {
  // otherwise I am the server in question, setup a new soap server with the a handle on the class QuoteOfTheDay 
    $soap = new Zend_Soap_Server("http://localhost/projects/webservice/zend_soap_server.php?wsdl"); // this current file here
    $soap->setClass('QuoteOfTheDay');
    $soap->handle();
}
?>

Save that as the zend_soap_server.php within your local PHP web serving directory you need to have the zend framework installed, below may help (within the php.ini there will either a extension list normally within a Windows setup)

in Linux (Ubuntu/Kubuntu etc) I installed the zend frame work for php by

aptitude install libzend-framework-php

or on windows this URL may be of use ? installing zend framework on windows

This is the output of the XML for the WSDL generation, it would be the output of a URL similar to http://localhost/zend_soap_server.php?wsdl

<definitions name="QuoteOfTheDay" targetNamespace="http://localhost/projects/webservice/zend_soap_server.php">
-
<types>
<xsd:schema targetNamespace="http://localhost/projects/webservice/zend_soap_server.php"/>
</types>
-
<portType name="QuoteOfTheDayPort">
-
<operation name="getQuote">
<documentation>@param string $quote</documentation>
<input message="tns:getQuoteIn"/>
<output message="tns:getQuoteOut"/>
</operation>
</portType>
-
<binding name="QuoteOfTheDayBinding" type="tns:QuoteOfTheDayPort">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
-
<operation name="getQuote">
<soap:operation soapAction="http://localhost/projects/webservice/zend_soap_server.php#getQuote"/>
-
<input>
<soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://localhost/projects/webservice/zend_soap_server.php"/>
</input>
-
<output>
<soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://localhost/projects/webservice/zend_soap_server.php"/>
</output>
</operation>
</binding>
-
<service name="QuoteOfTheDayService">
-
<port name="QuoteOfTheDayPort" binding="tns:QuoteOfTheDayBinding">
<soap:address location="http://localhost/projects/webservice/zend_soap_server.php"/>
</port>
</service>
-
<message name="getQuoteIn">
<part name="quote" type="xsd:string"/>
</message>
-
<message name="getQuoteOut">
<part name="return" type="xsd:string"/>
</message>
</definitions>

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

RMI – the Library factory

Now we have the library book, we can now build up the RMI library factory that will be the server for the clients to communicate to.

To start with we have to setup the security settings

System.setSecurityManager(new RMISecurityManager());

this is because the RMI library factory needs to “listen” on the server to remote clients to connect and due to the Java sandbox we have to make a request to the security manager to be allowed to “listen” for remote clients, without the permission there will be a error.

Remote error :Connection refused to host: 127.0.1.1; nested exception is: 
        java.net.ConnectException: Connection refused

I shall include the policy file when it comes to running the server/client setup.

now all we need to do is to bind the Book class RMI server and call it the “RemoteBook”, you need this to call with the client because you are basically allowing the server to communicate with the client to the Book that has been created (new Book(“Genuxs book… ) )

Book remoteBook = new Book("Genuxs book to borrow");
Naming.rebind("RemoteBook",remoteBook);

here is the full RMI Factory code

import java.rmi.*;
import java.rmi.server.*;
import java.net.*;
 
public class RMIFactory {
 
	public RMIFactory()
	{
		System.setSecurityManager(new RMISecurityManager());
	}
 
	public void createRemoteBook()
	{
		try {
			Book remoteBook = new Book("Genuxs book to borrow");
			Naming.rebind("RemoteBook",remoteBook);
 
		} 
		catch (RemoteException e)
		{
			System.out.println("Remote error :"+ e.getMessage());
		}
		catch (MalformedURLException mue)
		{
			System.out.println("Remote malformed error :"+mue.getMessage());
		}
 
		System.out.println("Remote book object has been started");
	}
 
	public static void main(String[] args) {
		RMIFactory RMIF = new RMIFactory();
		RMIF.createRemoteBook();
	}
 
}

if you save that as RMIFactory.java and next it is the client and running the RMI server and RMI client.