Go: خدمات HTTP في Go

آخر تحديث: 2026-08-26

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

عندما يقرر فريقك «استخدام المكتبات القياسية فقط دون إدخال أي أطر عمل ويب»، هل يمكنك كتابة مسارات وبرمجيات وسيطة واضحة تمامًا مثل Gin أو Echo؟ في هذا الدرس، ستتقن جميع التقنيات الأساسية لخدمات HTTP بلغة Go.

1. ستتعلم



2. قصة حقيقية لمهندس برمجيات الخلفية

(1) المشكلة: واجهة برمجة تطبيقات (API) بسيطة — اخترنا Gin، لكن بعد ثلاثة أشهر، واجهنا عقبة أثناء عملية الترقية

أليس هي إحدى أعضاء فريق «الخلفية»، وتحتاج إلى إعداد واجهة برمجة تطبيقات REST لإدارة «المستخدمين»:

"لقد كتبت ثلاثة مسارات باستخدام إطار عمل Gin: GET /users، وPOST /users، وGET /users/:id. لكن بعد ثلاثة أشهر، تم إصدار Go 1.22، وأضيفت إلى المكتبة القياسية دعم أصلي لمعلمات الطرق والمسارات. والآن أرغب في إزالة التبعية لـ Gin، لكن سيتعين عليّ تغيير جميع توقيعات المعالجات — gin.Context مقابل http.ResponseWriter. قال مديري: «لا يستحق الأمر إعادة هيكلة مئات الأسطر من الكود لمجرد التخلص من تبعية واحدة.»"

قرارها في ذلك الوقت:

GO
// Gin dependency version (wanted to migrate three months later)
r := gin.Default()
r.GET("/users", listUsers)               // gin.Context
r.POST("/users", createUser)             // gin.Context
r.GET("/users/:id", getUser)             // gin.Context
// Want to migrate to the standard library? All handler signatures must change!

(2) حل لـ Go 1.22: التوجيه الأصلي في المكتبة القياسية

GO
// Standard library version (Go 1.22+, no dependencies required)
package main

import (
    "encoding/json"
    "log"
    "net/http"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

var users = []User{{ID: 1, Name: "Alice"}}

func main() {
    mux := http.NewServeMux()

    // Go 1.22 enhanced routing: method + path pattern + path parameters
    mux.HandleFunc("GET /users", listUsers)
    mux.HandleFunc("POST /users", createUser)
    mux.HandleFunc("GET /users/{id}", getUser)

    log.Println("Server started on :8080")
    log.Fatal(http.ListenAndServe(":8080", mux))
}

// Standard handler signature: http.ResponseWriter + *http.Request
func listUsers(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(users)
}

func createUser(w http.ResponseWriter, r *http.Request) {
    var u User
    if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    u.ID = len(users) + 1
    users = append(users, u)
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(u)
}

func getUser(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id") // Path parameter!
    // Look up user...
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(User{ID: 1, Name: "Alice"})
}

(3) الأداء: نمط Gin مقابل المكتبة القياسية (Go 1.22)

ميزة Gin (مكتبة خارجية) المكتبة القياسية (Go < 1.22) المكتبة القياسية (Go 1.22+)
معلمة المسار :id ❌ يجب تحليلها يدويًّا {id}
توجيه الأسلوب ❌ التحقق داخل المُعالج "GET /path"
استجابة JSON c.JSON() تعيين الرأس يدويًّا تعيين الرأس يدويًّا
التبعيات حزمة خارجية واحدة 0 0
الأداء أبطأ قليلاً (انعكاس) أصلي أصلي
💡 نصيحة: تعد ميزة التوجيه المحسّنة في net/http في Go 1.22 كافية لمعظم مشاريع الويب. إذا لم تكن بحاجة إلى ميزات خاصة بإطار العمل (مثل الربط/التحقق التلقائي أو نظام بيئي غني بالبرمجيات الوسيطة)، فأعطِ الأولوية للمكتبة القياسية.



3. أساسيات HTTP

▶ مثال: أبسط خدمة HTTP

GO
package main

import (
    "fmt"
    "log"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s!", r.URL.Query().Get("name"))
}

func main() {
    http.HandleFunc("/hello", helloHandler)
    log.Println("Server started on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}
▶ جرّب الكود

اختبار:

BASH
$ curl "http://localhost:8080/hello?name=Alice"
Hello, Alice!

(2) أنواع النوى

النوع الوصف
http.ResponseWriter واجهة لكتابة استجابات HTTP
*http.Request طلب HTTP، بما في ذلك عنوان URL والرؤوس والنص الأساسي والنموذج
http.Handler الواجهة: ServeHTTP(w, r)
http.HandlerFunc محول الدالة: يحول دالة عادية إلى معالج
http.ServeMux معدد مسارات

(3) شرح مفصل لواجهة المعالج

GO
package main

import (
    "fmt"
    "log"
    "net/http"
)

// Method 1: Implement the Handler interface
type Greeter struct {
    Greeting string
}

func (g *Greeter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "%s, %s!", g.Greeting, r.URL.Path[1:])
}

// Method 2: HandlerFunc adapter
func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s!", r.URL.Query().Get("name"))
}

func main() {
    mux := http.NewServeMux()

    // Struct Handler
    mux.Handle("/greet", &Greeter{Greeting: "Welcome"})

    // Function Handler (HandlerFunc automatic conversion)
    mux.HandleFunc("/hello", helloHandler)

    log.Print(http.ListenAndServe(":8080", mux))
}


4. Go 1.22: تحسينات في التوجيه

▶ مثال: الطريقة + نمط المسار + معلمة المسار

GO 📖 للعرض فقط
package main

import (
    "encoding/json"
    "log"
    "net/http"
)

type Item struct {
    ID    int     `json:"id"`
    Name  string  `json:"name"`
    Price float64 `json:"price"`
}

var items = []Item{
    {ID: 1, Name: "Laptop", Price: 999.99},
    {ID: 2, Name: "Mouse", Price: 29.99},
}

func main() {
    mux := http.NewServeMux()

    // Go 1.22 method + path pattern
    mux.HandleFunc("GET /items", listItems)
    mux.HandleFunc("POST /items", createItem)
    mux.HandleFunc("GET /items/{id}", getItem)
    mux.HandleFunc("PUT /items/{id}", updateItem)
    mux.HandleFunc("DELETE /items/{id}", deleteItem)

    // Wildcard suffix: path prefix matching
    mux.HandleFunc("GET /items/{path...}", wildcardHandler)

    log.Print(http.ListenAndServe(":8080", mux))
}

func listItems(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(items)
}

func createItem(w http.ResponseWriter, r *http.Request) {
    var item Item
    if err := json.NewDecoder(r.Body).Decode(&item); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    item.ID = len(items) + 1
    items = append(items, item)
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(item)
}

func getItem(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    // Look up item...
    _ = id
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(items[0])
}

func updateItem(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    _ = id
    // Update logic...
    w.WriteHeader(http.StatusNoContent)
}

func deleteItem(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    _ = id
    // Delete logic...
    w.WriteHeader(http.StatusNoContent)
}

func wildcardHandler(w http.ResponseWriter, r *http.Request) {
    path := r.PathValue("path")
    w.Header().Set("Content-Type", "text/plain")
    http.Error(w, "Not found: "+path, http.StatusNotFound)
}
61 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

(2) مقارنة بين أوضاع التوجيه في Go 1.22

الوضع Go < 1.22 Go 1.22+ مثال
مطابقة الطرق غير مدعوم (عبارة if داخل المعالج) مدعوم "GET /items"
معلمة المسار غير مدعومة {name} صيغة "GET /items/{id}"
لاحقة البدل غير مدعومة {path...} "GET /static/{file...}"
المسار الدقيق /items "GET /items" تطابق تام مع /items
مطابقة البادئة /items/ "GET /items/" تتطابق مع /items/...

▶ مثال: أولوية المسار

GO
package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    mux := http.NewServeMux()

    // Exact path > prefix path
    mux.HandleFunc("GET /items", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "items list")
    })
    mux.HandleFunc("GET /items/{id}", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "item %s\n", r.PathValue("id"))
    })
    mux.HandleFunc("GET /items/featured", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "featured items")
    })

    log.Print(http.ListenAndServe(":8080", mux))
}
▶ جرّب الكود

اختبار:

BASH
$ curl localhost:8080/item
item list
$ curl localhost:8080/item/42
item 42
$ curl localhost:8080/item/featured
featured item    # Exact match takes priority over {id} wildcard
🔥 خطأ شائع: لا يهم الترتيب الذي يتم به تسجيل المسارات — فمطابقة المسارات في Go 1.22 تستند إلى قواعد الأولوية (المطابقة التامة > البادئة > حرف البدل)، وليس إلى ترتيب التسجيل. ستتغلب المسارات ذات الأولوية الأعلى على المطابقات ذات الأولوية الأقل، حتى لو كانت هذه الأخيرة قد سُجلت أولاً.



5. الطلبات والردود

▶ مثال: معلمات الاستعلام، النماذج، JSON

GO 📖 للعرض فقط
package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
)

type Response struct {
    Method string      `json:"method"`
    Path   string      `json:"path"`
    Query  interface{} `json:"query,omitempty"`
    Form   interface{} `json:"form,omitempty"`
    JSON   interface{} `json:"json,omitempty"`
}

func handler(w http.ResponseWriter, r *http.Request) {
    resp := Response{
        Method: r.Method,
        Path:   r.URL.Path,
    }

    // Query parameters
    if r.Method == http.MethodGet {
        resp.Query = r.URL.Query()
    }

    // Form data
    if r.Method == http.MethodPost {
        contentType := r.Header.Get("Content-Type")
        switch {
        case contentType == "application/x-www-form-urlencoded":
            r.ParseForm()
            resp.Form = r.Form
        case contentType == "application/json":
            var body interface{}
            json.NewDecoder(r.Body).Decode(&body)
            resp.JSON = body
        }
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(resp)
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", handler)

    log.Print(http.ListenAndServe(":8080", mux))
}
42 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

▶ مثال: دالة مساعدة للاستجابة بتنسيق JSON

GO
package main

import (
    "encoding/json"
    "log"
    "net/http"
)

// JSON response utility function
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}

func writeError(w http.ResponseWriter, status int, message string) {
    writeJSON(w, status, map[string]string{"error": message})
}

type Product struct {
    ID    int     `json:"id"`
    Name  string  `json:"name"`
    Price float64 `json:"price"`
}

func getProduct(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    // Simulate lookup
    if id != "1" {
        writeError(w, http.StatusNotFound, "product not found")
        return
    }
    writeJSON(w, http.StatusOK, Product{ID: 1, Name: "Laptop", Price: 999.99})
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /products/{id}", getProduct)

    log.Print(http.ListenAndServe(":8080", mux))
}
▶ جرّب الكود
100%
sequenceDiagram
    participant Client as HTTP Client
    participant Mux as ServeMux
    participant Handler as Handler
    
    Client->>Mux: GET /products/1
    Mux->>Mux: Route matching
    Mux->>Handler: ServeHTTP(w, r)
    Handler->>Handler: r.PathValue("id") → "1"
    Handler->>Handler: writeJSON(w, 200, product)
    Handler-->>Client: HTTP 200 + JSON body

(3) مرجع سريع لرموز حالة HTTP

الرمز الثابت الغرض
200 http.StatusOK نجاح
201 http.StatusCreated تم إنشاء المورد بنجاح
204 http.StatusNoContent نجحت العملية، لكن لا يوجد نص استجابة
400 http.StatusBadRequest خطأ في طلب العميل
401 http.StatusUnauthorized غير مصرح به
403 http.StatusForbidden لا يوجد إذن
404 http.StatusNotFound لم يتم العثور على المورد
500 http.StatusInternalServerError خطأ داخلي في الخادم


6. خدمة الملفات الثابتة

▶ مثال: الملفات الثابتة

GO
package main

import (
    "log"
    "net/http"
)

func main() {
    mux := http.NewServeMux()

    // Static file service: /static/ prefix → ./static/ directory
    mux.Handle("GET /static/", http.StripPrefix("/static/",
        http.FileServer(http.Dir("./static"))))

    // Single file
    mux.Handle("GET /favicon.ico", http.FileServer(http.Dir("./static")))

    log.Print(http.ListenAndServe(":8080", mux))
}
▶ جرّب الكود

7. مثال كامل: واجهة برمجة تطبيقات (API) للملاحظات بنمط REST

GO
// notes_api.go
package main

import (
    "encoding/json"
    "log"
    "net/http"
    "strconv"
    "sync"
    "time"
)

// ---------- Model ----------

type Note struct {
    ID        int       `json:"id"`
    Title     string    `json:"title"`
    Content   string    `json:"content"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

// ---------- Store ----------

type NoteStore struct {
    mu     sync.RWMutex
    notes  map[int]Note
    nextID int
}

func NewNoteStore() *NoteStore {
    return &NoteStore{
        notes:  make(map[int]Note),
        nextID: 1,
    }
}

func (s *NoteStore) Create(title, content string) Note {
    s.mu.Lock()
    defer s.mu.Unlock()
    n := Note{
        ID:        s.nextID,
        Title:     title,
        Content:   content,
        CreatedAt: time.Now(),
        UpdatedAt: time.Now(),
    }
    s.nextID++
    s.notes[n.ID] = n
    return n
}

func (s *NoteStore) List() []Note {
    s.mu.RLock()
    defer s.mu.RUnlock()
    result := make([]Note, 0, len(s.notes))
    for _, n := range s.notes {
        result = append(result, n)
    }
    return result
}

func (s *NoteStore) Get(id int) (Note, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    n, ok := s.notes[id]
    return n, ok
}

func (s *NoteStore) Update(id int, title, content string) (Note, bool) {
    s.mu.Lock()
    defer s.mu.Unlock()
    n, ok := s.notes[id]
    if !ok {
        return Note{}, false
    }
    n.Title = title
    n.Content = content
    n.UpdatedAt = time.Now()
    s.notes[id] = n
    return n, true
}

func (s *NoteStore) Delete(id int) bool {
    s.mu.Lock()
    defer s.mu.Unlock()
    _, ok := s.notes[id]
    if !ok {
        return false
    }
    delete(s.notes, id)
    return true
}

// ---------- API ----------

type NotesAPI struct {
    store *NoteStore
}

func NewNotesAPI(store *NoteStore) *NotesAPI {
    return &NotesAPI{store: store}
}

func (api *NotesAPI) Register(mux *http.ServeMux) {
    mux.HandleFunc("GET /notes", api.ListNotes)
    mux.HandleFunc("POST /notes", api.CreateNote)
    mux.HandleFunc("GET /notes/{id}", api.GetNote)
    mux.HandleFunc("PUT /notes/{id}", api.UpdateNote)
    mux.HandleFunc("DELETE /notes/{id}", api.DeleteNote)
}

// Utility functions
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}

func writeError(w http.ResponseWriter, status int, message string) {
    writeJSON(w, status, map[string]string{"error": message})
}

// ---------- Handlers ----------

func (api *NotesAPI) ListNotes(w http.ResponseWriter, r *http.Request) {
    notes := api.store.List()
    writeJSON(w, http.StatusOK, notes)
}

func (api *NotesAPI) CreateNote(w http.ResponseWriter, r *http.Request) {
    var input struct {
        Title   string `json:"title"`
        Content string `json:"content"`
    }
    if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
        writeError(w, http.StatusBadRequest, "invalid JSON body")
        return
    }
    if input.Title == "" {
        writeError(w, http.StatusBadRequest, "title is required")
        return
    }

    note := api.store.Create(input.Title, input.Content)
    writeJSON(w, http.StatusCreated, note)
}

func (api *NotesAPI) GetNote(w http.ResponseWriter, r *http.Request) {
    idStr := r.PathValue("id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        writeError(w, http.StatusBadRequest, "invalid note ID")
        return
    }

    note, ok := api.store.Get(id)
    if !ok {
        writeError(w, http.StatusNotFound, "note not found")
        return
    }
    writeJSON(w, http.StatusOK, note)
}

func (api *NotesAPI) UpdateNote(w http.ResponseWriter, r *http.Request) {
    idStr := r.PathValue("id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        writeError(w, http.StatusBadRequest, "invalid note ID")
        return
    }

    var input struct {
        Title   string `json:"title"`
        Content string `json:"content"`
    }
    if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
        writeError(w, http.StatusBadRequest, "invalid JSON body")
        return
    }

    note, ok := api.store.Update(id, input.Title, input.Content)
    if !ok {
        writeError(w, http.StatusNotFound, "note not found")
        return
    }
    writeJSON(w, http.StatusOK, note)
}

func (api *NotesAPI) DeleteNote(w http.ResponseWriter, r *http.Request) {
    idStr := r.PathValue("id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        writeError(w, http.StatusBadRequest, "invalid note ID")
        return
    }

    if !api.store.Delete(id) {
        writeError(w, http.StatusNotFound, "note not found")
        return
    }
    w.WriteHeader(http.StatusNoContent)
}

func main() {
    store := NewNoteStore()
    api := NewNotesAPI(store)

    mux := http.NewServeMux()
    api.Register(mux)

    log.Println("Note API started on :8080")
    log.Println("Available endpoints:")
    log.Println("  GET    /notes       — List all notes")
    log.Println("  POST   /notes       — Create a note")
    log.Println("  GET    /notes/{id}  — Get a single note")
    log.Println("  PUT    /notes/{id}  — Update a note")
    log.Println("  DELETE /notes/{id}  — Delete a note")
    log.Fatal(http.ListenAndServe(":8080", mux))
}
🔥 خطأ شائع: لا يقوم http.Error(w, msg, code) بتعيين Content-Type إلى JSON. إذا كنت تُرجع خطأً في JSON، فاستخدم json.NewEncoder(w).Encode(errResp) وقم بتعيين Header() يدويًّا. يُرجع http.Error() في المكتبة القياسية نصًّا عاديًّا.



❓ أسئلة شائعة

س كيف تقارن تحسينات التوجيه في Go 1.22 بـ Gin؟
ج إنها كافية لمعظم السيناريوهات. يدعم Go 1.22 مطابقة الطرق (method matching) ومعلمات المسار (path parameters) وأحرف البدل (wildcards). تكمن القيمة المضافة لـ Gin في ربط/التحقق من صحة الطلبات (request binding/validation)، ونظامها البيئي للبرمجيات الوسيطة (middleware ecosystem)، ومعالجة الأخطاء (error handling). إذا كان مشروعك لا يتطلب هذه الميزات، فإن المكتبة القياسية أخف وزنًا وأكثر أمانًا (بدون تبعيات).
س ما الفرق بين Handler وHandlerFunc؟
ج Handler هي واجهة (تتطلب تنفيذ طريقة ServeHTTP)، بينما HandlerFunc هو مُحَوِّل من نوع الدالة — فهو يسمح للدوال العادية بالامتثال تلقائيًا لواجهة Handler. والاثنان متكافئان تمامًا: mux.Handle("/path", handler) وmux.HandleFunc("/path", handlerFunc) لهما نفس التأثير.
س كيف يمكنني استرداد معلمات المسار؟
ج في Go 1.22 والإصدارات الأحدث، استخدم r.PathValue("name") لاسترداد معلمات المسار من النوع {name}. في الإصدارات الأقدم، تحتاج إلى تحليلها يدويًا من r.URL.Path أو استخدام مكتبة تابعة لجهة خارجية. يجب أن تتطابق أسماء معلمات المسار مع {name} في نمط الجذر.
س ما الفرق بين ListenAndServe وListenAndServeTLS؟
ج الأول يستخدم بروتوكول HTTP (المنفذ 80)، بينما الثاني يستخدم بروتوكول HTTPS (المنفذ 443) ويتطلب شهادة وملف مفتاح خاص. مثال على HTTPS: http.ListenAndServeTLS(":443", "cert.pem", "key.pem", mux).
س كيف يمكنني إيقاف تشغيل خادم HTTP بشكل سلس؟
ج استخدم server.Shutdown(ctx) بالاقتران مع os/signal لالتقاط إشارات الإنهاء. سيقوم Shutdown بالانتظار حتى تتم معالجة جميع الاتصالات النشطة قبل الإغلاق. لا تستخدم server.Close() — فهو سيؤدي إلى إنهاء الطلبات قيد المعالجة حالياً بشكل قسري.
س ما معنى تمرير nil كمعلمة ثانية إلى http.ListenAndServe؟
ج يشير تمرير nil إلى استخدام http.DefaultServeMux (الموجه الافتراضي العام). يُنصح بإنشاء http.NewServeMux() بشكل صريح لتجنب إفساد التوجيه العام — خاصةً عند عزل المسارات أثناء الاختبار.
س هل يدعم ServeMux المسارات الفرعية المتداخلة؟
ج لا يدعم ServeMux الموجود في المكتبة القياسية المسارات المتداخلة (تجميع المسارات الفرعية). يمكنك تحقيق نتيجة مشابهة عن طريق تسجيل المسارات يدويًّا باستخدام بادئة مشتركة، أو باستخدام مكتبة توجيه تابعة لجهة خارجية مثل chi (وهي خفيفة الوزن للغاية) لتنفيذ التداخل.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (مستوى الصعوبة ⭐): أنشئ خدمة HTTP بسيطة وقم بتسجيل ثلاثة مسارات: GET /time تعرض الوقت الحالي بتنسيق JSON، وGET /health تعرض {"status": "ok"}، وGET /version تعرض رقم الإصدار. استخدم بناء الجملة المحسّن للتوجيه في Go 1.22.

  2. مشكلة متقدمة (صعوبة ⭐⭐): قم بتنفيذ واجهة برمجة تطبيقات (API) لقائمة المهام. المتطلبات: (1) عمليات CRUD كاملة؛ (2) استخدام طرق Go 1.22 والتوجيه القائم على المسار؛ (3) طلبات واستجابات بتنسيق JSON؛ (4) التخزين في الذاكرة (map + حماية RWMutex)؛ (5) إرجاع رموز حالة HTTP المناسبة.

  3. التحدي (الصعوبة: ⭐⭐⭐): قم بتنفيذ خدمة تقصير عناوين URL. المتطلبات: (1) تقبل POST /shorten عنوان URL طويلًا وتُرجع رمزًا قصيرًا (سلسلة عشوائية مكونة من 6 أحرف)؛ (2) يجب أن تقوم GET /{code} بإجراء إعادة توجيه 301 إلى عنوان URL الأصلي؛ (3) إحصائيات الوصول: يجب أن تُرجع GET /stats/{code} العدد للزيارات؛ (4) استخدم -race للتحقق من أمان التزامن؛ (5) استخدم RWMutex لحماية عداد الإحصائيات.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%