request.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. "use strict";
  2. const _ = require('../../lodash'), { Url } = require('../../postman-collection-lite'), sanitizeOptions = require('./util').sanitizeOptions, sanitize = require('./util').sanitize, addFormParam = require('./util').addFormParam, parseRequest = require('./parseRequest');
  3. var self;
  4. /**
  5. * returns snippet of nodejs(native) by parsing data from Postman-SDK request object
  6. *
  7. * @param {Object} request - Postman SDK request object
  8. * @param {String} indentString - indentation required for code snippet
  9. * @param {Object} options
  10. * @returns {String} - nodejs(native) code snippet for given request object
  11. */
  12. function makeSnippet(request, indentString, options) {
  13. var nativeModule = (request.url.protocol === 'http' ? 'http' : 'https'), snippet, optionsArray = [], postData = '', url, host, path, query;
  14. if (options.ES6_enabled) {
  15. snippet = 'const ';
  16. }
  17. else {
  18. snippet = 'var ';
  19. }
  20. if (options.followRedirect) {
  21. snippet += `${nativeModule} = require('follow-redirects').${nativeModule};\n`;
  22. }
  23. else {
  24. snippet += `${nativeModule} = require('${nativeModule}');\n`;
  25. }
  26. if (options.includesFileSystem) {
  27. if (options.ES6_enabled) {
  28. snippet += 'const ';
  29. }
  30. else {
  31. snippet += 'var ';
  32. }
  33. snippet += 'fs = require(\'fs\');\n\n';
  34. }
  35. else {
  36. snippet += '\n';
  37. }
  38. if (_.get(request, 'body.mode') && request.body.mode === 'urlencoded') {
  39. if (options.ES6_enabled) {
  40. snippet += 'const ';
  41. }
  42. else {
  43. snippet += 'var ';
  44. }
  45. snippet += 'qs = require(\'querystring\');\n\n';
  46. }
  47. if (options.ES6_enabled) {
  48. snippet += 'const ';
  49. }
  50. else {
  51. snippet += 'var ';
  52. }
  53. snippet += 'options = {\n';
  54. /**
  55. * creating string to represent options object using optionArray.join()
  56. * example:
  57. * options: {
  58. * method: 'GET',
  59. * hostname: 'www.google.com',
  60. * path: '/x?a=10',
  61. * headers: {
  62. * 'content-type': 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW'
  63. * }
  64. * }
  65. */
  66. // The following code handles multiple files in the same formdata param.
  67. // It removes the form data params where the src property is an array of filepath strings
  68. // Splits that array into different form data params with src set as a single filepath string
  69. if (request.body && request.body.mode === 'formdata') {
  70. let formdata = request.body.formdata, formdataArray = [];
  71. formdata.members.forEach((param) => {
  72. let key = param.key, type = param.type, disabled = param.disabled, contentType = param.contentType;
  73. // check if type is file or text
  74. if (type === 'file') {
  75. // if src is not of type string we check for array(multiple files)
  76. if (typeof param.src !== 'string') {
  77. // if src is an array(not empty), iterate over it and add files as separate form fields
  78. if (Array.isArray(param.src) && param.src.length) {
  79. param.src.forEach((filePath) => {
  80. addFormParam(formdataArray, key, param.type, filePath, disabled, contentType);
  81. });
  82. }
  83. // if src is not an array or string, or is an empty array, add a placeholder for file path(no files case)
  84. else {
  85. addFormParam(formdataArray, key, param.type, '/path/to/file', disabled, contentType);
  86. }
  87. }
  88. // if src is string, directly add the param with src as filepath
  89. else {
  90. addFormParam(formdataArray, key, param.type, param.src, disabled, contentType);
  91. }
  92. }
  93. // if type is text, directly add it to formdata array
  94. else {
  95. addFormParam(formdataArray, key, param.type, param.value, disabled, contentType);
  96. }
  97. });
  98. request.body.update({
  99. mode: 'formdata',
  100. formdata: formdataArray
  101. });
  102. }
  103. if (request.body && request.body[request.body.mode]) {
  104. postData += parseRequest.parseBody(request.body.toJSON(), indentString, options.trimRequestBody, request.headers.get('Content-Type'));
  105. }
  106. if (request.body && !request.headers.has('Content-Type')) {
  107. if (request.body.mode === 'file') {
  108. request.addHeader({
  109. key: 'Content-Type',
  110. value: 'text/plain'
  111. });
  112. }
  113. else if (request.body.mode === 'graphql') {
  114. request.addHeader({
  115. key: 'Content-Type',
  116. value: 'application/json'
  117. });
  118. }
  119. }
  120. url = Url.parse(request.url.toString());
  121. host = url.host ? url.host.join('.') : '';
  122. path = url.path ? '/' + url.path.join('/') : '/';
  123. query = url.query ? _.reduce(url.query, (accum, q) => {
  124. accum.push(`${q.key}=${q.value}`);
  125. return accum;
  126. }, []) : [];
  127. if (query.length > 0) {
  128. query = '?' + query.join('&');
  129. }
  130. else {
  131. query = '';
  132. }
  133. optionsArray.push(indentString + `'method': '${request.method}'`);
  134. optionsArray.push(`${indentString}'hostname': '${sanitize(host)}'`);
  135. if (url.port) {
  136. optionsArray.push(`${indentString}'port': ${url.port}`);
  137. }
  138. optionsArray.push(`${indentString}'path': '${sanitize(path)}${sanitize(encodeURI(query))}'`);
  139. const headerString = parseRequest.parseHeader(request, indentString);
  140. headerString && optionsArray.push(headerString);
  141. if (options.followRedirect) {
  142. optionsArray.push(indentString + '\'maxRedirects\': 20');
  143. }
  144. snippet += optionsArray.join(',\n') + '\n';
  145. snippet += '};\n\n';
  146. if (options.ES6_enabled) {
  147. snippet += 'const ';
  148. }
  149. else {
  150. snippet += 'var ';
  151. }
  152. snippet += `req = ${nativeModule}.request(options, `;
  153. if (options.ES6_enabled) {
  154. snippet += '(res) => {\n';
  155. snippet += indentString + 'const chunks = [];\n\n';
  156. snippet += indentString + 'res.on("data", (chunk) => {\n';
  157. }
  158. else {
  159. snippet += 'function (res) {\n';
  160. snippet += indentString + 'var chunks = [];\n\n';
  161. snippet += indentString + 'res.on("data", function (chunk) {\n';
  162. }
  163. snippet += indentString.repeat(2) + 'chunks.push(chunk);\n';
  164. snippet += indentString + '});\n\n';
  165. if (options.ES6_enabled) {
  166. snippet += indentString + 'res.on("end", (chunk) => {\n';
  167. snippet += indentString.repeat(2) + 'const body = Buffer.concat(chunks);\n';
  168. }
  169. else {
  170. snippet += indentString + 'res.on("end", function (chunk) {\n';
  171. snippet += indentString.repeat(2) + 'var body = Buffer.concat(chunks);\n';
  172. }
  173. snippet += indentString.repeat(2) + 'console.log(body.toString());\n';
  174. snippet += indentString + '});\n\n';
  175. if (options.ES6_enabled) {
  176. snippet += indentString + 'res.on("error", (error) => {\n';
  177. }
  178. else {
  179. snippet += indentString + 'res.on("error", function (error) {\n';
  180. }
  181. snippet += indentString.repeat(2) + 'console.error(error);\n';
  182. snippet += indentString + '});\n';
  183. snippet += '});\n\n';
  184. if (request.body && !(_.isEmpty(request.body)) && postData.length) {
  185. if (options.ES6_enabled) {
  186. snippet += 'const ';
  187. }
  188. else {
  189. snippet += 'var ';
  190. }
  191. snippet += `postData = ${postData};\n\n`;
  192. if (request.method === 'DELETE') {
  193. snippet += 'req.setHeader(\'Content-Length\', postData.length);\n\n';
  194. }
  195. if (request.body.mode === 'formdata') {
  196. snippet += 'req.setHeader(\'content-type\',' +
  197. ' \'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW\');\n\n';
  198. }
  199. snippet += 'req.write(postData);\n\n';
  200. }
  201. if (options.requestTimeout) {
  202. snippet += `req.setTimeout(${options.requestTimeout}, function() {\n`;
  203. snippet += indentString + 'req.abort();\n';
  204. snippet += '});\n\n';
  205. }
  206. snippet += 'req.end();';
  207. return snippet;
  208. }
  209. /**
  210. * Converts Postman sdk request object to nodejs native code snippet
  211. *
  212. * @param {Object} request - postman-SDK request object
  213. * @param {Object} options
  214. * @param {String} options.indentType - type for indentation eg: Space, Tab
  215. * @param {String} options.indentCount - number of spaces or tabs for indentation.
  216. * @param {Boolean} options.followRedirect - whether to enable followredirect
  217. * @param {Boolean} options.trimRequestBody - whether to trim fields in request body or not
  218. * @param {Boolean} options.ES6_enabled - whether to generate snippet with ES6 features
  219. * @param {Number} options.requestTimeout : time in milli-seconds after which request will bail out
  220. * @param {Function} callback - callback function with parameters (error, snippet)
  221. */
  222. self = module.exports = {
  223. /**
  224. * Used to return options which are specific to a particular plugin
  225. *
  226. * @returns {Array}
  227. */
  228. getOptions: function () {
  229. return [{
  230. name: 'Set indentation count',
  231. id: 'indentCount',
  232. type: 'positiveInteger',
  233. default: 2,
  234. description: 'Set the number of indentation characters to add per code level'
  235. },
  236. {
  237. name: 'Set indentation type',
  238. id: 'indentType',
  239. type: 'enum',
  240. availableOptions: ['Tab', 'Space'],
  241. default: 'Space',
  242. description: 'Select the character used to indent lines of code'
  243. },
  244. {
  245. name: 'Set request timeout',
  246. id: 'requestTimeout',
  247. type: 'positiveInteger',
  248. default: 0,
  249. description: 'Set number of milliseconds the request should wait for a response' +
  250. ' before timing out (use 0 for infinity)'
  251. },
  252. {
  253. name: 'Follow redirects',
  254. id: 'followRedirect',
  255. type: 'boolean',
  256. default: true,
  257. description: 'Automatically follow HTTP redirects'
  258. },
  259. {
  260. name: 'Trim request body fields',
  261. id: 'trimRequestBody',
  262. type: 'boolean',
  263. default: false,
  264. description: 'Remove white space and additional lines that may affect the server\'s response'
  265. },
  266. {
  267. name: 'Enable ES6 features',
  268. id: 'ES6_enabled',
  269. type: 'boolean',
  270. default: false,
  271. description: 'Modifies code snippet to incorporate ES6 (EcmaScript) features'
  272. }];
  273. },
  274. convert: function (request, options, callback) {
  275. if (!_.isFunction(callback)) {
  276. throw new Error('NodeJS-Request-Converter: callback is not valid function');
  277. }
  278. options = sanitizeOptions(options, self.getOptions());
  279. // String representing value of indentation required
  280. var indentString;
  281. indentString = options.indentType === 'Tab' ? '\t' : ' ';
  282. indentString = indentString.repeat(options.indentCount);
  283. return callback(null, makeSnippet(request, indentString, options));
  284. }
  285. };