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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 package gitiles
5
6 import (
7 "bufio"
8 "bytes"
9 "encoding/base64"
10 "encoding/json"
11 "fmt"
12 "io"
13 "io/ioutil"
14 "net/http"
15 "reflect"
16 "strings"
17 )
18
19 // Gitiles wraps one Gitiles server. Use NewGitiles to create one.
20 type Gitiles struct {
21 url string
22 requests chan<- request
23 }
24
25 // URL returns the base url for this Gitiles service wrapper
26 func (g Gitiles) URL() string { return g.url }
27
28 // NewGitiles creates a new Gitiles instance for the given |url|, and a maximum
29 // number of concurrent requests.
30 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
31 // TODO(iannucci): make a separate http.Client?
32 // TODO(iannucci): have a way to destroy the Gitiles instance?
33 requestChan := make(chan request, maxConnections)
34 ret := &Gitiles{
35 url: strings.TrimRight(url, "/"),
36 requests: requestChan,
37 }
38 for i := 0; i < maxConnections; i++ {
Vadim Sh. 2014/10/21 15:27:00 Isn't there built in connection pool? :(
39 go ret.requestProcessor(requestChan)
40 }
41 return ret
42 }
43
44 // StatusError is a simple error type which wraps a Http StatusCode.
45 type StatusError int
46
47 // Bad returns true iff the status code is not 2XX
48 func (s StatusError) Bad() bool { return s < 200 || s >= 300 }
49 func (s StatusError) Error() string { return fmt.Sprintf("got status code %d", s ) }
50
51 // JSON Returns an instance of target or an error.
52 //
53 // Example:
54 // data := map[string]int{}
55 // result, err := g.JSON(data, "some", "url", "pieces")
56 // if err != nil { panic(err) }
57 // data = *result.(*map[string]int)
58 func (g *Gitiles) JSON(target interface{}, pieces ...string) (interface{}, error ) {
59 reply := make(chan jsonResult, 1)
60 g.requests <- jsonRequest{
61 strings.Join(pieces, "/"),
62 reflect.TypeOf(target),
63 reply,
64 }
65 rslt := <-reply
66 return rslt.dataPtr, rslt.err
67 }
68
69 // Text returns the decoded text data or an error. It will load the given path
70 // with format=TEXT and apply the base64 decoding.
71 func (g *Gitiles) Text(pieces ...string) ([]byte, error) {
72 reply := make(chan textResult, 1)
73 g.requests <- textRequest{
74 strings.Join(pieces, "/"),
75 reply,
76 }
77 rslt := <-reply
78 return rslt.data, rslt.err
79 }
80
81 // private decoders
82 type textResult struct {
83 err error
84 data []byte
85 }
86
87 type jsonResult struct {
88 err error
89 dataPtr interface{}
90 }
91
92 type request interface {
93 Process(rsp *http.Response, err error)
94 Method() string
95 URLPath() string
96 Body() io.Reader
97 }
98
99 type jsonRequest struct {
100 urlPath string
101 typ reflect.Type
102 resultChan chan<- jsonResult
103 }
104
105 func (j jsonRequest) URLPath() string { return j.urlPath + "?format=JSON" }
106 func (j jsonRequest) Method() string { return "GET" }
107 func (j jsonRequest) Body() io.Reader { return nil }
108
109 func (j jsonRequest) Process(resp *http.Response, err error) {
110 var rslt jsonResult
111 if err != nil {
112 rslt.err = err
113 } else {
114 bufreader := bufio.NewReader(resp.Body)
115 firstFour, err := bufreader.Peek(4)
116 if err != nil {
117 rslt.err = err
118 } else {
119 if bytes.Equal(firstFour, []byte(")]}'")) {
120 bufreader.Read(make([]byte, 4))
121 }
122
123 data := reflect.New(j.typ).Interface()
124 err = json.NewDecoder(bufreader).Decode(data)
125 if err != nil {
126 rslt.err = err
127 } else {
128 rslt.dataPtr = data
129 }
130 }
131 }
132 j.resultChan <- rslt
133 }
134
135 type textRequest struct {
136 urlPath string
137 resultChan chan<- textResult
138 }
139
140 func (t textRequest) URLPath() string { return t.urlPath + "?format=TEXT" }
141 func (t textRequest) Method() string { return "GET" }
142 func (t textRequest) Body() io.Reader { return nil }
143
144 func (t textRequest) internalProcess(rsp *http.Response, err error) ([]byte, err or) {
145 if err != nil {
146 return nil, err
147 }
148 return ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, rsp.Body))
149 }
150
151 func (t textRequest) Process(rsp *http.Response, err error) {
152 if data, err := t.internalProcess(rsp, err); err != nil {
153 t.resultChan <- textResult{err: err}
154 } else {
155 t.resultChan <- textResult{data: data}
156 }
157 }
158
159 func (g *Gitiles) requestProcessor(queue <-chan request) {
160 // Launched as a goroutine to avoid blocking the request processor.
161 for r := range queue {
162 r := r
163 if r == nil {
164 continue
165 }
166
167 var req *http.Request
168 req, err := http.NewRequest(r.Method(), g.url+"/"+r.URLPath(), r .Body())
169 if err != nil {
170 r.Process(nil, err)
171 continue
172 }
173
174 rsp, err := http.DefaultClient.Do(req)
175 if err != nil {
176 r.Process(nil, err)
177 continue
178 }
179
180 e := StatusError(rsp.StatusCode)
181 if e.Bad() {
182 err = e
183 }
184 func() {
185 defer rsp.Body.Close()
186 r.Process(rsp, err)
187 }()
188 }
189 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698