Bỏ qua nội dung

Ngày 13 - Thực hiện chương trình

Xin chào mọi người, như vậy là sau những ngày bận rộn với nhiều việc thi nhật trình ngày 13 cũng đã sẵn sàng để cho lên sóng rồi đây. Hãy sẵn sàng để chứng kiến ứng dụng của chúng ta lên sóng chính thức thôi nào!

Chỉnh sửa mã nguồn

Hãy nhìn đoạn mã nguồn hậu chỉnh sửa ngay sau đây. Với mã nguồn này, chúng ta sẽ xây dựng một trang web chứa các thông tin liên quan đến hành trình của chúng ta.

src/main.go
package main
import (
"fmt"
"html/template"
"log"
"net/http"
"os"
"strconv"
"strings"
)
type Page struct {
Title string
facebookName string
daysCompleted uint
remainingDays uint
}
func loadPage(title string) (*Page, error) {
filename := title + ".txt"
data, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
parts := strings.Split(string(data), ",")
if len(parts) != 3 {
return nil, fmt.Errorf("Thông tin không hợp lệ trong: %s", filename)
}
facebookName := parts[0]
if len(facebookName) == 0 {
return nil, fmt.Errorf("Facebook không hợp lệ: %s", parts[0])
}
daysCompleted, err := strconv.Atoi(parts[1])
if err != nil {
return nil, fmt.Errorf("Số ngày không hợp lệ: %s", parts[1])
}
remainingDays, err := strconv.Atoi(parts[2])
if err != nil {
return nil, fmt.Errorf("Số ngày còn lại không hợp lệ: %s", parts[2])
}
stringDaysCompleted := uint(daysCompleted)
stringRemainingDays := uint(remainingDays)
return &Page{
Title: title,
facebookName: facebookName,
daysCompleted: stringDaysCompleted,
remainingDays: stringRemainingDays,
}, nil
}
func viewController(w http.ResponseWriter, r *http.Request) {
title := r.URL.Path[len("/view/"):]
p, err := loadPage(title)
t, err := template.ParseFiles("index.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
totalDays := 90
challenge := "#90NgàyDevOps"
data := map[string]interface{}{
"Title": p.Title,
"FacebookName": p.facebookName,
"Challenge": challenge,
"TotalDays": totalDays,
"DaysCompleted": p.daysCompleted,
"RemainingDays": p.remainingDays,
}
if err := t.Execute(w, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func main() {
http.HandleFunc("/view/", viewController)
log.Fatal(http.ListenAndServe("localhost:8080", nil))
}

Cây thư mục

Với mã nguồn này, cây thư mục web của chúng ta sẽ có tất cả ba tập tin như sau.

  • Danh mụcsrc/
    • Danh mụchello/
      • main.go
    • Danh mụcweb/
      • index.html
      • note.txt
      • main.go

Tập tin index.html 💻

Một tập tin có phần mở rộng kết thúc bằng .html là một trang web. Điểm đặc biệt của tập tin này chính là, nếu ta mở nó ở trình duyệt, ta sẽ thấy trọn vẹn trang web đó (giống như web 90 ngày DevOps chẳng hạn). Tuy nhiên, nếu ta mở ở trình chỉnh sửa tập tin, nó sẽ có cấu trúc như thế này.

index.html
<html>
<head>
<title>Xin chào Việt Nam</title>
</head>
<body>
<p>Xin chào Việt Nam!</p>
</body>
</html>

Ở đây, tập tin index.html sẽ được điều chỉnh để tiếp nhận những thông tin từ chương trình Go của chúng ta. Cụ thể nó như thế này.

src/index.html
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>{{ .Title }} for {{ .FacebookName }}</title>
</head>
<body>
<h1>Your Note</h1>
<div>
<p>Chào mừng {{ .FacebookName }} đến với {{ .Challenge }}.</p>
<p>Đây là hành trình {{ .TotalDays }} ngày.</p>
<p>Bạn đã hoàn thành {{ .DaysCompleted }} ngày và còn {{ .RemainingDays }} ngày nữa.</p>
<p><b>Chúc bạn thượng lộ bình an!</b></p>
</div>
</body>
</html>
  • html lang="vi" chỉ định ngôn ngữ tiếng Việt cho trang Web
  • Các nội dung còn lại vẫn tương tự như cũ, tuy nhiên vẫn có những chỗ lạ lẫm mà ta có thể không rõ như {{ .FacebookName }} hay {{ .DaysCompleted }}, ta sẽ lưu ý nó sau.

Tập tin note.txt 📝

Đây là nội dung của tập tin.

src/note.txt
buile.tuananh,13,77

Có 3 trường thông tin, gồm tên facebook, số ngày hoàn thành (13) và số ngày còn lại (77), ngăn cách với nhau bằng dấu phẩy. Tập tin này có thể điều chỉnh tùy ý.

Tập tin main.go

Đoạn mã nguồn đầu tiên

Hãy chú ý đoạn mã nguồn này.

import (
"fmt"
"html/template"
"log"
"net/http"
"os"
"strconv"
"strings"
)

Ngoài thư viện fmt chuẩn ra, ta có thấy có một loạt các thư viện khác xuất hiện. Cụ thể như sau.

  • html/templatenet/http: Một thư viện chuyên dụng để sử dụng các tập tin HTML, còn thư viện còn lại là thư viện chủ lực trong thực hiện tạo một máy chủ cho trang web.
  • logos: Cặp thư viện này có hai vai trò: Một là phục vụ tương tác với tập tin/hệ điều hành (OS), hai là xử lý ghi nhật trình và báo lỗi nếu có sự cố xảy ra trong quá trình vận hành ứng dụng.
  • strconvstrings: Cặp thư viện liên quan đến xử lý kiểu dữ liệu dạng chuỗi, trong đó strconv là chuyển đổi một biến bất kỳ sang chuỗi hoặc ngược lại.

Khối cấu trúc định hình

Ta tạo một khối cấu trúc như sau.

type Page struct {
Title string
facebookName string
daysCompleted uint
remainingDays uint
}

Khối này định hình trang web của chúng ta chứa những thông tin gì. Kiểu dữ liệu và tên gọi có thể mọi người đã quen thuộc, nếu chưa, hãy quay lại những ngày trước để xem lại nhé.

Hàm xử lý nội dung trang web

func loadPage(title string) (*Page, error) {
filename := title + ".txt"
data, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
parts := strings.Split(string(data), ",")
if len(parts) != 3 {
return nil, fmt.Errorf("Thông tin không hợp lệ trong: %s", filename)
}
facebookName := parts[0]
if len(facebookName) == 0 {
return nil, fmt.Errorf("Facebook không hợp lệ: %s", parts[0])
}
daysCompleted, err := strconv.Atoi(parts[1])
if err != nil {
return nil, fmt.Errorf("Số ngày không hợp lệ: %s", parts[1])
}
remainingDays, err := strconv.Atoi(parts[2])
if err != nil {
return nil, fmt.Errorf("Số ngày còn lại không hợp lệ: %s", parts[2])
}
stringDaysCompleted := uint(daysCompleted)
stringRemainingDays := uint(remainingDays)
return &Page{
Title: title,
facebookName: facebookName,
daysCompleted: stringDaysCompleted,
remainingDays: stringRemainingDays,
}, nil
}

Hàm này nhận đầu vào chính là tên tập tin văn bản (txt) của chúng ta, đồng thời trả về một con trỏ tương ứng với trang đó. Hàm chạy như sau.

  1. Mở tập tin văn bản. Nếu không mở được tập tin nào có định dạng tương ứng, báo lỗi.
  2. Chia tách dãy thông tin trong tập tin thành ba thông tin khác nhau. Kiểm tra định dạng và sự tồn tại của cả ba thông tin, nếu có sai sót, báo lỗi.
  3. Chuyển đổi ngược trở lại hai thông tin số ngày về kiểu dữ liệu số (sau khi chuyển sang kiểu chuỗi để kiểm tra đầu vào).
  4. Truyền tham chiếu (con trỏ) chứa toàn bộ thông tin tương ứng để chờ tạo một trang web.

Hàm xử lý giao diện web

func viewController(w http.ResponseWriter, r *http.Request) {
title := r.URL.Path[len("/view/"):]
p, err := loadPage(title)
t, err := template.ParseFiles("index.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
totalDays := 90
challenge := "#90NgàyDevOps"
data := map[string]interface{}{
"Title": p.Title,
"FacebookName": p.facebookName,
"Challenge": challenge,
"TotalDays": totalDays,
"DaysCompleted": p.daysCompleted,
"RemainingDays": p.remainingDays,
}
if err := t.Execute(w, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

Controller, cũng như một cái cần gạt, giúp xử lý toàn bộ ứng dụng web của chúng ta. Hàm này vận hành như sau.

  1. Đọc đường dẫn và kiểm tra sự hiện diện của tập tin văn bản có tên tương ứng. Cụ thể là view/abc/ ứng với tập tin abc.txt. Nếu không có, báo lỗi.
  2. Tải tập tin mẫu index.html lên hệ thống, nếu có mẫu. Nếu không có, báo lỗi.
  3. Gán dữ liệu (map) thông tin của các trường thông tin tương ứng với các biến vào tập tin mẫu index.html để xử lý. Đây chính là nơi mà các ký hiệu lạ trong tập tin mẫu của chúng ta ở phần đầu như {{ .FacebookName }} hay {{ .DaysCompleted }} được lưu dữ liệu.

Hàm chính và kết quả cuối cùng

func main() {
http.HandleFunc("/view/", viewController)
log.Fatal(http.ListenAndServe("localhost:8080", nil))
}

Hàm này làm nhiệm vụ khởi động và đưa cần gạt về đúng bộ phận quản lý. Đồng thời, hàm này cũng sẽ mở kết nối trên cổng 8080 để đưa trang web vận hành cũng như báo lỗi nếu phát hiện sai sót.

Chạy chương trình bằng lệnh quen thuộc tại thư mục chứa các tập tin này.

Starting the webapp...
go run main.go

Đây là kết quả cuối cùng của chúng ta.

Webapp

Nhấn Ctrl+C để kết thúc chương trình.

Như vậy, hành trình Golang của chúng ta đã hoàn thành. Với đoạn mã nguồn này, chúng ta còn có thể làm được nhiều điều hơn, ví dụ chỉnh sửa tổng số ngày cũng như tên của một hành trình nào đó để người dùng có thể tự nhập vào hệ thống.

Hy vọng, tất cả mọi người đã có được những trải nghiệm tuyệt vời cùng điểm dừng chân này.

Tài liệu tham khảo 📚

Mời mọi người chuyển sang trang này để theo dõi tất cả tài liệu liên quan trong giai đoạn 2, để giúp bản thân có được những tài liệu hữu ích về Golang trong làm việc với DevOps.

Hẹn gặp mọi người ở những ngày tiếp theo, nơi mà chúng ta sẽ bắt đầu làm việc với Linux - một hệ điều hành cực kỳ gần gũi với các lập trình viên cũng như kỹ sư DevOps. 🚀