--- title: "Learning Go: Day Eleven" date: 2024-05-13T08:00:00.0Z tags: - learning - go excerpt: "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: ```go // 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. ```go // 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. ```go // 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: ```go // main.go func main() { site := sites.Site{Name: "Lewisdale.dev", Url: "https://lewisdale.dev"} site.Save(db) ping.SendPing(db, site) } ``` ```bash 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: ```bash 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: ```go // 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: ```go 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 operation[^2]. Then `rows.Scan` will automagically insert the values to the variables I pass it, in the order the columns are read from the database[^3]. 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: ```go 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. [^1]: Yes, despite what I said in another post I've not written any _actual_ tests. I'm human, alright? [^2]: I think [^3]: This is where it helps to be explicit with what is selected and avoid `SELECT *`.