[Clojure] backblaze 이미지 업로드

준비

  1. https://www.backblaze.com/ 에서 backblaze 회원가입

  2. 버킷 생성


Create a Bucket 를 선택한다.


적당한 이름을 입력하고, Files in Bucket are 를 Public으로 변경한다.

Buckets에서 생성한 버킷의 내용을 보면, Bucket ID를 확인할 수 있다.

  1. 앱키 생성

Account > Application Keys 에서 Add a New Application Key를 눌러서 앱키를 생성한다.

생성후에 나오는, keyId, applicationKey를 확인한다.

한번만 볼 수 있으므로 잘 보관하도록 한다.

이렇게 얻은 3가지 값을 이용해서 이미지 업로드를 진행한다.

  • Bucket ID
  • keyId
  • applicationKey

1단계 : b2_authorize_account 요청

https://www.backblaze.com/apidocs/b2-authorize-account 에 GET 요청으로 authorization-token을 얻는다.

요청할때 헤더에 위에서 확인한 값을 넣는다.
<keyId>:<applicationKey> 를 base64로 인코딩한 값을 Authorization으로 전달한다.

b2_authorize_account 요청 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(:require [camel-snake-kebab.core :as csk]
[clojure.data.codec.base64 :as base64]
[clojure.data.json :as json]
[hato.client :as hato])

(defn str->base64 [s]
(String. (base64/encode (.getBytes s)) "UTF-8"))

(defn b2-authorize-account [key-id app-key]
(let [api-url "https://api.backblazeb2.com/b2api/v3/b2_authorize_account"
encoded (str->base64 (str key-id ":" app-key))
req-opts {:content-type :json
:headers {"Authorization" (str "Basic " encoded)}}]
(-> (hato/get api-url req-opts)
:body
(json/read-str :key-fn csk/->kebab-case-keyword))))

요청이 성공하면 다음의 결과를 확인할 수 있다.
키값은 케밥케이스로 변환하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{:account-id "<account-id>",
:api-info
{:storage-api
{:capabilities
["deleteKeys"
"bypassGovernance"
"readFileRetentions"
"readBucketReplications"
"writeFileRetentions"
"writeKeys"
"readFiles"
"writeBucketEncryption"
"writeBucketReplications"
"listBuckets"
"listFiles"
"readBucketRetentions"
"writeFiles"
"deleteFiles"
"writeBucketRetentions"
"listKeys"
"readBuckets"
"readFileLegalHolds"
"shareFiles"
"readBucketEncryption"
"writeBuckets"
"deleteBuckets"
"writeFileLegalHolds"],
:info-type "storageApi",
:download-url "https://fNNN.backblazeb2.com",
:s-3-api-url "https://s3.us-east-NNN.backblazeb2.com",
:api-url "https://apiNNN.backblazeb2.com",
:recommended-part-size 100000000,
:absolute-minimum-part-size 5000000,
:name-prefix nil,
:bucket-id nil,
:bucket-name nil}},
:application-key-expiration-timestamp nil,
:authorization-token "<authorization-token>"}

2단계 : b2-get-upload-url 요청

위에서 얻은 authorization-token, api-url를 사용하여 upload-url 을 요청한다.
추가적으로 버킷 생성후에 얻는 Bucket ID를 사용한다.

1
2
3
4
5
6
7
8
(defn b2-get-upload-url [bucket-id {:keys [token api-url]}]
(let [api-url (str api-url "/b2api/v2/b2_get_upload_url")
req-opts {:content-type :json
:headers {"Authorization" token}
:query-params {:bucketId bucket-id}}]
(-> (hato/get api-url req-opts)
:body
(json/read-str :key-fn csk/->kebab-case-keyword))))

결과

1
2
3
4
{:authorization-token "<authorization-token>",
:bucket-id "<bucket-id>",
:upload-url
"https://pod-NNN.backblaze.com/b2api/v2/b2_upload_file/f-/c-_v-_t-"}

3단계 : b2_upload_file 요청

위에서 받은 결과값에서, authorization-token, upload-url 를 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(defn sha1-str [bytes]
(->> (-> "sha1"
java.security.MessageDigest/getInstance
(.digest bytes))
(map #(.substring
(Integer/toString
(+ (bit-and % 0xff) 0x100) 16) 1))
(apply str)))

(defn upload-file [base-64 {:keys [token api-url file-name]
:or {file-name "image.png"}
:as _opts}]
(let [file-bytes (base64/decode (.getBytes base-64))
req-opts {:headers {"Authorization" token
"Content-Type" "b2/x-auto"
"X-Bz-File-Name" file-name
"X-Bz-Content-Sha1" (sha1-str file-bytes)}
:body file-bytes}]
(-> (hato/post api-url req-opts)
:body
(json/read-str :key-fn csk/->kebab-case-keyword))))

정상적으로 이미지가 업로드가 되면 다음 결과를 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{:legal-hold {:is-client-authorized-to-read true, :value nil},
:file-id "<file-id>",
:server-side-encryption {:algorithm nil, :mode nil},
:file-info {},
:account-id "<account-id>",
:content-length 941,
:file-name "<file-name.png>",
:content-sha-1 "1d114e4981aaaaaaed692c9ab312b2bcacea0",
:content-type "image/png",
:action "upload",
:bucket-id "<bucket-id>",
:content-md-5 "7ecdcc00cd50ecb2fffff43077287878",
:file-retention {:is-client-authorized-to-read true, :value {:mode nil, :retain-until-timestamp nil}},
:upload-timestamp 1688775435757}

모두 완료되었으면, 웹 페이지로 돌아가서 이미지 파일이 있는지 확인하자.

링크

공유하기