build.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. // Package rest provides RESTful serialization of AWS requests and responses.
  2. package rest
  3. import (
  4. "bytes"
  5. "encoding/base64"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "net/url"
  10. "path"
  11. "reflect"
  12. "strconv"
  13. "strings"
  14. "time"
  15. "github.com/aws/aws-sdk-go/aws"
  16. "github.com/aws/aws-sdk-go/aws/awserr"
  17. "github.com/aws/aws-sdk-go/aws/request"
  18. )
  19. // RFC822 returns an RFC822 formatted timestamp for AWS protocols
  20. const RFC822 = "Mon, 2 Jan 2006 15:04:05 GMT"
  21. // Whether the byte value can be sent without escaping in AWS URLs
  22. var noEscape [256]bool
  23. var errValueNotSet = fmt.Errorf("value not set")
  24. func init() {
  25. for i := 0; i < len(noEscape); i++ {
  26. // AWS expects every character except these to be escaped
  27. noEscape[i] = (i >= 'A' && i <= 'Z') ||
  28. (i >= 'a' && i <= 'z') ||
  29. (i >= '0' && i <= '9') ||
  30. i == '-' ||
  31. i == '.' ||
  32. i == '_' ||
  33. i == '~'
  34. }
  35. }
  36. // BuildHandler is a named request handler for building rest protocol requests
  37. var BuildHandler = request.NamedHandler{Name: "awssdk.rest.Build", Fn: Build}
  38. // Build builds the REST component of a service request.
  39. func Build(r *request.Request) {
  40. if r.ParamsFilled() {
  41. v := reflect.ValueOf(r.Params).Elem()
  42. buildLocationElements(r, v)
  43. buildBody(r, v)
  44. }
  45. }
  46. func buildLocationElements(r *request.Request, v reflect.Value) {
  47. query := r.HTTPRequest.URL.Query()
  48. for i := 0; i < v.NumField(); i++ {
  49. m := v.Field(i)
  50. if n := v.Type().Field(i).Name; n[0:1] == strings.ToLower(n[0:1]) {
  51. continue
  52. }
  53. if m.IsValid() {
  54. field := v.Type().Field(i)
  55. name := field.Tag.Get("locationName")
  56. if name == "" {
  57. name = field.Name
  58. }
  59. if m.Kind() == reflect.Ptr {
  60. m = m.Elem()
  61. }
  62. if !m.IsValid() {
  63. continue
  64. }
  65. var err error
  66. switch field.Tag.Get("location") {
  67. case "headers": // header maps
  68. err = buildHeaderMap(&r.HTTPRequest.Header, m, field.Tag.Get("locationName"))
  69. case "header":
  70. err = buildHeader(&r.HTTPRequest.Header, m, name)
  71. case "uri":
  72. err = buildURI(r.HTTPRequest.URL, m, name)
  73. case "querystring":
  74. err = buildQueryString(query, m, name)
  75. }
  76. r.Error = err
  77. }
  78. if r.Error != nil {
  79. return
  80. }
  81. }
  82. r.HTTPRequest.URL.RawQuery = query.Encode()
  83. updatePath(r.HTTPRequest.URL, r.HTTPRequest.URL.Path, aws.BoolValue(r.Config.DisableRestProtocolURICleaning))
  84. }
  85. func buildBody(r *request.Request, v reflect.Value) {
  86. if field, ok := v.Type().FieldByName("_"); ok {
  87. if payloadName := field.Tag.Get("payload"); payloadName != "" {
  88. pfield, _ := v.Type().FieldByName(payloadName)
  89. if ptag := pfield.Tag.Get("type"); ptag != "" && ptag != "structure" {
  90. payload := reflect.Indirect(v.FieldByName(payloadName))
  91. if payload.IsValid() && payload.Interface() != nil {
  92. switch reader := payload.Interface().(type) {
  93. case io.ReadSeeker:
  94. r.SetReaderBody(reader)
  95. case []byte:
  96. r.SetBufferBody(reader)
  97. case string:
  98. r.SetStringBody(reader)
  99. default:
  100. r.Error = awserr.New("SerializationError",
  101. "failed to encode REST request",
  102. fmt.Errorf("unknown payload type %s", payload.Type()))
  103. }
  104. }
  105. }
  106. }
  107. }
  108. }
  109. func buildHeader(header *http.Header, v reflect.Value, name string) error {
  110. str, err := convertType(v)
  111. if err == errValueNotSet {
  112. return nil
  113. } else if err != nil {
  114. return awserr.New("SerializationError", "failed to encode REST request", err)
  115. }
  116. header.Add(name, str)
  117. return nil
  118. }
  119. func buildHeaderMap(header *http.Header, v reflect.Value, prefix string) error {
  120. for _, key := range v.MapKeys() {
  121. str, err := convertType(v.MapIndex(key))
  122. if err == errValueNotSet {
  123. continue
  124. } else if err != nil {
  125. return awserr.New("SerializationError", "failed to encode REST request", err)
  126. }
  127. header.Add(prefix+key.String(), str)
  128. }
  129. return nil
  130. }
  131. func buildURI(u *url.URL, v reflect.Value, name string) error {
  132. value, err := convertType(v)
  133. if err == errValueNotSet {
  134. return nil
  135. } else if err != nil {
  136. return awserr.New("SerializationError", "failed to encode REST request", err)
  137. }
  138. uri := u.Path
  139. uri = strings.Replace(uri, "{"+name+"}", EscapePath(value, true), -1)
  140. uri = strings.Replace(uri, "{"+name+"+}", EscapePath(value, false), -1)
  141. u.Path = uri
  142. return nil
  143. }
  144. func buildQueryString(query url.Values, v reflect.Value, name string) error {
  145. switch value := v.Interface().(type) {
  146. case []*string:
  147. for _, item := range value {
  148. query.Add(name, *item)
  149. }
  150. case map[string]*string:
  151. for key, item := range value {
  152. query.Add(key, *item)
  153. }
  154. case map[string][]*string:
  155. for key, items := range value {
  156. for _, item := range items {
  157. query.Add(key, *item)
  158. }
  159. }
  160. default:
  161. str, err := convertType(v)
  162. if err == errValueNotSet {
  163. return nil
  164. } else if err != nil {
  165. return awserr.New("SerializationError", "failed to encode REST request", err)
  166. }
  167. query.Set(name, str)
  168. }
  169. return nil
  170. }
  171. func updatePath(url *url.URL, urlPath string, disableRestProtocolURICleaning bool) {
  172. scheme, query := url.Scheme, url.RawQuery
  173. hasSlash := strings.HasSuffix(urlPath, "/")
  174. // clean up path
  175. if !disableRestProtocolURICleaning {
  176. urlPath = path.Clean(urlPath)
  177. }
  178. if hasSlash && !strings.HasSuffix(urlPath, "/") {
  179. urlPath += "/"
  180. }
  181. // get formatted URL minus scheme so we can build this into Opaque
  182. url.Scheme, url.Path, url.RawQuery = "", "", ""
  183. s := url.String()
  184. url.Scheme = scheme
  185. url.RawQuery = query
  186. // build opaque URI
  187. url.Opaque = s + urlPath
  188. }
  189. // EscapePath escapes part of a URL path in Amazon style
  190. func EscapePath(path string, encodeSep bool) string {
  191. var buf bytes.Buffer
  192. for i := 0; i < len(path); i++ {
  193. c := path[i]
  194. if noEscape[c] || (c == '/' && !encodeSep) {
  195. buf.WriteByte(c)
  196. } else {
  197. fmt.Fprintf(&buf, "%%%02X", c)
  198. }
  199. }
  200. return buf.String()
  201. }
  202. func convertType(v reflect.Value) (string, error) {
  203. v = reflect.Indirect(v)
  204. if !v.IsValid() {
  205. return "", errValueNotSet
  206. }
  207. var str string
  208. switch value := v.Interface().(type) {
  209. case string:
  210. str = value
  211. case []byte:
  212. str = base64.StdEncoding.EncodeToString(value)
  213. case bool:
  214. str = strconv.FormatBool(value)
  215. case int64:
  216. str = strconv.FormatInt(value, 10)
  217. case float64:
  218. str = strconv.FormatFloat(value, 'f', -1, 64)
  219. case time.Time:
  220. str = value.UTC().Format(RFC822)
  221. default:
  222. err := fmt.Errorf("Unsupported value for param %v (%s)", v.Interface(), v.Type())
  223. return "", err
  224. }
  225. return str, nil
  226. }