diff options
| author | Jacob McDonnell <jacob@jacobmcdonnell.com> | 2026-04-19 19:23:05 -0400 |
|---|---|---|
| committer | Jacob McDonnell <jacob@jacobmcdonnell.com> | 2026-04-19 19:23:05 -0400 |
| commit | 9bc0c33a952dc906344964890bce58761d6ce2c7 (patch) | |
| tree | c2ec7bdf4369f97010f3496bd7d85c7da83abb2b | |
feat: Initial Commit, Partial Functionality
This is the initial commit. The url-shortener is partially functional.
URLs can be shortened and stored for the runtime of the program. API
keys are not checked, nothing is persistent, and short URLs are size
limited to 4 alpha numeric characters.
| -rw-r--r-- | go.mod | 3 | ||||
| -rw-r--r-- | main.go | 105 | ||||
| -rwxr-xr-x | test.sh | 9 |
3 files changed, 117 insertions, 0 deletions
@@ -0,0 +1,3 @@ +module mcdonnell.dev/url-shortener + +go 1.26.1 @@ -0,0 +1,105 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "math/rand/v2" + "net/http" + "unicode/utf8" +) + +const charSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" +const version = "0.0.1" + +var Table map[string]string = make(map[string]string) + +type Request struct { + URL string +} + +type Response struct { + Version string + Status string + Original string + New string +} + +func NewRequest(r *http.Request) (Request, error) { + var request Request + + err := json.NewDecoder(r.Body).Decode(&request) + + return request, err +} + +func log_header(r *http.Request) { + log.Println("Header {") + for k, v := range r.Header { + log.Printf("\t\"%s\": \"%s\"\n", k, v) + } + log.Println("}") +} + +func redirect(w http.ResponseWriter, r *http.Request) { + log.Println("Request for:", r.URL.String()) + log_header(r) + + _, i := utf8.DecodeRuneInString(r.URL.String()) + url := r.URL.String()[i:] + + original, ok := Table[url] + + if ok { + w.Header().Set("location", original) + http.Error(w, http.StatusText(301), 301) + } else { + http.Error(w, http.StatusText(404), 404) + } +} + +func shorten(w http.ResponseWriter, r *http.Request) { + log.Println("Request to Shorten") + log_header(r) + + request, err := NewRequest(r) + if err != nil { + http.Error(w, http.StatusText(500), 500) + } + + url := GenerateString(4) + _, ok := Table[url] + + for ok { + url = GenerateString(4) + _, ok = Table[url] + } + + Table[url] = request.URL + + response := Response{version, "ok", request.URL, url} + + data, err := json.Marshal(response) + if err != nil { + http.Error(w, http.StatusText(500), 500) + } + + fmt.Fprintf(w, "%s", string(data)) +} + +func GenerateString(size uint) string { + buffer := make([]rune, size) + + for i := uint(0); i < size; i++ { + index := rand.Uint() % uint(len(charSet)) + buffer[i] = []rune(charSet)[index] + } + + return string(buffer) +} + +func main() { + http.HandleFunc("/shorten", shorten) + http.HandleFunc("/", redirect) + log.Fatal(http.ListenAndServe(":8000", nil)) +} @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +RES=$(curl -X 'POST' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + --data '{ "URL": "https://git.mcdonnell.dev/" }' \ + http://localhost:8000/shorten 2>/dev/null) +printf '%s\n' "$(echo "$RES" | json_pp)" + |
