In this section you'll learn how to write a simple HTTP server in Go.
We will use the net/http package to do so, click
on that link to browse its documentation.
The net/http package defines the HandlerFunc type:
type HandlerFunc func(ResponseWriter, *Request)The first parameter of this function type is a
ResponseWriter, which
provides a way to set headers on the HTTP response. It also provides a Write
method which makes it satisfy the io.Writer interface.
Let's see a very simple HTTP handler that simply writes "Hello, web" to the output:
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, web")
}As you can see we're using the fmt.Fprintln function, whose first parameter
is an io.Writer.
Once a handler is defined we need to inform the http package about it and
specify when to run it. To do so we can use the http.HandleFunc function:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))The first parameter is a pattern which will be used to decide when to execute a handler, and the second argument is the handler itself.
Patterns name fixed, rooted paths, like "/favicon.ico", or rooted subtrees,
like "/images/" (note the trailing slash). Longer patterns take precedence
over shorter ones, so that if there are handlers registered for both
"/images/" and "/images/thumbnails/", the latter handler will be called for
paths beginning "/images/thumbnails/" and the former will receive requests
for any other paths in the "/images/" subtree.
Let's see how to register our helloHandler defined above:
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, web")
}
func main() {
http.HandleFunc("/hello", helloHandler)
}Note that we're registering our handler as part of the main function.
Try to run the code above:
$ go run examples/step2/main.goWhat happens? Well, we're missing the last piece of the puzzle: starting the web server!
Using http.HandleFunc and passing a value of type http.HandlerFunc can be pretty constraining.
There's also another function http.Handle that will accept any value satisfying the http.Handler interface.
The http.Handler interface is defined in the http package as:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}And guess what, the type http.HandlerFunc satisfies http.Handler thanks to
HandlerFunc.ServeHTTP.
The code of the ServeHTTP method for HandlerFunc is something of beauty.
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}We will see how this interface is the extension points where web frameworks and toolkits add functionality.
Once the handlers have been defined and registered we need to start the HTTP server to listen for requests and execute the corresponding handler.
To do so we use the function
http.ListenAndServe:
func ListenAndServe(addr string, handler Handler) errorThe first parameter is the address on which we want the server to listen,
we could use something like "127.0.0.1:8080" or "localhost:80".
The second parameter is an http.Handler, a type that allows you to define
different ways of handling requests. Since we're using the default methods
with HandleFunc we don't need to provide any value here: nil will do.
And last but definitely not least the function returns an error. In Go,
errors are handled by returning values rather than throwing exceptions.
The type error is a predefined type (just like int or bool) and is an interface
with only one method:
type error interface {
Error() string
}By convention errors are the last value returned by methods and functions and
when no error has occurred the returned value equals to nil.
So if we want to check that our server started successfully and log an error
otherwise we would modify our code to add a call to ListenAndServe.
package main
import (
"fmt"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, web")
}
func main() {
http.HandleFunc("/hello", helloHandler)
err := http.ListenAndServe("127.0.0.1:8080", nil)
if err != nil {
log.Fatal(err)
}
}Running this code should now start a web server listening on 127.0.0.1:8080.
Try it:
$ go run main.go
And then visit http://127.0.0.1:8080/hello.
Modify the program above by adding a second handler named byeHandler that prints "Bye, web"
to the http response.
Modify the program from the previous example so you can replace the call to http.HandleFunc
by a call to http.Handle. You will need to define a new type helloHandler and make that type
satisfy the http.Handler interface.
Soon you will start having more complicated requirements to route your requests to your handlers such as:
- route depending on the methods:
POSTandGETrouted different handlers. - variable extraction from paths:
/product/{productID}/part/{partID}
These cases can be handled either by hand or using a toolkit that will plug
correctly into the existing net/http package, such as the
Gorilla toolkit and its mux package.
package main
import (
"log"
"net/http"
"github.com/gorilla/mux"
)
func listProducts(w http.ResponseWriter, r *http.Request) {
// list all products
}
func addProduct(w http.ResponseWriter, r *http.Request) {
// add a product
}
func getProduct(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["productID"]
log.Printf("fetching product with ID %q", id)
// get a specific product
}
func main() {
r := mux.NewRouter()
// match only GET requests on /product/
r.HandleFunc("/product/", listProducts).Methods("GET")
// match only POST requests on /product/
r.HandleFunc("/product/", addProduct).Methods("POST")
// match GET regardless of productID
r.HandleFunc("/product/{productID}", getProduct)
// handle all requests with the Gorilla router.
http.Handle("/", r)
if err := http.ListenAndServe("127.0.0.1:8080", nil); err != nil {
log.Fatal(err)
}
}Gorilla also provides packages for session management, cookies, etc. Have a look at the documentation.
Using the mux package from the previous example write a new web server.
This server will handle all HTTP requests sent to /hello/name with an HTTP page
containing the text "Hello, name". The name in this example can of course change,
so if the request was /hello/Francesc the response should say "Hello, Francesc".
Note: to install the mux package in your machine you can use go get:
$ go get github.com/gorilla/muxYou just wrote your first HTTP server in Go! Isn't it awesome? Well, it doesn't do much yet but the best is to come.
On the next chapter we'll learn how to validate whatever the input of your HTTP endpoints and how to signal different problems in the HTTP responses.
Continue to the next section.