Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(107)

Unified Diff: go/src/infra/libs/gitiles/gitiles.go

Issue 662113003: Drover's back, baby! (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git/+/master
Patch Set: Lots of fixes Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: go/src/infra/libs/gitiles/gitiles.go
diff --git a/go/src/infra/libs/gitiles/gitiles.go b/go/src/infra/libs/gitiles/gitiles.go
new file mode 100644
index 0000000000000000000000000000000000000000..955117b839243a31095ef2a57b4e126d4984aeb8
--- /dev/null
+++ b/go/src/infra/libs/gitiles/gitiles.go
@@ -0,0 +1,189 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+package gitiles
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "reflect"
+ "strings"
+)
+
+// Gitiles wraps one Gitiles server. Use NewGitiles to create one.
+type Gitiles struct {
+ url string
+ requests chan<- request
+}
+
+// URL returns the base url for this Gitiles service wrapper
+func (g Gitiles) URL() string { return g.url }
+
+// NewGitiles creates a new Gitiles instance for the given |url|, and a maximum
+// number of concurrent requests.
+func NewGitiles(url string, maxConnections int) *Gitiles {
Vadim Sh. 2014/10/21 15:27:00 there's some idiomatic Go way to support HTTP midl
+ // TODO(iannucci): make a separate http.Client?
+ // TODO(iannucci): have a way to destroy the Gitiles instance?
+ requestChan := make(chan request, maxConnections)
+ ret := &Gitiles{
+ url: strings.TrimRight(url, "/"),
+ requests: requestChan,
+ }
+ for i := 0; i < maxConnections; i++ {
Vadim Sh. 2014/10/21 15:27:00 Isn't there built in connection pool? :(
+ go ret.requestProcessor(requestChan)
+ }
+ return ret
+}
+
+// StatusError is a simple error type which wraps a Http StatusCode.
+type StatusError int
+
+// Bad returns true iff the status code is not 2XX
+func (s StatusError) Bad() bool { return s < 200 || s >= 300 }
+func (s StatusError) Error() string { return fmt.Sprintf("got status code %d", s) }
+
+// JSON Returns an instance of target or an error.
+//
+// Example:
+// data := map[string]int{}
+// result, err := g.JSON(data, "some", "url", "pieces")
+// if err != nil { panic(err) }
+// data = *result.(*map[string]int)
+func (g *Gitiles) JSON(target interface{}, pieces ...string) (interface{}, error) {
+ reply := make(chan jsonResult, 1)
+ g.requests <- jsonRequest{
+ strings.Join(pieces, "/"),
+ reflect.TypeOf(target),
+ reply,
+ }
+ rslt := <-reply
+ return rslt.dataPtr, rslt.err
+}
+
+// Text returns the decoded text data or an error. It will load the given path
+// with format=TEXT and apply the base64 decoding.
+func (g *Gitiles) Text(pieces ...string) ([]byte, error) {
+ reply := make(chan textResult, 1)
+ g.requests <- textRequest{
+ strings.Join(pieces, "/"),
+ reply,
+ }
+ rslt := <-reply
+ return rslt.data, rslt.err
+}
+
+// private decoders
+type textResult struct {
+ err error
+ data []byte
+}
+
+type jsonResult struct {
+ err error
+ dataPtr interface{}
+}
+
+type request interface {
+ Process(rsp *http.Response, err error)
+ Method() string
+ URLPath() string
+ Body() io.Reader
+}
+
+type jsonRequest struct {
+ urlPath string
+ typ reflect.Type
+ resultChan chan<- jsonResult
+}
+
+func (j jsonRequest) URLPath() string { return j.urlPath + "?format=JSON" }
+func (j jsonRequest) Method() string { return "GET" }
+func (j jsonRequest) Body() io.Reader { return nil }
+
+func (j jsonRequest) Process(resp *http.Response, err error) {
+ var rslt jsonResult
+ if err != nil {
+ rslt.err = err
+ } else {
+ bufreader := bufio.NewReader(resp.Body)
+ firstFour, err := bufreader.Peek(4)
+ if err != nil {
+ rslt.err = err
+ } else {
+ if bytes.Equal(firstFour, []byte(")]}'")) {
+ bufreader.Read(make([]byte, 4))
+ }
+
+ data := reflect.New(j.typ).Interface()
+ err = json.NewDecoder(bufreader).Decode(data)
+ if err != nil {
+ rslt.err = err
+ } else {
+ rslt.dataPtr = data
+ }
+ }
+ }
+ j.resultChan <- rslt
+}
+
+type textRequest struct {
+ urlPath string
+ resultChan chan<- textResult
+}
+
+func (t textRequest) URLPath() string { return t.urlPath + "?format=TEXT" }
+func (t textRequest) Method() string { return "GET" }
+func (t textRequest) Body() io.Reader { return nil }
+
+func (t textRequest) internalProcess(rsp *http.Response, err error) ([]byte, error) {
+ if err != nil {
+ return nil, err
+ }
+ return ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, rsp.Body))
+}
+
+func (t textRequest) Process(rsp *http.Response, err error) {
+ if data, err := t.internalProcess(rsp, err); err != nil {
+ t.resultChan <- textResult{err: err}
+ } else {
+ t.resultChan <- textResult{data: data}
+ }
+}
+
+func (g *Gitiles) requestProcessor(queue <-chan request) {
+ // Launched as a goroutine to avoid blocking the request processor.
+ for r := range queue {
+ r := r
+ if r == nil {
+ continue
+ }
+
+ var req *http.Request
+ req, err := http.NewRequest(r.Method(), g.url+"/"+r.URLPath(), r.Body())
+ if err != nil {
+ r.Process(nil, err)
+ continue
+ }
+
+ rsp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ r.Process(nil, err)
+ continue
+ }
+
+ e := StatusError(rsp.StatusCode)
+ if e.Bad() {
+ err = e
+ }
+ func() {
+ defer rsp.Body.Close()
+ r.Process(rsp, err)
+ }()
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698