303 lines
7.3 KiB
Go
303 lines
7.3 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 builder
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
|
|
"github.com/pengsrc/go-shared/convert"
|
|
"github.com/pengsrc/go-shared/json"
|
|
|
|
"github.com/yunify/qingstor-sdk-go/request/data"
|
|
"github.com/yunify/qingstor-sdk-go/utils"
|
|
)
|
|
|
|
// BaseBuilder is the base builder for all services.
|
|
type BaseBuilder struct {
|
|
parsedURL string
|
|
parsedProperties *map[string]string
|
|
parsedParams *map[string]string
|
|
parsedHeaders *map[string]string
|
|
parsedBodyString string
|
|
parsedBody io.Reader
|
|
|
|
operation *data.Operation
|
|
input *reflect.Value
|
|
}
|
|
|
|
// BuildHTTPRequest builds http request with an operation and an input.
|
|
func (b *BaseBuilder) BuildHTTPRequest(o *data.Operation, i *reflect.Value) (*http.Request, error) {
|
|
b.operation = o
|
|
b.input = i
|
|
|
|
_, err := b.parse()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return b.build()
|
|
}
|
|
|
|
func (b *BaseBuilder) build() (*http.Request, error) {
|
|
httpRequest, err := http.NewRequest(b.operation.RequestMethod, b.parsedURL, b.parsedBody)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = b.setupHeaders(httpRequest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return httpRequest, nil
|
|
}
|
|
|
|
func (b *BaseBuilder) parse() (*BaseBuilder, error) {
|
|
err := b.parseRequestParamsAndHeaders()
|
|
if err != nil {
|
|
return b, err
|
|
}
|
|
err = b.parseRequestBody()
|
|
if err != nil {
|
|
return b, err
|
|
}
|
|
err = b.parseRequestProperties()
|
|
if err != nil {
|
|
return b, err
|
|
}
|
|
err = b.parseRequestURL()
|
|
if err != nil {
|
|
return b, err
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
func (b *BaseBuilder) parseRequestParamsAndHeaders() error {
|
|
requestParams := map[string]string{}
|
|
requestHeaders := map[string]string{}
|
|
maps := map[string](map[string]string){
|
|
"params": requestParams,
|
|
"headers": requestHeaders,
|
|
}
|
|
|
|
b.parsedParams = &requestParams
|
|
b.parsedHeaders = &requestHeaders
|
|
|
|
if !b.input.IsValid() {
|
|
return nil
|
|
}
|
|
|
|
fields := b.input.Elem()
|
|
if !fields.IsValid() {
|
|
return nil
|
|
}
|
|
|
|
for i := 0; i < fields.NumField(); i++ {
|
|
tagName := fields.Type().Field(i).Tag.Get("name")
|
|
tagLocation := fields.Type().Field(i).Tag.Get("location")
|
|
if tagDefault := fields.Type().Field(i).Tag.Get("default"); tagDefault != "" {
|
|
maps[tagLocation][tagName] = tagDefault
|
|
}
|
|
if tagName != "" && tagLocation != "" && maps[tagLocation] != nil {
|
|
switch value := fields.Field(i).Interface().(type) {
|
|
case *string:
|
|
if value != nil {
|
|
maps[tagLocation][tagName] = *value
|
|
}
|
|
case *int:
|
|
if value != nil {
|
|
maps[tagLocation][tagName] = strconv.Itoa(int(*value))
|
|
}
|
|
case *int64:
|
|
if value != nil {
|
|
maps[tagLocation][tagName] = strconv.FormatInt(int64(*value), 10)
|
|
}
|
|
case *bool:
|
|
case *time.Time:
|
|
if value != nil {
|
|
formatString := fields.Type().Field(i).Tag.Get("format")
|
|
format := ""
|
|
switch formatString {
|
|
case "RFC 822":
|
|
format = convert.RFC822
|
|
case "ISO 8601":
|
|
format = convert.ISO8601
|
|
}
|
|
maps[tagLocation][tagName] = convert.TimeToString(*value, format)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *BaseBuilder) parseRequestBody() error {
|
|
requestData := map[string]interface{}{}
|
|
|
|
if !b.input.IsValid() {
|
|
return nil
|
|
}
|
|
|
|
fields := b.input.Elem()
|
|
if !fields.IsValid() {
|
|
return nil
|
|
}
|
|
|
|
for i := 0; i < fields.NumField(); i++ {
|
|
location := fields.Type().Field(i).Tag.Get("location")
|
|
if location == "elements" {
|
|
name := fields.Type().Field(i).Tag.Get("name")
|
|
requestData[name] = fields.Field(i).Interface()
|
|
}
|
|
}
|
|
|
|
if len(requestData) != 0 {
|
|
dataValue, err := json.Encode(requestData, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b.parsedBodyString = string(dataValue)
|
|
b.parsedBody = strings.NewReader(b.parsedBodyString)
|
|
(*b.parsedHeaders)["Content-Type"] = "application/json"
|
|
} else {
|
|
value := fields.FieldByName("Body")
|
|
if value.IsValid() {
|
|
switch value.Interface().(type) {
|
|
case string:
|
|
if value.String() != "" {
|
|
b.parsedBodyString = value.String()
|
|
b.parsedBody = strings.NewReader(value.String())
|
|
}
|
|
case io.Reader:
|
|
if value.Interface().(io.Reader) != nil {
|
|
b.parsedBody = value.Interface().(io.Reader)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *BaseBuilder) parseRequestProperties() error {
|
|
propertiesMap := map[string]string{}
|
|
b.parsedProperties = &propertiesMap
|
|
|
|
if b.operation.Properties != nil {
|
|
fields := reflect.ValueOf(b.operation.Properties).Elem()
|
|
if fields.IsValid() {
|
|
for i := 0; i < fields.NumField(); i++ {
|
|
switch value := fields.Field(i).Interface().(type) {
|
|
case *string:
|
|
if value != nil {
|
|
propertiesMap[fields.Type().Field(i).Tag.Get("name")] = *value
|
|
}
|
|
case *int:
|
|
if value != nil {
|
|
numberString := strconv.Itoa(int(*value))
|
|
propertiesMap[fields.Type().Field(i).Tag.Get("name")] = numberString
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *BaseBuilder) parseRequestURL() error {
|
|
return nil
|
|
}
|
|
|
|
func (b *BaseBuilder) setupHeaders(httpRequest *http.Request) error {
|
|
if b.parsedHeaders != nil {
|
|
|
|
for headerKey, headerValue := range *b.parsedHeaders {
|
|
if headerKey == "X-QS-Fetch-Source" {
|
|
// header X-QS-Fetch-Source is a URL to fetch.
|
|
// We should first parse this URL.
|
|
requestURL, err := url.Parse(headerValue)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid HTTP header value: %s", headerValue)
|
|
}
|
|
headerValue = requestURL.String()
|
|
} else {
|
|
for _, r := range headerValue {
|
|
if r > unicode.MaxASCII {
|
|
headerValue = utils.URLQueryEscape(headerValue)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
httpRequest.Header.Set(headerKey, headerValue)
|
|
}
|
|
}
|
|
|
|
if httpRequest.Header.Get("Content-Length") == "" {
|
|
var length int64
|
|
switch body := b.parsedBody.(type) {
|
|
case nil:
|
|
length = 0
|
|
case io.Seeker:
|
|
//start, err := body.Seek(0, io.SeekStart)
|
|
start, err := body.Seek(0, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
//end, err := body.Seek(0, io.SeekEnd)
|
|
end, err := body.Seek(0, 2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
//body.Seek(0, io.SeekStart)
|
|
body.Seek(0, 0)
|
|
length = end - start
|
|
default:
|
|
return errors.New("Can not get Content-Length")
|
|
}
|
|
if length > 0 {
|
|
httpRequest.ContentLength = length
|
|
httpRequest.Header.Set("Content-Length", strconv.Itoa(int(length)))
|
|
} else {
|
|
httpRequest.Header.Set("Content-Length", "0")
|
|
}
|
|
}
|
|
length, err := strconv.Atoi(httpRequest.Header.Get("Content-Length"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
httpRequest.ContentLength = int64(length)
|
|
|
|
if httpRequest.Header.Get("Date") == "" {
|
|
httpRequest.Header.Set("Date", convert.TimeToString(time.Now(), convert.RFC822))
|
|
}
|
|
|
|
return nil
|
|
}
|