Skip to content

Instantly share code, notes, and snippets.

@tyndyll
Last active February 26, 2019 12:44
Show Gist options
  • Save tyndyll/cce72c16dc112cbe7ffac44dbb1dc5e8 to your computer and use it in GitHub Desktop.
Save tyndyll/cce72c16dc112cbe7ffac44dbb1dc5e8 to your computer and use it in GitHub Desktop.
Introduction to Go - Hello World Server

Hello World

This gist is the code portion of a talk given at the Crystal Ball Bash.

The talk was a very high level introduction to go, and the code here is a commented example of the syntax and features that Go has to offer. It is a Hello World example, but with the twist of being a HTTP server, that uses concurrency to print out to the terminal. It looks long, but it amounts to about 50 lines of actual code once you remove the comments

Running

It is assumed that you have previously downloaded and installed Go, and configured it appropriately

Clone this gist or simply download the hello_world.go file. We are not importing any external packages so it doesn't necessarily have to exist inside the GOPATH.

go run hello_world.go

You can then open a browser and go to http://localhost:8080?name=YourName to get your special hello message. Flicking back to the terminal will show a log entry of the message receive

Compiling

If you wish to compile the binary to demonstrate the portability of the Go compiler, simply run

go install

in the directory containing the hello_world.go file. To remove ambiguity I would strongly recommend creating a directory first and moving the file into it. If go install succeeds, going to $GOPATH/bin will show the compiled file, which will be the name of the directory it was compiled from. To compile for other operating systems, simply set the GOOS environmental variable e.g.

GOOS=linux go install
GOOS=windows go install
GOOS=darwin go install # OSX

For different architectures, set the GOARCH variable

GOARCH=amd64 go install
GOARCH=386 go install
GOARCH=arm go install

Depending on the combination of GOOS and GOARCH, you will find the resulting output in a directory inside $GOPATH/bin

Resources

// package main is the entry point into the application.
//
// Packages are used to bundle common pieces of code together for the purposes
// of application architecture or for reusablility in other applications. The
// package name should be the first line of the file after any initial comment
//
// These comments in the code are used to generate the documentation which can
// be made available via the godoc tool. These particular comments will not
// show as this is the main package however. For further information see
//
// https://blog.golang.org/godoc-documenting-go-code
package main
import (
// These three packages are both from the standard library, the
// documentation for which can be found at
//
// https://golang.org/pkg/
//
// To import packages, they can be easily pulled from source control
// via the go get command
// e.g. go get github.com/tyndyll/alexa
// which will fetch a package to work with Alexa requests in Go, and
// which when imported could be accessed via alexa.FunctionName().
// Imports can also be aliased in the case where there are packages
// with the same name
"fmt"
"log"
"net/http"
)
// Printer defines the print interface
//
// Interfaces in Go contain a list of functions that a type must implement
// in order to satisfy it. Any type that satisfies an interface can be
// passed to a function that takes the interface as a parameter.
type Printer interface {
// Print outputs the provided string. There are no return values
Print(string)
}
// linePrinter is the first type we will use to implement the Print interface
// It is a simple printer that will simply output the passed string to a log
//
// Note that the linePrinter type starts with a lower case 'l'. In Go types and
// functions that start with a lower case letter are private, while types and
// functions that start with an upper case letter are Public, and available
// outside of the package (and in the documentation)
type linePrinter struct{}
// Print takes a string and does not return anything. The Print function is
// defined onto a pointer to the linePrinter type. 'p' is roughly equivalent to
// self, but is rarely if ever called that. For more information see
// https://golang.org/doc/effective_go.html#methods
func (p *linePrinter) Print(name string) {
log.Printf(p.BuildString(name))
}
// BuildString takes a name, and returns a combined string. This isn't in the
// original Printer interface, but that doesn't matter. The check as to whether
// it conforms to the interface is just the list of methods that the interface
// defines
func (p *linePrinter) BuildString(name string) string {
return fmt.Sprintf("Hello %s\n", name)
}
// GreeterService is the type that will contain a function that will provide
// the HTTP handler. We could just use a function, but we want to have access
// to a variable without relying on a global
type GreeterService struct {
// ToPrinter is a field on the struct, which contains a channel through
// which strings can be passed. Fields are accessed via a dot notation
// If no value is assigned to it, it will automatically be given a
// nil value
ToPrinter chan string
}
// SayHello is a function which implements the HTTP Handler interface.
// https://golang.org/pkg/net/http/#Handler
// The ResponseWriter is an instance of type, while the request is a pointer
// to it
func (g *GreeterService) SayHello(w http.ResponseWriter, r *http.Request) {
// name is using the short declaration form to create a new variable. It
// tells the compiler, take the type of the value that is returned from
// the right hand side, and create a variable called name with that type
//
// We can see that we are accessing the URL field of the request. From
// that URL we are calling the Query function to get a Values type, and
// then the Get method. This only works up to the Get call if the function
// has a single return
name := r.URL.Query().Get("name")
// There are no brackets around the if statement, and the { is required
// on this line to terminate the statement
if name == "" {
// I'm sorry, I watch a lot of CSI while I'm working
http.Error(w, "Who are you? (who who, who who)", http.StatusBadRequest)
return
}
// We have a name, so we want to pass that through to the printer to let
// it do what ever it is that it does. This type has no idea what that
// could be and doesn't really care. The only time this would be a concern
// is if the channel becomes full, at which point this call will block
g.ToPrinter <- name
// This type of if statement is very common in Go, where the if statement
// is tested, and then a check performed after the semi colon. These can
// be chained with && and ||
//
// The _ variable throws away the value that is assigned to it (in this case
// the number of bytes written). Go refuses to compile if there are variables
// that are declared and not used, so it is necessary to throw them away or
// delete them
if _, err := w.Write([]byte("Hello " + name)); err != nil {
http.Error(w, "Server Write Error", http.StatusInternalServerError)
return
}
// The function automatically returns
}
// main is the entry point to the program. It takes no arguments and does not
// return anything. Any command line arguments are available via the os.Args
// variable (which requires importing the os package)
func main() {
// We are creating a channel that will contain strings, giving it a
// buffer size of 10. The make keyword properly creates the structure
// and sizes it appropriately
//
// Channels are a convenient way to pass information through goroutines
// without having to rely on locks and mutexes.
// https://golang.org/doc/effective_go.html#channels
printerChannel := make(chan string, 10)
// Create an instance of the linePrinter type. We are creating a
// pointer to the struct by asking for it's address (&).
printer := &linePrinter{}
// We are creating an anonymous function and then running it in a
// goroutine (https://golang.org/doc/effective_go.html#goroutines)
//
// The function accepts an argument which satisfies the Printer
// interface
// At this point this function will be executed in the background
// and the program will continue
go func(p Printer) {
// This is the long form declaration. It is possible to assign
// a value here (e.g. var name string = "Tyndyll") but in this
// instance name will be set to ""
var name string
// We want this loop to run forever
for {
// Using the channel defined above, we use the <-
// notation to take a value from the channel, if one
// is available. If the channel is empty this request
// will block.
name = <-printerChannel
// Call the Print method. Attempts to call the other
// Public method BuildString would fail, because we have
// explicitely passed through and typed it as the
// Printer interface, which only has the Print method.
// To access the other methods we could cast this to the
// appropriate type.
//
// What is important to realise here, is that we could
// have a type, which prints the result to an actual
// printer, or emails it, or displays it on a stadium
// jumbotron. We don't care what the type is, as long
// as it satisfies the interface
p.Print(name)
}
}(printer)
// The goroutine is now being started
// Create an instance of a pointer to a GreeterService
g := &GreeterService{
// Set the initial value of the field. Notice the comma at the
// end of the line. This is another of Go's style idioms, which
// declares that you're always going to add another field
// eventually, lets add the comma and avoid the error later
ToPrinter: printerChannel,
}
// For the route "/", take use the function pointed to here.
http.HandleFunc("/", g.SayHello)
// GO! This starts the HTTP server on port 8080. There is also a TLS server
// available that operates in the same manner
http.ListenAndServe(":8080", nil)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment