Let's say that I have a service that returns customer information by a given id. If it's not found I return null.
My app calls this services like this:
http.get('http://myserver/myservice/customer/123', function(res) {
res.on('data', function(d) {
callback(null, d);
});
}).on('error', function(e) {
callback(e);
});
Currently my service responds with 200 but no data.
How do I handle when no data is returned?
Should I change it to return a different http code? In this case how to handle this? I tried many different approaches without success.
First off, it's important to remember that res is a stream of data; as it stands, your code is likely to call callback multiple times with chunks of data. (The data event is fired each time new data comes in.) If your method has been working up to this point, it's only because the responses have been small enough that the response payload wasn't broken into multiple chunks.
To make your life easier, use a library that handles buffering HTTP response bodies and allows you to get the complete response. A quick search on npm reveals the request package.
var request = require('request');
request('http://server/customer/123', function (error, response, body) {
if (!error && response.statusCode == 200) {
callback(null, body);
} else {
callback(error || response.statusCode);
}
});
As far as your service goes, if a customer ID is not found, return a 404 – it's semantically invalid to return a 200 OK when in fact there is no such customer.
Use the Client Response 'end' event:
//...
res.on('data', function(d) {
callback(null, d);
}).on('end', function() {
console.log('RESPONSE COMPLETE!');
});
I finally found a way to solve my problem:
http.get('http://myserver/myservice/customer/123', function(res) {
var data = '';
response.on('data', function(d) {
data += d;
});
response.on('end', function() {
if (data != '') {
callback(null, data);
}
else {
callback('No customer was found');
}
});
}).on('error', function(e) {
callback(e);
});
This solves the problem, but I'm also going to adopt josh3736's suggestion and return a 404 on my service when the customer is not found.
Related
I'm trying to implement the following scenario:
1. Client calls a meteor-method.
2. Inside the meteor-method i make an HTTP-Post to a different server.
3. When the HTTP-Call is responded, the meteor method should return true and in the case an error occurs it should return false.
Here is what my meteor method looks like:
uploadUserImage: function(data_url,userid) {
asyncfnc =function(data,uid){
HTTP.post("http://localhost:2000/upload", {
data: {
"data_url": data,
"user_id": uid
}
},function(err,res){
console.log(res);
if (err){
console.log("error");
throw new Error(err.message);
}
else{
console.log("return true");
return true;
}
});
};
var waitForResult = Meteor.wrapAsync(asyncfnc);
var result = waitForResult(data_url,userid);
return result;
}
The HTTP-Call works and I also get into the Callback of the HTTP.post-function.
But on the clientside where I called the meteor-method i don't get into my callback-function. It looks like this:
Meteor.call("uploadUserImage",data_url,Session.get("newUserID"),function (err, res) {
if(err){
console.log(err);
} else {
console.log('response: ', res);
}
});
What am I doing wrong? Why is my meteor-method not returning anything?
Is everything correct with my Meteor.wrapAsync()?
Thanks for your help!
I found a solution, which does not require Meteor.wrapAsync().
var url = "http://localhost:2000/upload";
//synchronous GET
var result = Meteor.http.post(url,{
data: {
"title": "i want to upload a picture",
"data_url": data_url,
"user_id": userid
},timeout:30000});
if(result.statusCode==200) {
console.log(result);
console.log("response received.");
return result;
} else {
console.log("Response issue: ", result.statusCode);
var errorJson = JSON.parse(result.content);
throw new Meteor.Error(result.statusCode, errorJson.error);
}
This makes the HTTP-Post-Call synchronous, so there is no need to wrap async.
You are asking too much in this situation.
Meteor methods can be called synchronously, but it's not advisable if the method is doing a remote call like this.
My feeling is that you are hanging on to a procedural programming model where you want a synchronous result to 1) a call to your server, and 2) a request sent to another remote server. And you want to get a return value from your call. It doesn't work like that.
Meteor protects you to a large degree from dealing with asynchronicity, but sometimes you have to accept that a little more work is required to deal with it correctly.
So my recommendation is to use callbacks for notification.
I'm trying to use Meteor's (v1.0) HTTP.call method to communicate with a Python-based server which accepts only application/json content type in header but I cannot set the HTTP header properly in Meteor when calling the API URL from a client side.
With a snippet like this, I get a 415 (Unsupported Media Type) error from Python server:
if (Meteor.isClient) {
Template.testing.events({
'click button': function(event, tpl) {
event.preventDefault();
var method = 'GET';
var url = 'http://localhost:6543/test';
var options = {
headers: {'Content-Type': 'application/json'}
}
HTTP.call(method, url, options, function(error, result) {
if (error) {
console.log('ERRR');
console.log(error);
} else
console.log('RESULT');
console.log(result);
});
}
});
}
However, if I call the same URL from a server side in Meteor like this:
if (Meteor.isClient) {
Template.testing.events({
'click button': function(event, tpl) {
event.preventDefault();
var method = 'GET';
var url = 'http://localhost:6543/test';
var options = {
headers: {'Content-Type': 'application/json'}
}
Meteor.call('APICall', method, url, options, function (error, result) {
if (error) {
console.log('CLIENT ERRR');
console.log(error);
} else {
console.log('CLIENT RESULT');
console.log(result);
}
});
}
});
}
if (Meteor.isServer) {
Meteor.methods({
APICall: function (method, url, options) {
HTTP.call(method, url, options, function(error, result) {
if (error) {
console.log('SERVER ERRR');
console.log(error);
} else
console.log('SERVER RESULT');
console.log(result);
});
}
});
}
I get a proper response from the server.
On Python side I enabled CORS origins for all possible requests (e.g. cors_origins=('*')).
So... Is is possible to set header on client-side or should I always call this service from server-side?
I've never had any success on the client-side either, but it is supposed to. Check out the HTTP.call client part of the meteor HTTP package:
https://github.com/meteor/meteor/blob/devel/packages/http/httpcall_client.js
Mostly it uses the browser XHR object on the client-side which can lead to a host of problems, like incompatibilities and stuff. You can even see an issue referenced on one of the code comments (around line 136)
And when you check out the server implementation you can see it uses the request library (from connect), which, in my book, is very reliable and you can generate uniform results across all users (and not dance around browser differences).
My choice and recommendation for you is obviously going to be server-side calls. Not just because it works and it's reliable, it's also 'safer' on your part as you don't have to expose more inner workings of your system to the client/end-user. Who knows? maybe you have sensitive data on your API run on your Python-based server.
I'm using balanced-payments and their version 1.1 of balanced.js within Meteor.
I'm trying to create a new customer using
balanced.marketplace.customers.create(formData);
Here is my CheckFormSubmitEvents.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("balancedCardCreate", checkForm, function(error, result) {
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);
});
}
});
Here is my Methods.js file
var wrappedDelayedFunction = Async.wrap(balanced.marketplace.customers.create);
Meteor.methods({
balancedCardCreate: function (formData) {
console.log(formData);
var response = wrappedDelayedFunction(formData);
console.log(response);
return response;
}
});
I get nothing back when I submit the form, except that on the server console I do see the log of the form data.
I'm sure I'm not calling some of these async functions correctly. The hard part for me here is that the balanced function are async, but I don't know if they fit into the same mold as some of the examples I've seen.
I've tried to follow this example code.
http://meteorhacks.com/improved-async-utilities-in-meteor-npm.html
Is there a specific change that needs to be done in regard to working with balanced here? Does anyone have any tips for working with Async functions or see something specific about my code that I've done wrong?
Thanks
The NPM utilities Async.wrap does the same thing as the undocumented Meteor function Meteor._wrapAsync, in that it takes an asynchronous function with the last argument function(err, result) {} and turns it into a synchronous function which takes the same arguments, but either returns a result or throws an error instead of using the callback. The function yields in a Fiber until the asynchronous callback returns, so that other code in the event loop can run.
One pitfall with this is that you need to make sure that the function you wrap is called with the correct context. So if balanced.marketplace.customers.create is a prototype method that expects this to be set to something, it will not be set properly unless you bind it yourself, using function.bind or any of the other various library polyfills.
For more information, see https://stackoverflow.com/a/21542356/586086.
What I ended up doing was using a future. This works great, I just need to do better at catching errors. Which will be a question for a pro I think ; - )
Credit should go to user3374348 for answering another similar question of mine, which solved both of these.
https://stackoverflow.com/a/23777507/582309
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();
}
Meteor.methods({
createCustomer: function (data) {
balanced.configure(Meteor.settings.balancedPaymentsAPI);
var customerData = extractFromPromise(balanced.marketplace.customers.create({
'name': data.fname + " " + data.lname,
"address": {
"city": data.city,
"state": data.region,
"line1": data.address_line1,
"line2": data.address_line2,
"postal_code": data.postal_code,
},
'email': data.email_address,
'phone': data.phone_number
}));
var card = extractFromPromise(balanced.marketplace.cards.create({
'number': data.card_number,
'expiration_year': data.expiry_year,
'expiration_month': data.expiry_month,
'cvv': data.cvv
}));
var associate = extractFromPromise(card.associate_to_customer(customerData.href).debit({
"amount": data.total_amount*100,
"appears_on_statement_as": "Trash Mountain" }));
});
As Andrew mentioned, you need to set the context for the method.
Here's the way you can do that with Async.wrap
Async.wrap(balanced.marketplace.customers, "create");
I'm using Q.js for promises.
I'd like to know if it's possible to quickly format/change the error-message when a Q-promise fails.
Consider the contrived example:
return Q.when(//$.ajaxpromise for instance).then(function(result){
//handle result
}).fail(function(err){
//somehow change err (returned from $.ajax) to something
//sensible (say the statuscode + responseText) and
//push it up the callstack
});
Of course I could do the following but it feels kind of cumbersome:
var deferred = Q.defer();
Q.when( //$.ajaxpromise for instance).then(function(result){
//handle result
deferred.resolve();
}).fail(function(err){
deferred.reject(new Error(err.responseText));
});
return deferred.promise;
Anyway to do this more elegantly?
The wonderful thing about Q promises (and any Promises/A+ implementation) is that you can just throw:
return Q.when(otherPromise)
.then(function (result) { /* handle result */ })
.fail(function (err) { throw new Error('A real error!'); });
You can find this information in the "Propagation" section of the Q readme.
I'm making a HTTP request and listen for "data":
response.on("data", function (data) { ... })
The problem is that the response is chunked so the "data" is just a piece of the body sent back.
How do I get the whole body sent back?
request.on('response', function (response) {
var body = '';
response.on('data', function (chunk) {
body += chunk;
});
response.on('end', function () {
console.log('BODY: ' + body);
});
});
request.end();
Over at https://groups.google.com/forum/?fromgroups=#!topic/nodejs/75gfvfg6xuc, Tane Piper provides a good solution very similar to scriptfromscratch's, but for the case of a JSON response:
request.on('response',function(response){
var data = [];
response.on('data', function(chunk) {
data.push(chunk);
});
response.on('end', function() {
var result = JSON.parse(data.join(''))
return result
});
});`
This addresses the issue that OP brought up in the comments section of scriptfromscratch's answer.
I never worked with the HTTP-Client library, but since it works just like the server API, try something like this:
var data = '';
response.on('data', function(chunk) {
// append chunk to your data
data += chunk;
});
response.on('end', function() {
// work with your data var
});
See node.js docs for reference.
In order to support the full spectrum of possible HTTP applications, Node.js's HTTP API is very low-level. So data is received chunk by chunk not as whole.
There are two approaches you can take to this problem:
1) Collect data across multiple "data" events and append the results
together prior to printing the output. Use the "end" event to determine
when the stream is finished and you can write the output.
var http = require('http') ;
http.get('some/url' , function (resp) {
var respContent = '' ;
resp.on('data' , function (data) {
respContent += data.toString() ;//data is a buffer instance
}) ;
resp.on('end' , function() {
console.log(respContent) ;
}) ;
}).on('error' , console.error) ;
2) Use a third-party package to abstract the difficulties involved in
collecting an entire stream of data. Two different packages provide a
useful API for solving this problem (there are likely more!): bl (Buffer
List) and concat-stream; take your pick!
var http = require('http') ;
var bl = require('bl') ;
http.get('some/url', function (response) {
response.pipe(bl(function (err, data) {
if (err) {
return console.error(err)
}
data = data.toString() ;
console.log(data) ;
}))
}).on('error' , console.error) ;
The reason it's messed up is because you need to call JSON.parse(data.toString()). Data is a buffer so you can't just parse it directly.
If you don't mind using the request library
var request = require('request');
request('http://www.google.com', function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body) // Print the google web page.
}
})
If you are dealing with non-ASCII contents(Especially for Chinese/Japanese/Korean characters, no matter what encoding they are), you'd better not treat chunk data passed over response.on('data') event as string directly.
Concatenate them as byte buffers and decode them in response.on('end') only to get the correct result.
// Snippet in TypeScript syntax:
//
// Assuming that the server-side will accept the "test_string" you post, and
// respond a string that concatenates the content of "test_string" for many
// times so that it will triggers multiple times of the on("data") events.
//
const data2Post = '{"test_string": "swamps/沼泽/沼澤/沼地/늪"}';
const postOptions = {
hostname: "localhost",
port: 5000,
path: "/testService",
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data2Post) // Do not use data2Post.length on CJK string, it will return improper value for 'Content-Length'
},
timeout: 5000
};
let body: string = '';
let body_chunks: Array<Buffer> = [];
let body_chunks_bytelength: number = 0; // Used to terminate connection of too large POST response if you need.
let postReq = http.request(postOptions, (res) => {
console.log(`statusCode: ${res.statusCode}`);
res.on('data', (chunk: Buffer) => {
body_chunks.push(chunk);
body_chunks_bytelength += chunk.byteLength;
// Debug print. Please note that as the chunk may contain incomplete characters, the decoding may not be correct here. Only used to demonstrating the difference compare to the final result in the res.on("end") event.
console.log("Partial body: " + chunk.toString("utf8"));
// Terminate the connection in case the POST response is too large. (10*1024*1024 = 10MB)
if (body_chunks_bytelength > 10*1024*1024) {
postReq.connection.destroy();
console.error("Too large POST response. Connection terminated.");
}
});
res.on('end', () => {
// Decoding the correctly concatenated response data
let mergedBodyChunkBuffer:Buffer = Buffer.concat(body_chunks);
body = mergedBodyChunkBuffer.toString("utf8");
console.log("Body using chunk: " + body);
console.log(`body_chunks_bytelength=${body_chunks_bytelength}`);
});
});
How about HTTPS chunked response? I've been trying to read a response from an API that response over HTTPS with a header Transfer-Encoding: chunked. Each chunk is a Buffer but when I concat them all together and try converting to string with UTF-8 I get weird characters.