Điều gì nếu ứng dụng Rails của bạn không thể nói ai đang truy cập? Nếu bạn không có ý tưởng rằng cùng một người request hai trang khác nhau? Nếu tất cả dữ liệu bạn lưu biến mất ngay khi bạn trả về phản hồi?
Điều đó có thể ổn cho trang web chủ yếu tĩnh. Nhưng hầu hết app cần có thể lưu một chút dữ liệu về user. Có thể là user id, hoặc ngôn ngữ ưa thích, hoặc liệu họ có luôn muốn thấy phiên bản desktop của trang trên iPad không.
session là nơi hoàn hảo để đặt loại dữ liệu này. Những mẩu dữ liệu nhỏ bạn muốn giữ quanh cho nhiều hơn một request.
Session dễ dùng:
session[:current_user_id] = @user.id
Nhưng chúng có thể hơi thần kỳ. Session là gì? Làm sao Rails biết hiển thị dữ liệu đúng cho người đúng? Và bạn quyết định nơi giữ dữ liệu session như thế nào?
Session là gì?
Một session chỉ là nơi lưu dữ liệu trong một request mà bạn có thể đọc trong request sau.
Bạn có thể đặt dữ liệu trong action controller:
def create # ... session[:current_user_id] = @user.id # ... end
Và đọc nó trong cái khác:
def index current_user = User.find_by_id(session[:current_user_id]) # ... end
Nó có thể không trông thú vị. Nhưng nó cần sự phối hợp giữa trình duyệt user và ứng dụng Rails để mọi thứ kết nối. Và tất cả bắt đầu với cookie.
Khi bạn request trang web, máy chủ có thể đặt cookie khi phản hồi lại:
~ jweiss$ curl -I https://www.google.com | grep Set-Cookie Set-Cookie: NID=67=J2xeyegolV0SSneukSOANOCoeuDQs7G1FDAK2j-nVyaoejz-4K6aouUQtyp5B_rK3Z7G-EwTIzDm7XQ3_ZUVNnFmlGfIHMAnZQNd4kM89VLzCsM0fZnr_N8-idASAfBEdS; expires=Wed, 16-Sep-2015 05:44:42 GMT; path=/; domain=.google.com; HttpOnly
Trình duyệt của bạn sẽ lưu những cookie đó. Và cho đến khi cookie hết hạn, mỗi lần bạn làm request, trình duyệt sẽ gửi cookie ngược lại máy chủ:
... > GET / HTTP/1.1 > User-Agent: curl/7.37.1 > Host: www.google.com > Accept: */* > Cookie: NID=67=J2xeyegolV0SSneukSOANOCoeuDQs7G1FDAK2j-nVyaoejz-4K6aouUQtyp5B_rK3Z7G-EwTIzDm7XQ3_ZUVNnFmlGfIHMAnZQNd4kM89VLzCsM0fZnr_N8-idASAfBEdS; expires=Wed, 16-Sep-2015 05:44:42 GMT; path=/; domain=.google.com; HttpOnly ...
Nhiều cookie chỉ trông như rác rưởi. Và chúng nên như vậy. Vì thông tin bên trong cookie không dành cho user. Ứng dụng Rails của bạn chịu trách nhiệm tìm ra cookie nghĩa là gì. App của bạn đặt nó, vậy app của bạn có thể đọc nó.
Điều này liên quan gì đến session?
Vậy, bạn có cookie. Bạn đặt dữ liệu trong một request, và bạn nhận lại dữ liệu giống hệt trong request sau. Sự khác biệt giữa đó và session là gì?
Mặc định, trong Rails, không có nhiều sự khác biệt. Rails làm một chút việc với cookie để làm nó an toàn hơn. Nhưng bên cạnh đó, nó hoạt động như bạn mong đợi. App Rails đặt dữ liệu vào cookie, cùng dữ liệu ra khỏi cookie. Nếu đây là tất cả, sẽ không có lý do phân biệt session với cookie.
Nhưng cookie không phải lúc nào cũng là câu trả lời cho dữ liệu session:
Bạn chỉ có thể lưu khoảng 4kb dữ liệu trong cookie.
Điều này thường đủ, nhưng đôi khi không.
Cookie được gửi kèm mọi request bạn làm.
Cookie to nghĩa là request và response to, nghĩa là trang web chậm hơn.
Nếu bạn vô tình lộ
secret_key_base, user của bạn có thể thay đổi dữ liệu bạn đặt bên trong cookie.Khi điều này bao gồm thứ như
current_user_id, bất kỳ ai có thể trở thành user nào họ muốn!Lưu loại dữ liệu sai bên trong cookie có thể không an toàn.
Nếu bạn cẩn thận, những điều này không phải vấn đề lớn.
Nhưng khi bạn không thể lưu dữ liệu session bên trong cookie vì một trong những lý do đó, Rails có vài nơi khác để giữ session của bạn:
Alternative session stores
Tất cả session store không phải cookie session store hoạt động theo cách khá giống nhau. Nhưng dễ nhất nghĩ về dùng ví dụ thực.
Nếu bạn theo dõi session với ActiveRecord:
Khi bạn gọi
session[:current_user_id] = 1trong app, và session chưa tồn tại:Rails sẽ tạo bản ghi mới trong bảng
sessionsvới session ID ngẫu nhiên (giả sử09497d46978bf6f32265fefb5cc52264).Nó sẽ lưu
{current_user_id: 1}(Base64-encoded) trong thuộc tínhdatacủa bản ghi đó.Và nó sẽ trả session ID tạo ra,
09497d46978bf6f32265fefb5cc52264, cho trình duyệt dùngSet-Cookie.
Lần sau bạn request trang,
Trình duyệt gửi cookie đó đến app, dùng header
Cookie:.(như này:
Cookie: _my_app_session=09497d46978bf6f32265fefb5cc52264;
path=/; HttpOnly)Khi bạn gọi
session[:current_user_id]:App lấy session ID từ cookie, và tìm bản ghi trong bảng
sessions.Sau đó, nó trả
current_user_idtừ thuộc tínhdatacủa bản ghi đó.
Dù bạn lưu session trong database, Memcached, Redis, hoặc đâu khác, chúng hầu hết theo cùng quy trình. Cookie của bạn chỉ chứa session ID, và ứng dụng Rails tìm dữ liệu trong session store dùng ID đó.
Cookie store, cache store, hay database store?
Khi nó hoạt động, lưu session trong cookie là cách dễ dàng nhất. Nó không cần hạ tầng hoặc thiết lập thêm.
Nhưng nếu bạn cần vượt qua cookie session store, bạn có hai lựa chọn:
Lưu session trong database, hoặc lưu trong cache.
Lưu session trong cache
Bạn có thể đã dùng thứ như Memcache để cache partial hoặc dữ liệu. Nếu vậy, cache store là nơi dễ thứ hai để lưu dữ liệu session, vì nó đã được thiết lập.
Bạn không phải lo session store phát triển ra khỏi kiểm soát, vì session cũ sẽ tự động bị đá ra khỏi cache nếu nó quá to. Và nó nhanh, vì cache của bạn nhiều khả năng được giữ trong bộ nhớ.
Nhưng nó không hoàn hảo:
Nếu bạn thực sự quan tâm giữ session cũ, bạn có thể không muốn chúng bị đá ra khỏi cache.
Session và dữ liệu cache của bạn sẽ tranh giành không gian. Nếu bạn không có đủ bộ nhớ, bạn có thể đối mặt ton cache miss và session hết hạn sớm.
Nếu bạn bao giờ cần reset cache (giả sử bạn nâng cấp Rails và dữ liệu cache cũ không còn chính xác), không có cách làm điều đó mà không expire session của mọi người.
Vẫn vậy, đây là cách chúng tôi lưu dữ liệu session tại Avvo, và nó hoạt động tốt cho chúng tôi cho đến nay.
Lưu session trong database
Nếu bạn muốn giữ dữ liệu session quanh cho đến khi nó thực sự hết hạn, bạn có thể muốn giữ nó trong某种 database. Dù đó là Redis, ActiveRecord, hoặc thứ khác.
Nhưng lưu session database cũng có nhược điểm:
Với một số database store, session không được dọn dẹp tự động.
Vì vậy bạn phải đi qua và dọn session hết hạn tự mình.
Bạn phải biết database sẽ hành động như thế nào khi nó đầy dữ liệu session.
Bạn dùng Redis làm session store? Nó sẽ cố giữ tất cả dữ liệu session trong bộ nhớ? Server có đủ bộ nhớ không, hay nó sẽ bắt đầu swap tệ đến mức bạn không thể
sshvào sửa?Bạn phải cẩn thận hơn về khi nào tạo dữ liệu session, hoặc bạn sẽ lấp đầy database với session vô ích.
Ví dụ, nếu bạn vô tình chạm session mỗi request, googlebot có thể tạo hàng trăm nghìn session vô ích. Và đó sẽ là thời gian tệ.
Hầu hết vấn đề này khá hiếm. Nhưng bạn vẫn nên nhận ra chúng.
Vậy bạn nên lưu session như thế nào?
Nếu bạn khá chắc bạn không sẽ gặp giới hạn của cookie store, hãy dùng nó. Nó không cần nhiều thiết lập, và không đau đầu để maintain.
Lưu session trong cache vs database là phán đoán về việc expire session sớm tệ đến đâu. Tôi đối xử dữ liệu session như khá tạm thời, vậy cache store hoạt động tốt cho tôi. Vì vậy tôi thường thử cookie trước, rồi cache, rồi database.
Nhưng bạn sao? Bạn lưu session của bạn như thế nào? Để lại bình luận và cho tôi biết!
Và nếu bạn muốn học thêm về cách Ruby và Rails internals hoạt động, xem bài này: How do gems work? Hoặc, nếu bạn quan tâm hơn về cách web hoạt động, xem A web server vs. an app server.