summaryrefslogblamecommitdiffhomepage
path: root/src/nodejs/unit-http/http_server.js
blob: 8a536cdee78cc4e8856681d5c0e6cc5f8daee101 (plain) (tree)





















































                                                                         
                                                          






                                     

                                                                              





                                                                

                                                                                   







                                                             
                              


































































































                                                                                            





                                                                           

                                                                             
 
                                































                                                                             



                                           

                                               
                


                                                                        

                                                   
 



                                         


















































                                                                           





















                                                                            



                                    

                            

                             























                                                                    
                                                            






                                                              
                                     
 


                            


       


                                     






                                           

/*
 * Copyright (C) NGINX, Inc.
 */

'use strict';

const EventEmitter = require('events');
const http = require('http');
const util = require('util');
const unit_lib = require('unit-http/build/Release/unit-http.node');
const unit_socket = require('unit-http/socket');

const { Socket } = unit_socket;


function ServerResponse(req) {
    EventEmitter.call(this);

    this.headers = {};
}
util.inherits(ServerResponse, EventEmitter);

ServerResponse.prototype.statusCode = 200;
ServerResponse.prototype.statusMessage = undefined;
ServerResponse.prototype.headers_len = 0;
ServerResponse.prototype.headers_count = 0;
ServerResponse.prototype.headersSent = false;
ServerResponse.prototype.finished = false;

ServerResponse.prototype._finish = function _finish() {
    this.headers = {};
    this.headers_len = 0;
    this.headers_count = 0;
    this.finished = true;
};

ServerResponse.prototype.assignSocket = function assignSocket(socket) {
};

ServerResponse.prototype.detachSocket = function detachSocket(socket) {
};

ServerResponse.prototype.writeContinue = function writeContinue(cb) {
};

ServerResponse.prototype.writeProcessing = function writeProcessing(cb) {
};

ServerResponse.prototype.setHeader = function setHeader(key, value) {
    if (typeof key !== 'string') {
        throw new TypeError('Key argument must be a string');
    }

    let header_key_len = Buffer.byteLength(key, 'latin1');
    let header_len = 0
    let header_count = 0;

    if (Array.isArray(value)) {
        header_count = value.length;

        value.forEach(function(val) {
            if (typeof val !== 'string' && typeof val !== 'number') {
                throw new TypeError('Array entries must be string or number');
            }

            header_len += Buffer.byteLength(val + "", 'latin1');
        });

    } else {
        if (typeof value !== 'string' && typeof value !== 'number') {
            throw new TypeError('Value argument must be string, number, or array');
        }

        header_count = 1;
        header_len = Buffer.byteLength(value + "", 'latin1');
    }

    this.removeHeader(key);

    this.headers[key] = value;
    this.headers_len += header_len + (header_key_len * header_count);
    this.headers_count += header_count;
};

ServerResponse.prototype.getHeader = function getHeader(name) {
    return this.headers[name];
};

ServerResponse.prototype.getHeaderNames = function getHeaderNames() {
    return Object.keys(this.headers);
};

ServerResponse.prototype.getHeaders = function getHeaders() {
    return this.headers;
};

ServerResponse.prototype.hasHeader = function hasHeader(name) {
    return name in this.headers;
};

ServerResponse.prototype.removeHeader = function removeHeader(name) {
    if (!(name in this.headers)) {
        return;
    }

    let name_len = Buffer.byteLength(name + "", 'latin1');

    if (Array.isArray(this.headers[name])) {
        this.headers_count -= this.headers[name].length;
        this.headers_len -= this.headers[name].length * name_len;

        this.headers[name].forEach(function(val) {
            this.headers_len -= Buffer.byteLength(val + "", 'latin1');
        });

    } else {
        this.headers_count--;
        this.headers_len -= name_len + Buffer.byteLength(this.headers[name] + "", 'latin1');
    }

    delete this.headers[name];
};

ServerResponse.prototype.sendDate = function sendDate() {
    throw new Error("Not supported");
};

ServerResponse.prototype.setTimeout = function setTimeout(msecs, callback) {
    this.timeout = msecs;

    if (callback) {
        this.on('timeout', callback);
    }

    return this;
};

// for Express
ServerResponse.prototype._implicitHeader = function _implicitHeader() {
    this.writeHead(this.statusCode);
};

ServerResponse.prototype.writeHead = writeHead;
ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead;

function writeHead(statusCode, reason, obj) {
    var originalStatusCode = statusCode;

    statusCode |= 0;

    if (statusCode < 100 || statusCode > 999) {
        throw new ERR_HTTP_INVALID_STATUS_CODE(originalStatusCode);
    }

    if (typeof reason === 'string') {
        this.statusMessage = reason;

    } else {
        if (!this.statusMessage) {
            this.statusMessage = http.STATUS_CODES[statusCode] || 'unknown';
        }

        obj = reason;
    }

    this.statusCode = statusCode;

    if (obj) {
        var k;
        var keys = Object.keys(obj);

        for (var i = 0; i < keys.length; i++) {
            k = keys[i];

            if (k) {
                this.setHeader(k, obj[k]);
            }
        }
    }
};

ServerResponse.prototype._writeBody = function(chunk, encoding, callback) {
    var contentLength = 0;

    if (!this.headersSent) {
        unit_lib.unit_response_headers(this, this.statusCode, this.headers,
                                       this.headers_count, this.headers_len);

        this.headersSent = true;
    }

    if (typeof chunk === 'function') {
        callback = chunk;
        chunk = null;

    } else if (typeof encoding === 'function') {
        callback = encoding;
        encoding = null;
    }

    if (chunk) {
        if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) {
            throw new TypeError('First argument must be a string or Buffer');
        }

        if (typeof chunk === 'string') {
            contentLength = Buffer.byteLength(chunk, encoding);

        } else {
            contentLength = chunk.length;
        }

        unit_lib.unit_response_write(this, chunk, contentLength);
    }

    if (typeof callback === 'function') {
        callback(this);
    }
};

ServerResponse.prototype.write = function write(chunk, encoding, callback) {
    if (this.finished) {
        throw new Error("Write after end");
    }

    this._writeBody(chunk, encoding, callback);

    return true;
};

ServerResponse.prototype.end = function end(chunk, encoding, callback) {
    if (!this.finished) {
        this._writeBody(chunk, encoding, callback);

        unit_lib.unit_response_end(this);

        this.finished = true;
    }

    return this;
};

function ServerRequest(server) {
    EventEmitter.call(this);

    this.server = server;
}
util.inherits(ServerRequest, EventEmitter);

ServerRequest.prototype.unpipe = undefined;

ServerRequest.prototype.setTimeout = function setTimeout(msecs, callback) {
    this.timeout = msecs;

    if (callback) {
        this.on('timeout', callback);
    }

    return this;
};

ServerRequest.prototype.statusCode = function statusCode() {
    /* Only valid for response obtained from http.ClientRequest. */
};

ServerRequest.prototype.statusMessage = function statusMessage() {
    /* Only valid for response obtained from http.ClientRequest. */
};

ServerRequest.prototype.trailers = function trailers() {
    throw new Error("Not supported");
};

ServerRequest.prototype.METHODS = function METHODS() {
    return http.METHODS;
};

ServerRequest.prototype.STATUS_CODES = function STATUS_CODES() {
    return http.STATUS_CODES;
};

ServerRequest.prototype.listeners = function listeners() {
    return [];
};

ServerRequest.prototype.resume = function resume() {
    return [];
};

/*
 * The "on" method is overridden to defer reading data until user code is
 * ready, that is (ev === "data").  This can occur after req.emit("end") is
 * executed, since the user code can be scheduled asynchronously by Promises
 * and so on.  Passing the data is postponed by process.nextTick() until
 * the "on" method caller completes.
 */
ServerRequest.prototype.on = function on(ev, fn) {
    Server.prototype.on.call(this, ev, fn);

    if (ev === "data") {
        process.nextTick(function () {
            if (this.server.buffer.length !== 0) {
                this.emit("data", this.server.buffer);
            }

        }.bind(this));
    }
};

ServerRequest.prototype.addListener = ServerRequest.prototype.on;

function Server(requestListener) {
    EventEmitter.call(this);

    this.unit = new unit_lib.Unit();
    this.unit.server = this;

    this.unit.createServer();

    this.socket = Socket;
    this.request = ServerRequest;
    this.response = ServerResponse;

    if (requestListener) {
        this.on('request', requestListener);
    }
}
util.inherits(Server, EventEmitter);

Server.prototype.setTimeout = function setTimeout(msecs, callback) {
    this.timeout = msecs;

    if (callback) {
        this.on('timeout', callback);
    }

    return this;
};

Server.prototype.listen = function () {
    this.unit.listen();
};

Server.prototype.emit_events = function (server, req, res) {
    req.server = server;
    res.server = server;
    req.res = res;
    res.req = req;

    server.buffer = server.unit._read(req.socket.req_pointer);

    server.emit("request", req, res);

    process.nextTick(() => {
        req.emit("finish");
        req.emit("end");
    });
};

function connectionListener(socket) {
}

module.exports = {
    STATUS_CODES: http.STATUS_CODES,
    Server,
    ServerResponse,
    ServerRequest,
    _connectionListener: connectionListener
};