طلب GET
كل مثال في هذه الصفحة يشغّل خادمه الخاص بـ httptest.NewServer ويستدعيه عبر اتصال TCP حقيقي على localhost. استبدل srv.URL بعنوان حقيقي مثل https://api.example.com وتبقى شيفرة العميل كما هي.
القواعد الثلاث الظاهرة هنا:
- افحص
errأولًا. يبلغ عن الفشل في الحصول على أي ردّ أصلًا: أخطاء DNS، أو الاتصالات المرفوضة، أو انقضاء المهل، أو مشكلات TLS. defer resp.Body.Close()مباشرة بعد فحص الخطأ. الجسم يُبقي الاتصال مفتوحًا. والجسم غير المغلق يسرّب الاتصال.- اقرأ الجسم أو فكّ ترميزه.
io.ReadAllللنص، وjson.NewDecoder(resp.Body).Decode(&v)لـ JSON.
رموز الحالة ليست أخطاء
تعيد http.Get القيمة err == nil لأي ردّ يرسله الخادم، بما في ذلك 404 و500. عليك فحص الحالة بنفسك:
يحدّ io.LimitReader من مقدار ما تقرؤه من جسم الخطأ، فلا يستطيع خادم سيئ السلوك أن يجعلك تحمّل ميغابايتات في رسالة خطأ. كثير من الواجهات البرمجية تعيد 201 أو 202 أو 204 للنجاح؛ افحص resp.StatusCode >= 200 && resp.StatusCode < 300 عندما يكون أي رمز 2xx مقبولًا.
المهل: لا تستخدم العميل الافتراضي للحركة الحقيقية أبدًا
ليس لـ http.Get وhttp.Post وhttp.DefaultClient أي مهلة كلية (الناقل الافتراضي يحدّ فقط الاتصال ومصافحة TLS). الخادم الذي يقبل الاتصال ولا يجيب أبدًا يوقف الـ goroutine الخاصة بك إلى الأبد. أنشئ عميلك الخاص:
تغطّي Client.Timeout التبادل كله: الاتصال، وإعادات التوجيه، وقراءة الجسم. الخطأ الذي تعيده *url.Error يبلغ Timeout() == true، وتجده errors.As عبر الواجهة net.Error. أنشئ عميلًا واحدًا وأعد استخدامه في كل مكان؛ فـ http.Client آمن للاستخدام المتزامن. يعيش مجمّع الاتصالات في Transport الخاص به، لا في Client: إنشاء http.Client{} جديد لكل طلب ما زال يتشارك http.DefaultTransport ومجمّعه، لكن إنشاء http.Transport جديد لكل طلب يبدأ بمجمّع فارغ في كل مرة.
طلبات مع سياق وترويسات
تبني http.NewRequestWithContext طلبًا يمكنك تخصيصه قبل إرساله بـ client.Do. يعطي السياق موعدًا نهائيًا أو إلغاء لكل طلب فوق مهلة العميل، ويربط الاستدعاءات الصادرة بعمر طلب وارد في الخادم.
ضمّ مدخلات المستخدم إلى عنوان URL ("?q=" + q) ينكسر مع & و# والمسافات. تتولّى url.Values وتابعها Encode الهروب، وترتّب المفاتيح. ولأجزاء المسار استخدم url.PathEscape.
POST JSON
client.Post(url, contentType, body) اختصار لطلب بترويسة Content-Type تلك. لأي شيء آخر (ترويسة مصادقة، أو PUT أو PATCH أو DELETE) استخدم http.NewRequestWithContext مع الطريقة وclient.Do. ولإرسال النماذج، ترمّز client.PostForm(url, url.Values{...}) القيم وتضبط application/x-www-form-urlencoded.
إعادة استخدام الاتصالات
لا يعيد العميل استخدام اتصال TCP إلا عندما تقرأ الجسم السابق حتى النهاية وتغلقه. إذا توقّفت عن القراءة مبكرًا (مثلًا بعد حالة خطأ)، فأفرغه قبل الإغلاق عندما ترسل طلبات كثيرة إلى المضيف نفسه:
defer func() {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}()
للأجسام التي قد تكون ضخمة، أفرغها عبر io.LimitReader بدلًا من ذلك، أو اقبل خسارة الاتصال.
إعادات التوجيه
يتبع العميل ما يصل إلى 10 إعادات توجيه تلقائيًا، ويخبرك resp.Request.URL أين انتهيت. للتوقّف عند أول إعادة توجيه، مثلًا لقراءة ترويسة Location، اضبط CheckRedirect:
client := &http.Client{
Timeout: 10 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // return the 3xx response itself
},
}
أخطاء شائعة
- استخدام
http.Getأوhttp.DefaultClientفي الإنتاج. لا مهلة. أنشئhttp.ClientمعTimeout. - نسيان
resp.Body.Close(). تتسرّب الاتصالات حتى تبدأ الطلبات بالفشل. - إغلاق الجسم قبل فحص
err. عندما لا يكونerrمساويًا لـnilيكونrespمساويًا لـnil، فتسبّبresp.Body.Close()حالة panic. - اعتبار
err == nilنجاحًا. افحصresp.StatusCode. - إنشاء
http.Transportجديد لكل طلب. لكل ناقل مجمّع اتصالاته الخاص، فيفتح كل طلب اتصالًا جديدًا (ومصافحة TLS جديدة). شارك ناقلًا واحدًا، عادة عبرhttp.Clientواحد مشترك. - بناء نصوص الاستعلام يدويًا. استخدم
url.Values.
الأسئلة الشائعة
كيف أرسل طلب GET في Go؟
resp, err := http.Get(url)، افحص err، ثم defer resp.Body.Close() واقرأ الجسم بـ io.ReadAll(resp.Body) أو فكّ ترميزه بـ json.NewDecoder(resp.Body).Decode(&v). وافحص أيضًا resp.StatusCode: الرمز 404 أو 500 ليس خطأ من http.Get.
كيف أضبط مهلة لطلب HTTP في Go؟
أنشئ عميلًا بمهلة، client := &http.Client{Timeout: 10 * time.Second}، واستخدمه بدل http.Get أو http.DefaultClient، اللذين لا مهلة كلية لهما: الخادم الذي يقبل الاتصال ولا يجيب أبدًا يوقف الاستدعاء إلى الأبد. لحد خاص بكل طلب ابنِ الطلب بـ http.NewRequestWithContext وسياق من context.WithTimeout.
كيف أرسل طلب POST بجسم JSON في Go؟
سلسل القيمة ثم أرسلها: body, _ := json.Marshal(v) ثم http.Post(url, "application/json", bytes.NewReader(body)). لإضافة ترويسات مثل Authorization ابنِ الطلب بـ http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))، واضبط req.Header.Set("Content-Type", "application/json")، وأرسله بـ client.Do(req).
لماذا يجب إغلاق resp.Body في Go؟
الجسم تدفّق على اتصال مفتوح. إغلاقه يعيد الاتصال إلى المجمّع لإعادة استخدامه؛ ونسيانه يسرّب الاتصالات وواصفات الملفات حتى يبلغ البرنامج حدّه. أغلقه حتى عندما لا تقرؤه، واقرأه حتى النهاية إذا أردت إعادة استخدام الاتصال.