การพัฒนาเว็บเซิร์ฟเวอร์ใน Golang - จากง่ายไปจนถึงซับซ้อน

การพัฒนาเว็บเซิร์ฟเวอร์ใน Golang - จากง่ายไปจนถึงซับซ้อน

ห้าปีที่แล้วฉันเริ่มต้น พัฒนาโกฟิชนี่เป็นการเปิดโอกาสให้ได้เรียนรู้ Golang ฉันรู้ว่า Go เป็นภาษาที่ทรงพลัง พร้อมด้วยห้องสมุดหลายแห่ง Go มีความหลากหลาย โดยเฉพาะอย่างยิ่ง สามารถใช้ในการพัฒนาแอปพลิเคชันฝั่งเซิร์ฟเวอร์ได้โดยไม่มีปัญหาใดๆ

บทความนี้เกี่ยวกับการเขียนเซิร์ฟเวอร์ใน Go เริ่มต้นด้วยสิ่งง่ายๆ เช่น "Hello world!" และปิดท้ายด้วยแอปพลิเคชันที่มีความสามารถดังต่อไปนี้:

- การใช้ Let's Encrypt สำหรับ HTTPS
— ทำงานเป็นเราเตอร์ API
- การทำงานกับมิดเดิลแวร์
— การประมวลผลไฟล์คงที่
- การปิดเครื่องที่ถูกต้อง

Skillbox แนะนำ: หลักสูตรภาคปฏิบัติ "นักพัฒนา Python ตั้งแต่เริ่มต้น".

เราเตือนคุณ: สำหรับผู้อ่าน "Habr" ทุกคน - ส่วนลด 10 rubles เมื่อลงทะเบียนในหลักสูตร Skillbox ใด ๆ โดยใช้รหัสส่งเสริมการขาย "Habr"

สวัสดีชาวโลก!

คุณสามารถสร้างเว็บเซิร์ฟเวอร์ใน Go ได้อย่างรวดเร็ว นี่คือตัวอย่างการใช้ตัวจัดการที่ส่งคืนข้อความ “Hello, world!” ตามที่สัญญาไว้ข้างต้น

package main
 
import (
"fmt"
"net/http"
)
 
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
})
http.ListenAndServe(":80", nil)
}

หลังจากนี้หากคุณรันแอพพลิเคชั่นแล้วเปิดเพจขึ้นมา localhostจากนั้นคุณจะเห็นข้อความ “Hello, world!” ทันที (ถ้าทุกอย่างทำงานถูกต้องแน่นอน)

เราจะใช้ตัวจัดการหลายครั้งในภายหลัง แต่ก่อนอื่นเรามาทำความเข้าใจว่าทุกอย่างทำงานอย่างไร

สุทธิ/http

ตัวอย่างที่ใช้แพ็คเกจ net/httpเป็นเครื่องมือหลักใน Go สำหรับการพัฒนาทั้งเซิร์ฟเวอร์และไคลเอนต์ HTTP เพื่อให้เข้าใจโค้ดนี้ เรามาทำความเข้าใจความหมายขององค์ประกอบสำคัญ XNUMX ส่วนกันดีกว่า: http.Handler, http.ServeMux และ http.Server

ตัวจัดการ HTTP

เมื่อเราได้รับคำขอ ผู้จัดการจะวิเคราะห์และสร้างการตอบกลับ ตัวจัดการใน Go มีการใช้งานดังนี้:

type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
}

ตัวอย่างแรกใช้ฟังก์ชันตัวช่วย http.HandleFunc มันจะรวมฟังก์ชันอื่นซึ่งจะนำ http.ResponseWriter และ http.Request เข้าสู่ ServeHTTP

กล่าวอีกนัยหนึ่งตัวจัดการใน Golang จะถูกนำเสนอในอินเทอร์เฟซเดียวซึ่งให้ตัวเลือกมากมายแก่โปรแกรมเมอร์ ตัวอย่างเช่น มิดเดิลแวร์ถูกใช้งานโดยใช้ตัวจัดการ โดยที่ ServeHTTP จะทำอะไรบางอย่างก่อน จากนั้นจึงเรียกเมธอด ServeHTTP ของตัวจัดการอื่น

ตามที่กล่าวไว้ข้างต้น ตัวจัดการเพียงแค่สร้างการตอบสนองต่อคำขอ แต่ควรใช้ตัวจัดการตัวใด ณ จุดใดจุดหนึ่ง?

ขอเส้นทาง

เพื่อให้ตัดสินใจได้ถูกต้อง ให้ใช้มัลติเพล็กเซอร์ HTTP ในไลบรารีหลายแห่งจะเรียกว่า muxer หรือเราเตอร์ แต่ทั้งหมดนั้นเป็นสิ่งเดียวกัน หน้าที่ของมัลติเพล็กเซอร์คือการวิเคราะห์เส้นทางคำขอและเลือกตัวจัดการที่เหมาะสม

หากคุณต้องการการสนับสนุนสำหรับการกำหนดเส้นทางที่ซับซ้อน ควรใช้ไลบรารีของบุคคลที่สาม บางส่วนของที่ทันสมัยที่สุด - กอริลลา/mux и โกชิ/ชิไลบรารีเหล่านี้ทำให้สามารถใช้การประมวลผลระดับกลางได้โดยไม่มีปัญหาใดๆ ด้วยความช่วยเหลือของพวกเขา คุณสามารถกำหนดค่าการกำหนดเส้นทางไวด์การ์ดและทำงานอื่นๆ ได้หลายอย่าง ข้อได้เปรียบของพวกเขาคือความเข้ากันได้กับตัวจัดการ HTTP มาตรฐาน เป็นผลให้คุณสามารถเขียนโค้ดง่ายๆ ที่สามารถแก้ไขได้ในอนาคต

การทำงานกับเฟรมเวิร์กที่ซับซ้อนในสถานการณ์ปกติจะต้องใช้โซลูชันที่ไม่ได้มาตรฐาน และทำให้การใช้ตัวจัดการเริ่มต้นมีความซับซ้อนอย่างมาก ในการสร้างแอปพลิเคชันส่วนใหญ่ การรวมกันของไลบรารีเริ่มต้นและเราเตอร์แบบธรรมดาก็เพียงพอแล้ว

การประมวลผลแบบสอบถาม

นอกจากนี้ เราต้องการส่วนประกอบที่จะ "รับฟัง" สำหรับการเชื่อมต่อขาเข้าและเปลี่ยนเส้นทางคำขอทั้งหมดไปยังตัวจัดการที่ถูกต้อง http.Server สามารถจัดการงานนี้ได้อย่างง่ายดาย

ข้อมูลต่อไปนี้แสดงว่าเซิร์ฟเวอร์รับผิดชอบงานทั้งหมดที่เกี่ยวข้องกับการประมวลผลการเชื่อมต่อ ตัวอย่างเช่น สิ่งนี้ทำงานโดยใช้โปรโตคอล TLS ในการใช้การเรียก http.ListenAndServer จะใช้เซิร์ฟเวอร์ HTTP มาตรฐาน

ตอนนี้เรามาดูตัวอย่างที่ซับซ้อนมากขึ้น

กำลังเพิ่ม Let's Encrypt

ตามค่าเริ่มต้น แอปพลิเคชันของเราทำงานบนโปรโตคอล HTTP แต่ขอแนะนำให้ใช้โปรโตคอล HTTPS ซึ่งสามารถทำได้โดยไม่มีปัญหาใน Go หากคุณได้รับใบรับรองและคีย์ส่วนตัว การลงทะเบียน ListenAndServeTLS ด้วยใบรับรองและไฟล์คีย์ที่ถูกต้องก็เพียงพอแล้ว

http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)

คุณสามารถทำได้ดีกว่าเสมอ

ขอเข้ารหัส ให้ใบรับรองฟรีพร้อมการต่ออายุอัตโนมัติ ในการใช้บริการคุณต้องมีแพ็คเกจ autocert.

วิธีที่ง่ายที่สุดในการกำหนดค่าคือการใช้เมธอด autocert.NewListener ร่วมกับ http.Serve วิธีการนี้ช่วยให้คุณรับและอัปเดตใบรับรอง TLS ในขณะที่เซิร์ฟเวอร์ HTTP ประมวลผลคำขอ:

http.Serve(autocert.NewListener("example.com"), nil)

หากเราเปิดในเบราว์เซอร์ example.comเราจะได้รับการตอบสนอง HTTPS “สวัสดีชาวโลก!”

หากคุณต้องการการกำหนดค่าโดยละเอียดเพิ่มเติม คุณควรใช้ตัวจัดการ autocert.Manager จากนั้นเราสร้างอินสแตนซ์ http.Server ของเราเอง (จนถึงตอนนี้เราใช้มันตามค่าเริ่มต้น) และเพิ่มตัวจัดการให้กับเซิร์ฟเวอร์ TLSConfig:

m := &autocert.Manager{
Cache:      autocert.DirCache("golang-autocert"),
Prompt:     autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("example.org", "www.example.org"),
}
server := &http.Server{
    Addr:      ":443",
    TLSConfig: m.TLSConfig(),
}
server.ListenAndServeTLS("", "")

นี่เป็นวิธีง่ายๆ ในการใช้งานการสนับสนุน HTTPS เต็มรูปแบบพร้อมการต่ออายุใบรับรองอัตโนมัติ

การเพิ่มเส้นทางที่กำหนดเอง

เราเตอร์เริ่มต้นที่รวมอยู่ในไลบรารีมาตรฐานนั้นดี แต่ก็มีพื้นฐานมาก แอปพลิเคชันส่วนใหญ่ต้องการการกำหนดเส้นทางที่ซับซ้อนมากขึ้น รวมถึงเส้นทางแบบซ้อนและแบบไวด์การ์ด หรือขั้นตอนสำหรับการตั้งค่ารูปแบบและพารามิเตอร์ของเส้นทาง

ในกรณีนี้ควรใช้แพ็คเกจ กอริลลา/mux и โกชิ/ชิ. เราจะได้เรียนรู้วิธีการทำงานกับสิ่งหลัง - ตัวอย่างแสดงด้านล่าง

ระบุเป็นไฟล์ api/v1/api.go ที่มีเส้นทางสำหรับ API ของเรา:

/ HelloResponse is the JSON representation for a customized message
type HelloResponse struct {
Message string `json:"message"`
}
 
// HelloName returns a personalized JSON message
func HelloName(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
response := HelloResponse{
Message: fmt.Sprintf("Hello %s!", name),
}
jsonResponse(w, response, http.StatusOK)
}
 
// NewRouter returns an HTTP handler that implements the routes for the API
func NewRouter() http.Handler {
r := chi.NewRouter()
r.Get("/{name}", HelloName)
return r
}

เราตั้งค่าคำนำหน้า api/vq สำหรับเส้นทางในไฟล์หลัก

จากนั้นเราสามารถติดตั้งสิ่งนี้กับเราเตอร์หลักของเราภายใต้คำนำหน้า api/v1/ ย้อนกลับไปในแอปพลิเคชันหลักของเรา:

// NewRouter returns a new HTTP handler that implements the main server routes
func NewRouter() http.Handler {
router := chi.NewRouter()
    router.Mount("/api/v1/", v1.NewRouter())
    return router
}
http.Serve(autocert.NewListener("example.com"), NewRouter())

ความง่ายในการทำงานกับเส้นทางที่ซับซ้อนทำให้การจัดโครงสร้างและการบำรุงรักษาแอปพลิเคชันขนาดใหญ่และซับซ้อนง่ายขึ้น

การทำงานกับมิดเดิลแวร์

การจัดเตรียมเกี่ยวข้องกับการรวมตัวจัดการ HTTP ตัวหนึ่งเข้ากับอีกตัวหนึ่ง ทำให้สามารถดำเนินการตรวจสอบสิทธิ์ การบีบอัด การบันทึก และฟังก์ชันอื่นๆ ได้อย่างรวดเร็ว

ตัวอย่างเช่น ลองดูที่อินเทอร์เฟซ http.Handler เราจะใช้เพื่อเขียนตัวจัดการที่รับรองความถูกต้องของผู้ใช้บริการ

func RequireAuthentication(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !isAuthenticated(r) {
            http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
            return
        }
        // Assuming authentication passed, run the original handler
        next.ServeHTTP(w, r)
    })
}

มีเราเตอร์ของบริษัทอื่น เช่น ไค ที่ช่วยให้คุณสามารถขยายการทำงานของมิดเดิลแวร์ได้

การทำงานกับไฟล์แบบคงที่

ไลบรารีมาตรฐาน Go มีความสามารถในการทำงานกับเนื้อหาคงที่ รวมถึงรูปภาพ, ไฟล์ JavaScript และ CSS สามารถเข้าถึงได้ผ่านฟังก์ชัน http.FileServer มันจะส่งคืนตัวจัดการที่ให้บริการไฟล์จากไดเร็กทอรีที่ระบุ

func NewRouter() http.Handler {
    router := chi.NewRouter()
    r.Get("/{name}", HelloName)
 
// Настройка раздачи статических файлов
staticPath, _ := filepath.Abs("../../static/")
fs := http.FileServer(http.Dir(staticPath))
    router.Handle("/*", fs)
    
    return r

เป็นเรื่องที่ควรค่าแก่การจดจำว่า http.Dir จะแสดงเนื้อหาของไดเร็กทอรีหากไม่มีไฟล์ index.html หลัก ในกรณีนี้ เพื่อป้องกันไม่ให้ไดเร็กทอรีถูกบุกรุก คุณควรใช้แพ็คเกจ unindexed.

การปิดเครื่องที่ถูกต้อง

Go ยังมีคุณสมบัติที่เรียกว่าการปิดระบบเซิร์ฟเวอร์ HTTP อย่างสง่างาม ซึ่งสามารถทำได้โดยใช้วิธี Shutdown() เซิร์ฟเวอร์จะเริ่มทำงานใน goroutine จากนั้นจะรับฟังช่องสัญญาณเพื่อรับสัญญาณขัดจังหวะ ทันทีที่ได้รับสัญญาณ เซิร์ฟเวอร์จะปิด แต่ไม่ใช่ทันที แต่หลังจากนั้นไม่กี่วินาที

handler := server.NewRouter()
srv := &http.Server{
    Handler: handler,
}
 
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
 
// Wait for an interrupt
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
 
// Attempt a graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)

เป็นข้อสรุป

Go เป็นภาษาที่ทรงพลังพร้อมไลบรารี่มาตรฐานสากลที่เกือบจะเป็นสากล ความสามารถเริ่มต้นนั้นกว้างมาก และสามารถปรับปรุงได้โดยใช้อินเทอร์เฟซ ซึ่งช่วยให้คุณพัฒนาเซิร์ฟเวอร์ HTTP ที่เชื่อถือได้อย่างแท้จริง

Skillbox แนะนำ:

ที่มา: will.com

เพิ่มความคิดเห็น