code.gitea.io/gitea@v1.22.3/modules/lfs/http_client.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package lfs 5 6 import ( 7 "bytes" 8 "context" 9 "errors" 10 "fmt" 11 "io" 12 "net/http" 13 "net/url" 14 "strings" 15 16 "code.gitea.io/gitea/modules/json" 17 "code.gitea.io/gitea/modules/log" 18 "code.gitea.io/gitea/modules/proxy" 19 ) 20 21 const httpBatchSize = 20 22 23 // HTTPClient is used to communicate with the LFS server 24 // https://github.com/git-lfs/git-lfs/blob/main/docs/api/batch.md 25 type HTTPClient struct { 26 client *http.Client 27 endpoint string 28 transfers map[string]TransferAdapter 29 } 30 31 // BatchSize returns the preferred size of batchs to process 32 func (c *HTTPClient) BatchSize() int { 33 return httpBatchSize 34 } 35 36 func newHTTPClient(endpoint *url.URL, httpTransport *http.Transport) *HTTPClient { 37 if httpTransport == nil { 38 httpTransport = &http.Transport{ 39 Proxy: proxy.Proxy(), 40 } 41 } 42 43 hc := &http.Client{ 44 Transport: httpTransport, 45 } 46 47 basic := &BasicTransferAdapter{hc} 48 client := &HTTPClient{ 49 client: hc, 50 endpoint: strings.TrimSuffix(endpoint.String(), "/"), 51 transfers: map[string]TransferAdapter{ 52 basic.Name(): basic, 53 }, 54 } 55 56 return client 57 } 58 59 func (c *HTTPClient) transferNames() []string { 60 keys := make([]string, len(c.transfers)) 61 i := 0 62 for k := range c.transfers { 63 keys[i] = k 64 i++ 65 } 66 return keys 67 } 68 69 func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Pointer) (*BatchResponse, error) { 70 log.Trace("BATCH operation with objects: %v", objects) 71 72 url := fmt.Sprintf("%s/objects/batch", c.endpoint) 73 74 request := &BatchRequest{operation, c.transferNames(), nil, objects} 75 payload := new(bytes.Buffer) 76 err := json.NewEncoder(payload).Encode(request) 77 if err != nil { 78 log.Error("Error encoding json: %v", err) 79 return nil, err 80 } 81 82 req, err := createRequest(ctx, http.MethodPost, url, map[string]string{"Content-Type": MediaType}, payload) 83 if err != nil { 84 return nil, err 85 } 86 87 res, err := performRequest(ctx, c.client, req) 88 if err != nil { 89 return nil, err 90 } 91 defer res.Body.Close() 92 93 var response BatchResponse 94 err = json.NewDecoder(res.Body).Decode(&response) 95 if err != nil { 96 log.Error("Error decoding json: %v", err) 97 return nil, err 98 } 99 100 if len(response.Transfer) == 0 { 101 response.Transfer = "basic" 102 } 103 104 return &response, nil 105 } 106 107 // Download reads the specific LFS object from the LFS server 108 func (c *HTTPClient) Download(ctx context.Context, objects []Pointer, callback DownloadCallback) error { 109 return c.performOperation(ctx, objects, callback, nil) 110 } 111 112 // Upload sends the specific LFS object to the LFS server 113 func (c *HTTPClient) Upload(ctx context.Context, objects []Pointer, callback UploadCallback) error { 114 return c.performOperation(ctx, objects, nil, callback) 115 } 116 117 func (c *HTTPClient) performOperation(ctx context.Context, objects []Pointer, dc DownloadCallback, uc UploadCallback) error { 118 if len(objects) == 0 { 119 return nil 120 } 121 122 operation := "download" 123 if uc != nil { 124 operation = "upload" 125 } 126 127 result, err := c.batch(ctx, operation, objects) 128 if err != nil { 129 return err 130 } 131 132 transferAdapter, ok := c.transfers[result.Transfer] 133 if !ok { 134 return fmt.Errorf("TransferAdapter not found: %s", result.Transfer) 135 } 136 137 for _, object := range result.Objects { 138 if object.Error != nil { 139 log.Trace("Error on object %v: %v", object.Pointer, object.Error) 140 if uc != nil { 141 if _, err := uc(object.Pointer, object.Error); err != nil { 142 return err 143 } 144 } else { 145 if err := dc(object.Pointer, nil, object.Error); err != nil { 146 return err 147 } 148 } 149 continue 150 } 151 152 if uc != nil { 153 if len(object.Actions) == 0 { 154 log.Trace("%v already present on server", object.Pointer) 155 continue 156 } 157 158 link, ok := object.Actions["upload"] 159 if !ok { 160 log.Debug("%+v", object) 161 return errors.New("missing action 'upload'") 162 } 163 164 content, err := uc(object.Pointer, nil) 165 if err != nil { 166 return err 167 } 168 169 err = transferAdapter.Upload(ctx, link, object.Pointer, content) 170 if err != nil { 171 return err 172 } 173 174 link, ok = object.Actions["verify"] 175 if ok { 176 if err := transferAdapter.Verify(ctx, link, object.Pointer); err != nil { 177 return err 178 } 179 } 180 } else { 181 link, ok := object.Actions["download"] 182 if !ok { 183 log.Debug("%+v", object) 184 return errors.New("missing action 'download'") 185 } 186 187 content, err := transferAdapter.Download(ctx, link) 188 if err != nil { 189 return err 190 } 191 192 if err := dc(object.Pointer, content, nil); err != nil { 193 return err 194 } 195 } 196 } 197 198 return nil 199 } 200 201 // createRequest creates a new request, and sets the headers. 202 func createRequest(ctx context.Context, method, url string, headers map[string]string, body io.Reader) (*http.Request, error) { 203 log.Trace("createRequest: %s", url) 204 req, err := http.NewRequestWithContext(ctx, method, url, body) 205 if err != nil { 206 log.Error("Error creating request: %v", err) 207 return nil, err 208 } 209 210 for key, value := range headers { 211 req.Header.Set(key, value) 212 } 213 req.Header.Set("Accept", AcceptHeader) 214 215 return req, nil 216 } 217 218 // performRequest sends a request, optionally performs a callback on the request and returns the response. 219 // If the status code is 200, the response is returned, and it will contain a non-nil Body. 220 // Otherwise, it will return an error, and the Body will be nil or closed. 221 func performRequest(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) { 222 log.Trace("performRequest: %s", req.URL) 223 res, err := client.Do(req) 224 if err != nil { 225 select { 226 case <-ctx.Done(): 227 return res, ctx.Err() 228 default: 229 } 230 log.Error("Error while processing request: %v", err) 231 return res, err 232 } 233 234 if res.StatusCode != http.StatusOK { 235 defer res.Body.Close() 236 return res, handleErrorResponse(res) 237 } 238 239 return res, nil 240 } 241 242 func handleErrorResponse(resp *http.Response) error { 243 var er ErrorResponse 244 err := json.NewDecoder(resp.Body).Decode(&er) 245 if err != nil { 246 if err == io.EOF { 247 return io.ErrUnexpectedEOF 248 } 249 log.Error("Error decoding json: %v", err) 250 return err 251 } 252 253 log.Trace("ErrorResponse(%v): %v", resp.Status, er) 254 return errors.New(er.Message) 255 }