user949300 user949300 - 3 months ago 28
Javascript Question

How to POST multiple values same name in Node.js Request

I need to programmatically submit multiple values to a POST (in this example, US states), using node.js and Request.

For example, the HTML form might be be

<select name="stateprov[]" id="stateprov" multiple="multiple" >


followed by 50 options..., one per state

And the submitted form data would look like

stateprov%5B%5D=CA&stateprov%5B%5D=WI


How can I do this using Request? Given that I have an array of states, ['CA','WI'}, I've tried

form['stateprov[]'] = states
fails
it generates stateprov%5B%5D[0]=WI&stateprov%5B%5D[1]=CA as the output


form['stateprov[]'] = states.join(',') doesn't work either

BTW, Node people, I'm really trying to like the project, there's a lot of cool things, but your documentation is less than great.

Followup: I think the problem might be that Request (https://npmjs.org/package/request) uses qs (https://npmjs.org/package/qs) to encode the form data, and it adds the extraneous [0] and [1]. Node's built in queryString (http://nodejs.org/api/querystring.html#querystring_querystring_stringify_obj_sep_eq) does the encoding that I want.

Followup #2: Chatted with Mikeal Rogers who does a great job supporting Request, and he basically said that I can't do it this way in Request. Since I'm not exploiting many of the cool features of Request I'll look at the more basic http.

Answer

Sorry to answer my own question, but in case others run into this issue...

Mikeal Rogers is of course right. Request uses the npm package qs (https://npmjs.org/package/qs) as his query string parser, and, for better or worse, when it "stringifies" an array it adds '[n]'.

function stringifyArray(arr, prefix) {
  var ret = [];
  if (!prefix) throw new TypeError('stringify expects an object');
  for (var i = 0; i < arr.length; i++) {
    ret.push(stringify(arr[i], prefix + '[' + i + ']'));  <<< see here
  }
  return ret.join('&');
}

So a form with multiple values would look like:

foo[0]=value0&foo[1]=value1

Maybe this is what you want, but it's not what I want, and this seems to mismatch normal HTML form behavior. My HTML experience is limited, so this may be wrong:-)

It turns out that node's built in querystring.stringify does what I want, outputting

foo=value0&foo=value1

The quick hack is to change one line in Request.form() (roughly line 974)

this.body = *querystring*.stringify(form).toString('utf8')

However, anytime you do an update, you'll have to remember to do this again. Not robust. The "proper" way is to subclass. It took me a while to find one little gotcha - you cannot require('request'), because that brings in index.js, which exports the lowercase factory request() method. The "real" uppercase with a new constructor is in request.js. So you must be specific: require('request/request.js')

Here's the code: (also at https://gist.github.com/MorganConrad/8827916)

var Request = require('request/request.js');  // IMPORTANT - specify request.js, don't get index.js!!!
var querystring = require('querystring');

MyRequest.prototype = Object.create(Request.prototype);
MyRequest.prototype.constructor = MyRequest;

function MyRequest(options, callbackfn) {
  "use strict";
  if (callbackfn)
    options.callback = callbackfn;
  options.method = options.method || 'POST';
  Request.prototype.constructor.call(this, options);
}

MyRequest.prototype.form = function (form) {
  "use strict";
  if (form) {
    this.setHeader('content-type', 'application/x-www-form-urlencoded; charset=utf-8');
    this.body = querystring.stringify(form).toString('utf8');
    return this;
  }

  else
    return Request.prototype.form.apply(this, arguments);
};


module.exports = MyRequest;
Comments