Getting Started with Go: HTTP1 Networking

Mike Dyne
Evendyne
Published in
5 min readOct 4, 2023
A minimal Go server that listens on port 8080 and responds with “Hello, World!” to incoming HTTP requests

Networking is a fundamental aspect of computer science and is essential for the communication between different systems. The Go programming language is a modern language that is well-suited for network programming. This article will explore some basic concepts and techniques for networking in Golang, including examples to illustrate the concepts.

A common use case for network programming is the creation of servers and clients. In Golang, the net package provides a variety of functions and types for creating servers and clients. Let’s look at servers first.

Server

A Go server is an performant software component designed to handle incoming network requests, making it suitable for building various network services such as web servers, APIs, and microservices.

Let’s see a simple server that listens on port 8080 and echoes back any data received from clients:

package main

import (
"fmt"
"net"
)

func main() {
proto := "myproto"
ln, err := net.Listen(proto, ":8080")
if err != nil {
fmt.Println(err)
return
}
defer ln.Close()

for {
conn, err := ln.Accept()
if err != nil {
fmt.Println(err)
continue
}

go func(conn net.Conn) {
defer conn.Close()
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
if err != nil {
fmt.Println(err)
return
}
_, err = conn.Write(buf[:n])
if err != nil {
fmt.Println(err)
return
}
}
}(conn)
}
}

This is a simple server that listens on port 8080 using a custom protocol named “myproto.” When a client connects to this server, it accepts the connection, reads data from the client, and immediately writes it back to the client. The server can handle multiple client connections concurrently by launching a new goroutine for each incoming connection, making it suitable for concurrent network communication.

Additionally, you may also use lower-level libraries such as netlink or gopacket for working with network protocols at a more granular level. These libraries provide a way to directly manipulate the Linux kernel’s networking stack, which is particularly useful for creating network appliances or other specialized networking applications. But that’s a topic for a more advanced article.

Another important aspect of network programming is the ability to work with different types of network protocols. Golang provides support for a wide range of protocols, including TCP, UDP, and HTTP. This allows for the creation of a variety of different types of networked applications, such as web servers, message queues, and more. However, it is important to note that Go’s built-in support for certain protocols may not provide all the necessary functionality for certain use cases and in such cases it may be necessary to use third party libraries.

For example, the http package provides a set of functions and types for creating HTTP servers and clients. However, it is common for experts to use a more powerful and flexible third-party library such as Chi or Echo for creating web applications. Chi is my favorite of the plenty out there. It’s a lightweight and powerful library that provides a lot of useful features such as routing, middleware, and request context handling. It also has a small footprint and is easy to use, making it a popular choice among developers for creating web applications in Go.

package main

import (
"fmt"
"net/http"

"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
)

func main() {
r := chi.NewRouter()

r.Use(middleware.Logger)
r.Use(middleware.Recoverer)

r.Get("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Welcome to my website!")
})

r.Post("/login", func(w http.ResponseWriter, r *http.Request) {
username := r.FormValue("username")
password := r.FormValue("password")
fmt.Fprintf(w, "Username: %s, Password: %s", username, password)
})

http.ListenAndServe(":8080", r)
}

This example creates a new router and handles different HTTP routes. The router is configured to handle the GET request on the root URL (“/”) and the POST request on the “/login” URL. When a user navigates to the root URL, a simple welcome message is displayed. When a user submits a form to the “/login” URL, the router extracts the username and password from the request body and displays it on the browser.

The example also uses middleware from chi library to log the requests and recover from panics. The middleware.Logger logs the request and middleware.Recoverer recover from panics, these middleware are added to the router using the Use method.

Client

Go clients are used to initiate connections to servers we saw in the previous section, enabling applications to communicate with and consume the services provided by the server.

Let’s see a simple HTTP client that makes a GET request to an API endpoint using the http package:

package main

import (
"fmt"
"io/ioutil"
"net/http"
)

func main() {
resp, err := http.Get("https://jsonplaceholder.typicode.com/todos/1")
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}

fmt.Println(string(body))
}

This snippet uses the http.Get function to make a GET request to the specified API endpoint. The Get function returns a response struct and an error. If the request is successful, the response struct contains information about the response such as the status code, headers, and the response body. The response body is read using the ioutil.ReadAll function which reads from an io.Reader and returns a byte slice.

It’s also worth mentioning that you can use other http libraries such as net/http, golang.org/x/net/http2, or even third-party libraries like golang.org/x/oauth2 to make http calls with more features and functionalities.

For example, to make a POST request with a JSON payload, you can use the http.NewRequest function and set the request body and headers accordingly:

package main

import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
)

func main() {
jsonData := []byte(`{"title":"foo","body":"bar","userId":1}`)

req, err := http.NewRequest("POST", "https://jsonplaceholder.typicode.com/posts", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}

fmt.Println(string(body))
}

This example uses the http.NewRequest function to create a new POST request with a JSON payload. It sets the request body using the bytes.NewBuffer function and sets the “Content-Type” header to “application/json”. Then it uses an http.Client to execute the request and receives the response. The response body is read using the ioutil.ReadAll function and printed to the console.

It’s important to note that the http package is low-level and does not provide advanced features like automatic retries, connection pooling, and redirect handling out of the box, for that you can use third-party libraries such as go-http-client or httpclient that provide these functionalities.

It’s also worth mentioning that for more advanced usage like handling authentication, cookies, and other headers, you can use the net/http package with net/http/cookiejar and net/http/httputil packages, or use third-party libraries such as gorequest or resty.

Wrapping Up

In conclusion, Golang provides a robust and efficient platform for network programming. Its built-in support for concurrency, a wide range of network protocols, and a comprehensive standard library make it a great choice for developing networked applications of all types. The examples provided in this article should give you a good starting point for exploring the capabilities of Golang for network programming.

You can find more about Evendyne here.

--

--

Mike Dyne
Evendyne

I write articles in various software engineering topics. Read every story from me by subscribing here: https://medium.com/@mikedyne/membership