Skip to content

Instantly share code, notes, and snippets.

@zeisss
Last active July 14, 2021 21: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 zeisss/7023ab087088c1efb00b37741959bb1e to your computer and use it in GitHub Desktop.
Save zeisss/7023ab087088c1efb00b37741959bb1e to your computer and use it in GitHub Desktop.
A POC showing how to serve a HTTP2 server over an outgoing connection (PTTH style)
// Run with
//
// $ GODEBUG=http2debug=2 go test . -v
//
// for debug output
package main
import (
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/net/http2"
)
const handlerPayload = "Hello World"
func TestHTTP2Stuff(t *testing.T) {
cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey)
require.NoError(t, err)
l, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
t.Cleanup(func() { l.Close() })
cfg := tls.Config{
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: true,
}
tlsListener := tls.NewListener(l, &cfg)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
dialServer(t, cert, l.Addr().String())
}()
go func() {
defer wg.Done()
cc, err := tlsListener.Accept()
require.NoError(t, err)
defer func() { cc.Close() }()
handleConn(t, cc)
}()
wg.Wait()
}
func handleConn(t *testing.T, c net.Conn) {
t.Logf("server: Accepted connection from %s", c.RemoteAddr().String())
err := c.(*tls.Conn).Handshake()
require.NoError(t, err)
tr := http2.Transport{}
cc, err := tr.NewClientConn(c)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodPost, "https://"+c.RemoteAddr().String(), nil)
require.NoError(t, err)
resp1, err := cc.RoundTrip(req)
require.NoError(t, err)
defer resp1.Body.Close()
t.Logf("server: Received first response: %s", resp1.Status)
// Do a second request while the first is still in progress
resp2, err := cc.RoundTrip(req)
require.NoError(t, err)
t.Logf("server: Received second response: %s", resp2.Status)
data, err := io.ReadAll(resp1.Body)
require.NoError(t, err)
require.Equal(t, "HTTP/2.0", resp1.Proto)
require.Equal(t, handlerPayload, string(data))
data, err = io.ReadAll(resp2.Body)
require.NoError(t, err)
require.Equal(t, "HTTP/2.0", resp2.Proto)
require.Equal(t, handlerPayload, string(data))
}
func dialServer(t *testing.T, cert tls.Certificate, addr string) {
t.Logf("client: Dialing %s", addr)
cfg := tls.Config{
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: true,
}
con, err := tls.Dial("tcp", addr, &cfg)
require.NoError(t, err)
t.Log("client: Serving requests on connection now")
var server http2.Server
server.ServeConn(con, &http2.ServeConnOpts{
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
t.Logf("client: Serving request: url=%s proto=%s", req.URL.String(), req.Proto)
fmt.Fprint(w, handlerPayload)
}),
})
}
// Copied from stdlib net/http/internal/testcert.go
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// LocalhostCert is a PEM-encoded TLS cert with SAN IPs
// "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT.
// generated from src/crypto/tls:
// go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var LocalhostCert = []byte(`-----BEGIN CERTIFICATE-----
MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
iQKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9SjY1bIw4
iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZBl2+XsDul
rKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQABo2gwZjAO
BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw
AwEB/zAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAA
AAAAATANBgkqhkiG9w0BAQsFAAOBgQCEcetwO59EWk7WiJsG4x8SY+UIAA+flUI9
tyC4lNhbcF2Idq9greZwbYCqTTTr2XiRNSMLCOjKyI7ukPoPjo16ocHj+P3vZGfs
h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM
fblo6RBxUQ==
-----END CERTIFICATE-----`)
// LocalhostKey is the private key for localhostCert.
var LocalhostKey = []byte(testingKey(`-----BEGIN RSA TESTING KEY-----
MIICXgIBAAKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9
SjY1bIw4iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZB
l2+XsDulrKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQAB
AoGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet
3Zm4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKwZatTXtdZrhu+Jk7hx0nTPy8Jcb
uJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+hgN4H
qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz8vp
jy4SHEg1AkEA/v13/5M47K9vCxmb8QeD/asydfsgS5TeuNi8DoUBEmiSJwma7FXY
fFUtxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su4b3sjXNueLKH85Q+phy2U
fQtuUE9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xlp/DoCzjA0CQQDU
y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6PYj013sovGKUFfYAqVXVlxtIX
qyUBnu3X9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYZHOde3JEMhNRcVFMO8dJDaFeo
f9Oeos0UUothgiDktdQHxdNEwLjQf7lJJBzV+5OtwswCWA==
-----END RSA TESTING KEY-----`))
func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment