欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang實(shí)現(xiàn)簡(jiǎn)單http服務(wù)器的示例詳解

 更新時(shí)間:2023年03月20日 15:26:53   作者:starrySky  
這篇文章主要為大家詳細(xì)介紹了如何利用Golang實(shí)現(xiàn)簡(jiǎn)單http服務(wù)器,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Golang有一定的幫助,需要的可以參考一下

一、基本描述

完成一個(gè)http請(qǐng)求的處理和響應(yīng),主要有以下幾個(gè)步驟:

  • 監(jiān)聽(tīng)端口
  • 建立連接
  • 解析http請(qǐng)求
  • 處理請(qǐng)求
  • 返回http響應(yīng)

完成上面幾個(gè)步驟,便能夠?qū)崿F(xiàn)一個(gè)簡(jiǎn)單的http服務(wù)器,完成對(duì)基本的http請(qǐng)求的處理

二 、具體方法

2.1 連接的建立

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

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

可以運(yùn)行這段代碼,然后在瀏覽器對(duì)本地8080端口發(fā)送請(qǐng)求,該程序能夠讀取到瀏覽器發(fā)送過(guò)來(lái)的http請(qǐng)求體數(shù)據(jù)。

當(dāng)通過(guò)Accept方法獲取到連接后,能夠使用該連接和客戶端進(jìn)行通信,該連接實(shí)現(xiàn)了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
}

能夠通過(guò)調(diào)用Read方法從客戶端讀取數(shù)據(jù),使用Write方法往客戶端返回?cái)?shù)據(jù)。

2.2 http請(qǐng)求解析

當(dāng)和客戶端建立連接后,同時(shí)也能夠讀取到客戶端發(fā)送過(guò)來(lái)的請(qǐng)求,此時(shí)要處理http請(qǐng)求的話,此時(shí)是需要解析出http請(qǐng)求體的,然后才能對(duì)http請(qǐng)求進(jìn)行處理。接下來(lái)我們看一下一個(gè)http請(qǐng)求例子:

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

接下來(lái)對(duì)HTTP請(qǐng)求體來(lái)進(jìn)行分析,第一行是請(qǐng)求行,包含請(qǐng)求的方法,請(qǐng)求URI,以及HTTP版本。下面這個(gè)例子中,請(qǐng)求方法是GET,請(qǐng)求URI是/ping,HTTP版本是1.1。

GET /ping HTTP/1.1

請(qǐng)求行到空行之間的內(nèi)容便是請(qǐng)求頭部,每一個(gè)頭部字段都有其對(duì)應(yīng)的作用,比如Connection首部字段,這里值為keep-alive,這里的作用是告訴服務(wù)器,這個(gè)連接要處理多個(gè)http請(qǐng)求,不要處理完一個(gè)http請(qǐng)求就把連接斷開(kāi)了。

而且一個(gè)http請(qǐng)求首部字段,是可以有多個(gè)對(duì)應(yīng)的值的,多個(gè)值之間用逗號(hào)隔開(kāi)。

Cache-Control: public, max-age=31536000

第三部分的內(nèi)容為請(qǐng)求體,也就是空行之后直到整個(gè)http請(qǐng)求的結(jié)束。可以看下面例子,請(qǐng)求體的內(nèi)容是hello world。實(shí)際上GET請(qǐng)求是不應(yīng)該有請(qǐng)求體的內(nèi)容的,此處是我手動(dòng)加進(jìn)去的,只是為了方便展示使用。

GET /ping HTTP/1.1
....(省略http請(qǐng)求體部分首部字段)
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

當(dāng)我們了解完http請(qǐng)求體的結(jié)構(gòu),接下來(lái)可以編寫(xiě)代碼來(lái)解析http請(qǐng)求體。

我們定義一個(gè)Conn結(jié)構(gòu)體,由Conn完成數(shù)據(jù)的讀取和HTTP請(qǐng)求體的解析,Conn定義如下:

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

同時(shí)解析出來(lái)的HTTP請(qǐng)求,也需要有個(gè)結(jié)構(gòu)體來(lái)存儲(chǔ)這部分?jǐn)?shù)據(jù),Request定義如下,這里暫時(shí)只支持GET請(qǐng)求,所以并沒(méi)有保存請(qǐng)求體的內(nèi)容

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

接下來(lái)由Conn完成HTTP請(qǐng)求體的解析,然后將解析的結(jié)果存儲(chǔ)到Request對(duì)象當(dāng)中。只需要根據(jù)HTTP請(qǐng)求體結(jié)構(gòu)來(lái)進(jìn)行解析即可,具體邏輯如下:

func (c *Conn) readRequest() (*Request, error) {
   req := NewRequest()
   // 現(xiàn)在只支持Get請(qǐng)求,讀取第一行內(nèi)容
   // GET /ping HTTP1.1
   line, err := c.br.ReadBytes('\n')
   if err != nil {
      return req, err
   }
   var f []string
   // 按空格來(lái)進(jìn)行分割,將請(qǐng)求行分割為三部分
   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]
   // 解析請(qǐng)求體首部字段
   for {   
      line, err = c.br.ReadBytes('\n')
      if err != nil {
         return nil, err
      }
      // 當(dāng)讀取到空行時(shí),說(shuō)明已經(jīng)首部字段已經(jīng)讀取完了
      if len(strings.TrimSpace(string(line))) == 0 {
         break
      }
      //舉例,讀取connection: keep-alive,獲取第一個(gè)空格的下標(biāo)
      i := bytes.IndexByte(line, ' ')
      if i == -1 {
         return nil, errors.New("header is error")
      }
      // 此時(shí)獲取到請(qǐng)求首部key,為connection
      key := string(line[:i-1])
      // 讀取到對(duì)應(yīng)的值,這里讀取到keep-alive
      value := strings.TrimSpace(string(line[i:]))
      // 簡(jiǎn)單讀取頭部字段即可
      req.Header[key] = value
   }
}

2.3 http請(qǐng)求處理

此時(shí)已經(jīng)獲取到HTTP請(qǐng)求了,之后需要對(duì)HTTP請(qǐng)求來(lái)進(jìn)行處理,這里可以先簡(jiǎn)單進(jìn)行處理,根據(jù)不同的請(qǐng)求執(zhí)行不同的處理邏輯:

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

2.4 http請(qǐng)求響應(yīng)

當(dāng)http請(qǐng)求處理完成之后,需要將返回一個(gè)處理結(jié)果返回給客戶端,有時(shí)候還需要返回一些數(shù)據(jù)給客戶端,這里返回的數(shù)據(jù)需要符合HTTP響應(yīng)體的結(jié)構(gòu),接下來(lái)我們看看HTTP響應(yīng)體的結(jié)構(gòu)

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響應(yīng)數(shù)據(jù)

可以看到,HTTP響應(yīng)體和請(qǐng)求體結(jié)構(gòu)類似,當(dāng)需要返回?cái)?shù)據(jù)給客戶端時(shí),需要按照HTTP協(xié)議定義好的響應(yīng)體結(jié)構(gòu)來(lái)進(jìn)行返回,這樣客戶端才能夠正確解析。

為了方便使用,構(gòu)造HTTP響應(yīng)體結(jié)構(gòu)這部分邏輯應(yīng)該由Conn對(duì)象來(lái)承載,由Conn對(duì)象提供一個(gè)Write方法,當(dāng)需要返回?cái)?shù)據(jù)時(shí),只需要調(diào)用Write方法寫(xiě)入要返回的數(shù)據(jù)即可,不需要去操心去構(gòu)造HTTP響應(yīng)體的內(nèi)容,Writer方法具體邏輯如下:

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

// 返回響應(yīng)行
// 構(gòu)造響應(yīng)行 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)
   }
   // 寫(xiě)入數(shù)據(jù) HTTP1.1 200 OK\r\n
   c.bw.WriteString(proto + " " + strconv.Itoa(status) + " " + text + "\r\n")
   // 寫(xiě)入空行
   c.bw.Write("\r\n")
   return nil
}
// 寫(xiě)入響應(yīng)數(shù)據(jù)
func (c *Conn) WriteData(data []byte) error {
   // 還沒(méi)寫(xiě)入請(qǐng)求頭 
   if !c.wroteHeader {
       //默認(rèn)狀態(tài)碼是200 OK
       c.writeHeader(StatusOK)
   }
   c.bw.Write(data)
   return nil
}

三、完整示例

func main() {
   // 對(duì)8080端口進(jìn)行監(jiān)聽(tīng)
   l, _ := net.Listen("tcp", ":8080")
   // 獲取和端口8080完成三次握手的tcp連接
   conn, _ := l.Accept()
   // 獲取到conn連接
   c := NewConn(conn)
   // 讀取到請(qǐng)求體
   req, _ := c.readRequest()
   if request.uri == "hello" {
      c.WriteData("hello")
   }else{
      c.WriteData("hello world")
   }
   // 在最后,需要將緩沖區(qū)的數(shù)據(jù)進(jìn)行清空
   c.bw.Flush()
   // 因?yàn)轫憫?yīng)沒(méi)有設(shè)置content-length,所以只有連接斷開(kāi)后,
   // 瀏覽器才知道數(shù)據(jù)讀取完成了,此處需要斷開(kāi)連接
   c.rwc.Close()
 }

到此為止,一個(gè)簡(jiǎn)單的HTTP服務(wù)器已經(jīng)實(shí)現(xiàn)了,能夠?qū)崿F(xiàn)簡(jiǎn)單的HTTP請(qǐng)求的解析和響應(yīng)。

以上就是Golang實(shí)現(xiàn)簡(jiǎn)單http服務(wù)器的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang http服務(wù)器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go 泛型和非泛型代碼詳解

    Go 泛型和非泛型代碼詳解

    Go 在 1.17 中支持泛型,但是默認(rèn)未開(kāi)啟;1.18 中會(huì)正式支持泛型,下面文章內(nèi)容小編將給大家講解Go 語(yǔ)言中的泛型和非泛型并且附上代碼詳解,剛興趣的小伙伴請(qǐng)參考下面文章的具體內(nèi)容
    2021-10-10
  • Go語(yǔ)言中的Array、Slice、Map和Set使用詳解

    Go語(yǔ)言中的Array、Slice、Map和Set使用詳解

    這篇文章主要介紹了Go語(yǔ)言中的Array、Slice、Map和Set使用詳解,本文給出了它們的創(chuàng)建、使用、多維等代碼實(shí)例,需要的朋友可以參考下
    2014-10-10
  • go 生成器模式的具體使用

    go 生成器模式的具體使用

    生成器是一種創(chuàng)建型設(shè)計(jì)模式,使你能夠分步驟創(chuàng)建復(fù)雜對(duì)象,本文主要介紹了go生成器模式的具體使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • Go語(yǔ)言區(qū)別于其他語(yǔ)言的特性

    Go語(yǔ)言區(qū)別于其他語(yǔ)言的特性

    在本文中,今天這篇文章將給大家介紹一下 Go 與其他語(yǔ)言不同的 9 個(gè)特性,需要的朋友可以參考下面文章的具體內(nèi)容
    2021-10-10
  • GO語(yǔ)言延遲函數(shù)defer用法分析

    GO語(yǔ)言延遲函數(shù)defer用法分析

    這篇文章主要介紹了GO語(yǔ)言延遲函數(shù)defer用法,較為詳細(xì)的分析了GO語(yǔ)言的特性與具體用法,并給出了一個(gè)比較典型的應(yīng)用實(shí)例,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2014-12-12
  • golang coroutine 的等待與死鎖用法

    golang coroutine 的等待與死鎖用法

    這篇文章主要介紹了golang coroutine 的等待與死鎖用法詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-05-05
  • golang 實(shí)現(xiàn)json類型不確定時(shí)的轉(zhuǎn)換

    golang 實(shí)現(xiàn)json類型不確定時(shí)的轉(zhuǎn)換

    這篇文章主要介紹了golang 實(shí)現(xiàn)json類型不確定時(shí)的轉(zhuǎn)換操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-01-01
  • Golang 實(shí)現(xiàn)獲取當(dāng)前函數(shù)名稱和文件行號(hào)等操作

    Golang 實(shí)現(xiàn)獲取當(dāng)前函數(shù)名稱和文件行號(hào)等操作

    這篇文章主要介紹了Golang 實(shí)現(xiàn)獲取當(dāng)前函數(shù)名稱和文件行號(hào)等操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-05-05
  • golang如何使用gomobile進(jìn)行Android開(kāi)發(fā)

    golang如何使用gomobile進(jìn)行Android開(kāi)發(fā)

    golang可以開(kāi)發(fā)android,使用golang開(kāi)發(fā)android需要下載安裝gomobile,下面這篇文章主要給大家介紹了關(guān)于golang如何使用gomobile進(jìn)行Android開(kāi)發(fā)的相關(guān)資料,需要的朋友可以參考下
    2023-01-01
  • Golang中優(yōu)秀的消息隊(duì)列NSQ基礎(chǔ)安裝及使用詳解

    Golang中優(yōu)秀的消息隊(duì)列NSQ基礎(chǔ)安裝及使用詳解

    這篇文章主要介紹了Golang中優(yōu)秀的消息隊(duì)列NSQ基礎(chǔ)安裝及使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12

最新評(píng)論