+3

Tạo một project với Spring Boot - Thymeleaf - Elasticsearch sử dụng HTTP/2 - Tạo service run Elasticsearch

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:

https://viblo.asia/p/tao-mot-project-voi-spring-boot-va-elasticsearch-841-su-dung-thu-vien-spring-data-elasticsearch-huong-dan-cai-dat-va-su-khac-nhau-giua-2-phien-ban-8xx-voi-7xx-elasticsearch-Rk74aRXvJeO

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ả:

image.png

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":

image.png

Ta thấy service với tên "ElasticsearchService" đã được tạo thành công:

image.png

Tiến hành Start service lên:

3.png

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:

image.png

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:

image.png

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

image.png

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

image.png

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"

image.png

Thông báo service được cài đặt thành công, vào giao diện "Services" kiểm tra:

image.png

Service mới được tạo thành công, tiến hành start service:

image.png

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":

image.png

=> Elasticsearch đã được khởi động thành công

Tiếp tục mở extension Elasticsearch head để kiểm tra xem trang thái:

image.png

2. Cấu trúc thư mục project:

image.png

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":

image.png

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:

image.png

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:

image.png

Để 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:

https://viblo.asia/p/tao-mot-project-voi-spring-boot-va-elasticsearch-841-su-dung-thu-vien-spring-data-elasticsearch-huong-dan-cai-dat-va-su-khac-nhau-giua-2-phien-ban-8xx-voi-7xx-elasticsearch-Rk74aRXvJeO

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"

image.png

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:

image.png

Sao chép tất cả thư mục sources đến thư mục "static" trong "resources" của project:

image.png

Riêng file "index.html" sao chép đến thư mục "templates" trong thư mục "resources":

image.png

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":

image.png

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ị đó:

image.png

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

image.png

Bây giờ, restart lại project và kiểm tra:

image.png

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:

image.png

image.png

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"

image.png

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

image.png

  • 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":

image.png

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:

image.png

image.png

Với thẻ <input> để in giá trị ta có thể dùng thuộc tính "th:value" của Thymeleaf:

image.png

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"

image.png

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":

image.png

Kiểm tra và thấy trang "register.html" được tải thành công:

image.png

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

image.png

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

image.png

Thấy user đăng ký thành công:

image.png

Kiểm tra xem dữ liệu đã được lưu thành công hay chưa, dùng extension Elasticsearch head:

image.png

=> 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:

image.png

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"

image.png

=> 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":

image.png

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:

image.png

Kiểm tra lại với thông tin login sai:

image.png

=> 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:

image.png

=> 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:

image.png

Kiểm tra Data trong elasticsearch:

image.png

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:

image.png

Để 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

image.png

Sau khi restart project -> load lại trang để thấy protocol có thay đổi hay không:

image.png

=> 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:

image.png

Để 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":

image.png

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:

image.png

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:

image.png

Để 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":

image.png

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

image.png

  • -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:

image.png

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:

image.png

=> 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:

image.png

Cấu trúc thư mục project:

image.png

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

image.png

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:

image.png

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)":

image.png

Kiểm tra tất cả request url:

image.png

image.png

image.png

image.png

=> 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:

image.png

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.

Download source: http://megaurl.in/OLaeEtc


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí