Working example of recaptcha in meteor - meteor

Can anyone help with a working example of recaptcha in meteor without using iframes?
I cannot make the recaptcha scripts run even when I try to run them from the client.js using jquery append.

After doing some investigations I found that I had to manually integrate the reCaptcha.
The client side code:
HTML:
<form id="mySecuredForm" novalidate>
<!-- labels and inputs here -->
<div class="row">
<div id="captcha-container">
<div id="rendered-captcha-container">loading...</div>
</div>
</div>
<div class="row">
<button type="submit" id="submit" class="submit-button">Submit</button>
</div>
</form>
JS
if (Meteor.isClient) {
Template.myTemplate.rendered = function() {
$.getScript('http://www.google.com/recaptcha/api/js/recaptcha_ajax.js', function() {
Recaptcha.create('add_your_public_key_here', 'rendered-captcha-container', {
theme: 'red',
callback: Recaptcha.focus_response_field
});
});
}
Template['myTemplate'].events({
'submit form#mySecuredForm': function(event) {
event.preventDefault();
event.stopPropagation();
var formData = {
captcha_challenge_id: Recaptcha.get_challenge(),
captcha_solution: Recaptcha.get_response()
//add the data from form inputs here
};
Meteor.call('submitMySecuredForm', formData, function(error, result) {
if (result.success) {
//set session vars, redirect, etc
} else {
Recaptcha.reload();
// alert error message according to received code
switch (result.error) {
case 'captcha_verification_failed':
alert('captcha solution is wrong!');
break;
case 'other_error_on_form_submit':
alert('other error');
break;
default:
alert('error');
}
}
});
}
Server side code
function verifyCaptcha(clientIP, data) {
var captcha_data = {
privatekey: 'add_private_key_here',
remoteip: clientIP
challenge: data.captcha_challenge_id,
response: data.captcha_solution
};
var serialized_captcha_data =
'privatekey=' + captcha_data.privatekey +
'&remoteip=' + captcha_data.remoteip +
'&challenge=' + captcha_data.challenge +
'&response=' + captcha_data.response;
var captchaVerificationResult = null;
var success, parts; // used to process response string
try {
captchaVerificationResult = HTTP.call("POST", "http://www.google.com/recaptcha/api/verify", {
content: serialized_captcha_data.toString('utf8'),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': serialized_captcha_data.length
}
});
} catch(e) {
return {
'success': false,
'error': 'google_service_not_accessible'
};
}
parts = captchaVerificationResult.content.split('\n');
success = parts[0];
if (success !== 'true') {
return {
'success': false,
'error': 'captcha_verification_failed'
};
}
return {
'success': true
};
}
Meteor.methods({
"submitMySecuredForm": function(data) {
//!add code here to separate captcha data from form data.
var verifyCaptchaResponse = verifyCaptcha(this.connection.clientAddress, data);
if (!verifyCaptchaResponse.success) {
console.log('Captcha check failed! Responding with: ', verifyCaptchaResponse);
return verifyCaptchaResponse;
}
console.log('Captcha verification passed!');
//!add code here to process form data
return {success: true};
});
There is also the possibility to listen to the post event on the server side. The http calls can be done synchronous as above or asynchronous with fibers/futures.
Server side http call to google API was inspired from:
https://github.com/mirhampt/node-recaptcha/blob/master/lib/recaptcha.js

Related

How should I encode a form value I send with XMLHttpRequest [duplicate]

I'd like to send some data using an XMLHttpRequest in JavaScript.
Say I have the following form in HTML:
<form name="inputform" action="somewhere" method="post">
<input type="hidden" value="person" name="user">
<input type="hidden" value="password" name="pwd">
<input type="hidden" value="place" name="organization">
<input type="hidden" value="key" name="requiredkey">
</form>
How can I write the equivalent using an XMLHttpRequest in JavaScript?
The code below demonstrates on how to do this.
var http = new XMLHttpRequest();
var url = 'get_data.php';
var params = 'orem=ipsum&name=binny';
http.open('POST', url, true);
//Send the proper header information along with the request
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
http.onreadystatechange = function() {//Call a function when the state changes.
if(http.readyState == 4 && http.status == 200) {
alert(http.responseText);
}
}
http.send(params);
In case you have/create an object you can turn it into params using the following code, i.e:
var params = new Object();
params.myparam1 = myval1;
params.myparam2 = myval2;
// Turn the data object into an array of URL-encoded key/value pairs.
let urlEncodedData = "", urlEncodedDataPairs = [], name;
for( name in params ) {
urlEncodedDataPairs.push(encodeURIComponent(name)+'='+encodeURIComponent(params[name]));
}
var xhr = new XMLHttpRequest();
xhr.open('POST', 'somewhere', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onload = function () {
// do something to response
console.log(this.responseText);
};
xhr.send('user=person&pwd=password&organization=place&requiredkey=key');
Or if you can count on browser support you could use FormData:
var data = new FormData();
data.append('user', 'person');
data.append('pwd', 'password');
data.append('organization', 'place');
data.append('requiredkey', 'key');
var xhr = new XMLHttpRequest();
xhr.open('POST', 'somewhere', true);
xhr.onload = function () {
// do something to response
console.log(this.responseText);
};
xhr.send(data);
Use modern JavaScript!
I'd suggest looking into fetch. It is the ES5 equivalent and uses Promises. It is much more readable and easily customizable.
const url = "http://example.com";
fetch(url, {
method : "POST",
body: new FormData(document.getElementById("inputform")),
// -- or --
// body : JSON.stringify({
// user : document.getElementById('user').value,
// ...
// })
}).then(
response => response.text() // .json(), etc.
// same as function(response) {return response.text();}
).then(
html => console.log(html)
);
In Node.js, you'll need to import fetch using:
const fetch = require("node-fetch");
If you want to use it synchronously (doesn't work in top scope):
const json = await fetch(url, optionalOptions)
.then(response => response.json()) // .text(), etc.
.catch((e) => {});
More Info:
Mozilla Documentation
Can I Use (96% Nov 2020)
David Walsh Tutorial
Here is a complete solution with application-json:
// Input values will be grabbed by ID
<input id="loginEmail" type="text" name="email" placeholder="Email">
<input id="loginPassword" type="password" name="password" placeholder="Password">
// return stops normal action and runs login()
<button onclick="return login()">Submit</button>
<script>
function login() {
// Form fields, see IDs above
const params = {
email: document.querySelector('#loginEmail').value,
password: document.querySelector('#loginPassword').value
}
const http = new XMLHttpRequest()
http.open('POST', '/login')
http.setRequestHeader('Content-type', 'application/json')
http.send(JSON.stringify(params)) // Make sure to stringify
http.onload = function() {
// Do whatever with response
alert(http.responseText)
}
}
</script>
Ensure that your Backend API can parse JSON.
For example, in Express JS:
import bodyParser from 'body-parser'
app.use(bodyParser.json())
Minimal use of FormData to submit an AJAX request
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=Edge, chrome=1"/>
<script>
"use strict";
function submitForm(oFormElement)
{
var xhr = new XMLHttpRequest();
xhr.onload = function(){ alert (xhr.responseText); } // success case
xhr.onerror = function(){ alert (xhr.responseText); } // failure case
xhr.open (oFormElement.method, oFormElement.action, true);
xhr.send (new FormData (oFormElement));
return false;
}
</script>
</head>
<body>
<form method="post" action="somewhere" onsubmit="return submitForm(this);">
<input type="hidden" value="person" name="user" />
<input type="hidden" value="password" name="pwd" />
<input type="hidden" value="place" name="organization" />
<input type="hidden" value="key" name="requiredkey" />
<input type="submit" value="post request"/>
</form>
</body>
</html>
Remarks
This does not fully answer the OP question because it requires the user to click in order to submit the request. But this may be useful to people searching for this kind of simple solution.
This example is very simple and does not support the GET method. If you are interesting by more sophisticated examples, please have a look at the excellent MDN documentation. See also similar answer about XMLHttpRequest to Post HTML Form.
Limitation of this solution: As pointed out by Justin Blank and Thomas Munk (see their comments), FormData is not supported by IE9 and lower, and default browser on Android 2.3.
NO PLUGINS NEEDED!
Select the below code and drag that into in BOOKMARK BAR (if you don't see it, enable from Browser Settings), then EDIT that link :
javascript:var my_params = prompt("Enter your parameters", "var1=aaaa&var2=bbbbb"); var Target_LINK = prompt("Enter destination", location.href); function post(path, params) { var xForm = document.createElement("form"); xForm.setAttribute("method", "post"); xForm.setAttribute("action", path); for (var key in params) { if (params.hasOwnProperty(key)) { var hiddenField = document.createElement("input"); hiddenField.setAttribute("name", key); hiddenField.setAttribute("value", params[key]); xForm.appendChild(hiddenField); } } var xhr = new XMLHttpRequest(); xhr.onload = function () { alert(xhr.responseText); }; xhr.open(xForm.method, xForm.action, true); xhr.send(new FormData(xForm)); return false; } parsed_params = {}; my_params.split("&").forEach(function (item) { var s = item.split("="), k = s[0], v = s[1]; parsed_params[k] = v; }); post(Target_LINK, parsed_params); void(0);
That's all! Now you can visit any website, and click that button in BOOKMARK BAR!
NOTE:
The above method sends data using XMLHttpRequest method, so, you have to be on the same domain while triggering the script. That's why I prefer sending data with a simulated FORM SUBMITTING, which can send the code to any domain - here is code for that:
javascript:var my_params=prompt("Enter your parameters","var1=aaaa&var2=bbbbb"); var Target_LINK=prompt("Enter destination", location.href); function post(path, params) { var xForm= document.createElement("form"); xForm.setAttribute("method", "post"); xForm.setAttribute("action", path); xForm.setAttribute("target", "_blank"); for(var key in params) { if(params.hasOwnProperty(key)) { var hiddenField = document.createElement("input"); hiddenField.setAttribute("name", key); hiddenField.setAttribute("value", params[key]); xForm.appendChild(hiddenField); } } document.body.appendChild(xForm); xForm.submit(); } parsed_params={}; my_params.split("&").forEach(function(item) {var s = item.split("="), k=s[0], v=s[1]; parsed_params[k] = v;}); post(Target_LINK, parsed_params); void(0);
I have faced similar problem, using the same post and and this link I have resolved my issue.
var http = new XMLHttpRequest();
var url = "MY_URL.Com/login.aspx";
var params = 'eid=' +userEmailId+'&pwd='+userPwd
http.open("POST", url, true);
// Send the proper header information along with the request
//http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
//http.setRequestHeader("Content-Length", params.length);// all browser wont support Refused to set unsafe header "Content-Length"
//http.setRequestHeader("Connection", "close");//Refused to set unsafe header "Connection"
// Call a function when the state
http.onreadystatechange = function() {
if(http.readyState == 4 && http.status == 200) {
alert(http.responseText);
}
}
http.send(params);
This link has completed information.
Try to use json object instead of formdata. below is the code working for me. formdata doesnot work for me either, hence I came up with this solution.
var jdata = new Object();
jdata.level = levelVal; // level is key and levelVal is value
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "http://MyURL", true);
xhttp.setRequestHeader('Content-Type', 'application/json');
xhttp.send(JSON.stringify(jdata));
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
console.log(this.responseText);
}
}
There's some duplicates that touch on this, and nobody really expounds on it. I'll borrow the accepted answer example to illustrate
http.open('POST', url, true);
http.send('lorem=ipsum&name=binny');
I oversimplified this (I use http.onload(function() {}) instead of that answer's older methodology) for the sake of illustration. If you use this as-is, you'll find your server is probably interpreting the POST body as a string and not actual key=value parameters (i.e. PHP won't show any $_POST variables). You must pass the form header in to get that, and do that before http.send()
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
If you're using JSON and not URL-encoded data, pass application/json instead
var util = {
getAttribute: function (dom, attr) {
if (dom.getAttribute !== undefined) {
return dom.getAttribute(attr);
} else if (dom[attr] !== undefined) {
return dom[attr];
} else {
return null;
}
},
addEvent: function (obj, evtName, func) {
//Primero revisar attributos si existe o no.
if (obj.addEventListener) {
obj.addEventListener(evtName, func, false);
} else if (obj.attachEvent) {
obj.attachEvent(evtName, func);
} else {
if (this.getAttribute("on" + evtName) !== undefined) {
obj["on" + evtName] = func;
} else {
obj[evtName] = func;
}
}
},
removeEvent: function (obj, evtName, func) {
if (obj.removeEventListener) {
obj.removeEventListener(evtName, func, false);
} else if (obj.detachEvent) {
obj.detachEvent(evtName, func);
} else {
if (this.getAttribute("on" + evtName) !== undefined) {
obj["on" + evtName] = null;
} else {
obj[evtName] = null;
}
}
},
getAjaxObject: function () {
var xhttp = null;
//XDomainRequest
if ("XMLHttpRequest" in window) {
xhttp = new XMLHttpRequest();
} else {
// code for IE6, IE5
xhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
return xhttp;
}
};
//START CODE HERE.
var xhr = util.getAjaxObject();
var isUpload = (xhr && ('upload' in xhr) && ('onprogress' in xhr.upload));
if (isUpload) {
util.addEvent(xhr, "progress", xhrEvt.onProgress());
util.addEvent(xhr, "loadstart", xhrEvt.onLoadStart);
util.addEvent(xhr, "abort", xhrEvt.onAbort);
}
util.addEvent(xhr, "readystatechange", xhrEvt.ajaxOnReadyState);
var xhrEvt = {
onProgress: function (e) {
if (e.lengthComputable) {
//Loaded bytes.
var cLoaded = e.loaded;
}
},
onLoadStart: function () {
},
onAbort: function () {
},
onReadyState: function () {
var state = xhr.readyState;
var httpStatus = xhr.status;
if (state === 4 && httpStatus === 200) {
//Completed success.
var data = xhr.responseText;
}
}
};
//CONTINUE YOUR CODE HERE.
xhr.open('POST', 'mypage.php', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
if ('FormData' in window) {
var formData = new FormData();
formData.append("user", "aaaaa");
formData.append("pass", "bbbbb");
xhr.send(formData);
} else {
xhr.send("?user=aaaaa&pass=bbbbb");
}
This helped me as I wanted to use only xmlHttpRequest and post an object as form data:
function sendData(data) {
var XHR = new XMLHttpRequest();
var FD = new FormData();
// Push our data into our FormData object
for(name in data) {
FD.append(name, data[name]);
}
// Set up our request
XHR.open('POST', 'https://example.com/cors.php');
// Send our FormData object; HTTP headers are set automatically
XHR.send(FD);
}
https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript
Short & modern
You can catch form input values using FormData and send them by fetch
fetch(form.action, {method:'post', body: new FormData(form)});
function send() {
let form = document.forms['inputform'];
fetch(form.action, {method:'post', body: new FormData(form)});
}
<form name="inputform" action="somewhere" method="post">
<input value="person" name="user">
<input type="hidden" value="password" name="pwd">
<input value="place" name="organization">
<input type="hidden" value="key" name="requiredkey">
</form>
<!-- I remove type="hidden" for some inputs above only for show them --><br>
Look: chrome console>network and click <button onclick="send()">send</button>
Just for feature readers finding this question. I found that the accepted answer works fine as long as you have a given path, but if you leave it blank it will fail in IE. Here is what I came up with:
function post(path, data, callback) {
"use strict";
var request = new XMLHttpRequest();
if (path === "") {
path = "/";
}
request.open('POST', path, true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.onload = function (d) {
callback(d.currentTarget.response);
};
request.send(serialize(data));
}
You can you it like so:
post("", {orem: ipsum, name: binny}, function (response) {
console.log(respone);
})

Update dynamic data in service-worker.js

I have the below data coming in form of array from a url.
[{"title":"hey hi","body":"hello","url":"https://simple-push-demo.appspot.com/","tag":"new"}]
service-worker.js
it has the above url in fetch()
'use strict';
console.log('Started', self);
self.addEventListener('install', function(event) {
self.skipWaiting();
console.log('Installed new', event);
});
self.addEventListener('activate', function(event) {
console.log('Activatednew', event);
});
self.addEventListener('push', function(event) {
try{
console.log('Push message', event);
var ev = event;
//sample
return fetch("http://localhost/push-notifications-master/app/json.php").then(function(ev,response) {
response = JSON.parse(JSON.stringify(response));
return response;
}).then(function(ev,j) {
// Yay, `j` is a JavaScript object
console.log("j", j);
for(var i in j) {
var _title = j[i].title;
var _body = j[i].body;
var _tag = j[i].tag;
console.log("_body", _body);
}
ev.waitUntil(
self.registration.showNotification("push title", {
body: _body,
icon: 'images/icon.png',
tag: _tag
}));
});
return Promise.all(response);
}
catch(e){console.log("e", e)}
});
I am trying to see the above array data coming from that particular url in console.log("j",j);. but it shows undefined. How can i get dymanic data in sw.js Please Guide.
In your addEventListener('push' .... method, I think it might be better to wait for a response before parsing it.
Also, to be checked, but your php request should be in https (not checked by myself, but my request are on https).
Here how I do this :
event.waitUntil(
fetch('YOUR PHP URL').then(function(response) {
if (response.status !== 200) {
console.log('Problem. Status Code: ' + response.status);
throw new Error();
}
// Examine the text in the response
return response.json().then(function(data) {
if (data.error || !data.notification) {
console.error('The API returned an error.', data.error);
throw new Error();
}
var title = data.notification[0].title;
var body = data.notification[0].body;
var icon = data.notification[0].icon;
var notificationTag = data.notification[0].tag;
return self.registration.showNotification(title, {body: body,icon:icon, tag: notificationTag});
});
})
);
The json :
{"notification" : [{"title":"TITLE","body":"BODY","icon":"URL TO ICON","tag":"TAG"}]}
Hope it can be useful.

I'm using Meteor, what do I need to do to wait for a promise to be returned from an API call?

if (Meteor.isClient) {
Template.hello.events({
'click input': function () {
//create a new customer
Meteor.call('createCustomer', function (error, result) {
console.log("Error: " + error + " Result: " + result); } );
}
});
}
if (Meteor.isServer) {
Meteor.methods({
createCustomer: function () {
try {
balanced.configure('MyBalancedPaymentsTestKey');
var customer = Meteor._wrapAsync(balanced.marketplace.customers.create());
var callCustomer = customer();
var returnThis = console.log(JSON.stringify(callCustomer, false, 4));
return returnThis;
} catch (e) {
console.log(e);
var caughtFault = JSON.stringify(e, false, 4);
}
return caughtFault;
}
});
}
And I just used the default hello world without the greetings line.
<head>
<title>testCase</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
<h1>Hello World!</h1>
<input type="button" value="Click" />
</template>
On the client side the log prints
Error: undefined Result: {}
On the server side the log prints
[TypeError: Object [object Promise] has no method 'apply']
Any idea how I can wait for that promise instead of returning the blank result?
I'm assuming balanced.marketplace.customers.create returns a Promises/A+ promise. This is an object with a method .then(fulfillmentCallback, rejectionCallback) - the fulfillmentCallback is called when the operation succeeds, and the rejectionCallback is called if the operation had an error. Here's how you could use Futures to synchronously get the value out of a promise:
var Future = Npm.require("fibers/future");
function extractFromPromise(promise) {
var fut = new Future();
promise.then(function (result) {
fut["return"](result);
}, function (error) {
fut["throw"](error);
});
return fut.wait();
}
Then you can just call balanced.marketplace.customers.create normally (no _wrapAsync) to get a promise, then call extractFromPromise on that promise to get the actual result value. If there's an error, then extractFromPromise will throw an exception.
By the way, code in if (Meteor.isServer) blocks is still sent to the client (even if the client doesn't run it), so you don't want to put your API key in there. You can put code in the server directory, and then Meteor won't send it to the client at all.
Update this line
var customer = Meteor._wrapAsync(balanced.marketplace.customer.create)();
Another approach is to use Futures. I use this a lot on the server side to wait for results to return back to the client.
Here's a small example of that I use for logins:
Accounts.login(function (req, user) {
var Future = Npm.require("fibers/future");
var fut = new Future();
HTTP.call("POST", globals.server + 'api/meteor-approvals/token',
{
timeout: 10000, followRedirects: true,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
params: {
username: userName,
password: req.password
}},
function (err, result) {
if (err) {
logger.error("Login error: " + err);
fut.throw(err);
}
else {
fut.return("Success");
}
}
);
return fut.wait();
}

Cannot call method 'create' of undefined

Here is what I'm getting from the console server side.
I20140516-21:27:12.142(0)? There was an error on this page. Cannot call method 'create' of undefined
I am not finding a good reason why this method isn't defined. I have the balanced-payments-production package from Atmosphere loaded and this includes the balanced.js file and the api export to the server. Any help here is appreciated.
Here is my events.js file
Template.CheckFormSubmit.events({
'submit form': function (e, tmpl) {
e.preventDefault();
var recurringStatus = $(e.target).find('[name=is_recurring]').is(':checked');
var checkForm = {
name: $(e.target).find('[name=name]').val(),
account_number: $(e.target).find('[name=account_number]').val(),
routing_number: $(e.target).find('[name=routing_number]').val(),
recurring: { is_recurring: recurringStatus },
created_at: new Date
}
checkForm._id = Donations.insert(checkForm);
Meteor.call("addCustomer", checkForm, function(error, result) {
console.log(error);
console.log(result);
// Successful tokenization
if(result.status_code === 201 && result.href) {
// Send to your backend
jQuery.post(responseTarget, {
uri: result.href
}, function(r) {
// Check your backend result
if(r.status === 201) {
// Your successful logic here from backend
} else {
// Your failure logic here from backend
}
});
} else {
// Failed to tokenize, your error logic here
}
// Debuging, just displays the tokenization result in a pretty div
$('#response .panel-body pre').html(JSON.stringify(result, false, 4));
$('#response').slideDown(300);
});
var form = tmpl.find('form');
//form.reset();
//Will need to add route to receipt page here.
//Something like this maybe - Router.go('receiptPage', checkForm);
},
'click [name=is_recurring]': function (e, tmpl) {
var id = this._id;
console.log($id);
var isRecuring = tmpl.find('input').checked;
Donations.update({_id: id}, {
$set: { 'recurring.is_recurring': true }
});
}
});
Here is my Methods.js file
function getCustomer(req, callback) {
try {
balanced.marketplace.customers.create(req, callback);
console.log(req.links.customers.bank_accounts);
}
catch (error){
var error = "There was an error on this page. " + error.message;
console.log(error);
}
}
var wrappedGetCustomer = Meteor._wrapAsync(getCustomer);
Meteor.methods({
addCustomer: function(formData) {
try {
console.log(formData);
return wrappedGetCustomer(formData);
}
catch (error) {
var error = "There was an error on this page." + error.message;
console.log(error);
}
}
});
I needed to run balanced.configure('APIKEYHERE'); first, then run the balanced code.

How to set a timeout on a http.request() in Node?

I'm trying to set a timeout on an HTTP client that uses http.request with no luck. So far what I did is this:
var options = { ... }
var req = http.request(options, function(res) {
// Usual stuff: on(data), on(end), chunks, etc...
}
/* This does not work TOO MUCH... sometimes the socket is not ready (undefined) expecially on rapid sequences of requests */
req.socket.setTimeout(myTimeout);
req.socket.on('timeout', function() {
req.abort();
});
req.write('something');
req.end();
Any hints?
2019 Update
There are various ways to handle this more elegantly now. Please see some other answers on this thread. Tech moves fast so answers can often become out of date fairly quickly. My answer will still work but it's worth looking at alternatives as well.
2012 Answer
Using your code, the issue is that you haven't waited for a socket to be assigned to the request before attempting to set stuff on the socket object. It's all async so:
var options = { ... }
var req = http.request(options, function(res) {
// Usual stuff: on(data), on(end), chunks, etc...
});
req.on('socket', function (socket) {
socket.setTimeout(myTimeout);
socket.on('timeout', function() {
req.abort();
});
});
req.on('error', function(err) {
if (err.code === "ECONNRESET") {
console.log("Timeout occurs");
//specific error treatment
}
//other error treatment
});
req.write('something');
req.end();
The 'socket' event is fired when the request is assigned a socket object.
Just to clarify the answer above:
Now it is possible to use timeout option and the corresponding request event:
// set the desired timeout in options
const options = {
//...
timeout: 3000,
};
// create a request
const request = http.request(options, response => {
// your callback here
});
// use its "timeout" event to abort the request
request.on('timeout', () => {
request.destroy();
});
See the docs:
At this moment there is a method to do this directly on the request object:
request.setTimeout(timeout, function() {
request.abort();
});
This is a shortcut method that binds to the socket event and then creates the timeout.
Reference: Node.js v0.8.8 Manual & Documentation
The Rob Evans anwser works correctly for me but when I use request.abort(), it occurs to throw a socket hang up error which stays unhandled.
I had to add an error handler for the request object :
var options = { ... }
var req = http.request(options, function(res) {
// Usual stuff: on(data), on(end), chunks, etc...
}
req.on('socket', function (socket) {
socket.setTimeout(myTimeout);
socket.on('timeout', function() {
req.abort();
});
}
req.on('error', function(err) {
if (err.code === "ECONNRESET") {
console.log("Timeout occurs");
//specific error treatment
}
//other error treatment
});
req.write('something');
req.end();
There is simpler method.
Instead of using setTimeout or working with socket directly,
We can use 'timeout' in the 'options' in client uses
Below is code of both server and client, in 3 parts.
Module and options part:
'use strict';
// Source: https://github.com/nodejs/node/blob/master/test/parallel/test-http-client-timeout-option.js
const assert = require('assert');
const http = require('http');
const options = {
host: '127.0.0.1', // server uses this
port: 3000, // server uses this
method: 'GET', // client uses this
path: '/', // client uses this
timeout: 2000 // client uses this, timesout in 2 seconds if server does not respond in time
};
Server part:
function startServer() {
console.log('startServer');
const server = http.createServer();
server
.listen(options.port, options.host, function () {
console.log('Server listening on http://' + options.host + ':' + options.port);
console.log('');
// server is listening now
// so, let's start the client
startClient();
});
}
Client part:
function startClient() {
console.log('startClient');
const req = http.request(options);
req.on('close', function () {
console.log("got closed!");
});
req.on('timeout', function () {
console.log("timeout! " + (options.timeout / 1000) + " seconds expired");
// Source: https://github.com/nodejs/node/blob/master/test/parallel/test-http-client-timeout-option.js#L27
req.destroy();
});
req.on('error', function (e) {
// Source: https://github.com/nodejs/node/blob/master/lib/_http_outgoing.js#L248
if (req.connection.destroyed) {
console.log("got error, req.destroy() was called!");
return;
}
console.log("got error! ", e);
});
// Finish sending the request
req.end();
}
startServer();
If you put all the above 3 parts in one file, "a.js", and then run:
node a.js
then, output will be:
startServer
Server listening on http://127.0.0.1:3000
startClient
timeout! 2 seconds expired
got closed!
got error, req.destroy() was called!
Hope that helps.
For me - here is a less confusing way of doing the socket.setTimeout
var request=require('https').get(
url
,function(response){
var r='';
response.on('data',function(chunk){
r+=chunk;
});
response.on('end',function(){
console.dir(r); //end up here if everything is good!
});
}).on('error',function(e){
console.dir(e.message); //end up here if the result returns an error
});
request.on('error',function(e){
console.dir(e); //end up here if a timeout
});
request.on('socket',function(socket){
socket.setTimeout(1000,function(){
request.abort(); //causes error event ↑
});
});
Elaborating on the answer #douwe here is where you would put a timeout on a http request.
// TYPICAL REQUEST
var req = https.get(http_options, function (res) {
var data = '';
res.on('data', function (chunk) { data += chunk; });
res.on('end', function () {
if (res.statusCode === 200) { /* do stuff with your data */}
else { /* Do other codes */}
});
});
req.on('error', function (err) { /* More serious connection problems. */ });
// TIMEOUT PART
req.setTimeout(1000, function() {
console.log("Server connection timeout (after 1 second)");
req.abort();
});
this.abort() is also fine.
You should pass the reference to request like below
var options = { ... }
var req = http.request(options, function(res) {
// Usual stuff: on(data), on(end), chunks, etc...
});
req.setTimeout(60000, function(){
this.abort();
});
req.write('something');
req.end();
Request error event will get triggered
req.on("error", function(e){
console.log("Request Error : "+JSON.stringify(e));
});
Curious, what happens if you use straight net.sockets instead? Here's some sample code I put together for testing purposes:
var net = require('net');
function HttpRequest(host, port, path, method) {
return {
headers: [],
port: 80,
path: "/",
method: "GET",
socket: null,
_setDefaultHeaders: function() {
this.headers.push(this.method + " " + this.path + " HTTP/1.1");
this.headers.push("Host: " + this.host);
},
SetHeaders: function(headers) {
for (var i = 0; i < headers.length; i++) {
this.headers.push(headers[i]);
}
},
WriteHeaders: function() {
if(this.socket) {
this.socket.write(this.headers.join("\r\n"));
this.socket.write("\r\n\r\n"); // to signal headers are complete
}
},
MakeRequest: function(data) {
if(data) {
this.socket.write(data);
}
this.socket.end();
},
SetupRequest: function() {
this.host = host;
if(path) {
this.path = path;
}
if(port) {
this.port = port;
}
if(method) {
this.method = method;
}
this._setDefaultHeaders();
this.socket = net.createConnection(this.port, this.host);
}
}
};
var request = HttpRequest("www.somesite.com");
request.SetupRequest();
request.socket.setTimeout(30000, function(){
console.error("Connection timed out.");
});
request.socket.on("data", function(data) {
console.log(data.toString('utf8'));
});
request.WriteHeaders();
request.MakeRequest();

Resources