How to detect user cancels request - http

I'm trying out Node.js by writing a very basic http/web caching proxy, and have hit something I haven't managed to break through.
Assuming I have a very basic proxy functionality (listen to request, pipe it to external server, wait for response, pipe it back to client), how do I detect when the client (web browser) cancels the request? When the user clicks "Stop"/Esc on his browser, the browser doesn't send me any "request" or info and attaching a callback for when the "response" connection ends doesn't get called.
Here's what I mean:
http.createServer (clientRequest, clientResponse) {
var client = http.createClient (port, hostname);
var request = client.request (method, url, headers);
request.addListener ('response', function(response){
response.addListener ('data', function(chunk){
// forward data to clientResponse..
}
response.addListener ('end', function(){
// end clientResponse..
}
});
clientResponse.addListener('end', function(){
// this never gets called :(
// I want it to terminate the request/response created just above
}
}

Turns out I should be binding to the "close" event instead of the "end" event of the request.
That does actually make sense.
I'm posting this here for anyone else who might encounter the same issue:
clientResponse.addListener('close', function(){
// this gets called when the user terminates his request
}

Related

How to make asynchronous calls from external services to actions on google?

I'm trying to connect Google Home to an external chatbot with actionssdk. I have an API that take user inputs and send them to my chatbot with webhook, but my chatbot make a response calling another endpoint of my API in an async way, and I can't show the response in actions on Google or Google Home.
I create an actionssdkApp.
const {
actionssdk,
SimpleResponse,
Image,
} = require('actions-on-google');
var app = actionssdk();
var express_app = express();
My API has 2 endpoints. One of them is for actions on google to send user inputs to my chatbot:
app.intent('actions.intent.MAIN', conv => {
console.log('entra en main');
conv.ask('Hi, how is it going?');
});
app.intent('actions.intent.TEXT', (conv, input) => {
var userId = conv.body.user.userId;
console.log(userId);
if(userId && input){
textFound(conv, input, userId);
}else{
textnotFound(conv);
}
});
TextFound function send user inputs to my chatbot with webhook, but the request doesn't receive the response. My chatbot call another endpoint with the text answer:
express_app.post('/webhook', bodyParser.json(), (req, res)=>{
console.log("Webhook");
const userId = req.body.userId;
if (!userId) {
return res.status(400).send('Missing User ID');
}
console.log(req.body);
res.sendStatus(200);
});
And here is where I want to send the answer to Google Home. But I need the conv object to show the answer in google Home, or actions on google, or any other device.
Edit:
My textFound function:
webhook.messageToBot(metadata.channelUrl, metadata.channelSecretKey, userId, input, function(err){
if(err){
console.log('Error in sending message');
conv.ask("Error in sending message");
}else{
conv.ask("some text");
}
});
From here my api send user inputs to my bot through messageToBot function:
request.post({
uri: channelUrl,
headers: headers,
body: body,
timeout: 60000,
followAllRedirects: true,
followOriginalHttpMethod: true,
callback: function(err, res, body) {
if (err) {
console.log('err: '+err);
callback(err);
} else {
console.log('Message sent');
callback(null);
}
}
});
From now on, my bot doesn't send a response but makes a call to /webhook endpoint of my api with the answer. But in this function I haven't de conv object and I can't send the answer to google. I don't know how to access to this object. Maybe there is an uri to connect with my project in actions on google from my api.
Typically, Actions on Google works in a request-response way. The user says something to the Action, and the Action replies with a response. That reply needs to come within about 5 seconds. If you think the call to /webhook can come that quickly, and you will only deliver a message to the user after they say something, you can have /webhook save the response in a queue for the user, and have your Intent handler be in a loop that checks this queue for any messages to reply with - if there is a message within 5 seconds, you reply with it, if not, you need to reply before the 5 seconds are up.
If you can't guarantee it will be done within 5 seconds, however, there are a couple of workarounds that might be useful depending on your needs.
The first is that you might be able to use notifications. In this scenario, you would send the message from the user and then close the conversation. When your /webhook endpiont is triggered, you would locate the user and send the notification to their Assistant. Unfortunately, this is a bit bulky, doesn't lead to a very interactive chat system, and notifications also aren't supported on smart speakers.
You can also look into using a Media Response to set up a way for you to poll for new messages periodically. Under this scheme, your user would send their message. In your reply to them, you would include a Media Response for some audio that plays for, say, 15 seconds. When the audio finishes, your Action will be called again and you can check to see if any messages have been queued up to be delivered to the user. If so, you relay those messages, followed by a Media Response gain. Otherwise, just send a Media Response. Your call to /webhook would have to put messages in a queue to be delivered to the user. This is more complex, especially to scale, but can be made more interactive. It is also a more general case of trying to handle it in a loop inside 5 seconds.

Can I delay PUT upload in Express until my server finishes an operation?

I have a server that receives files via PUT and in turn stores them in S3.
After a client PUTs files it will usually request them right in return for rendering. Unfortunately my server takes a bit of time to actually process the upload and store them in S3.
Here is the code I use in Express:
req.pipe(writeStream);
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'PUT, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
if ('OPTIONS' === req.method) {
res.sendStatus(200);
res.end('OK!');
return;
}
if ('PUT' === req.method) {
writeStream.on('error', () => {
res.sendStatus(500);
});
writeStream.on('close', async () => {
// This might take 30s
await this.storage.store(fn, args.blobID);
// This does not seem to have any effect
// I would like to prevent the client from 'finishing' the
// PUT request before I say it is done.
res.end('OK!')
});
res.sendStatus(200);
}
Ideally I could 'stall' my client's PUT request for until storage.store() completed, making sure the file is actually available.
Is there any way how I could do this?
Also, I am not sure about the sendStatus() / end() calls, feel free to comment if I mixed something up.
P.S. I know, in an ideal world I would just hand out signed URLs and let the client upload to S3 directly, but I need AES256 encryption ...
I'm not familiar with S3 uploading, but I guess that there will be a callback(such as sending back a response(success or fail... etc) to you). (If not, please tell me)
S3.upload(data,function(response){ // something like this
// then, you can callback
// ...
// res.end() or res.sendStatus(...)
})
As I remember it, Express don't send timeout automatically, so you just put response in the S3's response function, so that your request should not end until your storing end.
From Must res.end() be called in express with node.js?
You don't have to call res.end() if you call res.send(). res.send() calls res.end() for you.
and res.sendStatus() equals to res.status(...).send(message or code) (via http://expressjs.com/en/api.html)
So, res.sendStatus() will call end().

Is there a way to be notified when a client has unsubscribe from server sent events?

As I understand when a request to an event emitter on the server arrives, that request is never closed and you only need to res.write() every time you would like to send a message. However is there a way to be notified when the client that performed this request has left? Is there a property on the request object?
suppose I have the following route
app.get('/event',function(req,res){
//set response headers
//how do I check if req object is still active to send a message and perform other actions?
})
The basic sequence of events should be similar in other frameworks, but this example is Grails 3.3.
First set up endpoints to subscribe, and to close the connection.
def index() {
// handler for GET /api/subscribe
rx.stream { Observer observer ->
// This is the Grails event bus. background tasks,
// services and other controllers can post these
// events, CLIENT_HANGUP, SEND_MSG, which are
// just string constants.
eventBus.subscribe(CLIENT_HANGUP) {String msg ->
// Code to handle when the grails event bus
// posts CLIENT_HANGUP
// Do any side effects here, like update your counter
// Close the SSE connection
observer.onCompleted()
return
}
eventBus.subscribe(SEND_MSG) {String msg ->
// Send a Server Sent Event
observer.onNext(rx.respond(msg))
}
}
}
def disconnecting()
{
// handler for GET /api/disconnect
// Post the CLIENT_HANGUP event to the Grails event bus
notify(CLIENT_HANGUP, 'disconnect')
}
Now in the client, you need to arrange to GET /api/disconnect whenever your use-case requires it. Assuming you want to notice when someone navigates away from your page, you could register a function on window.onbeforeunload. This example is using Vue.js and Axios.
window.onbeforeunload = function (e) {
e.preventDefault()
Vue.$http({
method: 'get',
url: 'http://localhost:8080/api/disconnect'
})
.then((response) => { console.log(response) })
.catch(({error}) => { console.log(error) })
}
In the case of Servlet stacks like Grails, I found that I needed to do this even if I had no housekeeping of my own to do when the browser went away. Without it, page reloads were causing IOExceptions on the back end.

Meteor http calls limitations

Currently, I use the built-in meteor http method (see http://docs.meteor.com/#http) for issuing http calls, on both my client and my server.
However, I'm experiencing two issues:
is it possible to cancel a request?
is it possible to have multiple query parameters which share the same key?
Are these just Meteor limitations, or are there ways to get both to work using Meteor?
I know I could you jquery on the clientside, and there must be a server-side solution which supports both as wel, but I'd prefer sticking with meteor code here.
"is it possible to cancel a request?"
HTTP.call() does not appear to return an object on which we could call something like a stop() method. Perhaps a solution would be to prevent execution of your callback based on a Session variable?
HTTP.call("GET", url, function(error, result) {
if (!Session.get("stopHTTP")) {
// Callback code here
}
});
Then when you reach a point where you want to cancel the request, do this:
Session.set("stopHTTP", true);
On the server, instead of Session perhaps you could use an environment variable?
Note that the HTTP.call() options object does accept a timeout key, so if you're just worried about the request never timing out, you can set this to whatever millisecond integer you want.
"is it possible to have multiple query parameters which share the same key?"
Yes, this appears to be possible. Here's a simple test I used:
Meteor code:
HTTP.call("GET", "http://localhost:1337", {
query: "id=foo&id=bar"
}, function(error, result) {
// ...
});
Separate Node.js server: (just the basic example on the Node.js homepage, with a console.log line to output the request URL with query string)
var http = require('http');
http.createServer(function(req, res) {
console.log(req.url); // Here I log the request URL, with the query string
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
When the Meteor server is run, the Node.js server logged:
/?id=foo&id=bar
Of course, this is only for GET URL query parameters. If you need to do this for POST params, perhaps you could store the separate values as a serialized array string with EJSON.stringify?

Setting a timeout for ServerXMLHTTP request

Does anyone know how to set up set up a default action for when a ServerXMLHTTP request times out? I'm using setTimeouts() to set the time out options according to the MSDN site.
Ideally I would like to initialize the request again from the beginning or refresh the page should it time out.
I'm using classic asp and jscript.
Here's my request:
function serverXmlHttp(url) {
var serverXmlHttp;
serverXmlHttp = Server.CreateObject("Msxml2.ServerXMLHTTP.6.0");
// set time out options
serverXmlHttp.setTimeouts(15000,15000,15000,15000);
// does not work
// serverXmlHttp.ontimeout(Response.Write("page has timed out"));
serverXmlHttp.open("GET", url, false);
serverXmlHttp.send();
if (serverXmlHttp.readyState == 4) {
return serverXmlHttp.responseText;
}
}
The important thing is to find out why it is timing out ..
Is the remote Url on the same application as the calling page ?
if so have a look at INFO: Do Not Send ServerXMLHTTP or WinHTTP Requests to the Same Server as you will be facing thread starvation ..
Figured it out. I just need to use a try/catch statement.
function serverXmlHttp(url) {
try {
var serverXmlHttp;
serverXmlHttp = Server.CreateObject("Msxml2.ServerXMLHTTP.6.0");
// set time out options
serverXmlHttp.setTimeouts(15000,15000,15000,15000);
serverXmlHttp.open("GET", url, false);
serverXmlHttp.send();
if (serverXmlHttp.readyState == 4) {
return serverXmlHttp.responseText;
}
catch(error) {
// whatever I want to happen if there's an error
Response.Write("Sorry, your request has timed out");
}
}

Resources