lfshttp: support http.version
There are some servers that cannot speak HTTP/2 in all cases and demand to fall back to HTTP/1.1 with a HTTP_1_1_REQUIRED. Notably, this happens with IIS 10 when using NTLM. Go's HTTP library doesn't seem to like this response and aborts the transfer, leading to a failure. Fortunately, Git has an option (http.version) to control the protocol used when speaking HTTP to a remote server. Implement this option to allow users to set the protocol to use when speaking HTTP and work around these broken servers.
This commit is contained in:
parent
47b03957d8
commit
bdacae1fbe
@ -346,6 +346,26 @@ func (c *Client) doWithRedirects(cli *http.Client, req *http.Request, remote str
|
||||
return c.doWithRedirects(cli, redirectedReq, remote, via)
|
||||
}
|
||||
|
||||
func (c *Client) configureProtocols(u *url.URL, tr *http.Transport) error {
|
||||
version, _ := c.uc.Get("http", u.String(), "version")
|
||||
switch version {
|
||||
case "HTTP/1.1":
|
||||
// This disables HTTP/2, according to the documentation.
|
||||
tr.TLSNextProto = make(map[string]func(authority string, c *tls.Conn) http.RoundTripper)
|
||||
case "HTTP/2":
|
||||
if u.Scheme != "https" {
|
||||
return fmt.Errorf("HTTP/2 cannot be used except with TLS")
|
||||
}
|
||||
http2.ConfigureTransport(tr)
|
||||
delete(tr.TLSNextProto, "http/1.1")
|
||||
case "":
|
||||
http2.ConfigureTransport(tr)
|
||||
default:
|
||||
return fmt.Errorf("Unknown HTTP version %q", version)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) HttpClient(u *url.URL) (*http.Client, error) {
|
||||
c.clientMu.Lock()
|
||||
defer c.clientMu.Unlock()
|
||||
@ -443,7 +463,9 @@ func (c *Client) HttpClient(u *url.URL) (*http.Client, error) {
|
||||
tr.TLSClientConfig.RootCAs = getRootCAsForHost(c, host)
|
||||
}
|
||||
|
||||
http2.ConfigureTransport(tr)
|
||||
if err := c.configureProtocols(u, tr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: tr,
|
||||
|
@ -335,3 +335,77 @@ func TestHttp2(t *testing.T) {
|
||||
assert.Equal(t, 200, res.StatusCode)
|
||||
assert.EqualValues(t, 1, calledSrv)
|
||||
}
|
||||
|
||||
func TestHttpVersion(t *testing.T) {
|
||||
testcases := []struct {
|
||||
Proto string
|
||||
Setting string
|
||||
TLSOk bool
|
||||
PlaintextOk bool
|
||||
Error string
|
||||
}{
|
||||
{"HTTP/2.0", "HTTP/2", true, false, "HTTP/2 cannot be used except with TLS"},
|
||||
{"HTTP/1.1", "HTTP/1.1", true, true, ""},
|
||||
{"HTTP/2.0", "lalala", false, false, `Unknown HTTP version "lalala"`},
|
||||
}
|
||||
|
||||
for _, test := range testcases {
|
||||
var calledSrvTLS uint32
|
||||
var calledSrv uint32
|
||||
|
||||
srvTLS := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
atomic.AddUint32(&calledSrvTLS, 1)
|
||||
assert.Equal(t, "GET", r.Method)
|
||||
assert.Equal(t, test.Proto, r.Proto)
|
||||
w.WriteHeader(200)
|
||||
}))
|
||||
srvTLS.TLS = &tls.Config{NextProtos: []string{"h2", "http/1.1"}}
|
||||
srvTLS.StartTLS()
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
atomic.AddUint32(&calledSrv, 1)
|
||||
assert.Equal(t, "GET", r.Method)
|
||||
assert.Equal(t, "HTTP/1.1", r.Proto)
|
||||
w.WriteHeader(200)
|
||||
}))
|
||||
|
||||
defer srvTLS.Close()
|
||||
defer srv.Close()
|
||||
|
||||
c, err := NewClient(NewContext(nil, nil, map[string]string{
|
||||
"http.sslverify": "false",
|
||||
"http.version": test.Setting,
|
||||
}))
|
||||
require.Nil(t, err)
|
||||
|
||||
req, err := http.NewRequest("GET", srvTLS.URL, nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
if test.TLSOk {
|
||||
res, err := c.Do(req)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 200, res.StatusCode)
|
||||
assert.EqualValues(t, 1, calledSrvTLS)
|
||||
} else {
|
||||
_, err := c.Do(req)
|
||||
require.NotNil(t, err)
|
||||
assert.EqualValues(t, err.Error(), test.Error)
|
||||
assert.EqualValues(t, 0, calledSrv)
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("GET", srv.URL, nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
if test.PlaintextOk {
|
||||
res, err := c.Do(req)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 200, res.StatusCode)
|
||||
assert.EqualValues(t, 1, calledSrv)
|
||||
} else {
|
||||
_, err := c.Do(req)
|
||||
require.NotNil(t, err)
|
||||
assert.EqualValues(t, err.Error(), test.Error)
|
||||
assert.EqualValues(t, 0, calledSrv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user