AJAX và một số kỹ thuật liên quan - Phần cuối

Web Service

Trong mô hình hoạt động của mỗi dịch vụ web, thông thường sẽ có 3 thành phần tham gia.

Đầu tiên là các nhà cung cấp dịch vụ, tạm gọi là Provider. Nếu bạn thuộc lớp này, công việc của bạn là xây dựng và cài đặt đối tượng dịch vụ lên 1 web server và cung cấp bản mô tả dịch vụ để hướng dẫn mọi người cách khai thác. Ngôn ngữ sử dụng cho các bản mô tả dịch vụ là SDL - Service Description Language, với cấu trúc cú pháp của XML.

Thành phần thứ 2 : các nhà khai thác dịch vụ, tạm gọi là Consumer. Nếu bạn thuộc nhóm này, công việc của bạn là xem xét bản mô tả dịch vụ của nhà cung cấp, dựa vào đó để xây dựng lớp trung gian truy xuất đối tượng dịch vụ, và cuối cùng, thiết kế giao diện cho ứng dụng.

Thành phần thứ 3 trong chuỗi là những người dùng cuối, End - user. Họ truy cập trang web của các nhà khai thác dịch vụ, và sử dụng, một cách gián tiếp, Web Service của Provider.

Như vậy, Provider là người nhào bột. Consumer mang bột đó làm thành bánh và trưng bày ở các cửa hiệu. End-user đến cửa hiệu, thưởng thức bánh và thường là không cần biết ai đã nhào bột !


PHP và SOAP

PHP, ngôn ngữ lập trình hướng đối tượng có mã nguồn mở, sử dụng trên các máy chủ web, được sáng tạo bởi Rasmus Lerdorf vào năm 1994, trong hơn 10 năm qua đã trở thành niềm say mê của không ít người phát triển ứng dụng web. Những người yêu thích PHP và có tài năng, chủ yếu quy tụ ở http://www.php.nethttp://www.zend.com/, không ngừng bổ sung cho PHP các tính năng mới nhằm đáp ứng với những đổi thay của công nghệ.

Muốn thêm tính năng vào PHP, người ta sẽ dùng một ngôn ngữ lập trình cao cấp nào đó để tạo ra các lớp đối tượng và phương thức nhằm thực thi một nhóm tác vụ cụ thể, rồi biên dịch thành những file dạng DLL - Dynamic Link Library - và đặt vào thư viện mở rộng của PHP. Người viết mã PHP sau đó chỉ cần tạo ra thể hiện của những đối tượng xây dựng sẵn trong thư viện và gọi các phương thức của chúng.

Có một dạng thư viện khác không được biên dịch trước, với những lớp đối tượng tạo sẵn nằm trong các file PHP. Khi sử dụng, người viết mã phải chèn file thư viện vào trang PHP của họ bằng các chỉ thị include, require... Việc sử dụng các thư viện không biên dịch sẽ đòi hỏi nhiều thời gian xử lý hơn, nhưng đôi khi, đó lại là cách lựa chọn duy nhất.

Để làm việc với SOAP trong PHP, bạn cần đảm bảo web server đã có thư viện php_soap.dll. Nếu chưa có, bạn tải về và đặt file này vào folder các thư viện mở rộng mà việc ấn định nằm trong file php.ini ở dòng

extension_dir =path


Đồng thời, cũng cần kiểm tra lại file php.ini để chắc chắn có các dòng sau :

extension=php_soap.dll
always_populate_raw_post_data = On

Nếu chưa có, hãy thêm vào. Nếu đang ở dạng chú thích, hãy gỡ bỏ comment. Nếu giá trị thiết lập không đúng như trên, hãy sửa lại.

Thư viện php_soap.dll cung cấp 2 đối tượng chính : SoapServerSoapClient. Mọi thông tin về 2 đối tượng này, bạn có thể tham khảo tại đây.


SoapServer chủ yếu phục vụ cho việc xây dựng web service, cài đặt đối tượng dịch vụ web, tự động tạo ra bản mô tả dịch vụ. SoapClient chủ yếu dùng trong quá trình khai thác dịch vụ web, triệu gọi đối tượng web service và nhận phản hồi từ nhà cung cấp.

Trong trường hợp bạn không có quyền can thiệp vào file cấu hình PHP, và không thể cài đặt thêm php_soap.dll, bạn có thể sử dụng các thư viện hỗ trợ SOAP không cần biên dịch, như NuSOAP, PearSOAP... NuSOAP tỏ ra khá hiệu quả vì tên các đối tượng trong thư viện này gần giống với trong thư viện php_soap.dll. 2 đối tượng quan trọng nhất ở NuSOAP là soap_serversoap_client có các chức năng gần tương tự SoapServer và SoapClient trong thư viện php_soap.dll.


Nếu bạn cần các thư viện, có thể download ngay tại đây

Thư viện php_soap.dll
Thư viện PearSOAP
Thư viện NuSOAP
Các chỉ dẫn về NuSOAP


Trong ví dụ minh họa cho AJAX - SOAP dưới đây, tôi sẽ sử dụng thư viện NuSOAP.


Xây dựng Web Service

Giả sử chúng ta muốn cung cấp một dịch vụ có tên là Reverse String, với phương thức Reverse cho phép đảo ngược một chuỗi text bất kỳ. Chẳng hạn khi bạn đưa vào một chuỗi "I saw Elba", cái mà bạn nhận được sẽ là "ablE was I". Vậy thì trước tiên, chúng ta tải xuống file NuSOAP.zip, giải nén và đặt thư mục libs vừa có được vào đâu đó trong thư mục ứng dụng web để chèn vào trang khi cần. Ở đây tôi đặt vào thư mục gốc.

Tiếp theo chúng ta tạo ra 1 folder cùng cấp với libs, đặt tên là Provider, mở ra và tạo 1 file service.php, có nội dung như sau :

<?php
include('../libs/nusoap.php');
$server = new soap_server();

$server->configureWSDL('Reverse String', 'uri:http://snlibs.googlepages.com/schema.xml');

function Reverse($s){
$r='';
  for($i=1;$i<=strlen($s);$i++){
    $r.=substr($s,-$i,1);
  }
return $r;
}
$server->register("Reverse", array('text' => 'xsd:string'),array('result' => 'xsd:string'));

$query = isset($HTTP_RAW_POST_DATA)? $HTTP_RAW_POST_DATA : '';
$server->service($query);
?>


Sau khi chèn thư viện NuSOAP vào trang, chúng ta khởi tạo 1 thể hiện của lớp soap_server trong biến $server. Phương thức configureWSDL nhận vào 2 tham số là tên của dịch vụ web, và đường dẫn đến giản đồ sử dụng cho dịch vụ.

Hàm Reverse tiếp nhận 1 chuỗi văn bản, và lật ngược nó, được cài đặt vào dịch vụ thông qua phương thức register của soap_server. Nó cần 3 tham số : tên của hàm, mảng các request và mảng các response. Ở đây, request là các yêu cầu mà dịch vụ muốn nhận được khi triệu gọi; response là các thông điệp trả về lớp trung gian. Phương thức register tương đương với addFunction của SoapServer trong php_soap.dll.

Câu lệnh :
     $query = isset($HTTP_RAW_POST_DATA)? $HTTP_RAW_POST_DATA : '';

sẽ đem mọi dữ liệu nhận được qua HTTP - POST gán vào biến $query. Bạn chỉ nhận được dữ liệu post lên theo cách này khi trong file php.ini, always_populate_raw_post_data đã được thiếp lập giá trị On.

Và cuối cùng, phương thức service sẽ thực hiện việc gọi hàm Reverse để đảo ngược chuỗi $query. Phương thức service tương đương với handle của SoapServer trong php_soap.dll.

Nếu không có gì sai sót, khi bạn gọi http://localhost/Provider/service.php, sẽ thấy kết quả như sau :



Bản mô tả dịch vụ xuất hiện khi bạn cung cấp tham số query ?wsdl.

Còn một điểm cuối cùng cho Web Service là schema, chúng ta thêm vào cho đủ đội hình. Đây là nội dung của schema.xml :
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs=" http://www.w3.org/2001/XMLSchema"
          xmlns:tns=" http://schemas.xmlsoap.org/soap/envelope/"
          targetNamespace="http://snlibs.googlepages.com/schema.xml">
<xs:element name="ReverseRequest">
  <xs:complexType>
    <xs:sequence>
    <xs:element name="text" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
</xs:element>
<xs:element name="ReverseResponse">
  <xs:complexType>
    <xs:sequence>
    <xs:element name="result" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
</xs:element>
</xs:schema>


SOAP gửi và nhận dữ liệu ở dạng XML, schema trên sẽ quy định các phần tử xuất hiện trong thông điệp HTTP - XML mà SOAP chuyển đi. Trong thông điệp triệu gọi, chúng ta có 1 phần tử ReverseRequest và 1 phần tử text. Trong thông điệp phản hồi, chúng ta có ReverseResponse và 1 phần tử result. 2 phần tử text và result đều có kiểu dữ liệu string.

Như vậy, chúng ta đã hoàn chỉnh phần thiết lập cho dịch vụ web. Bây giờ, hãy chuyển sang vai trò của một nhà khai thác dịch vụ.


Khai thác dịch vụ web

Xem xét bản mô tả khi gọi trang http://localhost/Provider/service.php/Reverse?wsdl, bạn có thể nhận thấy :



Có 2 phần tử message, 1 chỉ định tên của tham số dùng cho lời triệu gọi : text, và 1 cho biết tên của phần tử sẽ chứa kết quả trả về : result.

Phần tử operation cho biết tên của phương thức : Reverse, và URL dùng để gọi hành động SOAP.

Chừng đó kể như là đủ cho việc tạo ra ứng dụng trung gian. Cũng trong thư mục chứa Provider và libs, chúng ta tạo ra 1 folder có tên là Consumer, mở ra và tạo 1 file call.php có nội dung như sau :

<?php
header("Content-Type: text/xml; charset=utf-8");
include('../libs/nusoap.php');
  $query = isset($HTTP_RAW_POST_DATA)? $HTTP_RAW_POST_DATA : 0;
  $sp = new soap_parser($query, 'UTF-8', 'POST');
  $text=$sp->buildVal(3);
  $c = new soapclient('http://localhost/Provider/service.php/Reverse');
  $c->call('Reverse', array('text'=>$text));
echo $c->responseData;
?>

Dòng lệnh đầu tiên quy định dữ liệu mang định dạng XML. SOAP và XML không thể tách rời nhau. Thư viện NuSOAP được chèn vào ở dòng lệnh thứ 2. Biến $query đón bắt các thông điệp post lên từ máy khách.

Vì chúng ta post request lên bằng AJAX- SOAP, và dữ liệu có định dạng XML nên tôi tạo ra một thể hiện của lớp soap_parser để lấy chuỗi văn bản trong phần tử text.

Đối tượng soap_parser khi khởi tạo cần có 3 thuộc tính : dữ liệu XML (là chuỗi text mà AJAX gửi lên và nằm trong biến $query), bảng mã sử dụng trong tài liệu XML, và phương thức gửi nhận dữ liệu.

soap_parser cung cấp phương thức buildVal, cho phép lấy dữ liệu từ 1 phần tử bất kỳ trong tài liệu XML thông qua chỉ mục của phần tử. Theo cấu trúc của thông điệp SOAP mà chúng ta gửi lên, văn bản do người dùng nhập vào sẽ nằm ở node thứ 3.

Tiếp đó, chúng ta khởi tạo 1 thể hiện của lớp soap_client. Cần 1 tham số là URL của dịch vụ web mà chúng ta có nhờ sự chỉ dẫn của bản mô tả dịch vụ.

Phương thức call sẽ yêu cầu dịch vụ thực hiện Reverse chuỗi text. Đây là bước triệu gọi web sevice. Nếu dùng thư viện php_soap.dll, bạn tạo ra thể hiện của lớp SoapClient và gọi Reverse như sau :

$c = new SoapClient("http://localhost/Provider/service.php/Reverse");
$c->__soapCall('Reverse', array('text'=>$text));

// hoặc :  $c->__call('Reverse', array('text'=>$text));


Và cuối cùng, phương thức responseData của soap_client trả về kết quả phản hồi từ Provider. Nó tương đương với getLastResponse của SoapClient trong thư viện php_soap.dll.

call.php đóng vai trò trung gian, sẽ nhận tham số là chuỗi text do người dùng post lên, và chuyển cho Provider. Khi dịch vụ web của Provider nhận được chuỗi text, nó dùng phương thức Reverse đảo ngược chuỗi rồi trả kết quả trở lại.

Thư viện NuSOAP còn có nhiều đối tượng và phương thức xử lý khác. Để đưa vào hoạt động thực sự, bạn nên sử dụng các phương thức xử lý lỗi của lớp soap_fault. Ứng dụng này chỉ mang tính minh họa và chúng ta chỉ mới khai thác một cách rất hạn chế những gì có trong NuSOAP.



Gửi nhận data với AJAX và SOAP

Việc giao dịch giữa người dùng cuối và trang trung gian sẽ được thực hiện bởi AJAX-SOAP. Đây là hàm Java Script trong file index.htm đảm nhiệm chức năng đó :

function soap_request(oText){
  var s='<'+'?'+'xml version="1.0" encoding="UTF-8"'+'?'+'>';
    s+='<SOAP-ENV:Envelope ';
    s+=      ' xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"';
    s+=      ' SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">';
    s+=        '<SOAP-ENV:Body>';
    s+=            '<sn:ReverseRequest xmlns:sn="http://snlibs.googlepages.com/schema.xml">';
    s+=                '<sn:text>'+oText+'</sn:text>';
    s+=            '</sn:ReverseRequest>';
    s+=        '</SOAP-ENV:Body>';
    s+=    '</SOAP-ENV:Envelope>';
      var xmlhttp=request();
      xmlhttp.onreadystatechange=function(){
        if (xmlhttp.readyState==4&&xmlhttp.status==200){
            setResult(xmlhttp.responseText);
        }
    }
    xmlhttp.open("POST", "call.php",true);
    xmlhttp.setRequestHeader("SOAPAction", 'http://localhost/Provider/service.php/Reverse');
    xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    xmlhttp.setRequestHeader('Content-Length', s.length);
    xmlhttp.send(s);
}


Hàm này nhận vào 1 tham số là chuỗi ký tự do người dùng nhập vào. Chuỗi s được biên tập theo dạng XML, với tham số oText lồng vào trong phần tử text để trở thành giá trị của phần tử này. Các dòng setRequestHeader thiết lập thông số trong header của gói tin SOAP gửi đi. Khi đã nhận được thông điệp phản hồi, hàm setResult được gọi với tham số là dữ liệu trả về dạng text/html.

Tôi chọn responseText thay vì responseXML để có thể vừa lấy ra giá trị text trong phần tử result, vừa có thể trình bày lại nguyên dạng thông điệp HTTP-SOAP trả về từ web service trong 1 textArea. Đây là hàm setResult.

function setResult(str){
   var r='',xmlDoc=null;
      if(window.DOMParser){
         var parser = new DOMParser();
       xmlDoc = parser.parseFromString(str, "text/xml");
    }
    else{
       xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
       xmlDoc.async="false";
       xmlDoc.loadXML(str);
    }
    r=xmlDoc.getElementsByTagName('result')[0].childNodes[0].nodeValue;
    document.f.result.value=r;
    document.f.responseMsg.value=str;
}


setResult kiểm tra xem trình duyệt có lớp DOMParser hay không, nếu có, thì tạo ra một thể hiện và gán vào xmlDoc. Đây là trường hợp của FireFox và Opera. Phương thức parseFromString của DOMParser cho phép chuyển một chuỗi về dạng XML để thao tác như với dữ liệu XML.

Chẳng hạn bạn có chuỗi s='<result>13479</result>', đây không phải là dữ liệu trong 1 hồ sơ XML nên bạn không thể xử lý result như phần tử XML. Và bạn cần parseFromString để làm điều đó.

Ngược lại với parseFromString là phương thức serializeToString của đối tượng XMLSerializer, nó cho phép bạn xem xét hồ sơ XML như với 1 chuỗi bình thường.


Trên IE không có DOMParser, vì vậy chúng ta khởi tạo XMLDOM và gọi phương thức loadXML để load chuỗi str vào đó. Hành động này cũng chuyển tham số str về dạng XML để có thể truy vấn node được.

Sau khi có thể xem xét str như 1 tập hợp XML node, chúng ta lấy trị trong node result và gán vào biến r. Cuối cùng chỉ còn việc hiển thị :