This blog post explains how one can use Node.js to expand a URL that has been shortened by a service such as t.co (built into Twitter) and bit.ly. We’ll look at a simple implementation and at an advanced implementation that uses promises.
The minimum
You need to install Mikeal Rogers’
request module:
npm install request
That module automatically follows all redirects from the shortened URL. Once you are at your final destination, you only need to find out where you are:
var request = require("request");
function expandUrl(shortUrl) {
request( { method: "HEAD", url: shortUrl, followAllRedirects: true },
function (error, response) {
console.log(response.request.href);
});
}
Prettier with promises
If you want to write a function that returns the expanded URL, more work is needed. You have the option of using a callback, but promises usually lead to prettier code. Let’s use Kris Kowal’s
Q module:
npm install q
The “promising” code looks as follows.
var Q = require("q");
var request = require("request");
function expandUrl(shortUrl) {
return Q.ncall(request, null, {
method: "HEAD",
url: shortUrl,
followAllRedirects: true
// If a callback receives more than one (non-error) argument
// then the promised value is an array. We want element 0.
}).get('0').get('request').get('href');
}
Node that the callback created by
deferred.node() automatically handles errors. Invoking the function works like this:
expandUrl("http://t.co/Zc3cUoly")
.then(function (longUrl) {
console.log(longUrl);
});
Related reading
18 comments:
It can be done without any dependencies in just a few lines of code. See https://github.com/mathiasbynens/node-unshorten.
You should use a HEAD request. For unshortening of URLs you usually don't need the actual content of a page.
Good point. Fixed.
Might be able to make that even prettier:
var Q = require("q");
var request = require("request");
function expandUrl(shortUrl) {
var deferred = Q.defer();
request(
{ method: "HEAD", url: shortUrl, followAllRedirects: true },
deferred.node()
);
return deferred.promise
.get('request')
.get('href');
}
The error propagates through the .get promises.
Very nice. I wasn’t aware of this feature. When I first read it, it wasn’t obvious to me what method `node` did. If I figured it out correctly, then better names might be: createNodejsCallback or getNodejsCallback (or something similar). Rationale: `node` is too overloaded a term and I would like to see the word `callback`, along with an indicator of whether a new instance is created each time or the instance is cached.
Added https://github.com/kriskowal/q/issues/55
Thanks.
It could be even further abbreviated with Q.ncall.
var Q = require("q");
var request = require("request");
function expandUrl(shortUrl) {
return Q.ncall(request, null, {
method: "HEAD",
url: shortUrl,
followAllRedirects: true
}).get('request').get('href');
}
As in my previous post, the error, if there is one, propagates through the .get promises. The null is this for the function.
Enough now, cannot handle any more beauty. ;-)
Alas, it doesn’t completely work:
> expandUrl("http://t.co/Zc3cUoly").then(function (longUrl) {
... console.log(longUrl);
... });
{ promiseSend: [Function],
valueOf: [Function] }
...and with `deferred` promise implementation it would look like:
var request = require('deferred').promisify(require("request"));
function expandUrl(shortUrl) {
return request( { method: "HEAD", url: shortUrl, followAllRedirects: true } )
.get(0, 'request', 'href');
}
expandUrl("http://t.co/Zc3cUoly")(console.log).end();
;)
I’ve adopted this solution, with a small bug fix (the promised value is an array, hence you need a get('0') first.
With 'deferred' promise implementation, it may look as easy as:
var request = require('deferred').promisify(require("request"));function expandUrl(shortUrl) { return request( { method: "HEAD", url: shortUrl, followAllRedirects: true } ) .get(0, 'request', 'href');}expandUrl("http://t.co/Zc3cUoly")(console.log).end();
;-)
I wonder what's the secret of providing readable source code on disquiss both 'code' and 'pre' seem to fail :/
pre works for me. Try again, possibly with br at the end.
hmm.. 'br' in 'pre'? It's definitely not conventional method, I'try it.
With 'deferred' promise implementation, it may look as easy as:
var request = require('deferred').promisify(require("request"));
function expandUrl(shortUrl) {
return request( { method: "HEAD", url: shortUrl, followAllRedirects: true } )
.get(0, 'request', 'href');
}
expandUrl("http://t.co/Zc3cUoly")(console.log).end();
;-)
It works, I need to note it somewhere ;-)
You can remove this broken post, thanks
You should be able to edit existing comments. If it works, you could edit the root of this tree and I’ll remove the duplicate. If it doesn’t work, I’ll remove the root of this tree.
I think it's better to remove this tree, as after this post is fixed discussion below wouldn't make sense to newcomers.
Other thing, when rules for formatting are not intuitive, it would be good to have some small note explaining them (disqus just says what elements we can use, but it doesn't say that 'pre' won't work as expected).
e.g. "Please use 'pre' with 'br' for line endings for code markup"
Post a Comment