253 lines
5.5 KiB
Go
253 lines
5.5 KiB
Go
// +-------------------------------------------------------------------------
|
|
// | 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 {
|
|
r.Operation.Config.InitHTTPClient()
|
|
}
|
|
|
|
retries := r.Operation.Config.ConnectionRetries + 1
|
|
for {
|
|
if retries > 0 {
|
|
logger.Infof(nil, fmt.Sprintf(
|
|
"Sending request: [%d] %s %s",
|
|
convert.StringToTimestamp(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
|
|
}
|