إرسال JSON في طلب POST بلغة Go: من "كيف يعمل هذا أساساً" إلى "فعلته ونسيته"
هل سبق لك أن علقت في Stack Overflow محاولاً فهم لماذا لا يقبل الخادم طلب POST الخاص بك مع JSON بلغة Go؟ مألوف: يبدو كل شيء صحيحاً، لكن الرد يأتي بـ 400 Bad Request أو نص فارغ. اتضح أن هناك عدة طرق لإرسال JSON في Go، واختيار الطريقة الصحيحة ليس مجرد مسألة ذوق، بل مسألة استقرار الخدمة المصغرة الخاصة بك.
في هذه المقالة، لن نعرض الكود فقط. سنستعرض ثلاثة أساليب: من الساذج (الذي يعمل لكنه خطير) إلى حل جاهز للإنتاج مع معالجة صحيحة للأخطاء والسياقات. إذا كنت تكتب بلغة Go لواجهات API أو البوتات أو التكاملات، فهذه قراءة إلزامية.
لماذا هذا الموضوع شائع جداً؟
Go هي لغة للخدمات الشبكية. عاجلاً أم آجلاً، يواجه أي مطور مهمة: "إرسال JSON في نص طلب POST". قد يبدو الأمر بسيطاً، لكن هناك تفاصيل دقيقة:
- تحتاج إلى تسلسل الهيكل بشكل صحيح إلى JSON (تعبئة).
- من المهم ألا تنسى العنوان
Content-Type: application/json. - من الضروري إغلاق نص الرد بشكل صحيح.
- نريد تجنب تسرب الذاكرة وحالات الذعر.
دعنا نستعرض كل أسلوب بأمثلة حية.
الأسلوب 1: "على عجالة" — bytes.NewBuffer + json.Marshal
هذه هي أبسط طريقة. نأخذ هيكلاً، نعبئه إلى بايتات، نضعه في مخزن مؤقت ونرسله. العديد من مطوري Go المبتدئين يكتبون بهذه الطريقة.
package main
import ( "bytes" "encoding/json" "fmt" "net/http")
type User struct { Name string `json:"name"` Email string `json:"email"`}
func main() { user := User{Name: "إيفان بيتروف", Email: "ivan@example.com"} // الخطوة 1: تعبئة الهيكل إلى JSON jsonData, err := json.Marshal(user) if err != nil { fmt.Println("خطأ في التعبئة:", err) return } // الخطوة 2: إنشاء طلب POST resp, err := http.Post( "https://api.example.com/users", "application/json", bytes.NewBuffer(jsonData), ) if err != nil { fmt.Println("خطأ في الطلب:", err) return } defer resp.Body.Close() fmt.Println("الحالة:", resp.Status)}مزايا وعيوب هذا الأسلوب
- الميزة: أقل كود، مفهوم للمبتدئين.
- العيب: لا مرونة. لا يمكنك إضافة عناوين مخصصة (مصادقة، trace-id). لا تحكم في المهلات. إذا أعاد الخادم خطأً، فلن تعالجه.
- العيب: مشكلة محتملة: إذا أعاد
json.Marshalخطأً، ستلتقطه، لكن في الكود الحقيقي غالباً ما يُنسى التحقق.
هذه الطريقة مناسبة للنماذج الأولية والاختبارات. للإنتاج — لا.
الأسلوب 2: "الصحيح" — http.NewRequest + json.NewEncoder
يوصي مجتمع Go باستخدام http.NewRequest و json.NewEncoder. لماذا؟ لأنه يعطي تحكماً كاملاً في الطلب وأكثر كفاءة في الذاكرة.
package main
import ( "bytes" "encoding/json" "fmt" "net/http" "time")
type Order struct { ID int `json:"id"` Amount float64 `json:"amount"` Items []string `json:"items"`}
func sendOrder(order Order) error { // إنشاء مخزن مؤقت ومشفر var buf bytes.Buffer enc := json.NewEncoder(&buf) // الترميز مباشرة في المخزن المؤقت (بدون []byte وسيط) if err := enc.Encode(order); err != nil { return fmt.Errorf("خطأ في ترميز JSON: %w", err) } // إنشاء الطلب req, err := http.NewRequest("POST", "https://api.example.com/orders", &buf) if err != nil { return fmt.Errorf("خطأ في إنشاء الطلب: %w", err) } // تعيين العناوين req.Header.Set("Content-Type", "application/json") req.Hea