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 }