Introduction
URL shorteners are a popular web service that provides a shortened alias for long URLs, making them easier to share and manage. Services like Bitly and TinyURL have made URL shortening a common practice on the internet. In this tutorial, we will create a simple URL shortener in Go (Golang) that takes a long URL, generates a short hash for it, and stores the mapping between them. We will also cover how to handle URL redirection using the stored mappings.
This guide will walk you through setting up the server, handling HTTP requests, generating short URLs, and managing URL mappings with file storage for persistence.
Prerequisites
Before starting, make sure you have:
Go: Installed on your machine (version 1.16 or above is recommended).
Basic Knowledge of Go: Familiarity with Go's standard library, particularly HTTP handling, file I/O, and concurrency.
Feel free to add your customizations and features, your own configurations, etc.
Step-by-Step Implementation
1. Initialize the Project
To start, create a new directory for your project and create a file named main.go
.
mkdir go-url-shortener
cd go-url-shortener
touch main.go
2. Import Necessary Packages
In main.go
, import the necessary packages that will be used in our URL shortener service.
package main
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"sync"
)
crypto/sha1: For generating a unique hash of the long URL.
encoding/hex: For encoding the hash to a hex string.
encoding/json: For converting the URL mapping to and from JSON format.
net/http: For handling HTTP requests and responses.
os and io/ioutil: For file I/O operations.
sync: For concurrent access to shared data.
3. Set Up URL Storage and Mutex
We need to store the mappings between short URLs and long URLs. We will use a Go map and a mutex to handle concurrent access safely.
var (
urlStore = make(map[string]string) // In-memory store for URL mappings
mutex = &sync.Mutex{} // Mutex to ensure safe concurrent access
)
4. Define the main
Function
The main
function initializes the server, loads any existing URL mappings from a file, and sets up HTTP routes.
func main() {
// Load existing URL mappings from file
loadURLMapping()
// Define HTTP handlers
http.HandleFunc("/shorten", shortenHandler)
http.HandleFunc("/", redirectHandler)
fmt.Println("Starting server on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
loadURLMapping
: Loads URL mappings from a file at the start.shortenHandler
: Handles requests to shorten a long URL.redirectHandler
: Handles requests to redirect to the original long URL.
5. Implement the Shortening Logic
The shortenHandler
function reads the long URL from the query parameters, generates a short URL, stores the mapping, and returns the short URL to the user.
func shortenHandler(w http.ResponseWriter, r *http.Request) {
longURL := r.URL.Query().Get("url")
if longURL == "" {
http.Error(w, "URL parameter is missing", http.StatusBadRequest)
return
}
// Generate short URL
shortURL := generateShortURL(longURL)
// Store the mapping
mutex.Lock()
urlStore[shortURL] = longURL
saveURLMapping()
mutex.Unlock()
// Return the short URL
w.Write([]byte(fmt.Sprintf("Short URL: http://localhost:8080/%s", shortURL)))
}
Get Long URL: Extracts the original URL from the request query parameters.
Generate Short URL: Calls
generateShortURL
to create a unique hash.Store Mapping: Uses a mutex lock to safely store the mapping and save it to a file.
6. Handle Redirection
The redirectHandler
function takes the short URL from the request path, finds the corresponding long URL, and redirects the user.
func redirectHandler(w http.ResponseWriter, r *http.Request) {
shortURL := r.URL.Path[1:] // Extract short URL from path
mutex.Lock()
longURL, exists := urlStore[shortURL]
mutex.Unlock()
if !exists {
http.NotFound(w, r)
return
}
http.Redirect(w, r, longURL, http.StatusFound)
}
Extract Short URL: Gets the short URL from the request path.
Find Long URL: Looks up the long URL in the
urlStore
.Redirect: Uses
http.Redirect
to redirect the user to the original long URL.
7. Generate a Short URL
The generateShortURL
function generates a unique short URL using the SHA-1 hashing algorithm.
func generateShortURL(longURL string) string {
hash := sha1.New()
hash.Write([]byte(longURL))
return hex.EncodeToString(hash.Sum(nil))[:8] // Use the first 8 characters of the hash
}
SHA-1 Hashing: Creates a hash of the long URL.
Shorten Hash: Takes the first 8 characters to use as the short URL.
8. Save and Load URL Mappings
The saveURLMapping
and loadURLMapping
functions handle storing and retrieving URL mappings from a file to ensure data persistence.
func saveURLMapping() {
data, err := json.Marshal(urlStore)
if err != nil {
log.Println("Error marshaling data:", err)
return
}
err = ioutil.WriteFile("urls.json", data, 0644)
if err != nil {
log.Println("Error writing to file:", err)
}
}
func loadURLMapping() {
file, err := os.Open("urls.json")
if err != nil {
if os.IsNotExist(err) {
return // If the file doesn't exist, nothing to load
}
log.Println("Error opening file:", err)
return
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
log.Println("Error reading file:", err)
return
}
err = json.Unmarshal(data, &urlStore)
if err != nil {
log.Println("Error unmarshaling data:", err)
}
}
Save Mapping: Serializes the
urlStore
map to JSON and writes it tourls.json
.Load Mapping: Reads from
urls.json
and loads the data back into theurlStore
map.
Running the Application
To run the code:
Save the above code in
main.go
.Run the following command in your terminal:
go run main.go
- Your server will start on
http://localhost:8080
.
Testing the URL Shortener
Shorten a URL: Open your browser or use a tool like
curl
to test.curl "http://localhost:8080/shorten?url=https://example.com/long-url"
Redirect: Use the short URL provided by the server to check the redirection.
Conclusion
This tutorial shows how to build a basic URL shortener in Go. We covered how to handle HTTP requests, generate short URLs, manage concurrent access using a mutex, and persist data to a file. This example serves as a foundation for more advanced features, such as database integration, custom URL slugs, and user authentication.