---
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 *`.