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