Skip to content

Instantly share code, notes, and snippets.

@timblair
Created July 22, 2016 07:18
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 timblair/2578882d17b294b534a24bbd7c4e123e to your computer and use it in GitHub Desktop.
Save timblair/2578882d17b294b534a24bbd7c4e123e to your computer and use it in GitHub Desktop.
HTTP connection changes between Go 1.5.x and 1.6.x
⇒ make
rm -f http-test-1.5 http-test-1.6
GOENV_VERSION=1.5.4 goenv exec go build -o http-test-1.5
GOENV_VERSION=1.6.3 goenv exec go build -o http-test-1.6
./http-test-1.5
* status = 204 No Content / err = <nil>
* status = 204 No Content / err = <nil>
* ERROR: Get http://127.0.0.1:53396: EOF
* status = 204 No Content / err = <nil>
./http-test-1.6
* status = 204 No Content / err = <nil>
* status = 204 No Content / err = <nil>
* status = 204 No Content / err = <nil>
* status = 204 No Content / err = <nil>
package main
import (
"fmt"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"sync"
)
func main() {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
serverURL, _ := url.Parse(server.URL)
proxy, _ := NewProxyTCP("127.0.0.1:0", serverURL.Host)
proxyURL := fmt.Sprintf("http://%s", proxy.Addr())
get(proxyURL)
get(proxyURL)
proxy.KillConnected()
get(proxyURL) // this errors (EOF / ECONNRESET) on 1.5.x, but not on 1.6.x
get(proxyURL)
server.Close()
proxy.Close()
}
func get(url string) {
resp, err := http.Get(url)
if err != nil {
fmt.Printf("* ERROR: %+v\n", err)
} else {
fmt.Println("* status =", resp.Status, "/ err =", err)
}
}
// ProxyTCP extracted verbatim from alphagov/govuk_crawler_worker
type ProxyTCP struct {
sync.Mutex
listener net.Listener
remote string
conns []net.Conn
wg sync.WaitGroup
}
func NewProxyTCP(lAddr, rAddr string) (*ProxyTCP, error) {
ln, err := net.Listen("tcp", lAddr)
if err != nil {
return nil, err
}
proxy := &ProxyTCP{
listener: ln,
remote: rAddr,
}
go proxy.AcceptLoop()
return proxy, nil
}
func (p *ProxyTCP) Addr() string {
return p.listener.Addr().String()
}
func (p *ProxyTCP) AcceptLoop() {
for {
p.wg.Add(1)
defer p.wg.Done()
lConn, err := p.listener.Accept()
if err != nil {
return
}
p.Lock()
p.conns = append(p.conns, lConn)
p.Unlock()
rConn, err := net.Dial("tcp", p.remote)
if err != nil {
return
}
go io.Copy(lConn, rConn)
go io.Copy(rConn, lConn)
}
}
func (p *ProxyTCP) Close() {
p.listener.Close()
p.wg.Wait()
p.KillConnected()
}
func (p *ProxyTCP) KillConnected() {
p.Lock()
defer p.Unlock()
for _, conn := range p.conns {
conn.Close()
}
}
.PHONY: build clean run
V15 := 1.5.4
V16 := 1.6.3
B15 := http-test-1.5
B16 := http-test-1.6
all: clean build run
build:
GOENV_VERSION=$(V15) goenv exec go build -o $(B15)
GOENV_VERSION=$(V16) goenv exec go build -o $(B16)
run:
./$(B15)
./$(B16)
clean:
rm -f $(B15) $(B16)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment