الانتقال إلى المحتوى الرئيسي
الوحدة 10 · الدرس 5 من 550/50 في الدورة~18 min
دروس الوحدة (5/5)

مشروع صغير: خادم HTTP

تعد حزمة net/http stdlib كافية لإنشاء خادم HTTP جاهز للإنتاج: التوجيه، والمعالجات، والبرامج الوسيطة، والإغلاق السلس. لا يوجد إطار مطلوب. بالنسبة لواجهات برمجة تطبيقات REST النموذجية، هذا هو الأساس.

مرحبا بالعالم

Go
package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "hello %s", r.URL.Path[1:])
    })
    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }
}

http.ListenAndServe يحظر ولا يرجع إلا في حالة حدوث خطأ. nil كالوسيطة الثانية = "استخدم DefaultServeMux".

توقيع المعالج

جميع المعالجات تشترك في نفس التوقيع:

Go
func(w http.ResponseWriter, r *http.Request)
  • w http.ResponseWriter — الإخراج: الرؤوس، رمز الحالة، النص.
  • r *http.Request — الإدخال: الطريقة، وعنوان URL، والرؤوس، والنص، والسياق.

النمط النموذجي:

Go
func ping(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodGet {
        http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
        return
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK) // optional, defaults to 200
    fmt.Fprintln(w, `{"ok":true}`)
}

التوجيه باستخدام ServeMux

*http.ServeMux هو جهاز التوجيه الأساسي. منذ Go 1.22 وهو يدعم الأنماط ذات معلمات الطريقة والمسار:

Go
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    fmt.Fprintln(w, "user", id)
})
mux.HandleFunc("POST /users", createUser)

http.ListenAndServe(":8080", mux)

بالنسبة لأجهزة التوجيه الأكثر تطورًا (المجموعات، البرامج الوسيطة المتكاملة، التعبير العادي) هناك chi، gorilla/mux، gin، echo. يغطي stdlib 90٪ من الحالات.

الوسيطة كديكور

البرنامج الوسيط هو وظيفة func(http.Handler) http.Handler:

Go
func logMW(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
    })
}

http.ListenAndServe(":8080", logMW(mux))

لتسلسلهم: logMW(authMW(rateLimitMW(mux))). كل وسيطة تزين التي تليها.

http.Server للتحكم الدقيق

http.ListenAndServe(addr, h) هو اختصار. بالنسبة للمهلات، وTLS، والإيقاف الآمن، تحتاج إلى إنشاء مثيل http.Server:

Go
srv := &http.Server{
    Addr:              ":8080",
    Handler:           mux,
    ReadHeaderTimeout: 5 * time.Second,
    ReadTimeout:       30 * time.Second,
    WriteTimeout:      30 * time.Second,
    IdleTimeout:       2 * time.Minute,
}
log.Fatal(srv.ListenAndServe())

إغلاق رشيقة مع السياق

عندما تتلقى العملية SIGINT/SIGTERM، فأنت تريد إنهاء الطلبات أثناء الرحلة قبل الإغلاق:

Go
ctx, stop := signal.NotifyContext(context.Background(),
    os.Interrupt, syscall.SIGTERM)
defer stop()

srv := &http.Server{Addr: ":8080", Handler: mux}
go func() {
    if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        log.Fatal(err)
    }
}()

<-ctx.Done() // wait for the signal

shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
    log.Println("shutdown:", err)
}

srv.Shutdown يتوقف عن قبول الاتصالات الجديدة وينتظر انتهاء الاتصالات النشطة (حتى الموعد النهائي المحدد).

قراءة نص JSON

Go
type CreateUserReq struct {
    Name string `json:"name"`
}

func createUser(w http.ResponseWriter, r *http.Request) {
    var req CreateUserReq
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    // ... use req.Name
    w.WriteHeader(http.StatusCreated)
}

r.Body هو io.ReadCloser: يقوم الخادم بإغلاقه تلقائيًا، ولكن json.NewDecoder لا يقرأ بعد كائن JSON الأول (مفيد للتحقق من صحة الإدخال).

تمارين

تمرين#go.m10.l5.e1
المحاولات: 0جارٍ التحميل…

سجل المعالج مرحبًا على الطريق '/' باستخدام http.HandleFunc.

جارٍ تحميل المحرر…

الحل متاح بعد 3 من المحاولات

تمرين#go.m10.l5.e2
المحاولات: 0جارٍ التحميل…

انتقل إلى الخادم:8080 مع http.ListenAndServe وختم الخطأ إذا لم يكن هناك أي شيء.

جارٍ تحميل المحرر…

الحل متاح بعد 3 من المحاولات

Quiz#go.m10.l5.e3
جاهز

ما هي الشركة الاصطلاحية لمعالج HTTP في Go؟

Go
func H(???) { ... }
خيارات الإجابة