URL shortener

From Rosetta Code
Revision as of 15:46, 5 January 2020 by Thundergnat (talk | contribs) (→‎{{header|Perl 6}}: better variable name)
URL shortener is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

The job of a URL shortener is to take a long URL (e.g. "https://www.cockroachlabs.com/docs/stable/build-a-go-app-with-cockroachdb.html") and turn it into something shorter (e.g. "https://bit.ly/2QLUQIu").

A simple URL shortener with no special rules is very simple and consists of 2 endpoints:

  • One to generate a short version of a URL given a long version of a URL.
  • One to handle a call to the short version of a URL and redirect the user to the long (original) version of the URL.


Create a simple URL shortening API with the following endpoints:

POST /

A POST endpoint that accepts a JSON body describing the URL to shorten. Your URL shortener should generate a short version of the URL and keep track of the mapping between short and long URLs. For example:

   $ curl -X POST 'localhost:8080' \
   -H 'Content-Type: application/json' \
   -d '{
       "long": "https://www.cockroachlabs.com/docs/stable/build-a-go-app-with-cockroachdb.html"
   }'

Should returning something similar to:

   http://localhost:8080/9eXmFnuj

GET /:short

A GET endpoint that accepts a short version of the URL in the URL path and redirects the user to the original URL. For example:

   $ curl -L http://localhost:3000/9eXmFnuj

Should redirect the user to the original URL:

   <!DOCTYPE html>
   <html lang="en">
   ...

Rules:

  • Store the short -> long mappings in any way you like. In-memory is fine.
  • There are no auth requirements. Your API can be completely open.

Crystal

<lang ruby>require "kemal"

CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".chars entries = Hash(String, String).new

post "/" do |env|

 short = Random::Secure.random_bytes(8).map{|b| CHARS[b % CHARS.size]}.join
 entries[short] = env.params.json["long"].as(String)
 "http://localhost:3000/#{short}"

end

get "/:short" do |env|

 if long = entries[env.params.url["short"]]?
   env.redirect long
 else
   env.response.status_code = 404
 end

end

error 404 do

 "invalid short url"

end

Kemal.run</lang>

Perl 6

Works with: Rakudo version 2019.11

As there is no requirement to obfuscate the shortened urls, I elected to just use a simple base 36 incremental counter starting at "0" to "shorten" the urls.

Sets up a micro web service on the local computer at port 10000. Run the service, then access it with any web browser at address localhost:10000 . Any saved urls will be displayed with their shortened id. Enter a web address in the text field to assign a shortened id. Append that id to the web address to automatically be redirected to that url. The url of this page is entered as id 0, so the address: " localhost:10000/0 " will redirect to here, to this page.

The next saved address would be accessible at localhost:10000/1 . And so on.

Saves the shortened urls in a local json file called urls.json so saved urls will be available from session to session. No provisions to edit or delete a saved url. If you want to edit the saved urls, edit the urls.json file directly with some third party editor then restart the service.

There is NO security or authentication on this minimal app. Not recommended to run this as-is on a public facing server. It would not be too difficult to add appropriate security, but it isn't a requirement of this task. Very minimal error checking and recovery.

<lang perl6># Persistent URL storage use JSON::Fast;

my $urlfile = './urls.json'; my %urls = ($urlfile.IO.e and $urlfile.IO.f and $urlfile.IO.s) ??

 ( $urlfile.IO.slurp.&from-json ) !!
 ( index => 1, url => { 0 => 'http://rosettacode.org/wiki/URL_shortener#Perl_6' } );

$urlfile.IO.spurt(%urls.&to-json);

my $add-url-form = qq:to/HTML/; <form action="http://localhost:10000/add_url" method="post"> URL to add:
<input type="text" name="url">
<input type="submit" value="Submit"></form> HTML

  1. Micro HTTP service

use Cro::HTTP::Router; use Cro::HTTP::Server;

my $application = route {

   post -> 'add_url' {
       redirect :permanent, ;
       request-body -> (:$url) { store $url }
   }
   get -> {
      content 'text/html', qq:to/END/;
      $add-url-form 
Saved URLs:
      { %urls<url>.sort( +(*.key.parse-base(36)) ).join: '
' }
      END
   }
   get -> $short {
       (my $link = retrieve($short)) ??
       (redirect :permanent, $link) !!
       not-found
   }

}

my Cro::Service $shorten = Cro::HTTP::Server.new:

   :host<localhost>, :port<10000>, :$application;

$shorten.start;

react whenever signal(SIGINT) { $shorten.stop; exit; }

sub retrieve ($short) { %urls<url>{$short} }

sub store ($url) {

   %urls<url>{ %urls<index>.base(36) } = $url;
   ++%urls<index>;
   $urlfile.IO.spurt(%urls.&to-json);

}</lang>

Output: