Skip to content

Commit

Permalink
Update web hosting api to handle splited transaction (#374)
Browse files Browse the repository at this point in the history
* Handle splited files
* Add redirection to enforce "/" at the end of url
* Update API as reference tx don't have file content
  • Loading branch information
Neylix committed Jun 15, 2022
1 parent 41c5495 commit 714799c
Show file tree
Hide file tree
Showing 2 changed files with 303 additions and 44 deletions.
62 changes: 47 additions & 15 deletions lib/archethic_web/controllers/api/web_hosting_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,36 @@ defmodule ArchethicWeb.API.WebHostingController do
require Logger

@spec web_hosting(Plug.Conn.t(), map) :: Plug.Conn.t()
def web_hosting(conn, %{"address" => address, "url_path" => url_path}) do
def web_hosting(conn, params = %{"url_path" => url_path}) do
# Redirect to enforce "/" at the end of the root url
if Enum.empty?(url_path) and String.last(conn.request_path) != "/" do
redirect(conn, to: conn.request_path <> "/")
else
do_web_hosting(conn, params)
end
end

defp do_web_hosting(conn, %{"address" => address, "url_path" => url_path}) do
with {:ok, address} <- Base.decode16(address, case: :mixed),
true <- Crypto.valid_address?(address),
{:ok, %Transaction{address: last_address, data: %TransactionData{content: content}}} <-
Archethic.get_last_transaction(address),
{:ok, json_content} <- Jason.decode(content),
{:ok, file, mime_type} <- get_file(json_content, url_path),
{cached?, etag} <- get_cache(conn, last_address, url_path),
{:ok, file_content, encodage} <- get_file_content(file, cached?) do
{:ok, file_content, encodage} <- get_file_content(file, cached?, url_path) do
conn =
conn
|> put_resp_content_type(mime_type, "utf-8")
|> put_resp_header("content-encoding", "gzip")
|> put_resp_header("cache-control", "public")
|> put_resp_header("etag", etag)

if cached? do
send_resp(conn, 304, "")
else
case encodage do
"gzip" ->
send_resp(conn, 200, file_content)
{conn, response_content} = encode_res(conn, file_content, encodage)

_ ->
send_resp(conn, 200, :zlib.gzip(file_content))
end
send_resp(conn, 200, response_content)
end
else
# Base.decode16 || Crypto.valid_address
Expand Down Expand Up @@ -69,6 +73,20 @@ defmodule ArchethicWeb.API.WebHostingController do
end
end

defp encode_res(conn, file_content, encodage) do
if Enum.any?(get_req_header(conn, "accept-encoding"), &String.contains?(&1, "gzip")) do
res_conn = put_resp_header(conn, "content-encoding", "gzip")

if encodage == "gzip",
do: {res_conn, file_content},
else: {res_conn, :zlib.gzip(file_content)}
else
if encodage == "gzip",
do: {conn, :zlib.gunzip(file_content)},
else: {conn, file_content}
end
end

# API without path returns default index.html file
# or the only file if there is only one
@spec get_file(json_content :: map(), url_path :: list()) ::
Expand Down Expand Up @@ -119,7 +137,7 @@ defmodule ArchethicWeb.API.WebHostingController do
# Control if it is a file or a folder
file_name = Enum.at(keys, 0)

if Map.get(json_content, file_name) |> Map.has_key?("content") do
if Map.get(json_content, file_name) |> Map.has_key?("address") do
file_name
else
"index.html"
Expand Down Expand Up @@ -155,20 +173,34 @@ defmodule ArchethicWeb.API.WebHostingController do
end

# All file are encoded in base64 in JSON content
@spec get_file_content(file :: map(), cached? :: boolean()) ::
@spec get_file_content(file :: map(), cached? :: boolean(), url_path :: list()) ::
{:ok, binary(), binary() | nil} | :encodage_error | :file_error
defp get_file_content(_file, _cached? = true), do: {:ok, nil, nil}
defp get_file_content(_file, _cached? = true, _url_path), do: {:ok, nil, nil}

defp get_file_content(file = %{"content" => content}, _cached = false) do
defp get_file_content(file = %{"address" => address_list}, _cached? = false, url_path) do
try do
content =
Enum.map_join(address_list, fn tx_address ->
{:ok, %Transaction{data: %TransactionData{content: content}}} =
Base.decode16!(tx_address, case: :mixed) |> Archethic.search_transaction()

{:ok, json_content} = Jason.decode(content)
{:ok, nested_file_content, _} = get_file(json_content, url_path)

nested_file_content
end)

file_content = Base.url_decode64!(content, padding: false)
encodage = Map.get(file, "encodage")
{:ok, file_content, encodage}
rescue
_ ->
ArgumentError ->
:encodage_error

error ->
error
end
end

defp get_file_content(_, _), do: :file_error
defp get_file_content(_, _, _), do: :file_error
end
Loading

0 comments on commit 714799c

Please sign in to comment.