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