Giới thiệu về package net/http của ngôn ngữ Go (Phần 1)
hi tìm hiểu về ngôn ngữ lập trình Go, lời khuyên mà người mới học thường gặp nhất là thay vì tìm một web framework có sẵn thì hãy tìm hiểu và sử dụng package net/http
đi kèm sẵn của Go. Có thể thấy, net/http
thật sự rất mạnh, đủ dùng cho phần lớn các trường hợp phát triển web thông thường. Ở bài viết này, chúng ta cũng tìm hiểm các tính năng thông dụng nhất của net/http
Cơ bản về net/http
HTTP server đơn giản
Đầu tiên, sử dụng net/http
chúng ta hãy khỏi tạo một server HTTP vô cùng đơn giản
// main.go
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", HelloServer)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatalf("Error creating server %s\n", err.Error())
}
}
func HelloServer(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}
Ở đoạn code trên, chúng ta tạo một http server ở cổng 8080 và dùng hàm http.HandleFunc
để thiết lập thực thi chạy hàm HelloServer
mỗi khi có request đến đường dẫn root /
. Khi có request gửi đến, HelloServer
sẽ được gọi và hai paramerter w
(http.ResponseWriter
) và r
(*http.Request
) sẽ được set tương ứng là 2 giá trị đại diện cho response và request của request đó.
Ta sử dụng câu lệnh sau để chạy test thử http server:
go run main.go
Truy cập vào địa chỉ web http://localhost:8080/handsome/good/boy
, ta sẽ thấy đoạn text sau được trả về
Hello, handsome/good/guy!
Để add thêm các tính năng cho từng đường dẫn, ta đơn giản chỉ cần tiếp tục sử dụng hàm http.HandleFunc
:
http.handleFunc("/route1", Function1)
http.handleFunc("/route2", Function2)
HTTP Client
Ta cũng có thể sử dụng net/http
để gửi request HTTP. Sửa đoạn code ở trên để thêm 1 đường dẫn để gọi API thông tin về mèo như sau
func main() {
// ...
http.HandleFunc("/api/facts", CatFacts)
// ...
}
// ...
func GetFacts(w http.ResponseWriter, r *http.Request) {
resp, err := http.Get("https://cat-fact.herokuapp.com/facts/random?animal_type=cat&amount=29")
if err != nil {
log.Printf("Error getting data %s", err.Error())
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("Error parsing data %s", err.Error())
}
var catFacts []CatFact
err = json.Unmarshal(body, &catFacts)
if err != nil {
log.Printf("Error parsing json %s", err.Error())
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(catFacts)
if err != nil {
log.Printf("Error encoding json %s", err.Error())
}
}
Truy cập vào địa chỉ http://localhost:8080/api/cat
, ta sẽ thấy chuỗi json kết quả được trả về (hơi bị vô nghĩa khi gọi API lấy json rồi parse lại thành Go Struct rồi lại trả về json, nhưng vì là code demo nên tất cả đều là ok :D). Ở đoạn code trên ta sử dụng hàm http.Get
để gửi request lấy dữ liệu từ api cat-fact. Response trả về là định dạng io.Reader
do đó phải dùng hàm ioutil.ReadAll
để chuyển sang dạng []byte
rồi parse thành dạng Struct CatFact. Để trả về data dưới định dạng json thì ta cần set Header Content-Type
cho response trả về đồng thời ghi dữ liệu muốn trả về vào http.ResponseWriter
w
.
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(catFacts)
net/http
hiện giờ chỉ hỗ trợ GET, POST và HEAD tương ứng với các hàm http.Get
, http.Post
, http.Post
. Ngoài ra, để có thể chỉnh sửa các setting cho request ta có thể sử dụng 2 cách tạo request khác:
- Thay vì dùng trực tiếp các method từ
http
, khởi tạo mộtClient
object và gọi các methodGet
,Post
,Head
var client = &http.Client{Timeout: 10 * time.Second} resp, err := client.Get("...")
- Dùng hàm
http.NewRequest
var client = &http.Client{} req, err := http.NewRequest("GET", "...", nil) req.Header.Add("If-None-Match", `W/"wyzzy"`) resp, err := client.Do(req)
ServerMux
Hầu hết các web framework đều có một thành phần quan trọng là Router để giúp người phát triển có thể thiết kế các đường dẫn trong ứng dụng của mình, mapping các đường dẫn tới các hàm thực thi trong ứng dụng. Có rất nhiều các http router được viết và opensource cho go, net/http
đã đi kèm sẵn với ServerMux
, một HTTP request multiplexer, đủ dùng cho việc làm router đơn giản. Việc sử dụng http.HandleFunc
cùng với http.ListenAndServe
như ở dưới đây thực chất là đã định nghĩa mapping đường dẫn cho DefaultServerMux
, một ServerMux
mặc định.
http.HandleFunc("/", HelloServer)
err := http.ListenAndServe(":8080", nil)
Tuy nhiên đây là ServerMux
global có thể được access trong toàn bộ ứng dụng nên có thể xảy ra các vấn đề về security nên tốt nhất ta nên tạo riêng 1 ServerMux
ở local scope và dùng riêng.
//...
mux := http.NewServeMux()
mux.HandleFunc("/", HelloServer)
mux.HandleFunc("/api/facts", GetFacts)
err := http.ListenAndServe(":8080", mux)
//...
Vì bản chất của ServerMux
chỉ là một HTTP request multiplexer nên tính năng của ServerMux
khá là hạn chế. Với các đường dẫn kết thúc bằng /
ví dụ /home/
thì ServerMux
sẽ match tất cả những đường dẫn con như là /home/sweet
hay /home/sweet/home
. Do vậy với http server ở trên của chúng ta, khi truy cập vào /home/sweet/home
thì hàm HelloServer
cũng được gọi. Còn nếu đường dẫn không kết thúc bằng /
thì chỉ khi nào đường dẫn match đúng thì hàm handler mới được gọi. Ngoài ra, ServerMux
cũng không support wildcat matching, matching theo method của Request, tự động lấy parameter từ đường dẫn. Nếu muốn làm những việc trên thì đều phải code bằng tay. Ví dụ như sau:
- Giới hạn chỉ xử lý GET request
func HelloServer(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { w.Header().Set("Allow", "GET") http.Error(w, "Method Not Allowed", 405) return } fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:] }
- Đọc query parameter từ đường dẫn
//... func GetFacts(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { w.Header().Set("Allow", "GET") http.Error(w, "Method Not Allowed", 405) return } animalType := r.URL.Query().Get("animal_type") if animalType == "" { animalType = "cat" } amount, err := strconv.Atoi(r.URL.Query().Get("amount")) if err != nil { amount = 29 } resp, err := http.Get(fmt.Sprintf("https://cat-fact.herokuapp.com/facts/random?animal_type=%s&amount=%d", animalType, amount)) //... }
Có thể thấy ta nên chỉ dùng ServeMux
cho những ứng dụng web đơn giản với số đường dẫn ít, với những ứng dụng đòi hỏi đường dẫn phức tạp hay REST API thì ta nên sử dụng một trong rất nhiều những router bên thứ ba với đầy đủ tính năng hơn.
Kết luận
Bài viết này đã giới thiệu qua những tính năng cơ bản nhất của package net/http
. Ở bài viết sau ta sẽ cùng tìm hiểu các tính năng nâng cao hơn làm nên sức mạnh của net/http
như middleware, context,..