Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Làm chủ Phát triển API với Ruby on Rails

Ngày nay việc dựa nhiều vào API (application programming interfaces) là thực tế phổ biến. Không chỉ các dịch vụ lớn như Facebook, Twitter sử dụng chúng—API rất phổ biến do sự lan rộng của các framework client-side như React, Angular và nhiều khác. Ruby on Rails theo xu hướng này, phiên bản mới nhất trình bày tính năng mới cho phép tạo ứng dụng API-only.

Ban đầu chức năng này được đóng gói trong gem riêng tên rails-api, nhưng kể từ Rails 5, nó đã trở thành phần lõi của framework. Tính năng này cùng với ActionCable có lẽ là những thứ được mong đợi nhất, nên hôm nay chúng ta sẽ thảo luận về nó.

Bài viết bao gồm cách tạo ứng dụng Rails API-only, cấu trúc routes và controllers, phản hồi JSON, thêm serializers, thiết lập CORS (Cross-Origin Resource Sharing). Bạn cũng sẽ học về các tùy chọn bảo mật API và bảo vệ khỏi lạm dụng.

Mã nguồn bài viết có sẵn tại GitHub.

Tạo Ứng Dụng API-Only

Để bắt đầu, chạy lệnh sau:

Sẽ tạo ứng dụng Rails API-only tên RailsApiDemo. Nhớ rằng hỗ trợ option --api chỉ thêm từ Rails 5, nên đảm bảo bạn cài phiên bản này hoặc mới hơn.

Mở Gemfile và thấy nó nhỏ gọn hơn bình thường: các gem như coffee-rails, turbolinks, sass-rails đã biến mất.

File config/application.rb chứa dòng mới:

Nghĩa là Rails sẽ load tập middleware nhỏ hơn: ví dụ không có hỗ trợ cookies và sessions. Hơn nữa, nếu bạn generate scaffold, view và assets không được tạo. Thực tế, nếu kiểm tra thư mục views/layouts, file application.html.erb cũng mất.

Khác biệt quan trọng khác: ApplicationController kế thừa từ ActionController::API, không phải ActionController::Base.

Đó là cơ bản—tóm lại đây là ứng dụng Rails cơ bản bạn đã thấy nhiều lần. Bây giờ thêm vài model để có dữ liệu làm việc:

Không phức tạp: bài viết có title, body thuộc về user.

Đảm bảo associations đúng và thêm validation đơn giản:

models/user.rb

models/post.rb

Tuyệt! Bước tiếp theo load vài record mẫu vào bảng mới tạo.

Load Dữ Liệu Demo

Cách dễ nhất load dữ liệu là dùng file seeds.rb trong thư mục db. Tuy nhiên tôi lười (như nhiều lập trình viên) và không muốn nghĩ nội dung mẫu. Vậy tại sao không dùng gem faker tạo dữ liệu ngẫu nhiên: tên, email, từ hipster, văn bản "lorem ipsum", và nhiều hơn nữa.

Gemfile

Cài gem:

Sửa seeds.rb:

db/seeds.rb

Cuối cùng, load dữ liệu:

Phản Hồi Với JSON

Bây giờ cần routes và controllers để xây API. Thông thường nest routes API dưới path api/. Cũng thường cung cấp version API trong path, ví dụ api/v1/. Sau này nếu có breaking changes, chỉ cần tạo namespace mới (v2) và controller riêng.

Routes có thể như sau:

config/routes.rb

Sinh ra routes như:

Bạn có thể dùng scope thay namespace, nhưng mặc định nó sẽ tìm UsersController và PostsController trong thư mục controllers, không phải controllers/api/v1, nên cẩn thận.

Tạo thư mục api với nested v1 trong controllers. Điền controllers:

controllers/api/v1/users_controller.rb

controllers/api/v1/posts_controller.rb

Lưu ý không chỉ file controller đặt dưới api/v1, mà class cũng phải namespaced trong module Api và V1.

Câu hỏi tiếp theo: làm sao phản hồi đúng định dạng JSON? Bài viết sẽ thử hai giải pháp: gem jBuilder và active_model_serializers. Trước khi sang phần sau, thêm chúng vào Gemfile:

Gemfile

Chạy:

Sử Dụng Gem jBuilder

jBuilder là gem phổ biến do đội Rails maintain, cung cấp DSL (domain-specific language) đơn giản cho phép định nghĩa cấu trúc JSON trong view.

Giả sử muốn hiển thị tất cả bài viết khi user hit action index:

controllers/api/v1/posts_controller.rb

Chỉ cần tạo view tên theo action tương ứng với đuôi .json.jbuilder. View phải đặt dưới path api/v1:

views/api/v1/posts/index.json.jbuilder

json.array! duyệt mảng @posts. json.id, json.title, json.body sinh key với tên tương ứng, giá trị là tham số. Truy cập https://localhost:3000/api/v1/posts.json sẽ thấy output như:

Nếu muốn hiển thị tác giả cho mỗi bài viết? Đơn giản:

Output thay đổi thành:

Nội dung file .jbuilder là code Ruby thuần, có thể dùng các operation cơ bản như bình thường.

Lưu ý jBuilder hỗ trợ partials như view Rails thường, nên có thể dùng:

Rồi tạo file views/api/v1/posts/_post.json.jbuilder với nội dung:

Như vậy jBuilder dễ dàng và tiện lợi. Tuy nhiên, thay vào đó bạn có thể dùng serializers, hãy thảo luận ở phần sau.

Sử Dụng Serializers

Gem rails_model_serializers do team ban đầu quản lý rails-api tạo ra. Theo tài liệu, rails_model_serializers mang convention over configuration vào JSON generation. Cơ bản, bạn định nghĩa field nào dùng khi serialize (tức generate JSON).

Serializer đầu tiên:

serializers/post_serializer.rb

Ở đây nói rằng tất cả field này phải có trong JSON kết quả. Bây giờ các method như to_json và as_json gọi trên post sẽ dùng config này và trả về nội dung đúng.

Để thấy hoạt động, sửa action index:

controllers/api/v1/posts_controller.rb

as_json sẽ tự động gọi trên object @posts.

Còn user? Serializers cho phép chỉ định relations, giống như model. Hơn nữa, serializers có thể nest:

serializers/post_serializer.rb

Khi serialize post, sẽ tự động chứa key user lồng với id và name. Nếu sau tạo serializer riêng cho user loại trừ :id:

serializers/post_serializer.rb

Thì @user.as_json không trả user id. Nhưng @post.as_json vẫn trả cả name và id của user, hãy nhớ điều này.

Bảo Mật API

Nhiều trường hợp không muốn ai cũng thực hiện bất kỳ action nào qua API. Hãy trình bày kiểm tra bảo mật đơn giản và bắt user gửi token khi tạo/xóa bài viết.

Token có vòng đời không hạn và tạo khi user đăng ký. Trước hết, thêm cột token vào bảng users:

Index này đảm bảo duy nhất vì không thể có hai user cùng token:

db/migrate/xyz_add_token_to_users.rb

Áp dụng migration:

Thêm callback before_save:

models/user.rb

Phương thức private generate_token tạo token trong vòng lặp vô tận và kiểm tra xem có unique không. Sớm nhất khi tìm thấy token unique, trả về:

models/user.rb

Bạn có thể dùng thuật toán khác sinh token, ví dụ dựa trên MD5 hash của tên user và salt.

Đăng Ký User

Cần cho user đăng ký, nếu không họ không lấy được token. Tôi không muốn đưa view HTML vào app, nên thêm API method mới:

controllers/api/v1/users_controller.rb

Ý tưởng tốt là trả HTTP status code có ý nghĩa để developer hiểu chính xác chuyện gì xảy ra. Bây giờ bạn có thể cung cấp serializer mới cho user hoặc dùng file .json.jbuilder. Tôi thích cách sau (đó là lý do không pass option :json cho render), nhưng bạn tự do chọn. Lưu ý token không được serialize luôn, ví dụ khi trả danh sách tất cả user—nó phải giữ an toàn!

views/api/v1/users/create.json.jbuilder

Bước tiếp theo test xem mọi thứ hoạt động. Có thể dùng lệnh curl hoặc viết code Ruby. Vì bài về Ruby, tôi chọn code.

Test Đăng Ký User

Để thực hiện HTTP request, dùng gem Faraday, cung cấp interface chung qua nhiều adapter (mặc định Net::HTTP). Tạo file Ruby riêng, include Faraday, setup client:

api_client.rb

Các option dễ hiểu: chọn default adapter, set request URL https://localhost:300/api/v1/users, đổi content type application/json, cung cấp body request.

Response server chứa JSON, để parse dùng gem Oj:

api_client.rb

Ngoài response đã parse, cũng hiển thị status code để debug.

Chạy script:

Và lưu token nhận được—sẽ dùng ở phần sau.

Xác Thực Với Token

Để bắt buộc xác thực token, dùng phương thức authenticate_or_request_with_http_token. Nó thuộc module ActionController::HttpAuthentication::Token::ControllerMethods, nhớ include:

controllers/api/v1/posts_controller.rb

Thêm before_action và method tương ứng:

controllers/api/v1/posts_controller.rb

Bây giờ nếu token không set hoặc không tìm thấy user với token đó, lỗi 401 sẽ trả về, ngăn action thực thi.

Lưu ý giao tiếp client-server phải qua HTTPS, vì nếu không token dễ bị giả mạo. Giải pháp này không lý tưởng, nhiều trường hợp nên dùng OAuth 2. Có ít nhất hai gem đơn giản hóa hỗ trợ tính năng này: Doorkeeper và oPRO.

Tạo Bài Viết

Để thấy xác thực hoạt động, thêm action create vào PostsController:

controllers/api/v1/posts_controller.rb

Tận dụng serializer để hiển thị JSON đúng. @user đã set trong before_action.

Test với code đơn giản:

api_client.rb

Thay đối số token_auth bằng token nhận khi đăng ký, chạy script.

Xóa Bài Viết

Xóa bài viết tương tự. Thêm action destroy:

controllers/api/v1/posts_controller.rb

Chỉ cho user xóa bài viết họ sở hữu. Nếu xóa thành công, status 204 (no content) trả về. Hoặc có thể phản hồi với id bài viết vừa xóa vì vẫn còn trong memory.

Code test tính năng mới:

api_client.rb

Thay id bài viết bằng số phù hợp cho bạn.

Thiết Lập CORS

Nếu muốn cho các web service khác truy cập API (từ client-side), CORS (Cross-Origin Resource Sharing) phải setup đúng. Cơ bản, CORS cho phép web app gửi AJAX request đến third-party services. May mắn là có gem rack-cors giúp setup dễ dàng. Thêm vào Gemfile:

Gemfile

Cài đặt:

Cung cấp config trong file config/initializers/cors.rb. File này đã tạo sẵn với ví dụ dùng. Cũng tìm thấy tài liệu chi tiết trên trang gem.

Cấu hình sau, ví dụ, cho phép bất kỳ ai truy cập API bằng bất kỳ method nào:

config/initializers/cors.rb

Ngăn Chặn Lạm Dụng

Điều cuối cùng trong hướng dẫn: bảo vệ API khỏi lạm dụng và tấn công denial of service. Có gem hay tên rack-attack (do người Kickstarter tạo) cho phép blacklist/whitelist client, ngăn chặn flood request, và hơn thế.

Thêm gem vào Gemfile:

Gemfile

Cài đặt:

Cung cấp config trong file initializer rack_attack.rb. Tài liệu gem liệt kê các option và gợi ý use case. Đây là config mẫu giới hạn ai trừ bạn truy cập service và limit tối đa 5 request/giây:

config/initializers/rack_attack.rb

Cần thêm include RackAttack làm middleware:

config/application.rb

Kết Luận

Đến cuối bài viết. Hy vọng bạn tự tin hơn khi xây API với Rails! Lưu ý đây không phải lựa chọn duy nhất—giải pháp phổ biến khác là framework Grape, bạn có thể quan tâm kiểm tra.

Đừng ngần ngại đặt câu hỏi nếu chưa rõ. Cảm ơn bạn đã theo dõi, happy coding!