build.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  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. "github.com/aws/aws-sdk-go/private/protocol"
  19. )
  20. // RFC822 returns an RFC822 formatted timestamp for AWS protocols
  21. const RFC822 = "Mon, 2 Jan 2006 15:04:05 GMT"
  22. // Whether the byte value can be sent without escaping in AWS URLs
  23. var noEscape [256]bool
  24. var errValueNotSet = fmt.Errorf("value not set")
  25. func init() {
  26. for i := 0; i < len(noEscape); i++ {
  27. // AWS expects every character except these to be escaped
  28. noEscape[i] = (i >= 'A' && i <= 'Z') ||
  29. (i >= 'a' && i <= 'z') ||
  30. (i >= '0' && i <= '9') ||
  31. i == '-' ||
  32. i == '.' ||
  33. i == '_' ||
  34. i == '~'
  35. }
  36. }
  37. // BuildHandler is a named request handler for building rest protocol requests
  38. var BuildHandler = request.NamedHandler{Name: "awssdk.rest.Build", Fn: Build}
  39. // Build builds the REST component of a service request.
  40. func Build(r *request.Request) {
  41. if r.ParamsFilled() {
  42. v := reflect.ValueOf(r.Params).Elem()
  43. buildLocationElements(r, v, false)
  44. buildBody(r, v)
  45. }
  46. }
  47. // BuildAsGET builds the REST component of a service request with the ability to hoist
  48. // data from the body.
  49. func BuildAsGET(r *request.Request) {
  50. if r.ParamsFilled() {
  51. v := reflect.ValueOf(r.Params).Elem()
  52. buildLocationElements(r, v, true)
  53. buildBody(r, v)
  54. }
  55. }
  56. func buildLocationElements(r *request.Request, v reflect.Value, buildGETQuery bool) {
  57. query := r.HTTPRequest.URL.Query()
  58. // Setup the raw path to match the base path pattern. This is needed
  59. // so that when the path is mutated a custom escaped version can be
  60. // stored in RawPath that will be used by the Go client.
  61. r.HTTPRequest.URL.RawPath = r.HTTPRequest.URL.Path
  62. for i := 0; i < v.NumField(); i++ {
  63. m := v.Field(i)
  64. if n := v.Type().Field(i).Name; n[0:1] == strings.ToLower(n[0:1]) {
  65. continue
  66. }
  67. if m.IsValid() {
  68. field := v.Type().Field(i)
  69. name := field.Tag.Get("locationName")
  70. if name == "" {
  71. name = field.Name
  72. }
  73. if kind := m.Kind(); kind == reflect.Ptr {
  74. m = m.Elem()
  75. } else if kind == reflect.Interface {
  76. if !m.Elem().IsValid() {
  77. continue
  78. }
  79. }
  80. if !m.IsValid() {
  81. continue
  82. }
  83. if field.Tag.Get("ignore") != "" {
  84. continue
  85. }
  86. var err error
  87. switch field.Tag.Get("location") {
  88. case "headers": // header maps
  89. err = buildHeaderMap(&r.HTTPRequest.Header, m, field.Tag)
  90. case "header":
  91. err = buildHeader(&r.HTTPRequest.Header, m, name, field.Tag)
  92. case "uri":
  93. err = buildURI(r.HTTPRequest.URL, m, name, field.Tag)
  94. case "querystring":
  95. err = buildQueryString(query, m, name, field.Tag)
  96. default:
  97. if buildGETQuery {
  98. err = buildQueryString(query, m, name, field.Tag)
  99. }
  100. }
  101. r.Error = err
  102. }
  103. if r.Error != nil {
  104. return
  105. }
  106. }
  107. r.HTTPRequest.URL.RawQuery = query.Encode()
  108. if !aws.BoolValue(r.Config.DisableRestProtocolURICleaning) {
  109. cleanPath(r.HTTPRequest.URL)
  110. }
  111. }
  112. func buildBody(r *request.Request, v reflect.Value) {
  113. if field, ok := v.Type().FieldByName("_"); ok {
  114. if payloadName := field.Tag.Get("payload"); payloadName != "" {
  115. pfield, _ := v.Type().FieldByName(payloadName)
  116. if ptag := pfield.Tag.Get("type"); ptag != "" && ptag != "structure" {
  117. payload := reflect.Indirect(v.FieldByName(payloadName))
  118. if payload.IsValid() && payload.Interface() != nil {
  119. switch reader := payload.Interface().(type) {
  120. case io.ReadSeeker:
  121. r.SetReaderBody(reader)
  122. case []byte:
  123. r.SetBufferBody(reader)
  124. case string:
  125. r.SetStringBody(reader)
  126. default:
  127. r.Error = awserr.New("SerializationError",
  128. "failed to encode REST request",
  129. fmt.Errorf("unknown payload type %s", payload.Type()))
  130. }
  131. }
  132. }
  133. }
  134. }
  135. }
  136. func buildHeader(header *http.Header, v reflect.Value, name string, tag reflect.StructTag) error {
  137. str, err := convertType(v, tag)
  138. if err == errValueNotSet {
  139. return nil
  140. } else if err != nil {
  141. return awserr.New("SerializationError", "failed to encode REST request", err)
  142. }
  143. header.Add(name, str)
  144. return nil
  145. }
  146. func buildHeaderMap(header *http.Header, v reflect.Value, tag reflect.StructTag) error {
  147. prefix := tag.Get("locationName")
  148. for _, key := range v.MapKeys() {
  149. str, err := convertType(v.MapIndex(key), tag)
  150. if err == errValueNotSet {
  151. continue
  152. } else if err != nil {
  153. return awserr.New("SerializationError", "failed to encode REST request", err)
  154. }
  155. header.Add(prefix+key.String(), str)
  156. }
  157. return nil
  158. }
  159. func buildURI(u *url.URL, v reflect.Value, name string, tag reflect.StructTag) error {
  160. value, err := convertType(v, tag)
  161. if err == errValueNotSet {
  162. return nil
  163. } else if err != nil {
  164. return awserr.New("SerializationError", "failed to encode REST request", err)
  165. }
  166. u.Path = strings.Replace(u.Path, "{"+name+"}", value, -1)
  167. u.Path = strings.Replace(u.Path, "{"+name+"+}", value, -1)
  168. u.RawPath = strings.Replace(u.RawPath, "{"+name+"}", EscapePath(value, true), -1)
  169. u.RawPath = strings.Replace(u.RawPath, "{"+name+"+}", EscapePath(value, false), -1)
  170. return nil
  171. }
  172. func buildQueryString(query url.Values, v reflect.Value, name string, tag reflect.StructTag) error {
  173. switch value := v.Interface().(type) {
  174. case []*string:
  175. for _, item := range value {
  176. query.Add(name, *item)
  177. }
  178. case map[string]*string:
  179. for key, item := range value {
  180. query.Add(key, *item)
  181. }
  182. case map[string][]*string:
  183. for key, items := range value {
  184. for _, item := range items {
  185. query.Add(key, *item)
  186. }
  187. }
  188. default:
  189. str, err := convertType(v, tag)
  190. if err == errValueNotSet {
  191. return nil
  192. } else if err != nil {
  193. return awserr.New("SerializationError", "failed to encode REST request", err)
  194. }
  195. query.Set(name, str)
  196. }
  197. return nil
  198. }
  199. func cleanPath(u *url.URL) {
  200. hasSlash := strings.HasSuffix(u.Path, "/")
  201. // clean up path, removing duplicate `/`
  202. u.Path = path.Clean(u.Path)
  203. u.RawPath = path.Clean(u.RawPath)
  204. if hasSlash && !strings.HasSuffix(u.Path, "/") {
  205. u.Path += "/"
  206. u.RawPath += "/"
  207. }
  208. }
  209. // EscapePath escapes part of a URL path in Amazon style
  210. func EscapePath(path string, encodeSep bool) string {
  211. var buf bytes.Buffer
  212. for i := 0; i < len(path); i++ {
  213. c := path[i]
  214. if noEscape[c] || (c == '/' && !encodeSep) {
  215. buf.WriteByte(c)
  216. } else {
  217. fmt.Fprintf(&buf, "%%%02X", c)
  218. }
  219. }
  220. return buf.String()
  221. }
  222. func convertType(v reflect.Value, tag reflect.StructTag) (str string, err error) {
  223. v = reflect.Indirect(v)
  224. if !v.IsValid() {
  225. return "", errValueNotSet
  226. }
  227. switch value := v.Interface().(type) {
  228. case string:
  229. str = value
  230. case []byte:
  231. str = base64.StdEncoding.EncodeToString(value)
  232. case bool:
  233. str = strconv.FormatBool(value)
  234. case int64:
  235. str = strconv.FormatInt(value, 10)
  236. case float64:
  237. str = strconv.FormatFloat(value, 'f', -1, 64)
  238. case time.Time:
  239. str = value.UTC().Format(RFC822)
  240. case aws.JSONValue:
  241. if len(value) == 0 {
  242. return "", errValueNotSet
  243. }
  244. escaping := protocol.NoEscape
  245. if tag.Get("location") == "header" {
  246. escaping = protocol.Base64Escape
  247. }
  248. str, err = protocol.EncodeJSONValue(value, escaping)
  249. if err != nil {
  250. return "", fmt.Errorf("unable to encode JSONValue, %v", err)
  251. }
  252. default:
  253. err := fmt.Errorf("unsupported value for param %v (%s)", v.Interface(), v.Type())
  254. return "", err
  255. }
  256. return str, nil
  257. }