Current section

Files

Jump to
elixir_wechat lib http middleware component.ex
Raw

lib/http/middleware/component.ex

defmodule WeChat.Http.Middleware.Component do
@moduledoc false
@behaviour Tesla.Middleware
alias WeChat.Http.Middleware.Common
alias WeChat.{Http, Error}
require Logger
def call(env, next, options) do
execute(env, next, options)
end
defp execute(env, next, options) do
try do
updated_env = populate_request(env, options)
response = Tesla.run(updated_env, next)
decode_response(response, updated_env, next, options)
rescue
error ->
{:error, error}
end
end
defp populate_request(env, options) do
env
|> populate_component_access_token(options)
|> Common.encode_request(env.opts)
end
defp populate_component_access_token(env, options) do
populate_component_access_token(URI.parse(env.url), env, options)
end
defp populate_component_access_token(
%URI{path: "/cgi-bin/component/api_component_token"} = _uri,
env,
options
) do
credential = Http.grep_credential(options)
updated_body =
env.body
|> populate_required_into_body([:component_verify_ticket])
|> Map.put(:component_appid, credential.appid)
|> Map.put(:component_appsecret, credential.secret)
Map.put(env, :body, updated_body)
end
defp populate_component_access_token(
%URI{path: "/cgi-bin/component/api_create_preauthcode"} = _uri,
env,
options
) do
credential = Http.grep_credential(options)
updated_env = append_component_access_token(env, options)
updated_body =
populate_required_into_body(env.body, [], %{component_appid: credential.appid})
Map.put(updated_env, :body, updated_body)
end
defp populate_component_access_token(
%URI{path: "/cgi-bin/component/api_query_auth"} = _uri,
env,
options
) do
credential = Http.grep_credential(options)
updated_env = append_component_access_token(env, options)
updated_body =
populate_required_into_body(env.body, [:authorization_code], %{
component_appid: credential.appid
})
Map.put(updated_env, :body, updated_body)
end
defp populate_component_access_token(
%URI{path: "/cgi-bin/component/api_authorizer_token"} = _uri,
env,
options
) do
credential = Http.grep_credential(options)
updated_env = append_component_access_token(env, options)
updated_body =
populate_required_into_body(env.body, [:authorizer_appid, :authorizer_refresh_token], %{
component_appid: credential.appid
})
Map.put(updated_env, :body, updated_body)
end
defp populate_component_access_token(
%URI{path: "/cgi-bin/component/api_get_authorizer_info"} = _uri,
env,
options
) do
credential = Http.grep_credential(options)
updated_env = append_component_access_token(env, options)
updated_body =
populate_required_into_body(env.body, [:authorizer_appid], %{
component_appid: credential.appid
})
Map.put(updated_env, :body, updated_body)
end
defp populate_component_access_token(
%URI{path: "/cgi-bin/component/api_get_authorizer_option"} = _uri,
env,
options
) do
credential = Http.grep_credential(options)
updated_env = append_component_access_token(env, options)
updated_body =
populate_required_into_body(env.body, [:authorizer_appid, :option_name], %{
component_appid: credential.appid
})
Map.put(updated_env, :body, updated_body)
end
defp populate_component_access_token(
%URI{path: "/cgi-bin/component/api_set_authorizer_option"} = _uri,
env,
options
) do
credential = Http.grep_credential(options)
updated_env = append_component_access_token(env, options)
updated_body =
populate_required_into_body(env.body, [:authorizer_appid, :option_name, :option_value], %{
component_appid: credential.appid
})
Map.put(updated_env, :body, updated_body)
end
defp populate_component_access_token(
%URI{path: "/cgi-bin/component/api_get_authorizer_list"} = _uri,
env,
options
) do
credential = Http.grep_credential(options)
updated_env = append_component_access_token(env, options)
updated_body =
populate_required_into_body(env.body, [:offset, :count], %{
component_appid: credential.appid
})
Map.put(updated_env, :body, updated_body)
end
defp populate_component_access_token(_uri, env, options) do
append_component_access_token(env, options)
end
defp append_component_access_token(env, options) do
refreshed_component_access_token = Keyword.get(options, :refreshed_component_access_token)
required =
if refreshed_component_access_token == nil do
wechat_component_module = Keyword.get(options, :module)
component_access_token =
cond do
function_exported?(wechat_component_module, :get_component_access_token, 1) ->
apply(wechat_component_module, :get_component_access_token, [Http.grep_appid(options)])
true ->
apply(wechat_component_module, :get_component_access_token, [])
end
Logger.info ">>> auto append_component_access_token wechat_component_module: #{inspect wechat_component_module}"
Logger.info ">>> auto append_component_access_token component_access_token: #{inspect component_access_token}"
[component_access_token: component_access_token]
else
Logger.info ">>> auto append_component_access_token using the latest refreshed component_access_token: #{inspect refreshed_component_access_token}"
[component_access_token: refreshed_component_access_token]
end
Map.update!(env, :query, &(Keyword.merge(&1, required)))
end
defp decode_response({:ok, response}, init_env, next, options) do
Logger.info(">>> decode_response for component")
Logger.info("#{inspect response}")
Logger.info("#{inspect init_env}")
initial = %{
status: response.status,
headers: response.headers,
body: ""
}
if response.body != "" do
response_body = Jason.decode!(response.body)
request_query = init_env.query
Logger.info ">>> response_body: #{inspect response_body}"
Logger.info ">>> request_query: #{inspect request_query}"
case rerun_when_token_expire(init_env, next, options, response_body, request_query) do
:ok ->
request_body = init_env.body
prepared_request_body =
if is_bitstring(request_body) do
Jason.decode!(request_body)
else
request_body
end
Logger.info ">>> prepared_request_body: #{inspect prepared_request_body}"
reserve_access_token(URI.parse(response.url), response_body, options, prepared_request_body)
{:ok, %{initial | body: response_body}}
retry_result ->
retry_result
end
else
{:error, %Error{reason: :unknown, message: "response body is empty, http status code: #{inspect(response.status)}"}}
end
end
defp decode_response({:error, reason}, _init_env, _next, _options) do
Logger.error "occurs error when decode response with reason: #{inspect(reason)}"
{:error, %Error{reason: reason}}
end
defp reserve_access_token(
%URI{path: "/cgi-bin/component/api_query_auth"},
response_body,
options,
_request_body
) do
access_token = Map.get(response_body, "authorizer_access_token")
expires_in = Map.get(response_body, "expires_in")
if access_token != nil and expires_in != nil do
wechat_module = Keyword.get(options, :module)
updated_response_body = %{
"access_token" => access_token,
"authorizer_appid" => Map.get(response_body, "authorizer_appid"),
"authorizer_refresh_token" => Map.get(response_body, "authorizer_refresh_token"),
"expires_in" => expires_in
}
apply(wechat_module, :set_access_token, [updated_response_body, options])
end
end
defp reserve_access_token(
%URI{path: "/cgi-bin/component/api_authorizer_token"},
response_body,
options,
request_body
) do
Logger.info ">>>> reserve_access_token <<<<"
Logger.info "response_body: #{inspect response_body}, request_body: #{inspect request_body}"
access_token = Map.get(response_body, "authorizer_access_token")
expires_in = Map.get(response_body, "expires_in")
if access_token != nil and expires_in != nil do
wechat_module = Keyword.get(options, :module)
authorizer_appid =
Map.get_lazy(request_body, "authorizer_appid", fn ->
Map.get(request_body, :authorizer_appid)
end)
Logger.info "authorizer_appid: #{inspect authorizer_appid}"
updated_response_body = %{
"access_token" => access_token,
"authorizer_appid" => authorizer_appid,
"authorizer_refresh_token" => Map.get(response_body, "authorizer_refresh_token"),
"expires_in" => expires_in
}
appid = Http.grep_appid(options)
apply(wechat_module, :set_access_token, [appid, updated_response_body, options])
end
end
defp reserve_access_token(
%URI{path: "/cgi-bin/component/api_component_token"},
response_body,
options,
_request_body
) do
component_access_token = Map.get(response_body, "component_access_token")
expires_in = Map.get(response_body, "expires_in")
if component_access_token != nil and expires_in != nil do
wechat_module = Keyword.get(options, :module)
updated_response_body = %{
"component_access_token" => component_access_token,
"expires_in" => expires_in
}
appid = Http.grep_appid(options)
apply(wechat_module, :set_component_access_token, [appid, updated_response_body, options])
end
end
defp reserve_access_token(_uri, _response_body, _options, _request_body) do
:ok
end
defp populate_required_into_body(body, fields, prepared \\ %{})
defp populate_required_into_body(body, [], prepared)
when is_map(prepared) and is_bitstring(body) do
prepared
end
defp populate_required_into_body(body, [], prepared) when is_map(prepared) and is_map(body) do
Map.merge(body, prepared)
end
defp populate_required_into_body(nil, [], prepared) when is_map(prepared) do
prepared
end
defp populate_required_into_body(body, [], prepared) when is_map(prepared) do
raise "invalid body: #{inspect(body)} while process required fields"
end
defp populate_required_into_body(body, [current_field | rest_fields], prepared)
when is_map(prepared) and is_map(body) do
cond do
is_bitstring(current_field) ->
current_field_atom = String.to_atom(current_field)
value =
Map.get_lazy(body, current_field, fn ->
Map.get(body, current_field_atom)
end)
updated = Map.put(prepared, current_field_atom, value)
populate_required_into_body(body, rest_fields, updated)
is_atom(current_field) ->
value =
Map.get_lazy(body, current_field, fn ->
Map.get(body, to_string(current_field))
end)
updated = Map.put(prepared, current_field, value)
populate_required_into_body(body, rest_fields, updated)
true ->
raise "invalid field: #{inspect(current_field)} in body"
end
end
defp populate_required_into_body(body, [current_field | _rest_fields], prepared)
when is_map(prepared) and is_bitstring(body) do
cond do
is_bitstring(current_field) ->
updated = Map.put(prepared, String.to_atom(current_field), body)
populate_required_into_body(body, [], updated)
is_atom(current_field) ->
updated = Map.put(prepared, current_field, body)
populate_required_into_body(body, [], updated)
true ->
raise "invalid field: #{inspect(current_field)} in body"
end
end
defp populate_required_into_body(nil, fields, prepared)
when is_map(prepared) and length(fields) > 0 do
prepared
end
defp populate_required_into_body(body, fields, prepared)
when is_map(prepared) and length(fields) > 0 do
raise "invalid body: #{inspect(body)} while process required fields"
end
defp rerun_when_token_expire(env, next, options, response_result, request_query) do
errcode = Map.get(response_result, "errcode")
cond do
errcode in [40001, 42001] ->
wechat_appid = Http.grep_appid(options)
wechat_module = Keyword.get(options, :module)
Logger.info "when invoke wechat component apis occurs expired component_access_token for wechat_appid: #{wechat_appid}, will refresh to fetch a new component_access_token"
new_component_access_token = apply(wechat_module, :refresh_component_access_token, [wechat_appid, Keyword.merge(options, request_query)])
updated_options = Keyword.put(options, :refreshed_component_access_token, new_component_access_token)
execute(env, next, updated_options)
errcode in [61005, 61006] ->
wechat_appid = Http.grep_appid(options)
Logger.error "component_verify_ticket of appid: #{wechat_appid} is expired or invalid (errcode: #{errcode}), please re-auth or update verify_ticket"
{:error, %Error{reason: :invalid_component_verify_ticket, errcode: errcode, message: Map.get(response_result, "errmsg")}}
errcode == 61023 ->
wechat_appid = Http.grep_appid(options)
Logger.info "refresh_token of appid: #{wechat_appid} is invalid, will retry fetch, env: #{inspect env}"
{:error, %Error{reason: :invalid_component_refresh_token, errcode: errcode, message: Map.get(response_result, "errmsg")}}
errcode == 61004 ->
wechat_appid = Http.grep_appid(options)
Logger.error "clientip is not in whitelist of appid: #{wechat_appid}"
{:error, %Error{reason: :invalid_clientip, errcode: errcode, message: Map.get(response_result, "errmsg")}}
true ->
:ok
end
end
end