รู้จักแพ็คเกจ http
ในส่วนก่อนหน้านี้ เราได้เรียนรู้เกี่ยวกับเวิร์กโฟลว์ของเว็บและพูดคุยกันเล็กน้อยเกี่ยวกับแพ็คเกจ http ของ Go ในส่วนนี้ เราจะมาเรียนรู้เกี่ยวกับสองฟังก์ชันหลักในแพ็คเกจ http คือ Conn และ ServeMux
goroutine ใน Conn
ต่างจากเซิร์ฟเวอร์ HTTP ทั่วไป Go ใช้ goroutines สำหรับทุกงานที่ Conn ริเริ่มเพื่อให้เกิดการทำงานพร้อมกันและประสิทธิภาพสูง ดังนั้นทุกงานจึงเป็นอิสระ
Go ใช้โค้ดต่อไปนี้เพื่อรอการเชื่อมต่อใหม่จาก clients
c, err := srv.newConn(rw)
if err != nil {
continue
}
go c.serve()
อย่างที่คุณเห็น มันสร้าง goroutine ใหม่สำหรับทุกการเชื่อมต่อ และส่งตัวจัดการที่สามารถอ่านข้อมูลจากคำขอไปยัง goroutine
ปรับแต่ง ServeMux
เราใช้เราเตอร์เริ่มต้นของ Go ในส่วนก่อนหน้าเมื่อพูดถึง conn.server โดยเราเตอร์ส่งข้อมูลคำขอไปยังตัวจัดการส่วนหลัง
โครงสร้างของเราเตอร์เริ่มต้น:
type ServeMux struct {
mu sync.RWMutex // because of concurrency, we have to use a mutex here
m map[string]muxEntry // router rules, every string mapping to a handler
}
โครงสร้างของ muxEntry:
type muxEntry struct {
explicit bool // exact match or not
h Handler
}
อินเทอร์เฟซของตัวจัดการ:
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // routing implementer
}
Handler เป็นอินเทอร์เฟซ แต่ถ้าฟังก์ชัน sayhelloName ไม่ได้ใช้อินเทอร์เฟซนี้ แล้วเราจะเพิ่มเป็น handler ได้อย่างไร คำตอบอยู่ในประเภทอื่นที่เรียกว่า HandlerFunc ในแพ็คเกจ http เราเรียก HandlerFunc เพื่อกำหนดวิธี sayhelloName ของเรา ดังนั้น sayhelloName จึงใช้ Handler พร้อมกัน เหมือนกับว่าเรากำลังเรียก HandlerFunc(f) และฟังก์ชัน f ถูกบังคับให้แปลงเป็นประเภท HandlerFunc
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
ตัวจัดการการเรียกเราเตอร์หลังจากเราตั้งกฎเราเตอร์อย่างไร
เราเตอร์เรียก mux.handler.ServeHTTP(w, r) เมื่อได้รับการร้องขอ กล่าวอีกนัยหนึ่งจะเรียกอินเทอร์เฟซ ServeHTTP ของตัวจัดการที่ใช้งานได้
ตอนนี้เรามาดูกันว่า mux.handler ทำงานอย่างไร
func (mux *ServeMux) handler(r *Request) Handler {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
h := mux.match(r.Host + r.URL.Path)
if h == nil {
h = mux.match(r.URL.Path)
}
if h == nil {
h = NotFoundHandler()
}
return h
}
เราเตอร์ใช้ URL ของคำขอเป็นคีย์เพื่อค้นหาตัวจัดการที่เกี่ยวข้องที่บันทึกไว้ในแผนที่ จากนั้นเรียก handler.ServeHTTP เพื่อเรียกใช้ฟังก์ชันเพื่อจัดการข้อมูล
คุณควรเข้าใจขั้นตอนการทำงานของเราเตอร์เริ่มต้นเสียก่อน และ Go รองรับเราเตอร์ที่ปรับแต่งได้จริง อาร์กิวเมนต์ที่สองของ ListenAndServe ใช้สำหรับกำหนดค่าเราเตอร์ที่กำหนดเอง เป็นอินเทอร์เฟซของ Handler ดังนั้น เราเตอร์ใดๆ ที่ใช้อินเทอร์เฟซ Handler ก็สามารถใช้ได้
ตัวอย่างต่อไปนี้แสดงวิธีการใช้เราเตอร์อย่างง่าย
package main
import (
"fmt"
"net/http"
)
type MyMux struct {
}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
sayhelloName(w, r)
return
}
http.NotFound(w, r)
return
}
func sayhelloName(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello myroute!")
}
func main() {
mux := &MyMux{}
http.ListenAndServe(":9090", mux)
}
Routing (การกำหนดเส้นทาง)
หากคุณไม่ต้องการใช้เราเตอร์ คุณยังคงสามารถบรรลุสิ่งที่เราเขียนไว้ในส่วนข้างต้นโดยแทนที่อาร์กิวเมนต์ที่สองเป็น ListenAndServe เป็นศูนย์ และลงทะเบียน URL โดยใช้ฟังก์ชัน HandleFunc ซึ่งจะผ่าน URL ที่ลงทะเบียนทั้งหมดเพื่อค้นหารายการที่ตรงกันที่สุด จึงต้องระมัดระวังในลำดับการขึ้นทะเบียน
โค้ดตัวอย่าง:
http.HandleFunc("/", views.ShowAllTasksFunc)
http.HandleFunc("/complete/", views.CompleteTaskFunc)
http.HandleFunc("/delete/", views.DeleteTaskFunc)
//ShowAllTasksFunc is used to handle the "/" URL which is the default ons
//TODO add http404 error
func ShowAllTasksFunc(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
context := db.GetTasks("pending") //true when you want non deleted tasks
//db is a package which interacts with the database
if message != "" {
context.Message = message
}
homeTemplate.Execute(w, context)
message = ""
} else {
message = "Method not allowed"
http.Redirect(w, r, "/", http.StatusFound)
}
}
สิ่งนี้ใช้ได้สำหรับแอปพลิเคชันทั่วไปที่ไม่ต้องการการกำหนดเส้นทางแบบกำหนดพารามิเตอร์ เมื่อใดที่คุณต้องการ คุณสามารถใช้ชุดเครื่องมือหรือเฟรมเวิร์กที่มีอยู่ได้ แต่เนื่องจากบทความนี้นี้เป็นเรื่องเกี่ยวกับการเขียนเว็บแอปในภาษา Go เราจะสอนวิธีจัดการกับสถานการณ์นี้เช่นกัน
เมื่อจับคู่กับฟังก์ชัน HandleFunc แล้ว URL จะถูกจับคู่ ดังนั้น สมมติว่าเรากำลังเขียนตัวจัดการรายการสิ่งที่ต้องทำและเราต้องการลบงาน ดังนั้น URL ที่เราตัดสินใจสำหรับแอปพลิเคชันนั้นคือ /delete/1 ดังนั้นเราจึงลงทะเบียนการลบ URL แบบนี้ http.HandleFunc(“/delete/”, views.DeleteTaskFunc) /delete/1 URL นี้ตรงกับ URL “/delete/” ที่ใกล้เคียงที่สุด มากกว่า URL อื่น ๆ ดังนั้นใน r.URL.path เราจึงได้ URL ทั้งหมด ของการร้องขอ
http.HandleFunc("/delete/", views.DeleteTaskFunc)
//DeleteTaskFunc is used to delete a task, trash = move to recycle bin, delete = permanent delete
func DeleteTaskFunc(w http.ResponseWriter, r *http.Request) {
if r.Method == "DELETE" {
id := r.URL.Path[len("/delete/"):]
if id == "all" {
db.DeleteAll()
http.Redirect(w, r, "/", http.StatusFound)
} else {
id, err := strconv.Atoi(id)
if err != nil {
fmt.Println(err)
} else {
err = db.DeleteTask(id)
if err != nil {
message = "Error deleting task"
} else {
message = "Task deleted"
}
http.Redirect(w, r, "/", http.StatusFound)
}
}
} else {
message = "Method not allowed"
http.Redirect(w, r, "/", http.StatusFound)
}
}
ในวิธีการข้างต้นนี้ สิ่งที่เราทำโดยทั่วไปคือในฟังก์ชันที่จัดการ /delete/ URL เราใช้ URL ที่ครบถ้วนซึ่งก็คือ /delete/1 จากนั้นเรานำส่วนของสตริงและแยกทุกอย่างที่เริ่มต้นหลังจากคำลบซึ่ง คือพารามิเตอร์จริง ในกรณีนี้คือ 1 จากนั้นเราใช้แพ็คเกจ strconv เพื่อแปลงเป็นจำนวนเต็มและลบงานด้วย taskID นั้น
ในสถานการณ์ที่ซับซ้อนมากขึ้นเช่นกัน เราสามารถใช้วิธีนี้ได้ ข้อดีคือเราไม่จำเป็นต้องใช้ชุดเครื่องมือของบุคคลที่สาม แต่ชุดเครื่องมือของบุคคลที่สามก็มีประโยชน์ในตัวเอง คุณต้องตัดสินใจว่าวิธีใดของคุณ’ ง ชอบ ไม่มีคำตอบคือคำตอบที่ถูกต้อง
Go code execution flow
มาดูขั้นตอนการดำเนินการทั้งหมดกัน
- เรียก
http.HandleFunc
- เรียก HandleFunc ของ DefaultServeMux
- ตัวจัดการการโทรของ DefaultServeMux
- เพิ่มกฎของเราเตอร์ไปที่ map[string]muxEntry ของ DefaultServeMux
- เรียก
http.ListenAndServe(":9090", nil)
- สร้างอินสแตนซ์เซิร์ฟเวอร์
- เรียกใช้เมธอด ListenAndServe ของ Server
- เรียก net.Listen(“tcp”, addr) เพื่อฟัง port
- เริ่มการวนซ้ำและยอมรับคำขอในเนื้อหาของลูป
- สร้างอินสแตนซ์ Conn และเริ่ม goroutine สำหรับทุกคำขอ: go c.serve()
- อ่านข้อมูลคำขอ: w, err := c.readRequest()
- ตรวจสอบว่าตัวจัดการว่างเปล่าหรือไม่ หากว่างเปล่า ให้ใช้ DefaultServeMux
- เรียก ServeHTTP ของตัวจัดการ
- รันโค้ดใน DefaultServeMux ในกรณีนี้
- เลือกตัวจัดการตาม URL และรันโค้ดในฟังก์ชันตัวจัดการนั้น: mux.handler.ServeHTTP(w, r)
- วิธีเลือกตัวจัดการ: A. ตรวจสอบกฎของเราเตอร์สำหรับ URL นี้ B. เรียก ServeHTTP ในตัวจัดการนั้นหากมีหนึ่งตัว C. เรียก ServeHTTP ของ NotFoundHandler เป็นอย่างอื่น