5分でGo言語(Golang)でHTTPエージェント(Proxy)を実現
5973 ワード
私たちはソフトウェア開発の過程で、パッケージをつかんだり、HTTPコンテンツの転送を理解したり、Nginx逆エージェントなど、エージェントが必要な場所に遭遇します.
以前Linuxで降りる時、PrivoxyをインストールしてSocketエージェントをHTTPエージェントに変換して、起動して起動して、比較的に便利です.しかしMacではBrewでインストールしたPrivoxyを使うのは難しいので、SocketとHTTPエージェントを1つのソフトウェアで済ませてみたいと思っています.そうすれば、個別のソフトウェアをインストールして変換する必要はありません.
と思って始めましょう.以前はあまりネットプログラミングをしていませんでしたが、最近はちょうどGoを研究していて、ちょうど手を練習しています.
ここでは,主にHTTP/1.1プロトコルにおけるCONNECT法を用いて構築したトンネル接続,実現したHTTP Proxyについて述べる.このようなエージェントの利点は,クライアント要求のデータを知らずにそのまま転送すればよいことであり,HTTPSの要求を処理するのに非常に便利であり,彼の内容を解析することなくエージェントを実現できることである.
エージェントリスニングの開始
HTTP Proxyを作成するには、クライアントのリクエストを受信するためにポートを傍受するサーバを起動する必要があります.Golangは強力なnetパッケージを提供してくれて、エージェントサーバのリスニングを開始するのはとても便利です.
以上のエージェントは8080ポートで傍受するサーバを実現しました.ここではipアドレスを書かず、デフォルトではすべてのipアドレスで傍受しています.本機のみを適用したい場合は、127.0.0.1:8080を使用すると、エージェントサーバにアクセスできません.
エージェントリクエストの受信の傍受
エージェントサーバを起動すると、エージェントリクエストを受け入れられなくなります.リクエストがあれば、さらなる処理ができます.
ListenerインタフェースのAcceptメソッドは,クライアントからの接続データを受け入れるブロック型のメソッドであり,クライアントが接続データを送信していない場合はブロック待ちである.受信した接続データは、すぐにhandleClientRequestメソッドに渡され、ここでgoキーワードを使用してgoroutineを開く目的はクライアントの受信をブロックしないことであり、プロキシサーバはすぐに次の接続要求を受信することができる.
要求を解析し、アクセスするIPとポートを取得
クライアントのエージェントリクエストがあれば、クライアントがアクセスするリモートホストのIPとポートをリクエストから抽出しなければなりません.そうすれば、エージェントサーバはリモートホストとの接続を確立し、エージェント転送することができます.
HTTPプロトコルのヘッダ情報には、私たちが必要とするホスト名(IP)とポート情報が含まれており、明文であり、プロトコルは規範的であり、以下のようなものである.
私たちが必要とする最初の行で、最初の行の情報はスペースで分かれていて、最初の部分のCONNECTは要求方法で、ここはCONNECTで、それ以外にGET、POSTなどがあって、すべてHTTPプロトコルの標準的な方法です.
第2部はURLで、httpsのリクエストはhostとportだけで、httpのリクエストは完成したurlで、後でサンプルを見て、分かりました.
第3部はHTTPのプロトコルとバージョンで、これはあまり注目しなくてもいいです.
以上はhttpのリクエストです.httpを見てみましょう.
httが表示され、ポート番号はありません(デフォルトは80).httpよりschame-http://より多いです.
解析により,HTTPヘッダ情報から要求されたurlとmethod情報を取得できるようになった.
次にurlをさらに解析し、必要なリモートサーバ情報を取得する必要があります.
これにより、サーバを要求する情報が完全に取得されます.これらのフォーマットは次のとおりです.
IP(v 4 orv 6)、ホスト名(イントラネット)、ドメイン名(dns解析)の可能性があります.
プロキシサーバとリモートサーバの接続
リモートサーバの情報があれば、ダイヤルアップして接続を確立することができ、接続があれば、通信することができます.
データ転送
ダイヤルアップに成功すると、データエージェントの転送が可能になります.
このうちCONNECTメソッドには個別の応答があり,クライアントは接続を確立し,プロキシサーバは応答を確立してからHTTPのようにアクセスを要求することができると述べている.
完全なコード
ここで、エージェントサーバの開発はすべて完了しました.次は完全なソースコードです.
ソースコードをコンパイルして、自分のパソコンに置いて、テストしましょう.
以前Linuxで降りる時、PrivoxyをインストールしてSocketエージェントをHTTPエージェントに変換して、起動して起動して、比較的に便利です.しかしMacではBrewでインストールしたPrivoxyを使うのは難しいので、SocketとHTTPエージェントを1つのソフトウェアで済ませてみたいと思っています.そうすれば、個別のソフトウェアをインストールして変換する必要はありません.
と思って始めましょう.以前はあまりネットプログラミングをしていませんでしたが、最近はちょうどGoを研究していて、ちょうど手を練習しています.
ここでは,主にHTTP/1.1プロトコルにおけるCONNECT法を用いて構築したトンネル接続,実現したHTTP Proxyについて述べる.このようなエージェントの利点は,クライアント要求のデータを知らずにそのまま転送すればよいことであり,HTTPSの要求を処理するのに非常に便利であり,彼の内容を解析することなくエージェントを実現できることである.
エージェントリスニングの開始
HTTP Proxyを作成するには、クライアントのリクエストを受信するためにポートを傍受するサーバを起動する必要があります.Golangは強力なnetパッケージを提供してくれて、エージェントサーバのリスニングを開始するのはとても便利です.
l, err := net.Listen("tcp", ":8080") if err != nil {
log.Panic(err)
}
以上のエージェントは8080ポートで傍受するサーバを実現しました.ここではipアドレスを書かず、デフォルトではすべてのipアドレスで傍受しています.本機のみを適用したい場合は、127.0.0.1:8080を使用すると、エージェントサーバにアクセスできません.
エージェントリクエストの受信の傍受
エージェントサーバを起動すると、エージェントリクエストを受け入れられなくなります.リクエストがあれば、さらなる処理ができます.
for {
client, err := l.Accept() if err != nil {
log.Panic(err)
} go handleClientRequest(client)
}
ListenerインタフェースのAcceptメソッドは,クライアントからの接続データを受け入れるブロック型のメソッドであり,クライアントが接続データを送信していない場合はブロック待ちである.受信した接続データは、すぐにhandleClientRequestメソッドに渡され、ここでgoキーワードを使用してgoroutineを開く目的はクライアントの受信をブロックしないことであり、プロキシサーバはすぐに次の接続要求を受信することができる.
要求を解析し、アクセスするIPとポートを取得
クライアントのエージェントリクエストがあれば、クライアントがアクセスするリモートホストのIPとポートをリクエストから抽出しなければなりません.そうすれば、エージェントサーバはリモートホストとの接続を確立し、エージェント転送することができます.
HTTPプロトコルのヘッダ情報には、私たちが必要とするホスト名(IP)とポート情報が含まれており、明文であり、プロトコルは規範的であり、以下のようなものである.
CONNECT www.google.com:443 HTTP/1.1
Host: www.google.com:443
Proxy-Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36
私たちが必要とする最初の行で、最初の行の情報はスペースで分かれていて、最初の部分のCONNECTは要求方法で、ここはCONNECTで、それ以外にGET、POSTなどがあって、すべてHTTPプロトコルの標準的な方法です.
第2部はURLで、httpsのリクエストはhostとportだけで、httpのリクエストは完成したurlで、後でサンプルを見て、分かりました.
第3部はHTTPのプロトコルとバージョンで、これはあまり注目しなくてもいいです.
以上はhttpのリクエストです.httpを見てみましょう.
GET http://www.flysnow.org/ HTTP/1.1
Host: www.flysnow.org
Proxy-Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36
httが表示され、ポート番号はありません(デフォルトは80).httpよりschame-http://より多いです.
解析により,HTTPヘッダ情報から要求されたurlとmethod情報を取得できるようになった.
var b [1024]byte
n, err := client.Read(b[:]) if err != nil {
log.Println(err) return
} var method, host, address string
fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '
')]), "%s%s", &method, &host)
hostPortURL, err := url.Parse(host) if err != nil {
log.Println(err) return
}
次にurlをさらに解析し、必要なリモートサーバ情報を取得する必要があります.
if hostPortURL.Opaque == "443" { //https
address = hostPortURL.Scheme + ":443"
} else { //http
if strings.Index(hostPortURL.Host, ":") == -1 { //host , 80
address = hostPortURL.Host + ":80"
} else {
address = hostPortURL.Host
}
}
これにより、サーバを要求する情報が完全に取得されます.これらのフォーマットは次のとおりです.
ip:port
hostname:port
domainname:port
IP(v 4 orv 6)、ホスト名(イントラネット)、ドメイン名(dns解析)の可能性があります.
プロキシサーバとリモートサーバの接続
リモートサーバの情報があれば、ダイヤルアップして接続を確立することができ、接続があれば、通信することができます.
// host port,
server, err := net.Dial("tcp", address) if err != nil {
log.Println(err) return
}
データ転送
ダイヤルアップに成功すると、データエージェントの転送が可能になります.
if method == "CONNECT" {
fmt.Fprint(client, "HTTP/1.1 200 Connection established\r
")
} else {
server.Write(b[:n])
} //
go io.Copy(server, client)
io.Copy(client, server)
このうちCONNECTメソッドには個別の応答があり,クライアントは接続を確立し,プロキシサーバは応答を確立してからHTTPのようにアクセスを要求することができると述べている.
完全なコード
ここで、エージェントサーバの開発はすべて完了しました.次は完全なソースコードです.
package mainimport ( "bytes"
"fmt"
"io"
"log"
"net"
"net/url"
"strings")func main() {
log.SetFlags(log.LstdFlags|log.Lshortfile)
l, err := net.Listen("tcp", ":8081") if err != nil {
log.Panic(err)
} for {
client, err := l.Accept() if err != nil {
log.Panic(err)
} go handleClientRequest(client)
}
}func handleClientRequest(client net.Conn) { if client == nil { return
} defer client.Close() var b [1024]byte
n, err := client.Read(b[:]) if err != nil {
log.Println(err) return
} var method, host, address string
fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '
')]), "%s%s", &method, &host)
hostPortURL, err := url.Parse(host) if err != nil {
log.Println(err) return
} if hostPortURL.Opaque == "443" { //https
address = hostPortURL.Scheme + ":443"
} else { //http
if strings.Index(hostPortURL.Host, ":") == -1 { //host , 80
address = hostPortURL.Host + ":80"
} else {
address = hostPortURL.Host
}
} // host port,
server, err := net.Dial("tcp", address) if err != nil {
log.Println(err) return
} if method == "CONNECT" {
fmt.Fprint(client, "HTTP/1.1 200 Connection established\r
")
} else {
server.Write(b[:n])
} //
go io.Copy(server, client)
io.Copy(client, server)
}
ソースコードをコンパイルして、自分のパソコンに置いて、テストしましょう.