SoFunction
Updated on 2025-03-04

Detailed explanation of Golang's example of implementing a simple http server

1. Basic description

To complete the processing and response of an http request, there are mainly the following steps:

  • Listen to port
  • Establish a connection
  • Parsing http requests
  • Processing a request
  • Return http response

Complete the above steps to implement a simple http server and complete the processing of basic http requests

2. Specific methods

2.1 Connection establishment

There are two methods under the Go net package that provide Listen and Accept, which can complete the connection establishment. You can take a brief look at the example:

func main() {
   // Listen to port 8080   l, _ := ("tcp", ":8080")
   // Get the tcp connection with port 8080 and complete three-handshake   conn, _ := ()
   // You can use this connection to communicate with the client at this time   data := make([]byte, 4096)
   // You can read the data buffer from conn   (data)
   // Print the buffer data   print(string(data))
}

You can run this code and then send a request to the local port 8080 in the browser. The program can read the http request body data sent by the browser.

After obtaining the connection through the Accept method, the connection can be used to communicate with the client. The connection implements the interface. The specific interface is defined as follows:

type Conn interface {
   // Read reads data from the connection.
   // Read can be made to time out and return an error after a fixed
   // time limit; see SetDeadline and SetReadDeadline.
   Read(b []byte) (n int, err error)

   // Write writes data to the connection.
   // Write can be made to time out and return an error after a fixed
   // time limit; see SetDeadline and SetWriteDeadline.
   Write(b []byte) (n int, err error)

   // Close closes the connection.
   // Any blocked Read or Write operations will be unblocked and return errors.
   Close() error
}

You can read data from the client by calling the Read method and return data to the client using the Write method.

2.2 http request parsing

After establishing a connection with the client, the request sent by the client can also be read. If you want to process the http request, you need to parse out the http request body before you can process the http request. Next, let's take a look at an example of http request:

GET /ping HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
\r\n(blank line)
hello world

Next, the HTTP request body is analyzed. The first line is the request line, which contains the request method, the request URI, and the HTTP version. In the following example, the request method is GET, the request URI is /ping, and the HTTP version is 1.1.

GET /ping HTTP/1.1

The content between the request line and the blank line is the request header. Each header field has its corresponding function, such as the Connection header field, which is kept-alive. The function here is to tell the server that this connection must handle multiple http requests, and do not disconnect the connection after processing an http request.

Moreover, a http request header field can have multiple corresponding values, separated by commas.

Cache-Control: public, max-age=31536000

The content of the third part is the request body, that is, after the blank line, until the end of the entire http request. You can see the following example. The content of the request body is hello world. In fact, GET requests should not have the content of the request body. I added it manually here just to facilitate display and use.

GET /ping HTTP/1.1
.... (Omit the header field of the http request body part)
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
\r\n(blank line)
hello world

When we understand the structure of the http request body, we can write code to parse the http request body.

We define a Conn structure, which can complete the reading of data and parsing of HTTP request bodies. Conn is defined as follows:

type Conn struct {
   rwc     
   // Provides caching functions and some functions to facilitate us to parse HTTP request bodies   br      *
   bw      *
   // Whether the response header has been written   wroteHaeder bool
 }
 func NewConn(rwc ) *Conn {
   return &Conn{
      rwc:     rwc,
      br:      (rwc),
      bw:      (rwc),
   }
}

At the same time, the parsed HTTP request also requires a structure to store this part of the data. The Request is defined as follows. Only GET requests are supported for the time being, so the content of the request body is not saved.

type Request struct {
   // Storage request method, the above example is GET   method string
   // URI used to store requests   uri    string
   // Used to store http version   proto  string
   // http header field   Header map[string]string
}

Next, Conn completes the parsing of the HTTP request body, and then stores the parsed results in the Request object. You only need to parse according to the HTTP request body structure. The specific logic is as follows:

func (c *Conn) readRequest() (*Request, error) {
   req := NewRequest()
   // Now only Get request is supported, and the first line of content is read   // GET /ping HTTP1.1
   line, err := ('\n')
   if err != nil {
      return req, err
   }
   var f []string
   // Split by spaces, split the request line into three parts   if f = (string(line), " "); len(f) != 3 {
      return req, ("http Header error")
   }
   // Get GET, /ping, HTTP/1.1   , ,  = f[0], f[1], f[2]
   // parse the request body header field   for {   
      line, err = ('\n')
      if err != nil {
         return nil, err
      }
      // When reading a blank line, it means that the first field has been read.      if len((string(line))) == 0 {
         break
      }
      //For example, read connection: keep-alive, get the subscript of the first space      i := (line, ' ')
      if i == -1 {
         return nil, ("header is error")
      }
      // At this time, the request header key is obtained, which is the connection      key := string(line[:i-1])
      // Read the corresponding value, read the keep-alive here      value := (string(line[i:]))
      // Just simply read the header field      [key] = value
   }
}

2.3 http request processing

At this time, the HTTP request has been obtained, and the HTTP request needs to be processed later. Here you can simply process it first and execute different processing logics according to different requests:

func main() {
   // Listen to port 8080   l, _ := ("tcp", ":8080")
   // Get the tcp connection with port 8080 and complete three-handshake   conn, _ := ()
   // Get the conn connection   c := NewConn(conn, handler)
   // Read the request body   req, _ := ()
   if  == "/hello" {
     ....
   }else{
     .... 
   }
 }

2.4 http request response

After the http request is processed, a process result needs to be returned to the client. Sometimes some data needs to be returned to the client. The data returned here needs to conform to the structure of the HTTP response body. Next, let's take a look at the structure of the HTTP response body.

HTTP/1.1 200 OK
Server: CloudWAF
Date: Sun, 04 Dec 2022 02:29:27 GMT
Content-Type: text/html;charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Content-Language: zh-CN
Strict-Transport-Security: max-age= 31536000
Content-Encoding: gzip
\r\n(blank line)
xxxx response data

It can be seen that the HTTP response body is similar to the request body structure. When data needs to be returned to the client, it is necessary to return the response body structure defined by the HTTP protocol so that the client can parse correctly.

For convenience of use, the logic of constructing the HTTP response body structure should be carried by the Conn object, and a Write method is provided by the Conn object. When data needs to be returned, you only need to call the Write method to write the data to be returned. There is no need to worry about constructing the content of the HTTP response body. The specific logic of the Write method is as follows:

const (
   StatusOK = 200
)
var statusText = map[int]string{
   StatusOK:                   "OK",
}

// Return to the response line// Construct the response line HTTP/1.1 200 OKfunc (c *Conn) writeHeader(status int) error {
   if  {
      return ("code has been set")
   }
    = true
   var proto string
   //GET /hello HTTP/1.1
   proto = "HTTP/1.1"
   // Get the text description, here is OK   text, ok := statusText[status]
   if !ok {
      text = "status code " + (status)
   }
   // Write data HTTP1.1 200 OK\r\n   (proto + " " + (status) + " " + text + "\r\n")
   // Write to empty lines   ("\r\n")
   return nil
}
// Write response datafunc (c *Conn) WriteData(data []byte) error {
   // The request header has not been written yet   if ! {
       //The default status code is 200 OK       (StatusOK)
   }
   (data)
   return nil
}

3. Complete example

func main() {
   // Listen to port 8080   l, _ := ("tcp", ":8080")
   // Get the tcp connection with port 8080 and complete three-handshake   conn, _ := ()
   // Get the conn connection   c := NewConn(conn)
   // Read the request body   req, _ := ()
   if  == "hello" {
      ("hello")
   }else{
      ("hello world")
   }
   // At the end, the buffer data needs to be cleared   ()
   // Because the response does not set content-length, only after the connection is disconnected.   // The browser only knows that the data reading has been completed, so you need to disconnect here   ()
 }

Up to this point, a simple HTTP server has been implemented, which can implement the parsing and response of simple HTTP requests.

The above is a detailed explanation of the example of Golang implementing a simple http server. For more information about Golang http server, please follow my other related articles!