Add timeout to HTTP request in Coffeescript - http

I need to modify some Coffeescript to include a timeout for HTTP requests. Below is the code. I have tried adding a 'timeout' property to the requestOptions dictionary but no luck!
Below is the code which you can also find on Github (https://github.com/pcrawfor/iap_verifier/blob/master/src/iap_verifier.coffee#L144)
###
verifyReceipt
Verifies an In App Purchase receipt string against Apple's servers
params:
receipt - the receipt string
isBase64 - Is the receipt already encoded in base64? Optional, defaults to false.
cb - callback function that will return the status code and results for the verification call
###
verifyReceipt: (receipt, isBase64, cb) ->
if cb is undefined
cb = isBase64
isBase64 = false
data =
'receipt-data': ""
#verifyWithRetry(data, receipt, isBase64, cb)
###
verifyWithRetry
Verify with retry will automatically call the Apple Sandbox verification server in the event that a 21007 error code is returned.
This error code is an indication that the app may be receiving app store review requests.
###
verifyWithRetry: (receiptData, receipt, isBase64, cb) ->
encoded = null
if isBase64
encoded = receipt
else
buffer = new Buffer(receipt)
encoded = buffer.toString('base64')
receiptData['receipt-data'] = encoded
#verify receiptData, #requestOptions(), (valid, msg, data) =>
# on a 21007 error retry the request for the Sandbox environment (if the current environment is Production)
if (21007 == data.status) && (#productionHost == #host)
# retry...
if #debug then console.log("Retry on Sandbox")
options = #requestOptions()
options.host = #sandboxHost
#verify receiptData, options, (valid, msg, data) ->
if #debug then console.log("STATUS #{data.status}")
cb(valid, msg, data)
else
if #debug then console.log "else"
cb(valid, msg, data)
###
verify the receipt data
###
verify: (data, options, cb) ->
if #debug then console.log("verify!")
post_data = JSON.stringify(data)
options.headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': post_data.length
}
request = https.request options, (response) =>
if #debug then console.log("statusCode: #{response.statusCode}")
if #debug then console.log("headers: #{response.headers}")
apple_response_arr = []
response.on 'data', (data) =>
if #debug then console.log("data #{data}")
if response.statusCode != 200
if #debug then console.log("error: " + data)
return cb(false, "error", null)
apple_response_arr.push(data)
response.on 'end', () =>
totalData = apple_response_arr.join('')
if #debug then console.log "end: apple response: #{totalData}"
responseData = JSON.parse(totalData)
#processStatus(responseData, cb)
response.on 'timeout', () =>
console.log('timeout')
return cb(false, "error", null)
request.write(post_data)
request.end()
request.on 'error', (err) ->
if #debug then console.log("In App purchase verification error: #{err}")
processStatus: (data, cb) ->
# evaluate status code and take an action, write any new receipts to the database
if #debug then console.log("Process status #{data.status}")
#todo: check status code and react appropriately
response = #responseCodes[data.status]
# Try not to blow up if we encounter an unknown/unexepected status code
unless response
response =
valid: false
error: true
message: "Unknown status code: " + data.status
cb(response.valid, response.message, data)
requestOptions: ->
options =
host: #host
port: #port
path: #path
method: #method
# timeout: 100 // didn't work :(
module.exports = IAPVerifier

Related

Error while making request: socket hang up. Error code: ECONNRESET

I'm using node.js as a backend server for sending push notification from the Firebase Cloud Messaging service. The notifications are working fine with local server but on live server, I get this error:
Error while making request: socket hang up. Error code: ECONNRESET
Things to consider are that...
Number of users are in the thousands on live server
Firebase version is firebase-admin#6.5.1
Previously unregistered tokens are still there. But now registered tokens are being stored.
This is my code for sending notifications:
for (let c = 0; c < tokens.length; c++)
{
let notifyTo = tokens[c];
const platform = platforms[c];
let payload;
if (platform === "ios") {
payload = {
notification: {
title: "title",
subtitle :"messgae",
sound: "default",
badge: "1"
},
data: {
sendFrom: "",
notificationType: "",
flag: "true"
}
};
} else if (platform === "android") {
payload = {
data: {
title: "",
message : "",
flag: "true"
}
};
}
const registrationtoken = notifyTo;
await admin.messaging().sendToDevice(registrationtoken, payload)
.then(function (response) {
console.log("Successfully sent message:");
})
.catch(function (error) {
console.log("Error sending message: ");
});
}
Your issue is caused by your function taking too long to respond to the client (more than 60 seconds) and is caused by the following line:
await admin.messaging().sendToDevice(registrationtoken, payload)
Because you are waiting for each call of sendToDevice() individually, you are running your for-loop in synchronous sequential order, rather than asynchronously in parallel.
To avoid this, you want to make use of array mapping and Promise.all() which will allow you to build a queue of sendToDevice() requests. As in your current code, any failed messages will be silently ignored, but we will also count them.
Your current code makes use of two arrays, tokens and platforms, so in the code below I use a callback for Array.prototype.map() that takes two arguments - the current mapped value (from tokens) and it's index (your for-loop's c value). The index is then used to get the correct platform entry.
let fcmPromisesArray = tokens.map((token, idx) => {
let platform = platforms[idx];
if (platform === "ios") {
payload = {
notification: {
title: "title",
subtitle :"messgae",
sound: "default",
badge: "1"
},
data: {
sendFrom: "",
notificationType: "",
flag: "true"
}
};
} else if (platform === "android") {
payload = {
data: {
title: "",
message : "",
flag: "true"
}
};
}
return admin.messaging().sendToDevice(token, payload) // note: 'await' was changed to 'return' here
.then(function (response) {
return true; // success
})
.catch(function (error) {
console.log("Error sending message to ", token);
return false; // failed
});
});
let results = await Promise.all(fcmPromisesArray); // wait here for all sendToDevice() requests to finish or fail
let successCount = results.reduce((acc, v) => v ? acc + 1 : acc, 0); // this minified line just counts the number of successful results
console.log(`Successfully sent messages to ${successCount}/${results.length} devices.`);
After this snippet has run, don't forget to send a result back to the client using res.send(...) or similar.

REST API call using nodejs on localhost

I have made a REST API using R language.
#* #get /mean
normalMean <- function(samples=10){
data <- rnorm(samples)
mean(data)
}
I started the R server and tested the API using the url- http://localhost:8000/mean and it is working.
However when I tried to invoke the API using nodejs it returns an error:
Error: socket hang up
at TLSSocket.onHangUp (_tls_wrap.js:1124:19)
at TLSSocket.g (events.js:292:16)
at emitNone (events.js:91:20)
at TLSSocket.emit (events.js:185:7)
at endReadableNT (_stream_readable.js:974:12)
at _combinedTickCallback (internal/process/next_tick.js:80:11)
Here is the nodejs code:
var https = require('https');
var optionsget = {
host : 'localhost', // here only the domain name
// (no http/https !)
port : 8000,
path : '/mean', // the rest of the url with parameters if needed
method : 'GET' // do GET
};
console.info('Options prepared:');
console.info(optionsget);
console.info('Do the GET call');
var reqGet = https.request(optionsget, function(res) {
console.log("statusCode: ", res.statusCode);
// uncomment it for header details
// console.log("headers: ", res.headers);
res.on('data', function(d) {
console.info('GET result:\n');
process.stdout.write(d);
console.info('\n\nCall completed');
});
});
I am not understanding where I am going wrong. I intend to make a put request in a similar manner after this.
It means that socket does not send connection end event within the timeout period. If you are getting the request via http.request (not http.get). You have to call request.end() to finish sending the request.
https.get('http://localhost:8000/mean', (resp) => {
console.log("statusCode: ", res.statusCode);
let result = 0;
// on succ
resp.on('data', (d) => {
result = d;
});
// on end
resp.on('end', () => {
console.log(result);
});
}).on("error", (err) => {
console.log("Error: " + err.message);
});

passing cookie data on MeteorJS HTTP request

It already took me several hours implementing cookie on url MeteorJS. What I need to do is that, pass a cookie data n url like 'CURLOPT_COOKIE' PHP. I cant find any example code on their docs and even to forums. For now I have these functions:
/* HTTP REQUEST */
Meteor.methods({
httpRequest: function(type, uri, params){
this.unblock();
check(type, String);
check(uri, String);
check(params, Object);
try {
var result = HTTP.call(type, uri, {params: params});
return result;
} catch (e) {
// Got a network error, time-out or HTTP error in the 400 or 500 range.
return e;
}
}
});
// HTTP request with cooki
getUserDetails: function(session_id, uid){
var params = {
headers: {
Cookie: {
sessid: session_i
}
},
uid: uid
};
var response = Meteor.call('httpRequest', "POST", "http://example.com/rest/wp /alt_wp_resources/loaduser.json", params);
//res = JSON.parse(response.content);
return response;
}
// call here
Meteor.startup(function () {
// delay for 5 sec
Meteor.setTimeout(function (){
Meteor.call('getUserCredentials', 'api12345', '123qweasd', function (error, result) {
// check user authentication
var success = result.success;
console.log(result);
// user has account from lpgp site, let's save him to meteor.
if (success){
console.log('success');
var session_id = result.session_id;
//console.log(_session_id);
Meteor.call('getUserDetails', 'SESSba071091c09f79fefd66e4884dcdde50', 68558, function (error, result) {
if (!error)
console.log(result);
else
console.log(error);
});
}else
// app can't find user account from lpgp site.
console.log(error);
});
}, 5000);
});
The call is successful but, just returned a success: false.
Response:
Object {statusCode: 200, content: "{"success":false}", headers: Object, data: Object}
Meteor's HTTP module on the server side is merely a wrapper for the npm module named request. The request npm module includes support for specifying your own cookies as well as saving them into a cookie jar (just follow the link and search for 'cookie'). The default cookie jar is tough-cookie and interestingly, Meteor includes it even though I don't see any way to use it from Meteor.HTTP.
The upshot of these implementation details is that you can use request directly. I took a similar approach to wrapping request as Meteor's HTTP module but instead of the restricted sub-set of options that HTTP provides, my wrapper allows full access to all the capability of request and tough-cookie. The cool part is that you don't even need to directly add request as a dependency on your own since it's already a dependency of Meteor. The risk, of course, is that a later version of Meteor could use something besides request and your code would break.
Anyway, here is my own wrapper for request. It includes an example of JSessionID cookie support for making Jenkins API calls. Just put this into a file syncRequest.coffee under the \server folder and make sure you have added the coffeescript package (Meteor add coffeescript)... or compile my code and save it to a .js file in the \server folder.
request = Npm.require('request')
populateData = (response) ->
contentType = (response.headers["content-type"] or ";").split(";")[0]
if _.include([ "application/json", "text/javascript" ], contentType)
try
response.data = JSON.parse(response.content)
catch err
response.data = null
else
response.data = null
normalizeOptions = (uri, options, callback) ->
unless uri?
throw new Error("undefined is not a valid uri or options object.")
if (typeof options is "function") and not callback
callback = options
if options and typeof options is "object"
options.uri = uri
else if typeof uri is "string"
options = uri: uri
else
options = uri
return {options, callback}
normalizeResponse = (error, res, body) ->
response = null
unless error
response = {}
response.statusCode = res.statusCode
response.content = body
response.headers = res.headers
populateData(response)
if response.statusCode >= 400
error = makeErrorByStatus(response.statusCode, response.content)
return {error, response}
wrappedRequest = (uri, options, callback) ->
{options, callback} = normalizeOptions(uri, options, callback)
request(options, (error, res, body) ->
{error, response} = normalizeResponse(error, res, body)
callback(error, response)
)
wrappedCall = (method, uri, options, callback) ->
options.method = method
wrappedRequest(uri, options, callback)
wrappedGet = (uri, options, callback) -> wrappedCall("GET", uri, options, callback)
wrappedPost = (uri, options, callback) -> wrappedCall("POST", uri, options, callback)
wrappedPut = (uri, options, callback) -> wrappedCall("PUT", uri, options, callback)
wrappedDelete = (uri, options, callback) -> wrappedCall("DELETE", uri, options, callback)
getWithJSession = (j_username, j_password, securityCheckUri, uri, callback) ->
request = request.defaults({jar: true})
form = {j_username, j_password}
request.post({uri: securityCheckUri, form: form}, (error, response, body) ->
if error?
throw new Error(error)
else if response.statusCode isnt 302
throw new Error("Expected response code 302 (forward). Got #{response.statusCode}")
else
request.get(uri, (error, res, body) ->
{error, response} = normalizeResponse(error, res, body)
callback(error, response)
)
)
syncRequest = Meteor.wrapAsync(wrappedRequest)
syncRequest.call = Meteor.wrapAsync(wrappedCall)
syncRequest.get = Meteor.wrapAsync(wrappedGet)
syncRequest.post = Meteor.wrapAsync(wrappedPost)
syncRequest.put = Meteor.wrapAsync(wrappedPut)
syncRequest.delete = Meteor.wrapAsync(wrappedDelete)
syncRequest.del = syncRequest.delete
syncRequest.getWithJSession = Meteor.wrapAsync(getWithJSession)
syncRequest.getWithJsession = syncRequest.getWithJSession
(exports ? this).syncRequest = syncRequest

Calling Meteor method on server returns before function is complete

I am calling a method on the server with this code:
Meteor.call 'getTitle', post.url, (error, title) ->
console.log 'client side title is: ' + title
and on the server:
Meteor.methods
getTitle: (url) ->
fullURL = addhttp(url)
read fullURL, (err, article, meta) ->
if err
console.log err
err
else
console.log article.title
article.title
And on the client the return value is undefined. I want it to return the article.title value from the read function. Because once the read function is called it keeps going and does not wait for the callback with the return data. To see this clearly if I modify the function to include a return value below the read function the return value is sent correctly.
Meteor.methods
getTitle: (url) ->
fullURL = addhttp(url)
read fullURL, (err, article, meta) ->
if err
console.log err
err
else
console.log article.title
article.title
return 'this return value is sent!'
But how can I call this method, and have it return the value that is in the callback from the read function (article.title)?
Use futures.
#Future = Npm.require('fibers/future')
then:
Meteor.methods
getTitle: (url) ->
...
fut = new Future()
doSomething ->
...
fut.return {success: true}
fut.wait()

Iron Router Server Side Routing callback doesn't work

I am newer for IronRouter, why readFile callback executed the response are send to client.
Router.map(
()->
this.route 'readFile',
path: '/readFile'
where: 'server'
method: 'GET'
action: ()->
self = this
fs.readFile '/tmp/a.txt', (err, data)->
if err
throw err
console.log(data.toString())
self.response.writeHead(200, {'Content-Type': 'text/plain'})
self.response.end(data)
console.log('response ...')
)
http.js:733
W2049-12:04:26.781(8)? (STDERR) throw new Error('Can\'t render headers after they are sent to the client.'
W2049-12:04:26.781(8)? (STDERR) ^
W2049-12:04:26.782(8)? (STDERR) Error: Can't render headers after they are sent to the client.
But, I use express , like this is work well.
exports.index = function(req, res){
fs.readFile('/tmp/a.txt', function (err, data) {
if (err) throw err;
console.log(data.toString());
res.send(200, data.toString());
});
console.log('response ...');
};
thanks #ChristianF #Tarang Use Meteor._wrapAsync or Future all work well . when I use self define function replace fs.readFile. It take throw ex . I Doubt My defined function has error. As follows:
#getAccounts = (callback) ->
query = "SELECT Id, Name, AccountNumber FROM Account"
queryBySoql(query, (err, result)->
if err
return console.error(err)
return callback(result)
)
I invoked link this:
# using Meteor
#data = Meteor._wrapAsync(getAccounts)()
#using Future
waiter = Future.wrap(getAccounts)()
data = waiter.wait()
this.response.writeHead 200, {'Content-Type': 'text/plain'}
this.response.end JSON.stringify(data)
thanks all.
Just today I struggled with this very issue. The answer, it seems to me, is that meteor (or at least the iron router) doesn't handle async calls the way you'd expect. The solution is to wrap the async call into a fiber, which is the mechanism meteor uses to keep the programming synchronous.
In your case try this (sorry, I don't know coffeescript very well):
var Future = Npm.require('fibers/future');
...
var waiter = Future.wrap(fs.readFile);
var data = waiter('/tmp/a.txt').wait();
response.writeHead(200, {'Content-Type': 'text/plain'})
response.end(data)
EDIT Addressing the addition to the question.
Functions wrapped in a future need to have a callback as their last argument that has the (err, result) signature. Try this:
#getAccounts = (callback) ->
query = "SELECT Id, Name, AccountNumber FROM Account"
queryBySoql(query, (err, result)->
if err
return console.error(err)
return callback(err, result)
)
You could wrap up your read file request's callback into the same fiber. It will not block other requests & comes out quite clean.
readFile = Meteor_wrapAsync(fs.readFile.bind(fs))
data = readFile("/tmp/a.txt")
console.log data
#response.writeHead(200, {'Content-Type': 'text/plain'})
#response.end data
return

Resources