// +------------------------------------------------------------------------- // | 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 ( "fmt" "net/http" "net/url" "reflect" "regexp" "strconv" "strings" "time" "github.com/yunify/qingcloud-sdk-go/logger" "github.com/yunify/qingcloud-sdk-go/request/data" "github.com/yunify/qingcloud-sdk-go/utils" ) // Builder is the request builder for QingCloud service. type Builder struct { parsedURL string parsedForm url.Values parsedProperties *map[string]string parsedParams *map[string]string operation *data.Operation input *reflect.Value } // BuildHTTPRequest builds http request with an operation and an input. func (b *Builder) 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 *Builder) build() (*http.Request, error) { httpRequest, err := http.NewRequest(b.operation.RequestMethod, b.parsedURL, nil) httpRequest.Form = b.parsedForm if err != nil { return nil, err } if b.operation.RequestMethod == "POST" { httpRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded") } logger.Info(fmt.Sprintf( "Built QingCloud request: [%d] %s \n %s ", utils.StringToUnixInt(httpRequest.Header.Get("Date"), "RFC 822"), httpRequest.URL.String(), b.parsedForm)) return httpRequest, nil } func (b *Builder) parse() error { err := b.parseRequestProperties() if err != nil { return err } err = b.parseRequestParams() if err != nil { return err } err = b.parseRequestURL() if err != nil { return err } err = b.parseRequestForm() if err != nil { return err } return nil } func (b *Builder) parseRequestProperties() error { propertiesMap := map[string]string{} fields := reflect.ValueOf(b.operation.Properties).Elem() 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 { propertiesMap[fields.Type().Field(i).Tag.Get("name")] = strconv.Itoa(int(*value)) } } } b.parsedProperties = &propertiesMap return nil } func (b *Builder) parseRequestParams() error { var requestParams map[string]string if b.parsedParams != nil { requestParams = *b.parsedParams } else { requestParams = map[string]string{} } b.parsedParams = &requestParams requestParams["action"] = b.operation.APIName if !b.input.Elem().IsValid() { return nil } for i := 0; i < b.input.Elem().NumField(); i++ { tagName := b.input.Elem().Type().Field(i).Tag.Get("name") tagLocation := b.input.Elem().Type().Field(i).Tag.Get("location") tagDefault := b.input.Elem().Type().Field(i).Tag.Get("default") if tagName != "" && tagLocation != "" && requestParams != nil { switch value := b.input.Elem().Field(i).Interface().(type) { case *string: if tagDefault != "" { requestParams[tagName] = tagDefault } if value != nil { requestParams[tagName] = *value } case *int: if tagDefault != "" { requestParams[tagName] = tagDefault } if value != nil { requestParams[tagName] = strconv.Itoa(int(*value)) } case *bool: case *time.Time: if tagDefault != "" { requestParams[tagName] = tagDefault } if value != nil { format := b.input.Elem().Type().Field(i).Tag.Get("format") requestParams[tagName] = utils.TimeToString(*value, format) } case []*string: for index, item := range value { key := tagName + "." + strconv.Itoa(index+1) if tagDefault != "" { requestParams[tagName] = tagDefault } if item != nil { requestParams[key] = *item } } case []*int: for index, item := range value { key := tagName + "." + strconv.Itoa(index+1) if tagDefault != "" { requestParams[tagName] = tagDefault } if item != nil { requestParams[key] = strconv.Itoa(int(*item)) } } default: if value != nil { value = value.(interface{}) typeName := reflect.TypeOf(value.(interface{})).String() if strings.HasPrefix(typeName, "[]*") { valueArray := reflect.ValueOf(value) for i := 0; i < valueArray.Len(); i++ { item := valueArray.Index(i).Elem() for j := 0; j < item.NumField(); j++ { fieldTagName := item.Type().Field(j).Tag.Get("name") tagKey := tagName + "." + strconv.Itoa(i+1) + "." + fieldTagName switch fieldValue := item.Field(j).Interface().(type) { case *int: if fieldValue != nil { requestParams[tagKey] = strconv.Itoa(int(*fieldValue)) } case *string: if fieldValue != nil { requestParams[tagKey] = *fieldValue } case []*string: dst := make([]string, len(fieldValue)) for i := 0; i < len(fieldValue); i++ { if fieldValue[i] != nil { dst[i] = *(fieldValue[i]) } } if len(dst) != 0 { requestParams[tagKey] = strings.Join(dst, ",") } } } } } } } } } return nil } func (b *Builder) parseRequestURL() error { conf := b.operation.Config endpoint := conf.Protocol + "://" + conf.Host + ":" + strconv.Itoa(conf.Port) requestURI := regexp.MustCompile(`/+`).ReplaceAllString(conf.URI, "/") b.parsedURL = endpoint + requestURI if b.parsedParams != nil && b.operation.RequestMethod == "GET" { if _, ok := (*b.parsedParams)["zone"]; !ok { zone := (*b.parsedProperties)["zone"] if zone != "" { (*b.parsedParams)["zone"] = zone } } paramsParts := []string{} for key, value := range *b.parsedParams { paramsParts = append(paramsParts, fmt.Sprintf("%s=%s", key, url.QueryEscape(value))) } joined := strings.Join(paramsParts, "&") if joined != "" { b.parsedURL += "?" + joined } } return nil } func (b *Builder) parseRequestForm() error { if b.parsedParams != nil && b.operation.RequestMethod == "POST" { var values = make(url.Values) if _, ok := (*b.parsedParams)["zone"]; !ok { zone := (*b.parsedProperties)["zone"] if zone != "" { (*b.parsedParams)["zone"] = zone } } for key, value := range *b.parsedParams { values.Set(key, value) } b.parsedForm = values } return nil }