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.go
What 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) error
The 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 tohttp.Handle
. You will need to define a new typehelloHandler
and make that type satisfy thehttp.Handler
interface.
Soon you will start having more complicated requirements to route your requests to your handlers such as:
- route depending on the methods:
POST
andGET
routed 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
(httprouter is another option).
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"
. Thename
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 usego get
:$ go get github.com/gorilla/mux
You 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 part 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.