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.
shindakun / atlg
Source repo for the "Attempting to Learn Go" posts I've been putting up over on dev.to
Attempting to Learn Go
Here you can find the code I've been writing for my Attempting to Learn Go posts that I've been writing and posting over on Dev.to.
Post Index
Enjoy this post? |
---|
How about buying me a coffee? |
Top comments (0)