Commit 37389823 authored by Stan Hu's avatar Stan Hu

Refactor handlePostRPC to be more clear

parent 232cd84c
...@@ -127,9 +127,6 @@ func handleGetInfoRefs(rw http.ResponseWriter, r *http.Request, a *api.Response) ...@@ -127,9 +127,6 @@ func handleGetInfoRefs(rw http.ResponseWriter, r *http.Request, a *api.Response)
} }
func handlePostRPC(rw http.ResponseWriter, r *http.Request, a *api.Response) { func handlePostRPC(rw http.ResponseWriter, r *http.Request, a *api.Response) {
var err error
var body io.Reader
var isShallowClone bool
var writtenIn int64 var writtenIn int64
w := NewGitHttpResponseWriter(rw) w := NewGitHttpResponseWriter(rw)
...@@ -144,110 +141,165 @@ func handlePostRPC(rw http.ResponseWriter, r *http.Request, a *api.Response) { ...@@ -144,110 +141,165 @@ func handlePostRPC(rw http.ResponseWriter, r *http.Request, a *api.Response) {
return return
} }
if action == "git-upload-pack" { if action == "git-receive-pack" {
buffer := &bytes.Buffer{} writtenIn, _ = handleReceivePack(action, w, r, a)
// Only sniff on the first 4096 bytes: we assume that if we find no } else {
// 'deepen' message in the first 4096 bytes there won't be one later writtenIn, _ = handleUploadPack(action, w, r, a)
// either. }
_, err = io.Copy(buffer, io.LimitReader(r.Body, 4096)) }
if err != nil {
helper.Fail500(w, r, &copyError{fmt.Errorf("handlePostRPC: buffer git-upload-pack body: %v", err)})
return
}
isShallowClone = scanDeepen(bytes.NewReader(buffer.Bytes())) func handleReceivePack(action string, w *GitHttpResponseWriter, r *http.Request, a *api.Response) (writtenIn int64, err error) {
body = io.MultiReader(buffer, r.Body) body := r.Body
cmd, stdin, stdout, err := setupGitCommand(action, a, w, r)
// Read out the full HTTP request body so that we can reply if err != nil {
buf, err := ioutil.ReadAll(body) return
}
if err != nil { defer stdout.Close()
helper.Fail500(w, r, &copyError{fmt.Errorf("handlePostRPC: full buffer git-upload-pack body: %v", err)}) defer stdin.Close()
return defer helper.CleanUpProcessGroup(cmd) // Ensure brute force subprocess clean-up
}
body = ioutil.NopCloser(bytes.NewBuffer(buf)) // Write the client request body to Git's standard input
r.Body.Close() writtenIn, err = io.Copy(stdin, body)
} else {
body = r.Body if err != nil {
helper.Fail500(w, r, fmt.Errorf("handleReceivePack: write to %v: %v", cmd.Args, err))
return
} }
// Signal to the Git subprocess that no more data is coming
stdin.Close()
// It may take a while before we return and the deferred closes happen
// so let's free up some resources already.
r.Body.Close()
writePostRPCHeader(w, action)
// This io.Copy may take a long time, both for Git push and pull.
_, err = io.Copy(w, stdout)
// Prepare our Git subprocess
cmd := gitCommand(a.GL_ID, "git", subCommand(action), "--stateless-rpc", a.RepoPath)
stdout, err := cmd.StdoutPipe()
if err != nil { if err != nil {
helper.Fail500(w, r, fmt.Errorf("handlePostRPC: stdout: %v", err)) helper.LogError(
r,
&copyError{fmt.Errorf("handleReceivePack: copy output of %v: %v", cmd.Args, err)},
)
return return
} }
defer stdout.Close()
stdin, err := cmd.StdinPipe() err = cmd.Wait()
if err != nil { if err != nil {
helper.Fail500(w, r, fmt.Errorf("handlePostRPC: stdin: %v", err)) helper.LogError(r, fmt.Errorf("handleReceivePack: wait for %v: %v", cmd.Args, err))
return return
} }
defer stdin.Close()
if err := cmd.Start(); err != nil { return writtenIn, nil
helper.Fail500(w, r, fmt.Errorf("handlePostRPC: start %v: %v", cmd.Args, err)) }
func handleUploadPack(action string, w *GitHttpResponseWriter, r *http.Request, a *api.Response) (writtenIn int64, err error) {
var isShallowClone bool
var body io.Reader
buffer := &bytes.Buffer{}
// Only sniff on the first 4096 bytes: we assume that if we find no
// 'deepen' message in the first 4096 bytes there won't be one later
// either.
_, err = io.Copy(buffer, io.LimitReader(r.Body, 4096))
if err != nil {
helper.Fail500(w, r, &copyError{fmt.Errorf("handleUploadPack: buffer git-upload-pack body: %v", err)})
return return
} }
defer helper.CleanUpProcessGroup(cmd) // Ensure brute force subprocess clean-up
stdoutError := make(chan error, 1) isShallowClone = scanDeepen(bytes.NewReader(buffer.Bytes()))
var wg sync.WaitGroup body = io.MultiReader(buffer, r.Body)
wg.Add(1)
if action == "git-upload-pack" { // Read out the full HTTP request body so that we can reply
// Start writing the response buf, err := ioutil.ReadAll(body)
writePostRPCHeader(w, action)
if err != nil {
go func() { helper.Fail500(w, r, &copyError{fmt.Errorf("handleUploadPack: full buffer git-upload-pack body: %v", err)})
defer wg.Done() return
if _, err := io.Copy(w, stdout); err != nil {
helper.LogError(
r,
&copyError{fmt.Errorf("handlePostRPC: copy output of %v: %v", cmd.Args, err)},
)
stdoutError <- err
return
}
}()
} }
// Write the client request body to Git's standard input body = ioutil.NopCloser(bytes.NewBuffer(buf))
if writtenIn, err = io.Copy(stdin, body); err != nil { r.Body.Close()
helper.Fail500(w, r, fmt.Errorf("handlePostRPC: write to %v: %v", cmd.Args, err))
cmd, stdin, stdout, err := setupGitCommand(action, a, w, r)
if err != nil {
return return
} }
// Signal to the Git subprocess that no more data is coming
stdin.Close()
if action == "git-upload-pack" { defer stdout.Close()
wg.Wait() defer stdin.Close()
defer helper.CleanUpProcessGroup(cmd) // Ensure brute force subprocess clean-up
if len(stdoutError) > 0 { stdoutError := make(chan error, 1)
return var wg sync.WaitGroup
} wg.Add(1)
} else {
// It may take a while before we return and the deferred closes happen // Start writing the response
// so let's free up some resources already. writePostRPCHeader(w, action)
r.Body.Close()
writePostRPCHeader(w, action) go func() {
defer wg.Done()
// This io.Copy may take a long time, both for Git push and pull.
if _, err := io.Copy(w, stdout); err != nil { if _, err := io.Copy(w, stdout); err != nil {
helper.LogError( helper.LogError(
r, r,
&copyError{fmt.Errorf("handlePostRPC: copy output of %v: %v", cmd.Args, err)}, &copyError{fmt.Errorf("handleUploadPack: copy output of %v: %v", cmd.Args, err)},
) )
stdoutError <- err
return return
} }
}()
// Write the client request body to Git's standard input
if writtenIn, err = io.Copy(stdin, body); err != nil {
helper.Fail500(w, r, fmt.Errorf("handleUploadPack: write to %v: %v", cmd.Args, err))
return
}
// Signal to the Git subprocess that no more data is coming
stdin.Close()
wg.Wait()
if len(stdoutError) > 0 {
return
} }
if err := cmd.Wait(); err != nil && !(isExitError(err) && isShallowClone) {
helper.LogError(r, fmt.Errorf("handlePostRPC: wait for %v: %v", cmd.Args, err)) err = cmd.Wait()
if err != nil && !(isExitError(err) && isShallowClone) {
helper.LogError(r, fmt.Errorf("handleUploadPack: wait for %v: %v", cmd.Args, err))
return return
} }
return writtenIn, nil
}
func setupGitCommand(action string, a *api.Response, w *GitHttpResponseWriter, r *http.Request) (cmd *exec.Cmd, stdin io.WriteCloser, stdout io.ReadCloser, err error) {
// Prepare our Git subprocess
cmd = gitCommand(a.GL_ID, "git", subCommand(action), "--stateless-rpc", a.RepoPath)
stdout, err = cmd.StdoutPipe()
if err != nil {
helper.Fail500(w, r, fmt.Errorf("setupGitCommand: stdout: %v", err))
return nil, nil, nil, err
}
stdin, err = cmd.StdinPipe()
if err != nil {
helper.Fail500(w, r, fmt.Errorf("setupGitCommand: stdin: %v", err))
return nil, nil, nil, err
}
if err = cmd.Start(); err != nil {
helper.Fail500(w, r, fmt.Errorf("setupGitCommand: start %v: %v", cmd.Args, err))
return nil, nil, nil, err
}
return cmd, stdin, stdout, nil
} }
func writePostRPCHeader(w http.ResponseWriter, action string) { func writePostRPCHeader(w http.ResponseWriter, action string) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment