Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 } | |
| OLD | NEW |