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

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: 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..3c1f042d881203a9e10af97f05b92256d5d60e61
--- /dev/null
+++ b/go/src/infra/libs/gitiles/gitiles.go
@@ -0,0 +1,190 @@
+package gitiles
+
+import "bufio"
+import "bytes"
+import "encoding/base64"
+import "encoding/json"
+import "fmt"
+import "io"
+import "io/ioutil"
+import "net/http"
+import "reflect"
+import "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 {
+ // TODO(riannucci): make a separate http.Client?
+ // TODO(riannucci): 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++ {
+ 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) }
M-A Ruel 2014/10/18 00:47:06 return s < 200 || s >= 300 is actually shorter and
iannucci 2014/10/20 21:11:58 Done.
+func (s StatusError) Error() string { return fmt.Sprintf("got status code %d", s) }
+
+// TextResult is object you get back from JSON(). If Err is nil, DataPtr
+// contains an instance of whatever type you passed to |target|, unmarshalled
+// from the json response from gitiles.
+type TextResult struct {
+ Err error
+ Data []byte
+}
+
+// JSONResult is object you get back from JSON(). If Err is nil, DataPtr
+// contains an instance of whatever type you passed to |target|, unmarshalled
+// from the json response from gitiles.
+type JSONResult struct {
M-A Ruel 2014/10/18 00:47:06 Personally I had used specialized handlers, e.g. l
iannucci 2014/10/20 21:11:58 Made this struct private. no reason to expose to c
+ Err error
+ DataPtr interface{}
+}
+
+// JSON Returns a channel to a JSONResult where DataPtr is a pointer to
+// a datatype described by target.
+//
+// Example:
+// data := map[string]int{}
+// result := g.JSON(data, "some", "url", "pieces")
+// if result.Err != nil { panic(result.Err) }
+// data = *result.DataPtr.(*map[string]int)
+func (g *Gitiles) JSON(target interface{}, pieces ...string) <-chan JSONResult {
+ reply := make(chan JSONResult, 1)
+ g.requests <- jsonRequest{
+ strings.Join(pieces, "/"),
+ reflect.TypeOf(target),
+ reply,
+ }
+ return reply
+}
+
+// Text returns a channel to a TextResult. It will load the given path with
+// format=TEXT and return the decoded data.
+func (g *Gitiles) Text(pieces ...string) <-chan TextResult {
+ reply := make(chan TextResult, 1)
+ g.requests <- textRequest{
+ strings.Join(pieces, "/"),
+ reply,
+ }
+ return reply
+}
+
+// private decoders
+
+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) {
+ // Lanched as a goroutine to avoid blocking the request processor.
M-A Ruel 2014/10/18 00:47:06 Launched
iannucci 2014/10/20 21:11:58 Done.
+ 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
M-A Ruel 2014/10/18 00:47:06 why not? err = e
iannucci 2014/10/20 21:11:58 Oops, done.
+ }
+ func() {
+ defer rsp.Body.Close()
+ r.Process(rsp, err)
+ }()
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698