| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- package request
- import (
- "bytes"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "reflect"
- "strings"
- "time"
- "github.com/aws/aws-sdk-go/aws"
- "github.com/aws/aws-sdk-go/aws/awserr"
- "github.com/aws/aws-sdk-go/aws/client/metadata"
- )
- // A Request is the service request to be made.
- type Request struct {
- Config aws.Config
- ClientInfo metadata.ClientInfo
- Handlers Handlers
- Retryer
- Time time.Time
- ExpireTime time.Duration
- Operation *Operation
- HTTPRequest *http.Request
- HTTPResponse *http.Response
- Body io.ReadSeeker
- BodyStart int64 // offset from beginning of Body that the request body starts
- Params interface{}
- Error error
- Data interface{}
- RequestID string
- RetryCount int
- Retryable *bool
- RetryDelay time.Duration
- NotHoist bool
- SignedHeaderVals http.Header
- LastSignedAt time.Time
- built bool
- // Need to persist an intermideant body betweend the input Body and HTTP
- // request body because the HTTP Client's transport can maintain a reference
- // to the HTTP request's body after the client has returned. This value is
- // safe to use concurrently and rewraps the input Body for each HTTP request.
- safeBody *offsetReader
- }
- // An Operation is the service API operation to be made.
- type Operation struct {
- Name string
- HTTPMethod string
- HTTPPath string
- *Paginator
- }
- // Paginator keeps track of pagination configuration for an API operation.
- type Paginator struct {
- InputTokens []string
- OutputTokens []string
- LimitToken string
- TruncationToken string
- }
- // New returns a new Request pointer for the service API
- // operation and parameters.
- //
- // Params is any value of input parameters to be the request payload.
- // Data is pointer value to an object which the request's response
- // payload will be deserialized to.
- func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers,
- retryer Retryer, operation *Operation, params interface{}, data interface{}) *Request {
- method := operation.HTTPMethod
- if method == "" {
- method = "POST"
- }
- httpReq, _ := http.NewRequest(method, "", nil)
- var err error
- httpReq.URL, err = url.Parse(clientInfo.Endpoint + operation.HTTPPath)
- if err != nil {
- httpReq.URL = &url.URL{}
- err = awserr.New("InvalidEndpointURL", "invalid endpoint uri", err)
- }
- r := &Request{
- Config: cfg,
- ClientInfo: clientInfo,
- Handlers: handlers.Copy(),
- Retryer: retryer,
- Time: time.Now(),
- ExpireTime: 0,
- Operation: operation,
- HTTPRequest: httpReq,
- Body: nil,
- Params: params,
- Error: err,
- Data: data,
- }
- r.SetBufferBody([]byte{})
- return r
- }
- // WillRetry returns if the request's can be retried.
- func (r *Request) WillRetry() bool {
- return r.Error != nil && aws.BoolValue(r.Retryable) && r.RetryCount < r.MaxRetries()
- }
- // ParamsFilled returns if the request's parameters have been populated
- // and the parameters are valid. False is returned if no parameters are
- // provided or invalid.
- func (r *Request) ParamsFilled() bool {
- return r.Params != nil && reflect.ValueOf(r.Params).Elem().IsValid()
- }
- // DataFilled returns true if the request's data for response deserialization
- // target has been set and is a valid. False is returned if data is not
- // set, or is invalid.
- func (r *Request) DataFilled() bool {
- return r.Data != nil && reflect.ValueOf(r.Data).Elem().IsValid()
- }
- // SetBufferBody will set the request's body bytes that will be sent to
- // the service API.
- func (r *Request) SetBufferBody(buf []byte) {
- r.SetReaderBody(bytes.NewReader(buf))
- }
- // SetStringBody sets the body of the request to be backed by a string.
- func (r *Request) SetStringBody(s string) {
- r.SetReaderBody(strings.NewReader(s))
- }
- // SetReaderBody will set the request's body reader.
- func (r *Request) SetReaderBody(reader io.ReadSeeker) {
- r.Body = reader
- r.ResetBody()
- }
- // Presign returns the request's signed URL. Error will be returned
- // if the signing fails.
- func (r *Request) Presign(expireTime time.Duration) (string, error) {
- r.ExpireTime = expireTime
- r.NotHoist = false
- r.Sign()
- if r.Error != nil {
- return "", r.Error
- }
- return r.HTTPRequest.URL.String(), nil
- }
- // PresignRequest behaves just like presign, but hoists all headers and signs them.
- // Also returns the signed hash back to the user
- func (r *Request) PresignRequest(expireTime time.Duration) (string, http.Header, error) {
- r.ExpireTime = expireTime
- r.NotHoist = true
- r.Sign()
- if r.Error != nil {
- return "", nil, r.Error
- }
- return r.HTTPRequest.URL.String(), r.SignedHeaderVals, nil
- }
- func debugLogReqError(r *Request, stage string, retrying bool, err error) {
- if !r.Config.LogLevel.Matches(aws.LogDebugWithRequestErrors) {
- return
- }
- retryStr := "not retrying"
- if retrying {
- retryStr = "will retry"
- }
- r.Config.Logger.Log(fmt.Sprintf("DEBUG: %s %s/%s failed, %s, error %v",
- stage, r.ClientInfo.ServiceName, r.Operation.Name, retryStr, err))
- }
- // Build will build the request's object so it can be signed and sent
- // to the service. Build will also validate all the request's parameters.
- // Anny additional build Handlers set on this request will be run
- // in the order they were set.
- //
- // The request will only be built once. Multiple calls to build will have
- // no effect.
- //
- // If any Validate or Build errors occur the build will stop and the error
- // which occurred will be returned.
- func (r *Request) Build() error {
- if !r.built {
- r.Handlers.Validate.Run(r)
- if r.Error != nil {
- debugLogReqError(r, "Validate Request", false, r.Error)
- return r.Error
- }
- r.Handlers.Build.Run(r)
- if r.Error != nil {
- debugLogReqError(r, "Build Request", false, r.Error)
- return r.Error
- }
- r.built = true
- }
- return r.Error
- }
- // Sign will sign the request returning error if errors are encountered.
- //
- // Send will build the request prior to signing. All Sign Handlers will
- // be executed in the order they were set.
- func (r *Request) Sign() error {
- r.Build()
- if r.Error != nil {
- debugLogReqError(r, "Build Request", false, r.Error)
- return r.Error
- }
- r.Handlers.Sign.Run(r)
- return r.Error
- }
- // ResetBody rewinds the request body backto its starting position, and
- // set's the HTTP Request body reference. When the body is read prior
- // to being sent in the HTTP request it will need to be rewound.
- func (r *Request) ResetBody() {
- if r.safeBody != nil {
- r.safeBody.Close()
- }
- r.safeBody = newOffsetReader(r.Body, r.BodyStart)
- r.HTTPRequest.Body = r.safeBody
- }
- // GetBody will return an io.ReadSeeker of the Request's underlying
- // input body with a concurrency safe wrapper.
- func (r *Request) GetBody() io.ReadSeeker {
- return r.safeBody
- }
- // Send will send the request returning error if errors are encountered.
- //
- // Send will sign the request prior to sending. All Send Handlers will
- // be executed in the order they were set.
- //
- // Canceling a request is non-deterministic. If a request has been canceled,
- // then the transport will choose, randomly, one of the state channels during
- // reads or getting the connection.
- //
- // readLoop() and getConn(req *Request, cm connectMethod)
- // https://github.com/golang/go/blob/master/src/net/http/transport.go
- //
- // Send will not close the request.Request's body.
- func (r *Request) Send() error {
- for {
- if aws.BoolValue(r.Retryable) {
- if r.Config.LogLevel.Matches(aws.LogDebugWithRequestRetries) {
- r.Config.Logger.Log(fmt.Sprintf("DEBUG: Retrying Request %s/%s, attempt %d",
- r.ClientInfo.ServiceName, r.Operation.Name, r.RetryCount))
- }
- // The previous http.Request will have a reference to the r.Body
- // and the HTTP Client's Transport may still be reading from
- // the request's body even though the Client's Do returned.
- r.HTTPRequest = copyHTTPRequest(r.HTTPRequest, nil)
- r.ResetBody()
- // Closing response body to ensure that no response body is leaked
- // between retry attempts.
- if r.HTTPResponse != nil && r.HTTPResponse.Body != nil {
- r.HTTPResponse.Body.Close()
- }
- }
- r.Sign()
- if r.Error != nil {
- return r.Error
- }
- r.Retryable = nil
- r.Handlers.Send.Run(r)
- if r.Error != nil {
- if strings.Contains(r.Error.Error(), "net/http: request canceled") {
- return r.Error
- }
- err := r.Error
- r.Handlers.Retry.Run(r)
- r.Handlers.AfterRetry.Run(r)
- if r.Error != nil {
- debugLogReqError(r, "Send Request", false, r.Error)
- return r.Error
- }
- debugLogReqError(r, "Send Request", true, err)
- continue
- }
- r.Handlers.UnmarshalMeta.Run(r)
- r.Handlers.ValidateResponse.Run(r)
- if r.Error != nil {
- err := r.Error
- r.Handlers.UnmarshalError.Run(r)
- r.Handlers.Retry.Run(r)
- r.Handlers.AfterRetry.Run(r)
- if r.Error != nil {
- debugLogReqError(r, "Validate Response", false, r.Error)
- return r.Error
- }
- debugLogReqError(r, "Validate Response", true, err)
- continue
- }
- r.Handlers.Unmarshal.Run(r)
- if r.Error != nil {
- err := r.Error
- r.Handlers.Retry.Run(r)
- r.Handlers.AfterRetry.Run(r)
- if r.Error != nil {
- debugLogReqError(r, "Unmarshal Response", false, r.Error)
- return r.Error
- }
- debugLogReqError(r, "Unmarshal Response", true, err)
- continue
- }
- break
- }
- return nil
- }
- // AddToUserAgent adds the string to the end of the request's current user agent.
- func AddToUserAgent(r *Request, s string) {
- curUA := r.HTTPRequest.Header.Get("User-Agent")
- if len(curUA) > 0 {
- s = curUA + " " + s
- }
- r.HTTPRequest.Header.Set("User-Agent", s)
- }
|