<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Golang 實現(xiàn)一個簡單的 http 代理

          共 6863字,需瀏覽 14分鐘

           ·

          2021-07-22 08:20

          本文詳細介紹了Golang 實現(xiàn) http 代理的實現(xiàn),在實際業(yè)務中有需求的同學可以學起來了!

          代理是網(wǎng)絡中的一項重要的功能,其功能就是代理網(wǎng)絡用戶去取得網(wǎng)絡信息。形象的說:它是網(wǎng)絡信息的中轉(zhuǎn)站,對于客戶端來說,代理扮演的是服務器的角色,接收請求報文,返回響應報文;對于 web 服務器來說,代理扮演的是客戶端的角色,發(fā)送請求報文,接收響應報文。

          代理具有多種類型,如果是根據(jù)網(wǎng)絡用戶劃分的話,可以劃分為正向代理和反向代理:

          • 正向代理:將客戶端作為網(wǎng)絡用戶。客戶端訪問服務端時,先訪問代理服務器,隨后代理服務器再訪問服務端。此過程需客戶端進行代理配置,對服務端透明。
          • 反向代理:將服務端作為網(wǎng)絡用戶。訪問過程與正向代理相同,不過此過程對客戶端透明,需服務端進行代理配置(也可不配置)。

          針對正向代理和反向代理,分別有不同的代理協(xié)議,即代理服務器和網(wǎng)絡用戶之間通信所使用的協(xié)議:

          • 正向代理:
            • http
            • https
            • socks4
            • socks5
            • vpn:就功能而言,vpn 也可以被認為是代理
          • 反向代理:
            • tcp
            • udp
            • http
            • https

          接下來我們就說說 http 代理。

          http 代理概述

          http 代理是正向代理中較為簡單的代理方式,它使用 http 協(xié)議作為客戶端和代理服務器的傳輸協(xié)議。

          http 代理可以承載 http 協(xié)議,https 協(xié)議,ftp 協(xié)議等等。對于不同的協(xié)議,客戶端和代理服務器間的數(shù)據(jù)格式略有不同。

          http 協(xié)議

          我們先來看看 http 協(xié)議下客戶端發(fā)送給代理服務器的 HTTP Header:

          // 直接連接
          GET / HTTP/1.1
          Host: staight.github.io
          Connection: keep-alive

          // http 代理
          GET http://staight.github.io/ HTTP/1.1
          Host: staight.github.io
          Proxy-Connection: keep-alive

          可以看到,http 代理比起直接連接:

          • url 變成完整路徑,/->http://staight.github.io/
          • Connection字段變成Proxy-Connection字段
          • 其余保持原樣

          為什么使用完整路徑?

          為了識別目標服務器。如果沒有完整路徑,且沒有 Host 字段的話,代理服務器將無法得知目標服務器的地址。

          為什么使用 Proxy-Connection 字段代替 Connection 字段?

          為了兼容使用 HTTP/1.0 協(xié)議的過時的代理服務器。HTTP/1.1 才開始有長連接功能,直接連接的情況下,客戶端發(fā)送的 HTTP Header 中如果有Connection: keep-alive字段,表示使用長連接和服務端進行 http 通信,但如果中間有過時的代理服務器,該代理服務器將無法與客戶端和服務端進行長連接,造成客戶端和服務端一直等待,白白浪費時間。因此使用Proxy-Connection字段代替Connection字段,如果代理服務器使用 HTTP/1.1 協(xié)議,能夠識別Proxy-Connection字段,則將該字段轉(zhuǎn)換成Connection再發(fā)送給服務端;如果不能識別,直接發(fā)送給服務端,因為服務端也無法識別,則使用短連接進行通信。

          http 代理 http 協(xié)議交互過程如圖:

          http 代理 http 協(xié)議

          https 協(xié)議

          接下來我們來看看 https 協(xié)議下,客戶端發(fā)送給代理服務器的 HTTP Header:

          CONNECT staight.github.io:443 HTTP/1.1
          Host: staight.github.io:443
          Proxy-Connection: keep-alive

          如上,https 協(xié)議和 http 協(xié)議相比:

          • 請求方法從GET變成CONNECT
          • url 沒有 protocol 字段

          實際上,由于 https 下客戶端和服務端的通信除了開頭的協(xié)商以外都是密文,中間的代理服務器不再承擔修改 http 報文再轉(zhuǎn)發(fā)的功能,而是一開始就和客戶端協(xié)商好服務端的地址,隨后的 tcp 密文直接轉(zhuǎn)發(fā)即可。

          http 代理 https 協(xié)議交互過程如圖:

          http 代理 https 協(xié)議

          代碼實現(xiàn)

          首先,創(chuàng)建 tcp 服務,并且對于每個 tcp 請求,均調(diào)用 handle 函數(shù):

          // tcp 連接,監(jiān)聽 8080 端口
          l, err := net.Listen("tcp"":8080")
          if err != nil {
           log.Panic(err)
          }

          // 死循環(huán),每當遇到連接時,調(diào)用 handle
          for {
           client, err := l.Accept()
           if err != nil {
            log.Panic(err)
           }

           go handle(client)
             }

          然后將獲取的數(shù)據(jù)放入緩沖區(qū):

          // 用來存放客戶端數(shù)據(jù)的緩沖區(qū)
          var b [1024]byte
          //從客戶端獲取數(shù)據(jù)
          n, err := client.Read(b[:])
          if err != nil {
           log.Println(err)
           return
             }

          從緩沖區(qū)讀取 HTTP 請求方法,URL 等信息:

          var method, URL, address string
          // 從客戶端數(shù)據(jù)讀入 method,url
          fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '\n')]), "%s%s", &method, &URL)
          hostPortURL, err := url.Parse(URL)
          if err != nil {
           log.Println(err)
           return
             }

          http 協(xié)議和 https 協(xié)議獲取地址的方式不同,分別處理:

          // 如果方法是 CONNECT,則為 https 協(xié)議
          if method == "CONNECT" {
           address = hostPortURL.Scheme + ":" + hostPortURL.Opaque
          else { //否則為 http 協(xié)議
           address = hostPortURL.Host
           // 如果 host 不帶端口,則默認為 80
           if strings.Index(hostPortURL.Host, ":") == -1 { //host 不帶端口, 默認 80
            address = hostPortURL.Host + ":80"
           }
             }

          用獲取到的地址向服務端發(fā)起請求。如果是 http 協(xié)議,將客戶端的請求直接轉(zhuǎn)發(fā)給服務端;如果是 https 協(xié)議,發(fā)送 http 響應:

          //獲得了請求的 host 和 port,向服務端發(fā)起 tcp 連接
          server, err := net.Dial("tcp", address)
          if err != nil {
           log.Println(err)
           return
          }
          //如果使用 https 協(xié)議,需先向客戶端表示連接建立完畢
          if method == "CONNECT" {
           fmt.Fprint(client, "HTTP/1.1 200 Connection established\r\n\r\n")
          else { //如果使用 http 協(xié)議,需將從客戶端得到的 http 請求轉(zhuǎn)發(fā)給服務端
           server.Write(b[:n])
             }

          最后,將所有客戶端的請求轉(zhuǎn)發(fā)至服務端,將所有服務端的響應轉(zhuǎn)發(fā)給客戶端:

          //將客戶端的請求轉(zhuǎn)發(fā)至服務端,將服務端的響應轉(zhuǎn)發(fā)給客戶端。io.Copy 為阻塞函數(shù),文件描述符不關閉就不停止
          go io.Copy(server, client)
             io.Copy(client, server

          完整的源代碼:

          package main

          import (
           "bytes"
           "fmt"
           "io"
           "log"
           "net"
           "net/url"
           "strings"
          )

          func main() {
           // tcp 連接,監(jiān)聽 8080 端口
           l, err := net.Listen("tcp"":8080")
           if err != nil {
            log.Panic(err)
           }

           // 死循環(huán),每當遇到連接時,調(diào)用 handle
           for {
            client, err := l.Accept()
            if err != nil {
             log.Panic(err)
            }

            go handle(client)
           }
          }

          func handle(client net.Conn) {
           if client == nil {
            return
           }
           defer client.Close()

           log.Printf("remote addr: %v\n", client.RemoteAddr())

           // 用來存放客戶端數(shù)據(jù)的緩沖區(qū)
           var b [1024]byte
           //從客戶端獲取數(shù)據(jù)
           n, err := client.Read(b[:])
           if err != nil {
            log.Println(err)
            return
           }

           var method, URL, address string
           // 從客戶端數(shù)據(jù)讀入 method,url
           fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '\n')]), "%s%s", &method, &URL)
           hostPortURL, err := url.Parse(URL)
           if err != nil {
            log.Println(err)
            return
           }

           // 如果方法是 CONNECT,則為 https 協(xié)議
           if method == "CONNECT" {
            address = hostPortURL.Scheme + ":" + hostPortURL.Opaque
           } else { //否則為 http 協(xié)議
            address = hostPortURL.Host
            // 如果 host 不帶端口,則默認為 80
            if strings.Index(hostPortURL.Host, ":") == -1 { //host 不帶端口, 默認 80
             address = hostPortURL.Host + ":80"
            }
           }

           //獲得了請求的 host 和 port,向服務端發(fā)起 tcp 連接
           server, err := net.Dial("tcp", address)
           if err != nil {
            log.Println(err)
            return
           }
           //如果使用 https 協(xié)議,需先向客戶端表示連接建立完畢
           if method == "CONNECT" {
            fmt.Fprint(client, "HTTP/1.1 200 Connection established\r\n\r\n")
           } else { //如果使用 http 協(xié)議,需將從客戶端得到的 http 請求轉(zhuǎn)發(fā)給服務端
            server.Write(b[:n])
           }

           //將客戶端的請求轉(zhuǎn)發(fā)至服務端,將服務端的響應轉(zhuǎn)發(fā)給客戶端。io.Copy 為阻塞函數(shù),文件描述符不關閉就不停止
           go io.Copy(server, client)
           io.Copy(client, server)
          }

          添加代理,然后運行:

          添加代理

          運行

          轉(zhuǎn)自:staight

          http://t.hk.uy/B4p

          文章轉(zhuǎn)載:Go開發(fā)大全
          (版權(quán)歸原作者所有,侵刪)


          點擊下方“閱讀原文”查看更多

          瀏覽 80
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草视频免费观看 | 影音先锋男人资源网 | 免费黄色一级电影网站 | 中文字幕在线观看网 | 欧美日本亚洲 |