📜 ⬆️ ⬇️

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 you look at web services in the context of the network protocol stack, we will see that this, in the classic case, is nothing more than another add-on over HTTP.
')
On the other hand, if we hypothetically divide the Internet into several layers, we will be able to distinguish at least two conceptual types of applications — compute nodes that implement non-trivial functions and web-based application resources. At the same time, the latter are often interested in the services of the former.

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

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

In essence, web services are the implementation of absolutely clear data exchange interfaces between different applications, which are written not only in different languages, but also distributed across different nodes of the network.

It was with the advent of web services that the idea of ​​SOA — Service Oriented Architecture — was developed.

Web Services Protocols



To date, the following web-services implementation protocols have become most common:



In fact, SOAP is derived 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 the theory of manipulation of CRUD objects (Create Read Update Delete) in the context of WWW concepts.

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

We are primarily interested in creating new web services, rather than implementing clients to existing ones (as a rule, web service providers supply packages with API functions and documentation, therefore the issue of building clients to 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 the article by Leonid Chernyak , found on the portal www.citforum.ru .

According to the author, the following can be briefly highlighted:

SOAP is more applicable in complex architectures, where interaction with objects is beyond the scope of the CRUD theory, but in those applications that do not leave the framework of this theory, REST can be completely applicable due to its simplicity and transparency. Indeed, if any objects of your service do not need more complex relationships, except for “Create”, “Read”, “Change”, “Delete” (as a rule, in 99% of cases this is enough), it is possible that REST will be the right choice. In addition, REST as 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 executed - PUT, GET, POST, DELETE). Although SOAP, in turn, is more reliable and secure.

In any case, it’s up to you to decide what’s best 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 select it as a technology for building a service, and we will accept the following requirements as a task.

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 the web service, this information is transmitted to third-party applications for display in a convenient form.

As we 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.



Information about exchange rates, we will collect from the pages of the NBU site (National Bank of Ukraine) daily and put it into a database managed by 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) |
 + ------------- + ------------------ + 


Table of exchange values ​​(exchange):

  + ------------ + ------------------ +
 |  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. Implement the grabber:

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> ([AZ] +) <\ / 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 ('ukrain hryvnia')
                      -> 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 ('Ymd 00:00:00');
             $ exchange = new Exchange ();
             $ exchange-> setRateDate ($ date)
                      -> setRateValue ($ currencyData ['rate_value'])
                      -> setCode ($ currencyData ['code'])
                      -> save ();
         }

     }
 }
 ?> 


and the grabber itself (grabber.php):

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


Now let's make our grabber work once a day at 10:00 in the morning, by adding the command to start the grabber into the cron tables:

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


All - we have quite a useful service.

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

SOAP service implementation



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 specific 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 Zend Studio for Eclipse IDE . This feature 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 in order to automatically generate WSDL, we need to write javadoc-style comments, because it is in them that we write information about the types of arguments and return values. It is also good to describe in a few words the work of the methods - because WSDL will serve as a description of the API for third-party developers who will use your web service.

Do not write in param void or return void docblocks - this is not critical for WSDL, but you have problems when implementing REST access to the same class.


Now in Zend Studio we enter the menu File-> Export ..., select PHP-> WSDL, add our class, register the URI-address of our service and create the 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 re-create the WSDL file. But things are not so smooth here. Note that the 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 don't 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 a web service, which means that if there is a need to limit client functionality compared to the server, you can not include certain methods of your classes in WSDL. Thus, they cannot be called, despite the fact that they exist.

The implementation of the server itself now presents no difficulty:

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 http://mikhailstadnik.com/ctws/
A test client is also available there: http://mikhailstadnik.com/ctws/client.php

The simplest client code can be:

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


REST service implementation



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

Still, remote procedure call is applicable in REST. It uses the PUT, GET, POST, DELETE HTTP protocol methods to manipulate objects. Its fundamental difference from SOAP is that REST remains an HTTP request.

Since PHP does not yet have a REST implementation, we will use Zend Framwork , which includes the implementation of both the REST client and the REST of the north.

Let's 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 stipulated that our REST service is less secure than the SOAP service, since any added method to the CurrencyExchange class when it is called will work (the class itself determines the structure of the service).

Check the work of our service. To do this, it is enough to pass the parameters 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 you wish or need, you can autonomously define the structure of your XML responses for the REST service. In this case, it will also be necessary to take care of creating the definition of the type of your XML document (DTD - Document Type Definition). This will be the minimum description of the API of your service.

The simplest test client to the REST service may be in our case as follows:

  <? php
 $ client = new Zend_Rest_Client ('http://mikhailstadnik.com/ctws/rest.php');
 $ result = $ client-> getExchange (840, date ('Ym-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 has come down to remote procedure call (RPC), although the REST philosophy is much broader.

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

Conclusion



We completed the task minimum and showed what web services are, what they are for 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 that allow you to concentrate primarily on developing the functionality of the service itself, without worrying about low-level implementation of protocols.

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

Good luck in development!

Source: https://habr.com/ru/post/46374/


All Articles