# Copyright(c) 2015-2019 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 -> case binary_part(encoded_path_string, len - 1, 1) do # Avoid `String.last/1` as it takes O(length_of_string) "/" -> decoded_path_info ++ [""] # append "" due to trailing '/'; See also: `GearAction.split_path_to_segments/1` _ -> 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