Serve asciicast files from Phoenix

This commit is contained in:
Marcin Kulik 2017-05-16 14:54:13 +02:00
parent 51d6559b27
commit 867509a328
11 changed files with 135 additions and 2 deletions

View File

@ -28,6 +28,22 @@ config :phoenix, :template_engines,
config :bugsnag, api_key: System.get_env("BUGSNAG_API_KEY")
config :bugsnag, release_stage: Mix.env
if System.get_env("S3_BUCKET") do
config :asciinema, :file_store, Asciinema.FileStore.S3
config :asciinema, Asciinema.FileStore.S3,
region: System.get_env("S3_REGION"),
bucket: System.get_env("S3_BUCKET"),
path: "uploads/"
config :ex_aws,
access_key_id: [{:system, "AWS_ACCESS_KEY_ID"}, :instance_role],
secret_access_key: [{:system, "AWS_SECRET_ACCESS_KEY"}, :instance_role]
else
config :asciinema, :file_store, Asciinema.FileStore.Local
config :asciinema, Asciinema.FileStore.Local, path: "uploads/"
end
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env}.exs"

View File

@ -17,3 +17,6 @@ config :asciinema, Asciinema.Repo,
database: "asciinema_test",
hostname: "localhost",
pool: Ecto.Adapters.SQL.Sandbox
config :asciinema, :file_store, Asciinema.FileStore.Local
config :asciinema, Asciinema.FileStore.Local, path: "uploads/test/"

View File

@ -24,11 +24,11 @@ server {
try_files /maintenance.html $uri/index.html $uri.html $uri @phoenix;
}
location ~ ^/a/[^.]+\.(json|png)$ {
location ~ ^/a/[^.]+\.png$ {
try_files $uri $uri/index.html $uri.html @clj;
}
location ~ ^/a/[^.]+\.gif$ {
location ~ ^/a/[^.]+\.(json|gif)$ {
try_files $uri $uri/index.html $uri.html @phoenix;
}

View File

@ -0,0 +1,4 @@
defmodule Asciinema.FileStore do
@doc "Serves file at given path in store"
@callback serve_file(conn :: %Plug.Conn{}, path :: String.t, filename :: String.t) :: %Plug.Conn{}
end

View File

@ -0,0 +1,29 @@
defmodule Asciinema.FileStore.Local do
@behaviour Asciinema.FileStore
import Plug.Conn
alias Plug.MIME
def serve_file(conn, path, nil) do
do_serve_file(conn, path)
end
def serve_file(conn, path, filename) do
conn
|> put_resp_header("content-disposition", "attachment; filename=#{filename}")
|> do_serve_file(path)
end
defp do_serve_file(conn, path) do
conn
|> put_resp_header("content-type", MIME.path(path))
|> send_file(200, base_path() <> path)
|> halt
end
defp config do
Application.get_env(:asciinema, Asciinema.FileStore.Local)
end
defp base_path do
Keyword.get(config(), :path)
end
end

View File

@ -0,0 +1,36 @@
defmodule Asciinema.FileStore.S3 do
@behaviour Asciinema.FileStore
import Phoenix.Controller, only: [redirect: 2]
def serve_file(conn, path, nil) do
do_serve_file(conn, path)
end
def serve_file(conn, path, filename) do
do_serve_file(conn, path, ["response-content-disposition": "attachment; filename=#{filename}"])
end
defp do_serve_file(conn, path, query_params \\ []) do
{:ok, url} =
ExAws.Config.new(:s3, region: region())
|> ExAws.S3.presigned_url(:get, bucket(), base_path() <> path, query_params: query_params)
conn
|> redirect(external: url)
end
defp config do
Application.get_env(:asciinema, Asciinema.FileStore.S3)
end
defp region do
Keyword.get(config(), :region)
end
defp bucket do
Keyword.get(config(), :bucket)
end
defp base_path do
Keyword.get(config(), :path)
end
end

View File

@ -21,6 +21,7 @@ defmodule Asciinema.Mixfile do
applications: [
:bugsnag,
:cowboy,
:ex_aws,
:gettext,
:logger,
:phoenix,
@ -42,6 +43,7 @@ defmodule Asciinema.Mixfile do
defp deps do
[
{:cowboy, "~> 1.0"},
{:ex_aws, "~> 1.0"},
{:gettext, "~> 0.11"},
{:phoenix, "~> 1.2.1"},
{:phoenix_ecto, "~> 3.0"},

View File

@ -7,6 +7,7 @@
"decimal": {:hex, :decimal, "1.1.2", "79a769d4657b2d537b51ef3c02d29ab7141d2b486b516c109642d453ee08e00c", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "2.0.4", "03fd3b9aa508b1383eb38c00ac389953ed22af53811aa2e504975a3e814a8d97", [:mix], [{:db_connection, "~> 1.0-rc.2", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.7.7", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.11.2", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0-beta", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"ex_aws": {:hex, :ex_aws, "1.1.2", "b78416d0a84efe92c22e5df8ba7ca028d63b2b4228f95871a1ecf10324b6493b", [:mix], [{:configparser_ex, "~> 0.2.1", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.0.6", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"},
"fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], [], "hexpm"},
"gettext": {:hex, :gettext, "0.11.0", "80c1dd42d270482418fa158ec5ba073d2980e3718bacad86f3d4ad71d5667679", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.7.1", "e238c52c5df3c3b16ce613d3a51c7220a784d734879b1e231c9babd433ac1cb4", [:rebar3], [{:certifi, "1.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "4.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},

View File

@ -0,0 +1,23 @@
defmodule Asciinema.AsciicastFileController do
use Asciinema.Web, :controller
alias Asciinema.{Repo, Asciicast}
def show(conn, %{"id" => id} = params) do
asciicast = Repo.one!(Asciicast.by_id_or_secret_token(id))
path = Asciicast.json_store_path(asciicast)
filename = download_filename(asciicast, params)
file_store().serve_file(conn, path, filename)
end
defp download_filename(%Asciicast{id: id}, %{"dl" => _}) do
"asciicast-#{id}.json"
end
defp download_filename(_asciicast, _params) do
nil
end
defp file_store do
Application.get_env(:asciinema, :file_store)
end
end

View File

@ -6,6 +6,7 @@ defmodule Asciinema.Asciicast do
field :file, :string
field :stdout_data, :string
field :stdout_timing, :string
field :stdout_frames, :string
field :private, :boolean
field :secret_token, :string
end
@ -22,4 +23,11 @@ defmodule Asciinema.Asciicast do
end
end
end
def json_store_path(%__MODULE__{id: id, file: file}) when is_binary(file) do
"asciicast/file/#{id}/#{file}"
end
def json_store_path(%__MODULE__{id: id, stdout_frames: stdout_frames}) when is_binary(stdout_frames) do
"asciicast/stdout_frames/#{id}/#{stdout_frames}"
end
end

View File

@ -10,6 +10,17 @@ defmodule Asciinema.Router do
plug Asciinema.Auth
end
pipeline :asciicast_file do
plug :accepts, ["json"]
end
scope "/", Asciinema do
pipe_through :asciicast_file
# rewritten by TrailingFormat from /a/123.json to /a/123/json
get "/a/:id/json", AsciicastFileController, :show
end
pipeline :asciicast_animation do
plug :accepts, ["html"]
end