Add a managed HTTP(S) transport implementation

This implements a "smart subtransport" which lets us implement just the I/O
portion and leave the Git Smart Protocol logic in the library.
This commit is contained in:
Carlos Martín Nieto 2017-04-15 20:10:19 +02:00 committed by Carlos Martín Nieto
parent 2cff3f2ef4
commit 1be680fdf0
3 changed files with 357 additions and 0 deletions

8
git.go
View File

@ -129,6 +129,14 @@ func init() {
panic("libgit2 was not built with threading support")
}
if err := RegisterManagedHttp(); err != nil {
panic(err)
}
if err := RegisterManagedHttps(); err != nil {
panic(err)
}
// This is not something we should be doing, as we may be
// stomping all over someone else's setup. The user should do
// this themselves or use some binding/wrapper which does it

315
http.go Normal file
View File

@ -0,0 +1,315 @@
package git
/*
#include <git2.h>
#include <git2/sys/transport.h>
typedef struct {
git_smart_subtransport parent;
void *ptr;
} managed_smart_subtransport;
typedef struct {
git_smart_subtransport_stream parent;
void *ptr;
} managed_smart_subtransport_stream;
int _go_git_transport_register(const char *scheme);
int _go_git_transport_smart(git_transport **out, git_remote *owner);
void _go_git_setup_smart_subtransport(managed_smart_subtransport *t, void *ptr);
void _go_git_setup_smart_subtransport_stream(managed_smart_subtransport_stream *t, void *ptr);
*/
import "C"
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"runtime"
//"runtime/debug"
"unsafe"
)
type SmartService int
const (
SmartServiceUploadpackLs = C.GIT_SERVICE_UPLOADPACK_LS
SmartServiceUploadpack = C.GIT_SERVICE_UPLOADPACK
SmartServiceReceivepackLs = C.GIT_SERVICE_RECEIVEPACK_LS
SmartServiceReceivepack = C.GIT_SERVICE_RECEIVEPACK
)
type SmartSubtransport interface {
Action(url string, action SmartService) (SmartSubtransportStream, error)
Close() error
Free()
}
type SmartSubtransportStream interface {
Read(buf []byte) (int, error)
Write(buf []byte) error
Free()
}
func RegisterManagedHttp() error {
httpStr := C.CString("http")
defer C.free(unsafe.Pointer(httpStr))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C._go_git_transport_register(httpStr)
if ret != 0 {
return MakeGitError(ret)
}
return nil
}
func RegisterManagedHttps() error {
httpsStr := C.CString("https")
defer C.free(unsafe.Pointer(httpsStr))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C._go_git_transport_register(httpsStr)
if ret != 0 {
return MakeGitError(ret)
}
return nil
}
type ManagedTransport struct{}
func (self *ManagedTransport) Action(url string, action SmartService) (SmartSubtransportStream, error) {
var req *http.Request
var err error
switch action {
case SmartServiceUploadpackLs:
req, err = http.NewRequest("GET", url+"/info/refs?service=git-upload-pack", nil)
case SmartServiceUploadpack:
req, err = http.NewRequest("POST", url+"/git-upload-pack", nil)
if err != nil {
break
}
req.Header["Content-Type"] = []string{"application/x-git-upload-pack-request"}
case SmartServiceReceivepackLs:
req, err = http.NewRequest("GET", url+"/info/refs?service=git-receive-pack", nil)
case SmartServiceReceivepack:
req, err = http.NewRequest("POST", url+"/info/refs?service=git-upload-pack", nil)
if err != nil {
break
}
req.Header["Content-Type"] = []string{"application/x-git-receive-pack-request"}
default:
err = errors.New("unknown action")
}
if err != nil {
return nil, err
}
req.Header["User-Agent"] = []string{"git/2.0 (git2go)"}
return newManagedHttpStream(req), nil
}
func (self *ManagedTransport) Close() error {
return nil
}
func (self *ManagedTransport) Free() {
}
type ManagedHttpStream struct {
req *http.Request
resp *http.Response
postBuffer bytes.Buffer
sentRequest bool
}
func newManagedHttpStream(req *http.Request) *ManagedHttpStream {
return &ManagedHttpStream{
req: req,
}
}
func (self *ManagedHttpStream) Read(buf []byte) (int, error) {
if !self.sentRequest {
if err := self.sendRequest(); err != nil {
return 0, err
}
}
return self.resp.Body.Read(buf)
}
func (self *ManagedHttpStream) Write(buf []byte) error {
// We write it all into a buffer and send it off when the transport asks
// us to read.
self.postBuffer.Write(buf)
return nil
}
func (self *ManagedHttpStream) Free() {
self.resp.Body.Close()
}
func (self *ManagedHttpStream) sendRequest() error {
self.req.Body = ioutil.NopCloser(&self.postBuffer)
resp, err := http.DefaultClient.Do(self.req)
if err != nil {
return err
}
self.sentRequest = true
self.resp = resp
return nil
}
func setLibgit2Error(err error) C.int {
cstr := C.CString(err.Error())
defer C.free(unsafe.Pointer(cstr))
C.giterr_set_str(C.GITERR_NET, cstr)
return -1
}
//export httpAction
func httpAction(out **C.git_smart_subtransport_stream, t *C.git_smart_subtransport, url *C.char, action C.git_smart_service_t) C.int {
transport, err := getSmartSubtransportInterface(t)
if err != nil {
return setLibgit2Error(err)
}
managed, err := transport.Action(C.GoString(url), SmartService(action))
if err != nil {
return setLibgit2Error(err)
}
stream := C.calloc(1, C.size_t(unsafe.Sizeof(C.managed_smart_subtransport_stream{})))
managedPtr := pointerHandles.Track(managed)
C._go_git_setup_smart_subtransport_stream(stream, managedPtr)
*out = (*C.git_smart_subtransport_stream)(stream)
return 0
}
//export httpClose
func httpClose(t *C.git_smart_subtransport) C.int {
transport, err := getSmartSubtransportInterface(t)
if err != nil {
return setLibgit2Error(err)
}
if err := transport.Close(); err != nil {
return setLibgit2Error(err)
}
return 0
}
//export httpFree
func httpFree(transport *C.git_smart_subtransport) {
wrapperPtr := (*C.managed_smart_subtransport)(unsafe.Pointer(transport))
pointerHandles.Untrack(wrapperPtr.ptr)
}
var errNoSmartSubtransport = errors.New("passed object does not implement SmartSubtransport")
func getSmartSubtransportInterface(_t *C.git_smart_subtransport) (SmartSubtransport, error) {
wrapperPtr := (*C.managed_smart_subtransport)(unsafe.Pointer(_t))
transport, ok := pointerHandles.Get(wrapperPtr.ptr).(SmartSubtransport)
if !ok {
return nil, errNoSmartSubtransport
}
return transport, nil
}
//export httpTransportCb
func httpTransportCb(out **C.git_transport, owner *C.git_remote, param unsafe.Pointer) C.int {
return C._go_git_transport_smart(out, owner)
}
//export httpSmartSubtransportCb
func httpSmartSubtransportCb(out **C.git_smart_subtransport, owner *C.git_transport, param unsafe.Pointer) C.int {
if out == nil {
return -1
}
transport := C.calloc(1, C.size_t(unsafe.Sizeof(C.managed_smart_subtransport{})))
managed := &ManagedTransport{}
managedPtr := pointerHandles.Track(managed)
C._go_git_setup_smart_subtransport(transport, managedPtr)
*out = (*C.git_smart_subtransport)(transport)
return 0
}
var errNoSmartSubtransportStream = errors.New("passed object does not implement SmartSubtransportStream")
func getSmartSubtransportStreamInterface(_s *C.git_smart_subtransport_stream) (SmartSubtransportStream, error) {
wrapperPtr := (*C.managed_smart_subtransport_stream)(unsafe.Pointer(_s))
transport, ok := pointerHandles.Get(wrapperPtr.ptr).(SmartSubtransportStream)
if !ok {
return nil, errNoSmartSubtransportStream
}
return transport, nil
}
//export smartSubtransportRead
func smartSubtransportRead(s *C.git_smart_subtransport_stream, data unsafe.Pointer, l C.size_t, read *C.size_t) C.int {
stream, err := getSmartSubtransportStreamInterface(s)
if err != nil {
return setLibgit2Error(err)
}
var p []byte
header := (*reflect.SliceHeader)(unsafe.Pointer(&p))
header.Cap = int(l)
header.Len = int(l)
header.Data = uintptr(data)
n, err := stream.Read(p)
if err != nil {
return setLibgit2Error(err)
}
*read = C.size_t(n)
return 0
}
//export smartSubtransportWrite
func smartSubtransportWrite(s *C.git_smart_subtransport_stream, data unsafe.Pointer, l C.size_t) C.int {
stream, err := getSmartSubtransportStreamInterface(s)
if err != nil {
return setLibgit2Error(err)
}
var p []byte
header := (*reflect.SliceHeader)(unsafe.Pointer(&p))
header.Cap = int(l)
header.Len = int(l)
header.Data = uintptr(data)
if err := stream.Write(p); err != nil {
return setLibgit2Error(err)
}
return 0
}
//export smartSubtransportFree
func smartSubtransportFree(s *C.git_smart_subtransport_stream) {
}

View File

@ -2,6 +2,7 @@
#include <git2.h>
#include <git2/sys/odb_backend.h>
#include <git2/sys/refdb_backend.h>
#include <git2/sys/transport.h>
typedef int (*gogit_submodule_cbk)(git_submodule *sm, const char *name, void *payload);
@ -180,4 +181,37 @@ void _go_git_writestream_free(git_writestream *stream)
stream->free(stream);
}
int _go_git_transport_register(const char *scheme)
{
return git_transport_register(scheme, httpTransportCb, NULL);
}
int _go_git_transport_smart(git_transport **out, git_remote *owner)
{
git_smart_subtransport_definition defn = {
httpSmartSubtransportCb,
1, // RPC/Stateless
};
return git_transport_smart(out, owner, &defn);
}
void _go_git_setup_smart_subtransport(managed_smart_subtransport *t, void *ptr)
{
t->parent.action = httpAction;
t->parent.close = httpClose;
t->parent.free = httpFree;
t->ptr = ptr;
}
void _go_git_setup_smart_subtransport_stream(managed_smart_subtransport_stream *s, void *ptr)
{
s->parent.read = smartSubtransportRead;
s->parent.write = smartSubtransportWrite;
s->parent.free = smartSubtransportFree;
s->ptr = ptr;
}
/* EOF */