func (c *HTTPClient) Send(data []byte) (response []byte, err error) { // Don't exit on panic defer func() { if r := recover(); r != nil { Debug("[HTTPClient]", r, string(data)) if _, ok := r.(error); !ok { log.Println("[HTTPClient] Failed to send request: ", string(data)) log.Println("PANIC: pkg:", r, debug.Stack()) } } }() if c.conn == nil || !c.isAlive() { Debug("[HTTPClient] Connecting:", c.baseURL) if err = c.Connect(); err != nil { log.Println("[HTTPClient] Connection error:", err) response = errorPayload(HTTP_CONNECTION_ERROR) return } } timeout := time.Now().Add(c.config.Timeout) c.conn.SetWriteDeadline(timeout) if !c.config.OriginalHost { data = proto.SetHost(data, []byte(c.baseURL), []byte(c.host)) } if c.auth != "" { data = proto.SetHeader(data, []byte("Authorization"), []byte(c.auth)) } if c.config.Debug { Debug("[HTTPClient] Sending:", string(data)) } if _, err = c.conn.Write(data); err != nil { Debug("[HTTPClient] Write error:", err, c.baseURL) response = errorPayload(HTTP_TIMEOUT) return } var readBytes, n int var currentChunk []byte timeout = time.Now().Add(c.config.Timeout) chunked := false contentLength := -1 currentContentLength := 0 chunks := 0 for { c.conn.SetReadDeadline(timeout) if readBytes < len(c.respBuf) { n, err = c.conn.Read(c.respBuf[readBytes:]) readBytes += n chunks++ if err != nil { if err == io.EOF { err = nil } break } // First chunk if chunked || contentLength != -1 { currentContentLength += n } else { // If headers are finished if bytes.Contains(c.respBuf[:readBytes], proto.EmptyLine) { if bytes.Equal(proto.Header(c.respBuf, []byte("Transfer-Encoding")), []byte("chunked")) { chunked = true } else { status, _ := strconv.Atoi(string(proto.Status(c.respBuf))) if (status >= 100 && status < 200) || status == 204 || status == 304 { contentLength = 0 } else { l := proto.Header(c.respBuf, []byte("Content-Length")) if len(l) > 0 { contentLength, _ = strconv.Atoi(string(l)) } } } currentContentLength += len(proto.Body(c.respBuf[:readBytes])) } } if chunked { // Check if chunked message finished if bytes.HasSuffix(c.respBuf[:readBytes], chunkedSuffix) { break } } else if contentLength != -1 { if currentContentLength > contentLength { Debug("[HTTPClient] disconnected, wrong length", currentContentLength, contentLength) c.Disconnect() break } else if currentContentLength == contentLength { break } } } else { if currentChunk == nil { currentChunk = make([]byte, readChunkSize) } n, err = c.conn.Read(currentChunk) if err == io.EOF { break } else if err != nil { Debug("[HTTPClient] Read the whole body error:", err, c.baseURL) break } readBytes += int(n) chunks++ currentContentLength += n if chunked { // Check if chunked message finished if bytes.HasSuffix(currentChunk[:n], chunkedSuffix) { break } } else if contentLength != -1 { if currentContentLength > contentLength { Debug("[HTTPClient] disconnected, wrong length", currentContentLength, contentLength) c.Disconnect() break } else if currentContentLength == contentLength { break } } else { Debug("[HTTPClient] disconnected, can't find Content-Length or Chunked") c.Disconnect() break } } if readBytes >= maxResponseSize { Debug("[HTTPClient] Body is more than the max size", maxResponseSize, c.baseURL) break } // For following chunks expect less timeout timeout = time.Now().Add(c.config.Timeout / 5) } if err != nil { Debug("[HTTPClient] Response read error", err, c.conn, readBytes) response = errorPayload(HTTP_TIMEOUT) return } if readBytes > len(c.respBuf) { readBytes = len(c.respBuf) } payload := make([]byte, readBytes) copy(payload, c.respBuf[:readBytes]) if c.config.Debug { Debug("[HTTPClient] Received:", string(payload)) } if c.config.FollowRedirects > 0 && c.redirectsCount < c.config.FollowRedirects { status := payload[9:12] // 3xx requests if status[0] == '3' { c.redirectsCount++ location := proto.Header(payload, []byte("Location")) redirectPayload := []byte("GET " + string(location) + " HTTP/1.1\r\n\r\n") if c.config.Debug { Debug("[HTTPClient] Redirecting to: " + string(location)) } return c.Send(redirectPayload) } } if bytes.Equal(proto.Status(payload), []byte("400")) { c.Disconnect() Debug("[HTTPClient] Closed connection on 400 response") } c.redirectsCount = 0 return payload, err }
func (c *HTTPClient) Send(data []byte) (response []byte, err error) { // Don't exit on panic defer func() { if r := recover(); r != nil { Debug("[HTTPClient]", r, string(data)) if _, ok := r.(error); !ok { log.Println("[HTTPClient] Failed to send request: ", string(data)) log.Println("PANIC: pkg:", r, debug.Stack()) } } }() if c.conn == nil || !c.isAlive() { Debug("[HTTPClient] Connecting:", c.baseURL) if err = c.Connect(); err != nil { log.Println("[HTTPClient] Connection error:", err) response = errorPayload(HTTP_CONNECTION_ERROR) return } } timeout := time.Now().Add(c.config.Timeout) c.conn.SetWriteDeadline(timeout) if !c.config.OriginalHost { data = proto.SetHost(data, []byte(c.baseURL), []byte(c.host)) } if c.config.Debug { Debug("[HTTPClient] Sending:", string(data)) } if _, err = c.conn.Write(data); err != nil { Debug("[HTTPClient] Write error:", err, c.baseURL) response = errorPayload(HTTP_TIMEOUT) return } c.conn.SetReadDeadline(timeout) n, err := c.conn.Read(c.respBuf) // If response large then our buffer, we need to read all response buffer // Otherwise it will corrupt response of next request // Parsing response body is non trivial thing, especially with keep-alive // Simples case is to to close connection if response too large // // See https://github.com/buger/gor/issues/184 if n == len(c.respBuf) { c.Disconnect() } if err != nil { Debug("[HTTPClient] Response read error", err, c.conn) response = errorPayload(HTTP_TIMEOUT) return } payload := c.respBuf[:n] if c.config.Debug { Debug("[HTTPClient] Received:", string(payload)) } if c.config.FollowRedirects > 0 && c.redirectsCount < c.config.FollowRedirects { status := payload[9:12] // 3xx requests if status[0] == '3' { c.redirectsCount++ location := proto.Header(payload, []byte("Location")) redirectPayload := []byte("GET " + string(location) + " HTTP/1.1\r\n\r\n") if c.config.Debug { Debug("[HTTPClient] Redirecting to: " + string(location)) } return c.Send(redirectPayload) } } c.redirectsCount = 0 return payload, err }
func (c *HTTPClient) Send(data []byte) (response []byte, err error) { // Don't exit on panic defer func() { if r := recover(); r != nil { Debug("[HTTPClient]", r, string(data)) if _, ok := r.(error); !ok { log.Println("[HTTPClient] Failed to send request: ", string(data)) log.Println("PANIC: pkg:", r, debug.Stack()) } } }() if c.conn == nil || !c.isAlive() { Debug("[HTTPClient] Connecting:", c.baseURL) if err = c.Connect(); err != nil { log.Println("[HTTPClient] Connection error:", err) return } } timeout := time.Now().Add(5 * time.Second) c.conn.SetWriteDeadline(timeout) data = proto.SetHost(data, []byte(c.baseURL), []byte(c.host)) if c.config.Debug { Debug("[HTTPClient] Sending:", string(data)) } if _, err = c.conn.Write(data); err != nil { Debug("[HTTPClient] Write error:", err, c.baseURL) return } c.conn.SetReadDeadline(timeout) n, err := c.conn.Read(c.respBuf) if err != nil { Debug("[HTTPClient] Response read error", err, c.conn) return } payload := c.respBuf[:n] if c.config.Debug { Debug("[HTTPClient] Received:", string(payload)) } if c.config.FollowRedirects > 0 && c.redirectsCount < c.config.FollowRedirects { status := payload[9:12] // 3xx requests if status[0] == '3' { c.redirectsCount += 1 location, _, _, _ := proto.Header(payload, []byte("Location")) redirectPayload := []byte("GET " + string(location) + " HTTP/1.1\r\n\r\n") if c.config.Debug { Debug("[HTTPClient] Redirecting to: " + string(location)) } return c.Send(redirectPayload) } } c.redirectsCount = 0 return payload, err }