«

»

Oct 12

Print this Post

Criando um URL Shortener com ColdFusion

Há vários sites onde podemos encurtar nossos URLs, como tinyURL.com, is.gd, dwarfURL.com, bit.ly e outros.
Recebi uma chamada de um amigo que tem vários artigos distribuidos com URLs encurtados e ele estava com medo de caso os sites que hospedam o redirecionamento parassem, seus artigos distribuidos estariam perdidos.
Então ele me perguntou se eu podia criar um site de URL Shortener para seu uso privado e para ser hospedado em seu servidor junto com seus websites.
Decidi disponibilizar o código para a comunidade para aqueles que pensam da mesma forma.
Nosso ambiente é Windows Server 2003, Microsoft SQL 2008 e ColdFusion 8.

Domain Name

Primeiro, voce precisa registrar um domain name bem curto e criar seu website. Suponho que voce tenha registrado o domínio zzz.com e configurou os registros DNS.

  • Crie uma pasta para seu site no raiz do seu web, ex: c:\inetpub\wwwroot\zzz
    • Adicione o site no IIS

ISAPI Rewrite

Para redirecionar o URL curto para o destino, voce precisa configurar o ISAPI URL Rewrite.

  • Se voce tiver IIS 5 ou 6
    • Voce deve baixar a DLL e o arquivo de configuração (.ini) AQUI.
    • Salve o IsapiRewrite4.dll e o arquivo de configuração em uma pasta fora do seu raiz. Eu salvei em “c:\ISAPI\urlShortener\”.
      O arquivo de configuraç?o é bem simples e contem a seguinte regra:

      RewriteRule  ^/([^/.\?]+)$    /index.cfm?go=$1
  • Se voce tiver o IIS 7+ (Windows Server 2008 ou Windows 7)
    • Voce precisa baixar a ferramenta de ISAPI Rewrite (http://www.iis.net/download/URLRewrite)
      • Clique no link x86 ou x64 (64-bit) no menu  à direita, caso voce não queira instalar o Microsoft Werb Platform Installer.
    • Execute a instalação e o seu IIS terá a ferramenta “Rewrite” para todos os seus sites.
    • Configure o ISAPI:
      • Abra o IIS Manager e selecione o seu web site ou pasta virtual (caso Windows 7)
      • No bloco de icons IIS, clique em “URL Rewrite”
      • A janela URL Rewrite abrirá, então clique em “Add Rule(s)” no menu  à direita.
      • No Rule Template, selecione “User-friendly URL” e clique OK.
      • Entre o seguinte no campo URL:
        http://zzz.com/index.cfm?go=xyz123
        (naturalmente, substitua o zzz.com pelo seu domínio)

        • O IIS mostrará o URL curto e o pattern nos campos abaixo. Apenas clique OK.
      • Agora a janela URL Rewrite mostrará a regra recem criada.

Database

Próximo passo: Configurar a base de dados:

  • Crie uma base de dados e uma tabela (eu nomeei a base “urlShortener” e a tabela “urls”) (baixe os scripts AQUI) (códigos SP1 e SP2)
  • Crie duas stored procedures: dbo.setShortUrl e dbo.getShortURL (baixe os scripts AQUI)
    • A stored procedure setShortUrl checa se o URL detino e o código passado já existem, e se não existem, então salva o novo destino e código. (código SP3)
    • A stored procedure getShortUrl recupera o URL destino a partir de um código fornecido (short code). (código SP4)

ColdFusion Data Source

Adicione o datasource “urlShortener” no Administrador do ColdFusion. Vou assumir que voce sabe fazê-lo.

CFML

Agora vamos trabalhar no projeto no CFEclipse ou CFBuilder.

  • Crie o “Application.cfc ” (código CF1)
  • Crie o componente “shortener.cfc” (código CF2)
  • Crie o “index.cfm” (código CF3)

Voce pode baixar o projeto completo AQUI.

Espero que este artigo lhe sirva. Caso voce ache algum erro, por favor reporte, ou caso faça melhorias no projeto, entre em contato. Seus comentários são valiosos.

Obrigado.

Ricardo Parente

Códigos:

Código S1: Criar Database urlShortener

USE [master]
GO
CREATE DATABASE [urlShortener] ON  PRIMARY 
( NAME = N'urlShortener', FILENAME = N'C:\MSSQLDATA\urlShortener.mdf' , SIZE = 2048KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
 LOG ON 
( NAME = N'urlShortener_log', FILENAME = N'C:\MSSQLDATA\urlShortener_log.ldf' , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 2048KB )
GO
ALTER DATABASE [urlShortener] SET COMPATIBILITY_LEVEL = 100
GO
IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled'))
begin
EXEC [urlShortener].[dbo].[sp_fulltext_database] @action = 'enable'
end
GO
ALTER DATABASE [urlShortener] SET ANSI_NULL_DEFAULT OFF 
GO
ALTER DATABASE [urlShortener] SET ANSI_NULLS OFF 
GO
ALTER DATABASE [urlShortener] SET ANSI_PADDING OFF 
GO
ALTER DATABASE [urlShortener] SET ANSI_WARNINGS OFF 
GO
ALTER DATABASE [urlShortener] SET ARITHABORT OFF 
GO
ALTER DATABASE [urlShortener] SET AUTO_CLOSE OFF 
GO
ALTER DATABASE [urlShortener] SET AUTO_CREATE_STATISTICS ON 
GO
ALTER DATABASE [urlShortener] SET AUTO_SHRINK OFF 
GO
ALTER DATABASE [urlShortener] SET AUTO_UPDATE_STATISTICS ON 
GO
ALTER DATABASE [urlShortener] SET CURSOR_CLOSE_ON_COMMIT OFF 
GO
ALTER DATABASE [urlShortener] SET CURSOR_DEFAULT  GLOBAL 
GO
ALTER DATABASE [urlShortener] SET CONCAT_NULL_YIELDS_NULL OFF 
GO
ALTER DATABASE [urlShortener] SET NUMERIC_ROUNDABORT OFF 
GO
ALTER DATABASE [urlShortener] SET QUOTED_IDENTIFIER OFF 
GO
ALTER DATABASE [urlShortener] SET RECURSIVE_TRIGGERS OFF 
GO
ALTER DATABASE [urlShortener] SET  DISABLE_BROKER 
GO
ALTER DATABASE [urlShortener] SET AUTO_UPDATE_STATISTICS_ASYNC OFF 
GO
ALTER DATABASE [urlShortener] SET DATE_CORRELATION_OPTIMIZATION OFF 
GO
ALTER DATABASE [urlShortener] SET TRUSTWORTHY OFF 
GO

ALTER DATABASE [urlShortener] SET ALLOW_SNAPSHOT_ISOLATION OFF 
GO
ALTER DATABASE [urlShortener] SET PARAMETERIZATION SIMPLE 
GO
ALTER DATABASE [urlShortener] SET READ_COMMITTED_SNAPSHOT OFF 
GO
ALTER DATABASE [urlShortener] SET HONOR_BROKER_PRIORITY OFF 
GO
ALTER DATABASE [urlShortener] SET  READ_WRITE 
GO
ALTER DATABASE [urlShortener] SET RECOVERY SIMPLE 
GO
ALTER DATABASE [urlShortener] SET  MULTI_USER 
GO
ALTER DATABASE [urlShortener] SET PAGE_VERIFY CHECKSUM  
GO
ALTER DATABASE [urlShortener] SET DB_CHAINING OFF 
GO

Código S2: Criar Table urls

USE [urlShortener]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[urls](
    [ID] [uniqueidentifier] NOT NULL,
    [shortURL] [varchar](25) NULL,
    [targetURL] [varchar](255) NULL,
    [hitCount] [int] NULL,
    [dateCreated] [datetime] NULL,
    [isActive] [bit] NULL,
 CONSTRAINT [PK_urls] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[urls] ADD  CONSTRAINT [DF_urls_ID]  DEFAULT (newid()) FOR [ID]
GO
ALTER TABLE [dbo].[urls] ADD  CONSTRAINT [DF_urls_hitCount]  DEFAULT ((0)) FOR [hitCount]
GO
ALTER TABLE [dbo].[urls] ADD  CONSTRAINT [DF_urls_dateCreated]  DEFAULT (getdate()) FOR [dateCreated]
GO
ALTER TABLE [dbo].[urls] ADD  CONSTRAINT [DF_urls_isActive]  DEFAULT ((1)) FOR [isActive]
GO

Código S3: Stored Procedure setShortURL

USE [urlShortener]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:        Ricardo Parente
-- Create date: 2010-10-09
-- Description:    I try to add a new URL
-- =============================================
CREATE PROCEDURE [dbo].[setShortUrl]
    @arg_shortURL varchar(25)
,    @arg_targetURL varchar(255)
AS
BEGIN
    SET NOCOUNT ON;
    -- check if targetURL already exists
    IF (NOT EXISTS (
            SELECT TOP 1 shortURL
            FROM urls
            WHERE targetURL = @arg_targetURL))
        BEGIN
-- since targetURL does not exist, let's check if the
-- shortURL already exists
            IF (NOT EXISTS (
                    SELECT TOP 1 shortURL
                    FROM urls
                    WHERE shortURL = @arg_shortURL))
                BEGIN
                -- since the shortURL and targetURL do not exist,
-- let's add them
                INSERT INTO urls (
                    shortURL
                ,    targetURL
                ) VALUES (
                    @arg_shortURL
                ,    @arg_targetURL
                )
                -- return the newly created shortURL
                SELECT @arg_shortURL AS shortURL
                END
            ELSE
                -- shortURL already exists and it is not for the 
                --given target URL, so return blank
                SELECT NULL AS shortURL
        END
    ELSE
        -- targetURL already exists, so return its shortURL
        SELECT TOP 1 shortURL
        FROM urls
        WHERE targetURL = @arg_targetURL
END
GO

Código S4: Stored Procedure getShortURL

USE [urlShortener]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:        Ricardo Parente
-- Create date: 2010-10-10
-- Description:    I retrieve one record
-- =============================================
CREATE PROCEDURE [dbo].[getShortURL]
    @arg_shortURL varchar(25)
AS
BEGIN
    SET NOCOUNT ON;
    UPDATE urls SET
        hitCount = hitCount + 1
    WHERE shortURL = @arg_shortURL
    SELECT TOP 1 targetURL
    FROM urls
    WHERE shortURL = @arg_shortURL
END

Código CF1: Aplication.cfc

<cfcomponent output="false">
    <cfscript>
    this.name = "urlShortener";
    this.applicationTimeout = createTimeSpan(0,6,0,0);
    this.sessionManagement = false;
    this.clientManagement = false;
    </cfscript>

    <cffunction name="onApplicationStart" returntype="boolean" 
        output="false">
        <cfscript>
        application.mainDSN = "urlShortener";
        application.emailFrom = "[email protected]";
        application.emailAdmin = "[email protected]";
        application.totChars = 6;
        </cfscript>
        <cfreturn True/>
    </cffunction>
        
    <cffunction name="onApplicationEnd" output="false">
        <cfargument name="applicationScope" required="true" />
    </cffunction> 

    <cffunction name="onRequestStart">
        <cfargument type="String" name="targetPage" required="true" />
        <cfparam name="url.appInit" type="string" default="false" />
        <cfparam name="url.serverInit" type="string" default="false" />
        <cfscript>
        if (url.appInit eq true or url.serverInit eq true) 
            onApplicationStart();
        </cfscript>
    </cffunction>
</cfcomponent>

Código CF2: shortener.cfc

<cfcomponent output="false">
    <cfscript>
    variables.aChars = 
listToArray("a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9", " ");
    </cfscript>
    
    <cffunction name="setShort" access="remote" output="false" 
        returntype="Any">
        <cfargument name="targetURL" type="string" required="true" />
        <cfset var myShort = "" />
        <cfset var totChars = application.totChars />
        <cfset var shortURL = "" />
        <!--- try up to 10 times to avoid duplicate shortURL --->
        <cfloop from="1" to="10" index="x">
            <cfset myShort = createShort() />
            <cfset shortURL = saveShort(myShort,arguments.targetURL) />
            <cfif len(trim(shortURL))>
                <cfbreak />
            </cfif>
        </cfloop>
        <cfif not len(trim(shortURL))>
            <!--- try again with more characters --->
            <cfset totChars = totChars + 1 />
            <cfloop from="1" to="10" index="x">
                <cfset myShort = createShort(totChars) />
                <cfset shortURL = 
                    saveShort(myShort,arguments.targetURL) />
                <cfif len(trim(shortURL))>
                    <cfbreak />
                </cfif>
            </cfloop>
        </cfif>
        <cfreturn shortURL />
    </cffunction>
    
    <cffunction name="saveShort" access="private" output="false" 
        returntype="Any">
        <cfargument name="shortURL" type="string" required="true" />
        <cfargument name="targetURL" type="string" required="true" />
        <cfset var myShortURL = "" />
         <cftry>  
            <!--- this procedure will save the url and return the 
            shorURL generated --->
            <cfstoredproc procedure="dbo.setShortURL" 
                datasource="#application.mainDSN#">
                <cfprocparam cfsqltype="CF_SQL_VARCHAR" 
                    value="#arguments.shortURL#" />
                <cfprocparam cfsqltype="CF_SQL_VARCHAR" 
                    value="#arguments.targetURL#" />
                <cfprocresult name="qShort" />
            </cfstoredproc>
            <cfset myShortURL = qShort.shortURL />
            <cfcatch>                
                <cfmail from="#application.emailFrom#" 
To="#application.emailAdmin#" 
subject="Error on SetShort function" 
Type="html">
                    <h1>Error trying to set short URL</h1>
                    <cfdump var="#cfcatch#">
                </cfmail>
                <cfset myShortURL = "" />
            </cfcatch> 
        </cftry> 
        <cfreturn myShortURL />
    </cffunction>
    
    <cffunction name="createShort" access="private" output="false" 
        returntype="Any">
        <cfargument name="totChars" type="numeric" required="false" 
            default="#application.totChars#" />
        <cfscript>
        var shortUrl = "";
        for (i=1; i lte arguments.totChars; i=i+1) {
            shortUrl = shortUrl & 
            variables.aChars[randRange(1,arrayLen(variables.aChars))];
        }
        return shortUrl;
        </cfscript>    
    </cffunction>

    <!--- function set to remote to be called as webservice --->
    <cffunction name="getShortURL" access="remote" output="false" 
        returntype="Any">
        <cfargument name="targetURL" type="string" required="true" />        
        <cfreturn setShort(arguments.targetURL) />
    </cffunction>
</cfcomponent>

Código CF3: index.cfm

<cfparam name="url.site" type="string" default="" />
<cfparam name="url.go" type="string" default="" />
<!--- let's check if we are in development server or localhost --->
<cfif listFindNoCase("localhost,127.0.0.1", cgi.host_name)>
    <cfset baseHost = "http://localhost/" />
<cfelse>
    <cfset baseHost = "http://lk3.us/" />
</cfif>
<!--- if a target URL was passed, we need to create the short URL --->
<cfif len(trim(url.site))>
    <cfset short = createObject("component", "shorten") />
    <cfif left(url.site, 7) neq 'http://'>
        <cfset url.site = "http://" & url.site />
    </cfif>
    <cfset shortURL = short.setShort(url.site) />
    <cfif len(trim(shortURL))>
        <cfset shortURL = baseHost & shortURL />
        <cfoutput>
        The short URL for: #url.site# is: <br/>
        <a href="#shortURL#">#shortURL#</a>
        </cfoutput>
    <!--- if any error occurred during the creation of the short URL --->
    <cfelse>
        <cfoutput>
        Sorry! We are experiencing problems now. Please try again later !
        </cfoutput>
    </cfif>
    <cfabort />
<!--- check if a short URL was passed, then get the target and jump to it --->
<cfelseif len(trim(url.go))>
    <cftry>
        <cfstoredproc procedure="dbo.getShortURL" 
            datasource="#application.mainDSN#">
            <cfprocparam cfsqltype="CF_SQL_VARCHAR" value="#url.go#" />
            <cfprocresult name="get" />
        </cfstoredproc>
        <cfcatch>
            <cfoutput>
            Sorry ! Short URL unavailable !<br/>
            #cfcatch.message#<br/>
            #cfcatch.detail#
            </cfoutput>
            <cfabort />
        </cfcatch>
    </cftry>
    <!--- jump to the target --->
    <cfheader statuscode="301" statustext="Moved Permanently" />
    <cfheader name="Location" value="#get.targetURL#" />
<cfelse>
    Please verify the query string. It should have either "site" or "go" parameters.
</cfif>

 

Permanent link to this article: http://ensina.me/coldfusion/criando-um-url-shortener-com-coldfusion/

Leave a Reply