loading...
Cover image for Attempting to Learn Go - Building a Downloader Part 02

Attempting to Learn Go - Building a Downloader Part 02

shindakun profile image Steve Layton Originally published at shindakun.glitch.me on ・4 min read

Attempting to Learn Go (23 Part Series)

1) Attempting to Learn Go - Building a Downloader Part 01 2) Attempting to Learn Go - Building a Downloader Part 02 3 ... 21 3) Attempting to Learn Go - Building a Downloader Part 03 4) Attempting to Learn Go - Building a Downloader Part 04 5) Attempting to Learn Go - Building a Downloader Part 05 6) Attempting to Learn Go - Consuming a REST API 7) Attempting to Learn Go - Continuing REST Adventures 8) Attempting to Learn Go - Now Sending REST Requests 9) Attempting to Learn Go - REST API and A Bit On Templates 10) Attempting to Learn Go - Sending Email Via API Again 11) Attempting to Learn Go - Let's Get Modular! 12) Attempting to Learn Go - Let's Get Modular - Again! 13) Attempting to Learn Go - Building Dev Log Part 01 14) Attempting to Learn Go - Building Dev Log Part 02 15) Attempting to Learn Go - Building Dev Log Part 03 16) Attempting to Learn Go - Building Dev Log Part 04 17) Attempting to Learn Go - Building Dev Log Part 05 18) Attempting to Learn Go - Listing Files By Extension 19) Attempting to Learn Go - Sorting and Moving Files by Extension 20) Attempting to Learn Go - Issuer 01 21) Attempting to Learn Go - Issuer 02 22) Attempting to Learn Go - Issuer 03 - Cloud Function Go! 23) Attempting to Learn Go - Issuer 04 - Addendum

Last time we outlined our plan to create a simple Go server that would download remote files. What I'd like to do this time is to begin to write some actual code. I'm going to try and document exactly how I put the program together along with any references I used. I've also tried to be explicit with regards to naming my variables. For instance, using response http.ResponseWriter, request *http.Request instead of w http.ResponseWriter, r *http.Request. The hope is that anyone of any skill will be able to follow the code.

Pass One

Our first task is simply to get some code on the screen. We're going to define our package, main and a single import fmt (format) so we can print to standard output.

package main

import "fmt"

We'll use the JSON object we defined previously and JSON-to-Go website to quickly create our struct. I recommend bookmarking this site, if you are going to work with JSON in Go it comes in very handy.

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

In our main function, for now, we're first going to simply print "Downloader" at startup. After that, we're going to create a test struct which will then be printed.

func main() {  
  fmt.Printf("Downloader")

  download := download{
    Title: "title-test",
    Location: "location-test",
  }

  fmt.Printf("%v", download)
}

Which, when run, results in the following output.

$ go run downloader.go
Downloader  
{title-test location-test}

Pass Two

The first thing to take note of is that we are using two new imports. io/ioutil and net/http are both parts of the Go standard library and will help take care of some of the functionality. The standard library gives the developer the raw tools needed to create many different programs. All they need to do is learn how to put it together. And as we'll see in a future post if a standard library package doesn't exist for a function there is a good chance someone in the community has thought of that.

package main

import (  
  "fmt"
  "io/ioutil"
  "net/http"
)

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

We come now to our first new function, status. Currently, requesting "/" on the server will result in a 200 response with a body of "Hello!". Eventually, we will update this to return a little bit of server information for service health check purposes.

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

The next new function handleDownloadRequest is going to be the main focus of the server portion of our program. In its current form, it will accept a request on "/download" and print the body from the request to the console and the word "Download!" to the browser.

func handleDownloadRequeset(response http.ResponseWriter, request *http.Request) {  
  r, err := ioutil.ReadAll(request.Body)
  if err != nil {
    fmt.Println(err)
  }
  defer request.Body.Close()
  fmt.Println(string(r))

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

To make use of our new functions we've updated main to set up the HTTP handlers. Finally, we start the server listening on port 3000.

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

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

Pass Three

The majority of changes this time around occur in the handleDownloadRequest function. However, if you look closely you'll see that I've also added encoding/json and log as imports. Being a server application I wanted to use the built-in features of log to print to the console. I'm not sure if I'm going to redirect it to file or just leave it on the console at this point though.

package main

import (  
  "encoding/json"
  "fmt"
  "io/ioutil"
  "log"
  "net/http"
)

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

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

Currently, the heart of our program resides here. We have what looks to be some significant changes. First, we are creating a struct called downloadReqest to hold our incoming title and URL. I've also added some better error handling, with actual responses to the browser when appropriate.

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()

Assuming the initial request is OK and we are able to read the body, we then need to get the request JSON into our struct. json.Unmarshal will take care of the heavy lifting for us. If not we can return an error to the browser, here we include the error message from json.Unmarshal in the output.

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

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

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

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

Not bad for a little bit of work. We have gone from a basic skeleton to a server in no time. Starting tomorrow and our next revision we'll update the handleDownloadRequest function by adding a getFile function that we can call to get our actual file.

Until next time...


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



Attempting to Learn Go (23 Part Series)

1) Attempting to Learn Go - Building a Downloader Part 01 2) Attempting to Learn Go - Building a Downloader Part 02 3 ... 21 3) Attempting to Learn Go - Building a Downloader Part 03 4) Attempting to Learn Go - Building a Downloader Part 04 5) Attempting to Learn Go - Building a Downloader Part 05 6) Attempting to Learn Go - Consuming a REST API 7) Attempting to Learn Go - Continuing REST Adventures 8) Attempting to Learn Go - Now Sending REST Requests 9) Attempting to Learn Go - REST API and A Bit On Templates 10) Attempting to Learn Go - Sending Email Via API Again 11) Attempting to Learn Go - Let's Get Modular! 12) Attempting to Learn Go - Let's Get Modular - Again! 13) Attempting to Learn Go - Building Dev Log Part 01 14) Attempting to Learn Go - Building Dev Log Part 02 15) Attempting to Learn Go - Building Dev Log Part 03 16) Attempting to Learn Go - Building Dev Log Part 04 17) Attempting to Learn Go - Building Dev Log Part 05 18) Attempting to Learn Go - Listing Files By Extension 19) Attempting to Learn Go - Sorting and Moving Files by Extension 20) Attempting to Learn Go - Issuer 01 21) Attempting to Learn Go - Issuer 02 22) Attempting to Learn Go - Issuer 03 - Cloud Function Go! 23) Attempting to Learn Go - Issuer 04 - Addendum

Posted on by:

shindakun profile

Steve Layton

@shindakun

I've been known to write some code from time to time.

Discussion

markdown guide