Overview of app
I have a node.js server application implemented with the express.js 4 module and the node.js core http module. At a high level, the app takes incoming client http messages, makes various http calls (using http module) to other external APIs, and lastly sends back a response to the client based on the responses from the aforementioned various external http API responses.
The Issue
My issue is that when the incoming client http request is terminated by the client (e.g. when the client wants to cancel their request), my node.js app continues to proceed in making the aforementioned various external http API calls. I cannot seem to find a way to signal to the rest of my node.js app to terminate its various outgoing http requests to external APIs in such cases.
When the client terminates their request, the express app (i.e. the express http server) receives a "close" event, which I'm listening for. The "close" event listener in my code catches this event; however, I cannot seem to figure out how to then signal to the "downstream" or "subsequent" http requests made by my code to terminate.
My Goal
How can I signal to all the outgoing http requests to external APIs which are associated with a single client incoming request to terminate when the client terminates their incoming request to my service?
I've provided a simplified version of my node.js app below with some inline code comments to help illustrate my issue more clearly. Any help or insight would be very much appreciated. Thanks!
Additional Info
I'm using the Apigee swagger-tools middleware to do my api routing.
I've found a few answered questions out there which are similar but not quite directly applicable to my question:
Handling cancelled request with Express/Node.js and Angular
How to detect user cancels request
Best,
Chris
test-app.js
// test-app.js
"use strict";
var swaggerTools = require("swagger-tools");
var app = require("express")();
// swaggerRouter configuration
// sends incoming http messages to test-controller.js
var options = {
controllers: './controllers'
};
// The Swagger document (require it, build it programmatically, fetch it from a URL, ...)
// describes the API specification
var apiSpec = require('./test-swagger.json');
// Initialize the Swagger middleware
swaggerTools.initializeMiddleware(apiSpec, function (middleware) {
"use strict"
// Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain
app.use(middleware.swaggerMetadata());
// Validate Swagger requests/responses based on test-swagger.json API specification
app.use(middleware.swaggerValidator());
// Route validated requests to appropriate controller, test-controller.js
app.use(middleware.swaggerRouter(options));
});
// Run http server on port 8080
app.listen(8080, function () {
"use strict";
console.log("Server running on port %d", this.address().port);
})
.on("connection", function (socket) {
console.log("a new connection was made by an incoming client request.");
socket.on("close", function () {
console.log("socket connection was closed by client");
// somehow signal to the rest of my node.js app to terminate any
// http requests being made to external APIs, e.g. twitter api
socket.destroy();
});
})
test-controller.js
//test-controller.js
"use strict";
var http = require("https");
// only one function currently, consequently, all incoming http requests are
// routed to this function, i.e. "compile"
module.exports = {
compile: compile
};
function compile(req, res, next) {
var options = {
"method": "GET",
"hostname": "api.twitter.com",
"path": "/1.1/statuses/mentions_timeline.json?count=2&since_id=14927799",
"headers": {"accept": "application/json"}
};
// how can i terminate this request when the http.server in test-app.js receives the "close" event?
http.request(options)
.on("response", function(response) {
var apiResponse = [];
response.on("data", function (chunk) {
apiResponse.push(chunk);
});
response.on("end", function () {
apiResponse = Buffer.concat(apiResponse).toString();
res.status(response.statusCode).set(response.headers).send(apiResponse);
});
})
}
In your test controller's compile method you should just be able to do something like this:
var request = http.request(options, function (response) {
res.writeHead(response.statusCode, response.headers)
response.pipe(res, { end: true })
})
req.on('close', function(){
request.abort()
})
Related
I'm writing an http server application in node js. I want to have an event that triggers when an http exchange is over but I couldn't figure out how to do it. I tried to do it the way it would be done with other streams in node js using .on('end', callback) but it doesn't seem to work:
what I tried (which doesn't work):
const Http = require('http');
const httpServer = Http.createServer((httpRequest, httpResponse) => {
console.log('<started>');
httpResponse.on('end', () => console.log('<ended>'));
httpResponse.end('END');
console.log('<finished>');
});
httpServer.listen(80);
what I expect my console to print after navigating to localhost in my browser:
<started>
<finished>
<ended>
what I really get:
<started>
<finished>
How to do this correctly? I want a callback that triggers when the server finished streaming the response stream for this http request to the client.
httpResponse is a http.IncomingMessage, you can listen to close event (instead of end), the event will be emitted when the request has been completed.
const Http = require('http');
const httpServer = Http.createServer((httpRequest, httpResponse) => {
console.log('<started>');
httpResponse.on('close', () => console.log('<ended>'));
httpResponse.end('END');
console.log('<finished>');
});
httpServer.listen(80);
I am building my app:
Frontend: ReactJS / Javascript Native Websocket:
In my component that uses websocket:
useEffect(() => {
const orgId = localData.get('currentOrganizationId');
const username = localData.get('username');
let socket = new WebSocket(process.env.REACT_APP_WEBSOCKET_URL); // this is the AWS WebSocket URL after I have deployed it.
// let socket = new WebSocket("ws://127.0.0.1:3001");
socket.onopen = function(e) {
console.log('socket on onopen');
const info = JSON.stringify({orgId:orgId, username: username, action: "message"});
socket.send(info);
};
socket.onmessage = function(event) {
console.log(`[message] Data received from server: ${event.data}`);
};
socket.onclose = function(event) {
if (event.wasClean) {
console.log(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
} else {
console.log(`[close] Connection died; code=${event.code}`);
}
};
socket.onerror = function(error) {
console.log(`[error] ${error.message}`);
};
}, [])
Backend: NodeJS / ExpressJS / ws websocket library:
./utils/websocket:
import WebSocket from "ws";
import queryString from "query-string";
import { StateManager } from '../state';
import logger from '../logger';
export default async (expressServer) => {
StateManager.addToState('sockets', {});
const websocketServer = new WebSocket.Server({
noServer: true,
// path: "/websockets",
path: "/",
});
expressServer.on("upgrade", (request, socket, head) => {
logger.info(`on upgrade: ${JSON.stringify(head)}`);
websocketServer.handleUpgrade(request, socket, head, (websocket) => {
websocketServer.emit("connection", websocket, request);
});
});
websocketServer.on("connection", function connection(websocketConnection, connectionRequest) {
logger.info(`on connection`);
websocketConnection.on("message", (message) => {
const info = JSON.parse(message);
logger.info(`info: ${typeof info} ${info}`);
const sockets = StateManager.readFromState('sockets');
sockets[info.username] = websocketConnection;
});
});
return websocketServer;
};
and in index.ts:
import websocket from './utils/websocket';
...other code
const app = express();
...other code
const server = app.listen(parseInt(process.env.PORT) || 3001);
websocket(server);
While this works well in local dev environment, I am really confused on how to move this to AWS like this:
Frontend WebSocket client ---> AWS API Gateway Websocket API ----> Backend in EC2 instance
I have configured like this:
I have read the document, but I am still confused about where to start. How should I configure the $connect, $disconnect, $default routes?
How are they related to my current websocket client and nodejs server running in EC2?
How should I change the code to integrate it with AWS API Gateway Websocket API?
Currently there is simply no response:
The Websocket URL I provided is indeed correct though.
Even after reading the document, I am still confused about where to start fixing this issue.
The reason why there is no response when you connect to the websocket is because you do not have the HTTP endpoint setup in your backend express app.
When you connect to the AWS API Gateway WebSocket API, WebSocket API takes action set by your $connect integration. In your current configuration, you have set the VPC Link integration with HTTP Method Any on the target url. Thus, for your backend endpoint to be called, you need to create a HTTP endpoint and set the address of the endpoint to "Endpoint URL."
For example, let's say you have the following route set up.
app.get('/connect', function(req, res) {
res.send('success');
});
If you set the 'Endpoint URL' to '<BACKEND_URL>/connect', you will receive 'success' when you connect to the WebSocket.
In short, you don't need to be running the websocket server on your backend because WebSocket API takes care of it. Through the route integration, you just need to set up how different methods (which are provided by HTTP endpoints by your backend Node.js server) are invoked based on messages sent via WebSocket.
As a side note, I suggest one of the following options.
If you need to run your own backend, do not use AWS API Gateway WebSocket API because there is no need to. You can use AWS ALB instead.
If you don't have to run your own backend and if the features are not too complex, use AWS Lambda Integration with WebSocket API and take the real-time feature serverless.
-- Edit (another suggestion based on the comment) --
If you need to use API Gateway, you can set up the HTTP endpoints on your Node.js backend, and integrate it to the WebSocket API $connect integration. Then, the HTTP endpoint that you integrated will be invoked upon connection.
I am new to NODE JS + GAE so pls forgive newbie question.
I want my app to serve up a REST call that will consolidate the results of calls to other externally hosted API's (third parties - none GAE)
I have this javascript working from my PC but when I deploy to GAE i get "502 Bad Gateway" error (in my browser). I can't find anything in the GAE logs to help explain this error. Note my REST call is working (ie GAE is receiving the call) but is failing to make the outbound call to external web site.
Note here is sample NODE JS code that is failing (i am calling "/test"):
const express = require('express');
var http = require('http');
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
const app = express();
// [START hello_world]
app.get('/test', (req, res) => {
const Http = new XMLHttpRequest();
const url='https://www.google.com';
Http.open("GET", url);
Http.send();
Http.onreadystatechange=(e)=>{
console.log(Http.responseText)
}});
// [END hello_world]
if (module === require.main) {
// [START server]
// Start the server
const server = app.listen(process.env.PORT || 8080, () => {
const port = server.address().port;
console.log(`App listening on port ${port}`);
});
// [END server]
}
module.exports = app;
It takes a while to run from my browser (ie a minute) then I get the "502 Bad Gateway" error. I this a cross site issue, ie, is GAE stopping web services calls being made from NODE JS apps to other sites?
The issue is that your request is timing out, because you are not sending any response indicating that the request is done.
To solve this, you will need to return an response on your /test handler, for example, you can add this:
res.status(200).send("ok").end();
Which returns a 200 OK response to the request, indicating that it has been correctly processed.
This change, in your /test handler, should probably be added this way:
app.get('/test', (req, res) => {
const Http = new XMLHttpRequest();
const url='https://www.google.com';
Http.open("GET", url);
Http.send();
Http.onreadystatechange=(e)=>{
console.log(Http.responseText)
// Return response to '/test' handler request
res.status(200).send("ok").end();
}
});
I have made a React application which relies fully on WebSockets after the initial HTTP Upgrade. For security reasons i use a cookie AND a JWT token in my WebSockets connection.
It all works fine, but when opening a new tab, socket.io cookies get reissued and I want users to stay logged in over multiple tabs. So i want to set a cookie if the client doesn't already have one. If it already has one, then use that cookie.
So I want to handle the first HTTP polling requests and created middleware for that in Node's http server:
// HTTP SERVER
const server = require('http').createServer(function (request, response) {
console.log('test');
console.log(request);
if(!request.headers.cookie) { // cookie pseudo-logic
response.writeHead(200, {
'Set-Cookie': 'mycookie=test',
'Content-Type': 'text/plain'
});
}
// Socket.IO server instance
const io = require('socket.io')(server, {
origins: config.allowedOrigins,
cookie: false, // disable default io cookie
});
server.listen(port, () => console.log(`Listening on port ${port}`));
I use Socket.io as WebSockets framework. The problem however is that this middleware get's ignored, when registering the Socket.io server. When i comment out the Socket.io server, the middleware is active and the request get's logged.
It looks like Socket.io's server is overriding the handler for node http's server. In the Socket.io docs however they provide this example:
var app = require('http').createServer(handler)
var io = require('socket.io')(app);
var fs = require('fs');
app.listen(80);
function handler (req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
io.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
});
Thus indicating that it should be possible to handle thw first http polling requests and also the socket requests. I managed to get it work with Express, but I don't understand why node's http server can't.
Anybody who knows what's happening?
Thanks in advance,
Mike
Because normal usage of socket.io does not want regular http middleware to see socket.io connection requests (they would normally trigger 404 responses), socket.io places its own request handler first in line before any others, even ones that existed before it was installed.
You can see how it does that here: https://github.com/socketio/engine.io/blob/master/lib/server.js#L437 in the engine.io source.
I can think of the following ways for you to pre-process a request before socket.io sees it:
Use a proxy and do your cookie stuff in a proxy before socket.io even sees the request.
Patch socket.io/engine.io code to add a callback hook for what you want to do.
Copy the technique used by socket.io/engine.io to put your own request handler first in line after socket.io is configured.
Find a way to override the socket.io server object's handleRequest() method which is what gets called when there's an incoming connection request. You can see its code here.
I have setup a Primus websocket service as below.
http = require('http');
server = http.createServer();
Primus = require('primus');
primus = new Primus(server, {
transformer: 'websockets',
pathname: 'ws'
});
primus.on('connection', function connection(spark) {
console.log("client has connected");
spark.write("Herro Client, I am Server");
spark.on('data', function(data) {
console.log('PRINTED FROM SERVER:', data);
spark.write('receive '+data)
});
spark.on('error', function(data) {
console.log('PRINTED FROM SERVER:', data);
spark.write('receive '+data)
});
});
server.listen(5431);
console.log("Server has started listening");
It works fine. In above code, I use spark.write to send response message to users. Now I want to convert it to be used in a middleware.
The code becomes as below:
primus.use('name', function (req, res, next) {
doStuff();
});
in the doStuff() method, how I can get the spark instance to send message back to clients?
The readme is slightly vague about this, but middleware only deals with the HTTP request.
Primus has two ways of extending the functionality. We have plugins but also support middleware. And there is an important difference between these. The middleware layers allows you to modify the incoming requests before they are passed in to the transformers. Plugins allow you to modify and interact with the sparks. The middleware layer is only run for the requests that are handled by Primus.
To achieve what you want, you'll have to create a plugin. It's not much more complicated than middleware.
primus.plugin('herro', {
server: function(primus, options) {
primus.on('connection', function(spark) {
spark.write('Herro Client, I am Server')
})
},
client: function(primus, options) {}
})
For more info, see the Plugins section of the readme.