UNPKG

4.32 kBJavaScriptView Raw
1'use strict';
2
3/**
4 * Request plugin. Provides thin wrapper
5 * around http.request and https.request methods
6 *
7 * var that = this;
8 *
9 * this.request({
10 * ssl : true,
11 * hostname : 'example.com',
12 * path : '/test',
13 * type : 'form', // 'json', 'form-data', 'custom'
14 * data : {
15 * id : 243
16 * }
17 * }, function (error, response) {
18 * console.dir(response.headers);
19 * console.log(response.statusCode);
20 * console.log(response.body);
21 * });
22 *
23 * @module Crixalis
24 * @submodule request
25 * @for Controller
26 */
27
28var Crixalis = require('../controller'),
29 http = require('http'),
30 https = require('https'),
31 stream = require('stream'),
32 crypto = require('crypto'),
33 qs = require('querystring');
34
35module.exports = function () {
36 /**
37 * Do HTTP request
38 * @method request
39 * @param {Object} options
40 * @param {Function} callback
41 * @async
42 * @chainable
43 */
44 Crixalis.define('method', 'request', function (options, callback) {
45 var request, data;
46
47 /* Request method */
48 options.method = String(options.method || 'GET')
49 .toUpperCase();
50
51 /* Request type */
52 options.type = String(options.type || 'form')
53 .toLowerCase();
54
55 /* Request headers */
56 options.headers = Object(options.headers);
57
58 if (this.signature) {
59 options.headers['X-Requested-With'] = this.signature;
60 }
61
62 /* Request data */
63 if (options.data) {
64 switch (options.method) {
65 /* Replace query string with data */
66 case 'GET':
67 case 'HEAD':
68 case 'DELETE':
69 options.path = String(options.path || '/')
70 .replace(/(?:\?.*)?$/, '?' + qs.stringify(options.data));
71 break;
72
73 /* Prepare message body */
74 default:
75 switch (options.type) {
76 case 'json':
77 data = JSON.stringify(options.data);
78 options.headers['Content-Type'] = 'application/json';
79 options.headers['Content-Length'] = Buffer.byteLength(data);
80 break;
81
82 case 'form':
83 data = qs.stringify(options.data);
84 options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
85 options.headers['Content-Length'] = Buffer.byteLength(data);
86 break;
87
88 case 'form-data':
89 var key, boundary, headers, header;
90
91 boundary = crypto.createHash('md5')
92 .update([Date.now(), Math.random()].join(''))
93 .digest('hex');
94
95 headers = Object(options.dataHeaders);
96
97 data = '';
98
99 for (key in options.data) {
100 data += '--' + boundary + '\r\n';
101 data += 'Content-Disposition: form-data; name="' + key + '"';
102
103 /* TODO: filename in content-disposition */
104
105 for (header in headers[key]) {
106 data += '\r\n';
107 data += header + ': ' + headers[key][header];
108 }
109
110 data += '\r\n\r\n';
111 data += options.data[key];
112 data += '\r\n';
113 }
114
115 data += '--' + boundary + '--';
116 data += '\r\n';
117
118 options.headers['Content-Type'] = 'multipart/form-data; boundary=' + boundary;
119 options.headers['Content-Length'] = Buffer.byteLength(data);
120 break;
121
122 case 'custom':
123 data = options.data;
124 break;
125
126 default:
127 throw new Error('Unsupported type ' + options.type);
128 }
129 }
130 }
131
132 /* Prepare request */
133 request = (options.ssl? https : http).request(options, function (response) {
134 response.body = '';
135
136 /* Set response encoding */
137 if (options.encoding) {
138 response.setEncoding(options.encoding);
139 }
140
141 response
142 .on('error', callback)
143 .on('data', function (chunk) {
144 this.body += chunk;
145
146 /* TODO: check length */
147 })
148 .on('end', function () {
149 callback(null, this);
150 });
151 });
152
153 /* Set timeout */
154 if (options.timeout) {
155 request.setTimeout(options.timeout, function () {
156 /* Terminate request */
157 request.removeAllListeners();
158 request.abort();
159
160 /* Return error */
161 callback(new Error('Request timed out'));
162 });
163 }
164
165 /* Catch errors */
166 request.on('error', callback);
167
168 /* Write data */
169 if (data) {
170 /* Handle streams */
171 if (data instanceof stream.Readable || data instanceof stream.Duplex) {
172 data.pipe(request);
173 return this;
174 }
175
176 request.write(data);
177 }
178
179 /* End request */
180 request.end();
181
182 return this;
183 });
184};