summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/pleroma/benchmark.ex36
-rw-r--r--lib/pleroma/activity.ex16
-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.ex77
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex14
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex4
-rw-r--r--lib/pleroma/web/router.ex1
-rw-r--r--lib/pleroma/web/web.ex18
10 files changed, 240 insertions, 57 deletions
diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex
index 4cc634727..84dccf7f3 100644
--- a/lib/mix/tasks/pleroma/benchmark.ex
+++ b/lib/mix/tasks/pleroma/benchmark.ex
@@ -27,7 +27,7 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
})
end
- def run(["render_timeline", nickname]) do
+ def run(["render_timeline", nickname | _] = args) do
start_pleroma()
user = Pleroma.User.get_by_nickname(nickname)
@@ -37,33 +37,37 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
|> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
|> Map.put("user", user)
- |> Map.put("limit", 80)
+ |> Map.put("limit", 4096)
|> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
|> Enum.reverse()
inputs = %{
- "One activity" => Enum.take_random(activities, 1),
- "Ten activities" => Enum.take_random(activities, 10),
- "Twenty activities" => Enum.take_random(activities, 20),
- "Forty activities" => Enum.take_random(activities, 40),
- "Eighty activities" => Enum.take_random(activities, 80)
+ "1 activity" => Enum.take_random(activities, 1),
+ "10 activities" => Enum.take_random(activities, 10),
+ "20 activities" => Enum.take_random(activities, 20),
+ "40 activities" => Enum.take_random(activities, 40),
+ "80 activities" => Enum.take_random(activities, 80)
}
+ inputs =
+ if Enum.at(args, 2) == "extended" do
+ Map.merge(inputs, %{
+ "200 activities" => Enum.take_random(activities, 200),
+ "500 activities" => Enum.take_random(activities, 500),
+ "2000 activities" => Enum.take_random(activities, 2000),
+ "4096 activities" => Enum.take_random(activities, 4096)
+ })
+ else
+ inputs
+ end
+
Benchee.run(
%{
- "Parallel rendering" => fn activities ->
- Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
- activities: activities,
- for: user,
- as: :activity
- })
- end,
"Standart rendering" => fn activities ->
Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
activities: activities,
for: user,
- as: :activity,
- parallel: false
+ as: :activity
})
end
},
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index a7844c36b..44f1e3011 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -173,6 +173,13 @@ defmodule Pleroma.Activity do
|> Repo.one()
end
+ def all_by_ids_with_object(ids) do
+ Activity
+ |> where([a], a.id in ^ids)
+ |> with_preloaded_object()
+ |> Repo.all()
+ end
+
def by_object_ap_id(ap_id) do
from(
activity in Activity,
@@ -308,10 +315,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 08bf1c752..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
@@ -251,22 +276,36 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def whoami(_conn, _params), do: {:error, :not_found}
- def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
- if nickname == user.nickname do
- conn
- |> put_resp_content_type("application/activity+json")
- |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
- else
- err =
- dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
- nickname: nickname,
- as_nickname: user.nickname
- )
+ def read_inbox(
+ %{assigns: %{user: %{nickname: nickname} = user}} = conn,
+ %{"nickname" => nickname} = params
+ ) do
+ conn
+ |> put_resp_content_type("application/activity+json")
+ |> put_view(UserView)
+ |> render("inbox.json", user: user, max_id: params["max_id"])
+ end
- conn
- |> put_status(:forbidden)
- |> json(err)
- end
+ def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
+ err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
+
+ conn
+ |> put_status(:forbidden)
+ |> json(err)
+ end
+
+ def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
+ "nickname" => nickname
+ }) do
+ err =
+ dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
+ nickname: nickname,
+ as_nickname: as_nickname
+ )
+
+ conn
+ |> put_status(:forbidden)
+ |> json(err)
end
def handle_user_activity(user, %{"type" => "Create"} = params) do
diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
index 8dfad7a54..c54462bb3 100644
--- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
@@ -427,6 +427,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> render("index.json", %{activities: activities, for: user, as: :activity})
end
+ def get_statuses(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do
+ limit = 100
+
+ activities =
+ ids
+ |> Enum.take(limit)
+ |> Activity.all_by_ids_with_object()
+ |> Enum.filter(&Visibility.visible_for_user?(&1, user))
+
+ conn
+ |> put_view(StatusView)
+ |> render("index.json", activities: activities, for: user, as: :activity)
+ end
+
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
true <- Visibility.visible_for_user?(activity, user) do
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 708b8c2fd..ef796cddd 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -73,14 +73,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
def render("index.json", opts) do
replied_to_activities = get_replied_to_activities(opts.activities)
- parallel = unless is_nil(opts[:parallel]), do: opts[:parallel], else: true
opts.activities
|> safe_render_many(
StatusView,
"status.json",
- Map.put(opts, :replied_to_activities, replied_to_activities),
- parallel
+ Map.put(opts, :replied_to_activities, replied_to_activities)
)
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index cfb973f53..7cd59acb2 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -443,6 +443,7 @@ defmodule Pleroma.Web.Router do
get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
get("/timelines/list/:list_id", MastodonAPIController, :list_timeline)
+ get("/statuses", MastodonAPIController, :get_statuses)
get("/statuses/:id", MastodonAPIController, :get_status)
get("/statuses/:id/context", MastodonAPIController, :get_context)
diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex
index bfb6c7287..687346554 100644
--- a/lib/pleroma/web/web.ex
+++ b/lib/pleroma/web/web.ex
@@ -66,23 +66,9 @@ defmodule Pleroma.Web do
end
@doc """
- Same as `render_many/4` but wrapped in rescue block and parallelized (unless disabled by passing false as a fifth argument).
+ Same as `render_many/4` but wrapped in rescue block.
"""
- def safe_render_many(collection, view, template, assigns \\ %{}, parallel \\ true)
-
- def safe_render_many(collection, view, template, assigns, true) do
- Enum.map(collection, fn resource ->
- Task.async(fn ->
- as = Map.get(assigns, :as) || view.__resource__
- assigns = Map.put(assigns, as, resource)
- safe_render(view, template, assigns)
- end)
- end)
- |> Enum.map(&Task.await(&1, :infinity))
- |> Enum.filter(& &1)
- end
-
- def safe_render_many(collection, view, template, assigns, false) do
+ def safe_render_many(collection, view, template, assigns \\ %{}) do
Enum.map(collection, fn resource ->
as = Map.get(assigns, :as) || view.__resource__
assigns = Map.put(assigns, as, resource)