You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

http_client.go 3.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package lfs
  5. import (
  6. "bytes"
  7. "context"
  8. "encoding/json"
  9. "errors"
  10. "fmt"
  11. "io"
  12. "net/http"
  13. "net/url"
  14. "strings"
  15. "code.gitea.io/gitea/modules/log"
  16. )
  17. // HTTPClient is used to communicate with the LFS server
  18. // https://github.com/git-lfs/git-lfs/blob/main/docs/api/batch.md
  19. type HTTPClient struct {
  20. client *http.Client
  21. endpoint string
  22. transfers map[string]TransferAdapter
  23. }
  24. func newHTTPClient(endpoint *url.URL) *HTTPClient {
  25. hc := &http.Client{}
  26. client := &HTTPClient{
  27. client: hc,
  28. endpoint: strings.TrimSuffix(endpoint.String(), "/"),
  29. transfers: make(map[string]TransferAdapter),
  30. }
  31. basic := &BasicTransferAdapter{hc}
  32. client.transfers[basic.Name()] = basic
  33. return client
  34. }
  35. func (c *HTTPClient) transferNames() []string {
  36. keys := make([]string, len(c.transfers))
  37. i := 0
  38. for k := range c.transfers {
  39. keys[i] = k
  40. i++
  41. }
  42. return keys
  43. }
  44. func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Pointer) (*BatchResponse, error) {
  45. url := fmt.Sprintf("%s/objects/batch", c.endpoint)
  46. request := &BatchRequest{operation, c.transferNames(), nil, objects}
  47. payload := new(bytes.Buffer)
  48. err := json.NewEncoder(payload).Encode(request)
  49. if err != nil {
  50. return nil, fmt.Errorf("lfs.HTTPClient.batch json.Encode: %w", err)
  51. }
  52. log.Trace("lfs.HTTPClient.batch NewRequestWithContext: %s", url)
  53. req, err := http.NewRequestWithContext(ctx, "POST", url, payload)
  54. if err != nil {
  55. return nil, fmt.Errorf("lfs.HTTPClient.batch http.NewRequestWithContext: %w", err)
  56. }
  57. req.Header.Set("Content-type", MediaType)
  58. req.Header.Set("Accept", MediaType)
  59. res, err := c.client.Do(req)
  60. if err != nil {
  61. select {
  62. case <-ctx.Done():
  63. return nil, ctx.Err()
  64. default:
  65. }
  66. return nil, fmt.Errorf("lfs.HTTPClient.batch http.Do: %w", err)
  67. }
  68. defer res.Body.Close()
  69. if res.StatusCode != http.StatusOK {
  70. return nil, fmt.Errorf("lfs.HTTPClient.batch: Unexpected servers response: %s", res.Status)
  71. }
  72. var response BatchResponse
  73. err = json.NewDecoder(res.Body).Decode(&response)
  74. if err != nil {
  75. return nil, fmt.Errorf("lfs.HTTPClient.batch json.Decode: %w", err)
  76. }
  77. if len(response.Transfer) == 0 {
  78. response.Transfer = "basic"
  79. }
  80. return &response, nil
  81. }
  82. // Download reads the specific LFS object from the LFS server
  83. func (c *HTTPClient) Download(ctx context.Context, oid string, size int64) (io.ReadCloser, error) {
  84. var objects []Pointer
  85. objects = append(objects, Pointer{oid, size})
  86. result, err := c.batch(ctx, "download", objects)
  87. if err != nil {
  88. return nil, err
  89. }
  90. transferAdapter, ok := c.transfers[result.Transfer]
  91. if !ok {
  92. return nil, fmt.Errorf("lfs.HTTPClient.Download Transferadapter not found: %s", result.Transfer)
  93. }
  94. if len(result.Objects) == 0 {
  95. return nil, errors.New("lfs.HTTPClient.Download: No objects in result")
  96. }
  97. content, err := transferAdapter.Download(ctx, result.Objects[0])
  98. if err != nil {
  99. return nil, err
  100. }
  101. return content, nil
  102. }