幅広い知識と幅広いスキルを求められている系システムエンジニアです。リモートワークしかやりません。

GoでMeilisearchをリバースプロキシ

これがしたい

instant-searchはMeilisearchが公開されているのを前提で動くのですが、Meilisearchを非公開にしたいときや、独自認証を挟みたい時はどうするの?という時のTips。Goで。

ポイントは

  • GoでMeilisearchの /indexes/:index_uid/search へのリバースプロキシを書いてその中で独自の認証を処理する
  • 独自認証したい場合はinstant-searchのinit時に指定するMeilisearchのキーにTokenを併記する(Authorizationヘッダーに載る)
  • レスポンスの Content-Encoding: gzip も忘れずに

clientのinitialize

const searchClient = instantMeiliSearch(
  'https://your-backend.tld'
  meiliApiKey + ';' + yourApiToken,   // 今回はセミコロン区切りで併記
)

backend

func main() {
  ...
	router.POST("/indexes/:index_uid/search", meiliProxy)  // 使うルーティングライブラリに応じてカキカエテ
  ...
}
  

func meiliProxy(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	indexUID := params.ByName("index_uid")
  
  // https://your-meilisearch.tld/indexes/hogehogeindex/search
	endpoint, err := url.JoinPath(os.Getenv("MEILI_HOST"), "indexes", indexUID, "search")
	if err != nil {
		log.Println(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	req, err := http.NewRequest(r.Method, endpoint, r.Body)
	if err != nil {
		log.Println(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	req.Header = r.Header.Clone() // instant-searchから飛んできたHeaderをクローン
  // meilisearchapikey;dokujitokenhogehogehogehogehoge のような形式のAuthorizationを分割して返しているだけ
	meiliToken, dokujiToken, err := parseMeiliAuthorizationHeader(r.Header.Get("Authorization"))
	if err != nil {
		log.Println(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
  
  // dokujiTokenに含まれるパーミッションの確認
	claims := dokujiToken.GetClaims()
	if !claims.HasPermission("read") && !claims.HasPermission("write") {
		w.WriteHeader(http.StatusUnauthorized)
		return
	}
	req.Header.Set("Authorization", "Bearer "+meiliToken)

	res, err := client.Do(req)
	if err != nil {
		log.Println(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	body, err := io.ReadAll(res.Body)
	if err != nil {
    log.Println(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	defer res.Body.Close()
	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Content-Encoding", "gzip")   // これも必要
	w.WriteHeader(res.StatusCode)
	w.Write(body)
}
© 2023 @miiton