แงะ ex_aws_s3 ไลบรารี่สำหรับใช้งาน AWS S3 ผ่าน Elixir ว่ามันได้ response แบบไหนกันแน่
Tags: elixir, aws, s3
Published: 2021-03-02 00:38:11

งานที่ทำต้องการ upload file ขึ้น service ที่ compatible กับ AWS S3 เลยเลือกใช้ library [ex_aws](https://hexdocs.pm/ex_aws) ช่วยในการเรียกใช้ S3 API ในการอัพโหลดไฟล์ ซึ่งต้องใช้งานร่วมกับ library [ex_aws_s3](https://hexdocs.pm/ex_aws_s3)

สิ่งที่ทำให้ต้องมาเขียนโพสต์นี้ก็เพราะว่า document ของ library นี้มันไม่เคลียร์ว่า response ที่มันตอบกลับมานั้นจะอยู่ในรูปแบบไหนกันแน่ เช่นต้องการอัพโหลดไฟล์ในตัวอย่างเขียนไว้แค่นี้

```elixir
"path/to/big/file"
|> ExAws.S3.Upload.stream_file
|> ExAws.S3.upload("my-bucket", "path/on/s3")
|> ExAws.request #=> {:ok, :done}
```

กับ get_object เขียนไว้แค่นี้

```elixir
ExAws.S3.get_object("my-bucket", "image.png") |> ExAws.request
```

แต่จริงๆแล้วตอน upload จะได้ reponse แบบนี้

```elixir
{:ok,
 %{
   body: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<CompleteMultipartUploadResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Location>http://localhost:9000/my-bucket/mix.exs</Location><Bucket>my-bucket</Bucket><Key>mix.exs</Key><ETag>&#34;5ca9425fced3f0bc1670a8f1fccbe29d-1&#34;</ETag></CompleteMultipartUploadResult>",
   headers: [
     {"Accept-Ranges", "bytes"},
     {"Cache-Control", "no-cache"},
     {"Content-Length", "311"},
     {"Content-Security-Policy", "block-all-mixed-content"},
     {"Content-Type", "application/xml"},
     {"ETag", "\"5ca9425fced3f0bc1670a8f1fccbe29d-1\""},
     {"Server", "MinIO"},
     {"Vary", "Origin"},
     {"X-Accel-Buffering", "no"},
     {"X-Amz-Request-Id", "166840A0DBB74310"},
     {"X-Xss-Protection", "1; mode=block"},
     {"Date", "Mon, 01 Mar 2021 15:12:06 GMT"}
   ],
   status_code: 200
 }}
```

แต่จริงๆแล้วตอน get_object จะได้ reponse แบบนี้

```elixir
{:ok,
 %{
   body: "defmodule S3Example.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :s3_example,\n      version: \"0.1.0\",\n      elixir: \"~> 1.11\",\n      start_permanent: Mix.env() == :prod,\n      deps: deps()\n    ]\n  end\n\n  # Run \"mix help compile.app\" to learn about applications.\n  def application do\n    [\n      extra_applications: [:logger]\n    ]\n  end\n\n  # Run \"mix help deps\" to learn about dependencies.\n  defp deps do\n    [\n      {:ex_aws, \"~> 2.1\"},\n      {:ex_aws_s3, \"~> 2.1\"},\n      {:hackney, \"~> 1.9\"},\n      {:jason, \"~> 1.2\"},\n      {:sweet_xml, \"~> 0.6\"}\n      # {:dep_from_hexpm, \"~> 0.3.0\"},\n      # {:dep_from_git, git: \"https://github.com/elixir-lang/my_dep.git\", tag: \"0.1.0\"}\n    ]\n  end\nend\n",
   headers: [
     {"Accept-Ranges", "bytes"},
     {"Content-Length", "717"},
     {"Content-Security-Policy", "block-all-mixed-content"},
     {"Content-Type", "application/octet-stream"},
     {"ETag", "\"5ca9425fced3f0bc1670a8f1fccbe29d-1\""},
     {"Last-Modified", "Mon, 01 Mar 2021 15:12:06 GMT"},
     {"Server", "MinIO"},
     {"Vary", "Origin"},
     {"X-Amz-Request-Id", "166840A6DA0B6B08"},
     {"X-Xss-Protection", "1; mode=block"},
     {"Date", "Mon, 01 Mar 2021 15:12:31 GMT"}
   ],
   status_code: 200
 }}
```

จะเห็นว่าก็เป็น pattern `{:ok, %{body: , headers: [], status_code: }}`

ด้วยความสงสัยเลยไปแงะโค้ดดูพบว่า concept ของมันคือให้สร้าง Operation struct เพื่อเก็บขอมูลของ Request แล้วให้ implement protocol [ExAws.Operation](https://hexdocs.pm/ex_aws/ExAws.Operation.html) ที่มี function `perform` กับ `stream!`

เสร็จแล้วเมื่อเราส่งต่อค่าของ operation ไปให้ฟังก์ชัน `ExAws.request` มันจะเรียก `perform` ให้นั่นเอง ซึ่งพอไปดูโค้ดของ `ExAws.request` จะเห็นโค้ดแบบนี้

```elixir
ExAws.Operation.perform(op, ExAws.Config.new(op.service, config_overrides))
```

สรุปว่า จริงๆแล้วเวลาเห็นโค้ดแบบด้านบน เราต้องดูว่ามันสร้างค่าของ Operation struct ตัวไหนก่อนจะเรียกไปหา `ExAws.request` แล้วก็ไปดู `perform` ของมันว่าทำอะไรแล้วได้อะไรกลับออกมา เช่น `ExAws.S3.upload` นั้นได้ operation `ExAws.S3.Upload` ก็ต้องไปไล่ๆหาในฟังก์ชัน `perform` ของ operation Upload นั่นเอง

จะเห็นว่าตัว document ของ `ex_aws` กับ `ex_aws_s3` ยังปรับปรุงได้อีกเพื่อให้คนที่ต้องใช้งานเข้าใจได้ง่ายขึ้นว่าจะได้ response แบบไหนออกมา เดี๋ยวว่างๆวันหยุดจะลองไปช่วย contribute ดู

## ขอฝาก Buy Me a Coffee
สำหรับท่านใดที่อ่านแล้วชอบโพสต์ต่างๆของผมที่นี่ ต้องการสนับสนุนค่ากาแฟเล็กๆน้อยๆ สามารถสนับสนุนผมได้ผ่านทาง Buy Me a Coffee คลิ๊กที่รูปด้านล่างนี้ได้เลยครับ

<a href="https://www.buymeacoffee.com/weerasak" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>

ส่วนท่านใดไม่สะดวกใช้บัตรเครดิต หรือ Paypal สามารถสนับสนุนผมได้ผ่านทาง PromptPay โดยดู QR Code ได้จากโพสต์ที่พินเอาไว้ได้ที่ Page DevDose ครับ https://web.facebook.com/devdoseth

ขอบคุณครับ 🙏