Consuming URL Shortening Services – bit.ly

هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.

Read more about URL shortening services here.

Source- Elsheimy.Samples.ShortenSvcs.zip

Contents

Contents of this article:

  • Contents
  • Overview
  • Introduction
  • API
    • Overview
    • Function Explanation
    • Shortening Function
    • Expanding Function
    • Validation Function
    • Stats Function
    • Lookup Function
    • Info Function
    • Authentication Function
  • JSON Support
    • Sample
    • Where to go next

    Overview

    This is a very hot article that you can’t leave without checking it first. This article is talking about the most popular and powerful URL shortening service ever, bit.ly.

    Today, we are going to talk about bit.ly API, its functions, and how you can access them from your .NET application.

    Don’t forget to download the sample code at the end of the article.

    Let’s go!

    Introduction

    Today we are going to talk about the most popular yet most powerful URL shortening service ever in the web. Yes you guessed, it is bit.ly.

    Of course this service is well-known enough and we don’t need to describe it or talk about its features, so we’ll dig into the API immediately.

    API

    Overview

    There’re few things that you should keep in mind about bit.ly API:

    • REST API:

    The bit.ly API is a REST web service; means that it’s a collection of HTTP endpoints each accessed by a simple address filled with arguments the function (endpoint) requires the way you fill up query strings of a web page. Actually, you don’t need to care about REST web services or HTTP endpoints; you just need to know how to call those functions (simply, to access those addresses programmatically.)

    Yet, the current and only version of the API is version 3 and that can be accessed using the following address:

    http://api.bit.ly/v3/%5Bfunction_name%5D?%5Bargs%5D

    Simply, substitute function_name with the name of the function you need to call, and replace args with function arguments.

    Notice that you can try any function by just typing the address into the browser and navigating to the results.

    • Functions:

    The API provides you with lots of functions that satisfy your application needs. The following list contains the functions available:

    • Shorten:
      Used to shorten a long URL.
    • Expand:
      Used to expand the URL; to get the original long URL from the short one.
    • Validate:
      Used to validate a username and his API key.
    • Clicks:
      Used to retrieve stats (number of clicks) about the short URL specified.
    • Lookup:
      Used to check a long URL if exists in the database, i.e., if it has been shortened before.
    • Info:
      Used to retrieve information about the URL (e.g. the user created it, page title, etc.)
    • Authenticate:
      Used to check if a username and password are valid. Access restricted, more information available later at the end of this article.

    As you see, the API of bit.ly is the most sophisticated yet powerful API compared to the APIs of the other URL shortening services.

    • Input Arguments:

    Any URL passed to a function must be encoded first to eliminate the ‘#’, ‘?’, ‘=’ and other problematic characters in the URL. For encoding a URL, the function System.Net.Uri.EscapeUriString() is very sufficient.

    There’re three main required arguments that are used by all functions:

    • login:
      Username.
    • apiKey:
      The key used to authenticate the user access to the API.
    • format:
      The format (type) of returned data from functions.

    Those three are required by all functions and you cannot work without one of them.

    • Authentication:

    All functions require user authentication. The user can prove his identity using his login name (username) and his API key (not his password.) You can get your API key by accessing the page http://bit.ly/a/account (after logging on to your account) or directly from http://bit.ly/a/your_api_key.

    One of the hot features of the API is that it provides you a demo user that can be used in your API training, the information of that user is as follows:

    Username: bitlyapidemo

    API Key: R_0da49e0a9118ff35f52f629d2d71bf07

    You might face problems with this account like violation of rate limits and many other problems, and that because it’s used by many users in the same time. Therefore, it’s recommended that you use another account.

    • Supported Formats:

    The API supports two formats of its returned data, XML and JSON (the default.) Yes it supports plain text too, but it’s not supported by all functions. XML data is easily manipulated by XML, so we’ll concentrate on XML besides the plain text format of course.

    • Handling Errors:

    If the function failed and you have specified the format as Plain Text (txt) in the call, you get an exception thrown in your code. If the format was XML, you can check the returned data for whether the function succeeded or not.

    The XML data returned from functions must follow this schema:

    <?xml version="1.0" encoding="UTF-8"?>
    <response>
        <status_code />
        <status_txt />
        ...
    </response>

    Here we have status_code set to the value 200 if the function succeeded and to the error code if the function failed. The status_txt describes the status of the function, it’s set to ‘OK’ if the function succeeded and to the error description if the function failed.

    The rest of the XML data is defined based on the function.

    • Preferred Domain:

    You have the option to use one of two domains, http://bit.ly and http://j.mp (new,) both offer you the same functionality and the same flexibility, however, the first counts to 20 characters while the other counts to only 18. (The domain can be set in the shortening function in the argument domain.)

    Keep in mind that the code just after the domain name (e.g. bnPuEX of http://bit.ly/bnPuEX) is called Hash and it is exactly 6 characters (case-sensitive.)

    There’re two types of hash, each short URL has many hashes:

    • User Hash:
      That hash of the URL generated for a given user shortened the URL. That means that a long URL might have more than one user hash equals to the number of users shortened that URL. (More than one hash means more than one short URL.)
    • Global Hash:
      A hash that is shared by all users for the same short URL.

    Thus, a short URL has only one global hash, but it might have more than one user hash (for each user shortened the same long URL.)

    • Rate Limits:

    You cannot think about making thousands of function calls every hour, access to the API is limited for each user on an hourly base. Limits are very sufficient for your application, but it’s going not to be sufficient if you are willing to spam the service or to drop it!

    Function Explanation

    Now we are going to talk about each function and how you can call it.

    First, get your API key that will be used to authenticate your calls to the API. If you need to bother yourself and to clog your application use the demo API user bitlyapidemo that have the API key R_0da49e0a9118ff35f52f629d2d71bf07.

    Shortening Function

    The first function we are going to talk about today is the shortening function, shorten. This function has the following address http://api.bit.ly/v3/shorten (as you expected) and is used to shorten long URLs. Besides main arguments key, apiKey, and format, it takes two more:

    • longUrl:
      The long URL to be shortened.
    • domain:
      Optional. The preferred domain, bit.ly or j.mp.

    You can get hands on this function and try it simply by navigating to the results of the following URL:

    http://api.bit.ly/v3/shorten?login=bitlyapidemo&apiKey=R_0da49e0a9118ff35f52f629d2d71bf07 &format=txt&longUrl=http://JustLikeAMagic.com

    This call simply tries to shorten the URL http://JustLikeAMagic.com by using credentials of the demo API user (substitute the current information with your own.) The format is set to plain text.

    You can also use change the format to XML and get output like this:

    <?xml version="1.0" encoding="utf-8"?>
    <response>
        <status_code>200</status_code>
        <status_txt>OK</status_txt>
        <data>
            <url>http://bit.ly/bO9TgE</url>
            <hash>bO9TgE</hash>
            <global_hash>9gSDEU</global_hash>
            <long_url>http://JustLikeAMagic.com&lt/long_url>
            <new_hash>0</new_hash>
        </data>
    </response>

    Notice that the status code is 200 that means that everything went ‘OK’. Notice that we have 5 elements:

    • url:
      The long URL generated.
    • hash:
      The user hash string.
    • global_hash:
      The globally shared hash. Can be used to browse to the URL too, it would be counted in the global statistics but not in user’s.
    • long_url:
      The original URL.
    • new_hash:
      Equals to 1 if this is the first time that URL being shortened (using the bit.ly service of course,) or 0 otherwise.

    Now, let’s code! The following function accepts a long URL and user API credentials and tries to shorten the URL using our shortening function.

    Don’t forget to add using statements to namespaces System.IO, System.Net, and System.Xml to that code and to the other code demonstrated in this article.

    // C#
    
    string Shorten(string url, string login, string key, bool xml)
    {
        url = Uri.EscapeUriString(url);
        string reqUri =
            String.Format("http://api.bit.ly/v3/shorten?" +
            "login={0}&apiKey={1}&format={2}&longUrl={3}",
            login, key, xml ? "xml" : "txt", url);
    
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(reqUri);
        req.Timeout = 10000; // 10 seconds
    
        // if the function fails and format==txt throws an exception
        Stream stm = req.GetResponse().GetResponseStream();
    
        if (xml)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(stm);
    
            // error checking for xml
            if (doc["response"]["status_code"].InnerText != "200")
                throw new WebException(doc["response"]["status_txt"].InnerText);
    
            return doc["response"]["data"]["url"].InnerText;
        }
        else // Text
            using (StreamReader reader = new StreamReader(stm))
                return reader.ReadLine();
    }

    Take notice of the mechanism used to check for errors.

    Expanding Function

    The next function we have is the function that is used to expand a URL, i.e., to get the long URL from the short one. Obviously, this function is called expand and it accepts the short URL shortUrl besides the three main arguments.

    Likewise, calling this function generate a data based on the function format, txt, xml, or json. The following XML data is generated when the function is called while the format is set to xml:

    <?xml version="1.0" encoding="utf-8" ?>
    <response>
        <status_code>200</status_code>
        <status_txt>OK</status_txt>
        <data>
            <entry>
                <short_url>http://bit.ly/bnPuEX</short_url>
                <long_url>http://justlikeamagic.com</long_url>
                <user_hash>bnPuEX</user_hash>
                <global_hash>bdE96m</global_hash>
            </entry>
        </data>
    </response>

    Now you can see the two hashes, user_hash and global_hash, and the two lend the user to your page (although the access is counted differently.)

    Now, let’s code! The following function retrieves the long URL from the short one:

    // C#
    
    string Expand(string url, string login, string key, bool xml)
    {
        url = Uri.EscapeUriString(url);
        string reqUri =
            String.Format("http://api.bit.ly/v3/expand?" +
            "login={0}&apiKey={1}&format={2}&shortUrl={3}",
            login, key, xml ? "xml" : "txt", url);
    
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(reqUri);
        req.Timeout = 10000; // 10 seconds
    
        // if the function fails and format==txt throws an exception
        Stream stm = req.GetResponse().GetResponseStream();
    
        if (xml)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(stm);
    
            // error checking for xml
            if (doc["response"]["status_code"].InnerText != "200")
                throw new WebException(doc["response"]["status_txt"].InnerText);
    
            return doc["response"]["data"]["entry"]["long_url"].InnerText;
        }
        else // Text
            using (StreamReader reader = new StreamReader(stm))
                return reader.ReadLine();
    }

    Validation Function

    The function validate is used to check if another username and API key pair is valid. For this function to work, you should use valid API credentials to check for the other credentials if they are valid or not. Therefore, you are going to use two additional arguments for the additional credentials, x_login and x_apiKey.

    This function doesn’t support plain text format. If XML was used, the function returns data like the following:

    <?xml version="1.0" encoding="UTF-8"?>
    <response>
        <status_code>200</status_code>
        <status_txt>OK</status_txt>
        <data>
            <valid>0</valid>
        </data>
    </response>

    The valid element is set to 1 if the credentials were OK or 0 otherwise.

    And this is our C# function that validates user API credentials:

    // C#
    
    string Validate(string login, string key,
        string xLogin, string xKey, bool xml)
    {
        string reqUri =
            String.Format("http://api.bit.ly/v3/validate?" +
            "login={0}&apiKey={1}&x_login={4}&x_key={5}&format={2}" +
            login, key, xLogin, xKey, xml ? "xml" : "txt");
    
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(reqUri);
        req.Timeout = 10000; // 10 seconds
    
        // if the function fails and format==txt throws an exception
        Stream stm = req.GetResponse().GetResponseStream();
    
        if (xml)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(stm);
    
            // error checking for xml
            if (doc["response"]["status_code"].InnerText != "200")
                throw new WebException(doc["response"]["status_txt"].InnerText);
    
            return int.Parse(doc["response"]["data"]["valid"]) == 1 ? true : false;
        }
        else // Text
            using (StreamReader reader = new StreamReader(stm))
                return int.Parse(reader.ReadLine()) == 1 ? true : false;
    }

    Stats Function

    This function is used to get stats about the short URL; the stats are represented in two values, user clicks and global clicks. User clicks value is the number of access times made to that user link. Global clicks value is the number of access times made from all short URLs (from all users) refer to the same address (almost like user hashes and global hash.)

    The function only accepts the short URL, shortUrl, besides the three main arguments. The data returned from the function is almost like this (in XML):

    <?xml version="1.0" encoding="utf-8" ?>
    <response>
        <status_code>200</status_code>
        <data>
            <clicks>
                <short_url>http://bit.ly/bnPuEX</short_url>
                <global_hash>bdE96m</global_hash>
                <user_clicks>0</user_clicks>
                <user_hash>bnPuEX</user_hash>
                <global_clicks>0</global_clicks>
            </clicks>
        </data>
        <status_txt>OK</status_txt>
    </response>

    The following C# function is used to retrieve number of access times for the current user and for all users:

    // C#
    
    int GetClicks(string url, string login, string key, out int globalClicks)
    {
        url = Uri.EscapeUriString(url);
        string reqUri =
            String.Format("http://api.bit.ly/v3/clicks?" +
            "login={0}&apiKey={1}&shortUrl={2}&format=xml" +
            login, key, url);
    
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(reqUri);
        req.Timeout = 10000; // 10 seconds
    
        Stream stm = req.GetResponse().GetResponseStream();
    
        XmlDocument doc = new XmlDocument();
        doc.Load(stm);
    
        // error checking for xml
        if (doc["response"]["status_code"].InnerText != "200")
            throw new WebException(doc["response"]["status_txt"].InnerText);
    
        XmlElement el = doc["response"]["data"]["clicks"];
        globalClicks = int.Parse(el["global_clicks"].InnerText);
        return int.Parse(el["user_clicks"].InnerText);
    }

    Lookup Function

    This function is used with long URLs to check whether they have been shortened before, and if so, the function returns the short URLs.

    If the long URL was found in service database, XML data like this is returned:

    <?xml version="1.0" encoding="utf-8" ?>
    <response>
        <status_code>200</status_code>
        <status_txt>OK</status_txt>
        <data>
            <lookup>
                <url>http://JustLikeAMagic.com</url>
                <short_url>http://bit.ly/9gSDEU</short_url>
                <global_hash>9gSDEU</global_hash>
            </lookup>
        </data>
    </response>

    Otherwise, you get another form of XML data:

    <?xml version="1.0" encoding="utf-8" ?>
    <response>
        <status_code>200</status_code>
        <status_txt>OK</status_txt>
        <data>
            <lookup>
                <url>http://JustLikeAMagic.com/books</url>
                <error>NOT_FOUND</error>
            </lookup>
        </data>
    </response>

    The following C# function looks-up a URL and returns its short URL if found:

    // C#
    
    string Lookup(string url, string login, string key)
    {
        url = Uri.EscapeUriString(url);
        string reqUri =
            String.Format("http://api.bit.ly/v3/lookup?" +
            "login={0}&apiKey={1}&url={2}&format=xml" +
            login, key, url);
    
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(reqUri);
        req.Timeout = 10000; // 10 seconds
    
        Stream stm = req.GetResponse().GetResponseStream();
    
        XmlDocument doc = new XmlDocument();
        doc.Load(stm);
    
        // error checking for xml
        if (doc["response"]["status_code"].InnerText != "200")
            throw new WebException(doc["response"]["status_txt"].InnerText);
    
        if (doc["response"]["data"]["lookup"]["error"] == null)
            return null; // not found
    
        return doc["response"]["data"]["lookup"]["short_url"].InnerText;
    }

    Info Function

    The info function is used to retrieve information about the current short URL. This function returns XML data like the following:

    <?xml version="1.0" encoding="utf-8" ?>
    <response>
        <status_code>200</status_code>
        <status_txt>OK</status_txt>
        <data>
            <info>
                <short_url>http://bit.ly/bnPuEX</short_url>
                <global_hash>bdE96m</global_hash>
                <user_hash>bnPuEX</user_hash>
                <created_by>elsheimy</created_by>
                <title>Just Like a Magic</title>
            </info>
        </data>
    </response>

    Besides link hashes, it returns the name of user who created it and the page title.

    And this is our C# function that retrieves that information:

    // C# Code
    
    string GetInfo(string url, string login, string key, out string createdBy)
    {
        url = Uri.EscapeUriString(url);
        string reqUri =
            String.Format("http://api.bit.ly/v3/info?" +
            "login={0}&apiKey={1}&shortUrl={2}&format=xml" +
            login, key, url);
    
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(reqUri);
        req.Timeout = 10000; // 10 seconds
    
        Stream stm = req.GetResponse().GetResponseStream();
    
        XmlDocument doc = new XmlDocument();
        doc.Load(stm);
    
        // error checking for xml
        if (doc["response"]["status_code"].InnerText != "200")
            throw new WebException(doc["response"]["status_txt"].InnerText);
    
        XmlElement el = doc["response"]["data"]["info"];
        createdBy = el["created_by"].InnerText;
        return el["title"].InnerText;
    }

    Authentication Function

    This is the last function today, the authenticate function. This function is used to check whether a username and a password are valid. Although this function and the validation function work the same way, there’s a big difference. The validation function checks for API credentials, the username and the API key, while this function checks for login information, the username and the password. Another big difference is that this function is currently access-restricted and you cannot use it before asking for permission from api@bit.ly.

    This function accepts two addition parameters, the username x_login and the password x_password.

    This function is called in a very specific way. You add arguments in the body of your request. In addition, the request is made by the method POST.

    If the function succeeded you get the API key for that user. For example:

    <?xml version="1.0" encoding="UTF-8"?>
    <response>
        <status_code>200</status_code>
        <data>
            <authenticate>
                <username>bitlyapidemo</username>
                <successful>1</successful>
                <api_key>R_0da49e0a9118ff35f52f629d2d71bf07</api_key>
            </authenticate>
        </data>
        <status_txt>OK</status_txt>
    </response>

    Otherwise, the successful element is set to 0 and no other information is available:

    <?xml version="1.0" encoding="UTF-8"?>
    <response>
        <status_code>200</status_code>
        <data>
            <authenticate>
                <successful>0</successful>
            </authenticate>
        </data>
        <status_txt>OK</status_txt>
    </response>

    The next C# function tries to authenticate a given user and retrieve his API key (Notice how to set information in the body of the request):

    // C#
    
    string Authenticate(string login, string key, string xLogin, string xPassword)
    {
        string reqUri = "http://api.bit.ly/v3/authenticate";
        string body =
            string.Format("login={0}&apiKey={1}&x_login={2}" +
            "&x_password={3}&format=xml",
            login, key, xLogin,xPassword);
    
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(reqUri);
        req.Timeout = 10000;
        req.Method = "POST";
    
        StreamWriter writer = new StreamWriter(req.GetRequestStream());
        writer.WriteLine(body);
    
        Stream stm = req.GetResponse().GetResponseStream();
        XmlDocument doc = new XmlDocument();
        doc.Load(stm);
    
        // error checking for xml
        if (doc["response"]["status_code"].InnerText != "200")
            throw new WebException(doc["response"]["status_txt"].InnerText);
    
        XmlElement el = doc["response"]["data"]["authenticate"];
        if (el["successful"].InnerText == "1")
            return el["api_key"].InnerText;
        else
            return null;
    }

    JSON Support

    We have been talking about the API and its support for XML and plain text formats and we missed the third one (is also the default,) that is JSON (JavaScript Object Notation.) Although XML and plain-text are enough and sufficient for most applications, and they’re very easy too, there’re some times when you are forced to work with JSON (some web services support only JSON data.) At least for completeness’ sake, we need to know how to handle JSON data when we have it in our hands.

    Worth mentioning that JSON is not supported by versions before .NET 3.5.

    First thing to know about bit.ly API is that you can get JSON data out of a function by passing the value json to the format parameter. Other way is to omit the format parameter completely, that’s because JSON is the default format that will be used when the format parameter not specified.

    Let’s take the shortening function as an example. Try calling the shortening function specifying json in the format parameter or removing the parameter completely:

    http://api.bit.ly/v3/shorten?login=bitlyapidemo&apiKey=R_0da49e0a9118ff35f52f629d2d71bf07
    &format=json&longUrl=http://JustLikeAMagic.com

    When you call the previous function you get some results similar to those:

    {
        "status_code": 200, 
        "status_txt": "OK", 
        "data":
        {
            "long_url": "http://justlikeamagic.com",
            "url": "http://bit.ly/bnPuEX",
            "hash": "bnPuEX",
            "global_hash": "bdE96m",
            "new_hash": 0
        }
    }

    Can you notice the similarities between the structure of this JSON-formatted data and the XML-formatted data we have seen earlier? Actually, the structure is the same, there’s no difference except how data is formatted.

    Looking at this JSON snippet we can see that it’s just a structure contains a few members, every member has a name and value both separated by a colon (:) and each surrounded by double quotes, members are separated by commas (,), and the whole structure is surrounded by curly brackets. Worth mentioning that the data member is a compound data member, means that it defines some other members inside it.

    The first step in handling JSON data is to create .NET classes/structures from the JSON code keeping the same structure definitions and parameter names. For our example, we need to create the following classes:

    C#
    
        class ShortUrl
        {
            public int status_code;
            public string status_txt;
            public ShortUrlData data;
        }
    
        class ShortUrlData
        {
            public string long_url;
            public string url;
            public string hash;
            public string global_hash;
            public int new_hash;
        }

    Here comes the trick, the DataContractJsonSerializer class that’s available in the namespace System.Runtime.Serialization.Json which is available in System.Runtime.Serialization.dll for .NET 4.0 and in System.ServiceModel.Web.dll for .NET 3.5 (not available before version 3.5.)

    This class can be used in two ways: to serialize (i.e. generate JSON data from a structure) or to deserialize (i.e. return the data structure from JSON data) this data. To serialize some data you call any of the WriteXXX() functions (based on the data type,) to deserialize this data you call any of the ReadXXX() functions (based on the data type too.)

    After you add the right assembly to project references and import the namespace System.Runtime.Serialization.Json, you can now start coding:

    // C#
    
    ShortUrl Shorten(string url, string login, string key)
    {
        url = Uri.EscapeUriString(url);
        string reqUri =
            String.Format("http://api.bit.ly/v3/shorten?" +
            "login={0}&apiKey={1}&format={2}&longUrl={3}",
            login, key, "json", url);
    
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(reqUri);
        req.Timeout = 10000; // 10 seconds
    
        // if the function fails and format==txt throws an exception
        Stream stm = req.GetResponse().GetResponseStream();
    
        DataContractJsonSerializer con = 
            new DataContractJsonSerializer(typeof(ShortUrl));
        ShortUrl shortUrl = (ShortUrl)con.ReadObject(stm);
    
        return shortUrl;
    }

    Have fun with JSON!

    Sample

    Download the sample code here.

    Where to go next

    Read more about URL shortening services here.

    One thought on “Consuming URL Shortening Services – bit.ly

    1. Pingback: DotNetShoutout

    Leave a comment