// +------------------------------------------------------------------------- // | Copyright (C) 2016 Yunify, Inc. // +------------------------------------------------------------------------- // | Licensed under the Apache License, Version 2.0 (the "License"); // | you may not use this work except in compliance with the License. // | You may obtain a copy of the License in the LICENSE file, or at: // | // | http://www.apache.org/licenses/LICENSE-2.0 // | // | Unless required by applicable law or agreed to in writing, software // | distributed under the License is distributed on an "AS IS" BASIS, // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // | See the License for the specific language governing permissions and // | limitations under the License. // +------------------------------------------------------------------------- package request import ( "errors" "fmt" "net/http" "reflect" "strconv" "time" "github.com/pengsrc/go-shared/convert" "github.com/yunify/qingstor-sdk-go/logger" "github.com/yunify/qingstor-sdk-go/request/builder" "github.com/yunify/qingstor-sdk-go/request/data" "github.com/yunify/qingstor-sdk-go/request/signer" "github.com/yunify/qingstor-sdk-go/request/unpacker" ) // A Request can build, sign, send and unpack API request. type Request struct { Operation *data.Operation Input *reflect.Value Output *reflect.Value HTTPRequest *http.Request HTTPResponse *http.Response } // New create a Request from given Operation, Input and Output. // It returns a Request. func New(o *data.Operation, i data.Input, x interface{}) (*Request, error) { input := reflect.ValueOf(i) if input.IsValid() && input.Elem().IsValid() { err := i.Validate() if err != nil { return nil, err } } output := reflect.ValueOf(x) return &Request{ Operation: o, Input: &input, Output: &output, }, nil } // Send sends API request. // It returns error if error occurred. func (r *Request) Send() error { err := r.Build() if err != nil { return err } err = r.Sign() if err != nil { return err } err = r.Do() if err != nil { return err } return nil } // Build checks and builds the API request. // It returns error if error occurred. func (r *Request) Build() error { err := r.check() if err != nil { return err } err = r.build() if err != nil { return err } return nil } // Do sends and unpacks the API request. // It returns error if error occurred. func (r *Request) Do() error { err := r.send() if err != nil { return err } err = r.unpack() if err != nil { return err } return nil } // Sign sign the API request by setting the authorization header. // It returns error if error occurred. func (r *Request) Sign() error { err := r.sign() if err != nil { return err } return nil } // SignQuery sign the API request by appending query string. // It returns error if error occurred. func (r *Request) SignQuery(timeoutSeconds int) error { err := r.signQuery(int(time.Now().Unix()) + timeoutSeconds) if err != nil { return err } return nil } // ApplySignature applies the Authorization header. // It returns error if error occurred. func (r *Request) ApplySignature(authorization string) error { r.HTTPRequest.Header.Set("Authorization", authorization) return nil } // ApplyQuerySignature applies the query signature. // It returns error if error occurred. func (r *Request) ApplyQuerySignature(accessKeyID string, expires int, signature string) error { queryValue := r.HTTPRequest.URL.Query() queryValue.Set("access_key_id", accessKeyID) queryValue.Set("expires", strconv.Itoa(expires)) queryValue.Set("signature", signature) r.HTTPRequest.URL.RawQuery = queryValue.Encode() return nil } func (r *Request) check() error { if r.Operation.Config.AccessKeyID == "" { return errors.New("access key not provided") } if r.Operation.Config.SecretAccessKey == "" { return errors.New("secret access key not provided") } return nil } func (r *Request) build() error { b := &builder.QingStorBuilder{} httpRequest, err := b.BuildHTTPRequest(r.Operation, r.Input) if err != nil { return err } r.HTTPRequest = httpRequest return nil } func (r *Request) sign() error { s := &signer.QingStorSigner{ AccessKeyID: r.Operation.Config.AccessKeyID, SecretAccessKey: r.Operation.Config.SecretAccessKey, } err := s.WriteSignature(r.HTTPRequest) if err != nil { return err } return nil } func (r *Request) signQuery(expires int) error { s := &signer.QingStorSigner{ AccessKeyID: r.Operation.Config.AccessKeyID, SecretAccessKey: r.Operation.Config.SecretAccessKey, } err := s.WriteQuerySignature(r.HTTPRequest, expires) if err != nil { return err } return nil } func (r *Request) send() error { var response *http.Response var err error if r.Operation.Config.Connection == nil { return errors.New("connection not initialized") } retries := r.Operation.Config.ConnectionRetries + 1 for { if retries > 0 { logger.Infof(nil, fmt.Sprintf( "Sending request: [%d] %s %s", convert.StringToUnixTimestamp(r.HTTPRequest.Header.Get("Date"), convert.RFC822), r.Operation.RequestMethod, r.HTTPRequest.Host, )) response, err = r.Operation.Config.Connection.Do(r.HTTPRequest) if err == nil { retries = 0 } else { retries-- time.Sleep(time.Second) } } else { break } } if err != nil { return err } r.HTTPResponse = response return nil } func (r *Request) unpack() error { u := &unpacker.QingStorUnpacker{} err := u.UnpackHTTPRequest(r.Operation, r.HTTPResponse, r.Output) if err != nil { return err } return nil }