diff --git a/content.go b/content.go index 3de9932..37bb382 100644 --- a/content.go +++ b/content.go @@ -19,13 +19,15 @@ import ( // Content defines some content type Content struct { - Type string `json:"Type"` - Name string `json:"Name,omitempty"` - URL *url.URL `json:"-"` - Length uint64 `json:"Length"` - Data []byte `json:"Data"` - Headers http.Header `json:"headers,omitempty"` - Cookies []*http.Cookie `json:"-"` + Type string `json:"Type"` + StatusCode int `json:"StatusCode,omitempty"` + Status string `json:"Status,omitempty"` + Name string `json:"Name,omitempty"` + URL *url.URL `json:"-"` + Length uint64 `json:"Length"` + Data []byte `json:"Data"` + Headers http.Header `json:"headers,omitempty"` + Cookies []*http.Cookie `json:"-"` } // ContentWithData instantiates a Content from a simple byte array diff --git a/go.mod b/go.mod index 9063b1d..780933e 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/gildas/go-request -go 1.23 +go 1.23.0 -toolchain go1.23.3 +toolchain go1.24.1 require ( github.com/gildas/go-core v0.6.0 @@ -14,38 +14,38 @@ require ( ) require ( - cloud.google.com/go v0.117.0 // indirect - cloud.google.com/go/auth v0.13.0 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect + cloud.google.com/go v0.120.0 // indirect + cloud.google.com/go/auth v0.15.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.6.0 // indirect - cloud.google.com/go/logging v1.12.0 // indirect - cloud.google.com/go/longrunning v0.6.3 // indirect + cloud.google.com/go/logging v1.13.0 // indirect + cloud.google.com/go/longrunning v0.6.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/s2a-go v0.1.8 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect - go.opentelemetry.io/otel v1.33.0 // indirect - go.opentelemetry.io/otel/metric v1.33.0 // indirect - go.opentelemetry.io/otel/trace v1.33.0 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/oauth2 v0.24.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.8.0 // indirect - google.golang.org/api v0.214.0 // indirect - google.golang.org/genproto v0.0.0-20241223144023-3abc09e42ca8 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect - google.golang.org/grpc v1.69.2 // indirect - google.golang.org/protobuf v1.36.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/oauth2 v0.28.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.11.0 // indirect + google.golang.org/api v0.228.0 // indirect + google.golang.org/genproto v0.0.0-20250324211829-b45e905df463 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect + google.golang.org/grpc v1.71.1 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2d91ec8..b500ed0 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,17 @@ -cloud.google.com/go v0.117.0 h1:Z5TNFfQxj7WG2FgOGX1ekC5RiXrYgms6QscOm32M/4s= -cloud.google.com/go v0.117.0/go.mod h1:ZbwhVTb1DBGt2Iwb3tNO6SEK4q+cplHZmLWH+DelYYc= -cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= -cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= -cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= -cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA= +cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q= +cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= +cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= -cloud.google.com/go/iam v1.3.0 h1:4Wo2qTaGKFtajbLpF6I4mywg900u3TLlHDb6mriLDPU= -cloud.google.com/go/iam v1.3.0/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= -cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= -cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= -cloud.google.com/go/longrunning v0.6.3 h1:A2q2vuyXysRcwzqDpMMLSI6mb6o39miS52UEG/Rd2ng= -cloud.google.com/go/longrunning v0.6.3/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/iam v1.4.0 h1:ZNfy/TYfn2uh/ukvhp783WhnbVluqf/tzOaqVUPlIPA= +cloud.google.com/go/iam v1.4.0/go.mod h1:gMBgqPaERlriaOV0CUl//XUzDhSfXevn4OEUbg6VRs4= +cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= +cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= +cloud.google.com/go/longrunning v0.6.6 h1:XJNDo5MUfMM05xK3ewpbSdmt7R2Zw+aQEMbdQR65Rbw= +cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -31,14 +31,14 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8J github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= -github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= -github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -57,46 +57,46 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= -go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= -go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= -go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= -go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= -go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= -go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= -go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= -go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= -go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= -go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA= -google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE= -google.golang.org/genproto v0.0.0-20241223144023-3abc09e42ca8 h1:e26eS1K69yxjjNNHYqjN49y95kcaQLJ3TL5h68dcA1E= -google.golang.org/genproto v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:i5btTErZyoKCCubju3HS5LVho4nZd3yFnEp6moqeUjE= -google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 h1:st3LcW/BPi75W4q1jJTEor/QWwbNlPlDG0JTn6XhZu0= -google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:klhJGKFyG8Tn50enBn7gizg4nXGXJ+jqEREdCWaPcV4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= -google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= -google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs= +google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4= +google.golang.org/genproto v0.0.0-20250324211829-b45e905df463 h1:qEFnJI6AnfZk0NNe8YTyXQh5i//Zxi4gBHwRgp76qpw= +google.golang.org/genproto v0.0.0-20250324211829-b45e905df463/go.mod h1:SqIx1NV9hcvqdLHo7uNZDS5lrUJybQ3evo3+z/WBfA0= +google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM= +google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= +google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/request.go b/request.go index ad88198..2f9891b 100644 --- a/request.go +++ b/request.go @@ -154,35 +154,27 @@ func Send(options *Options, results interface{}) (*Content, error) { if res.StatusCode >= 400 { log.Errorf("Response %s in %s", res.Status, reqDuration) log.Debugf("Response Headers: %#v", res.Header) - if core.Contains(options.RetryableStatusCodes, res.StatusCode) { - if attempt+1 < options.Attempts { - var retryAfter time.Duration - - log.Infof("Retryable Response Status: %s", res.Status) - if options.InterAttemptUseRetryAfter && len(res.Header.Get("Retry-After")) > 0 { - retryAfter = time.Duration(core.Atoi(res.Header.Get("Retry-After"), 0))*time.Second + 1*time.Second // just to stay on the safe side, add 1 second - log.Debugf("Retry-After from headers (+1s safety net): %s", retryAfter) - } else { - elapsed := time.Since(start) - interval := int(elapsed/options.InterAttemptBackoffInterval) + 1 - retryAfter = time.Duration(math.Pow(options.InterAttemptDelay.Seconds(), float64(interval))) * time.Second - log.Debugf("Interval: %d, delay: %s, Exponential Backoff: %s", interval, options.InterAttemptDelay, retryAfter) - } - log.Infof("Waiting for %s before trying again", retryAfter) - time.Sleep(retryAfter) - req, _ = buildRequest(log, options) - continue - } + if shouldRetryAfterDelay(log, res, options, attempt, time.Since(start)) { + req, _ = buildRequest(log, options) + continue } // Read the body to get the error message resContent, err := ContentFromReader(res.Body, res.Header.Get("Content-Type"), core.Atoi(res.Header.Get("Content-Length"), 0), res.Header, res.Cookies(), log) if err != nil { return nil, errors.FromHTTPStatusCode(res.StatusCode) } + resContent.StatusCode = res.StatusCode + resContent.Status = res.Status log.Infof("Response body in %s: %s", time.Since(start), resContent.LogString(uint64(options.ResponseBodyLogSize))) return resContent, errors.FromHTTPStatusCode(res.StatusCode) } + // Response is successful, but we might still need to retry (common example: 202 Accepted) + if shouldRetryAfterDelay(log, res, options, attempt, time.Since(start)) { + req, _ = buildRequest(log, options) + continue + } + log.Debugf("Response %s in %s", res.Status, reqDuration) log.Tracef("Response Headers: %#v", res.Header) @@ -206,7 +198,6 @@ func Send(options *Options, results interface{}) (*Content, error) { log.Tracef("Computed Response Content-Type: %s", resContentType) // Reading the response body - if writer, ok := results.(io.Writer); ok { if options.ProgressWriter != nil { if options.ProgressSetMaxFunc != nil { @@ -230,12 +221,16 @@ func Send(options *Options, results interface{}) (*Content, error) { } log.Tracef("Read %d bytes", bytesRead) resContent := ContentWithData([]byte{}, resContentType, bytesRead, res.Header, res.Cookies()) + resContent.StatusCode = res.StatusCode + resContent.Status = res.Status return resContent, nil } else if results != nil { // Unmarshaling the response body if requested (structs, arrays, maps, etc) resContent, err := ContentFromReader(res.Body, resContentType, res.Header, res.Cookies(), log) if err != nil { return nil, errors.WithStack(err) } + resContent.StatusCode = res.StatusCode + resContent.Status = res.Status log.Tracef("Response body in %s: %s", time.Since(start), resContent.LogString(uint64(options.ResponseBodyLogSize))) if resContent.Length > 0 { err = json.Unmarshal(resContent.Data, results) @@ -572,6 +567,29 @@ func buildRequest(log *logger.Logger, options *Options) (*http.Request, error) { return req, nil } +// ShouldRetry checks if the request should be retried +// +// If true, it will also wait for the delay before returning +func shouldRetryAfterDelay(log *logger.Logger, res *http.Response, options *Options, attempt uint, elapsed time.Duration) bool { + if !core.Contains(options.RetryableStatusCodes, res.StatusCode) || attempt >= options.Attempts { + return false + } + var retryAfter time.Duration + + log.Infof("Retryable Response Status %d: %s", res.StatusCode, res.Status) + if options.InterAttemptUseRetryAfter && len(res.Header.Get("Retry-After")) > 0 { + retryAfter = time.Duration(core.Atoi(res.Header.Get("Retry-After"), 0))*time.Second + 1*time.Second // just to stay on the safe side, add 1 second + log.Debugf("Retry-After from headers (+1s safety net): %s", retryAfter) + } else { + interval := int(elapsed/options.InterAttemptBackoffInterval) + 1 + retryAfter = time.Duration(math.Pow(options.InterAttemptDelay.Seconds(), float64(interval))) * time.Second + log.Debugf("Interval: %d, delay: %s, Exponential Backoff: %s", interval, options.InterAttemptDelay, retryAfter) + } + log.Infof("Waiting for %s before trying again", retryAfter) + time.Sleep(retryAfter) + return true +} + func marshal(payload interface{}) ([]byte, error) { data, err := json.Marshal(payload) if errors.Is(err, errors.JSONMarshalError) { diff --git a/request_test.go b/request_test.go index ae33cf1..d9c1463 100644 --- a/request_test.go +++ b/request_test.go @@ -875,6 +875,19 @@ func (suite *RequestSuite) TestCanRetryPostingRequestWithAttachment() { suite.Require().NoError(err, "Failed reading response content, err=%+v", err) } +func (suite *RequestSuite) TestCanRetryWithStatusAccepted() { + serverURL, _ := url.Parse(suite.Server.URL) + serverURL, _ = serverURL.Parse("/retry-accepted") + _, err := request.Send(&request.Options{ + URL: serverURL, + RetryableStatusCodes: []int{http.StatusAccepted}, + Attempts: 5, + Timeout: 1 * time.Second, + Logger: suite.Logger, + }, nil) + suite.Require().NoError(err, "Failed reading response content, err=%+v", err) +} + func (suite *RequestSuite) TestShouldFailPostingWithNonSeekerPayloadAndAttempts() { serverURL, _ := url.Parse(suite.Server.URL) reader := failingReader(0) // This reader cannot seek @@ -1006,7 +1019,9 @@ func (suite *RequestSuite) TestShouldFailReceivingWithTooManyRetries() { }, nil) suite.Require().Error(err, "Should have failed sending request") suite.Logger.Errorf("Expected Error", err) - suite.Assert().ErrorIs(err, errors.HTTPServiceUnavailable, "error should be an HTTP Service Unavailable error, error: %+v", err) + // suite.Assert().ErrorIs(err, errors.HTTPServiceUnavailable, "error should be an HTTP Service Unavailable error, error: %+v", err) + // We can also receive a Request Timeout + suite.Assert().ErrorIs(err, errors.HTTPStatusRequestTimeout, "error should be an HTTP Request Timeout error, error: %+v", err) } func (suite *RequestSuite) TestShouldFailPostingWithTooManyRetries() { @@ -1024,7 +1039,9 @@ func (suite *RequestSuite) TestShouldFailPostingWithTooManyRetries() { }, nil) suite.Require().Error(err, "Should have failed sending request") suite.Logger.Errorf("Expected Error", err) - suite.Assert().ErrorIs(err, errors.HTTPServiceUnavailable, "error should be an HTTP Service Unavailable error, error: %+v", err) + // suite.Assert().ErrorIs(err, errors.HTTPServiceUnavailable, "error should be an HTTP Service Unavailable error, error: %+v", err) + // We can also receive a Request Timeout + suite.Assert().ErrorIs(err, errors.HTTPStatusRequestTimeout, "error should be an HTTP Request Timeout error, error: %+v", err) } func (suite *RequestSuite) TestShouldFailReceivingBadResponse() { diff --git a/server_test.go b/server_test.go index b6b2d9d..97da5fd 100644 --- a/server_test.go +++ b/server_test.go @@ -313,6 +313,16 @@ func CreateTestServerHandler(suite *RequestSuite, server *httptest.Server) http. res.WriteHeader(returnStatus) return } + case "/retry-accepted": + max := core.Atoi(req.Header.Get("X-Max-Retry"), 5) + attempt := core.Atoi(req.Header.Get("X-Attempt"), 0) + if attempt < max { // On the max-th attempt, we want to return 200 + retryAfter := core.Atoi(req.Header.Get("X-Retry-After"), 5) + res.Header().Add("Retry-After", strconv.Itoa(retryAfter)) + returnStatus := core.Atoi(req.Header.Get("X-Return-Status"), http.StatusAccepted) + res.WriteHeader(returnStatus) + return + } case "/redirect": queryString := "response-content-disposition=attachment%3Bfilename%3D%22Bo%C3%AEte-Vitamine-C.jpg%22&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEAQaDmFwLW5vcnRoZWFzdC0xIkYwRAIgXwjLAgEAHaXF5ADwxHrr%2BGzy2g5P69h5y5e68hHwjzECIFGV5tHl%2FS8VSIVjsWkxwE5Ks9QlkmM2MIVnB5XD5OvUKo0ECO3%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEQABoMNzY1NjI4OTg1NDcxIgz%2Bjt73UFcVYMCGvYQq4QO02iQHzW%2FH%2Fs8El%2BTPF0ekwyOpSN0rxQ6482INGSqGiW%2FELVFRayVTR9T1nbuZ74FVuDe2VYWHYOYPyfmaZiKAnh0cR1kQdSE2A6SUhhkG%2BW0KF1Sw0O5J33f43fvhQYqcj%2FIAMTUB8FuVAN03hNYPQck83F%2FjuGYepPJ7AZGHix%2FYtUAB18Wq%2B3idKOS1abya0wV5pS9PSYK2hnt2pDMu82U2rjhNciQpAwBYIt%2FgGmQ1KQ3YGa8hpp5%2BBMC%2FDHletUEo257cAhZzwMOO3uyhK%2FVC1%2Fc3vthmA9EuWXpnbMXXGykZh7Ya26ookMSRXj10Fsz%2BGSe%2Fyan4vePUuwvTS6aozvL7KxoSs6wxD8pAzQRKkn1lf0i6Xzip461xAy8X0YowwxGE8XPzGcLztxgi7L5ef7NI3IzMphWkwH4QMiBD4D7ptGoE16j2zflmXkXNBPH9IBA1KJ%2Bqv1g6Olmrav19oWMDmVoQWD8%2FW%2BYBEobNOAPUJ6hfppxpinuAPMf1uIueFQrgwY58y3vy6WuMQmvjaIIu2u2QqSqH%2BK3SA3AIzcrEmoEub6OxO5Kge8LroqKr18LK2MNj4ZGzgPRrG%2F3aEIT7y0OHqBMF4TUCBFpwPwpuQz6f9yjKhqlZxypBMJ%2BDtocGOqYBoNa1X%2B6yOM2qg7T%2BtP8UNOFw4vZN73svJWhiqXe9lQhdwPvfRFIhrgIdlbvym3eSBD4C%2FyxtxWr9E4lxyFPfW3a6fV%2B7kl1aPjaE85LgVBJ2EGQ4bm1jNwQ1WGIQo%2FYlKBRC5f%2BekV64pv0ol6BQRwOF%2B9mXHRPigu64LVRTgRQBhgrneLEa00GS%2Bur3Nt1yPmTVWvMIoO0%2FBNM0VbBXsbtePl5ozQ%3D%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20210713T123747Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3600&X-Amz-Credential=ASIA3EQYLGB7Q6I4XLIG%2F20210713%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Signature=853f1611536e57902b0fcabd36e7fbe77fe2278f40a0aeed4116953ea6ef4873" res.Header().Add("Cache-Control", "no-cache") diff --git a/version.go b/version.go index b3f855a..bdbe578 100644 --- a/version.go +++ b/version.go @@ -4,4 +4,4 @@ package request var commit string // VERSION is the version of this library -var VERSION = "0.9.14" + commit +var VERSION = "0.9.15" + commit