github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/backend/http.go (about)

     1  // Package backend contains implementation of various backend providers.
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package backend
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  
    13  	"github.com/NVIDIA/aistore/api/apc"
    14  	"github.com/NVIDIA/aistore/cmn"
    15  	"github.com/NVIDIA/aistore/cmn/cos"
    16  	"github.com/NVIDIA/aistore/cmn/debug"
    17  	"github.com/NVIDIA/aistore/cmn/nlog"
    18  	"github.com/NVIDIA/aistore/core"
    19  	"github.com/NVIDIA/aistore/core/meta"
    20  )
    21  
    22  type (
    23  	htbp struct {
    24  		t      core.TargetPut
    25  		cliH   *http.Client
    26  		cliTLS *http.Client
    27  		base
    28  	}
    29  )
    30  
    31  // interface guard
    32  var _ core.Backend = (*htbp)(nil)
    33  
    34  func NewHTTP(t core.TargetPut, config *cmn.Config) core.Backend {
    35  	htbp := &htbp{
    36  		t:    t,
    37  		base: base{apc.HTTP},
    38  	}
    39  	htbp.cliH, htbp.cliTLS = cmn.NewDefaultClients(config.Client.TimeoutLong.D())
    40  	return htbp
    41  }
    42  
    43  func (htbp *htbp) client(u string) *http.Client {
    44  	if cos.IsHTTPS(u) {
    45  		return htbp.cliTLS
    46  	}
    47  	return htbp.cliH
    48  }
    49  
    50  func (htbp *htbp) HeadBucket(ctx context.Context, bck *meta.Bck) (bckProps cos.StrKVs, ecode int, err error) {
    51  	// TODO: we should use `bck.RemoteBck()`.
    52  
    53  	origURL, err := getOriginalURL(ctx, bck, "")
    54  	if err != nil {
    55  		return nil, http.StatusBadRequest, err
    56  	}
    57  
    58  	if cmn.Rom.FastV(4, cos.SmoduleBackend) {
    59  		nlog.Infof("[head_bucket] original_url: %q", origURL)
    60  	}
    61  
    62  	// Contact the original URL - as long as we can make connection we assume it's good.
    63  	resp, err := htbp.client(origURL).Head(origURL)
    64  	if err != nil {
    65  		return nil, http.StatusBadRequest, err
    66  	}
    67  	resp.Body.Close()
    68  
    69  	if resp.StatusCode != http.StatusOK {
    70  		err = fmt.Errorf("HEAD(%s) failed, status %d", origURL, resp.StatusCode)
    71  		return nil, resp.StatusCode, err
    72  	}
    73  
    74  	if resp.Header.Get(cos.HdrETag) == "" {
    75  		// TODO: improve validation
    76  		nlog.Errorf("Warning: missing header %s (response header: %+v)", cos.HdrETag, resp.Header)
    77  	}
    78  
    79  	bckProps = make(cos.StrKVs)
    80  	bckProps[apc.HdrBackendProvider] = apc.HTTP
    81  	return
    82  }
    83  
    84  func (*htbp) ListObjects(*meta.Bck, *apc.LsoMsg, *cmn.LsoRes) (ecode int, err error) {
    85  	debug.Assert(false)
    86  	return
    87  }
    88  
    89  func (*htbp) ListBuckets(cmn.QueryBcks) (bcks cmn.Bcks, ecode int, err error) {
    90  	debug.Assert(false)
    91  	return
    92  }
    93  
    94  func getOriginalURL(ctx context.Context, bck *meta.Bck, objName string) (string, error) {
    95  	origURL, ok := ctx.Value(cos.CtxOriginalURL).(string)
    96  	if !ok || origURL == "" {
    97  		if bck.Props == nil {
    98  			return "", fmt.Errorf("failed to HEAD (%s): original_url is empty", bck)
    99  		}
   100  		origURL = bck.Props.Extra.HTTP.OrigURLBck
   101  		debug.Assert(origURL != "")
   102  		if objName != "" {
   103  			origURL = cos.JoinPath(origURL, objName) // see `cmn.URL2BckObj`
   104  		}
   105  	}
   106  	return origURL, nil
   107  }
   108  
   109  func (htbp *htbp) HeadObj(ctx context.Context, lom *core.LOM, _ *http.Request) (oa *cmn.ObjAttrs, ecode int, err error) {
   110  	var (
   111  		h   = cmn.BackendHelpers.HTTP
   112  		bck = lom.Bck() // TODO: This should be `cloudBck = lom.Bck().RemoteBck()`
   113  	)
   114  	origURL, err := getOriginalURL(ctx, bck, lom.ObjName)
   115  	debug.AssertNoErr(err)
   116  
   117  	if cmn.Rom.FastV(4, cos.SmoduleBackend) {
   118  		nlog.Infof("[head_object] original_url: %q", origURL)
   119  	}
   120  	resp, err := htbp.client(origURL).Head(origURL)
   121  	if err != nil {
   122  		return nil, http.StatusBadRequest, err
   123  	}
   124  	resp.Body.Close()
   125  	if resp.StatusCode != http.StatusOK {
   126  		return nil, resp.StatusCode, fmt.Errorf("error occurred: %v", resp.StatusCode)
   127  	}
   128  	oa = &cmn.ObjAttrs{}
   129  	oa.SetCustomKey(cmn.SourceObjMD, apc.HTTP)
   130  	if resp.ContentLength >= 0 {
   131  		oa.Size = resp.ContentLength
   132  	}
   133  	if v, ok := h.EncodeVersion(resp.Header.Get(cos.HdrETag)); ok {
   134  		oa.SetCustomKey(cmn.ETag, v)
   135  	}
   136  	if cmn.Rom.FastV(4, cos.SmoduleBackend) {
   137  		nlog.Infof("[head_object] %s", lom)
   138  	}
   139  	return
   140  }
   141  
   142  func (htbp *htbp) GetObj(ctx context.Context, lom *core.LOM, owt cmn.OWT, _ *http.Request) (int, error) {
   143  	res := htbp.GetObjReader(ctx, lom, 0, 0)
   144  	if res.Err != nil {
   145  		return res.ErrCode, res.Err
   146  	}
   147  	params := allocPutParams(res, owt)
   148  	res.Err = htbp.t.PutObject(lom, params)
   149  	core.FreePutParams(params)
   150  	if res.Err != nil {
   151  		return 0, res.Err
   152  	}
   153  	if cmn.Rom.FastV(4, cos.SmoduleBackend) {
   154  		nlog.Infof("[get_object] %s", lom)
   155  	}
   156  	return 0, nil
   157  }
   158  
   159  func (htbp *htbp) GetObjReader(ctx context.Context, lom *core.LOM, offset, length int64) (res core.GetReaderResult) {
   160  	var (
   161  		req  *http.Request
   162  		resp *http.Response
   163  		h    = cmn.BackendHelpers.HTTP
   164  		bck  = lom.Bck() // TODO: This should be `cloudBck = lom.Bck().RemoteBck()`
   165  	)
   166  
   167  	origURL, err := getOriginalURL(ctx, bck, lom.ObjName)
   168  	debug.AssertNoErr(err)
   169  
   170  	if cmn.Rom.FastV(4, cos.SmoduleBackend) {
   171  		nlog.Infof("[HTTP CLOUD][GET] original_url: %q", origURL)
   172  	}
   173  
   174  	req, res.Err = http.NewRequest(http.MethodGet, origURL, http.NoBody)
   175  	if err != nil {
   176  		res.ErrCode = http.StatusInternalServerError
   177  		return res
   178  	}
   179  	if length > 0 {
   180  		rng := cmn.MakeRangeHdr(offset, length)
   181  		req.Header = http.Header{cos.HdrRange: []string{rng}}
   182  	}
   183  	resp, res.Err = htbp.client(origURL).Do(req) //nolint:bodyclose // is closed by the caller
   184  	if res.Err != nil {
   185  		return res
   186  	}
   187  	if resp.StatusCode != http.StatusOK {
   188  		res.ErrCode = resp.StatusCode
   189  		res.Err = fmt.Errorf("error occurred: %v", resp.StatusCode)
   190  		return res
   191  	}
   192  
   193  	if cmn.Rom.FastV(4, cos.SmoduleBackend) {
   194  		nlog.Infof("[HTTP CLOUD][GET] success, size: %d", resp.ContentLength)
   195  	}
   196  
   197  	lom.SetCustomKey(cmn.SourceObjMD, apc.HTTP)
   198  	lom.SetCustomKey(cmn.OrigURLObjMD, origURL)
   199  	if v, ok := h.EncodeVersion(resp.Header.Get(cos.HdrETag)); ok {
   200  		lom.SetCustomKey(cmn.ETag, v)
   201  	}
   202  	res.Size = resp.ContentLength
   203  	res.R = resp.Body
   204  	return res
   205  }
   206  
   207  func (*htbp) PutObj(io.ReadCloser, *core.LOM, *http.Request) (int, error) {
   208  	return http.StatusBadRequest, cmn.NewErrUnsupp("PUT", " objects => HTTP backend")
   209  }
   210  
   211  func (*htbp) DeleteObj(*core.LOM) (int, error) {
   212  	return http.StatusBadRequest, cmn.NewErrUnsupp("DELETE", " objects from HTTP backend")
   213  }