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  }