Đôi khi tôi buộc phải thiết kế theo hướng tạo một Web Service trung gian (A) giữa Client và một Web Service khác (gọi là B).
Lý do tôi phải sử dụng hướng design này thì có nhiều, tuy nhiên thường gặp nhất là những vấn đề sau:
- Web Service B của bên khác thường xuyên thay đổi API, trong khi việc update software ở client (ví dụ thiết bị IoT đã ra thị trường) mất nhiều thời gian.
- Web Service B xử lý dữ liệu nặng nên cần một Web Service để cache dữ liệu nhằm tăng tốc truy xuất.
- Web Service B của bên khác không hỗ trợ CORS (Cross-Origin Resource Sharing), trình duyệt sẽ chặn nếu dùng JavaScript gọi trực tiếp Web Service này.
- Web Service B lọc chặn client theo địa phương, Web Service A được cài đặt trên server ngoài địa phương đó để forward data.
- Web Service B được sử dụng nội bộ, tuy nhiên một phần dữ liệu lại cần publish ra bên ngoài nhưng không cho phép truy cập trực tiếp B.
Cách thiết kế này gọi là “proxy”, ưu điểm là mềm dẻo và giải quyết gọn gàng những vấn đề trên. Tuy nhiên cũng có nhược điểm:
- Chậm chạp do phải thực hiện 2 lần request: Client ⇌ Web Service A ⇌ Web Service B. Xử lý data trên Web Service A nếu có thì phải thật đơn giản.
- Nếu Web Service B có rate limit theo địa chỉ IP thì sẽ làm giới hạn quy mô sử dụng của Web Service A.
Sau đây là một ví dụ đơn giản: Web Service A forward header & data từ Client đến Web Service B, sau đó trả về Client repsonse headers và data từ Web Service B.
forwarder.php
<?php // The base URI of the WebService B isset( $_GET['wsb_uri'] ) or die('No URL specified'); $url = $_GET['wsb_uri']; unset( $_GET['wsb_uri'] ); // Filter the API here // ... // Make a query to the Web Service B $url .= empty($_GET) ? '' : '?' . http_build_query($_GET); $cli_hdr = getallheaders(); // Sent from client to Server A $req_hdr = array(); // Sent from Server A to Server B $res_hdr = array(); // Responded from Server B to Server A unset( $cli_hdr['Host'] ); // Modify some headers if necessary if ( isset($cli_hdr['Referer']) ) { $cli_hdr['Referer'] = $url; } foreach ($cli_hdr as $name => $value) { $req_hdr[] = "$name: $value"; } $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL => $url, // Keep the request method CURLOPT_CUSTOMREQUEST => $_SERVER['REQUEST_METHOD'], // Forward necessary headers from the Client. Content-Type is mandatory. CURLOPT_HTTPHEADER => $req_hdr, CURLOPT_RETURNTRANSFER => true, CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_FAILONERROR => true ) ); // Forward the POST data from the Client to Web Service B if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) : $body = file_get_contents("php://input"); curl_setopt($curl, CURLOPT_POSTFIELDS, $body); endif; // Gather all response headers from Web Service B. $response_headers = array(); curl_setopt( $curl, CURLOPT_HEADERFUNCTION, function( $curl, $header ) use(&$res_hdr) { $h = explode( ": ", $header ); $res_hdr[$h[0]] = $h[1]; return strlen( $header ); } ); // Call the target URL $response = curl_exec($ch); $error_no = curl_errno($ch); $error_msg = curl_error($ch); $info = curl_getinfo($curl); curl_close($ch); if ( $response === false ) { http_response_code($info['http_code']); die( "Error {$error_no}: {$error_msg}" ); } else { // Forward some response headers from Server B to the client foreach ( array( 'Content-Type', 'Content-Transfer-Encoding', 'Content-Disposition', 'Accept-Ranges', /* ...other headers if any... */ ) as $header ) if ( isset($res_hdr[$header]) ) header( "{$header}: {$res_hdr[$header]}" ); echo $response; } ?>
Cấu hình .htaccess
để có một URL đẹp.
.htaccess
<IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^wsb/([a-zA-Z0-9-\._\/]+)/?$ forwarder.php?wsb_uri=https://web-service-b/$1 [NC,L,QSA] </IfModule>
Và ta có thể sử dụng REST API của Service B từ JavaScript trung gian qua Web Service A qua URL, ví dụ:
Client gọi Web Service A: https://web-service-a/wsb/api/query.php?param=test
…tương đương lời gọi tới Web Service B: https://web-service-b/api/query.php?param=test
.