Sau buổi trình bày trong hội thảo Giải pháp phát triển ứng dụng di động đa nền tảng và Ứng dụng FaceBook – Họ đã làm điều đó như thế nào ? do cộng đồng Webmaster Việt Nam phối hợp cùng trung tâm tin học Hoàng Nguyễn tổ chức tôi nhận được một số câu hỏi liên quan đến REST và RESTful Services. Trong bài viết này, tôi sẽ trình bày thêm về dịch vụ này và cách triển khai một dịch vụ RESTful Services trên nền CodeIgniter(CI). Việc sử dụng các mã nguồn khác cũng tương tự nếu bạn nắm được ý chính.
Phụ mục
Điều kiện
Để có thể hiểu được bài viết này bạn cần những nền tảng sau:
- Bạn đã cài đặt dịch vụ web trên máy tính cá nhân hoặc có web đang chạy trên internet, đồng thời bạn phải có quyền quản lý file trên đó.
- Bạn hiểu về CodeIgniter và có thể viết được nó.
- Bạn cũng cần biết về RESTful Services
Phần 1: Tạo RESTful API
Bước1: Cài đặt demo
Đầu tiên, bạn cần download codeigniter-restserver từ GitHub và chép nó vào thư mục chạy web của bạn. Sau khi giải nén, bạn sẽ thấy bộ mã nguồn CodeIgniter đã được chuẩn bị sẵn cho việc trả nghiệm thực tế của bạn.
Tiếp theo, bạn mở application/config/config.php và cấu hình lại base_url tương ứng với thư mục web của bạn để có thể thực thi được ứng dụng.
Bước 2: URL
Một khi đã giải nén vào thư mục chạy web và cấu hình xong base_url, bạn đã có thể truy cập vào web với đường dẫn tương ứng. Ví dụ:
http://localhost/restserver
Khi truy cập đường dẫn trên, bạn sẽ thấy các liên kết dẫn đến các ví dụ đã được thực hiện sẵn cho dịch vụ RESTful. Theo cấu trúc đã được xây dựng của CodeIgniter, bạn có thể tìm thấy file này ở application/controllers/example_api.php. Hãy xem phân tích dưới đây:
Đường dẫn được ví dụ trên có vẻ rất giống với cấu trúc của một URL trên CodeIgniter với một Controller và một method. Tuy nhiên, như ở trên bạn thấy, method trong CI được hiểu là “Resource”. Resource cơ bản là một danh từ được sử dụng trong ứng dụng của bạn để phân biệt tài nguyên truy cập (thêm, sửa, xóa) dưới hình thức các URL chứa biến hoặc các HTTP headers.
Mặc định, dữ liệu xuất ra dưới dạng xml, tuy nhiên bạn có thể lựa chọn một kiểu định dạng khác phù hợp với yêu cầu của client (lưu ý là chúng ta đang làm tầng server để cung cấp các tài nguyên cho một đối tượng sử dụng khác). Một URL chứa đủ các biến khai báo có thể được mô tả như sau:
Với CodeIgniter, bạn chỉ cần đặt giá trị của biến tham chiếu tương ứng vào URL, nhưng REST cần cung cấp đầy đủ những thông tin cần thiết hơn. Như ví dụ trên, bạn có thể cung cấp tham biến ở phía trước, tham trị ở phía sau và ngăn cách nhau bởi một dấu “/” như hình trên.
Giá trị cuối cùng trong chuỗi URL là tham biến “format”. Giá trị này sẽ giúp xác định client muốn nhận dữ liệu output ra như thế nào. Chúng ta có các kiểu định dạng đi kèm với ví dụ như hình dưới đây:
Tùy theo nhu cầu của mình, anh chàng developer khi viết ứng dụng truy xuất vào REST API của bạn có thể nhận được định dạng dữ liệu như mong muốn. Với REST API, bạn có thể nhận được các loại dữ liệu sau:
- xml – hầu hết các ngôn ngữ lập trình đều đọc được thằng này.
- json – Rất hữu ích cho các lập trình viên Javascript và ngay cả PHP
- csv – dễ dàng mở với một chương trình dạng bảng tính (spreadsheet programs)
- html – trình bày dạng table trong HTML
- php – Đại diện cho mã PHP và có thể được đọc bằng eval();
- serialize – dữ liệu đã được serialized và có thể được unserialize bằng PHP
Để kiểm tra các định dạng trên bạn không cần phải tạo ra một công cụ định dạng tương ứng khi gọi URL, bạn có thể dễ dàng kiểm tra các định dạng trên đơn giản bằng trình duyệt một cách dễ dàng.
Bước 3: Code
Bây giờ, chúng ta sẽ tạo một tập tin application/controllers/example_api.php và bắt đầu xây dựng RESTful API riêng của mình.
REST_Controller
Trong mô hình MVC, Controller giữ vai trò bộ điều khiển trung tâm về mặt logic. Nó được gọi khi người dùng gửi một yêu cầu đến để xử lý yêu cầu đó, lấy dữ liệu tương ứng và xuất ra màn hình.
Với CodeIgniter thông thường, để tạo một controller chúng ta có thể sử dụng đoạn code sau:
[code language=”php”] <?phpclass Example_api extends Controller {
}
[/code]
Nhưng với RESTful bạn sẽ kế thừa từ một Controller khác được xây dựng và đưa sẵn trong ví dụ của chúng ta:
[code language=”php”] <?phprequire(APPPATH’.libraries/REST_Controller.php’);
class Example_api extends REST_Controller {
}
[/code]
Làm việc với Resource
Bây giờ, việc tạo một controller rỗng đã hoàn chỉnh, việc tiếp theo chúng ta cần cung cấp các phương thức xử lý (method) tương ứng trong CodeIgniter. Tuy nhiên, như đã nói ở trên – chúng ta gọi nó là resource. Phần này những bạn đã biết CodeIgniter sẽ không cảm thấy lạ.
Về căn bản, bạn sẽ tận dụng resource (ở đây và user) và phương thức HTTP sử dụng để kết hợp tạo ra tên phương thức tương ứng trong CodeIgniter. Trong ví dụ này, tôi sử dụng 2 resource khác nhau là user – để tương tác với một người dùng và users – để tương tác với danh sách người dùng. Theo đó chúng ta có thể thiết lập các method trong CI như sau:
[code language=”php”]
<?php
require(APPPATH’.libraries/REST_Controller.php’);
class Example_api extends REST_Controller {
function user_get()
{
// respond with information about a user
}
function users_get()
{
// respond with information about several users
}
}
[/code]
Điều này có vẻ hơi lạ với nhiều người, nhưng nó sẽ cung cấp cho bạn một sự linh động khi xử lý thông tin tùy thuộc vào phương thức HTTP sử dụng. Nếu một ai đó cố gắng gọi một API với một phương thức không được phép(ví dụ PUT, DELETE) kêt quả trả về sẽ là 404. Các phương thức HTTP có thể sử dụng là gì ?
Hãy nhìn vào danh sách sau:
- GET: sử dụng để lấy dữ liệu từ API. Phương thức này được sử dụng bởi trình duyệt khi người dùng gõ một URL và click “Go” hoặc click chuột lên một liên kết. Đây là phương thức hoàn hảo nếu bạn cần lấy thông tin của một người dùng từ API.
- POST: sử dụng để cập nhật một resource có sẵn, trình duyệt có thể sử dụng để đưa những định dạng form lên Internet. Nói chung đối với developer thì phương thức này và GET là không hề lạ lẫm gì.
- PUT: ít được sử dụng và ít được hỗ trợ bởi các trình duyệt. PUT thường được sử dụng để tạo mới một tài nguyên.
- DELETE: cũng ít được trình duyệt hỗ trợ và ít được sử dụng. DELETE dùng để xóa đi một tài nguyên nào đó.
Như vậy, để sử dụng đầy đủ các phương thức, chúng ta sẽ cần khai báo các phương thức sau trong controller của mình:
[code language=”php”] require(APPPATH’.libraries/REST_Controller.php’);class Example_api extends REST_Controller {
function user_get()
{
// respond with information about a user
}
function user_put()
{
// create a new user and respond with a status/errors
}
function user_post()
{
// update an existing user and respond with a status/errors
}
function user_delete()
{
// delete a user and respond with a status/errors
}
}
[/code]
Dĩ nhiên, bạn cũng cần khai báo tương tự đối với resource users nữa.
Đọc và trả dữ liệu
Bây giờ bạn đã xây dựng xong các hàm cấu trúc (structure) của hệ thống. Việc tiếp theo là thêm các giải thuật cần thiết để các phương thức này có thể hoạt động. Hãy xem tôi viết thêm các đoạn mã cho các hàm trên:
[code language=”php”]
require(APPPATH’.libraries/REST_Controller.php’);
class Example_api extends REST_Controller {
function user_get()
{
$data = array(‘returned: ‘. $this->get(‘id’));
$this->response($data);
}
function user_post()
{
$data = array(‘returned: ‘. $this->post(‘id’));
$this->response($data);
}
function user_put()
{
$data = array(‘returned: ‘. $this->put(‘id’));
$this->response($data;
}
function user_delete()
{
$data = array(‘returned: ‘. $this->delete(‘id’));
$this->response($data);
}
}
[/code]
Ở đây bạn sẽ thấy xuất hiện thêm một số phương thức mới của đối tượng $this. Chúng ta sẽ cùng giải thích chúng.
$this->get()
Sử dụng để lấy giá trị từ một biến nào đó nếu HTTP sử dụng phương thức GET. Ví dụ: index.php/example_api/user?id=1 bạn có thể dùng $this->get(‘id’) để lấy về giá trị là 1. Bạn cũng có thể khai báo theo các của CI đều được. Ví dụ như: index.php/example_api/user/id/1.
$this->post()
Đây là một tham chiếu của $this->input->post() – là cách CI nhận giá trị của $_POST một cách an toàn để tránh lỗi XSS.
$this->put()
Đọc giá trị được cung cấp qua phương thức PUT được cấu hình trong HTTP headers hoặc qua cURL.
$this->delete()
Cái này tôi đoán là nó đọc giá trị qua phương thức DELETE được gửi qua HTTP headers hoặc cURL.
$this->response()
Gửi dữ liệu đến trình duyệt theo định dạng đã được chọn. Mặc định $this->response() sẽ trả về định dạng XML như tôi đã nói ở trên. Bạn có thể tùy chỉnh để gửi dữ liệu tương ứng cho người dùng. Ví dụ, nếu ID tương ứng không được tìm thấy trong cơ sở dữ liệu, bạn có thể gửi cho người dùng như sau: $this->response(array(‘error’ => ‘User not found.’), 404);.
Bước 4: Làm việc với Model
Từ đầu chúng ta làm việc với một CI không kết nối Database, trong phần tiếp theo chúng ta sẽ làm việc với REST nhưng có kết nối với ứng dụng cơ sở dữ liệu. Bạn hãy xem các file quan trọng được tôi chỉ ra sau đây:
Bạn lấy 2 file này và copy vào thư mục tương ứng trong dự án của bạn. Sau đó chúng ta sẽ tiếp tục tạo một Controller mới để làm API có kết nối và lấy dữ liệu từ cơ sở dữ liệu:
[code language=”php”]<?php
require(APPPATH.’/libraries/REST_Controller.php’);
class Api extends REST_Controller
{
function user_get()
{
if(!$this->get(‘id’))
{
$this->response(NULL, 400);
}
$user = $this->user_model->get( $this->get(‘id’) );
if($user)
{
$this->response($user, 200); // 200 being the HTTP response code
}
else
{
$this->response(NULL, 404);
}
}
function user_post()
{
$result = $this->user_model->update( $this->post(‘id’), array(
‘name’ => $this->post(‘name’),
’email’ => $this->post(’email’)
));
if($result === FALSE)
{
$this->response(array(‘status’ => ‘failed’));
}
else
{
$this->response(array(‘status’ => ‘success’));
}
}
function users_get()
{
$users = $this->user_model->get_all();
if($users)
{
$this->response($users, 200);
}
else
{
$this->response(NULL, 404);
}
}
}
?>
[/code]
Ví dụ trên cho bạn thấy một mô hình hoạt động chung của REST API với các model trong CI. Cách viết, cách gọi model trong CodeIgniter vẫn không thay đổi. Trong ví dụ đầu tiên, tôi chọn một URL có id=xxx và đi qua nó xuyên suốt các ví dụ để bạn hình dung mô hình hoạt động. Nếu dữ liệu được tìm thấy, chúng ta sẽ trả lại dữ liệu qua $this->response(); với status là 200, ngược lại chúng ta trả lại 1 response NULL với status là 404.
Bước 5: Bảo mật API
Bất kỳ ai cũng có thể đọc dữ liệu từ cơ sở dữ liệu của bạn hay chỉ có một số website, một số nền tảng nhất định được phép sử dụng ? Nếu bạn muốn bảo mật, bạn có thể sử dụng username và password để sử dụng cho việc đăng nhập. Hãy truy cập “application/config/rest.php”
[code language=”php”]
/*
|————————————————————————–
| REST Login
|————————————————————————–
|
| Is login required and if so, which type of login?
|
| ” = no login required, ‘basic’ = relatively secure login, ‘digest’ = secure login
|
*/
$config[‘rest_auth’] = ‘basic’;
[/code]
Bạn có thể sửa giá trị $config[‘rest_auth’] thành các giá trị tương ứng với mức độ bảo mật bạn yêu cầu. Những mức độ đó là gì ?
- none: bất kỳ ai cũng có thể truy cập API của bạn.
- basic: một phương pháp đăng nhập không an toàn và tôi khuyến cáo chỉ nên sử dụng trên mạng nội bộ.
- digest: một phương pháp bảo mật đòi hỏi phải có tài khoản và mật khẩu tương ứng mới có thể sử dụng được API.
Nếu bạn sử dụng digest, bạn cần cấu hình thêm tên tài khoản và mật khẩu như sua
[code language=”php”]/*
|————————————————————————–
| REST Login usernames
|————————————————————————–
|
| Array of usernames and passwords for login
|
| array(‘admin’ => ‘1234’)
|
*/
$config[‘rest_valid_logins’] = array(‘admin’ => ‘1234’);
[/code]
Bạn có thể tạo nhiều phần tử mảng hoặc có thể lấy mảng từ database tùy bạn.
Tới đây bạn đã viết xong phần Service API sử dụng RESTful.
Phần 2: tương tác với RESTful Services
Sau khi hoàn thành việc xây dựng service để sử dụng, chúng ta sẽ viết tiếp phần tương tác để bạn có thể lấy, cập nhật dữ liệu. Dưới đây tôi xin giới thiệu với bạn một vài cách tương tác với RESTful mà bạn có thể ứng dụng được ngay.
file_get_contents()
Cách đơn giản nhất để bạn lấy dữ liệu đó là sử dụng hàm file_get_contents được PHP cung cấp sẵn. Đây là cách mà tôi cũng thấy nhiều người sử dụng khi viết ứng dụng dựa trên nền tảng API do FaceBook cung cấp:
[code language=”php”]
$user = json_decode(
file_get_contents(‘http://example.com/index.php/api/user/id/1/format/json’)
);
echo $user->name;
[/code]
Nếu bạn sử dụng authentication với mức bảo mật là digest, bạn có thể thử với đoạn mã sau:
[code language=”php”]
$user = json_decode(
file_get_contents(‘http://admin:1234@example.com/index.php/api/user/id/1/format/json’)
);
echo $user->name;
[/code]
Tuy nhiên, có một vài vấn đề sẽ xảy ra ở đây: cách duy nhất để cấu hình lại header gửi đi là bạn phải cấu hình thủ công thông qua hàm stream_context_create() do PHP xây dựng sẵn. Điều này có vẻ rất phức tạp đối với các lập trình viên không thường xuyên làm việc với HTTP header hoặc những người ít sử dụng tới hàm này. Ngoài ra một nhược điểm nữa đó là dữ liệu bạn nhận được sẽ ở dạng thô và bạn phải mất thêm thời gian để định dạng lại nó.
cURL
cURL là cách linh hoạt nhật để tương tác với một REST API – đúng như cách nó được thiết kế: bạn có thể cấu hình lại HTTP Header, các thông số gửi kèm, các biến và nhiều hơn thế nữa. Dưới đây là một ví dụ tôi sử dụng cURL để gửi dữ liệu đến REST API sử dụng phương thức POST:
[code language=”php”]function native_curl($new_name, $new_email)
{
$username = ‘admin’;
$password = ‘1234’;
// Alternative JSON version
// $url = ‘http://twitter.com/statuses/update.json’;
// Set up and execute the curl process
$curl_handle = curl_init();
curl_setopt($curl_handle, CURLOPT_URL, ‘http://localhost/restserver/index.php/example_api/user/id/1/format/json’);
curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl_handle, CURLOPT_POST, 1);
curl_setopt($curl_handle, CURLOPT_POSTFIELDS, array(
‘name’ => $new_name,
’email’ => $new_email
));
// Optional, delete this line if your API is open
curl_setopt($curl_handle, CURLOPT_USERPWD, $username . ‘:’ . $password);
$buffer = curl_exec($curl_handle);
curl_close($curl_handle);
$result = json_decode($buffer);
if(isset($result->status) && $result->status == ‘success’)
{
echo ‘User has been updated.’;
}
else
{
echo ‘Something has gone wrong’;
}
}
[/code]
Việc tương tác với API của bạn có vẻ hoạt động tốt nhưng nó cũng gặp phải một số vấn đề:
- Sử dụng một cú pháp xấu xí, khó đọc, khó nhớ.
- cURL không được cài đặt sẵn trên nhiều máy chủ.
Để giải quyết vấn đề này, thư viện cURL đã được phát triển cho CodeIgniter và bạn có thể tìm download trên mạng. Sử dụng thư viện của CI, bạn có thể viết lại đoạn mã trên như sau:
[code language=”php”] function ci_curl($new_name, $new_email){
$username = ‘admin’;
$password = ‘1234’;
$this->load->library(‘curl’);
$this->curl->create(‘http://localhost/restserver/index.php/example_api/user/id/1/format/json’);
// Optional, delete this line if your API is open
$this->curl->http_login($username, $password);
$this->curl->post(array(
‘name’ => $new_name,
’email’ => $new_email
));
$result = json_decode($this->curl->execute());
if(isset($result->status) && $result->status == ‘success’)
{
echo ‘User has been updated.’;
}
else
{
echo ‘Something has gone wrong’;
}
}
[/code]
REST client library
Là một người phát triển code trên nền CodeIgniter, bạn cũng có thể sử dụng thư viện REST client library để tương tác với API đã được viết sẵn ở trên. Nếu bạn sử dụng REST client library, mọi chuyện sẽ đơn giản hơn rất nhiều.
[code language=”php”]
function rest_client_example($id)
{
$this->load->library(‘rest’, array(
‘server’ => ‘http://localhost/restserver/index.php/example_api/’,
‘http_user’ => ‘admin’,
‘http_pass’ => ‘1234’,
‘http_auth’ => ‘basic’ // or ‘digest’
));
$user = $this->rest->get(‘user’, array(‘id’ => $id), ‘json’);
echo $user->name;
}
[/code]
Ở đây tôi sử dụng phương thức GET, gửi đi biến id và yêu cầu trả về dữ liệu dạng json. Bạn có thể chuyển dữ liệu nhận được về dạng xml, json, serialize, php, csv hoặc các định dạng dữ liệu khác bạn thích như sau:
[code language=”php”] $user = $this->rest->get(‘user’, array(‘id’ => $id), ‘application/json’);[/code]
Như bạn thấy ở trên, chúng ta sử dụng $this->rest->get() để lấy dữ liệu về dạng GET, bạn cũng có thể dễ dàng đoán ra các phương thức khác có thể được dùng để gửi dữ liệu cần thiết lên API như $this->rest->post(), $this->rest->put(), $this->rest->delete(). Hoặc bạn có thể xem đầy đủ trong REST_Controller.php
Bạn cũng sẽ cần sử dụng var_dump() để kiểm tra xem dữ liệu bạn nhận được có đúng với định dạng mong muốn hay chưa. Việc chuyển đổi dữ liệu qua lại đôi lúc trả về là mảng(array), đôi lúc lại là đối tượng (object) tùy thuộc vào cách nó được chuyển đổi trong PHP. Nếu kiểu định dạng MIME-type không được hỗ trợ, nó sẽ được định dạng kiểu văn bản thông thường. Các bạn chú ý phần này nhé !
Bonus: Twitter
Sử dụng REST library mà tôi đã nói ở trên bạn có thể lấy và gửi thông tin lên các RESTful services như Twitter hay FaceBook. Dưới đây là một gợi ý cho việc lấy thông tin người dùng trên Twitter:
[code language=”php”]$this->load->library(‘rest’, array(‘server’ => ‘http://twitter.com/’));
$user = $this->rest->get(‘users/show’, array(‘screen_name’ => ‘philsturgeon’));
‘server’ => ‘http://twitter.com/’,
‘http_user’ => ‘username’,
‘http_pass’ => ‘password’,
‘http_auth’ => ‘basic’
));
$user = $this->rest->post(‘statuses/update.json’, array(‘status’ => ‘Using the REST client to do stuff’));
[/code]
Nhìn vào đoạn mã trên, bạn sẽ nhận ra vài điểm đặc biệt trong Twitter API:
- Họ sử dụng phần mở rộng .json thay cho /format/json trong chuỗi URL. Một vài cái đòi hỏi phần mở rộng này nhưng một vài cái lại không. Tuy nhiên, tốt nhất là cứ ném phần mở rộng vào cho chắc ăn.
- Họ chỉ hỗ trợ GET/POST nhưng hiện tại sắp hỗ trợ DELETE
- Họ không chỉ có duy nhất một tài nguyên cho một URL, ví dụ users/search là một phương thức.Ngoại trừ lists
Nếu bạn muốn biết thêm, bạn có thể đọc Twitter API document.
Dịch từ: net.tutsplus.com