How to Rails Sessions work?
Điều gì xảy ra nếu Rails app của bạn không biết được "Ai đã ghé thăm nó"? Nếu bạn không đặt ra được vấn đề rằng cùng một người dùng (user) request 2 trang (page) khác nhau? Và nếu tất cả dữ liệu (data) mà app lưu trữ bi mất ngay sau khi nó kịp phản hồi (response)? Đây là một vấn đề gặp phải ở ...
Điều gì xảy ra nếu Rails app của bạn không biết được "Ai đã ghé thăm nó"? Nếu bạn không đặt ra được vấn đề rằng cùng một người dùng (user) request 2 trang (page) khác nhau? Và nếu tất cả dữ liệu (data) mà app lưu trữ bi mất ngay sau khi nó kịp phản hồi (response)? Đây là một vấn đề gặp phải ở hầu hết các trang web tĩnh (static page). Tuy nhiên, hầu hết các ứng dụng cần phải có khả năng lưu trữ một số dữ liệu về user. Có thế đó là id của user, hoặc một ngôn ngữ ưa thích, ... "session is the perfect place to put this kind of data".
Session là dễ dàng để sử dụng, chỉ với câu lệnh sau:
session[:current_user_id] = @user.id
Nhưng đâu mới là "ma thuật" thật sự của nó? "Thế nào là session?" , "Làm thế nào để Rails biết để hiển thị đúng dữ liệu cho đúng user" và "Làm thế nào để biết nơi Session lưu trữ data user?"
Thế nào là Session?
Nói đơn giản: Session chỉ là nơi để lưu trữ data trong suốt quá trình của một request, mà có thể sử dụng trong các request sau này.
Bạn có thể set một vài data trong controller action như sau:
# Trong app/controllers/sessions_controller.rb def create # ... session[:current_user_id] = @user.id # ... end
Và get nó trong một controller khác:
# Trong app/controllers/users_controller.rb def index current_user = User.find_by id: session[:current_user_id] # ... end
Điều này không mấy là "hay ho". Điều thực sự cần ở đây là Sự phối hợp giữa browser của user và Rails app của bạn để tạo kết nối cho mọi thứ. Và tất cả bắt đầu với cookies Khi bạn request 1 trang web, sever có thể set 1 cookie và gửi response lại:
# Trên terminal, run command $ curl -I https://www.google.com.vn | grep Set-Cookie Set-Cookie: 1P_JAR=2018-01-20-16; expires=Mon, 19-Feb-2018 16:27:15 GMT; path=/; domain=.google.com.vn Set-Cookie: NID=122=HV3TuE6pFaDtWiBZS8ZOrZqK-17eW2hHEeQ-2L-ZS9bK1T8hJ3jPJ3i6GbgJDQLA1wdpxY2wYjh8S83HR6dskVfx-1gN9eRfFnQdsFUfOFwSBMYgxU-81bQzOHFR73Mw; expires=Sun, 22-Jul-2018 16:27:15 GMT; path=/; domain=.google.com.vn; HttpOnly
Browser của bạn sẽ lưu trữ cookies. Và cho đến khi cookies hết hạn (expires) thì mỗi khi bạn thực hiện request, browser sẽ gửi cookies trở lại server.
... > GET / HTTP/1.1 > User-Agent: curl/7.37.1 > Host: www.google.com.vn > Accept: */* > Cookie: NID=122=HV3TuE6pFaDtWiBZS8ZOrZqK-17eW2hHEeQ-2L-ZS9bK1T8hJ3jPJ3i6GbgJDQLA1wdpxY2wYjh8S83HR6dskVfx-1gN9eRfFnQdsFUfOFwSBMYgxU-81bQzOHFR73Mw; expires=Sun, 22-Jul-2018 16:27:15 GMT; path=/; domain=.google.com.vn; HttpOnly ...
Nhìn vào chuỗi Cookie sẽ thấy nó khó hiểu và vô nghĩa. Nhưng "Nó phải là như vậy", vì Thông tin bên trong cookies không dành cho user và Rails app của bạn có nhiệm vụ tìm ra cookies có ý nghĩa gì. Rails app thiết lập ra nó, do đó nó có cách để xử lý.
Tất cả những điều này liên quan gì đến Session?
Bạn có một cookies, bạn đưa data vào trong 1 request, và bạn sẽ nhận được data tương tự trong những trang tiếp theo. Vậy sự khác biệt giữa session và cookies là gì?
Theo mặc định, trong Rails không có nhiều sự khác biệt giữa chúng. Rails làm việc với cookies để đảm bảo sự an toàn hơn, ngoài ra thì cookies hoạt động theo cách mà bạn mong đợi. Nếu Rails app của bạn đặt một số data vào cookies, cùng data mới được sinh ra của cookies thì không thể phân biệt được session từ cookies. Tuy nhiên, cookies không phải lúc nào cũng là nơi dùng cho data của session, vì các lý do sau đây:
- Bạn chỉ có thể lưu trữ 4kb dữ liệu trong cookies (thường là đủ, tuy nhiên trong một số trường hợp thì không)
- Cookies luôn được gửi cùng với moi request mà bạn thực hiện (Big cookies làm cho request và response lớn hơn do vậy các trang web sẽ chậm hơn)
- Nếu vô tình bạn phơi bày secret_key_base, user của bạn có thể thay đổi data bạn đã put bên trong cookies
- Việc lưu trữ dữ liệu bên trong cookies có thể không an toàn
Nếu như bạn cẩn thận thì đây không phải vấn đề lớn. Tuy nhiên có thể vì một trong số lý do trên mà bạn không lưu trữ data của session bên trong 1 cookies, thì Rails có một vài nơi khác để giữ các session của user.
Lưu trữ session thay thế
Tất cả các cách lưu trữ session (session stores) mà không phải là lưu trữ session cookies (cookie session store) thì chúng đều làm việc khá giống nhau.
Chúng ta cùng nhau đi vào 1 ví dụ thực tế: Keeping track session with ActiveRecord
- Khi bạn gọi:
session[:current_user_id] = 1
trong app của bạn, và sessions này là không tồn tại
- Rails sẽ tạo ra 1 bản ghi mới (new record) trong table sessions với ID session là ngẫu nhiên (random session ID) Ví dụ: 09497d46978bf6f32265fefb5cc52264
- Nó sẽ lưu trữ {current_user_id: 1} (Base64-encoded) trong thuộc tính dữ liệu (data attribute) của bản ghi
- Và nó sẽ trả về session ID được tạo ra, 09497d46978bf6f32265fefb5cc52264, đến browser sử dụng Set-cookie
Trong lần tiếp theo bạn request trang web này:
- Browser sẽ gửi cùng 1 cookie tới app của bạn, sử dụng Cookie: header: Ví dụ:
Cookie: _my_app_session=09497d46978bf6f32265fefb5cc52264; path=/; HttpOnly
- Và khi gọi: session[:current_user_id] App của bạn sẽ tách session ID từ cookie, và nó sẽ tìm kiếm bản ghi tương ứng trong table sessions
- Sau đó nó sẽ trả về current_user_id
Cho dù bạn có đang lưu trữ session trong datadase, hay ở bất cứ nơi nào khác, hầu hết cách lưu trữ và sử dụng nó đều theo 1 quy trình. Cookie chỉ chứa session ID, và Rails app tìm kiếm data trng session store sử dụng ID này.
Cookie store, cache store, or database store?
Khi làm việc, việc lưu trữ session trong cookie là cách dễ dàng nhất để thực hiện, nó không cần bất kỳ cơ sở hạ tầng hay thiết lập nào khác. Tuy nhiên bạn cần di chuyển ra ngoài cookie session store, bạn có 2 sự lựa chọn:
- Storing sessions in the cache Bạn có thể đã từng sử dụng một cái gì đó như là Memcache để cache data của bạn. Nếu vậy cache store là nơi dễ dàng thứ hai để lưu trữ session data, vì nó đã được thiết lập sẵn. Bạn cũng không cần lo lắng về việc kiểm soát session store, bởi vì các session cũ hơn sẽ tự động bị kick khỏi cache nếu quá lớn. Và điều này được diễn ra rất nhanh chóng.
Tuy nhiên, nó không hoàn hảo đến vậy:
Nếu các session cũ hơn là thực sự quan trọng thì chắc chắn bạn không muốn loại bỏ chúng đi Session và cache data sẽ dành nhau không gian bộ nhớ nếu bạn không có đủ bộ nhớ cho chúng Trường hợp bạn cần phải reset cache của mình (có thể vì lý do nâng cấp Rails, và những cached data cũ là không còn chính xác nữa), không có cách nào khác là phải cho tất cả các session hết hạn (expiring session)
- Storing sessions in the database Nếu bạn muốn giữ session data cho đến khi chúng hết hạn, bạn có thể giữ chúng trong database, như là Redis, ActiveRecord hay bất cứ cái nào khác
Nhưng việc lưu trữ cơ sở dữ liệu session cũng có một số nhược điểm:
Với việc lưu trữ trong database, session là không được tự động clear Bạn phải biết cách làm thế nào mà database của bạn sẽ xử lý khi nó đầy session data Bạn sẽ phải chú ý hơn khi tao session data, vì có thể sẽ tạo ra những session vô ích (tạo ra nhưng không được sử dụng)