首页 > 解决方案 > 如何在 Elixir / Plug 中访问文件上传 / POST

问题描述

有很多关于如何使用 Phoenix 执行此操作的信息,但我有意避免使用 Phoenix,直到我了解有关 Elixir 工作原理的更多信息。

为此,我有以下Plug.Router路径:

defmodule ElixirHttpServer do
  use Plug.Router
  use Plug.ErrorHandler

  plug(Plug.Parsers, parsers: [:urlencoded, {:multipart, length: 1_000_000_000}])
  plug(Plug.Logger)
  plug(:match)
  plug(:dispatch)


  post "/upload" do
    IO.inspect(Plug.Conn.read_body(conn), label: "body")
    send_resp(conn, 201, "Uploaded")
  end
end

它接受来自表单的文件上传,在EEx模板中呈现:

<form action="/upload" method="post" enctype="multipart/form-data">
  <input type="file">
  <input type="submit">
</form>

通过此表单上传文件时,我得到以下输出IO.inspect(Plug.Conn.read_body(conn))

18:11:13.097 [info]  POST /upload                                                                                                                                                                                                                                                [205/3062]
body: {:ok, "",
 %Plug.Conn{
   adapter: {Plug.Cowboy.Conn, :...},
   assigns: %{},
   before_send: [#Function<1.128679493/1 in Plug.Logger.call/2>],
   body_params: %{},
   cookies: %Plug.Conn.Unfetched{aspect: :cookies},
   halted: false,
   host: "localhost",
   method: "POST",
   owner: #PID<0.750.0>,
   params: %{},
   path_info: ["upload"],
   path_params: %{},
   port: 8080,
   private: %{
     plug_multipart: :done,
     plug_route: {"/upload",
      #Function<1.2199674/2 in ElixirHttpServer.do_match/4>}
   },
   query_params: %{},
   query_string: "",
   remote_ip: {127, 0, 0, 1},
   req_cookies: %Plug.Conn.Unfetched{aspect: :cookies},
   req_headers: [
     {"accept",
      "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"},
     {"accept-encoding", "gzip, deflate, br"},
     {"accept-language", "en-US,en;q=0.9"},
     {"cache-control", "no-cache"},
     {"connection", "keep-alive"},
     {"content-length", "44"},
     {"content-type",
      "multipart/form-data; boundary=----WebKitFormBoundary4wTVqggydpkBg30n"},
     {"host", "localhost:8080"},
     {"origin", "http://localhost:8080"},
     {"pragma", "no-cache"},
     {"referer", "http://localhost:8080/"},
     {"sec-ch-ua",
      "\" Not;A Brand\";v=\"99\", \"Google Chrome\";v=\"91\", \"Chromium\";v=\"91\""},
     {"sec-ch-ua-mobile", "?0"},
     {"sec-fetch-dest", "document"},
     {"sec-fetch-mode", "navigate"},
     {"sec-fetch-site", "same-origin"},
     {"sec-fetch-user", "?1"},
     {"upgrade-insecure-requests", "1"},
     {"user-agent",
      "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36"}
   ],
   request_path: "/upload",
   resp_body: nil,
   resp_cookies: %{},
   resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}],
   scheme: :http,
   script_name: [],
   secret_key_base: nil,
   state: :unset,
   status: nil
 }}

18:11:13.099 [info]  Sent 201 in 2ms

我已经多次阅读Plug.Upload文档,但它似乎主要是您可以使用的结构。

Plug.Parsers文档说明了以下内容,但我不知道“启动:plug应用程序”的实际含义:

文件处理

如果通过任何解析器上传文件,Plug 会将上传的内容流式传输到临时目录中的文件,以避免将整个文件加载到内存中。为此,需要启动 :plug 应用程序才能进行文件上传。有关如何处理上传文件的更多详细信息,请参阅 Plug.Upload 的文档。

上传文件时,标识该文件的请求参数将是 Plug.Upload 结构,其中包含有关上传文件(例如文件名和内容类型)以及文件存储位置的信息。

我添加:plug到我的extra_applications,但这似乎并没有改变任何东西:

  def application do
    [
      extra_applications: [:plug, :plug_cowboy, :logger],
      mod: {ElixirHttpServer.Application, []}
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      {:plug_cowboy, "~> 2.4"},
      {:hackney, "~> 1.17.0"},
      {:ex_aws, "~> 2.1"},
      {:ex_aws_s3, "~> 2.0"},
      {:configparser_ex, "~> 4.0"},
      {:sweet_xml, "~> 0.6"}
    ]
  end
end

作为参考,这是我的主管应用程序:

defmodule ElixirHttpServer.Application do
  @moduledoc "OTP application for S3 bucket list/upload"

  use Application
  require Logger

  def start(_type, _args) do
    children = [
      {Plug.Cowboy, scheme: :http, plug: ElixirHttpServer, options: [port: cowboy_port()]}
    ]

    opts = [strategy: :one_for_one, name: ElixirHttpServer.Supervisor]

    Logger.info("Starting the application...")
    Supervisor.start_link(children, opts)
  end

  defp cowboy_port(), do: Application.get_env(:elixir_http_server, :cowboy_port, 8080)
end

标签: elixirplug

解决方案


我错过了我的元素的name属性。input type="file"

<input type="file" name="file">成功了。


推荐阅读