Attempting to Learn Go - Building a Downloader Part 03
Tags: go, continuingeducation, atlg
Published: 2018-01-12 04:09:19

---
title: "Attempting to Learn Go - Building a Downloader Part 03"
published: true
tags: go,golang,continuing-education, atlg
canonical_url: https://shindakun.glitch.me/attempting-to-learn-go-building-a-downloader-part-03/
cover_image: https://shindakun.glitch.me/content/images/2018/01/attemptinggo-3.png
series: "Attempting to Learn Go"
---
When we [last left off](https://dev.to/shindakun/attempting-to-learn-go---building-a-downloader-part-02-2k7i) we had just finished putting together our download endpoint. This time around we're going extend it to actually attempt to download a file. That means we'll also be touching on how we're going to test our download endpoint. Are you ready? Let's dive in!

## Pass Four

It may look scary as we've added a bunch more imports. With [io](https://godoc.org/io), [net/url](https://godoc.org/net/url), [os](https://godoc.org/os), and [path/filepath](https://godoc.org/path/filepath) appearing for the first time. Keep an eye out for them to be used later on, you'll see they aren't scary at all. Now, let's jump most of the way down our code to the bottom of the `handleDownRequest` function.

```go
package main

import (  
  "encoding/json"
  "fmt"
  "io"
  "io/ioutil"
  "log"
  "net/http"
  "net/url"
  "os"
  "path/filepath"
)

type download struct {  
  Title string `json:"title"`
  Location string `json:"location"`
}

func status(response http.ResponseWriter, request *http.Request) {  
  fmt.Fprintf(response, "Hello!")
}

func handleDownloadRequest(response http.ResponseWriter, request *http.Request) {  
  var downloadRequest download
  r, err := ioutil.ReadAll(request.Body)
  if err != nil {
    http.Error(response, "bad request", 400)
    log.Println(err)
    return
  }
  defer request.Body.Close()

  err = json.Unmarshal(r, &downloadRequest)
  if err != nil {
    http.Error(response, "bad request: "+err.Error(), 400)
    return
  }
  log.Printf("%#v", downloadRequest)
```

Everything should look pretty familiar from our last revision. Besides the new imports, nothing else has changed so far. At this point in the code, we have our struct populated with the title and URL for downloading. Let's pass that to a new function `getFile`. `getFile` will return an error or `nil`, if successful. If something happens we'll simply return a 500 "internal server" error back to the browser. We'll log something a bit more specific to the internal log though. We want to avoid leaking server information back to the browser whenever possible for security purposes. I'm not really too concerned about it for this project but it's a good thing to keep in mind.

```go
err = getFile(downloadRequest)
  if err != nil {
    http.Error(response, "internal server error", 500)
    return
  }

  fmt.Fprintf(response, "Download!")
}
```

At the moment getFile isn't a very large function. As noted above we're passing in our `download` struct and returning an error (or `nil`). Since we're going to be saving the file to disk I'm using `url.Parse` from the [net/url](https://godoc.org/net/url) package to parse the URL into its component parts.

```go
func getFile(downloadRequest download) error {  
  parsedUrl, err := url.Parse(downloadRequest.Location)
  if err != nil {
    log.Println(err)
    return err
  }
```

Finally, the heart of our project! `http.Get` takes our URL and requests the remote resource. However, as written this could be a problem - we're assuming that whatever URL retrieved from the JSON is OK. At the very least it's "well-formed" or it should not have made it past the `url.Parse` function. Later on, we'll likely need to inspect it much closer to try and determine if we should even attempt the GET request.

```go
response, err := http.Get(downloadRequest.Location)
  if err != nil {
    log.Println(err)
    return err
  }
  defer response.Body.Close()
```

Let's assume everything went as it should and we have retrieved the remote resource. We're going to used `parsedUrl.Path` and [`filepath.Base`](https://godoc.org/path/filepath) to pull out what should be the proper filename. Again - we might need to extend this later, what if we get a URL that doesn't have a proper filename in it? Since we control the testing environment we should be OK for now though.

```go
out, err := os.Create(filepath.Base(parsedUrl.Path))
  defer out.Close()
  _, err = io.Copy(out, response.Body)
  if err != nil {
    log.Println(err)
    return err
  }
  return nil
}

func main() {  
  log.Println("Downloader")

  http.HandleFunc("/", status)
  http.HandleFunc("/download", handleDownloadRequest)
  http.ListenAndServe(":3000", nil)
}
```

* * *

## Testing

To make our testing easy we're going to use the fantastic [Postman](https://www.getpostman.com/). If you are going to be doing any work with API's I recommend adding it to your toolkit, it's indispensable. I'm not going to go too deep into using Postman itself though, that is left as an exercise for the reader.

We'll use the following JSON object for this round of testing.

```json
{
  "title": "Attempting Go",
  "location": "https://shindakun.glitch.me/content/images/2018/01/attemptinggo-2.png"
}
```

![Attempting to Learn Go - Building a Downloader Part 03](https://shindakun.glitch.me/content/images/2018/01/postman01.png)

Upon running the current version of the code we should see something like:

```bash
$ go run downloader.go
2018/01/11 11:06:24 Downloader
```

Now, if we submit our PUT request with Postman we should see:

```bash
2018/01/11 11:07:32 main.download{Title:"Attempting Go", Location:"https://shindakun.glitch.me/content/images/2018/01/attemptinggo-2.png"}
```

Along with a newly created PNG in the same directory.

```bash
-rw-r--r-- 1 steve 197609 74860 Jan 11 11:07 attemptinggo-2.png
```

Awesome! Tune in tomorrow when we clean up a bit and add the ability to save files into a subdirectory. After that, I think we'll look into doing our initial deployment!

Until ~~tomorrow~~ [next time](https://dev.to/shindakun/attempting-to-learn-go---building-a-downloader-part-04-3ln9)...

***

You can find the code for this and most of the other Attempting to Learn Go posts in the repo on GitHub.

{% github shindakun/atlg %}

***

| Enjoy this post? |
|:----:|
| [How about buying me a coffee?](https://ko-fi.com/shindakun) |

***