ห้าปีที่แล้วฉันเริ่มต้น
บทความนี้เกี่ยวกับการเขียนเซิร์ฟเวอร์ใน 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)
}
หลังจากนี้หากคุณรันแอพพลิเคชั่นแล้วเปิดเพจขึ้นมา
เราจะใช้ตัวจัดการหลายครั้งในภายหลัง แต่ก่อนอื่นเรามาทำความเข้าใจว่าทุกอย่างทำงานอย่างไร
สุทธิ/http
ตัวอย่างที่ใช้แพ็คเกจ net/http
ตัวจัดการ HTTP
เมื่อเราได้รับคำขอ ผู้จัดการจะวิเคราะห์และสร้างการตอบกลับ ตัวจัดการใน Go มีการใช้งานดังนี้:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
ตัวอย่างแรกใช้ฟังก์ชันตัวช่วย http.HandleFunc มันจะรวมฟังก์ชันอื่นซึ่งจะนำ http.ResponseWriter และ http.Request เข้าสู่ ServeHTTP
กล่าวอีกนัยหนึ่งตัวจัดการใน Golang จะถูกนำเสนอในอินเทอร์เฟซเดียวซึ่งให้ตัวเลือกมากมายแก่โปรแกรมเมอร์ ตัวอย่างเช่น มิดเดิลแวร์ถูกใช้งานโดยใช้ตัวจัดการ โดยที่ ServeHTTP จะทำอะไรบางอย่างก่อน จากนั้นจึงเรียกเมธอด ServeHTTP ของตัวจัดการอื่น
ตามที่กล่าวไว้ข้างต้น ตัวจัดการเพียงแค่สร้างการตอบสนองต่อคำขอ แต่ควรใช้ตัวจัดการตัวใด ณ จุดใดจุดหนึ่ง?
ขอเส้นทาง
เพื่อให้ตัดสินใจได้ถูกต้อง ให้ใช้มัลติเพล็กเซอร์ HTTP ในไลบรารีหลายแห่งจะเรียกว่า muxer หรือเราเตอร์ แต่ทั้งหมดนั้นเป็นสิ่งเดียวกัน หน้าที่ของมัลติเพล็กเซอร์คือการวิเคราะห์เส้นทางคำขอและเลือกตัวจัดการที่เหมาะสม
หากคุณต้องการการสนับสนุนสำหรับการกำหนดเส้นทางที่ซับซ้อน ควรใช้ไลบรารีของบุคคลที่สาม บางส่วนของที่ทันสมัยที่สุด -
การทำงานกับเฟรมเวิร์กที่ซับซ้อนในสถานการณ์ปกติจะต้องใช้โซลูชันที่ไม่ได้มาตรฐาน และทำให้การใช้ตัวจัดการเริ่มต้นมีความซับซ้อนอย่างมาก ในการสร้างแอปพลิเคชันส่วนใหญ่ การรวมกันของไลบรารีเริ่มต้นและเราเตอร์แบบธรรมดาก็เพียงพอแล้ว
การประมวลผลแบบสอบถาม
นอกจากนี้ เราต้องการส่วนประกอบที่จะ "รับฟัง" สำหรับการเชื่อมต่อขาเข้าและเปลี่ยนเส้นทางคำขอทั้งหมดไปยังตัวจัดการที่ถูกต้อง 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)
หากเราเปิดในเบราว์เซอร์
หากคุณต้องการการกำหนดค่าโดยละเอียดเพิ่มเติม คุณควรใช้ตัวจัดการ 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 เต็มรูปแบบพร้อมการต่ออายุใบรับรองอัตโนมัติ
การเพิ่มเส้นทางที่กำหนดเอง
เราเตอร์เริ่มต้นที่รวมอยู่ในไลบรารีมาตรฐานนั้นดี แต่ก็มีพื้นฐานมาก แอปพลิเคชันส่วนใหญ่ต้องการการกำหนดเส้นทางที่ซับซ้อนมากขึ้น รวมถึงเส้นทางแบบซ้อนและแบบไวด์การ์ด หรือขั้นตอนสำหรับการตั้งค่ารูปแบบและพารามิเตอร์ของเส้นทาง
ในกรณีนี้ควรใช้แพ็คเกจ
ระบุเป็นไฟล์ 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 แนะนำ:
- หลักสูตรภาคปฏิบัติสองปี
"ฉันเป็นนักพัฒนาเว็บ PRO" .- หลักสูตรออนไลน์เพื่อการศึกษา
"นักพัฒนาจาวามืออาชีพ" .- หลักสูตรปีปฏิบัติ
"นักพัฒนา PHP จาก 0 ถึง PRO" .
ที่มา: will.com