The Unshorten plugin provides a means for your Grails application to expand Shortened URLs (http://bit.ly/jkD0Qr and http://tinyurl.com/3vy9xga, for example) into their Original, Unshortened form (http://amazon.com or http://grails.org, for example) without the need for calling a Third-Party API.
Provides a Grails Service, TagLib, and Controller to your application.
Backed by a configurable Least-Recently-Used (LRU) Cache, the Unshorten plugin optimizes performance by making a minimal number of HTTP HEAD method calls.
Contains service calls and controller actions to dynamically handle single or multiple URLs as well as GSP Tags to unshorten URLs within blocks of text, and to create links
Enter your application directory and run the following from the command line:
grails install-plugin unshorten
After you have installed the Unshorten plugin in your application, I'd recommend you point your browser to the Plugin test page to verify all is working and familiarize yourself with the functionality it provides:
http://localhost:8080/myAppContext/unshorten
The UnshortenPlugin may be configured with several parameters, all specified in your application's /grails-app/conf/Config.groovy
unshorten.cache.maxSize = 10000 // Default 10000
unshorten.http.connectTimeout = 1000 // in millis, default 1000
unshorten.http.readTimeout = 1000 // in millis, default 1000
unshorten.ajax.forward.html = [controller:'myController', action:'myHtmlAction']
unshorten.ajax.forward.json = [controller:'myController', action:'myJsonAction']
unshorten.ajax.forward.xml = [controller:'myController', action:'myXmlAction']
This is the maximum number of Unshortened URLs that will be stored in the LRU Cache at any given time. When this number is exceeded, the Least-Recently-Used entry in the Cache will be evicted.
Defaults to 10000 entries
A java.net.URLConnection is used to perform the unshorten functionality of the plugin.
This is the maximum amount of time in milliseconds that we will wait for a response during the TCP Handshake stage of the HTTP connection
It sets the [connectionTimeout](http://download.oracle.com/javase/6/docs/api/java/net/URLConnection.html#setConnectTimeout(\)) property of the URLConnection
Defaults to 1000 milliseconds
If you're returning many Unshortened urls with a status of 'TIMED_OUT' you may try increasing this setting
A java.net.URLConnection is used to perform the unshorten functionality of the plugin.
This is the maximum amount of time in milliseconds that we will wait for reading from the input stream of an established connection.
It sets the [readTimeout](http://download.oracle.com/javase/6/docs/api/java/net/URLConnection.html#setReadTimeout(\)) property of the URLConnection
Defaults to 1000 milliseconds
If you're returning many Unshortened urls with a status of 'TIMED_OUT' you may try increasing this setting
Optional map containing an action
and controller
to forward an AJAX request after the plugin's /unshorten/ajax?format=html
action has finished processing.
Setting this property allows you to override the default HTML display. You will receive a java.util.Map assigned to the request.unshortenResponse
variable to the response containing the data you may use in the response.
Example action in your own MyCustomController.groovy:
def ajaxHtmlTemplate = {
def results = request.unshortenResponse
for(e in results.data)
e.enrichedData = "ENRICHED_FOR_HTML"
[unshortenResponse: results]
}
Optional map containing an action
and controller
to forward an AJAX request after the plugin's /unshorten/ajax?format=json
action has finished processing.
Setting this property allows you to augment/enrich the JSON data before it is returned to the caller in the response.
You will receive a String assigned to the request.unshortenResponse
variable containing the JSON data you may use in the response. If you wish to work with it as JSON, you must parse it first.
Example action in your own MyCustomController.groovy:
def ajaxJsonTemplate = {
def results = JSON.parse(request.unshortenResponse)
for(e in results.data)
e.enrichedData = "ENRICHED_FOR_JSON"
render results as JSON
}
Optional map containing an action
and controller
to forward an AJAX request after the plugin's /unshorten/ajax?format=xml
action has finished processing.
Setting this property allows you to augment/enrich the XML data before it is returned to the caller in the response.
You will receive a String assigned to the request.unshortenResponse
variable containing the XML data you may use in the response. If you wish to work with it as XML, you must parse it first.
Example action in your own MyCustomController.groovy:
def ajaxXmlTemplate = {
def results = new XmlParser().parseText(request.unshortenResponse)
results.data.entry.each { e ->
e.appendNode("enrrichedData","ENRICHED_FOR_XML")
}
def writer = new StringWriter()
new XmlNodePrinter(new PrintWriter(writer)).print(results)
def result = writer.toString()
render(text: result, contentType:"text/xml", encoding:"UTF-8")
}
Takes a single String and returns a Map representing the Unshortened URL and ancillary data regarding its HTTP and caching statuses
Parameters
- a String - the URL to unshorten
Returns
- A Map
The values in the returnMap
are as follows:
- returnMap.shortUrl - the original URL
- returnMap.fullUrl - the unshortened URL
- returnMap.cached - A boolean that is true if the shortenUrl was retrieved from the cache, and false if HTTP rigamarole was required
- returnMap.status - A String, the state of the URL and one of the following possible values:
UNSHORTENED
- successfully unshortenedNOT_SHORTENED
- URL was already expanded, i.e. no redirect requiredREDIRECTED
- URL was redirected internal to the TLD of the original URLNOT_FOUND
- No document at this URL, returned 404 ErrorTIMED_OUT
- Destination server or Shortener API did not respond within the timeouts specified by Config.groovyINVALID
- Poorly formed, or non-existent URLUNKNOWN
- Something else happened
Note: A URL that has TIMED_OUT is not cached, the reason being that sometimes Third-party Shortening services may be temporarily unavailable now, but responsive next time we try.
Considering addressing configuration of this behavior in future releases.
Example:
def shortUrl = "http://bit.ly/jkD0Qr"
unshortenService?.unshorten(shortUrl)
returns
[fullUrl:http://www.cbsnews.com/8301-503543_162-20063168-503543.html, status:UNSHORTENED, shortUrl:http://bit.ly/jkD0Qr, cached:false]
Takes a single String OR List of Strings representing 1 - n Shortened URLs and returns a Map of Maps with their Unshortened versions, keyed by the ShortURL(s) passed into the method
Parameters
- a String - a single URL to unshorten
OR
- a List of Strings - 1 or more URLs to Unshorten
Returns
- a Map of Maps - The returnMap's values match those from the single unshorten() method, but keyed by the corresponding originalUrl passed in as a parameter.
Example:
def shortUrls = ["http://bit.ly/jkD0Qr","http://minu.me/4fmw"]
unshortenService?.unshortenAll(shortUrls)
returns
[http://bit.ly/jkD0Qr:[fullUrl:http://www.cbsnews.com/8301-503543_162-20063168-503543.html, status:UNSHORTENED, shortUrl:http://bit.ly/jkD0Qr, cached:false],http://minu.me/4fmw:[fullUrl:http://www.twitcaps.com/search?q=dsk, status:UNSHORTENED, shortUrl:http://minu.me/4fmw, cached:false]]
Takes a String representing a block of text and replaces all URL occurrences with their Unshortened versions
Parameters
- a String - a block of text containing 1 - n potentially shortened URLs
Returns
-
a String - the block of text with all URLs replaced by their Unshortened counterparts
def shortUrl = "Check out these links! http://bit.ly/jkD0Qr and http://minu.me/4fmw" unshortenService?.unshorten(shortUrl)
returns
Check out these links! http://www.cbsnews.com/8301-503543_162-20063168-503543.html and http://www.twitcaps.com/search?q=dsk
###expandUrlsInTextAll()
Takes a single String or List of Strings representing a block of text and replaces all URL occurrences with their Unshortened versions returning a Map of Maps with the fullText keyed by the shortText
Parameters
- a String - a block of text containing 1 - n potentially shortened URLs
OR
- a List of Strings - 1 or more blocks of text containing 1 - n potentially shortened URLs
Returns
- a Map of Maps - The returnMap's values match those from the single expandUrlsInTextAll() method, but keyed by the corresponding originalTextBlock passed in as a parameter.
[TODO: Provide Sample Data]
All Unshorten tags exist in the unshorten
namespace.
<unshorten:expandUrls>
I just tweeted this URL so you could see it http://bit.ly/jkD0Qr,
and also this one http://t.co/8lrqrZf
</unshorten:expandUrls>
results in
I just tweeted this URL so you could see it http://www.cbsnews.com/8301-503543_162-20063168-503543.html
and also this one http://iamthetrend.com/2011/02/10/10-examples-of-awesome-indie-clothing-look-books/
<unshorten:expandAndLinkUrls linkClass="myLinkClass">
I just tweeted this URL so you could see it http://bit.ly/jkD0Qr,
and also this one http://t.co/8lrqrZf
</unshorten:expandAndLinkUrls>
results in
I just tweeted this URL so you could see it
<a class="myLinkClass" href="http://www.cbsnews.com/8301-503543_162-20063168-503543.html">
http://www.cbsnews.com/8301-503543_162-20063168-503543.html
</a>
and also this one
<a class="myLinkClass" href="http://iamthetrend.com/2011/02/10/10-examples-of-awesome-indie-clothing-look-books/">
http://iamthetrend.com/2011/02/10/10-examples-of-awesome-indie-clothing-look-books/
</a>
<unshorten:unshortenUrl url='http://bit.ly/jkD0Qr'/>
results in
http://www.cbsnews.com/8301-503543_162-20063168-503543.html
<unshorten:unshortenAndLinkUrl class="myLinkClass" url='http://bit.ly/jkD0Qr'/>
results in
<a class="myLinkClass" href="http://www.cbsnews.com/8301-503543_162-20063168-503543.html">
http://www.cbsnews.com/8301-503543_162-20063168-503543.html
</a>
Provides a test form for validating the functionality of the Unshorten plugin and testing individual URLs. May serve as a template for your own application.
AJAX Action, accepts params and returns an HTML fragment, JSON, or XML
Parameters
- shortUrl - one or more URLs
- shortText - one or more blocks of text that may contain shortened links (i.e. Tweets)
- format -
json
,xml
, orhtml
. Determines the format of the response. Defaults tojson
.
At least 1 shortUrl OR shortText must be supplied, or the response will return a 500 status_code
For example, doing an HTTP GET on this URL:
app-context/unshorten/ajax?shortUrl=http://bit.ly/jkD0Qr&shortUrl=http://t.co/8lrqrZf&shortText=Tweet!%20http://bit.ly/11Da1f
might return the following JSON:
{
"status_code":"200",
"status_text":"OK",
"elapsedTime":13,
"errors":[],
"data":
[
{
"cached":false,
"fullUrl":"http://iamthetrend.com/2011/02/10/10-examples-of-awesome-indie-clothing-look books/",
"status":"UNSHORTENED",
"shortUrl":"http://t.co/8lrqrZf"
"type":"url"
},
{
"cached":false,
"fullUrl":"http://www.cbsnews.com/8301-503543_162-20063168-503543.html",
"status":"UNSHORTENED",
"shortUrl":"http://bit.ly/jkD0Qr"
"type":"url"
},
{
"fullText":"Tweet! http://twitcaps.com",
"shortText":"Tweet! http://bit.ly/11Da1f"
"type":"text"
}
]
}
OR the following XML:
<response>
<status_code>200</status_code>
<status_text>OK</status_text>
<errors />
<data>
<entry>
<type>url</type>
<shortUrl>http://t.co/8lrqrZf</shortUrl>
<fullUrl>
http://iamthetrend.com/2011/02/10/10-examples-of-awesome-indie-clothing-look-books/
</fullUrl>
<status>UNSHORTENED</status>
<cached>false</cached>
</entry>
<entry>
<type>url</type>
<shortUrl>http://bit.ly/jkD0Qr</shortUrl>
<fullUrl>
http://www.cbsnews.com/8301-503543_162-20063168-503543.html
</fullUrl>
<status>UNSHORTENED</status>
<cached>false</cached>
</entry>
<entry>
<type>text</type>
<shortText>Tweet! http://bit.ly/11Da1f</shortText>
<fullText>Tweet! http://twitcaps.com/</fullText>
</entry>
</data>
<elapsedTime>91</elapsedTime>
</response>
urlreversi: Revert your shortened URLs
The urlreversi plugin has been around for quite a while longer than Unshorten and provides the basic functionality of Unshortening (shortUrl-in / fullUrl-out) in a Service as well as a TagLib for convenience.
While it does not feature a Caching implementation as far as I can tell, it should not be too difficult to implement your own cache around its functionality.
The source code is available on GitHub at https://github.com/boatmeme/grails-unshorten.
Find a bug? Fork it. Fix it. Issue a pull request.
git clone git://github.com/boatmeme/grails-unshorten
Contributions welcome!
Issue tracking is also on GitHub at https://github.com/boatmeme/grails-unshorten/issues.
Bug reports, Feature requests, and general inquiries welcome.
Feel free to contact me by email (jonathan.griggs at gmail.com) or follow me on GitHub at https://github.com/boatmeme.
unshorten.http.readTimeout
property was incorrectly named- Cosmetic changes on the test view,
app_context/unshorten
-
Fixed bug where URL Status was being set to UNKNOWN when it should be set to TIMED_OUT
-
AJAX response can now return HTML
-
AJAX format parameter supports 'html' value
-
Support for 3 new configuration options:
unshorten.ajax.forward.html = [controller:'myController', action:'myAction'] unshorten.ajax.forward.json = [controller:'myController', action:'myAction'] unshorten.ajax.forward.xml = [controller:'myController', action:'myAction']
These can be (optionally) set to a map with the 'controller' and 'action' in your application to forward the results of the Unshorten AJAX action. By specifying these options you can process or style the data before returning it to the browser.
- Added UnshortenService.expandUrlsInTextAll() to take a list of 1 - n text blocks and return the results of expanding all of them
- AJAX action now supports ‘shortText’ parameter which operates on blocks of text instead of individual urls
- AJAX response ‘data’ object now returns ‘type’ property. This can be either ‘url’ or ‘text’
- AJAX response now returns ‘elapsedTime’ property (time of call in milliseconds)
- AJAX response can now return XML
- AJAX action now supports ‘format’ parameter which can be either ‘json’ or ‘xml’. Defaults to ‘json’
- Added UrlStatus Enum to UnshortenService
- Added support for redirects via HTTP 302 and 303 (bad shortener!)
- Added support for chaining redirects
- Fixed bug with relative redirects
- Added status for "redirect"
- Initial release