Right now your shortener shortens anything it gets. Even a body with no "url" gets a code: reading a key that's missing from a Go map gives the type's zero value, here the empty string "", and "" is saved like any other text. Short links are clicked by strangers, so the app should check what it gets first. Bad input is turned away with 400 Bad Request, before anything is counted or stored.
strings.HasPrefix(s, prefix) tells you whether a string begins with a given text. And writing an answer doesn't end a handler: after writeJSON, a return stops it, or it goes on to the lines below. Here is the same idea on another route, a list of tags that only accepts names starting with #:
app.HandleFunc("POST /tags", func(w http.ResponseWriter, r *http.Request) {
var data map[string]string
json.NewDecoder(r.Body).Decode(&data)
name := data["name"]
if !strings.HasPrefix(name, "#") {
writeJSON(w, 400, map[string]string{"error": "A tag starts with #"})
return
}
tags = append(tags, name)
writeJSON(w, 201, map[string]string{"name": name})
})
Only addresses that start with http:// or https:// are web pages to send people to. Without that start, a browser reads www.python.org as a page of your own app, and ftp: or javascript: addresses don't belong behind a link strangers click. One more rule: the same URL sent twice gets the code it already has, with 200 OK, because nothing new was created. These are the test requests, in order:
POST /links {"url": "https://www.python.org/downloads/"} → 201 {"code": "1", "url": "https://www.python.org/downloads/"}
POST /links {"url": "ftp://files.example.com/report.pdf"} → 400 {"error": "The URL must start with http:// or https://"}
POST /links {"url": "javascript:alert(1)"} → 400 {"error": "The URL must start with http:// or https://"}
POST /links {"url": "www.python.org"} → 400 {"error": "The URL must start with http:// or https://"}
POST /links {} → 400 {"error": "The URL must start with http:// or https://"}
POST /links {"url": "http://example.com/old-page"} → 201 {"code": "2", "url": "http://example.com/old-page"}
POST /links {"url": "https://www.python.org/downloads/"} → 200 {"code": "1", "url": "https://www.python.org/downloads/"}
GET /links/3/stats → 404 {"error": "Link not found"}
GET /2 → 302 Location: http://example.com/old-page
Your task: in the POST /links handler, below url, answer 400 with the error above when url starts with neither http:// nor https://, and stop the handler. Then, before the counter, look through links: if a stored link has the same URL, answer its code and URL with 200 and stop the handler.
Assignment
Follow the Coding Tutorial to make the POST /links handler accept only real web addresses and give a URL it already knows its old code.
Hint
Both checks go above counter += 1, and each answer needs its own return, so a refused or repeated URL uses up no number: that is why GET /links/3/stats answers 404. A range loop over a map with one variable gives its keys, here the codes.
Free plan: 30 tutor messages. PRO is unlimited.
