code.gitea.io/gitea@v1.19.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 "net/http" 12 "net/url" 13 "strings" 14 15 "code.gitea.io/gitea/modules/json" 16 "code.gitea.io/gitea/modules/log" 17 "code.gitea.io/gitea/modules/proxy" 18 ) 19 20 const batchSize = 20 21 22 // HTTPClient is used to communicate with the LFS server 23 // https://github.com/git-lfs/git-lfs/blob/main/docs/api/batch.md 24 type HTTPClient struct { 25 client *http.Client 26 endpoint string 27 transfers map[string]TransferAdapter 28 } 29 30 // BatchSize returns the preferred size of batchs to process 31 func (c *HTTPClient) BatchSize() int { 32 return batchSize 33 } 34 35 func newHTTPClient(endpoint *url.URL, httpTransport *http.Transport) *HTTPClient { 36 if httpTransport == nil { 37 httpTransport = &http.Transport{ 38 Proxy: proxy.Proxy(), 39 } 40 } 41 42 hc := &http.Client{ 43 Transport: httpTransport, 44 } 45 46 client := &HTTPClient{ 47 client: hc, 48 endpoint: strings.TrimSuffix(endpoint.String(), "/"), 49 transfers: make(map[string]TransferAdapter), 50 } 51 52 basic := &BasicTransferAdapter{hc} 53 54 client.transfers[basic.Name()] = basic 55 56 return client 57 } 58 59 func (c *HTTPClient) transferNames() []string { 60 keys := make([]string, len(c.transfers)) 61 62 i := 0 63 for k := range c.transfers { 64 keys[i] = k 65 i++ 66 } 67 68 return keys 69 } 70 71 func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Pointer) (*BatchResponse, error) { 72 log.Trace("BATCH operation with objects: %v", objects) 73 74 url := fmt.Sprintf("%s/objects/batch", c.endpoint) 75 76 request := &BatchRequest{operation, c.transferNames(), nil, objects} 77 78 payload := new(bytes.Buffer) 79 err := json.NewEncoder(payload).Encode(request) 80 if err != nil { 81 log.Error("Error encoding json: %v", err) 82 return nil, err 83 } 84 85 log.Trace("Calling: %s", url) 86 87 req, err := http.NewRequestWithContext(ctx, "POST", url, payload) 88 if err != nil { 89 log.Error("Error creating request: %v", err) 90 return nil, err 91 } 92 req.Header.Set("Content-type", MediaType) 93 req.Header.Set("Accept", MediaType) 94 95 res, err := c.client.Do(req) 96 if err != nil { 97 select { 98 case <-ctx.Done(): 99 return nil, ctx.Err() 100 default: 101 } 102 log.Error("Error while processing request: %v", err) 103 return nil, err 104 } 105 defer res.Body.Close() 106 107 if res.StatusCode != http.StatusOK { 108 return nil, fmt.Errorf("Unexpected server response: %s", res.Status) 109 } 110 111 var response BatchResponse 112 err = json.NewDecoder(res.Body).Decode(&response) 113 if err != nil { 114 log.Error("Error decoding json: %v", err) 115 return nil, err 116 } 117 118 if len(response.Transfer) == 0 { 119 response.Transfer = "basic" 120 } 121 122 return &response, nil 123 } 124 125 // Download reads the specific LFS object from the LFS server 126 func (c *HTTPClient) Download(ctx context.Context, objects []Pointer, callback DownloadCallback) error { 127 return c.performOperation(ctx, objects, callback, nil) 128 } 129 130 // Upload sends the specific LFS object to the LFS server 131 func (c *HTTPClient) Upload(ctx context.Context, objects []Pointer, callback UploadCallback) error { 132 return c.performOperation(ctx, objects, nil, callback) 133 } 134 135 func (c *HTTPClient) performOperation(ctx context.Context, objects []Pointer, dc DownloadCallback, uc UploadCallback) error { 136 if len(objects) == 0 { 137 return nil 138 } 139 140 operation := "download" 141 if uc != nil { 142 operation = "upload" 143 } 144 145 result, err := c.batch(ctx, operation, objects) 146 if err != nil { 147 return err 148 } 149 150 transferAdapter, ok := c.transfers[result.Transfer] 151 if !ok { 152 return fmt.Errorf("TransferAdapter not found: %s", result.Transfer) 153 } 154 155 for _, object := range result.Objects { 156 if object.Error != nil { 157 objectError := errors.New(object.Error.Message) 158 log.Trace("Error on object %v: %v", object.Pointer, objectError) 159 if uc != nil { 160 if _, err := uc(object.Pointer, objectError); err != nil { 161 return err 162 } 163 } else { 164 if err := dc(object.Pointer, nil, objectError); err != nil { 165 return err 166 } 167 } 168 continue 169 } 170 171 if uc != nil { 172 if len(object.Actions) == 0 { 173 log.Trace("%v already present on server", object.Pointer) 174 continue 175 } 176 177 link, ok := object.Actions["upload"] 178 if !ok { 179 log.Debug("%+v", object) 180 return errors.New("Missing action 'upload'") 181 } 182 183 content, err := uc(object.Pointer, nil) 184 if err != nil { 185 return err 186 } 187 188 err = transferAdapter.Upload(ctx, link, object.Pointer, content) 189 190 content.Close() 191 192 if err != nil { 193 return err 194 } 195 196 link, ok = object.Actions["verify"] 197 if ok { 198 if err := transferAdapter.Verify(ctx, link, object.Pointer); err != nil { 199 return err 200 } 201 } 202 } else { 203 link, ok := object.Actions["download"] 204 if !ok { 205 log.Debug("%+v", object) 206 return errors.New("Missing action 'download'") 207 } 208 209 content, err := transferAdapter.Download(ctx, link) 210 if err != nil { 211 return err 212 } 213 214 if err := dc(object.Pointer, content, nil); err != nil { 215 return err 216 } 217 } 218 } 219 220 return nil 221 }