Skip to content

Commit

Permalink
Task/function to overwrite response headers (#104)
Browse files Browse the repository at this point in the history
* Use setResponseStatus to overwrite response status code

    - rename from setResponseHeaders
    - add class attribute for headers and status code
    - if status code is set, then dont use the one generated from the message
    (this allows a user to overwrite the status if its not one of the ones in errorToStatus)
    - send response for notification in gotNotification
    - Fix gotNotification doc

* Use .end() to send body data in response.write() and writeHead()

* Fix doc for gotError

* Add tests for setResponseStatus and overrides

* Call messageBuffer and _waitForData when client request comes in

* Update doc for clientConnected
  • Loading branch information
isaacgr committed Sep 11, 2021
1 parent 1c787da commit 467fd3c
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 41 deletions.
4 changes: 2 additions & 2 deletions src/server/protocol/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ class JsonRpcServerProtocol {
* Calls `emit` on factory with the event name being `message.method` and
* the date being `message`.
*
* @param {string} message Stringified JSON-RPC message object
* @param {string} message JSON-RPC message object
*/
gotNotification(message) {
this.factory.emit(message.method, message);
Expand Down Expand Up @@ -337,7 +337,7 @@ class JsonRpcServerProtocol {
* Writes error to the client. Will send a JSON-RPC error object if the
* passed error cannot be parsed.
*
* @param {string} error Stringified error object
* @param {Error} error `Error` object instance where the message should be a JSON-RPC message object
*/
gotError(error) {
let err;
Expand Down
86 changes: 47 additions & 39 deletions src/server/protocol/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,81 +12,89 @@ class HttpServerProtocol extends JsonRpcServerProtocol {
* the following properties are available.
*
* @property {class} response HTTP response object
*
* @property {object} headers={"Content-Type":"application/json"} HTTP response headers
*/
constructor(factory, client, response, version, delimiter) {
super(factory, client, version, delimiter);
this.response = response;
this.headers = {
"Content-Type": "application/json"
};
this.status = null;
}

/**
* Send message to the client. If a notification is passed, then
* a 204 response code is sent.
* Send message to the client.
*
* @param {string} message Stringified JSON-RPC message object
* @param {boolean} notification Indicates if message is a notification
*/
writeToClient(message, notification) {
if (notification) {
this._setResponseHeader({
response: this.response,
notification: true
});
this.response.end();
return;
}
writeToClient(message) {
const json = JSON.parse(message);
const header = { response: this.response };
if ("error" in json && json.error !== null) {
header.errorCode = json.error.code;
if (!this.status) {
// set the status code if it has not been overwritten
if (json.error) {
this.setResponseStatus({
errorCode: json.error.code
});
} else {
this.status = 200;
}
}
this._setResponseHeader(header);
this.response.write(message, () => {
this.response.end();
});
this.response.writeHead(this.status, this.headers).end(message);
}

/**
* Calls `emit` on factory with the event name being `message.method` and
* the date being `message`. Responds to client.
* the data being `message`.
*
* @param {string} message Stringified JSON-RPC message object
* Responds to client with 204 status.
*
* @param {object} message JSON-RPC message object
*/
gotNotification(message) {
super.gotNotification(message.method, message);
this.writeToClient(message, true);
this.setResponseStatus({
notification: true
});
this.response.writeHead(this.status, this.headers).end();
}

/** @inheritdoc */
/**
* Registers the `event` data listener when client connects.
*
* Pushes received data into `messageBuffer` and calls
* [_waitForData]{@link JsonRpcServerProtocol#_waitForData}.
*
* @extends JsonRpcServerProtocol.clientConnected
*/
clientConnected() {
this.client.on(this.event, (data) => {
this.client.on("end", () => {
this._validateData(data);
this.messageBuffer.push(data);
this._waitForData();
});
});
}

/**
* Set response header and response code
* Set response status code
*
* @param {object} options
* @param {class} options.response Http response instance
* @param {boolean} options.notification Inidicate if setting header for notification
* @param {number} options.errorCode The JSON-RPC error code to lookup a corresponding status code for
* @private
* @param {number} options.status The HTTP status code (will override the errorCode)
* @param {boolean} options.notification Inidicate if setting header for notification (will override other options with 204 status)
*/
_setResponseHeader({ response, errorCode, notification }) {
let statusCode = 200;
if (notification) {
statusCode = 204;
}
const header = {
"Content-Type": "application/json"
};
setResponseStatus({ errorCode, status, notification }) {
this.status = 200;
if (errorCode) {
statusCode = errorToStatus[String(errorCode)];
this.status = errorToStatus[String(errorCode)];
}
if (status) {
this.status = status;
}
if (notification) {
this.status = 204; // notification responses must be 204 per spec, so no point in allowing an override
}
response.writeHead(statusCode, header);
}
}

Expand Down
61 changes: 61 additions & 0 deletions tests/server/http-server-protocol.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const { expect } = require("chai");
const Jaysonic = require("../../src/");
const HttpServerFactory = require("../../src/server/http");
const HttpServerProtocol = require("../../src/server/protocol/http");

class Protocol extends HttpServerProtocol {
constructor(factory, client, response, version, delimiter) {
super(factory, client, response, version, delimiter);
this.status = 404;
}
}

class Factory extends HttpServerFactory {
constructor(options) {
super(options);
this.protocol = Protocol;
}
}

const server = new Factory({
port: 6969
});
const client = new Jaysonic.client.http({ port: 6969 });

describe("HTTP Server Protocol Status Override", () => {
before((done) => {
server.listen().then(() => {
done();
});
});
after((done) => {
server.close().then(() => {
done();
});
});
it("should overwrite status code", (done) => {
client.send("foo").catch((e) => {
expect(e.body.error.code).to.be.eql(-32601);
expect(e.statusCode).to.be.eql(404);
done();
});
});
it("should overwrite errorCode if status is passed", (done) => {
class OverwritePcol extends HttpServerProtocol {
clientConnected() {
this.setResponseStatus({ errorCode: 400, status: 301 });
this.gotError(
new Error(
"{\"jsonrpc\": \"2.0\", \"error\": {\"code\": -32600, \"message\": \"Invalid Request\"}, \"id\": 2}"
)
);
}
}
server.protocol = OverwritePcol;
client.send("foo").catch((e) => {
expect(e.body.error.code).to.be.eql(-32600);
expect(e.statusCode).to.be.eql(301);
done();
});
});
});

0 comments on commit 467fd3c

Please sign in to comment.