Go Basic: Understanding HTTP Client, HTTP Server, Context, Processes, Signals, Exit in Go

Covering networking concepts, HTTP communication, and process management.

full lessons here👇:
https://programmerscareer.com/golang-basic-skill/

Generated by AI, there may be errors, for reference only

Topic: 0.1 Understanding of Basic Networking

Starting from basics often makes the learning process more productive. Even if you’re already familiar with the subject matter, a quick refresher can help reinforce your understanding.

Computer networks are comprised of many computers that are connected to each other for the purpose of sharing resources and data.

The most common types of computer networks are:

  • Local Area Network (LAN): A network that is confined to a relatively small area. It’s usually confined to a single building, such as a home, office, or a group of buildings.
  • Wide Area Network (WAN): A network that spans a large physical distance. The internet is a WAN.

A basic understanding of networking protocols is also crucial. A protocol is a set of rules that dictate how data should be transferred over the network.

Internet Protocol(IP), Transmission Control Protocol(TCP), and User Datagram Protocol(UDP) are some of the fundamental protocols you must understand.

In Go, the ‘net’ package presents a general interface for network I/O, including TCP/IP, UDP, domain name resolution, and Unix domain sockets.

These networking concepts will be the foundation as we venture deeper into how Go handle HTTP communication, and manage processes.

Topic: 0.2 Introduction to the Golang Net Package

Go’s standard library comes with excellent support for HTTP. The net/httppackage provides functionalities for building HTTP clients and servers. However, before diving into HTTP, the foundational package that deals with network calls is ‘net’.

The ‘net’ package in Go provides a portable interface for network I/O, including TCP/IP, UDP, domain name resolution, and Unix domain sockets.

Let’s take a quick look at functions provided by the ‘net’ package that we will use:

  1. Dial: The Dial function connects to the address on the named network.
1
2
3
4
conn, err := net.Dial("tcp", "golang.org:80")  
if err != nil {
log.Fatal(err)
}
  1. Listen: Listen announces on the local network address. You would use this to create a server that listens for incoming connections.
1
2
3
4
ln, err := net.Listen("tcp", ":8080")  
if err != nil {
log.Fatal(err)
}
  1. Accept: And this is how you wait for and return the next connection to the listener.
1
2
3
4
5
conn, err := ln.Accept()  
if err != nil {
log.Fatal(err)
}
// handle the connection

Don’t worry if it seems overwhelming the first time. In the next sections, we’ll dive into more specifics and play around with these functions a lot more.

Topic: 0.3 Introduction to HTTP

HTTP stands for Hypertext Transfer Protocol, which is the foundation for any data exchange on the Web. HTTP is a protocol used globally for transmission of hypertext over the internet.

The key feature of HTTP includes:

  1. Statelessness: HTTP is stateless, which means that each client request is an independent one. Server and client do not preserve data between different requests.
  2. Media Independent: As long as a client can interpret the data, any type of media file can be sent by HTTP.

HTTP works as a request-response protocol between a client and a server, including browsers, mobile applications, and other servers are the typical HTTP clients. Whenever an HTTP request is made by a client, the server responds with an HTTP response.

In Go, creating an HTTP server is made easy with its standard ‘net/http’ package by granting us the ListenAndServe function.

We can also make HTTP requests via the ‘http.Client’, ‘http.NewRequest’ and ‘http.Do’ commands.

Go features a really powerful http package that provides all the necessary utilities needed to run HTTP servers or make client-side HTTP requests. We’ll examine how to work with Go’s ‘http’ package in the upcoming segments as we go hands-on with code and examples.

Topic: 1.1 HTTP Client

Go’s ‘net/http’ package provides methods for performing HTTP requests. In this first part of our HTTP module, we will cover the basics of sending GET requests, which are one of the most common types of HTTP requests.

Here is a basic example of sending a GET request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main  

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

func main() {
resp, err := http.Get("http://example.com/")
if err != nil {
fmt.Printf("Error making the request: %v", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading the response: %v", err)
return
}
fmt.Println(string(body))
}

In this example, we’re making a GET request to “http://example.com“. The http.Get function makes a request to the provided URL and returns the response and any errors.

The resp.Body contains the server’s response to the request. The ioutil.ReadAll function reads all the data from the response body. We then print the body of the response to the console.

The defer resp.Body.Close() line is crucial. It ensures that the network connection established for the request will be closed after the function finishes, not immediately. This is a good practice because reusing connections in the pool improves performance.

I hope you found this start to our HTTP Client lesson helpful. Remember, practice is beneficial so feel free to try coding a GET request to familiarize yourself with the process.

When we send an HTTP request, we receive an HTTP response. An HTTP response is made of the following parts:

  1. Status line: It includes the HTTP version, a status code, and a status message.
  2. Headers: They provide more information about the response or the request. In Go, they are available as a map[string][]string in the Headerfield of the http.Response struct.
  3. Body: This is the actual data (content) returned by the server.

When we make a request with http.Get(), we get a pointer to an http.Response. Here’s an example of how to access these fields:

1
2
3
4
5
resp, _ := http.Get("http://example.com/")  
fmt.Println("HTTP version:", resp.Proto)
fmt.Println("Status code:", resp.StatusCode)
fmt.Println("Status:", resp.Status)
fmt.Println("Headers:", resp.Header)

It’s important to remember, as before, to close the response body when you’re done with it:

1
defer resp.Body.Close()

With all this new knowledge, you can inspect your HTTP responses more accurately when creating a client in Go.

Topic: 1.2 HTTP Server

In Go, we can build a robust web server using the net/http package. We can create RESTful services, serve static files, use dynamic routes and the list goes on. For the sake of this lesson, let’s focus on creating a simple web server.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main  

import (
"fmt"
"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to Mr. Ranedeer's Go Tutorial, %s!", r.URL.Path[1:])
}

func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}

In this simple example, ‘http.HandleFunc’ tells the http package to handle all requests to the web root (“/”) with ‘handler’.

The ‘handler’ function receives two parameters: an ‘http.ResponseWriter’ and an ‘http.Request’. The ‘http.ResponseWriter’ is where you write your text/html response to and ‘http.Request’ is the data structure that represents the client HTTP request.

‘fmt.Fprintf’ then assembles the HTTP server’s response; in this case, it’s just a string that we send down to the client.

Finally, ‘http.ListenAndServe’: This function listens at the specified port until the program is terminated. In our case it’s port 8080.

In reality, servers are not often used for sending a “Hello, World” message. Instead, they are used to send dynamic contents that change based on specific situations or parameters.

To illustrate this, we’re going to look at a simple example of how to handle HTTP POST requests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Global variable to simulate a database.  
var database []string

func formHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
html := `<form method="post" action="/submit">
Name: <input type="text" name="name">
<input type="submit" value="Submit">

</form>`
w.Write([]byte(html))
case "POST":
r.ParseForm()
// Logic part to store into database
database = append(database, r.Form.Get("name"))
}
}

func submitHandler(w http.ResponseWriter, r *http.Request) {
html := "Received POST data: <br/><br/>"
html += "<ul style='list-style:none'>"
for _, item := range database {
html += "<li>"
html += item
html += "</li><br/>"
}
html += "</ul>"
w.Write([]byte(html))
}

func main() {
server := http.Server{
Addr: ":8080",
}
http.HandleFunc("/form", formHandler)
http.HandleFunc("/submit", submitHandler)
server.ListenAndServe()
}

The code creates a form asking for a name. When you submit the form, it makes a POST request to ‘/submit’. This request handler retrieves the name from POST data and stores it.

Topic: 1.3 Context Package in Go

The context package in Go is typically used in routines that may need to be canceled or timed out. A common theme during concurrent programming in Go is the ability to manage long-lived and short-lived worker routines. The need arises in scenarios like processing streams of data that can last indefinitely or managing automated tasks that can be triggered to stop on certain conditions such as a calculated result being reached, an error occurring, prerequisites not being met, etc.

The context package provides a standardized interface to define parent-child relationships between routines, and propagates deadlines, cancellation signals, and other information required to handle the tasks effectively.

Let’s take a look at an example of how contexts can be used:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main  

import (
"fmt"
"context"
"time"
)

func main() {
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
go func(ctx context.Context) {
for {
select {
case <-time.After(1 * time.Second):
fmt.Println("working")
case <-ctx.Done():
fmt.Println("canceled")
return
}
}
}(ctx)
time.Sleep(4 * time.Second)
}

In this example, we initialized a new background context ctx and then created a new context that is automatically canceled after 3 seconds using context.WithTimeout(ctx, 3*time.Second). The cancel function returned by context.WithTimeout is called using defer to ensure resources are cleaned up properly.

The function go func(ctx context.Context)represents a worker routine that performs a task every second indefinitely until a cancel signal is received. In this case, it’s canceled when the context timeout of 3 seconds is reached.

Topic: 1.4 Processes in Go

In the computing world, a process is an instance of a computer program that is executing. It contains the program code and its current activity. In Go, a process can be started by using the ‘os/exec’ package.

Here’s a simple code snippet that shows how to start a process in Go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main  

import (
"fmt"
"os/exec"
)
func main() {
cmd := exec.Command("ls")
output, err := cmd.Output()
if err != nil {
fmt.Println("An error occurred: ", err)
}
fmt.Println(string(output))
}

Here’s what happening in this code:

  • exec.Command("ls") prepares a command to execute. It’s like typing ls in a terminal but it’s not running the command yet.
  • cmd.Output() actually runs the command and waits for it to finish. It returns the standard output and a value of Go’s built-in interface type ‘error’.

This program will therefore print the contents of your current directory, somewhat like the ls command in Unix-based systems.

Go offers a powerful set of tools for process management. In simple terms, process management in any language involves keeping track of and controlling the processes we’ve started. It’s all about knowing what’s happening with your program.

Let’s consider this example where we start a process (Sleep 5 seconds), then stop it before it completes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main  

import (
"fmt"
"os/exec"
"time"
)

func main() {
cmd := exec.Command("sleep", "5")
if err := cmd.Start(); err != nil {
fmt.Printf("Error starting command: %v\n", err)
return
}
timer := time.AfterFunc(2*time.Second, func() {
err := cmd.Process.Kill()
if err != nil {
fmt.Printf("Error killing process: %v\n", err)
return
}
})
err := cmd.Wait()
timer.Stop()
if err != nil {
fmt.Printf("Command finished with error: %v\n", err)
}
}

In this script:

  • cmd.Start() starts the process, but it does not wait for it to complete.
  • time.AfterFunc creates a timer that executes a function after a certain duration (2 seconds in this case).
  • cmd.Process.Kill() forcibly stops the process. Note that there’s also a graceful cmd.Process.Signal function that allows the process to cleanup before it exits.
  • cmd.Wait() waits for the process to finish.

Topic 1.5: Signals in Go

In an operating system, a signal is a software interrupt delivered to a process. A process can use these signals to trigger certain behaviors. For example, when a program is running in a terminal, hitting ctrl+c sends an interrupt signal, usually causing the program to terminate.

In Go, we can decide what to do when our programs receive signals using the os/signal package. This can allow us to gracefully stop a program, performing any cleanup that might be required before stopping.

Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main  

import (
"fmt"
"os"
"os/signal"
"syscall"
)

func main() {
signals := make(chan os.Signal, 1)
done := make(chan bool, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-signals
fmt.Println()
fmt.Println(sig)
done <- true
}()
fmt.Println("waiting for signal")
<-done
fmt.Println("exiting")
}

In this program:

  • We first create a channel to receive signals.
  • We use signal.Notify to relay incoming signals to the channel. We’re specifically looking for SIGINT(sent by pressing ctrl+c) and SIGTERM(a termination request sent to the program).
  • When a signal is received, it’s printed out and then “true” is sent over the done channel to indicate that we’re ready to exit.

This way, interruption can be gracefully handled by saving progression, freeing resources, finishing important operations, or asking for user confirmation before exiting.

Topic 1.6: Clean Exit in Go

In any properly structured program, there’s always the requirement to free up resources that the program has consumed before it ends. These resources may include opened files, database connections, network connections, or allocated memory. Go provides us with the defer statement which helps us in automatically managing this.

The key idea behind the defer statement is that it allows us to delay the execution of a function until the enclosing function returns, no matter how it returns.

Consider this common situation wherein we open a file, perform some operation on it, and then close it:

1
2
3
4
5
6
func main() {  
f, _ := os.Open("myfile.txt")
// Perform operations on the file
// …
f.Close()
}

Now, let’s suppose an error occurs during the file operations and we return from the function prematurely. In this situation, the Close function will never be called, leading to a file descriptor leak. This is where defer can come into play:

1
2
3
4
5
6
7
func main() {  
f, _ := os.Open("myfile.txt")
defer f.Close()
// Perform operations on the file
// …
// the file will automatically be closed now
}

In the revised version, f.Close() will be executed every time the function encloses, no matter where it encloses. This ensures that we’re not leaving any resources hanging.

Topic: 1.7 Review and Practice

Let’s have a brief recap:

  • HTTP Client: We journeyed through making HTTP requests using the ‘net/http’ standard library in Go, and learned how to get and post data.
  • HTTP Server: We delved into how to design an HTTP server and handle incoming requests.
  • Context in Go: We discovered how to handle and manage long-running processes using the context package.
  • Processes in Go: We took a peek at how to create and manage processes in Go.
  • Signals in Go: We explored handling system signals for a graceful shutdown of the application.
  • Clean Exit in Go: We learned about resource cleanup before application termination, and you made acquaintance with defer keyword.

Now, to put this knowledge into practice, let’s work on a couple of exercises:

  1. Create an HTTP server in Go which listens on port 8080 and responds to the /ping route with a Pong!.
  2. Create an HTTP client in Go which sends an HTTP GET request to an API endpoint.
  3. Write a Go function, which runs an infinite loop, but exists gracefully on receiving a shutdown signal (SIGINT or SIGTERM).
  4. Develop a simple Go script which opens a file, writes something to it, and then closes it, ensuring that the file gets closed even if an error occurs during the write operation.

If you encounter any issues while solving these exercises or if you want to clarify something we’ve studied, please don’t hesitate to ask.

Topic: 1.8 Assessments

It’s essential to know precisely where you stand in order to improve. To this end, we’ve prepared a unique set of questions for you to answer. Some may be challenging, but we’re confident you can handle them.

Questions:

  1. You are required to write a REST API in Go. This API consists of a GETendpoint that retrieves a user’s data from the database. Describe how you would design this endpoint.
  2. How would you handle timeouts on long-running http requests in Go?
  3. Suppose your Go application has a long-running Goroutine that needs to be stopped under certain conditions. How would you design your code to stop this Goroutine gracefully?
  4. What happens if your Go application receives an SIGKILL signal? Can you handle such a signal in your code?
  5. You have an open file in your Go application. Now due to some unexpected error, your application panics. How would you ensure that the file is closed before the application exits?

Take your time to think carefully about each question and provide thoughtful answers. Remember, it’s not about speed; it’s about concept understanding and being thorough!

中文文章: https://programmerscareer.com/zh-cn/go-basic-15/
Author: Wesley Wei – Twitter Wesley Wei – Medium
Note: If you choose to repost or use this article, please cite the original source.

My Path to front-end learning: Medium Stats Insights version 1.1.2 Go Basic: Understanding Command Line, Environment Variables in Go

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×