Add option to configure http/s managed transport #870
85
http.go
85
http.go
|
@ -1,6 +1,8 @@
|
||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -16,7 +18,7 @@ import (
|
||||||
// If Shutdown or ReInit are called, make sure that the smart transports are
|
// If Shutdown or ReInit are called, make sure that the smart transports are
|
||||||
// freed before it.
|
// freed before it.
|
||||||
func RegisterManagedHTTPTransport(protocol string) (*RegisteredSmartTransport, error) {
|
func RegisterManagedHTTPTransport(protocol string) (*RegisteredSmartTransport, error) {
|
||||||
return NewRegisteredSmartTransport(protocol, true, httpSmartSubtransportFactory)
|
return NewRegisteredSmartTransport(protocol, true, httpSmartSubtransportFactory(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerManagedHTTP() error {
|
func registerManagedHTTP() error {
|
||||||
|
@ -27,7 +29,7 @@ func registerManagedHTTP() error {
|
||||||
if _, ok := globalRegisteredSmartTransports.transports[protocol]; ok {
|
if _, ok := globalRegisteredSmartTransports.transports[protocol]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
managed, err := newRegisteredSmartTransport(protocol, true, httpSmartSubtransportFactory, true)
|
managed, err := newRegisteredSmartTransport(protocol, true, httpSmartSubtransportFactory(nil), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to register transport for %q: %v", protocol, err)
|
return fmt.Errorf("failed to register transport for %q: %v", protocol, err)
|
||||||
}
|
}
|
||||||
|
@ -36,7 +38,14 @@ func registerManagedHTTP() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func httpSmartSubtransportFactory(remote *Remote, transport *Transport) (SmartSubtransport, error) {
|
// httpSmartSubtransportFactory implements SmartSubtransportCallback which
|
||||||
|
// returns a SmartSubtransport for a remote and transport.
|
||||||
|
func httpSmartSubtransportFactory(opts *SmartSubtransportOptions) SmartSubtransportCallback {
|
||||||
|
return func(remote *Remote, transport *Transport) (SmartSubtransport, error) {
|
||||||
|
sst := &httpSmartSubtransport{
|
||||||
|
transport: transport,
|
||||||
|
}
|
||||||
|
|
||||||
var proxyFn func(*http.Request) (*url.URL, error)
|
var proxyFn func(*http.Request) (*url.URL, error)
|
||||||
proxyOpts, err := transport.SmartProxyOptions()
|
proxyOpts, err := transport.SmartProxyOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -56,14 +65,26 @@ func httpSmartSubtransportFactory(remote *Remote, transport *Transport) (SmartSu
|
||||||
proxyFn = http.ProxyURL(parsedUrl)
|
proxyFn = http.ProxyURL(parsedUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &httpSmartSubtransport{
|
// Add the proxy to the http transport.
|
||||||
transport: transport,
|
httpTransport := &http.Transport{
|
||||||
client: &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
Proxy: proxyFn,
|
Proxy: proxyFn,
|
||||||
},
|
}
|
||||||
},
|
|
||||||
}, nil
|
// Add any provided certificate to the http transport.
|
||||||
|
if opts != nil && len(opts.CABundle) > 0 {
|
||||||
|
cap := x509.NewCertPool()
|
||||||
|
if ok := cap.AppendCertsFromPEM(opts.CABundle); !ok {
|
||||||
|
return nil, fmt.Errorf("failed to use certificate from PEM")
|
||||||
|
}
|
||||||
|
httpTransport.TLSClientConfig = &tls.Config{
|
||||||
|
RootCAs: cap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sst.client = &http.Client{Transport: httpTransport}
|
||||||
|
|
||||||
|
return sst, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpSmartSubtransport struct {
|
type httpSmartSubtransport struct {
|
||||||
|
@ -89,7 +110,7 @@ func (t *httpSmartSubtransport) Action(url string, action SmartServiceAction) (S
|
||||||
req, err = http.NewRequest("GET", url+"/info/refs?service=git-receive-pack", nil)
|
req, err = http.NewRequest("GET", url+"/info/refs?service=git-receive-pack", nil)
|
||||||
|
|
||||||
case SmartServiceActionReceivepack:
|
case SmartServiceActionReceivepack:
|
||||||
req, err = http.NewRequest("POST", url+"/info/refs?service=git-upload-pack", nil)
|
req, err = http.NewRequest("POST", url+"/git-receive-pack", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -105,7 +126,7 @@ func (t *httpSmartSubtransport) Action(url string, action SmartServiceAction) (S
|
||||||
|
|
||||||
req.Header.Set("User-Agent", "git/2.0 (git2go)")
|
req.Header.Set("User-Agent", "git/2.0 (git2go)")
|
||||||
|
|
||||||
stream := newManagedHttpStream(t, req)
|
stream := newManagedHttpStream(t, req, t.client)
|
||||||
if req.Method == "POST" {
|
if req.Method == "POST" {
|
||||||
stream.recvReply.Add(1)
|
stream.recvReply.Add(1)
|
||||||
stream.sendRequestBackground()
|
stream.sendRequestBackground()
|
||||||
|
@ -124,6 +145,7 @@ func (t *httpSmartSubtransport) Free() {
|
||||||
|
|
||||||
type httpSmartSubtransportStream struct {
|
type httpSmartSubtransportStream struct {
|
||||||
owner *httpSmartSubtransport
|
owner *httpSmartSubtransport
|
||||||
|
client *http.Client
|
||||||
req *http.Request
|
req *http.Request
|
||||||
resp *http.Response
|
resp *http.Response
|
||||||
reader *io.PipeReader
|
reader *io.PipeReader
|
||||||
|
@ -133,10 +155,11 @@ type httpSmartSubtransportStream struct {
|
||||||
httpError error
|
httpError error
|
||||||
}
|
}
|
||||||
|
|
||||||
func newManagedHttpStream(owner *httpSmartSubtransport, req *http.Request) *httpSmartSubtransportStream {
|
func newManagedHttpStream(owner *httpSmartSubtransport, req *http.Request, client *http.Client) *httpSmartSubtransportStream {
|
||||||
r, w := io.Pipe()
|
r, w := io.Pipe()
|
||||||
return &httpSmartSubtransportStream{
|
return &httpSmartSubtransportStream{
|
||||||
owner: owner,
|
owner: owner,
|
||||||
|
client: client,
|
||||||
req: req,
|
req: req,
|
||||||
reader: r,
|
reader: r,
|
||||||
writer: w,
|
writer: w,
|
||||||
|
@ -192,6 +215,23 @@ func (self *httpSmartSubtransportStream) sendRequest() error {
|
||||||
var err error
|
var err error
|
||||||
var userName string
|
var userName string
|
||||||
var password string
|
var password string
|
||||||
|
|
||||||
|
// Obtain the credentials and use them if available.
|
||||||
|
cred, err := self.owner.transport.SmartCredentials("", CredentialTypeUserpassPlaintext)
|
||||||
|
if err != nil {
|
||||||
|
// Passthrough error indicates that no credentials were provided.
|
||||||
|
// Continue without credentials.
|
||||||
|
if err.Error() != ErrorCodePassthrough.String() {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
userName, password, err = cred.GetUserpassPlaintext()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cred.Free()
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
req := &http.Request{
|
req := &http.Request{
|
||||||
Method: self.req.Method,
|
Method: self.req.Method,
|
||||||
|
@ -204,7 +244,7 @@ func (self *httpSmartSubtransportStream) sendRequest() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
req.SetBasicAuth(userName, password)
|
req.SetBasicAuth(userName, password)
|
||||||
resp, err = http.DefaultClient.Do(req)
|
resp, err = self.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -213,23 +253,6 @@ func (self *httpSmartSubtransportStream) sendRequest() error {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusUnauthorized {
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
cred, err := self.owner.transport.SmartCredentials("", CredentialTypeUserpassPlaintext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer cred.Free()
|
|
||||||
|
|
||||||
userName, password, err = cred.GetUserpassPlaintext()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any other error we treat as a hard error and punt back to the caller
|
// Any other error we treat as a hard error and punt back to the caller
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
return fmt.Errorf("Unhandled HTTP error %s", resp.Status)
|
return fmt.Errorf("Unhandled HTTP error %s", resp.Status)
|
||||||
|
|
16
transport.go
16
transport.go
|
@ -219,6 +219,22 @@ type RegisteredSmartTransport struct {
|
||||||
handle unsafe.Pointer
|
handle unsafe.Pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SmartSubtransportOptions is an option for configuring the smart subtransport.
|
||||||
|
type SmartSubtransportOptions struct {
|
||||||
|
CABundle []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRegisterSmartTransportWithOptions registers Go-native transport configured
|
||||||
|
// with options.
|
||||||
|
func NewRegisterSmartTransportWithOptions(protocol string, opts *SmartSubtransportOptions) (*RegisteredSmartTransport, error) {
|
||||||
|
switch protocol {
|
||||||
|
case "http", "https":
|
||||||
|
return NewRegisteredSmartTransport(protocol, true, httpSmartSubtransportFactory(opts))
|
||||||
|
default:
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewRegisteredSmartTransport adds a custom transport definition, to be used
|
// NewRegisteredSmartTransport adds a custom transport definition, to be used
|
||||||
// in addition to the built-in set of transports that come with libgit2.
|
// in addition to the built-in set of transports that come with libgit2.
|
||||||
func NewRegisteredSmartTransport(
|
func NewRegisteredSmartTransport(
|
||||||
|
|
Loading…
Reference in New Issue