91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Golang如何實現簡單http服務器

發布時間:2023-03-21 14:41:21 來源:億速云 閱讀:91 作者:iii 欄目:開發技術

這篇文章主要介紹“Golang如何實現簡單http服務器”,在日常操作中,相信很多人在Golang如何實現簡單http服務器問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Golang如何實現簡單http服務器”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

一、基本描述

完成一個http請求的處理和響應,主要有以下幾個步驟:

  • 監聽端口

  • 建立連接

  • 解析http請求

  • 處理請求

  • 返回http響應

完成上面幾個步驟,便能夠實現一個簡單的http服務器,完成對基本的http請求的處理

二 、具體方法

2.1 連接的建立

go中net包下有提供Listen和Accept兩個方法,可以完成連接的建立,可以簡單看下示例:

func main() {
   // 對8080端口進行監聽
   l, _ := net.Listen("tcp", ":8080")
   // 獲取和端口8080完成三次握手的tcp連接
   conn, _ := l.Accept()
   // 此時便能夠使用該連接和客戶端進行通信
   data := make([]byte, 4096)
   // 可以從conn讀取數據緩沖區當中
   conn.Read(data)
   // 將緩沖區的數據打印處理
   print(string(data))
}

可以運行這段代碼,然后在瀏覽器對本地8080端口發送請求,該程序能夠讀取到瀏覽器發送過來的http請求體數據。

當通過Accept方法獲取到連接后,能夠使用該連接和客戶端進行通信,該連接實現了net.Conn接口,具體接口的定義如下:

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
}

能夠通過調用Read方法從客戶端讀取數據,使用Write方法往客戶端返回數據。

2.2 http請求解析

當和客戶端建立連接后,同時也能夠讀取到客戶端發送過來的請求,此時要處理http請求的話,此時是需要解析出http請求體的,然后才能對http請求進行處理。接下來我們看一下一個http請求例子:

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(空行)
hello world

接下來對HTTP請求體來進行分析,第一行是請求行,包含請求的方法,請求URI,以及HTTP版本。下面這個例子中,請求方法是GET,請求URI是/ping,HTTP版本是1.1。

GET /ping HTTP/1.1

請求行到空行之間的內容便是請求頭部,每一個頭部字段都有其對應的作用,比如Connection首部字段,這里值為keep-alive,這里的作用是告訴服務器,這個連接要處理多個http請求,不要處理完一個http請求就把連接斷開了。

而且一個http請求首部字段,是可以有多個對應的值的,多個值之間用逗號隔開。

Cache-Control: public, max-age=31536000

第三部分的內容為請求體,也就是空行之后直到整個http請求的結束。可以看下面例子,請求體的內容是hello world。實際上GET請求是不應該有請求體的內容的,此處是我手動加進去的,只是為了方便展示使用。

GET /ping HTTP/1.1
....(省略http請求體部分首部字段)
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
\r\n(空行)
hello world

當我們了解完http請求體的結構,接下來可以編寫代碼來解析http請求體。

我們定義一個Conn結構體,由Conn完成數據的讀取和HTTP請求體的解析,Conn定義如下:

type Conn struct {
   rwc     net.Conn
   // bufio.Reader 提供了緩存的功能,以及一些函數,方便我們解析HTTP請求體
   br      *bufio.Reader
   bw      *bufio.Writer
   // 是否已經寫入響應頭
   wroteHaeder bool
 }
 func NewConn(rwc net.Conn) *Conn {
   return &Conn{
      rwc:     rwc,
      br:      bufio.NewReader(rwc),
      bw:      bufio.NewWriter(rwc),
   }
}

同時解析出來的HTTP請求,也需要有個結構體來存儲這部分數據,Request定義如下,這里暫時只支持GET請求,所以并沒有保存請求體的內容

type Request struct {
   // 存儲請求方法,上面例子便是GET 
   method string
   // 用于存儲請求的uri
   uri    string
   // 用于存儲http版本
   proto  string
   // http首部字段
   Header map[string]string
}

接下來由Conn完成HTTP請求體的解析,然后將解析的結果存儲到Request對象當中。只需要根據HTTP請求體結構來進行解析即可,具體邏輯如下:

func (c *Conn) readRequest() (*Request, error) {
   req := NewRequest()
   // 現在只支持Get請求,讀取第一行內容
   // GET /ping HTTP1.1
   line, err := c.br.ReadBytes('\n')
   if err != nil {
      return req, err
   }
   var f []string
   // 按空格來進行分割,將請求行分割為三部分
   if f = strings.Split(string(line), " "); len(f) != 3 {
      return req, errors.New("http Header error")
   }
   // 獲取到GET, /ping, HTTP/1.1
   req.method, req.url, req.proto = f[0], f[1], f[2]
   // 解析請求體首部字段
   for {   
      line, err = c.br.ReadBytes('\n')
      if err != nil {
         return nil, err
      }
      // 當讀取到空行時,說明已經首部字段已經讀取完了
      if len(strings.TrimSpace(string(line))) == 0 {
         break
      }
      //舉例,讀取connection: keep-alive,獲取第一個空格的下標
      i := bytes.IndexByte(line, ' ')
      if i == -1 {
         return nil, errors.New("header is error")
      }
      // 此時獲取到請求首部key,為connection
      key := string(line[:i-1])
      // 讀取到對應的值,這里讀取到keep-alive
      value := strings.TrimSpace(string(line[i:]))
      // 簡單讀取頭部字段即可
      req.Header[key] = value
   }
}

2.3 http請求處理

此時已經獲取到HTTP請求了,之后需要對HTTP請求來進行處理,這里可以先簡單進行處理,根據不同的請求執行不同的處理邏輯:

func main() {
   // 對8080端口進行監聽
   l, _ := net.Listen("tcp", ":8080")
   // 獲取和端口8080完成三次握手的tcp連接
   conn, _ := l.Accept()
   // 獲取到conn連接
   c := NewConn(conn, handler)
   // 讀取到請求體
   req, _ := c.readRequest()
   if request.uri == "/hello" {
     ....
   }else{
     .... 
   }
 }

2.4 http請求響應

當http請求處理完成之后,需要將返回一個處理結果返回給客戶端,有時候還需要返回一些數據給客戶端,這里返回的數據需要符合HTTP響應體的結構,接下來我們看看HTTP響應體的結構

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(空行)
xxxx響應數據

可以看到,HTTP響應體和請求體結構類似,當需要返回數據給客戶端時,需要按照HTTP協議定義好的響應體結構來進行返回,這樣客戶端才能夠正確解析。

為了方便使用,構造HTTP響應體結構這部分邏輯應該由Conn對象來承載,由Conn對象提供一個Write方法,當需要返回數據時,只需要調用Write方法寫入要返回的數據即可,不需要去操心去構造HTTP響應體的內容,Writer方法具體邏輯如下:

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

// 返回響應行
// 構造響應行 HTTP/1.1 200 OK
func (c *Conn) writeHeader(status int) error {
   if c.wroteHeader {
      return errors.New("code has been set")
   }
   c.wroteHeader = true
   var proto string
   //GET /hello HTTP/1.1
   proto = "HTTP/1.1"
   // 獲取文本描述,這里為OK
   text, ok := statusText[status]
   if !ok {
      text = "status code " + strconv.Itoa(status)
   }
   // 寫入數據 HTTP1.1 200 OK\r\n
   c.bw.WriteString(proto + " " + strconv.Itoa(status) + " " + text + "\r\n")
   // 寫入空行
   c.bw.Write("\r\n")
   return nil
}
// 寫入響應數據
func (c *Conn) WriteData(data []byte) error {
   // 還沒寫入請求頭 
   if !c.wroteHeader {
       //默認狀態碼是200 OK
       c.writeHeader(StatusOK)
   }
   c.bw.Write(data)
   return nil
}

三、完整示例

func main() {
   // 對8080端口進行監聽
   l, _ := net.Listen("tcp", ":8080")
   // 獲取和端口8080完成三次握手的tcp連接
   conn, _ := l.Accept()
   // 獲取到conn連接
   c := NewConn(conn)
   // 讀取到請求體
   req, _ := c.readRequest()
   if request.uri == "hello" {
      c.WriteData("hello")
   }else{
      c.WriteData("hello world")
   }
   // 在最后,需要將緩沖區的數據進行清空
   c.bw.Flush()
   // 因為響應沒有設置content-length,所以只有連接斷開后,
   // 瀏覽器才知道數據讀取完成了,此處需要斷開連接
   c.rwc.Close()
 }

到此,關于“Golang如何實現簡單http服務器”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

邛崃市| 秦安县| 仁化县| 阳新县| 湘乡市| 遂川县| 嘉善县| 耿马| 河源市| 寿光市| 繁昌县| 兰坪| 瑞金市| 伽师县| 东兰县| 东光县| 化州市| 黎平县| 石林| 滦南县| 东源县| 大理市| 台东县| 葵青区| 寻乌县| 灌南县| 阳江市| 涡阳县| 镶黄旗| 苏尼特右旗| 永昌县| 淳化县| 新邵县| 宜兴市| 清涧县| 双桥区| 崇义县| 蓝田县| 南川市| 延庆县| 颍上县|