รู้จักแพ็คเกจ 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
  1. เรียก HandleFunc ของ DefaultServeMux
  2. ตัวจัดการการโทรของ DefaultServeMux
  3. เพิ่มกฎของเราเตอร์ไปที่ map[string]muxEntry ของ DefaultServeMux
  • เรียก http.ListenAndServe(":9090", nil)
  1. สร้างอินสแตนซ์เซิร์ฟเวอร์
  2. เรียกใช้เมธอด ListenAndServe ของ Server
  3. เรียก net.Listen(“tcp”, addr) เพื่อฟัง port
  4. เริ่มการวนซ้ำและยอมรับคำขอในเนื้อหาของลูป
  5. สร้างอินสแตนซ์ Conn และเริ่ม goroutine สำหรับทุกคำขอ: go c.serve()
  6. อ่านข้อมูลคำขอ: w, err := c.readRequest()
  7. ตรวจสอบว่าตัวจัดการว่างเปล่าหรือไม่ หากว่างเปล่า ให้ใช้ DefaultServeMux
  8. เรียก ServeHTTP ของตัวจัดการ
  9. รันโค้ดใน DefaultServeMux ในกรณีนี้
  10. เลือกตัวจัดการตาม URL และรันโค้ดในฟังก์ชันตัวจัดการนั้น: mux.handler.ServeHTTP(w, r)
  11. วิธีเลือกตัวจัดการ: A. ตรวจสอบกฎของเราเตอร์สำหรับ URL นี้ B. เรียก ServeHTTP ในตัวจัดการนั้นหากมีหนึ่งตัว C. เรียก ServeHTTP ของ NotFoundHandler เป็นอย่างอื่น


Leave a Reply

Your email address will not be published. Required fields are marked *