I'm trying to build a Node.js server with console logging similar to that of Django's development server. E.g.
[27/Jun/2011 15:26:50] "GET /?test=5 HTTP/1.1" 200 545
The following server.js (based on the Node Beginner Book tutorial) gets me the time and request information:
var http = require("http");
var url = require ("url");
var port = 1234;
function start(route, handle) {
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname;
var query = url.parse(request.url).query;
route(handle, pathname, query, response);
logRequest(request);
}
http.createServer(onRequest).listen(port);
console.log("\nServer running at http://192.168.1.5:" + port + "/");
console.log("Press CONTROL-C to quit.\n");
}
function logRequest(request) {
var pathname = url.parse(request.url).pathname;
var query = url.parse(request.url).query;
if (query == undefined) {
query = "";
}
var currentDate = new Date();
var day = currentDate.getDate();
var month = currentDate.getMonth() + 1;
var year = currentDate.getFullYear();
var hours = currentDate.getHours();
var minutes = currentDate.getMinutes();
var seconds = currentDate.getSeconds();
if (minutes < 10) {
minutes = "0" + minutes;
}
if (seconds < 10) {
seconds = "0" + seconds;
}
console.log("[" + year + "/" + month + "/" + day +
" " + hours + ":" + minutes + ":" + seconds + '] "' +
request.method + " " + pathname + query +
" HTTP/" + request.httpVersion + '"');
}
exports.start = start;
Question: how would I update this code to get the response.statusCode and whatever the "545" number is into the log output?
When I tried adding the response object to the logRequest function, the response statusCode was always "200", even when I knew (via debug logging) that my router.js was generating 404 errors.
If you are using the route method found in the tutorial you linked, then the reason why using the response.statusCode doesn't work is because they set the 404 status code using this line
response.writeHead(404, {"Content-Type": "text/html"});
If you take a look at the documentation right there Node.JS Docs - writeHead it states that .writeHead will not set the .statusCode value. If you want to use the .statusCode value, you should change the line in the route method to read like this :
response.statusCode = 404;
response.setHeader("Content-Type", "text/html");
Directly assigning a value to statusCode and using "implicit" header declarations (setHeader instead of writeHead) will produce the same result for the user, but on your side you will have access to the statusCode later on in your log method.
Related
What I'm trying to achieve is a Zapier Code (JS) action to preform a Twitter reply to a given status ID with a given text .
I'm already using the functioning Zapier (JS) Code action offered by #KayCee which preform a POST favorites/create and would like to modify it so it would preform a reply action to any given status_id using the POST statuses/update (in_reply_to_status_id) as instructed by Twitter API.
Here is #KayCee's code with the modifications I made to preform a reply:
// This code requires that you set the ID of the tweet that you want to reply to as an input variable called "reply_to_id" and the text you wish to reply as an input variable called "status_text". Learn more at https://zapier.com/help/code/#data-variables
// INSTUCTIONS FOR SETTING THESE REQUIRED VARIABLES: After you create a new app at https://apps.twitter.com/, click on the name of the app to open it. Then, select the "Keys and Access Tokens" tab.
var twitterApplicationConsumerKey = 'CONSUMERKEY';
var twitterApplicationConsumerSecret = 'CONSUMERSECRET';
var twitterApplicationAccessToken = 'ACCESSTOKEN';
var twitterApplicationAccessTokenSecret = 'ACCESSTOKENSECRET';
// That's it. No need to edit anything below.
function b64_hmac_sha1(k,d,_p,_z){
if(!_p){_p='=';}if(!_z){_z=8;}function _f(t,b,c,d){if(t<20){return(b&c)|((~b)&d);}if(t<40){return b^c^d;}if(t<60){return(b&c)|(b&d)|(c&d);}return b^c^d;}function _k(t){return(t<20)?1518500249:(t<40)?1859775393:(t<60)?-1894007588:-899497514;}function _s(x,y){var l=(x&0xFFFF)+(y&0xFFFF),m=(x>>16)+(y>>16)+(l>>16);return(m<<16)|(l&0xFFFF);}function _r(n,c){return(n<<c)|(n>>>(32-c));}function _c(x,l){x[l>>5]|=0x80<<(24-l%32);x[((l+64>>9)<<4)+15]=l;var w=[80],a=1732584193,b=-271733879,c=-1732584194,d=271733878,e=-1009589776;for(var i=0;i<x.length;i+=16){var o=a,p=b,q=c,r=d,s=e;for(var j=0;j<80;j++){if(j<16){w[j]=x[i+j];}else{w[j]=_r(w[j-3]^w[j-8]^w[j-14]^w[j-16],1);}var t=_s(_s(_r(a,5),_f(j,b,c,d)),_s(_s(e,w[j]),_k(j)));e=d;d=c;c=_r(b,30);b=a;a=t;}a=_s(a,o);b=_s(b,p);c=_s(c,q);d=_s(d,r);e=_s(e,s);}return[a,b,c,d,e];}function _b(s){var b=[],m=(1<<_z)-1;for(var i=0;i<s.length*_z;i+=_z){b[i>>5]|=(s.charCodeAt(i/8)&m)<<(32-_z-i%32);}return b;}function _h(k,d){var b=_b(k);if(b.length>16){b=_c(b,k.length*_z);}var p=[16],o=[16];for(var i=0;i<16;i++){p[i]=b[i]^0x36363636;o[i]=b[i]^0x5C5C5C5C;}var h=_c(p.concat(_b(d)),512+d.length*_z);return _c(o.concat(h),512+160);}function _n(b){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s='';for(var i=0;i<b.length*4;i+=3){var r=(((b[i>>2]>>8*(3-i%4))&0xFF)<<16)|(((b[i+1>>2]>>8*(3-(i+1)%4))&0xFF)<<8)|((b[i+2>>2]>>8*(3-(i+2)%4))&0xFF);for(var j=0;j<4;j++){if(i*8+j*6>b.length*32){s+=_p;}else{s+=t.charAt((r>>6*(3-j))&0x3F);}}}return s;}function _x(k,d){return _n(_h(k,d));}return _x(k,d);
}
var replyToId = input.reply_to_id;
var status = input.status_text;
//create nonce
function generateRandomString(desiredLengthOfRandomString) {
var result = '';
var possibleCharactersForRandomString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < desiredLengthOfRandomString; i++ )
result += possibleCharactersForRandomString.charAt(Math.floor(Math.random() * possibleCharactersForRandomString.length));
return result;
}
var randomString = generateRandomString(32);
var nonce = new Buffer(randomString).toString('base64');
//create timestamp
var timestamp = Math.floor(new Date() / 1000);
//create the signature
var signatureParameterString = 'in_reply_to_status_id=' + replyToId + '&status=' + status + '&oauth_consumer_key=' + twitterApplicationConsumerKey + '&oauth_nonce=' + encodeURIComponent(nonce) + '&oauth_signature_method=HMAC-SHA1&oauth_timestamp=' + timestamp + '&oauth_token=' + twitterApplicationAccessToken + '&oauth_version=1.0';
var signatureBaseString = 'POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&' + encodeURIComponent(signatureParameterString);
var signingKey = encodeURIComponent(twitterApplicationConsumerSecret) + '&' + encodeURIComponent(twitterApplicationAccessTokenSecret);
var signature = b64_hmac_sha1(signingKey, signatureBaseString);
var apiUrl = 'https://api.twitter.com/1.1/statuses/update.json?in_reply_to_status_id=' + replyToId + '&status=' + status;
var oauthString = 'OAuth oauth_consumer_key="' + twitterApplicationConsumerKey + '", oauth_nonce="' + encodeURIComponent(nonce) + '", oauth_signature="' + encodeURIComponent(signature) + '", oauth_signature_method="HMAC-SHA1", oauth_timestamp="' + timestamp + '", oauth_token="' + twitterApplicationAccessToken + '", oauth_version="1.0"';
fetch(apiUrl, {
method: 'POST',
headers: {
'Authorization': oauthString
}
})
.then(function(res) {
return res.json();
})
.then(function(body) {
var output = body;
callback(null, output);
})
.catch(callback);
For some reason, I keep on getting error message: "Could not authenticate you" although the same code worked for performing a "like".
Not sure what am I doing wrong?
I am currently using a calender system that has an API I can access with SOAP requests. It exists on an IIS server.
Initially I figured I could create an HTML page that that I would then have use Javascript to return the contents of the SOAP request - and my SOAP requests returns exactly what I want it to, but not in valid XML - it just displays on the screen (currently commented out below).
What I need to know is how do I get a page to return just the valid XML response (and no other tags, so it is recognized as XML)? Would PHP be better suited for this - or ASP?
My current javascript looks like this:
function soap() {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open('POST', 'http://myserver/EMSAPI/', true);
//Todays Date now = new Date();
year = "" + now.getFullYear();
month = "" + (now.getMonth() + 1); if (month.length == 1) { month = "0" + month; }
day = "" + now.getDate(); if (day.length == 1) { day = "0" + day; }
hour = "" + now.getHours(); if (hour.length == 1) { hour = "0" + hour; }
minute = "" + now.getMinutes(); if (minute.length == 1) { minute = "0" + minute; }
second = "" + now.getSeconds(); if (second.length == 1) { second = "0" + second; }
todaydate = year + "-" + month + "-" + day + "T" + hour + ":" + minute + ":" + second + ".000";
// build SOAP request
var sr =
'<?xml version="1.0" encoding="utf-8"?>' +
'<soap:Envelope ' +
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
'xmlns:api="http://127.0.0.1/Integrics/Enswitch/API" ' +
'xmlns:xsd="http://www.w3.org/2001/XMLSchema" ' +
'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">' +
'<soap:Body>' +
'<GetAllBookings xmlns="http://DEA.EMS.API.Web.Service/">' +
'<UserName>EmsAPI</UserName>' +
'<Password>Mypass</Password>' +
'<StartDate>'+todaydate+'</StartDate>' +
'<EndDate>'+todaydate+'</EndDate>' +
'<BuildingID>36</BuildingID>' +
'<ViewComboRoomComponents>false</ViewComboRoomComponents>' +
'</GetAllBookings>' +
'</soap:Body>' +
'</soap:Envelope>';
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4) {
if (xmlhttp.status == 200) {
ret = xmlhttp.responseText;
//$(document.body).append(ret);
}
}
}
// Send the POST request
xmlhttp.setRequestHeader('Content-Type', 'text/xml');
xmlhttp.send(sr);
// send request
// ...
}
window.onload = function() { soap(); };
My HTML is very straight forward - it is a blank document besides including the javascript.
The problem is I'm using some software that cannot interface with the calender directly - and that software will only accept a valid XML response - so whatever page I write, it has to return just the pure XML so that way when I tell the software the page URL - it is given the XML response appropriately.
Just wondering what other ways there may be to go about this to get it to work. I apologize if the question is confusing - I can elaborate if needed.
I went with Classic ASP in order to do what I wanted it to.
My final code looked like this in classic ASP:
<%
Dim objXMLHTTP : set objXMLHTTP = Server.CreateObject("MSXML2.XMLHTTP")
Dim strRequest, strResult, strFunction, strURL, strNamespace
'URL to SOAP namespace and connection URL
strNamespace = "http://DEA.EMS.API.Web.Service/"
strURL = "http://myserver/EMSAPI/"
'function you want to call
strFunction = "GetBuildings"
'strFunction = "test" 'no parameters required
strRequest ="<?xml version=""1.0"" encoding=""utf-8""?>" &_
"<soap:Envelope" &_
" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""" &_
" xmlns:api=""http://127.0.0.1/Integrics/Enswitch/API""" &_
" xmlns:xsd=""http://www.w3.org/2001/XMLSchema""" &_
" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" &_
"<soap:Body>" &_
"<GetBuildings xmlns=""http://DEA.EMS.API.Web.Service/"">" &_
"<UserName>Myusername</UserName>" &_
"<Password>mypassword</Password>" &_
"</GetBuildings>" &_
"</soap:Body>" &_
"</soap:Envelope>"
objXMLHTTP.open "POST", ""& strURL &"", True
objXMLHTTP.setRequestHeader "Content-Type", "text/xml; charset=utf-8"
objXMLHTTP.setRequestHeader "Content-Length", Len(strRequest)
objXMLHTTP.setRequestHeader "SOAPAction", strNamespace & strFunction
'send the request and capture the result
objXMLHTTP.send(strRequest)
'Set a timer to wait for response
set shell = CreateObject("WScript.Shell")
t1 = timer()
sleep(1)
t2 = timer()
response.write "waited "& t2-t1 &" secs"
function sleep(seconds)
if seconds>=1 then shell.popup "pausing",seconds,"pause",64
end function
strResult = objXMLHTTP.responseText
'display the XML
response.write strResult
%>
The script below is working fantastically when monitoring a folder for new .ogg files. It successfully creates a folder with the new filename and then renames the file according to the file according to its created date/
However, the issues arise when I add multiple files at the same time as the script attempts to create a folder that already exists, suggesting it is mixing up the two filenames somehow. Has anyone any suggestions as to what I might be doing incorrectly? I presume its simple code structure although I'm not able to work out why.
var baseDir = './',
path = require('path'),
fs = require('fs');
// watch the directory for new files
fs.watch(baseDir, function(event, file) {
var ext = path.extname(file)
basename = path.basename(file).substring(0, path.basename(file).length - ext.length);
// check it wasnt a delete action
fs.exists(baseDir + file, function(exists) {
// check we have the right file type
if(exists && ext === '.ogg'){
// get the created date
fs.stat(baseDir + file, function (err, stats){
if (err)
throw err;
var year = stats.ctime.getFullYear();
var month = stats.ctime.getMonth()+1;
var day = stats.ctime.getDate();
var hour = stats.ctime.getHours();
var sec = stats.ctime.getSeconds();
if(month < 10){
month = '0' + month;
}
if(day < 10){
day = '0' + day;
}
if(hour < 10){
hour = '0' + hour;
}
if(sec < 10){
sec = '0' + sec;
}
var name = year + '' + month + '' + day + '' + hour + '' + sec;
// does the basename directory exist?
fs.exists(baseDir + '/' + basename, function(exists) {
// if the directory doesnt exist
if(!exists){
// make the directory
fs.mkdir(baseDir + '/' + basename, 0777, function (err, stats){
if (err)
throw err;
moveFile(file, basename, name, ext);
});
} else {
moveFile(file, basename, name, ext);
}
});
});
}
});
});
function moveFile(file, basename, name, ext){
// move the file to the new directory
fs.rename(baseDir + file, baseDir + '/' + basename + '/' + name + ext, function (err) {
if (err)
throw err;
// console.log('Rename complete');
});
}
Ok, so I had a few extra minutes and decided to have a look for you. I refactored your code a little, but the basic structure should be easy to recognize.
var baseDir = './test',
path = require('path'),
fs = require('fs');
// watch the directory for new files
fs.watch(baseDir, function(event, file) {
var ext = path.extname(file),
basename = path.basename(file).substring(0, path.basename(file).length - ext.length);
// check it wasnt a delete action
// check we have the right file type
var filePath = path.join(baseDir, file);
if(fs.existsSync(filePath) && ext === '.ogg'){
// get the created date
var stats = fs.statSync(filePath);
var name = getName(stats);
// if the directory doesnt exist
var baseDirPath = path.join(baseDir, basename);
if(!fs.existsSync(baseDirPath)){
// make the directory
fs.mkdirSync(baseDirPath, 0777);
}
moveFile(file, basename, name, ext);
}
});
function getName (stats) {
var year = stats.ctime.getFullYear();
var month = stats.ctime.getMonth()+1;
var day = stats.ctime.getDate();
var hour = stats.ctime.getHours();
// need minutes!
var minutes = stats.ctime.getMinutes();
var sec = stats.ctime.getSeconds();
if(month %lt 10){
month = '0' + month;
}
if(day < 10){
day = '0' + day;
}
if(hour < 10){
hour = '0' + hour;
}
if(minutes < 10){
minutes = '0' + minutes;
}
if(sec < 10){
sec = '0' + sec;
}
// missing the minute, previously
return year + '' + month + '' + day + '' + hour + '' + minutes + '' + sec;
}
function moveFile(file, basename, name, ext){
// move the file to the new directory
var src = path.join(baseDir, file),
dest = path.join(baseDir, basename, name+ext);
console.log("Moving ", src, "-", dest);
fs.renameSync(src, dest);
}
Some tips/corrections:
Stick with the synchronous fs methods that end in Sync when working on simple scripts like this. While node.js is famous for it's asynchronous ability, it's a bit of a premature optimization IMO. If you need to embed this in a high-performance webserver, for instance, optimize at that point, not before.
You were missing a minutes variable when you create the new filename. This has a pretty good chance of causing a name collision, so I corrected it.
Try to use the path library (like path.join) more to your advantage, as manually joining strings for paths can often lead to brittle code.
There are still several edge cases where this can crash. Creating a file without an extension that will have the same name as a directory you will create based on another file. (Files can't become directories, and you can't move a file inside another file.). If you plan to go into a production environment, you will want to harden the code with at least a few unit tests.
Cheers,
Dan
Why does the Heroku Nodejs AWS S3 tutorial (https://devcenter.heroku.com/articles/s3-upload-node) fail when a plus '+' character is present in the signature?
Summary: Regenerate the signature until it does not contain a plus '+' character anymore:
if (signature.indexOf('+') != -1) {
setTimeout(function(){
//regenerate signature until it doesn't contain + anymore
generateSignature();
}, 400);
}
The working solution I found was suggested by #chadsaun on this forum post:
http://www.uploadify.com/forum/#/discussion/comment/10777
Full heroku example code solution:
app.get('/sign_s3', function(req, res){
var object_name = req.query.s3_object_name;
var mime_type = req.query.s3_object_type;
var amz_headers = "x-amz-acl:public-read";
var signature, expires;
function generateSignature() {
var thisTime = new Date().getTime();
expires = Math.ceil((thisTime + 10000)/1000);
var put_request = "PUT\n\n" + mime_type + "\n"
+ expires + "\n" + amz_headers
+ "\n/" + S3_BUCKET + "/"
+ object_name;
signature = crypto.createHmac('sha1', AWS_SECRET_KEY)
.update(put_request)
.digest('base64');
console.log(signature);
if (signature.indexOf('+') != -1) {
setTimeout(function(){
//regenerate signature until it doesn't contain + anymore
generateSignature();
}, 400);
} else {
var url = 'https://' + S3_BUCKET + '.s3.amazonaws.com/' + object_name;
var credentials = {
signed_request: url + "?AWSAccessKeyId=" + AWS_ACCESS_KEY
+ "&Expires=" + expires + "&Signature="
+ signature,
url: url,
};
res.write(JSON.stringify(credentials));
res.end();
}
}
generateSignature();
});
I guess you need to uri encode your signature. like this:
signature = encodeURIComponent(
crypto.createHmac('sha1', AWS_SECRET_KEY)
.update(put_request)
.digest('base64')
);
this way you don't need to check if the signature contain '+'
Trying to make a simple Tumblr scraper using node.js
var request = require('request');
var fs = require('fs');
var apiKey = 'my-key-here';
var offset = 0;
for (var i=0; i<5; i++) {
console.log('request #' + i + '...');
var requestURL = 'http://api.tumblr.com/v2/blog/blog.tumblr.com/posts/text?api_key='
+ apiKey
+ '&offset='
+ offset;
console.log(requestURL);
request(requestURL, function(error, response, body) {
if (!error && response.statusCode == 200) {
var resultAsJSON = JSON.parse(body);
resultAsJSON.response.posts.forEach(function(obj) {
fs.appendFile('content.txt', offset + ' ' + obj.title + '\n', function (err) {
if (err) return console.log(err);
});
offset++;
});
}
});
}
By default, the API only returns a maximum of 20 latest posts. I want to grab all the posts instead. As a test, I want to get the latest 100 first, hence the i<5in the loop declaration.
The trick to do it is to use the offset parameter. Given an offset value of 20, for example, the API will not return the latest 20, but instead returns posts starting from the 21st from the top.
As I can't be sure that the API will always return 20 posts, I am using offset++ to get the correct offset number.
The code above works, but console.log(requestURL) returns http://api.tumblr.com/v2/blog/blog.tumblr.com/posts/text?api_key=my-key-here&offset=0
five times.
So my question is, why does the offset value in my requestURL remains as 0, even though I have added offset++?
You should increment the offset in the loop, not in callbacks. Callbacks fire only after the request has been completed, which means you make five requests with offset = 0 and it's incremented after you get a response.
var requestURL = 'http://api.tumblr.com/v2/blog/blog.tumblr.com/posts/text?api_key='
+ apiKey
+ '&offset='
+ (offset++); // increment here, before passing URL to request();
Edit:
To offset by 20 in each iteration, and use the offset in callback:
for (var i=0; i<5; i++) {
var offset = i * 20, requestURL = 'http://api.tumblr.com/v2/blog/blog.tumblr.com/posts/text?api_key='
+ apiKey
+ '&offset='
+ offset;
(function(off){
request(requestURL, function(error, response, body) {
if (!error && response.statusCode == 200) {
var resultAsJSON = JSON.parse(body);
resultAsJSON.response.posts.forEach(function(obj) {
fs.appendFile('content.txt', off + ' ' + obj.title + '\n', function (err) {
if (err) return console.log(err);
});
off++;
});
}
});
}(offset)); // pass the offset from loop to a closure
}