http: Add write()/end() callbacks
[platform/upstream/nodejs.git] / lib / _http_outgoing.js
1 // Copyright Joyent, Inc. and other Node contributors.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 var assert = require('assert').ok;
23 var Stream = require('stream');
24 var timers = require('timers');
25 var util = require('util');
26
27 var common = require('_http_common');
28
29 var CRLF = common.CRLF;
30 var chunkExpression = common.chunkExpression;
31 var continueExpression = common.continueExpression;
32 var debug = common.debug;
33
34
35 var connectionExpression = /Connection/i;
36 var transferEncodingExpression = /Transfer-Encoding/i;
37 var closeExpression = /close/i;
38 var contentLengthExpression = /Content-Length/i;
39 var dateExpression = /Date/i;
40 var expectExpression = /Expect/i;
41
42
43 var dateCache;
44 function utcDate() {
45   if (!dateCache) {
46     var d = new Date();
47     dateCache = d.toUTCString();
48     timers.enroll(utcDate, 1000 - d.getMilliseconds());
49     timers._unrefActive(utcDate);
50   }
51   return dateCache;
52 }
53 utcDate._onTimeout = function() {
54   dateCache = undefined;
55 };
56
57
58 function OutgoingMessage() {
59   Stream.call(this);
60
61   this.output = [];
62   this.outputEncodings = [];
63   this.outputCallbacks = [];
64
65   this.writable = true;
66
67   this._last = false;
68   this.chunkedEncoding = false;
69   this.shouldKeepAlive = true;
70   this.useChunkedEncodingByDefault = true;
71   this.sendDate = false;
72
73   this._hasBody = true;
74   this._trailer = '';
75
76   this.finished = false;
77   this._hangupClose = false;
78
79   this.socket = null;
80   this.connection = null;
81 }
82 util.inherits(OutgoingMessage, Stream);
83
84
85 exports.OutgoingMessage = OutgoingMessage;
86
87
88 OutgoingMessage.prototype.setTimeout = function(msecs, callback) {
89   if (callback)
90     this.on('timeout', callback);
91   if (!this.socket) {
92     this.once('socket', function(socket) {
93       socket.setTimeout(msecs);
94     });
95   } else
96     this.socket.setTimeout(msecs);
97 };
98
99
100 // It's possible that the socket will be destroyed, and removed from
101 // any messages, before ever calling this.  In that case, just skip
102 // it, since something else is destroying this connection anyway.
103 OutgoingMessage.prototype.destroy = function(error) {
104   if (this.socket)
105     this.socket.destroy(error);
106   else
107     this.once('socket', function(socket) {
108       socket.destroy(error);
109     });
110 };
111
112
113 // This abstract either writing directly to the socket or buffering it.
114 OutgoingMessage.prototype._send = function(data, encoding, callback) {
115   // This is a shameful hack to get the headers and first body chunk onto
116   // the same packet. Future versions of Node are going to take care of
117   // this at a lower level and in a more general way.
118   if (!this._headerSent) {
119     if (util.isString(data) &&
120         encoding !== 'hex' &&
121         encoding !== 'base64') {
122       data = this._header + data;
123     } else {
124       this.output.unshift(this._header);
125       this.outputEncodings.unshift('ascii');
126       this.outputCallbacks.unshift(null);
127     }
128     this._headerSent = true;
129   }
130   return this._writeRaw(data, encoding, callback);
131 };
132
133
134 OutgoingMessage.prototype._writeRaw = function(data, encoding, callback) {
135   if (util.isFunction(encoding)) {
136     callback = encoding;
137     encoding = null;
138   }
139
140   if (data.length === 0) {
141     if (util.isFunction(callback))
142       process.nextTick(callback);
143     return true;
144   }
145
146   if (this.connection &&
147       this.connection._httpMessage === this &&
148       this.connection.writable &&
149       !this.connection.destroyed) {
150     // There might be pending data in the this.output buffer.
151     while (this.output.length) {
152       if (!this.connection.writable) {
153         this._buffer(data, encoding, callback);
154         return false;
155       }
156       var c = this.output.shift();
157       var e = this.outputEncodings.shift();
158       var cb = this.outputCallbacks.shift();
159       this.connection.write(c, e, cb);
160     }
161
162     // Directly write to socket.
163     return this.connection.write(data, encoding, callback);
164   } else if (this.connection && this.connection.destroyed) {
165     // The socket was destroyed.  If we're still trying to write to it,
166     // then we haven't gotten the 'close' event yet.
167     return false;
168   } else {
169     // buffer, as long as we're not destroyed.
170     this._buffer(data, encoding, callback);
171     return false;
172   }
173 };
174
175
176 OutgoingMessage.prototype._buffer = function(data, encoding, callback) {
177   this.output.push(data);
178   this.outputEncodings.push(encoding);
179   this.outputCallbacks.push(callback);
180   return false;
181 };
182
183
184 OutgoingMessage.prototype._storeHeader = function(firstLine, headers) {
185   // firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n'
186   // in the case of response it is: 'HTTP/1.1 200 OK\r\n'
187   var state = {
188     sentConnectionHeader: false,
189     sentContentLengthHeader: false,
190     sentTransferEncodingHeader: false,
191     sentDateHeader: false,
192     sentExpect: false,
193     messageHeader: firstLine
194   };
195
196   var field, value;
197   var self = this;
198
199   if (headers) {
200     var keys = Object.keys(headers);
201     var isArray = util.isArray(headers);
202     var field, value;
203
204     for (var i = 0, l = keys.length; i < l; i++) {
205       var key = keys[i];
206       if (isArray) {
207         field = headers[key][0];
208         value = headers[key][1];
209       } else {
210         field = key;
211         value = headers[key];
212       }
213
214       if (util.isArray(value)) {
215         for (var j = 0; j < value.length; j++) {
216           storeHeader(this, state, field, value[j]);
217         }
218       } else {
219         storeHeader(this, state, field, value);
220       }
221     }
222   }
223
224   // Date header
225   if (this.sendDate == true && state.sentDateHeader == false) {
226     state.messageHeader += 'Date: ' + utcDate() + CRLF;
227   }
228
229   // Force the connection to close when the response is a 204 No Content or
230   // a 304 Not Modified and the user has set a "Transfer-Encoding: chunked"
231   // header.
232   //
233   // RFC 2616 mandates that 204 and 304 responses MUST NOT have a body but
234   // node.js used to send out a zero chunk anyway to accommodate clients
235   // that don't have special handling for those responses.
236   //
237   // It was pointed out that this might confuse reverse proxies to the point
238   // of creating security liabilities, so suppress the zero chunk and force
239   // the connection to close.
240   var statusCode = this.statusCode;
241   if ((statusCode == 204 || statusCode === 304) &&
242       this.chunkedEncoding === true) {
243     debug(statusCode + ' response should not use chunked encoding,' +
244           ' closing connection.');
245     this.chunkedEncoding = false;
246     this.shouldKeepAlive = false;
247   }
248
249   // keep-alive logic
250   if (state.sentConnectionHeader === false) {
251     var shouldSendKeepAlive = this.shouldKeepAlive &&
252         (state.sentContentLengthHeader ||
253          this.useChunkedEncodingByDefault ||
254          this.agent);
255     if (shouldSendKeepAlive) {
256       state.messageHeader += 'Connection: keep-alive\r\n';
257     } else {
258       this._last = true;
259       state.messageHeader += 'Connection: close\r\n';
260     }
261   }
262
263   if (state.sentContentLengthHeader == false &&
264       state.sentTransferEncodingHeader == false) {
265     if (this._hasBody) {
266       if (this.useChunkedEncodingByDefault) {
267         state.messageHeader += 'Transfer-Encoding: chunked\r\n';
268         this.chunkedEncoding = true;
269       } else {
270         this._last = true;
271       }
272     } else {
273       // Make sure we don't end the 0\r\n\r\n at the end of the message.
274       this.chunkedEncoding = false;
275     }
276   }
277
278   this._header = state.messageHeader + CRLF;
279   this._headerSent = false;
280
281   // wait until the first body chunk, or close(), is sent to flush,
282   // UNLESS we're sending Expect: 100-continue.
283   if (state.sentExpect) this._send('');
284 };
285
286 function storeHeader(self, state, field, value) {
287   // Protect against response splitting. The if statement is there to
288   // minimize the performance impact in the common case.
289   if (/[\r\n]/.test(value))
290     value = value.replace(/[\r\n]+[ \t]*/g, '');
291
292   state.messageHeader += field + ': ' + value + CRLF;
293
294   if (connectionExpression.test(field)) {
295     state.sentConnectionHeader = true;
296     if (closeExpression.test(value)) {
297       self._last = true;
298     } else {
299       self.shouldKeepAlive = true;
300     }
301
302   } else if (transferEncodingExpression.test(field)) {
303     state.sentTransferEncodingHeader = true;
304     if (chunkExpression.test(value)) self.chunkedEncoding = true;
305
306   } else if (contentLengthExpression.test(field)) {
307     state.sentContentLengthHeader = true;
308   } else if (dateExpression.test(field)) {
309     state.sentDateHeader = true;
310   } else if (expectExpression.test(field)) {
311     state.sentExpect = true;
312   }
313 }
314
315
316 OutgoingMessage.prototype.setHeader = function(name, value) {
317   if (arguments.length < 2) {
318     throw new Error('`name` and `value` are required for setHeader().');
319   }
320
321   if (this._header) {
322     throw new Error('Can\'t set headers after they are sent.');
323   }
324
325   var key = name.toLowerCase();
326   this._headers = this._headers || {};
327   this._headerNames = this._headerNames || {};
328   this._headers[key] = value;
329   this._headerNames[key] = name;
330 };
331
332
333 OutgoingMessage.prototype.getHeader = function(name) {
334   if (arguments.length < 1) {
335     throw new Error('`name` is required for getHeader().');
336   }
337
338   if (!this._headers) return;
339
340   var key = name.toLowerCase();
341   return this._headers[key];
342 };
343
344
345 OutgoingMessage.prototype.removeHeader = function(name) {
346   if (arguments.length < 1) {
347     throw new Error('`name` is required for removeHeader().');
348   }
349
350   if (this._header) {
351     throw new Error('Can\'t remove headers after they are sent.');
352   }
353
354   if (!this._headers) return;
355
356   var key = name.toLowerCase();
357   delete this._headers[key];
358   delete this._headerNames[key];
359 };
360
361
362 OutgoingMessage.prototype._renderHeaders = function() {
363   if (this._header) {
364     throw new Error('Can\'t render headers after they are sent to the client.');
365   }
366
367   if (!this._headers) return {};
368
369   var headers = {};
370   var keys = Object.keys(this._headers);
371   for (var i = 0, l = keys.length; i < l; i++) {
372     var key = keys[i];
373     headers[this._headerNames[key]] = this._headers[key];
374   }
375   return headers;
376 };
377
378
379 Object.defineProperty(OutgoingMessage.prototype, 'headersSent', {
380   configurable: true,
381   enumerable: true,
382   get: function() { return !!this._header; }
383 });
384
385
386 OutgoingMessage.prototype.write = function(chunk, encoding, callback) {
387   if (!this._header) {
388     this._implicitHeader();
389   }
390
391   if (!this._hasBody) {
392     debug('This type of response MUST NOT have a body. ' +
393           'Ignoring write() calls.');
394     return true;
395   }
396
397   if (!util.isString(chunk) && !util.isBuffer(chunk)) {
398     throw new TypeError('first argument must be a string or Buffer');
399   }
400
401
402   // If we get an empty string or buffer, then just do nothing, and
403   // signal the user to keep writing.
404   if (chunk.length === 0) return true;
405
406   var len, ret;
407   if (this.chunkedEncoding) {
408     if (util.isString(chunk) &&
409         encoding !== 'hex' &&
410         encoding !== 'base64' &&
411         encoding !== 'binary') {
412       len = Buffer.byteLength(chunk, encoding);
413       chunk = len.toString(16) + CRLF + chunk + CRLF;
414       ret = this._send(chunk, encoding, callback);
415     } else {
416       // buffer, or a non-toString-friendly encoding
417       if (util.isString(chunk))
418         len = Buffer.byteLength(chunk, encoding);
419       else
420         len = chunk.length;
421
422       if (this.connection)
423         this.connection.cork();
424       this._send(len.toString(16), 'ascii', null);
425       this._send(crlf_buf, null, null);
426       this._send(chunk, encoding, null);
427       ret = this._send(crlf_buf, null, callback);
428       if (this.connection)
429         this.connection.uncork();
430     }
431   } else {
432     ret = this._send(chunk, encoding, callback);
433   }
434
435   debug('write ret = ' + ret);
436   return ret;
437 };
438
439
440 OutgoingMessage.prototype.addTrailers = function(headers) {
441   this._trailer = '';
442   var keys = Object.keys(headers);
443   var isArray = util.isArray(headers);
444   var field, value;
445   for (var i = 0, l = keys.length; i < l; i++) {
446     var key = keys[i];
447     if (isArray) {
448       field = headers[key][0];
449       value = headers[key][1];
450     } else {
451       field = key;
452       value = headers[key];
453     }
454
455     this._trailer += field + ': ' + value + CRLF;
456   }
457 };
458
459
460 var zero_chunk_buf = new Buffer('\r\n0\r\n');
461 var crlf_buf = new Buffer('\r\n');
462
463
464 OutgoingMessage.prototype.end = function(data, encoding, callback) {
465   if (util.isFunction(data)) {
466     callback = data;
467     data = null;
468   } else if (util.isFunction(encoding)) {
469     callback = encoding;
470     encoding = null;
471   }
472
473   if (data && !util.isString(data) && !util.isBuffer(data)) {
474     throw new TypeError('first argument must be a string or Buffer');
475   }
476
477   if (this.finished) {
478     return false;
479   }
480   if (!this._header) {
481     this._implicitHeader();
482   }
483
484   if (data && !this._hasBody) {
485     debug('This type of response MUST NOT have a body. ' +
486           'Ignoring data passed to end().');
487     data = null;
488   }
489
490   if (this.connection && data)
491     this.connection.cork();
492
493   var ret;
494   if (data) {
495     // Normal body write.
496     ret = this.write(data, encoding);
497   }
498
499   if (this.chunkedEncoding) {
500     ret = this._send('0\r\n' + this._trailer + '\r\n', 'ascii', callback);
501   } else {
502     // Force a flush, HACK.
503     ret = this._send('', 'ascii', callback);
504   }
505
506   if (this.connection && data)
507     this.connection.uncork();
508
509   this.finished = true;
510
511   // There is the first message on the outgoing queue, and we've sent
512   // everything to the socket.
513   debug('outgoing message end.');
514   if (this.output.length === 0 && this.connection._httpMessage === this) {
515     this._finish();
516   }
517
518   return ret;
519 };
520
521
522 OutgoingMessage.prototype._finish = function() {
523   assert(this.connection);
524   this.emit('finish');
525 };
526
527
528 OutgoingMessage.prototype._flush = function() {
529   // This logic is probably a bit confusing. Let me explain a bit:
530   //
531   // In both HTTP servers and clients it is possible to queue up several
532   // outgoing messages. This is easiest to imagine in the case of a client.
533   // Take the following situation:
534   //
535   //    req1 = client.request('GET', '/');
536   //    req2 = client.request('POST', '/');
537   //
538   // When the user does
539   //
540   //   req2.write('hello world\n');
541   //
542   // it's possible that the first request has not been completely flushed to
543   // the socket yet. Thus the outgoing messages need to be prepared to queue
544   // up data internally before sending it on further to the socket's queue.
545   //
546   // This function, outgoingFlush(), is called by both the Server and Client
547   // to attempt to flush any pending messages out to the socket.
548
549   if (!this.socket) return;
550
551   var ret;
552   while (this.output.length) {
553
554     if (!this.socket.writable) return; // XXX Necessary?
555
556     var data = this.output.shift();
557     var encoding = this.outputEncodings.shift();
558     var cb = this.outputCallbacks.shift();
559
560     ret = this.socket.write(data, encoding, cb);
561   }
562
563   if (this.finished) {
564     // This is a queue to the server or client to bring in the next this.
565     this._finish();
566   } else if (ret) {
567     // This is necessary to prevent https from breaking
568     this.emit('drain');
569   }
570 };