Cloud Function HTTP Request parameter being split up at "&" - node.js

I am trying to make a Firebase Cloud Function call that passes a parameter that is the string representation of a particular url. For the most part, I have my Cloud Function getting the parameters with var somePath = req.query.somePath; and doing all the logic I would like with those variables. However, the url contains ? and & symbols which I think may be confusing Alamofire into thinking that the data following the & is its own parameter being passed.
MY ATTEMPT
let somePath = "https://www.somewebsite.com/someSubpath?firstID=firstValue&someID=someValue"
let url = "https://us-central1-[myProjectName].cloudfunctions.net/myfunction?somePath=" + somePath + "&someData=" + someData
Alamofire.request(url, method: .post, parameters: nil, encoding: JSONEncoding.default, headers: nil).responseJSON {
response in
print("original URL request")
print(response.request as Any) // original URL request
print("URL response")
print(response.response as Any) // URL response
print(response.response?.statusCode ?? "response.response?.statusCode is nil")
}
I am guessing that the & in somePath is causing someID to be treated as another parameter and so my
cloud function thinks somePath =
https://www.somewebsite.com/someSubpath?firstID=firstValue
but it should be =
https://www.somewebsite.com/someSubpath?firstID=firstValue&someID=someValue
With this guess in mind, I then tried
Changing:
Alamofire.request(url, method: .post, parameters: nil, encoding: JSONEncoding.default, headers: nil).responseJSON {
To:
Alamofire.request(url, method: .post, parameters: ["somePath": somePath ?? ""], encoding: JSONEncoding.default, headers: nil).responseJSON {
But this just led to another error that somePath had not been passed as a parameter.
Does anyone know how I can prevent somePath from being split up despite having ? and & in it?

If you want to send data via query parameters, you should use query encoding instead of json like
let somePath = "https://www.somewebsite.com/someSubpath?firstID=firstValue&someID=someValue"
let parameters = ["somePath":somePath,"someData":"someData"]
let url = "https://us-central1-myProjectName.cloudfunctions.net/myfunction"
Alamofire.request(url, method: .post, parameters: parameters, encoding: URLEncoding.queryString, headers: nil).responseJSON {
response in
Helper.shared.log(for: response)
print("original URL request")
print(response.request as Any) // original URL request
print("URL response")
print(response.response as Any) // URL response
print(response.response?.statusCode ?? "response.response?.statusCode is nil")
}
Alamofire will handle all the required escape characters.

Related

Redirect URL through AWS Lambda and CloudFront

I have a lambda function and I wish to perform a 301 redirect from https://foo.test.com/user_id/bar/ to https://app.test.com/user_id/new
The user's request has an user_id that set as a variable (${userId}) and passed to the target URI (${newUri}).
The value of the user_id is numbers and letters (12-qw12)
I wrote the following code, which sets the ID as a variable and passes it to the Response Headers:
exports.handler = function handler(event, context, callback) {
let request = event.Records[0].cf.request;
let uri = request.uri;
let userId = uri.split('/')[3];
let newUri = `https://app.test.com/` + userId + `/new`;
const response = {
headers: {
'location': [{
key: 'Location',
value: newUri,
}],
},
status: '301',
statusDescription: 'Moved Permanently',
};
callback(null,response);
};
Unfortunately, I get an empty value from the ${userId} that passed to the Response headers:
https://app.test.com//new
If I write the value in the response headers as a string (value: 'https://app.test.com/12-qw12/new') then it works properly.
How can I pass a variable (${userId}) to the response headers without getting an empty value?
You can view the Request Event structure
here:
You can see the actual value of the request URI field by using console.log and checking the respective logs in Cloudwatch.
To print request uri add the folowing the following line before the last two lines in your code:
console.log(Request uri is "${request.uri}");
Then you can know in which index you read userid value.

How can response header can be modified?

i have a problem that break my mind since 2 days.
request( {url: url } , function(err,res, body){ res.headers['...'] = ...; return res }).pipe(response);
I thought this code change the header from the first response then put him on the second response. but NOT, all attempt fail. I try and I try but NOTHING, NOTHING WORKK.
Look, I'm really in peace and open-minded.
that's my code clear and concise :
modify_header(err,res,body){
var header = res.headers;
header['x-frame-options'] = null;
header['Set-Cookie'] = 'HttpOnly;';
return res;
}
request_src(req,response){
let isabsolute = this.decode_href(req.url);
if(!isabsolute) {
request.get({ url : this.url+req.url , headers : this.headers },this.modify_header).pipe(response);
}else{
request.get({ url : isabsolute , headers : this.headers },this.modify_header).pipe(response);
}
return false;
}
request_src(req,response) is a function called in http.createServer so, req & response are just the request from clients.
then, when i do request.get( {url:this.url ... I send client's request to an another site, like a proxy. but i need to change the header between the "other site" and the client. and believe me for sure, function modify_header modify nothing.
no, i lie just a little, when i set header['x-frame-options'] = null;res.headers is equals to null. that's ok
BUT, in the browser (client side) It just doesn't work that way. 'x-frame-options' is deny or something that's not mine (the same for cookie).
can you help me please, I pull out my hair since 2 days and this isn't good for me.
thank you.
The issue is that callbacks actually happen after a request is complete, which is way after the pipe has already begun streaming data to the response. You probably want to hook into the response event, which would look something like this:
request.get({ url: this.url + req.url, headers: this.headers })
.on('response', function (resp) {
resp.headers['my-custom-header'] = 'blah';
})
.pipe(response);
The response event is emitted before streaming to the destination (which in this case, is back to the original caller), so you should be able to do what you want using this method.
my code looks like that right now :
modify_header(res){
var header = res.headers;
header['x-frame-options'] = null;
header['Set-Cookie'] = 'HttpOnly;';
return res;
}
request_src(req,response){
console.log('original: ',req.url);
let isabsolute = this.decode_href(req.url);
if(!isabsolute) {
request.get({ url : this.url+req.url , headers : this.headers })
.on('response',this.modify_header)
.pipe(response);
}else{
request.get({ url : isabsolute , headers : this.headers})
.on('response',this.modify_header)
.pipe(response);
}
return false;
}

Invalid characters (non UTF-8) from node url Request but valid from browser?

I am calling Google Place api web service with Request in Node js. The body of the request gives an error Invalid request. One of the input parameters contains a non-UTF-8 string. because I use Khmer characters in the url parameters (keyword parameter).
nearByUrl = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=11.55082623,104.93225202&radius=400&keyword=ស្ថានីយប្រេង&key=' + MY_KEY;
request({
url: nearByUrl,
json: true
}, function (error, response, body) {
console.log(JSON.stringify(body, null, 2));
})
However, I can get a valid JSON with results when calling the exact same URL with Khmer characters from Chrome browser.
Is this problem related to Request?
How can I fix this?
So if you enter URL to which you want to send request in Chrome and open development tools, you will see that raw URL to which request is sent looks similar to this:
https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=11.55082623,104.93225202&radius=400&keyword=%E1%9E%9F%E1%9F%92%E1%9E%90%E1%9E%B6%E1%9E%93%E1%9E%B8%E1%9E%99%E1%9E%94%E1%9F%92%E1%9E%9A%E1%9F%81%E1%9E%84
Basically Chrome encoded all query parameters to ASCII, and when you enter directly parameters to URL, query parameters are not encoded. But if you send your parameters to request library through qs object, library will encoded them for you, and you will not have issue from the question.
var request = require("request")
var option = {
uri: 'https://maps.googleapis.com/maps/api/place/nearbysearch/json',
qs: {
location: '11.55082623,104.93225202',
radius: 1000,
keyword: 'ស្ថានីយប្រេង',
key: MY_KEY
}
};
request(
option, function (error, response, body) {
console.log(JSON.stringify(body, null, 2));
})
You could use method that is build into js library like this, but I personally think that first method is much better solution:
nearByUrl = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=11.55082623,104.93225202&radius=400&keyword=' + encodeURIComponent(escape('ស្ថានីយប្រេង')) + '&key=' + MY_KEY;
request({
url: nearByUrl,
json: true
}, function (error, response, body) {
console.log(JSON.stringify(body, null, 2));
})
Why I think that first solution with qs param is better solution, because library is doing it for you and all parameters are encoded.
Better explanation of second method can be found here.
Hopefully this is solution to your issue :)

Amazon MWS SubmitFeed Content-MD5 HTTP header did not match the Content-MD5 calculated by Amazon

I know this question is not new but all the solution I get for this are in PHP or my issue is different from them.
I am using MWS feed API to submit flat file for Price and Quantity Updates and always get the following error:
the Content-MD5 HTTP header you passed for your feed did not match the
Content-MD5 we calculated for your feed
I would like to ask 3 questions here:-
ContentMD5Value parameter is optional as given in doc, but if i not passed that than it will say that you must enter ContentMD5Value.
As in doc the ContentFeed which we are given to Amazon. Amazon create contentMD5 for that file and then compares that contentMD5 value with the contentMD5 value we send to Amazon.
If both match then OK, otherwise it will throw an error. But if suppose I will not send the file then also the same errors come that MD5 does not match. How is that possible? Which file are they calculating the MD5 for? Because I haven't send the file in ContentFeed.
If I send the contentMD5 in a header as well as parameter and sending the ContentFeed in body, I still get the error.
Note:- I am sending the contentMD5 in a header as well as in a parameters in form using request module and also calculating the signature with that and then pass the contentFeed in body.
I am using JavaScript (Meteor), I calculate the md5 using the crpyto module.
First, I think that my md5 is wrong but then I tried with an online website that will give me the md5 for a file the md5.
for my file is:
MD5 value: d90e9cfde58aeba7ea7385b6d77a1f1e
Base64Encodevalue: ZDkwZTljZmRlNThhZWJhN2VhNzM4NWI2ZDc3YTFmMWU=
The flat file I downloaded from for Price and Quantity Updates:-
https://sellercentral.amazon.in/gp/help/13461?ie=UTF8&Version=1&entries=0&
I calculated the signature also by giving ContentMD5Value while calculating the signature.
FeedType:'_POST_FLAT_FILE_PRICEANDQUANTITYONLY_UPDATE_DATA_'
As, I read documentation for that I passed the MD5-header in headers and also send as parameter.
Amazon doc says:
Previously, Amazon MWS accepted the MD5 hash as a Content-MD5 header
instead of a parameter. Passing it as a parameter ensures that the MD5
value is part of the method signature, which prevents anyone on the
network from tampering with the feed content.
Amazon MWS will still accept a Content-MD5 header whether or not a
ContentMD5Value parameter is included. If both a header and parameter
are used, and they do not match, you will receive an
InvalidParameterValue error.
I am using the request module for http requests.
I am passing all the required keys, seller id, etc. in form of request module and passing the FeedContent in body.
I tried sending the file as follows:
Method for submitFeed is:-
submitFeed : function(){
console.log("submitFeedAPI running..");
app = mwsReport({auth: {sellerId:'A4TUFSCXD64V3', accessKeyId:'AKIAJBU3FTBCJUIZWF', secretKey:'Eug7ZbaLljtrnGKGFT/DTH23HJ' }, marketplace: 'IN'});
app.submitFeedsAPI({FeedType:'_POST_FLAT_FILE_PRICEANDQUANTITYONLY_UPDATE_DATA_'},Meteor.bindEnvironment(function(err,response){
if(err){
console.log("error in submit feed...")
console.log(err)
}
else{
console.log("suuccess submit feed....")
console.log(response);
}
}))
Method that call Amazon submitFeedAPI is:-
var submitFeedsAPI = function(options, callback){
console.log("submitFeedsAPI running...");
var fileReadStream = fs.createReadStream('/home/parveen/Downloads/test/testting.txt');
var contentMD5Value = crypto.createHash('md5').update(file).digest('base64');
var reqForm = {query: {"Action": "SubmitFeed", "MarketplaceId": mpList[mpCur].id, "FeedType":options.FeedType,"PurgeAndReplace":false,"ContentMD5Value":contentMD5Value}};
mwsReqProcessor(reqForm, 'submitFeedsAPI', "submitFeedsAPIResponse", "submitFeedsAPIResult", "mwsprod-0000",false,file, callback);
}
also try
var fileReadStream = fs.createReadStream('/home/parveen/Downloads/test/testting.txt');
var base64Contents = fileReadStream.toString('base64');
var contentMD5Value = crypto.createHash('md5').update(base64Contents).digest('base64');
mwsReqProcessor function is as below:-
mwsReqProcessor = function mwsReqProcessor(reqForm, name, responseKey, resultKey, errorCode,reportFlag,file, callback) {
reqOpt = {
url: mwsReqUrl,
method: 'POST',
timeout: 40000,
body:{FeedContent: fs.readFileSync('/home/parveen/feedContentFile/Flat.File.PriceInventory.in.txt')},
json:true,
form: null,
headers: {
// 'Transfer-Encoding': 'chunked',
//'Content-Type': 'text/xml',
// 'Content-MD5':'ZDkwZTljZmRlNThhZWJhN2VhNzM4NWI2ZDc3YTFmMWU=',
// 'Content-Type': 'text/xml; charset=iso-8859-1'
'Content-Type':'text/tab-separated-values;charset=UTF-8'
},
}
reqOpt.form = mwsReqQryGen(reqForm);
var r = request(reqOpt, function (err, res, body){
console.log(err)
console.log(res)
})
// var form = r.form();
//form.append('FeedContent',fs.createReadStream('/home/parveen/feedContent//File/Flat.File.PriceInventory.in.txt'))
}
Method for mwsReqQryGen generation:-
mwsReqQryGen = function mwsReqQryGen(options) {
var method = (options && options.method) ? ('' + options.method) : 'POST',
host = (options && options.host) ? ('' + options.host) : mwsReqHost,
path = (options && options.path) ? ('' + options.path) : mwsReqPath,
query = (options && options.query) ? options.query : null,
returnData = {
"AWSAccessKeyId": authInfo.accessKeyId,
"SellerId": authInfo.sellerId,
"SignatureMethod": "HmacSHA256",
"SignatureVersion": "2",
"Timestamp": new Date().toISOString(),
"Version":"2009-01-01",
},
key;
if(query && typeof query === "object")
for(key in query)
if(query.hasOwnProperty(key)) returnData[key] = ('' + query[key]);
if(authInfo.secretKey && method && host && path) {
// Sort query parameters
var keys = [],
qry = {};
for(key in returnData)
if(returnData.hasOwnProperty(key)) keys.push(key);
keys = keys.sort();
for(key in keys)
if(keys.hasOwnProperty(key)) qry[keys[key]] = returnData[keys[key]];
var sign = [method, host, path, qs.stringify(qry)].join("\n");
console.log("..................................................")
returnData.Signature = mwsReqSignGen(sign);
}
//console.log(returnData); // for debug
return returnData;
};
I also tried with following:-
reqOpt = {
url: mwsReqUrl,
method: 'POST',
timeout: 40000,
json:true,
form: null,
body: {FeedContent: fs.createReadStream('/home/parveen/feedContentFile/Flat.File.PriceInventory.in.txt')},
headers: {
// 'Transfer-Encoding': 'chunked',
//'Content-Type': 'text/xml',
// 'Content-MD5':'ZDkwZTljZmRlNThhZWJhN2VhNzM4NWI2ZDc3YTFmMWU=',
// 'Content-Type': 'text/xml; charset=iso-8859-1'
},
}
I also tried without JSON and directly send the file read stream in the
body, i.e:
reqOpt = {
url: mwsReqUrl,
method: 'POST',
timeout: 40000,
form: null,
body: fs.createReadStream('/home/parveen/feedContentFile/Flat.File.PriceInventory.in.txt'),
headers: {
// 'Transfer-Encoding': 'chunked',
//'Content-Type': 'text/xml',
// 'Content-MD5':'ZDkwZTljZmRlNThhZWJhN2VhNzM4NWI2ZDc3YTFmMWU=',
// 'Content-Type': 'text/xml; charset=iso-8859-1'
},
}
But same error comes every time:
the Content-MD5 HTTP header you passed for your feed did not match the
Content-MD5 we calculated for your feed
I want to know where I am doing wrong or what is the right way to submit feed API and sending the file using request module.
I also tried with the code given on MWS to generate the MD5 but same
error occurred each time.
My .txt file as follows:
sku price quantity
TP-T2-00-M 2
Any help is much appreciated
finally i got the solution as Ravi said above. Actually there are few points i want to clear here for you all who are facing the same issue:-
Amazon marketplace API doc is not giving proper information and example. Even i guess the documentation is not updated . As in doc they said that ContentMD5Value parameter value is optional on this page
You can check there they clearly mention that the field is not required but if you not pass than they gives the error that you must pass content MD5 value.
So that is wrong. ContentMD5 is required attribute.
They said in the same doc that you need to send file data weather its a xml or flat-file in the field key name i.e. FeedContent.
But that is also not needed you can send the file with any name no
need to give FeedContent key for the file you just need to send the
file in stream.
They will give the same error of contentMD5 not match weather you send file or not because if they not found file than the contentMD5 you send will not match to that. SO if you are getting the ContentMD5 not match error than check the following:-
Check that you are generating the right MD5 code for your file you can check whether you are generating the right code or not by there java code they given on doc . You can get that from this link
Don't trust on online websites for generating the MD5 hash and base64 encoding.
If your MD5 is matched with the MD5 generated from Java code they given than one thing is clear that your MD5 is right so no need to change on that.
Once your MD5 is correct and after that also if you get the same error that is:-
Amazon MWS SubmitFeed Content-MD5 HTTP header did not match the
Content-MD5 calculated by Amazon
ContentMD5 not matched .Than you need to check only and only you file uploading mechanism.
Because now the file you are sending to Amazon is not either correct or you are not sending it in the right way.
Check for file upload
For checking whether or not you are sending the right file you need to check with following:-
You need to send the required parameters like sellerId, marketplaceId, AWSAccessKey etc. as query params.
You need to send the file in the form-data as multipart , if you are using the request module of node.js than you can see the above code given by Ravi.
you need to set the header as only:-
'Content-Type': 'application/x-www-form-urlencoded'
No need to send the header as chunked or tab separated etc because i don't need them any more they are even confuse me because somewhere someone write use this header on other place someone write use this header.
So finally as i am abel to submit this API i didn't need any of the header rather than application/x-www-form-urlencoded.
Example:-
reqOpt = {
url: mwsReqUrl,
method: 'POST',
formData: {
my_file: fs.createReadStream('file.txt')
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
qs: { }// all the parameters that you are using while creating signature.
Code for creating the contentMD5 is:-
var fileData= fs.readFileSync('/home/parveen/Downloads/test/feed.txt','utf8');
var contentMD5Value = crypto.createHash('md5').update(fileData).digest('base64');
As i am facing the issue that is because i am using form and form-data simultaneously via request module so i convert my form data with qs(query string) and file in form-data as multipart.
So in this way you can successfully submit the API for submit feed.
Amazon requires the md5 hash of the file in base64 encoding.
Your code:
var fileReadStream = fs.createReadStream('/path/to/file.txt');
var file = fileReadStream.toString('base64'); //'[object Object]'
var contentMD5Value = crypto.createHash('md5').update(file).digest('base64');
wrongly assumes that a readStream's toString() will produce the file contents, when, in fact, this method is inherited from Object and produces the string '[object Object]'.
Base64-encoding that string always produces the 'FEGnkJwIfbvnzlmIG534uQ==' that you mentioned.
If you want to properly read and encode the hash, you can do the following:
var fileContents = fs.readFileSync('/path/to/file.txt'); // produces a byte Buffer
var contentMD5Value = crypto.createHash('md5').update(fileContents).digest('base64'); // properly encoded
which provides results equivalent to the following PHP snippet:
$contentMD5Value = base64_encode(md5_file('/path/to/file.txt', true));
Hey sorry for late reply but why don't you try to send the file in multipart in the form-data request and other queryStrings in 'qs' property of request module.
You can submit the request as follows:-
reqOpt = {
url: mwsReqUrl,
method: 'POST',
formData: {
my_file: fs.createReadStream('file.txt')
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
qs: {
AWSAccessKeyId: '<your AWSAccessKeyId>',
SellerId: '<your SellerId>',
SignatureMethod: '<your SignatureMethod>',
SignatureVersion: '<your SignatureVersion>',
Timestamp: '<your Timestamp>',
Version: '<your Version>',
Action: 'SubmitFeed',
MarketplaceId: '<your MarketplaceId>',
FeedType: '_POST_FLAT_FILE_PRICEANDQUANTITYONLY_UPDATE_DATA_',
PurgeAndReplace: 'false',
ContentMD5Value: '<your file.txt ContentMD5Value>',
Signature: '<your Signature>'
}
}
request(reqOpt, function(err, res){
})
Probably, I'm too late, but here are key points for C#:
1) Multipart form-data didn't work at all. Finished with the following (simplified):
HttpContent content = new StringContent(xmlStr, Encoding.UTF8, "application/xml");
HttpClient client = new HttpClient();
client.PostAsync(query, content)
2) About query:
UriBuilder builder = new UriBuilder("https://mws.amazonservices.com/");
NameValueCollection query = HttpUtility.ParseQueryString(builder.Query);
query["AwsAccessKeyId"] = your_key_str;
query["FeedType"] = "_POST_ORDER_FULFILLMENT_DATA_";
... other required params
query["ContentMD5Value"] = Md5Base64(xmlStr);
builder.Query = query.ToString();
query = builder.ToString();
3) About Md5base64
public static string Md5Base64(string xmlStr)
{
byte[] plainTextBytes = Encoding.UTF8.GetBytes(xmlStr);
MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider();
byte[] hash = provider.ComputeHash(plainTextBytes);
return Convert.ToBase64String(hash);
}

Node.js: Make a HTTP POST with params

call = "https://aaa#api.twilio.com/2010-04-01/Accounts/sasa/Calls.xml"
fields = { To : "+12321434", From : req.body.from }
request.post
url: call, body: fields (err,response,body) ->
console.log response.body
How can I pass fields to the HTTP POST request?
It works if I pass a string like "To=+12321434" but not "To=+12321434,From = req.body.from"
You need to stringify your data, look at the example:
http://nodejs.org/docs/latest/api/querystring.html#querystring.stringify

Resources