Last active
July 14, 2021 21:18
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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