aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorfeld <feld@feld.me>2019-09-09 18:53:09 +0000
committerfeld <feld@feld.me>2019-09-09 18:53:09 +0000
commitceb2e09126a48fa341d5ff84371db49ae022bc12 (patch)
treeca875c111e886b051385250811115284f88d1628 /lib
parentbce16f455743c3a6603fb5d2d81c4e8200b38494 (diff)
parent11e12b5761bcd67aa609d91f6f8d1f6755b2312b (diff)
downloadpleroma-ceb2e09126a48fa341d5ff84371db49ae022bc12.tar.gz
Merge branch 'cache-plug' into 'develop'
Add Pleroma.Plugs.Cache Closes #1174 See merge request pleroma/pleroma!1612
Diffstat (limited to 'lib')
-rw-r--r--lib/pleroma/activity.ex9
-rw-r--r--lib/pleroma/application.ex3
-rw-r--r--lib/pleroma/object.ex6
-rw-r--r--lib/pleroma/plugs/cache.ex122
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex33
5 files changed, 166 insertions, 7 deletions
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index a7844c36b..6a51d4cf3 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -308,10 +308,19 @@ defmodule Pleroma.Activity do
%{data: %{"type" => "Create", "object" => %{"id" => ap_id}}} -> ap_id == id
_ -> nil
end)
+ |> purge_web_resp_cache()
end
def delete_by_ap_id(_), do: nil
+ defp purge_web_resp_cache(%Activity{} = activity) do
+ %{path: path} = URI.parse(activity.data["id"])
+ Cachex.del(:web_resp_cache, path)
+ activity
+ end
+
+ defp purge_web_resp_cache(nil), do: nil
+
for {ap_type, type} <- @mastodon_notification_types do
def mastodon_notification_type(%Activity{data: %{"type" => unquote(ap_type)}}),
do: unquote(type)
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 483ac1f39..1d46925f8 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -116,7 +116,8 @@ defmodule Pleroma.Application do
build_cachex("object", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),
build_cachex("rich_media", default_ttl: :timer.minutes(120), limit: 5000),
build_cachex("scrubber", limit: 2500),
- build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500)
+ build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500),
+ build_cachex("web_resp", limit: 2500)
]
end
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index d58eb7f7d..5033798ae 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -130,14 +130,16 @@ defmodule Pleroma.Object do
def delete(%Object{data: %{"id" => id}} = object) do
with {:ok, _obj} = swap_object_with_tombstone(object),
deleted_activity = Activity.delete_by_ap_id(id),
- {:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do
+ {:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
+ {:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
{:ok, object, deleted_activity}
end
end
def prune(%Object{data: %{"id" => id}} = object) do
with {:ok, object} <- Repo.delete(object),
- {:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do
+ {:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
+ {:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
{:ok, object}
end
end
diff --git a/lib/pleroma/plugs/cache.ex b/lib/pleroma/plugs/cache.ex
new file mode 100644
index 000000000..a81a861d0
--- /dev/null
+++ b/lib/pleroma/plugs/cache.ex
@@ -0,0 +1,122 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.Cache do
+ @moduledoc """
+ Caches successful GET responses.
+
+ To enable the cache add the plug to a router pipeline or controller:
+
+ plug(Pleroma.Plugs.Cache)
+
+ ## Configuration
+
+ To configure the plug you need to pass settings as the second argument to the `plug/2` macro:
+
+ plug(Pleroma.Plugs.Cache, [ttl: nil, query_params: true])
+
+ Available options:
+
+ - `ttl`: An expiration time (time-to-live). This value should be in milliseconds or `nil` to disable expiration. Defaults to `nil`.
+ - `query_params`: Take URL query string into account (`true`), ignore it (`false`) or limit to specific params only (list). Defaults to `true`.
+
+ Additionally, you can overwrite the TTL inside a controller action by assigning `cache_ttl` to the connection struct:
+
+ def index(conn, _params) do
+ ttl = 60_000 # one minute
+
+ conn
+ |> assign(:cache_ttl, ttl)
+ |> render("index.html")
+ end
+
+ """
+
+ import Phoenix.Controller, only: [current_path: 1, json: 2]
+ import Plug.Conn
+
+ @behaviour Plug
+
+ @defaults %{ttl: nil, query_params: true}
+
+ @impl true
+ def init([]), do: @defaults
+
+ def init(opts) do
+ opts = Map.new(opts)
+ Map.merge(@defaults, opts)
+ end
+
+ @impl true
+ def call(%{method: "GET"} = conn, opts) do
+ key = cache_key(conn, opts)
+
+ case Cachex.get(:web_resp_cache, key) do
+ {:ok, nil} ->
+ cache_resp(conn, opts)
+
+ {:ok, record} ->
+ send_cached(conn, record)
+
+ {atom, message} when atom in [:ignore, :error] ->
+ render_error(conn, message)
+ end
+ end
+
+ def call(conn, _), do: conn
+
+ # full path including query params
+ defp cache_key(conn, %{query_params: true}), do: current_path(conn)
+
+ # request path without query params
+ defp cache_key(conn, %{query_params: false}), do: conn.request_path
+
+ # request path with specific query params
+ defp cache_key(conn, %{query_params: query_params}) when is_list(query_params) do
+ query_string =
+ conn.params
+ |> Map.take(query_params)
+ |> URI.encode_query()
+
+ conn.request_path <> "?" <> query_string
+ end
+
+ defp cache_resp(conn, opts) do
+ register_before_send(conn, fn
+ %{status: 200, resp_body: body} = conn ->
+ ttl = Map.get(conn.assigns, :cache_ttl, opts.ttl)
+ key = cache_key(conn, opts)
+ content_type = content_type(conn)
+ record = {content_type, body}
+
+ Cachex.put(:web_resp_cache, key, record, ttl: ttl)
+
+ put_resp_header(conn, "x-cache", "MISS from Pleroma")
+
+ conn ->
+ conn
+ end)
+ end
+
+ defp content_type(conn) do
+ conn
+ |> Plug.Conn.get_resp_header("content-type")
+ |> hd()
+ end
+
+ defp send_cached(conn, {content_type, body}) do
+ conn
+ |> put_resp_content_type(content_type, nil)
+ |> put_resp_header("x-cache", "HIT from Pleroma")
+ |> send_resp(:ok, body)
+ |> halt()
+ end
+
+ defp render_error(conn, message) do
+ conn
+ |> put_status(:internal_server_error)
+ |> json(%{error: message})
+ |> halt()
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 7b0075477..705dbc1c2 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -23,6 +23,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
action_fallback(:errors)
+ plug(Pleroma.Plugs.Cache, [query_params: false] when action in [:activity, :object])
plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
plug(:set_requester_reachable when action in [:inbox])
plug(:relay_active? when action in [:relay])
@@ -53,8 +54,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
{_, true} <- {:public?, Visibility.is_public?(object)} do
conn
+ |> set_cache_ttl_for(object)
|> put_resp_content_type("application/activity+json")
- |> json(ObjectView.render("object.json", %{object: object}))
+ |> put_view(ObjectView)
+ |> render("object.json", object: object)
else
{:public?, false} ->
{:error, :not_found}
@@ -96,14 +99,36 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
%Activity{} = activity <- Activity.normalize(ap_id),
{_, true} <- {:public?, Visibility.is_public?(activity)} do
conn
+ |> set_cache_ttl_for(activity)
|> put_resp_content_type("application/activity+json")
- |> json(ObjectView.render("object.json", %{object: activity}))
+ |> put_view(ObjectView)
+ |> render("object.json", object: activity)
else
- {:public?, false} ->
- {:error, :not_found}
+ {:public?, false} -> {:error, :not_found}
+ nil -> {:error, :not_found}
end
end
+ defp set_cache_ttl_for(conn, %Activity{object: object}) do
+ set_cache_ttl_for(conn, object)
+ end
+
+ defp set_cache_ttl_for(conn, entity) do
+ ttl =
+ case entity do
+ %Object{data: %{"type" => "Question"}} ->
+ Pleroma.Config.get([:web_cache_ttl, :activity_pub_question])
+
+ %Object{} ->
+ Pleroma.Config.get([:web_cache_ttl, :activity_pub])
+
+ _ ->
+ nil
+ end
+
+ assign(conn, :cache_ttl, ttl)
+ end
+
# GET /relay/following
def following(%{assigns: %{relay: true}} = conn, _params) do
conn