Skip to content

Instantly share code, notes, and snippets.

@monis0395
Created July 3, 2019 11:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save monis0395/0202b2129e943989e83a828553077cde to your computer and use it in GitHub Desktop.
Save monis0395/0202b2129e943989e83a828553077cde to your computer and use it in GitHub Desktop.
package main
import (
"bufio"
"bytes"
"compress/gzip"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/http/httptest"
"net/textproto"
"strings"
"time"
)
func main() {
getResponseWithCustomClient()
getWithDefaultClient()
}
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
encoding := r.Header.Get("Accept-Encoding")
if strings.Contains(encoding, "gzip") {
encoder, _ := gzip.NewWriterLevel(w, 6)
encodingName := "gzip"
w.Header().Set("Transfer-Encoding", encodingName )
w.Header().Set("Accept-Encoding", encodingName)
w.Header().Add("Vary", "Accept-Encoding")
fmt.Fprint(encoder, `{"alive": true, "query": "`+r.URL.RawQuery+`"}`)
} else {
fmt.Fprint(w, `{"alive": true, "query": "`+r.URL.RawQuery+`"}`)
}
}
// Test to check if the server will exit gracefully when client exits
func getWithDefaultClient() {
handler := http.HandlerFunc(healthCheckHandler)
server := httptest.NewServer(handler)
defer server.Close()
serverURL := server.URL + "?test=true"
req, err := http.NewRequest("GET", serverURL, nil)
if err != nil {
log.Println("http request error", err.Error())
return
}
req.Header.Set("Accept-Encoding", "gzip")
res, err := http.DefaultClient.Do(req)
if err != nil && !strings.Contains(err.Error(), "context canceled") {
log.Println("http client error", err.Error())
return
}
contents, err := ioutil.ReadAll(res.Body)
fmt.Println("response", string(contents))
}
func getResponseWithCustomClient() {
handler := http.HandlerFunc(healthCheckHandler)
server := httptest.NewServer(handler)
defer server.Close()
serverURL := server.URL + "?test=true"
req, err := http.NewRequest("GET", serverURL, nil)
if err != nil {
log.Println("http request error", err.Error())
return
}
req.Header.Set("Accept-Encoding", "gzip")
netClient := &http.Client{
Timeout: time.Second * 10,
Transport: &transferEncodingRedactor{base: http.DefaultTransport.(*http.Transport)},
}
res, err := netClient.Do(req)
if err != nil && !strings.Contains(err.Error(), "context canceled") {
log.Println("http client error", err.Error())
return
}
contents, err := ioutil.ReadAll(res.Body)
fmt.Println("response", string(contents))
}
type transferEncodingRedactor struct {
base *http.Transport
}
func (rt *transferEncodingRedactor) RoundTrip(req *http.Request) (*http.Response, error) {
// Optimistic route i.e common case.
res, err := rt.base.RoundTrip(req)
if err == nil || res != nil {
return res, err
}
// An error occured here but now time ot examine the contents
if !strings.Contains(err.Error(), "unsupported transfer encoding") {
return res, err
}
u := req.URL
var conn net.Conn
switch req.URL.Scheme {
case "https":
tr := rt.base
if tr == nil {
tr = http.DefaultTransport.(*http.Transport)
}
host := req.URL.Host
if !strings.Contains(host, ":") {
host += ":443"
}
conn, err = tls.Dial("tcp", host, tr.TLSClientConfig)
default:
host := req.URL.Host
if !strings.Contains(host, ":") {
host += ":80"
}
conn, err = net.Dial("tcp", host)
}
if err != nil {
return nil, fmt.Errorf("dial error: %v", err)
}
// Ensure we close the connection after.
// Perhaps you might want to reuse it per host?
defer conn.Close()
s := u.Path
if q := u.RawQuery; len(q) > 0 {
s += "?" + q
}
intro := fmt.Sprintf("%s /%s HTTP/1.1\r\nHost:%s\r\n\r\n", req.Method, s, u.Host)
fmt.Fprint(conn, intro)
br := bufio.NewReader(conn)
// And now we've got the case of an unsupported transfer
// encoding time for a raw fetch and header rewrite.
tp := textproto.NewReader(br)
protoLine, err := tp.ReadLine()
if err != nil {
return nil, fmt.Errorf("ReadLine error: %v", err)
}
// Time to parse the headers
rhdr, err := tp.ReadMIMEHeader()
if err != nil {
return nil, fmt.Errorf("ReadMIMEHeader: %v", err)
}
hdr := http.Header(rhdr)
// Now rewrite the header
if te := hdr.Get("Transfer-Encoding"); te == "gzip" {
hdr.Set("Content-Encoding", "gzip")
delete(hdr, "Transfer-Encoding")
}
// After this rewrite, reassemble the already read parts back
// and then get them to ReadResponse
hdrBuf := new(bytes.Buffer)
if err := hdr.Write(hdrBuf); err != nil {
return nil, fmt.Errorf("Rewriting header to wire: %v", err)
}
reconstructed := io.MultiReader(strings.NewReader(protoLine+"\r\n"), hdrBuf, strings.NewReader("\r\n"), br)
return http.ReadResponse(bufio.NewReader(reconstructed), req)
}
@monis0395
Copy link
Author

Response

response {"alive": true, "query": "test=true"}
2019/07/03 15:03:13 http client error Get http://127.0.0.1:51639?test=true: net/http: HTTP/1.x transport connection broken: unsupported transfer encoding "gzip"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment