4.1 KiB
title | date | tags | excerpt | ||
---|---|---|---|---|---|
Learning Go: Day Eleven | 2024-05-13T08:00:00.0Z |
|
Now it's time to actually try and send a "ping" to a website |
So, next up on my task list is to start populating my database with data. So to do that I want to be able to send a request to a given website, and then store the success or failure status based on the response.
Sending a request
It turns out that this is actually quite simple to do. The net/http
module has functions to do this, so all I need to do is:
// ping/ping.go
import (
"net/http"
)
func SendPing(db *sql.DB, site sites.Site) {
response, error := http.Get(site.url)
// Do something with the response or error
}
Easy enough!
Store the result
Now all I need to do is save the output to the database. I do that by using the inline if-statement syntax, and assigning ping.Status
depending on whether or not there is an error present. I don't actually care about the response, I just care whether or not the call succeeded.
// ping/ping.go
func SendPing(db *sql.DB, site sites.Site) {
p := Ping{
Site: site,
}
if _, err := http.Get(site.Url); err != nil {
p.Status = Failure
} else {
p.Status = Success
}
p.Save(db)
}
And as a little refactor, I can default the Status
to Success
, and get rid of the else-
branch.
// ping/ping.go
func SendPing(db *sql.DB, site sites.Site) {
p := Ping{
Site: site,
Status: Success
}
if _, err := http.Get(site.Url); err != nil {
p.Status = Failure
}
p.Save(db)
}
Now, if I write a quick test in my main.go
1, I can see that it succeeds:
// main.go
func main() {
site := sites.Site{Name: "Lewisdale.dev", Url: "https://lewisdale.dev"}
site.Save(db)
ping.SendPing(db, site)
}
sqlite> SELECT ping.id, ping.site, ping.timestamp, statuses.name FROM ping LEFT JOIN statuses ON (ping.status = statuses.id);
1|https://lewisdale.dev|2024-05-10 05:57:12|Success
And if I then force a failure, it should also work:
5|https://notreal.tld|2024-05-10 08:00:46|Failure
Adding some output
Right, now to actually output the values in the database. First of all, I've defined the Struct for the response:
// ping/ping.go
type PingResponse struct {
Site sites.Site
Timestamp string
Status string
}
And now I've added a List
function that reads the data I need from the database, and places it into an array slice of PingResponse
values:
func List(db *sql.DB) []PingResponse {
rows, err := db.Query(`SELECT sites.url as url, sites.name, ping.timestamp as timestamp, statuses.name as status FROM ping
JOIN sites ON ping.site = sites.url
JOIN statuses ON ping.status = statuses.id
ORDER BY timestamp DESC`)
if err != nil {
panic(err)
}
defer rows.Close()
pings := make([]PingResponse, 0)
for rows.Next() {
p := PingResponse{}
rows.Scan(&p.Site.Url, &p.Site.Name, &p.Timestamp, &p.Status)
pings = append(pings, p)
}
return pings
}
The interesting parts here are the defer
statement, and rows.Scan
. Defer queues that call up until after the function has executed, it's just a way of saying "I will be doing this at the end regardless" as a cleanup operation2. Then rows.Scan
will automagically insert the values to the variables I pass it, in the order the columns are read from the database3.
Then finally, I can update my handler function so that it uses json.Marshal
to convert the pings to JSON, and output them to the browser:
http.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
pings := ping.List(db)
if output, err := json.Marshal(pings); err != nil {
w.Write([]byte(err.Error()))
w.WriteHeader(http.StatusInternalServerError)
return
} else {
w.Header().Set("Content-Type", "application/json")
w.Write(output)
}
})
And that works! You can see it at https://oopsie.lewisdale.dev, with (hopefully) some actual output.