Web services in theory and practice for beginners

What are web services?



First of all, web services (or web services) is a technology. And like any other technology, they have a fairly well-defined application environment.

If we look at web services in the context of the stack of network protocols, we will see that this, in the classical case, is nothing more than another add-on over the HTTP protocol.

On the other hand, if we hypothetically divide the Internet into several layers, we can distinguish at least two conceptual types of applications - computing nodes that implement non-trivial functions and web application resources. Moreover, the latter are often interested in the services of the former.

But the Internet itself is heterogeneous, that is, various applications on different network nodes operate on different hardware and software platforms, and use different technologies and languages.

To connect all this and provide an opportunity for some applications to exchange data with others, web services were invented.

In fact, web services are the implementation of absolutely clear interfaces for exchanging data between different applications that are written not only in different languages, but also distributed on different network nodes.

It was with the advent of web services that the idea of ​​SOA, a service-oriented architecture of web applications (Service Oriented Architecture), developed.

Web service protocols



To date, the following protocols for implementing web services are most widely used:

  • SOAP (Simple Object Access Protocol) is essentially the top three SOAP / WSDL / UDDI standards
  • REST (Representational State Transfer)
  • XML-RPC (XML Remote Procedure Call)


In fact, SOAP evolved from XML-RPC and is the next step in its development. While REST is a concept based on an architectural style rather than a new technology based on CRUD (Create Read Update Delete) object manipulation theory in the context of WWW concepts.

Of course, other protocols exist, but since they are not widely used, we will stop in this brief review on two main ones - SOAP and REST. XML-RPC, in view of the fact that it is somewhat "obsolete," we will not consider in detail.

We are primarily interested in the issues of creating new web services, rather than the implementation of clients to existing ones (as a rule, web service providers supply packages with API functions and documentation, therefore the issue of building clients for existing web services is less interesting from the point of view of the author).

SOAP vs REST



The problems of this confrontation are well described in an article by Leonid Chernyak , found on the portal www.citforum.ru .

According to the author, we can briefly highlight the following:

SOAP is more applicable in complex architectures, where interaction with objects is beyond the scope of CRUD theory, but in those applications that do not leave the framework of this theory, REST may turn out to be quite applicable because of its simplicity and transparency. Indeed, if any objects of your service do not need more complex relationships, except: “Create”, “Read”, “Change”, “Delete” (as a rule, this is enough in 99% of cases), it is possible that REST will become the right choice. In addition, REST, compared to SOAP, may turn out to be more productive, since it does not require the cost of parsing complex XML commands on the server (regular HTTP requests are performed - PUT, GET, POST, DELETE). Although SOAP, in turn, is more reliable and secure.

In any case, you decide which is more suitable for your application. It is likely that you will even want to implement both protocols in order to leave the choice to the users of the service and that is your right.

Practical use of web services



Since this is a practical application, we need to choose a platform for building a web service and set a task. Since the author is closest to PHP 5, we will choose it as a technology for building a service, and as a task we will accept the following requirements.

Suppose we need to create a service that provides access to information about exchange rates, which is collected by our application, and is accumulated in the database. Further, through a web service, this information is transmitted to third-party applications for display in a form convenient for them.

As you can see, the task is quite simple and, from the point of view of the service itself, is limited only to reading information, but for practical purposes this will be enough for us.

Stage one - the implementation of the application for collecting information on exchange rates.



We will collect information on exchange rates from the pages of the NBU (National Bank of Ukraine) website on a daily basis and put into a database under the control of the MySQL DBMS.

Create a data structure.

Currency table (currency):

+-------------+------------------+
| Field       | Type             |
+-------------+------------------+
| code        | int(10) unsigned |
| charcode    | char(3)          |
| description | varchar(100)     |
| value       | int(10) unsigned |
| base        | tinyint(1)       |
+-------------+------------------+


Exchange denomination table:

+------------+------------------+
| Field      | Type             |
+------------+------------------+
| id         | bigint(20) ai    |
| rate_date  | timestamp        |
| rate_value | float            |
| code       | int(10) unsigned |
+------------+------------------+


To work with the database, we will use the ORM layer based on the PHP Doctrine package . We implement the grabber:
the Grubber class (models / Grabber.php):

<?php
/*
 * @package Currency_Service
 */
class Grabber {

    /**
     * Extracts data from outer web resource and returns it
     *
     * @param  void
     * @return array
     */
    public static function getData() {
        /**
         * Extracting data drom outer web-resource
         */
        $content = file_get_contents( 'http://www.bank.gov.ua/Fin_ryn/OF_KURS/Currency/FindByDate.aspx');
        if(preg_match_all( '/(\d+)<\/td>([A-Z]+)<\/td>(\d+)<\/td>(.+?)<\/td>(\d+\.\d+)<\/td>/i', $content, $m) == false) {
            throw new Exception( 'Can not parse data!');
        }

        /**
         * Preformatting data to return;
         */
        $data = array();
        foreach ($m[1] as $k => $code) {
            $data[] = array(
                'code'        => $code,
                'charcode'    => $m[2][$k],
                'value'       => $m[3][$k],
                'description' => $m[4][$k],
                'rate_value'  => $m[5][$k]
            );
        }
        return $data;
    }

    public static function run() {
        $data = self::getData();

        /**
         * Sets default currency if not exists
         */
        if (!Doctrine::getTable( 'Currency')->find( 980)) {
            $currency = new Currency();
            $currency->setCode( 980)
                     ->setCharcode( 'UAH')
                     ->setDescription( 'українська гривня')
                     ->setValue( 1)
                     ->setBase( 1)
                     ->save();
        }

        foreach ($data as $currencyData) {
            /**
             * Updating table of currencies with found values
             */
            if (!Doctrine::getTable( 'Currency')->find( $currencyData['code'])) {
                $currency = new Currency();
                $currency->setCode( $currencyData['code'])
                         ->setCharcode( $currencyData['charcode'])
                         ->setDescription( $currencyData['description'])
                         ->setValue( $currencyData['value'])
                         ->setBase( 0)
                         ->save();
            }

            /**
             * Updating exchange rates
             */
            $date = date( 'Y-m-d 00:00:00');
            $exchange = new Exchange();
            $exchange->setRateDate( $date)
                     ->setRateValue( $currencyData['rate_value'])
                     ->setCode( $currencyData['code'])
                     ->save();
        }

    }
}
?>


and grabber itself (grabber.php):

<?php
require_once('config.php');
Doctrine::loadModels('models');
Grabber::run();
?>


Now we will make our grabber work out once a day at 10:00 in the morning, by adding the grabber start command to the cron tables:

0 10 * * * /usr/bin/php /path/to/grabber.php


That's all - we have a rather useful service.

Now we are implementing a web service that will allow other applications to retrieve data from our database.

Implementing a SOAP Service



To implement a web service based on the SOAP protocol, we will use the built-in package in PHP for working with SOAP.

Since our web service will be public, a good option would be to create a WSDL file that describes the structure of our web service.

WSDL (Web Service Definition Language) - is an XML file of a certain format. A detailed description of the syntax can be found here .

In practice, it will be convenient to use the automatic file generation function provided by the Zend Studio for Eclipse IDE . This function allows you to generate a WSDL file from PHP classes. Therefore, first of all, we must write a class that implements the functionality of our service.

CurrencyExchange class (models / CurrencyExchange.php):

<?php
/**
 * Class providing web-service with all necessary methods
 * to provide information about currency exchange values
 *
 * @package Currency_Service
 */
class CurrencyExchange {

    /**
     * Retrievs exchange value for a given currency
     *
     * @param  integer $code - currency code
     * @param  string $data - currency exchange rate date
     * @return float - rate value
     */
    public function getExchange( $code, $date) {
        $currency = Doctrine::getTable( 'Currency')->find( $code);
        $exchange = $currency->getExchange( $date);
        return $exchange ? (float)$exchange->getRateValue() : null;
    }

    /**
     * Retrievs all available currencies
     *
     * @return array - list of all available currencies
     */
    public function getCurrencyList() {
        return Doctrine::getTable( 'Currency')->findAll()->toArray();
    }

}
?>


Note that for the automatic generation of WSDL, we need to write comments in the javadoc style, because it is in them that we prescribe information about the types of accepted arguments and return values. It’s also good to describe the operation of the methods in a few words - because WSDL will serve as a description of the API for third-party developers who will use your web service.

Не пишите в докблоках param void или return void — для WSDL это не критично, но вот при реализации REST доступа к тому-же классу у вас возникнут проблемы.


Now in Zend Studio we go to the File-> Export ... menu, select PHP-> WSDL, add our class, prescribe the URI of our service and create a WSDL file. The result should be something like this: http://mikhailstadnik.com/ctws/currency.wsdl
If you add new functionality to your web service, you will need to recreate the WSDL file. But things are not so smooth here. Keep in mind that a SOAP client that has already requested your WSDL file caches it on its side. Therefore, if you replace the old content with the new one in the WSDL file, some clients will not read it. So, when adding new functionality, add the version to the name of your file. And do not forget to provide backward compatibility for old customers, especially if you are not their supplier.

On the other hand, WSDL rather rigidly defines the structure of the web service, which means that if there is a need to limit the functionality of the client compared to the server, you can not include certain methods of your classes in WSDL. Thus, they cannot be invoked, even though they exist.

The implementation of the server itself does not present any difficulty now: the
index.php file:

<?php
require_once('config.php');

Doctrine::loadModels('models');

$server = new SoapServer( 'http://mikhailstadnik.com/ctws/currency.wsdl');
$server->setClass( 'CurrencyExchange');
$server->handle();
?>


You can try the web service at work at: http://mikhailstadnik.com/ctws/
There is also a test client available: http://mikhailstadnik.com/ctws/client.php
The code for a simple client can be like this:

<?php
$client = new SoapClient( 'http://mikhailstadnik.com/ctws/currency.wsdl');
echo 'USD exchange: ' . $client->getExchange( 840, date( 'Y-m-d'));
?>


REST service implementation



REST is not a standard or specification, but an architectural style built on existing, well-known and controlled by the W3C consortium standards, such as HTTP, URI (Uniform Resource Identifier), XML and RDF (Resource Description Format). In REST services, the emphasis is on access to resources, rather than on the execution of remote services; this is their fundamental difference from SOAP services.

Nevertheless, a remote procedure call is also applicable in REST. It uses the methods PUT, GET, POST, DELETE HTTP protocol for manipulating objects. Its fundamental difference from SOAP is that REST remains an HTTP request.

Since there is no REST implementation in PHP yet, we will use Zend Framwork , which includes the implementation of both the REST client and the REST north.

We will use the ready-made CurrencyExchange class. Let's write the server itself:
rest.php:

<?php
require_once 'config.php';
require_once 'Zend/Rest/Server.php';

Doctrine::loadModels('models');

$server = new Zend_Rest_Server();
$server->setClass( 'CurrencyExchange');
$server->handle();
?>


As you can see, everything is very similar and simple.

However, it should be noted that our REST service is less secure than the SOAP service, since any added method to the CurrencyExchange class will work when called (the class itself determines the structure of the service).

Check the operation of our service. To do this, it is enough to pass the param eters of the method call in the term of the GET request:

?method=getExchange&code=840&date=2008-11-29


or

?method=getExchange&arg1=840&arg2=2008-11-29


If desired or necessary, you can self-define the structure of your XML responses for the REST service. In this case, it will also be necessary to take care of creating a type definition for your XML document (DTD - Document Type Definition). This will be the minimum API description of your service.

The simplest test client for a REST service can be like this in our case:

<?php
$client = new Zend_Rest_Client( 'http://mikhailstadnik.com/ctws/rest.php');
$result = $client->getExchange( 840, date( 'Y-m-d'))->get();
if ($result->isSuccess()) {
    echo 'USD exchange: ' . $result->response;
}
?>


In principle, Zend_Rest today can not be called the most accurate implementation of the principles of REST. Exaggerating, we can say that this implementation came down to remote procedure call (RPC), although the REST philosophy is much broader.

You can download the example in source code from PHP Doctrine and Zend Framework (4.42 Mb).

Conclusion



We completed the minimum task and showed what web services are, why they are needed and how to implement them. Naturally, the given example may be somewhat divorced from life, but it was chosen only as a tool for explaining the subject and essence of web services.

In addition, we saw that the implementation of a web service is a fairly simple task when using modern tools, which allows you to concentrate, first of all, on developing the functionality of the service itself, without worrying about the low-level protocol implementation.

The author hopes that this material will be really useful for those who are on the path of developing web services.

Good luck in the development!