Go言語でReverse Proxyを構築する際に気づいたテクニックをメモしておきます。
最適解ではないかもですが、自分はこのように構築してOKをもらいました。
やりたいこと
- リバプロで受け取ったリクエストを処理(シグネチャ生成等)したとき、errorになる場合は、リバプロからエラーレスポンスを返す。
- エラーレスポンスはリバプロで生成
- errorにならない場合は、バックエンドからの応答をそのまま返す。
- リバプロでのレスポンス加工は行わない
指摘されたこと
疑似コードで説明すると、最初はこのような感じで実装しました。
func main() {
var err error
director := func(request *http.Request) {
err = // シグネチャ生成などの処理
}
modifier := func(res *http.Response) error {
if err != nil{
// エラーレスポンスのフォーマットで返す
} else {
// バックエンドからの応答をそのまま返す
}
}
rp := &httputil.ReverseProxy{
Director: director,
ModifyResponse: modifier,
}
server := http.Server{
Addr: ":9000",
Handler: rp,
}
}
この場合、以下のような懸念を指摘して頂きました。
- リクエストAがerrorを出していないにも関わらず、同時に受信したリクエストBが出したerrorが原因でリクエストAがエラーになるかも。。。
どのように対処したか
要点は以下の通りです。
- directorで何かの処理をした後、requestを返すようにする
- errorが発生しなかった場合はdirectorからバックエンドにリクエストを投げる
- レスポンスはそのまま返す
- 処理中にerrorが発生した場合は、
localhost:5001/dummy
からエラーレスポンスのフォーマットに合わせたレスポンスを返す
(directorから/dummy
へリクエストして、レスポンスをそのまま返す)- エラーレスポンスは
dummyHundler
で用意 - エラーレスポンスで必要な値はクエリに記載する
- エラーレスポンスは
このような感じで実装しました。(疑似コード)
// エラーレスポンスのJson
type ProxyErr struct{
hoge string
}
func main() {
director := func(request *http.Request) {
var nerReq *http.Request
var statuCode int
var err error
newReq, statuCode, err = // 何かの処理
// エラーレスポンスを返す場合
if err != nil{
endpoint = "http://localhost:5001/dummy"
u, err := url.parse(endpoint)
q := u.Query()
q.set("statuCode", strconv.Itoa(statuCode))
q.set("hoge", value)
u.RawQuery = q.encode()
newReq = http.NewRequest("GET", u.string(), nil)
}
*request = *newReq
}
rp := &httputil.ReverseProxy{
Director: director,
// レスポンスの加工は行わないので、modifierは不要となる
}
server := http.Server{
Addr: ":9000",
Handler: rp,
}
dummyHundler =:= func(w http.ResponseWriter, req *http.Request){
var statuCode int
statuCode , _ = strconv.Atoi(req.URL.Query().Get("statuCode"))
w.writeHeader(statuCode) // エラーレスポンスなので、ステータスコードを明示的に変更します
w.Header().Set("Context-Type" "application/json") // jsonフォーマットの場合はContext-Typeを明示的に指定します
response := ProxyErr{
hoge: req.URL.Query().Get("hoge"),
}
res, _ = json.Marshal(response)
w.Write(res)
}
go func(){
http.HandleFunc("/dummy", dummyHundler)
http.ListenAndServe("localhost:5001", nil)
}
go func(){
err := server.ListenAndServe()
if err != nil{
// エラー処理
}
}
}