# Copyright(c) 2015-2023 ACCESS CO., LTD. All rights reserved. use Croma defmodule AntikytheraCore.Handler.CowboyReq do alias Croma.Result, as: R alias Antikythera.{Time, GearName, PathInfo, Conn, Context} alias Antikythera.Http.{Method, QueryParams, Body} alias Antikythera.Request.PathMatches alias Antikythera.Context.GearEntryPoint alias AntikytheraCore.Conn, as: CoreConn alias AntikytheraCore.Handler.{GearError, BodyParser, HelperModules} alias AntikytheraCore.GearLog.{ContextHelper, Writer} @type result(a) :: {:ok, a} | {:error, :cowboy_req.req()} @type routing_info :: {GearName.t(), nil | GearEntryPoint.t(), Method.t(), PathInfo.t(), PathMatches.t()} defun method(req :: :cowboy_req.req()) :: result(Method.t()) do try do :cowboy_req.method(req) |> Method.from_string() |> R.pure() rescue FunctionClauseError -> {:error, :cowboy_req.reply(400, req)} end end defun path_info(%{path: encoded_path_string, path_info: decoded_path_info} :: :cowboy_req.req()) :: PathInfo.t() do case byte_size(encoded_path_string) do 0 -> [] len -> # Avoid `String.last/1` as it takes O(length_of_string) case binary_part(encoded_path_string, len - 1, 1) do # append "" due to trailing '/'; See also: `GearAction.split_path_to_segments/1` "/" -> decoded_path_info ++ [""] _ -> decoded_path_info end end end defun query_params(req :: :cowboy_req.req(), routing_info :: routing_info) :: result(QueryParams.t()) do try do :cowboy_req.parse_qs(req) |> Map.new(fn {k, true} -> {k, ""} kv_pair -> kv_pair end) |> R.pure() catch :exit, {:request_error, :qs, _message} -> {:error, with_conn(req, routing_info, %{}, &GearError.bad_request/1)} end end defun request_body_pair( req1 :: :cowboy_req.req(), routing_info :: routing_info, qparams :: v[QueryParams.t()], helper_modules :: v[HelperModules.t()] ) :: result({:cowboy_req.req(), {binary, Body.t()}}) do case BodyParser.parse(req1) do {:ok, req2, raw, parsed} -> {:ok, {req2, {raw, parsed}}} {:error, :invalid_body, req2} -> {:error, with_conn(req2, routing_info, qparams, &GearError.bad_request/1)} {:error, :timeout} -> {:error, with_conn( req1, routing_info, qparams, &bad_request_with_body_timeout_logging(&1, helper_modules) )} end end defunp bad_request_with_body_timeout_logging( %Conn{context: %Context{context_id: context_id}} = conn, %HelperModules{logger: logger} ) :: Conn.t() do Writer.info( logger, Time.now(), context_id, "timeout in receiving request body: something is wrong with the client?" ) GearError.bad_request(conn) end defun with_conn( req :: :cowboy_req.req(), routing_info :: routing_info, qparams :: v[QueryParams.t()], body_pair :: {binary, Body.t()} \\ {"", ""}, f :: (Conn.t() -> Conn.t()) ) :: :cowboy_req.req() do conn = CoreConn.make_from_cowboy_req(req, routing_info, qparams, body_pair) ContextHelper.set(conn) f.(conn) |> CoreConn.reply_as_cowboy_res(req) end end