Trong bài viết này, tôi sẽ hướng dẫn cách tạo một project với Spring boot kết hợp với Thymeleaf và Elasticsearch sử dụng HTTP/2:
Công cụ và thư viện được sử dụng trong bài viết:
- Spring boot 2.7.4
- Spring tool suite 4
- Spring data elasticsearch 4.4.2
- Maven 3
- Java 11
- Elasticsearch 7.17.6
- Nssm
- Elasticsearch head extension
1. Cài đặt Elasticsearch:
Vui lòng tham khảo bài viết bên dưới để cài đặt elasticsearch:
Trong bài viết này tôi sẽ hướng dẫn thêm cách tạo 1 service để khởi động elasticsearch:
-
Mở Command Prompt với quyền "Administrator"
-
Nhập câu lệnh như sau:
sc.exe create ElasticsearchService binPath="G:\elasticsearch-7.17.6\bin\elasticsearch.bat
Trong đó:
- "ElasticsearchService" là tên service bạn muốn đặt
- binPath: Đường dẫn tới file khởi động elasticsearch(elasticsearch.bat)
Tiến hành chạy câu lệnh bên trên và kết quả:
Kiểm tra xem service với name "ElasticsearchService" đã được tạo thành công chưa, nhấn tổ hợp phím "WIN + R" -> nhập "services.msc" và click "OK":
Ta thấy service với tên "ElasticsearchService" đã được tạo thành công:
Tiến hành Start service lên:
Như các bạn thấy, khi tôi start service thì báo lỗi như ở trên, với trường hợp này tôi sẽ dùng một công cụ khác để hỗ trợ việc tạo và start service thành công
Tôi dùng công cụ NSSM:
Link download: https://nssm.cc/download
Chọn bản mới nhất:
Sau khi tải xong nssm, tiến hành giải nén, cấu trúc thư mục nssm như bên dưới:
Tiếp theo để tạo service, trong thư mục nssm tôi vào thư mục "win64"(nếu bạn nào còn xài windows 32 bit thì chọn thư mục "win32", còn xài windows 64 bit thì chọn cái nào cũng được)
Trong thư mục "win64" có file "nssm.exe", tại đây các bạn mở Command prompt lên hoặc có thể dùng Powershell bằng cách nhấn giữ phím Shift -> click phải chuột chọn "Open PowerShell ....", nếu xài windows 10 thì chọn "Show more options -> chọn "Open PowerShell ...."
Tại giao diện Command prompt, với service name "ElasticsearchService" vừa tạo ở trên có thể dùng lệnh nssm để remove service đó ra và tiến hành tạo service mới nếu vẫn muốn dùng tên service "ElasticsearchService":
nssm remove ElasticsearchService
Chọn "Yes" tại popup "Remove the service"
Sau khi remove ta tiến hành tạo một service mới, ở đây tôi sẽ đặt tên service khác:
nssm install ESService
Sau khi chạy câu lệnh ở trên sẽ show popup như hình, các bạn cần thiết lập đường dẫn đến file khởi động của Elasticsearch, sau khi nhập đường dẫn -> click "Install sevice"
Thông báo service được cài đặt thành công, vào giao diện "Services" kiểm tra:
Service mới được tạo thành công, tiến hành start service:
Service được start thành công
Kiểm tra Elasticsearch đã được khởi động thành công hay chưa, tại trình duyệt nhập đường dẫn "localhost:9200":
=> Elasticsearch đã được khởi động thành công
Tiếp tục mở extension Elasticsearch head để kiểm tra xem trang thái:
2. Cấu trúc thư mục project:
Mặc định, tập tin cấu hình sẽ là "application.properties", nếu muốn chuyển đổi sang "application.yml" -> click phải chuột vào tập tin "application.properties" -> chọn "Convert .properties to .yaml"
3. Tạo 1 package với tên "com.example.thymeleaf.application":
Nội dung class "ThymeleafApplication":
4. Tạo 1 package model với tên "com.example.thymeleaf.model":
Tại đây, tạo 1 class tên "User" với nội dung:
5. Tạo 1 package repository với tên "com.example.thymeleaf.repository":
Tại đây tạo 1 interface "UserRepository" với nội dung:
Để biết nhiều thông tin hơn về repository các bạn có thể xem lại bài viết này:
6. Tạo 1 package controller với tên "com.example.thymeleaf.controller":
Tại đây, tạo 1 class controller tên "UserController" với nội dung bao gồm các chức năng cơ bản "Thêm - Xem thông tin user"
7. Tạo 1 trang login đơn giản với Thymeleaf:
Nguồn tải template: https://colorlib.com/wp/html5-and-css3-login-forms/
Sau khỉ tải template về sẽ gồm những thư mục - tập tin:
Sao chép tất cả thư mục sources đến thư mục "static" trong "resources" của project:
Riêng file "index.html" sao chép đến thư mục "templates" trong thư mục "resources":
Sau khi thực hiện xong các bước trên, tiến hành tạo 1 request mapping trong "UserController" để load trang "index.html":
Mặc định, spring sẽ tự động load page trong thư mục "templates", muốn load page nào thì nhập đúng tên page cần load
Tôi sẽ kiểm tra thử xem thymeleaf có hoạt động chưa, tại "UserController" tôi sẽ dùng Model để thêm 1 giá trị và tại "index.html" dùng thư viện thymeleaf để load giá trị đó:
Trong file "index.html" chúng ta cần thêm "xmlns:th="http://www.thymeleaf.org"" trong html tag và tại đây tôi sẽ thêm 1 tag với nội dung "<h2 th:text="${name}"></h2>", trong đó:
-
Biến giá trị "name" chính là name attribute tôi đã tạo trong UserController
-
"th:text" chính là thư viện được hỗ trợ bởi Thymelead để in giá trị từ Model
Bây giờ, restart lại project và kiểm tra:
Như các bạn thấy, thay vì tải trang "index.html" thì lại trả về nội dung là "index". Lý do là vì trong class UserController sử dùng annotation "@RestController", với annotation "@RestController" với giá trị trả về là "index" -> nó sẽ chỉ hiểu là trả về nội dung là "index". Ngược lại, với annotation "@Controller" nếu giá trị trả về là "index" nó sẽ chỉ hiểu là tìm đến trang html với tên là "index" trong thư mục "templates" và nếu trả về tên giá trị không nằm trong thư mục "templates" -> sẽ báo lỗi
Bây giờ tôi sẽ thay annotation "@RestController" đến annotation "@Controller" với giá trị trả về là "index" -> chắc chắn lúc này page "index.html" sẽ được tải thành công:
Các bạn thấy, page "index.html" được tải thành công, giá trị từ biến "name" được in ra thành công
Tiếp tục, tôi sẽ sửa lại code html cho phần form kết hợp với những thuộc tính được hỗ trợ từ Thymeleaf cho page "index.html"
Tôi sẽ phân 1 vài thuộc tính Thymeleaf hỗ trợ mà tôi sử dụng:
- th:action: Chỉ định mapping tại controller sẽ nhận form data, ở đây tôi để mapping là "/login" và tôi sẽ khai báo thêm mapping này trong UserController
- th:object: Chỉ định đối tượng model được khai báo trong UserController, tại đây trong controller tôi dùng annotation "@ModelAttribute" để nhận data model
Restart lại project và kiểm tra. Với mapping "/login" -> redirect đến page "dashboard.html":
Tải trang dashboard thành công. Tiếp theo, tôi sẽ lấy giá trị "name" vừa nhập tại trang "index.html" lưu trong model và in ra field "firstname" của trang "dashboard.html" dùng Thymeleaf:
Với thẻ <input> để in giá trị ta có thể dùng thuộc tính "th:value" của Thymeleaf:
Giá trị "name" được in ra thành công
Tiếp theo, tại trang dashboard chức năng "Log out" tôi sẽ dùng thuộc tính "th:href" của Thymeleaf để thiếp lập giá trị mapping là "/" để redirect đến trang "index.html"
Hoặc các bạn có thể tạo thêm 1 mapping như "/logout" trong controller để dễ xử lý cho những nhu cầu khác, ở đây tôi dùng chung với mapping đã tạo trước đó
Qua những phần ở trên tôi đã demo một vài thuộc tính Thymeleaf hỗ trợ, tiếp theo tôi sẽ đi đến phần data, tôi sẽ thêm 1 trang "register.html" để tiến hành tạo user và lưu dữ liệu trong elasticsearch
Nguồn templates cho dashboard và register: https://themewagon.com/themes/free-responsive-bootstrap-5-html5-admin-template-sneat/
Trong UseController tạo thêm mapping "/register":
Kiểm tra và thấy trang "register.html" được tải thành công:
Tại trang register tôi cũng sẽ dùng những thuộc tính từ Thymeleaf cho form data tương tự như trang index
Tôi chỉ add thuộc tính th:field cho 2 field là "name" và "password" bởi vì field "email" tôi không khai báo trong model User
Tôi khai báo mapping cho "th:action" là "/register" -> call đến controller mapping "/register", tại đây sẽ kiểm tra "name" có tồn tại hay chưa, nếu chưa sẽ tiến hành tạo mới dữ liệu cho user còn ngược lại sẽ in ra message
Thấy user đăng ký thành công:
Kiểm tra xem dữ liệu đã được lưu thành công hay chưa, dùng extension Elasticsearch head:
=> Dữ liệu được tạo thành công
Với user được tạo mới tôi sẽ tiến hành đăng nhập để thấy có redirect đến trang dashboard hay không, trước tiên tôi cũng cần kiểm tra thông tin user đăng nhập trong controller mapping "/login" đúng hoặc không:
Kiểm tra cho kịch bản user login đúng thông tin. Tôi đã tạo user trước đó với "name" là "Mask" và "password" là "123"
=> Login success
Kiểm tra cho kịch bản user login sai thông tin. Tôi thử login với "name" là "Mask" và "password" là "123456":
Với thông tin sai sẽ redirect về lại trang login "index.html", tôi sẽ thêm 1 tag tại trang index để in ra message:
Kiểm tra lại với thông tin login sai:
=> Message in ra thành công
Tiếp theo, tại trang register tôi thử dùng thông tin user đã tạo trước đó và kiểm tra xem khi click "Sign up" -> có in ra thông báo lỗi hay không:
=> In ra thông báo lỗi thành công
Tiếp tục với kịch bản đăng ký user mới:
Kiểm tra Data trong elasticsearch:
8. Cấu hình HTTP/2:
Các bạn thấy, hiện tại khi tôi load trang thì tất các các request url có protocol là HTTP/1.1, không phải HTTP/2:
Để bật tính năng HTTP/2 trong Spring Boot, tôi sẽ vào file "application.yml" thêm 1 khai báo:
server: http2: enabled: true
Sau khi restart project -> load lại trang để thấy protocol có thay đổi hay không:
=> Protocol không thay đổi, vẫn là HTTP/1.1. Lý do là HTTP/2 chỉ hỗ trợ với giao thức HTTPS, còn hiện tại localhost sử dụng HTTP:
Để cấu hình localhost sử dụng HTTPS, ta cần bật chế độ SSL, ta cấu hình SSL trong "application.yml":
Tiến hành restart lại project và kiểm tra, nhưng lúc này nhập url là "https://localhost:8080" để thấy HTTPS có được chấp nhận hay không:
Như các bạn thấy, sau khi enable SSL -> vẫn không có thay đổi gì, kiểm tra console log và thấy lỗi:
Để xứ lý lỗi này, chúng ta cần tạo 1 certificate và thêm certificate này cấu hình trong "application.yml":
Tạo certificate:
Đầu tiên tôi tạo 1 thư mục với tên là "ssl_certificate":
Trong thư mục này, mở Command prompt lên và nhập dòng lệnh sau:
keytool -genkeypair -alias local_ssl -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore local-ssl.p12 –validity 365 -ext san=dns:localhost
Trong đó:
- -alias: Đặt tên tuỳ ý, ở đây tôi đặt tên là "local_ssl"
- -storetype: Để mặc định như trên
- -keystore: Đặt tên tuỳ ý, ở đây tôi để tên là "local-ssl.p12"
- san=dns: Để là "localhost" bởi vì tôi muốn thiết lập HTTPS cho domain là "localhost"
Sau khi nhập dòng lệnh trên -> enter:
Trong tất cả thông tin trong hình, chỉ cần quan tâm "keystore password", khuyến nghị chỉ nên nhập số cho dễ nhớ và ít sai, chú ý khi nhập password -> sẽ không in ra bất kỳ ký tự nào nhưng thực chất giá trị đã được nhập thành công. Ở đây tôi để "keystore password" là "123456"
Cuối cùng, sau khi nhập tất cả thông tin -> nhập "yes" để generate certificate. Vào thư mục "ssl_certificate" vừa tạo để kiểm tra:
=> Certificate được tạo thành công
Tiếp theo cần thêm certificate này đến thư mục "resources" của project. Ở đây tôi sẽ tạo 1 thư mục tên là "ssl_config" trong thư mục "resources" và copy certificate đến thư mục này:
Cấu trúc thư mục project:
Tiếp theo, tôi sẽ khai báo thông tin certificate trong "application.yml":
server: ssl: key-store: classpath:ssl_config/local-ssl.p12 key-store-type: PKCS12 key-store-password: 123456 enabled: true http2: enabled: true
Các bạn cần thay thế thông tin của mình như "password" cho đúng
Sau khi khai báo xong thông tin cho certificate, re-build lại project và start project sẽ thấy tại console log HTTPS đã được chấp nhận:
Tôi sẽ load lại trang với đường dẫn là "https://localhost:8080" và thấy kết quả:
Nếu các bạn gặp màn hình này thì không cần lo lắng, click "Advanced" và click "Process to localhost(unsafe)":
Kiểm tra tất cả request url:
=> Tất cả request url giao thức đã được chuyển đổi thành HTTP/2
Lưu ý dành cho những bạn nào muốn apply HTTP/2 với java 8 -> báo lỗi thì xem tại đây:
Link nguồn: https://tomcat.apache.org/tomcat-9.0-doc/config/http.html#HTTP/2_Support
Cuối cùng, bài viết khá dài vì tôi muốn mô tả chi tiết những tình huống lỗi có thể xảy ra trong quá trình làm và cách xử lý lỗi. Trong thời gian tiếp theo, có thể tôi sẽ viết về việc mã hoá password khi login bởi vì như các bạn thấy nếu sử dụng form data -> mặc định trong "payload" sẽ có thông tin username và password. Ngoài ra trong các project lớn thường không lưu password trong database, một vài project sẽ sử dụng ldap. Trong thời gian tới, nếu có thể tôi sẽ dành thời gian để demo 1 project sử dụng ldap để quản lý password.