package awskit-s3

  1. Overview
  2. Docs
S3 client core — objects, buckets, multipart, and policies

Install

dune-project
 Dependency

Authors

Maintainers

Sources

awskit-v0.1.0.tbz
sha256=788e91d57b9eed047bdef011aec476e94588be20e2e2f1b8495cf48b1a90cf0f
sha512=0d441d599f3f3efb766270258bb4d8c9cd660943eb7f90ced0ec6f61a6790f5fb8977ca5cf87f466d84701ee34dbfdf81fe5043b568a2236411f577e698c6d1e

doc/src/awskit-s3/awskit_s3.ml.html

Source file awskit_s3.ml

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
include Awskit_s3_intf
module Credentials = Awskit.Credentials
module Endpoint = Awskit.Endpoint
module Region = Awskit.Region
module Error = Common.Error
module Metadata = Metadata
module Storage_class = Storage_class
module Tag = Tag
module Range = Range
module Endpoint_resolver = Endpoint_resolver
module Object = Object
module Bucket = Bucket
module Multipart = Multipart
module Transfer = Transfer
module Policy = Policy
module Presigned = Presigned
module Put_object = Object.Put
module Get_object = Object.Get
module Head_object = Object.Head
module Delete_object = Object.Delete
module Delete_objects = Object.Delete_many
module Copy_object = Object.Copy
module List_objects_v2 = Object.List
module List_object_versions = Object.Versions
module Create_bucket = Bucket.Create
module Delete_bucket = Bucket.Delete
module Head_bucket = Bucket.Head
module List_buckets = Bucket.List_buckets
module Get_bucket_location = Bucket.Get_location
module Create_multipart_upload = Multipart.Create
module Upload_part = Multipart.Upload_part
module Complete_multipart_upload = Multipart.Complete
module Abort_multipart_upload = Multipart.Abort
module List_parts = Multipart.List_parts

type addressing_style = [ `Auto | `Path | `Virtual_hosted ]

type endpoint_variant =
  [ `Regional
  | `Dualstack
  | `Fips
  | `Fips_dualstack
  | `Accelerate
  | `Accelerate_dualstack ]

type endpoint_config = Endpoint_resolver.t

let endpoint_config ?addressing_style ?endpoint_variant ?scheme ?endpoint () =
  Endpoint_resolver.create ?addressing_style ?endpoint_variant ?scheme ?endpoint
    ()

let default_endpoint_config = Endpoint_resolver.default

module Make (R : RUNTIME) = struct
  type connection = R.connection
  type 'a io = 'a R.t
  type request_body = R.request_body
  type response_body_reader = R.response_body_reader

  module Context = struct
    module R = R

    type connection = R.connection
    type 'a io = 'a R.t
    type request_body = R.request_body
    type response_body_reader = R.response_body_reader

    let bind = R.bind
    let ( let* ) = R.bind
    let return = R.return
    let return_ok value = R.return (Ok value)
    let return_error error = R.return (Error error)
    let empty_hash = Awskit.Body.Payload_hash.sha256_of_string ""
    let endpoint_config conn = R.s3_endpoint_config conn

    let object_request conn ~bucket ~key =
      Endpoint_resolver.resolve_object_request (endpoint_config conn)
        ~region:(R.region conn) ~bucket ~key

    let bucket_request conn ~bucket ~suffix ~signing_suffix =
      Endpoint_resolver.resolve_bucket_request (endpoint_config conn)
        ~region:(R.region conn) ~bucket ~suffix ~signing_suffix

    let root_request conn =
      match
        Endpoint_resolver.endpoint (endpoint_config conn)
          ~region:(R.region conn)
      with
      | Error _ as error -> error
      | Ok endpoint ->
          Ok
            {
              Endpoint_resolver.Request.endpoint;
              path = "/";
              signing_path = "/";
              style = `Path;
            }

    let read_body reader ~max_size =
      let buffer = Buffer.create 4096 in
      let chunk = Bytes.create 8192 in
      let rec loop total =
        let* read =
          R.Response_body.read reader chunk ~off:0 ~len:(Bytes.length chunk)
        in
        match read with
        | Error error -> return (Error error)
        | Ok 0 -> return_ok (Buffer.contents buffer)
        | Ok n ->
            let total = Int64.add total (Int64.of_int n) in
            if Int64.compare total max_size > 0 then
              return_error
                (Awskit.Error.body ~limit:max_size
                   "response body exceeded max_bytes")
            else begin
              Buffer.add_subbytes buffer chunk 0 n;
              loop total
            end
      in
      loop 0L

    let read_response_body body ~max_size =
      R.Response_body.with_reader body ~consume:(read_body ~max_size)

    let discard_response_body = R.Response_body.discard

    let service_error response body =
      Awskit.Error.service
        {
          status = Awskit.Response.status response;
          code = Option.bind body Common.Xml.service_code;
          message = Option.bind body Common.Xml.service_message;
          request_id = Awskit.Response.request_id response;
          host_id = Awskit.Response.host_id response;
          headers = Awskit.Response.headers response;
          body;
        }

    let signed_request conn ~method_ ~(request : Endpoint_resolver.Request.t)
        ~query ~headers ~payload_hash =
      let headers =
        ("host", Awskit.Endpoint.authority request.endpoint) :: headers
      in
      let* credentials = R.credentials conn in
      match credentials with
      | Error error -> return_error error
      | Ok credentials -> (
          match
            Awskit.Signing.sign_request_params ~credentials
              ~region:(R.region conn) ~service:"s3" ~method_
              ~path:request.signing_path ~query_params:query ~headers
              ~payload_hash ~now:(R.now conn)
          with
          | Error error -> return_error error
          | Ok signed -> (
              match
                Awskit.Request.Target.create
                  ~scheme:(Awskit.Endpoint.scheme request.endpoint)
                  ~host:(Awskit.Endpoint.host request.endpoint)
                  ?port:(Awskit.Endpoint.port request.endpoint)
                  ~path:request.path ~query ()
              with
              | Error error -> return_error error
              | Ok target -> (
                  match
                    Awskit.Request.create ~method_ ~target
                      ~headers:signed.headers ()
                  with
                  | Error error -> return_error error
                  | Ok request -> return_ok request)))

    let retry_or_error conn ~attempt ~replayable error retry =
      match Awskit.Retry.delay (R.retry_policy conn) ~attempt ~error with
      | Some delay when replayable ->
          let* () = R.sleep conn delay in
          retry (attempt + 1)
      | _ -> return_error error

    type 'a response_action =
      | Done of ('a, Awskit.Error.t) result
      | Retry of Awskit.Error.t

    let with_response conn ~method_ ~request ~query ~headers ~payload_hash body
        ~f =
      let replayable = (R.Request_body.descriptor body).replayable in
      let rec attempt attempt_number =
        let* request =
          signed_request conn ~method_ ~request ~query ~headers ~payload_hash
        in
        match request with
        | Error error -> return_error error
        | Ok request -> (
            let* response =
              R.with_response conn request body
                ~f:(fun response response_body ->
                  if Awskit.Response.is_success response then
                    let* result = f response response_body in
                    return_ok (Done result)
                  else
                    let* body =
                      read_response_body response_body ~max_size:1_048_576L
                    in
                    match body with
                    | Error error -> return_ok (Done (Error error))
                    | Ok body ->
                        return_ok (Retry (service_error response (Some body))))
            in
            match response with
            | Error error ->
                retry_or_error conn ~attempt:attempt_number ~replayable error
                  attempt
            | Ok (Done result) -> return result
            | Ok (Retry error) ->
                retry_or_error conn ~attempt:attempt_number ~replayable error
                  attempt)
      in
      attempt 1

    let with_empty_response conn ~method_ ~request ~query ~headers ~f =
      with_response conn ~method_ ~request ~query ~headers
        ~payload_hash:empty_hash R.Request_body.empty ~f

    let content_md5 body =
      Digestif.MD5.(digest_string body |> to_raw_string) |> Base64.encode_exn
  end

  module Multipart = Multipart_request.Make (Context)
  module Object = Object_request.Make (Context)
  module Bucket = Bucket_request.Make (Context)
  module Presigned = Presigned_request.Make (Context)
end