В этой статье я немного отклонюсь от предыдущей, чтобы шаг за шагом рассмотреть аспект взаимодействия с AWS S3 с помощью Clojure.
Думаю, AWS S3 не нуждается в представлении. На всякий случай, вот краткое изложение того, что такое S3, от самой Amazon:
Amazon Simple Storage Service (Amazon S3) — это сервис хранения объектов, который предлагает лучшую в отрасли масштабируемость, доступность данных, безопасность и производительность. Вы можете использовать Amazon S3 для хранения и извлечения любого объема данных в любое время и из любого места.
AWS S3, вероятно, является наиболее часто используемым сервисом хранения для пользователей AWS с вариантами использования, варьирующимися от таких, как мобильные/веб-приложения, до больших данных, машинного обучения и многого другого.
Предпосылки
Leiningen
— Убедитесь, что он установлен и находится в PATH. Необходим для работы с проектами в Clojure, что упрощает их создание и настройку с необходимыми зависимостями. Также необходим для запуска Clojure REPL. Если у вас его нет, вы можете пройти процесс быстрой установки здесь — https://leiningen.org/#installAWS account
— создать пользователя в IAM сProgrammatic Access
и с применением политики разрешенийAmazonS3FullAccess
. ЗапишитеAccess Key ID
иSecret Access Key
для пользователя. Если вам нужна помощь в этом, вы можете обратиться к шагам, подробно описанным здесь — Создание пользователя AWS IAM.
3. Наконец, не помешает некоторое знакомство с кодом Clojure.
Давайте приступим.
В Clojure есть две мощные библиотеки, которые помогают нам взаимодействовать с AWS S3.
а. Амазоника
б. aws-апи
Здесь я буду работать с библиотекой aws-api
через примеры кода. Библиотеку amazonica
мы обсудим в другой статье.
Кложур проект
Как всегда, давайте начнем с создания проекта Clojure для работы.
Откройте терминал и введите, как показано ниже:
lein new app aws-s3-demo
Это создаст папку с именем aws-s3-demo
в каталоге, из которого вы запустили указанную выше команду. Перейдите в каталог aws-s3-demo
и просмотрите список его содержимого. Должна получиться структура папок, как показано ниже:
. ├── CHANGELOG.md ├── LICENSE ├── README.md ├── doc │ └── intro.md ├── project.clj ├── resources ├── src │ └── aws_s3_demo │ └── core.clj └── test └── aws_s3_demo └── core_test.clj
Давайте теперь настроим зависимости. Откройте файл project.clj
, расположенный в aws-s3-demo
(корневой каталог проекта), и обновите раздел dependencies
. Окончательный файл project.clj
должен выглядеть следующим образом:
(defproject aws-s3-demo "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.10.3"] [com.cognitect.aws/api "0.8.596"] [com.cognitect.aws/endpoints "1.1.12.307"] [com.cognitect.aws/s3 "822.2.1145.0"]] :main ^:skip-aot aws-s3-demo.core :target-path "target/%s" :profiles {:uberjar {:aot :all :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})
Те, что выделены жирным шрифтом выше, — это библиотеки Clojure AWS (aws-api). Чтобы убедиться, что зависимости загружены, давайте запустим:
lein deps
Настроив проект и его зависимости, давайте начнем изучение.
Работа с библиотекой aws-api
Убедитесь, что вы находитесь в корневом каталоге проекта (aws-s3-demo
) и запустите REPL с помощью следующей команды:
lein repl
Должно появиться такое приглашение:
nREPL server started on port 60598 on host 127.0.0.1 - nrepl://127.0.0.1:60598 REPL-y 0.5.1, nREPL 0.8.3 Clojure 1.10.3 Java HotSpot(TM) 64-Bit Server VM 16.0.2+7-67 Docs: (doc function-name-here) (find-doc "part-of-name-here") Source: (source function-name-here) Javadoc: (javadoc java-object-or-class-here) Exit: Control+D or (exit) or (quit) Results: Stored in vars *1, *2, *3, an exception in *e aws-s3-demo.core=>
В командной строке давайте начнем с require
библиотек:
(require '[cognitect.aws.client.api :as aws] '[cognitect.aws.credentials :as credentials])
Рабочий процесс, которому нужно следовать, довольно прост:
Create a client for a specific AWS service → invoke an Operation on the service ( with optional request parameters )
Давайте продолжим и создадим client
для сервиса s3 на AWS.
(def s3-client (aws/client {:api :s3 :region "us-east-1" :credentials-provider (credentials/basic-credentials-provider {:access-key-id <your_access_key_id> :secret-access-key <your_secret_access_key>})})) ;; create a s3 client (def s3-client (aws/client {:api :s3 :region "us-east-1" :credentials-provider (credentials/basic-credentials-provider {:access-key-id "<your_access_key_id>" :secret-access-key "<your_secret_access_key>"})}))
Давайте разберем его:
- Мы пытаемся создать объект
s3-client
, который возвращается вызовом методаaws/client
в библиотеке. В этот метод вы передаетеmap
информации. - Эта карта содержит информацию о том, для какого
AWS service
вы хотите создать клиент, указанный ключом:api
(здесь написаноs3
) - Ключ
:region
указывает конечную точку/регион AWS, в котором вы хотите, чтобы клиентские операции отражались. :credentials-provider
использует метод из библиотеки для указанияaccess-key
иsecret-key
(убедитесь, что вы заменили текст<your_access_key_id>
и<your_secret_access_key>
соответствующими значениями для вашего пользователя AWS)
У нас есть готовый клиент S3. Давайте использовать его.
Примечание. Чтобы уточнить, текст, показанный после ;;
, является результатом, а не частью кода, который нужно ввести в REPL
Давайте попробуем перечислить текущие корзины S3. Введите ниже в REPL:
(-> s3-client (aws/invoke {:op :ListBuckets}) (:Buckets)) ;; []
Здесь я использую макрос thread-first
в Clojure, чтобы лучше представить поток. (Макросы thread-first
и thread-last
— это очень мощные конструкции в Clojure, которые позволяют создавать понятный и идиоматический код)
Что мы делаем выше:
- возьмите
s3-client
, который мы создали - используйте метод
aws/invoke
для выполнения операции, указанной картой на клиенте. Карта здесь указывает операцию (:op
) с именем:ListBuckets
, поскольку это то, что мы хотим. - из результата, возвращаемого
aws/invoke
(который снова является картой), мы извлекаем значение, связанное с ключом:Buckets
Это возвращает пустой вектор []
, как и сейчас, поскольку мы еще не создали ни одной корзины S3 (если у пользователя AWS, с которым вы работаете, уже есть несколько корзин, они появятся).
Давайте теперь создадим корзину S3. Введите ниже:
(def bucket-name "clojures3demo") (-> s3-client (aws/invoke {:op :CreateBucket :request {:Bucket bucket-name}})) ;; {:Location "/clojures3demo"}
Здесь снова, как и раньше, мы:
- определить имя для ведра —
bucket-name
- используйте
s3-client
, который мы создали - передать карту в
aws/invoke
с указанием операции:CreateBucket
над ней с дополнительным параметром карты:request
с указанием имени корзины, которое мы определили - В случае успеха возвращает карту
давайте проверим, действительно ли ведро было создано, перечислив ведра, как и раньше.
(-> s3-client (aws/invoke {:op :ListBuckets}) (:Buckets)) ;; [{:Name "clojures3demo", :CreationDate #inst "2022-10-06T06:59:43.000-00:00"}]
Мы видим, что он возвращает детали только что созданного ведра, круто!!
От пустых ведер мало толку :-) Давайте загрузим в него какой-нибудь контент.
(-> s3-client (aws/invoke {:op :PutObject :request {:Bucket bucket-name :Key "hello.txt" :Body (.getBytes "Hello, World")}})) ;; {:ETag "\"82bb413746aee42f89dea2b59614f9ef\""}
Здесь мы снова:
- вызвать операцию
:PutObject
для передачиs3-client
на картеrequest
- карта
request
содержит информацию оbucket-name
, где вы хотите создать объект,Key
, который в основном являетсяfully qualified path to the object/file on the S3 bucket that you want created
и - затем фактическое содержимое объекта,
:Body
, которое представляет собой не что иное, как поток байтов, представляющий строкуHello, World
Теперь объект создан и может быть проверен следующим образом:
(-> s3-client (aws/invoke {:op :ListObjectsV2 :request {:Bucket bucket-name}})) ;; {:Prefix "", :Contents [{:Key "hello.txt", :LastModified #inst "2022-10-06T07:41:34.000-00:00", :ETag "\"82bb413746aee42f89dea2b59614f9ef\"", :Size 12, :StorageClass "STANDARD"}], :MaxKeys 1000, :IsTruncated false, :Name "clojures3demo", :KeyCount 1}
→ вызов операции ListObjectsV2
на клиенте сообщает, что в корзине есть файл с Key — hello.txt
, а также дает :KeyCount
из 1
, так как на данный момент это единственный файл в корзине, фантастика!!
Просто чтобы убедиться, что все в порядке, давайте создадим еще один файл/объект в корзине S3.
(-> s3-client (aws/invoke {:op :PutObject :request {:Bucket bucket-name :Key "wonderful.txt" :Body (.getBytes "What a wonderful day!!")}})) ;; {:ETag "\"7e7634f1a88c50de19060fec6d4fefdc\""}
Давайте проверим, что это сработало:
(-> s3-client (aws/invoke {:op :ListObjectsV2 :request {:Bucket bucket-name}})) ;; {:Prefix "", :Contents [{:Key "hello.txt", :LastModified #inst "2022-10-06T07:41:34.000-00:00", :ETag "\"82bb413746aee42f89dea2b59614f9ef\"", :Size 12, :StorageClass "STANDARD"} {:Key "wonderful.txt", :LastModified #inst "2022-10-06T08:23:48.000-00:00", :ETag "\"7e7634f1a88c50de19060fec6d4fefdc\"", :Size 22, :StorageClass "STANDARD"}], :MaxKeys 1000, :IsTruncated false, :Name "clojures3demo", :KeyCount 2}
Все отлично выглядит!
Теперь давайте вернем содержимое созданного нами файла hello.txt
.
В REPL введите:
(-> s3-client (aws/invoke {:op :GetObject :request {:Bucket bucket-name :Key "hello.txt"}})) ;; {:LastModified #inst "2022-10-06T07:41:34.000-00:00", :ETag "\"82bb413746aee42f89dea2b59614f9ef\"", :Metadata {}, :ContentLength 12, :ContentType "application/octet-stream", :AcceptRanges "bytes", :Body #object[java.io.BufferedInputStream 0x131cadc1 "java.io.BufferedInputStream@131cadc1"]}
Мы вызываем операцию GetObject
на клиенте, указав bucket name
и object Key
для получения.
Но тут произошло кое-что очень интересное. Вместо того, чтобы получить содержимое файла/объекта, указанного :Key
hello.txt
, мы получаем что-то загадочное. По умолчанию ответ Body
представляет собой stream
, содержащий текст, ожидающий чтения.
Мы можем получить содержимое следующим образом:
(slurp (:Body *1)) ;; "Hello, World"
объединение этих двух вместе даст что-то вроде этого:
(-> s3-client (aws/invoke {:op :GetObject :request {:Bucket bucket-name :Key "hello.txt"}}) (:Body) (slurp)) ;; "Hello, World"
Давайте попробуем это и со вторым файлом, который мы создали:
(-> s3-client (aws/invoke {:op :GetObject :request {:Bucket bucket-name :Key "wonderful.txt"}}) (:Body) (slurp)) ;; "What a wonderful day!!"
Ура! это прекрасно работает.
Благодаря этому мы смогли успешно создать корзину S3, загрузить в нее контент и вернуть загруженный контент.
Теперь займемся уборкой дома.
Чтобы удалить файлы/объекты, загруженные в корзину S3, введите в REPL следующее:
(-> s3-client (aws/invoke {:op :DeleteObject :request {:Bucket bucket-name :Key "hello.txt"}})) ;; {}
Давайте проверим, что файл/объект действительно был удален:
(-> s3-client (aws/invoke {:op :ListObjectsV2 :request {:Bucket bucket-name}})) ;; {:Prefix "", :Contents [{:Key "wonderful.txt", :LastModified #inst "2022-10-06T08:23:48.000-00:00", :ETag "\"7e7634f1a88c50de19060fec6d4fefdc\"", :Size 22, :StorageClass "STANDARD"}], :MaxKeys 1000, :IsTruncated false, :Name "clojures3demo", :KeyCount 1}
hello.txt
был удален.
Давайте также удалим файл wonderful.txt
.
(-> s3-client (aws/invoke {:op :DeleteObject :request {:Bucket bucket-name :Key "wonderful.txt"}})) ;; {}
и проверьте то же самое:
(-> s3-client (aws/invoke {:op :ListObjectsV2 :request {:Bucket bucket-name}})) ;; {:Prefix "", :MaxKeys 1000, :IsTruncated false, :Name "clojures3demo", :KeyCount 0}
Объекты удаляются. Давайте также удалим корзину S3 и закончим очистку.
(-> s3-client (aws/invoke {:op :DeleteBucket :request {:Bucket bucket-name}})) ;; {}
проверка:
(-> s3-client (aws/invoke {:op :ListBuckets}) (:Buckets)) ;; []
Ведро пропало!
Объединяем весь поток фрагментов кода, с которыми мы работали:
;; require the aws-api library (require '[cognitect.aws.client.api :as aws] '[cognitect.aws.credentials :as credentials]) ;; create a s3 client (def s3-client (aws/client {:api :s3 :region "us-east-1" :credentials-provider (credentials/basic-credentials-provider {:access-key-id "<your_access_key_id>" :secret-access-key "<your_secret_access_key>"})})) ;; list the S3 buckets (-> s3-client (aws/invoke {:op :ListBuckets}) (:Buckets)) ;; create a bucket (def bucket-name "clojures3demo") (-> s3-client (aws/invoke {:op :CreateBucket :request {:Bucket bucket-name}})) ;; Create a file/object with some string content on the bucket (-> s3-client (aws/invoke {:op :PutObject :request {:Bucket bucket-name :Key "hello.txt" :Body (.getBytes "Hello, World")}})) ;; List the objects/files in the bucket (-> s3-client (aws/invoke {:op :ListObjectsV2 :request {:Bucket bucket-name}})) ;; Create a file/object with some string content on the bucket (-> s3-client (aws/invoke {:op :PutObject :request {:Bucket bucket-name :Key "wonderful.txt" :Body (.getBytes "What a wonderful day!!")}})) ;; Fetch the contents of an object/file in the bucket (-> s3-client (aws/invoke {:op :GetObject :request {:Bucket bucket-name :Key "hello.txt"}}) (--> (slurp :Body))) ;; Fetch the contents of an object/file in the bucket (-> s3-client (aws/invoke {:op :GetObject :request {:Bucket bucket-name :Key "hello.txt"}}) (:Body) (slurp)) ;; Fetch the contents of an object/file in the bucket (-> s3-client (aws/invoke {:op :GetObject :request {:Bucket bucket-name :Key "wonderful.txt"}}) (:Body) (slurp)) ;; Delete the object/file specified by :Key (-> s3-client (aws/invoke {:op :DeleteObject :request {:Bucket bucket-name :Key "hello.txt"}})) ;; Delete the object/file specified by :Key (-> s3-client (aws/invoke {:op :DeleteObject :request {:Bucket bucket-name :Key "wonderful.txt"}})) ;; Delete the bucket (-> s3-client (aws/invoke {:op :DeleteBucket :request {:Bucket bucket-name}})) ;; Delete the bucket (-> s3-client (aws/invoke {:op :DeleteBucket :request {:Bucket bucket-name}}))
Потрясающий! это должно было дать вам представление о том, как работать с AWS S3 в Clojure. Надеюсь, это было полезно.
В следующей статье давайте рассмотрим, как использовать другую библиотеку Clojure amazonica
для взаимодействия с сервисами AWS, взяв в качестве примера другой сервис AWS.