Skip to content

Commit 2527543

Browse files
feat: add the "initial_headers" and "headers" events
Those events will be emitted before the response headers are written to the socket: - "initial_headers": on the first request of the connection - "headers": on all requests (HTTP long-polling and WebSocket upgrade) Syntax: ```js server.on("initial_headers", (headers, req) => { headers["test"] = "123"; headers["set-cookie"] = "mycookie=456"; }); server.on("headers", (headers, req) => { headers["test"] = "789"; }); ``` Related: - #557 - socketio/socket.io#3630
1 parent 7096e98 commit 2527543

File tree

6 files changed

+224
-55
lines changed

6 files changed

+224
-55
lines changed

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,19 @@ The main server/manager. _Inherits from EventEmitter_.
204204
- Fired when a new connection is established.
205205
- **Arguments**
206206
- `Socket`: a Socket object
207+
208+
- `initial_headers`
209+
- Fired on the first request of the connection, before writing the response headers
210+
- **Arguments**
211+
- `headers` (`Object`): a hash of headers
212+
- `req` (`http.IncomingMessage`): the request
213+
214+
- `headers`
215+
- Fired on the all requests of the connection, before writing the response headers
216+
- **Arguments**
217+
- `headers` (`Object`): a hash of headers
218+
- `req` (`http.IncomingMessage`): the request
219+
207220
- `connection_error`
208221
- Fired when an error occurs when establishing the connection.
209222
- **Arguments**

lib/server.js

+32-9
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,25 @@ class Server extends EventEmitter {
8484
perMessageDeflate: this.opts.perMessageDeflate,
8585
maxPayload: this.opts.maxHttpBufferSize
8686
});
87+
88+
if (typeof this.ws.on === "function") {
89+
this.ws.on("headers", (headersArray, req) => {
90+
// note: 'ws' uses an array of headers, while Engine.IO uses an object (response.writeHead() accepts both formats)
91+
// we could also try to parse the array and then sync the values, but that will be error-prone
92+
const additionalHeaders = {};
93+
94+
const isInitialRequest = !req._query.sid;
95+
if (isInitialRequest) {
96+
this.emit("initial_headers", additionalHeaders, req);
97+
}
98+
99+
this.emit("headers", additionalHeaders, req);
100+
101+
Object.keys(additionalHeaders).forEach(key => {
102+
headersArray.push(`${key}: ${additionalHeaders[key]}`);
103+
});
104+
});
105+
}
87106
}
88107

89108
/**
@@ -328,15 +347,19 @@ class Server extends EventEmitter {
328347
const socket = new Socket(id, this, transport, req, protocol);
329348
const self = this;
330349

331-
if (this.opts.cookie) {
332-
transport.on("headers", headers => {
333-
headers["Set-Cookie"] = cookieMod.serialize(
334-
this.opts.cookie.name,
335-
id,
336-
this.opts.cookie
337-
);
338-
});
339-
}
350+
transport.on("headers", (headers, req) => {
351+
const isInitialRequest = !req._query.sid;
352+
353+
if (isInitialRequest) {
354+
if (this.opts.cookie) {
355+
headers["Set-Cookie"] = [
356+
cookieMod.serialize(this.opts.cookie.name, id, this.opts.cookie)
357+
];
358+
}
359+
this.emit("initial_headers", headers, req);
360+
}
361+
this.emit("headers", headers, req);
362+
});
340363

341364
transport.onRequest(req);
342365

lib/socket.js

+41-42
Original file line numberDiff line numberDiff line change
@@ -81,49 +81,48 @@ class Socket extends EventEmitter {
8181
* @api private
8282
*/
8383
onPacket(packet) {
84-
if ("open" === this.readyState) {
85-
// export packet event
86-
debug("packet");
87-
this.emit("packet", packet);
88-
89-
// Reset ping timeout on any packet, incoming data is a good sign of
90-
// other side's liveness
91-
this.resetPingTimeout(
92-
this.server.opts.pingInterval + this.server.opts.pingTimeout
93-
);
84+
if ("open" !== this.readyState) {
85+
return debug("packet received with closed socket");
86+
}
87+
// export packet event
88+
debug(`received packet ${packet.type}`);
89+
this.emit("packet", packet);
90+
91+
// Reset ping timeout on any packet, incoming data is a good sign of
92+
// other side's liveness
93+
this.resetPingTimeout(
94+
this.server.opts.pingInterval + this.server.opts.pingTimeout
95+
);
9496

95-
switch (packet.type) {
96-
case "ping":
97-
if (this.transport.protocol !== 3) {
98-
this.onError("invalid heartbeat direction");
99-
return;
100-
}
101-
debug("got ping");
102-
this.sendPacket("pong");
103-
this.emit("heartbeat");
104-
break;
105-
106-
case "pong":
107-
if (this.transport.protocol === 3) {
108-
this.onError("invalid heartbeat direction");
109-
return;
110-
}
111-
debug("got pong");
112-
this.schedulePing();
113-
this.emit("heartbeat");
114-
break;
115-
116-
case "error":
117-
this.onClose("parse error");
118-
break;
119-
120-
case "message":
121-
this.emit("data", packet.data);
122-
this.emit("message", packet.data);
123-
break;
124-
}
125-
} else {
126-
debug("packet received with closed socket");
97+
switch (packet.type) {
98+
case "ping":
99+
if (this.transport.protocol !== 3) {
100+
this.onError("invalid heartbeat direction");
101+
return;
102+
}
103+
debug("got ping");
104+
this.sendPacket("pong");
105+
this.emit("heartbeat");
106+
break;
107+
108+
case "pong":
109+
if (this.transport.protocol === 3) {
110+
this.onError("invalid heartbeat direction");
111+
return;
112+
}
113+
debug("got pong");
114+
this.schedulePing();
115+
this.emit("heartbeat");
116+
break;
117+
118+
case "error":
119+
this.onClose("parse error");
120+
break;
121+
122+
case "message":
123+
this.emit("data", packet.data);
124+
this.emit("message", packet.data);
125+
break;
127126
}
128127
}
129128

lib/transports/polling.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ class Polling extends Transport {
385385
headers["X-XSS-Protection"] = "0";
386386
}
387387

388-
this.emit("headers", headers);
388+
this.emit("headers", headers, req);
389389
return headers;
390390
}
391391
}

lib/transports/websocket.js

-3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ class WebSocket extends Transport {
1414
this.socket.on("message", this.onData.bind(this));
1515
this.socket.once("close", this.onClose.bind(this));
1616
this.socket.on("error", this.onError.bind(this));
17-
this.socket.on("headers", headers => {
18-
this.emit("headers", headers);
19-
});
2017
this.writable = true;
2118
this.perMessageDeflate = null;
2219
}

test/server.js

+137
Original file line numberDiff line numberDiff line change
@@ -3137,6 +3137,143 @@ describe("server", () => {
31373137
};
31383138
testForHeaders(headers, done);
31393139
});
3140+
3141+
it("should emit a 'initial_headers' event (polling)", done => {
3142+
const partialDone = createPartialDone(done, 2);
3143+
3144+
engine = listen({ cookie: true }, port => {
3145+
engine.on("initial_headers", (headers, req) => {
3146+
expect(req.method).to.be("GET");
3147+
headers["test"] = "123";
3148+
headers["set-cookie"] = "mycookie=456";
3149+
partialDone();
3150+
});
3151+
3152+
request
3153+
.get("http://localhost:%d/engine.io/".s(port))
3154+
.query({ transport: "polling" })
3155+
.end((err, res) => {
3156+
expect(err).to.be(null);
3157+
expect(res.status).to.be(200);
3158+
expect(res.headers["test"]).to.be("123");
3159+
expect(res.headers["set-cookie"].length).to.be(2);
3160+
expect(res.headers["set-cookie"][1]).to.be("mycookie=456");
3161+
3162+
const sid = JSON.parse(res.text.substring(4)).sid;
3163+
3164+
request
3165+
.post("http://localhost:%d/engine.io/".s(port))
3166+
.query({ transport: "polling", sid })
3167+
.send("1:6")
3168+
.end((err, res) => {
3169+
expect(err).to.be(null);
3170+
expect(res.status).to.be(200);
3171+
expect(res.headers["test"]).to.be(undefined);
3172+
expect(res.headers["set-cookie"]).to.be(undefined);
3173+
partialDone();
3174+
});
3175+
});
3176+
});
3177+
});
3178+
3179+
it("should emit a 'headers' event (polling)", done => {
3180+
const partialDone = createPartialDone(done, 3);
3181+
3182+
engine = listen({ cookie: true }, port => {
3183+
engine.on("headers", headers => {
3184+
headers["test"] = "123";
3185+
headers["set-cookie"] = "mycookie=456";
3186+
partialDone();
3187+
});
3188+
3189+
request
3190+
.get("http://localhost:%d/engine.io/".s(port))
3191+
.query({ transport: "polling" })
3192+
.end((err, res) => {
3193+
expect(err).to.be(null);
3194+
expect(res.status).to.be(200);
3195+
expect(res.headers["test"]).to.be("123");
3196+
expect(res.headers["set-cookie"].length).to.be(2);
3197+
expect(res.headers["set-cookie"][1]).to.be("mycookie=456");
3198+
3199+
const sid = JSON.parse(res.text.substring(4)).sid;
3200+
3201+
request
3202+
.post("http://localhost:%d/engine.io/".s(port))
3203+
.query({ transport: "polling", sid })
3204+
.send("1:6")
3205+
.end((err, res) => {
3206+
expect(err).to.be(null);
3207+
expect(res.status).to.be(200);
3208+
expect(res.headers["set-cookie"].length).to.be(1);
3209+
expect(res.headers["set-cookie"][0]).to.be("mycookie=456");
3210+
partialDone();
3211+
});
3212+
});
3213+
});
3214+
});
3215+
3216+
it("should emit a 'initial_headers' event (websocket)", function(done) {
3217+
if (process.env.EIO_WS_ENGINE === "eiows") {
3218+
this.skip();
3219+
}
3220+
const partialDone = createPartialDone(done, 2);
3221+
3222+
engine = listen({ cookie: true }, port => {
3223+
engine.on("initial_headers", (headers, req) => {
3224+
expect(req.method).to.be("GET");
3225+
headers["test"] = "123";
3226+
headers["set-cookie"] = "mycookie=456";
3227+
partialDone();
3228+
});
3229+
3230+
client = eioc("ws://localhost:%d".s(port), {
3231+
transports: ["websocket"]
3232+
});
3233+
3234+
client.transport.ws.on("upgrade", res => {
3235+
expect(res.headers["test"]).to.be("123");
3236+
expect(res.headers["set-cookie"].length).to.be(1);
3237+
expect(res.headers["set-cookie"][0]).to.be("mycookie=456");
3238+
partialDone();
3239+
});
3240+
});
3241+
});
3242+
3243+
it("should emit a single 'initial_headers' event per connection", done => {
3244+
const partialDone = createPartialDone(done, 2);
3245+
3246+
engine = listen(port => {
3247+
engine.on("initial_headers", () => {
3248+
partialDone();
3249+
});
3250+
3251+
client = eioc("ws://localhost:%d".s(port));
3252+
3253+
client.on("upgrade", () => {
3254+
partialDone();
3255+
});
3256+
});
3257+
});
3258+
3259+
it("should emit several 'headers' events per connection", function(done) {
3260+
if (process.env.EIO_WS_ENGINE === "eiows") {
3261+
this.skip();
3262+
}
3263+
const partialDone = createPartialDone(done, 4);
3264+
3265+
engine = listen(port => {
3266+
engine.on("headers", () => {
3267+
partialDone();
3268+
});
3269+
3270+
client = eioc("ws://localhost:%d".s(port));
3271+
3272+
client.on("upgrade", () => {
3273+
partialDone();
3274+
});
3275+
});
3276+
});
31403277
});
31413278

31423279
describe("cors", () => {

0 commit comments

Comments
 (0)