เรื่องน่ารู้เกี่ยวกับ HTTP Client ใน Golang

Teerapong Singthong 👨🏻‍💻
iamgoangle
Published in
3 min readMar 27, 2019
Original Photo By Pankaj Patel on Unsplash

สมมุติว่าเราต้องการเขียน HTTP Client เพื่อส่งข้อมูล และ รับข้อมูลไปสักที่ โดยไม่ใช้ External Library ใดๆเลย และใช้ค่า Default ที่ทาง GO ได้เตรียมไว้ให้ ตามตัวอย่างโค๊ดชุดนี้

การทำงานของโค๊ดเป็นการดึงข้อมูลแบบธรรมดา สามารถทำงานได้ปกติ แต่ถ้าเราใช้โค๊ดแบบนี้บน Production จะเกิดอะไรขึ้น?

Default value = 0 หรือ เท่ากับไม่มี Timeout

// Timeout specifies a time limit for requests made by this
// Client. The timeout includes connection time, any
// redirects, and reading the response body. The timer remains
// running after Get, Head, Post, or Do return and will
// interrupt reading of the Response.Body.
//
// A Timeout of zero means no timeout.
//
// The Client cancels requests to the underlying Transport
// as if the Request's Context ended.
https://golang.org/pkg/net/http/#Client

ตามเอกสารได้ออกแบบไว้ว่า http.Client นั้นได้ถูกกำหนดไว้ ให้รองรับการทำงานแบบ Long running connection ดังนั้น ผู้ใช้ต้องปรับแต่งตามความเหมาะสม ตามลักษณะงานนั้นๆ ด้วยนะครับ

กำหนด Timeout ให้กับ &http.Client{}

ใช้การกำหนด Context

อีกทางเลือกนึงในการ Cancel request เมื่อถึง timeout ที่กำหนด เราสามารถใช้ Context ได้ เช่นกัน

วิเคราะห์ปัญหา TIME_WAIT

เมื่อเกิดการ Request ตัว TCP State ในส่วนของ TIME_WAIT คือ ระยะการรอให้ Connection เก่าทำงานให้เสร็จก่อน และมีการ ACK ตอบกลับ ถึงจะ Close Connection แบบสมบูรณ์ได้ มีการเกิดจังหวะการรอ เคลีย์ของเก่าให้หมดก่อนนะ ถึงจะเริ่มสร้างของใหม่

เมื่อลองใช้คำสั่งวิเคราะห์ดูพบว่า TIME_WAIT ค่อยๆลดลง เพราะ กำลังเริ่มเคลีย์ของในคิว

ตัวอย่างโค๊ด ที่อาจเกิดปัญหา TIME_WAIT

เพื่อแก้ปัญหา TCP TIME_WAIT เราเลยขอใช้ ioutil.ReadAll()มาทดสอบ

และผลลัพธ์ที่ได้ TIME_WAIT จะเท่ากับ 0 เพราะ เราอ่านค่าใน Stream เรียบร้อยแล้ว และ Connection นั้นจะบอก TCP State ว่าทำงานเสร็จเรียบร้อยแล้ว

Clients และ Transport สร้างครั้งเดียว และสามารถ Re-use ได้

tr := &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://example.com")

http.Transport มีอะไรให้เล่นบ้าง?

ถ้าเรา initiate struct &http.Client{} ขึ้นมา สิ่งที่แถมมาด้วย คือ http.Transport

type Client struct {
// Transport specifies the mechanism by which individual
// HTTP requests are made.
// If nil, DefaultTransport is used.
Transport RoundTripper
}

ซึ่งถ้าเข้าไปดู DefaultTransport จะมีค่า ดังนี้

var DefaultTransport RoundTripper = &Transport{
Proxy: ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}

Configuration ที่น่าสนใจ

  • Proxy กำหนด function ที่ return http scheme ถ้าไม่ระบุเลย by default คือ http
  • TLSHandshakeTimeout กำหนด TLS Handshake ว่ามี timeout เท่าไหร่
  • DisableKeepAlives กำหนดว่าจะให้ คง HTTP Connect Pool เอาไว้ เพื่อ Re-use HTTP Connection หรือไม่
  • DisableCompression ต้องการ Compression Data ใน Transport Layer ไหม
  • MaxIdleConns จำนวน HTTP Connection ที่เก็บไว้ใน Pool
  • MaxIdleConnsPerHost จำนวน HTTP Connection ภายใต้ Host เดียว
  • IdleConnTimeout TTL ของ idle connection ถ้าภายใน 90 วินาที ไม่มี HTTP Request เลย มันก็จะ terminate ตัวเองไป

สามารถ Config ค่าเพิ่มเติมโดยดูจาก Struct Transport ได้ ที่นี่ https://golang.org/src/net/http/transport.go

ถ้ามี HTTP Request เข้ามาที่ Host จำนวนมาก จัดการอย่างไรดี

การที่เปิด ปิด HTTP Connection บ่อย มันมี overhead อยู่พอสมควร ดังนั้น ต้องมีการเทส เพื่อหาสถิติการใช้งาน ทางออกของปัญหา ที่สามารถควบคุมได้ คือ การใช้ HTTP Connection pool ด้วยการเซท MaxIdleConns และ IdleConnTimeout ให้เหมาะสม อย่าลืมว่า Memory มีจำกัด

และสุดท้ายบทความนี้ต้องการนำเสนอข้อมูลพื้นฐาน เพื่อใช้ประกอบการตัดสินใจ ในการออกแบบ http.Client ให้เหมาะสมกับโปรเจคที่ท่านผู้อ่านทำงานด้วยอยู่ ขอขอบคุณสำหรับการติดตามนะครับ หากผิดพลาดประการใด ขอคำแนะนำด้วยครับผม

Reference

--

--

Teerapong Singthong 👨🏻‍💻
iamgoangle

Engineering Manager, ex-Solution Engineering Lead at LINE | Tech | Team Building | System Design | Architecture | SWE | Large Scaling System