paint-brush
Make a teapot server in Golangby@hackernoon-archives
710 reads
710 reads

Make a teapot server in Golang

by HackerNoon ArchivesApril 23rd, 2017
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

The IETF (Internet Engineering Task Force) likes to do things for April Fools, and 1998 was the year they added 418, or <a href="https://tools.ietf.org/html/rfc2324">Hyper Text Coffee Pot Control Protocol</a>.

Company Mentioned

Mention Thumbnail
featured image - Make a teapot server in Golang
HackerNoon Archives HackerNoon profile picture

The IETF (Internet Engineering Task Force) likes to do things for April Fools, and 1998 was the year they added 418, or Hyper Text Coffee Pot Control Protocol.

It is, of course, complete nonsense, which makes it wonderful.

You should already have your Golang installation ready to go and know how create and run programs.

We begin with the thing you see in most programming languages: importing packages to do useful things.

package main

import (
	"fmt"
	"html"
	"log"
	"net/http"
	"os"
)

Main is just the package name for the program we’re making here. A more complicated program will have lots of packages created for that program, and they would have all their own names. You’d include those with import.

This is a very simple program, and Golang comes with all the necessary tools.

  • fmt: Used for formatting the output before sending it to the requesting client.
  • html: Used for stripping HTML out of the host, retrieved from the request. Without this, you’ll create a cross-site scripting vulnerability. Someone can forge the host field and put arbitrary, possibly nasty HTML in there, which your server will dutifully print.
  • log: used for outputting usage information to the console you run the program from.
  • net/http: A magical place full of useful tools for creating HTTP clients and servers.
  • os: Provides access to various operating system functions, like reading command line arguments, in a cross-platform way.

I linked each of those package names to the official Golang package documentation, which is very well-written.

func main() {

Every program has a main function!

if 2 == len(os.Args) {

Indexes, like os.Args, start from zero. os.Args[0] is the path of the executable. Executable path is supplied by the operating system. The port to run the server on is os.Args[1]. len() is different — it’s a count of items in an array, so we expect 2 arguments (executable path and port).

http.HandleFunc("/", func(write http.ResponseWriter, read *http.Request) {
			write.WriteHeader(http.StatusTeapot)
			fmt.Fprintf(write, "I'm a teapot running on %q", html.EscapeString(read.Host))
		})

This calls http.HandleFunc with two arguments: a URL to listen on, and a function — passed as an argument — to handle that URL. So / would be the root, like medium.com/ or any other site.

Can this second argument be a function you’ve written elsewhere and called here? Probably! I don’t know! You should try it and see if it breaks. There’s probably a reason many examples I found do it this way, and probably cases where, if it works, breaking it out into a separate function is best.

So this function built and called within a function is a little complex. Let’s break it down further.

There are two calls within the function’s brackets.

write.WriteHeader(http.StatusTeapot)

You may be asking: “where the heck did write come from?” This is a good question, and it’s easy to miss. write is the first argument of our little function within a function. It contains some methods (I am probably using the wrong term here). One of those methods is WriteHeader, which writes a header to the client. Headers always come first in HTTP. http.StatusTeapot is a constant, defined in net/http, and it’s set to the teapot status code: 418.

fmt.Fprintf(write, "I'm a teapot running on %q", html.EscapeString(read.Host))

Again, write is an instance of the object helpfully provided by Golang’s http package to send stuff to the requesting client. fmt.Printf takes two or more arguments: a thing to write to(the object called write in this case), and any number of things to write to the client separated by commas.

The string we send to the requesting client is two parts:

  • The fmt package doc defines %q as “a single-quoted character literal safely escaped with Go syntax.” Refer to the fmt package doc for other verbs. Formatted printing like this is useful when you’re trying to produce a single line of text derived from different sources of varying data types.

  • html.EscapeString(read.Host) is what fills in the %q. read.Host is the host requested by the client— domain or IP, port. This could be anything, as I mentioned early on when I listed the packages. This is why you call html.EscapeString() to sanitize the input before passing it back to the client. I’m not sure it’s necessary here, but it’s a good practice unless you really need to display raw input as output.

    port := string(os.Args[1])

This sets the port to the first argument supplied by the user, after the path supplied by the operating system.

http.ListenAndServe(":"+port, nil)

This directs the http package to start a server with port, and the second argument is nil since we already defined the handler earlier. You can call a handler function here instead of nil, but I like the way this looks more and found it easier to work with. The documentation for net/http shows other ways to go about this. Reading through package documentation is never a waste of time.

} else {
		log.Printf("Usage: %v <port>\n", os.Args[0])
	}

}

When you try to run a command line program and it gives you usage information instead of doing what you want, this is one way it’s done.

I’m sure I missed something, and wasn’t clear enough in parts. Feel free to select any piece of text and leave a comment.

Here’s the completed application. You can also find it on Github.

package main

import (
	"fmt"
	"html"
	"log"
	"net/http"
	"os"
)

func main() {

	if 2 == len(os.Args) {

		http.HandleFunc("/", func(write http.ResponseWriter, read *http.Request) {
			write.WriteHeader(http.StatusTeapot)
			fmt.Fprintf(write, "I'm a teapot running on %q", html.EscapeString(read.Host))
		})

		port := string(os.Args[1])
		http.ListenAndServe(":"+port, nil)

	} else {
		log.Printf("Usage: %v <port>\n", os.Args[0])
	}

}