Skip to content
This repository
Browse code

Refactor query parameter serialization

  • Loading branch information...
commit 80ecefbc9dea9ad26326a6ddba966ab9a8b1e114 1 parent 33db812
Loren Segal authored March 18, 2013
62  lib/service_interface/query.js
@@ -28,9 +28,10 @@ AWS.ServiceInterface.Query = {
28 28
     httpRequest.path = '/';
29 29
     httpRequest.headers['Content-Type'] =
30 30
       'application/x-www-form-urlencoded; charset=utf-8';
31  
-    httpRequest.params = new AWS.QueryParamList();
32  
-    httpRequest.params.add('Version', req.client.api.apiVersion);
33  
-    httpRequest.params.add('Action', operation.name);
  31
+    httpRequest.params = {
  32
+      Version: req.client.api.apiVersion,
  33
+      Action: operation.name
  34
+    };
34 35
 
35 36
     // convert the request parameters into a list of query params,
36 37
     // e.g. Deeply.NestedParam.0.Name=value
@@ -38,9 +39,9 @@ AWS.ServiceInterface.Query = {
38 39
     if (rules) rules = rules.members;
39 40
     var builder = new AWS.QueryParamSerializer(rules);
40 41
     builder.serialize(req.params, function(name, value) {
41  
-      httpRequest.params.add(name, value);
  42
+      httpRequest.params[name] = value;
42 43
     });
43  
-    httpRequest.body = httpRequest.params.toString();
  44
+    httpRequest.body = AWS.util.queryParamsToString(httpRequest.params);
44 45
   },
45 46
 
46 47
   extractError: function extractError(resp) {
@@ -108,57 +109,6 @@ AWS.ServiceInterface.Query = {
108 109
 /**
109 110
  * @api private
110 111
  */
111  
-AWS.QueryParamList = inherit({
112  
-
113  
-  constructor: function QueryParamList() {
114  
-    this.params = {};
115  
-  },
116  
-
117  
-  add: function add(name, value) {
118  
-    this.params[name] = new AWS.QueryParam(name, value);
119  
-  },
120  
-
121  
-  sortedParams: function sortedParams() {
122  
-    return AWS.util.values(this.params).sort(function (p1, p2) {
123  
-      return p1.name < p2.name ? -1 : 1;
124  
-    });
125  
-  },
126  
-
127  
-  toString: function toString() {
128  
-    var params = [];
129  
-    AWS.util.arrayEach(this.sortedParams(), function (param) {
130  
-      params.push(param.toString());
131  
-    });
132  
-    return params.join('&');
133  
-  }
134  
-
135  
-});
136  
-
137  
-/**
138  
- * @api private
139  
- */
140  
-AWS.QueryParam = inherit({
141  
-
142  
-  constructor: function QueryParam(name, value) {
143  
-    this.name = name;
144  
-    this.value = value;
145  
-  },
146  
-
147  
-  encode: function encode(string) {
148  
-    return AWS.util.uriEscape(string);
149  
-  },
150  
-
151  
-  toString: function toString() {
152  
-    return this.value !== undefined && this.value !== null ?
153  
-      this.encode(this.name) + '=' + this.encode(this.value) :
154  
-      this.encode(this.name);
155  
-  }
156  
-
157  
-});
158  
-
159  
-/**
160  
- * @api private
161  
- */
162 112
 AWS.QueryParamSerializer = inherit({
163 113
 
164 114
   constructor: function QueryParamSerializer(rules) {
4  lib/services/sqs.js
@@ -22,9 +22,9 @@ AWS.SQS = AWS.Service.defineService('./services/sqs.api', {
22 22
     },
23 23
 
24 24
     buildEndpoint: function buildEndpoint(request) {
25  
-      var url = request.httpRequest.params.params.QueueUrl;
  25
+      var url = request.httpRequest.params.QueueUrl;
26 26
       if (url) {
27  
-        request.httpRequest.endpoint = new AWS.Endpoint(url.value);
  27
+        request.httpRequest.endpoint = new AWS.Endpoint(url);
28 28
 
29 29
         // signature version 4 requires the region name to be set,
30 30
         // sqs queue urls contain the region name
19  lib/signers/v2.js
@@ -26,18 +26,19 @@ AWS.Signers.V2 = inherit(AWS.Signers.RequestSigner, {
26 26
 
27 27
     var r = this.request;
28 28
 
29  
-    r.params.add('Timestamp', AWS.util.date.iso8601(date));
30  
-    r.params.add('SignatureVersion', '2');
31  
-    r.params.add('SignatureMethod', 'HmacSHA256');
32  
-    r.params.add('AWSAccessKeyId', credentials.accessKeyId);
  29
+    r.params.Timestamp = AWS.util.date.iso8601(date);
  30
+    r.params.SignatureVersion = '2';
  31
+    r.params.SignatureMethod = 'HmacSHA256';
  32
+    r.params.AWSAccessKeyId = credentials.accessKeyId;
33 33
 
34  
-    if (credentials.sessionToken)
35  
-      r.params.add('SecurityToken', credentials.sessionToken);
  34
+    if (credentials.sessionToken) {
  35
+      r.params.SecurityToken = credentials.sessionToken;
  36
+    }
36 37
 
37 38
     delete r.params.Signature; // delete old Signature for re-signing
38  
-    r.params.add('Signature', this.signature(credentials));
  39
+    r.params.Signature = this.signature(credentials);
39 40
 
40  
-    r.body = r.params.toString();
  41
+    r.body = AWS.util.queryParamsToString(r.params);
41 42
     r.headers['Content-Length'] = r.body.length;
42 43
   },
43 44
 
@@ -50,7 +51,7 @@ AWS.Signers.V2 = inherit(AWS.Signers.RequestSigner, {
50 51
     parts.push(this.request.method);
51 52
     parts.push(this.request.endpoint.host.toLowerCase());
52 53
     parts.push(this.request.pathname());
53  
-    parts.push(this.request.params.toString());
  54
+    parts.push(AWS.util.queryParamsToString(this.request.params));
54 55
     return parts.join('\n');
55 56
   }
56 57
 
28  lib/util.js
@@ -65,6 +65,26 @@ AWS.util = {
65 65
     return require('url').parse(url);
66 66
   },
67 67
 
  68
+  queryParamsToString: function queryParamsToString(params) {
  69
+    var items = [];
  70
+    var escape = AWS.util.uriEscape;
  71
+    var sortedKeys = Object.keys(params).sort();
  72
+
  73
+    AWS.util.arrayEach(sortedKeys, function(name) {
  74
+      var value = params[name];
  75
+      var ename = escape(name);
  76
+      var result = ename;
  77
+      if (Array.isArray(value)) {
  78
+        result = ename + '=' + value.sort().join('&' + ename + '=');
  79
+      } else if (value !== undefined && value !== null) {
  80
+        result = ename + '=' + escape(value);
  81
+      }
  82
+      items.push(result);
  83
+    });
  84
+
  85
+    return items.join('&');
  86
+  },
  87
+
68 88
   readFileSync: function readFileSync(path) {
69 89
     return require('fs').readFileSync(path, 'utf-8');
70 90
   },
@@ -286,14 +306,6 @@ AWS.util = {
286 306
     }
287 307
   },
288 308
 
289  
-  values: function values(object) {
290  
-    var list = [];
291  
-    AWS.util.arrayEach(object, function(value) {
292  
-      list.push(value);
293  
-    });
294  
-    return list;
295  
-  },
296  
-
297 309
   update: function update(obj1, obj2) {
298 310
     AWS.util.each(obj2, function iterator(key, item) {
299 311
       obj1[key] = item;
10  test/service_interface/query.spec.coffee
@@ -47,6 +47,8 @@ describe 'AWS.ServiceInterface.Query', ->
47 47
     response = new AWS.Response(request)
48 48
 
49 49
   describe 'buildRequest', ->
  50
+    stringify = (params) -> AWS.util.queryParamsToString(params)
  51
+
50 52
     buildRequest = (input) ->
51 53
       if input == undefined
52 54
         input = 'foo+bar: yuck/baz=~'
@@ -68,22 +70,22 @@ describe 'AWS.ServiceInterface.Query', ->
68 70
 
69 71
     it 'should add the api version param', ->
70 72
       buildRequest()
71  
-      expect(request.httpRequest.params.toString()).
  73
+      expect(stringify(request.httpRequest.params)).
72 74
         toMatch(/Version=2012-01-01/)
73 75
 
74 76
     it 'should add the operation name as Action', ->
75 77
       buildRequest()
76  
-      expect(request.httpRequest.params.toString()).
  78
+      expect(stringify(request.httpRequest.params)).
77 79
         toMatch(/Action=OperationName/)
78 80
 
79 81
     it 'should uri encode params properly', ->
80 82
       buildRequest()
81  
-      expect(request.httpRequest.params.toString()).
  83
+      expect(stringify(request.httpRequest.params)).
82 84
         toMatch(/foo%2Bbar%3A%20yuck%2Fbaz%3D~/);
83 85
 
84 86
     it 'encodes empty string values properly', ->
85 87
       buildRequest('')
86  
-      expect(request.httpRequest.params.toString()).
  88
+      expect(stringify(request.httpRequest.params)).
87 89
         toMatch(/Input=($|&)/);
88 90
 
89 91
   describe 'extractError', ->
20  test/signers/v2.spec.coffee
@@ -24,7 +24,7 @@ describe 'AWS.Signers.V2', ->
24 24
 
25 25
   buildRequest = ->
26 26
     request = new AWS.HttpRequest(new AWS.Endpoint('localhost'))
27  
-    request.params = new AWS.QueryParamList()
  27
+    request.params = {}
28 28
     request
29 29
 
30 30
   buildSigner = (request) ->
@@ -39,6 +39,8 @@ describe 'AWS.Signers.V2', ->
39 39
     date = new Date(1935346573456)
40 40
     signRequest(buildRequest())
41 41
 
  42
+  stringify = AWS.util.queryParamsToString
  43
+
42 44
   describe 'constructor', ->
43 45
 
44 46
     it 'builds a signer for a request object', ->
@@ -47,24 +49,24 @@ describe 'AWS.Signers.V2', ->
47 49
   describe 'addAuthorization', ->
48 50
 
49 51
     it 'adds a url encoded iso8601 timestamp param', ->
50  
-      expect(request.params.toString()).toMatch(/Timestamp=2031-04-30T20%3A16%3A13.456Z/)
  52
+      expect(stringify(request.params)).toMatch(/Timestamp=2031-04-30T20%3A16%3A13.456Z/)
51 53
 
52 54
     it 'adds a SignatureVersion param', ->
53  
-      expect(request.params.toString()).toMatch(/SignatureVersion=2/)
  55
+      expect(stringify(request.params)).toMatch(/SignatureVersion=2/)
54 56
 
55 57
     it 'adds a SignatureMethod param', ->
56  
-      expect(request.params.toString()).toMatch(/SignatureMethod=HmacSHA256/)
  58
+      expect(stringify(request.params)).toMatch(/SignatureMethod=HmacSHA256/)
57 59
 
58 60
     it 'adds an AWSAccessKeyId param', ->
59  
-      expect(request.params.toString()).toMatch(/AWSAccessKeyId=akid/)
  61
+      expect(stringify(request.params)).toMatch(/AWSAccessKeyId=akid/)
60 62
 
61 63
     it 'omits SecurityToken when sessionToken has been omitted', ->
62  
-      expect(request.params.toString()).not.toMatch(/SecurityToken/)
  64
+      expect(stringify(request.params)).not.toMatch(/SecurityToken/)
63 65
 
64 66
     it 'adds the SecurityToken when sessionToken is provided', ->
65 67
       credentials.sessionToken = 'session'
66 68
       signRequest(buildRequest())
67  
-      expect(request.params.toString()).toMatch(/SecurityToken=session/)
  69
+      expect(stringify(request.params)).toMatch(/SecurityToken=session/)
68 70
 
69 71
     it 'populates the body', ->
70 72
       expect(request.body).toEqual('AWSAccessKeyId=akid&Signature=%2FrumhWptMPvyb4aaeOv5iGpl6%2FLfs5uVHu8k1d3NNfc%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2031-04-30T20%3A16%3A13.456Z')
@@ -74,8 +76,8 @@ describe 'AWS.Signers.V2', ->
74 76
 
75 77
     it 'signs additional body params', ->
76 78
       request = buildRequest()
77  
-      request.params.add('Param.1', 'abc')
78  
-      request.params.add('Param.2', 'xyz')
  79
+      request.params['Param.1'] = 'abc'
  80
+      request.params['Param.2'] = 'xyz'
79 81
       signRequest(request)
80 82
       expect(request.body).toEqual('AWSAccessKeyId=akid&Param.1=abc&Param.2=xyz&Signature=3pcXIWw0eVd4wFmp%2Blo24L93UTMGcYSNE%2BFYNNqzDts%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2031-04-30T20%3A16%3A13.456Z')
81 83
 
24  test/util.spec.coffee
@@ -47,6 +47,26 @@ describe 'uriEscapePath', ->
47 47
     s = '/ab cd/'
48 48
     expect(e(s)).toEqual('/ab%20cd/')
49 49
 
  50
+describe 'AWS.util.queryParamsToString', ->
  51
+  qpts = AWS.util.queryParamsToString
  52
+
  53
+  it 'sorts query parameters before stringifying', ->
  54
+    expect(qpts(c: '1', b: '2', a: '3')).toEqual('a=3&b=2&c=1')
  55
+
  56
+  it 'handles empty values', ->
  57
+    expect(qpts(a: '', b: '2')).toEqual('a=&b=2')
  58
+
  59
+  it 'handles null/undefined values', ->
  60
+    expect(qpts(a: undefined, b: null)).toEqual('a&b')
  61
+
  62
+  it 'calls uriEscape on each name and value', ->
  63
+    spy = spyOn(AWS.util, 'uriEscape').andCallThrough()
  64
+    qpts(c: '1', b: '2', a: '3')
  65
+    expect(spy.calls.length).toEqual(6)
  66
+
  67
+  it 'handles values as lists', ->
  68
+    expect(qpts(a: ['1', '2', '3'], b: '4')).toEqual('a=1&a=2&a=3&b=4')
  69
+
50 70
 describe 'AWS.util.date', ->
51 71
 
52 72
   util = AWS.util.date
@@ -257,10 +277,6 @@ describe 'AWS.util.arrayEach', ->
257 277
 
258 278
     expect(total).toEqual(1)
259 279
 
260  
-describe 'AWS.util.values', ->
261  
-  it 'returns values of a key-value map', ->
262  
-    expect(AWS.util.values(a: 1, b: 2, c: 3)).toEqual([1, 2, 3])
263  
-
264 280
 describe 'AWS.util.copy', ->
265 281
   it 'does not copy null or undefined', ->
266 282
     expect(AWS.util.copy(null)).toEqual(null)

0 notes on commit 80ecefb

Please sign in to comment.
Something went wrong with that request. Please try again.