HTTP Client and Server in Go
Among my favorite features of the Go language is the fact that Go has concurrency and network programming “natively” built in. It reflects Go as being designed for the 21st century, where the network is as much taken for granted as the filesystem was towards the end of the 20th.
As cut-n-paste examples, here are skeleton implementation of both an HTTP server and an HTTP client. These implementations are intentionally bare-bones, so as not to obscure the most relevant points. It should be easy enough to extend them as desired.
HTTP Server
package main
import ( "fmt"
"net/http" )
func main() {
PORT := ":8088" // As string!
DOCROOT := "/home/user/www" // Or whatever. No trailing slash!
// Default endpoint: Serve static files from DOCROOT
http.HandleFunc( "/",
func( w http.ResponseWriter, r *http.Request ) {
path := r.URL.Path // extract path from request URL
http.ServeFile( w, r, DOCROOT + path )
} )
// "action" endpoint: Return a custom response
http.HandleFunc( "/action",
func( w http.ResponseWriter, r *http.Request ) {
result := "Hello, World!"
fmt.Fprintf( w, result )
} )
// "form" endpoint: Parse a form submitted via POST
http.HandleFunc( "/form",
func( w http.ResponseWriter, r *http.Request ) {
// This populates the field r.Form with a map[string][]string
err := r.ParseForm()
if err != nil {
fmt.Println( "Error: Could not parse form", err )
}
// Print values
for key, vals := range( r.Form ) {
fmt.Printf( "%s : ", key )
for _, v := range( vals ) {
fmt.Printf( "%s ", v )
}
fmt.Printf( "\n" )
}
// Create a return
fmt.Fprintf( w, "ok" )
} )
// Listen on localhost:PORT
fmt.Println( http.ListenAndServe(PORT, nil) )
}
This server implementation provides three endpoints:
/
: Serves static files from a (hardcoded) doc root directory./action
: Calculates and returns a custom result; an example of an API endpoint./form
: Parses a form submitted via an HTTP POST request, and prints the contents of the form to standard output.
HTTP Client
package main
import ( "io"
"log"
"net/http"
"net/url"
"os" )
func main() {
host := "http://localhost:8088"
outfile := "/tmp/outfile"
// -----
// GET
res, err := http.Get( host )
if err != nil {
log.Fatalln( "GET failed", err )
}
bytes, err := io.ReadAll( res.Body ) // copy body into []bytes
res.Body.Close()
// Save bytes to file
out, err := os.Create( outfile )
if err != nil {
log.Fatalln( "Could not create output file", err )
}
_, err = out.Write( bytes )
if err != nil {
log.Fatalln( "Coult not write output file", err )
}
// -----
// POST
form := url.Values{} // create an empty form
form.Set( "key1", "val1" )
form.Add( "key2", "val2a" )
form.Add( "key2", "val2b" )
res, err = http.PostForm( host + "/form", form )
if err != nil {
log.Fatalln( "Could not POST form", err )
}
}
The client first performs an HTTP GET request, then saves the body
of the response to a file. Note that res.Body
implements the
io.ReadCloser
interface: it must be read (into a different data
structure, like a slice of bytes, for example), and the closed.
The client then creates and populates a form, which it then submits via HTTP POST to the server, specifying an appropriate endpoint.
Conclusion
This is it, these listings demonstrate most of common HTTP transactions;
one can take it from here. Of course, the net/http
package provides
much more functionality, if needed.