2021年3月2日 星期二

Elixir: HTTP Router Proxy

 在特殊的應用情境中,客戶端請求某些資源時可能會需要透過中介 Proxy 去存取資料,本篇文章引用 【用 Elixir 和 hackney 做 proxy】這篇文章所實作的 Proxy ,雖然作者最後自己寫了一個 PlugProxy 套件去取代這樣的手法,但本文就以當作練習的角度觀察 cowboy, hackney。


原本文章使用的 http client 是 hackney ,他是原來 erlang 的原生套件,最後用來開啟 http proxy server 的是 cowboy 的框架。


建立一個相關的專案:

mix new proxyserver --sup


相依性套件安裝

mix.exs:

defp deps do
    [
      {:hackney, github: "benoitc/hackney"},
      {:plug_cowboy, "~> 2.0"}
    ]
end

新增後,使用指令安裝:

mix deps.get


安裝完成後,在 proxyserver/lib/proxyserver 建立一個檔案 Proxy.ex,內容是:

defmodule Proxyserver.Proxy do
    import Plug.Conn
  
    def init(options), do: options
  
    # 會被任何的 http 進來的流量呼叫
    def call(conn, _) do
      target_method = method2atom(conn.method)
      target_url = url_builder(conn)
      {:ok, c} = :hackney.request(target_method, target_url, conn.req_headers, :stream, [])
  
      conn |> handle_transfer_out(c) |> handle_transfer_back(c)
    end
  
    # 把 method 字串轉成 atom 模式: "GET" -> :get
    defp method2atom(method), do: method |> String.downcase |> String.to_atom
  
    # 製作一個字串串接的 url with query string
    defp url_builder(conn) do
      dist_url = "http://localhost:8888" <> conn.request_path
  
      case conn.query_string do
        "" -> dist_url
        any_query_string -> dist_url <> "?" <> any_query_string
      end
    end
  
    # 將內容轉手請求出去
    defp handle_transfer_out(conn, client) do
      case read_body(conn, []) do
        # 如果是 :ok 讀完的情形,就把 conn 丟回去
        {:ok, body, conn} ->
          :hackney.send_body(client, body)
          conn
        
        # 如果是 :more 還沒讀完的情形,就繼續處理自己
        {:more, body, conn} ->
          :hackney.send_body(client, body)
          handle_transfer_out(conn, client)
      end
    end
    
    # 將 proxy 內容丟回去給使用者
    defp handle_transfer_back(conn, client) do
      {:ok, status, headers, client} = :hackney.start_response(client)
      {:ok, body} = :hackney.body(client)
  
      %{conn | resp_headers: headers} |> send_resp(status, body)
    end
end

然後,在 proxyserver/lib/proxyserver/ 目錄底下可以找到 application.ex ,改寫 children 內容如下
:

children = [
      {Plug.Cowboy, scheme: :http, plug: Proxyserver.Proxy, options: [port: 8081]}
]

啟動伺服器:

mix run --no-halt

也可以用 iex 來啟動伺服器做 debug:

iex -S mix


然後,針對 localhost:8888/test.html 以取得該資料為主,在 elixir 啟動時,訪問 localhost:8081/test.html 就會看到轉手 test.html 的資料。

Reference:
https://zespia.tw/blog/2016/07/24/build-proxy-with-elixir-and-hackney/
https://github.com/benoitc/hackney/tree/master/doc
https://elixirforum.com/t/accessing-the-request-body-in-plug/15975/2
https://hexdocs.pm/plug/Plug.Conn.Adapter.html#c:read_req_body/2
https://github.com/tallarium/reverse_proxy_plug/tree/master/lib
https://github.com/elixir-plug/plug_cowboy

沒有留言:

張貼留言

© Mac Taylor, 歡迎自由轉貼。
Background Email Pattern by Toby Elliott
Since 2014