s3
Package s3.
S3-compatible object storage, signed with AWS Signature Version 4: AWS S3, Cloudflare R2, MinIO and the rest.
import "s3";
import "proc";
let r = s3.new_client("https://s3.eu-west-1.amazonaws.com", "eu-west-1", access_key, secret_key);
// or s3.from_env(proc.getenv): AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY,
// AWS_SESSION_TOKEN, AWS_REGION, S3_ENDPOINT
guard let c = r else let e = err_of(r) { panic(e); }
s3.put_object(c, "uploads", "avatars/42.png", png, "image/png", deadline); // ok(etag)
s3.get_object(c, "uploads", "avatars/42.png", deadline); // ok(bytes)
s3.get_range(c, "uploads", "logs/today", 0, 1023, deadline); // first KiB
s3.head_object(c, "uploads", "avatars/42.png", deadline); // ok(some(Object)) or ok(none)
s3.delete_object(c, "uploads", "avatars/42.png", deadline);
s3.list_objects(c, "uploads", "avatars/", "/", "", 1000, deadline); // ok(Page); page.next continues
s3.put_large(c, "backups", "db.dump", dump, "", 16 * 1048576, deadline); // multipart
s3.presign_get(c, "uploads", "avatars/42.png", 900); // ok(url) for 15 minutes
s3.presign_put(c, "uploads", "incoming/x.bin", 300);The payload is hashed into the signature (x-amz-content-sha256), so a body altered in transit is refused by the server. Signing keys are cached for the day they belong to. Errors carry S3's own code and message (s3: NoSuchKey: The specified key does not exist.).
Addressing and listing. Addressing is path-style by default (endpoint/bucket/key), which MinIO, R2 and most compatible stores want. Set c.path_style = false for virtual-host style (bucket.endpoint/key). list_objects returns up to 1000 keys a page in key order. Pass page.next to get the next page; with a delimiter, deeper keys fold into page.prefixes.
Multipart. create_multipart, upload_part, complete_multipart and abort_multipart are the raw steps (every part but the last at least 5 MiB). put_large drives them, uploading in one request when the data fits in a part, and aborting if a part fails so no orphaned parts are left to be billed.
Presigned URLs last 1 second to 7 days. presign_at signs at a given time, for tests and for URLs several servers must agree on.
Limits. Objects are read and written whole, in memory (or as one byte range). Redirects are not followed, since a redirect would change the host the signature covers.
API#
fn new_client(endpoint: str, region: str, access_key: str, secret_key: str) -> result[Client, str] stdlib/s3/s3.sl:65#A client for endpoint ("https://s3.us-east-1.amazonaws.com", "https://<account>.r2.cloudflarestorage.com", "http://127.0.0.1:9000").
A client from AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN (optional), AWS_REGION (or AWS_DEFAULT_REGION) and S3_ENDPOINT (default: the region's AWS endpoint).
fn put_object(c: Client, bucket: str, key: str, data: bytes, content_type: str, deadline: until) -> result[str, str] stdlib/s3/s3.sl:123#Store data at bucket/key; the object's ETag.
fn get_object(c: Client, bucket: str, key: str, deadline: until) -> result[bytes, str] stdlib/s3/s3.sl:134#fn get_range(c: Client, bucket: str, key: str, first: int, last: int, deadline: until) -> result[bytes, str] stdlib/s3/s3.sl:141#Bytes first..last of the object, both included (an HTTP Range).
fn head_object(c: Client, bucket: str, key: str, deadline: until) -> result[opt[Object], str] stdlib/s3/s3.sl:155#Size, ETag, type and modification time, without the body. none when the object does not exist.
fn delete_object(c: Client, bucket: str, key: str, deadline: until) -> result[bool, str] stdlib/s3/s3.sl:175#Delete bucket/key. S3 answers success for a key that was not there.
fn list_objects(c: Client, bucket: str, prefix: str, delimiter: str, next: str, max_keys: int, deadline: until) -> result[Page, str] stdlib/s3/s3.sl:184#One page (up to max_keys, at most 1000) of the keys under prefix, in key order, from where next (a Page's next, or "" to start) says. With delimiter "/", keys below the next "/" fold into page.prefixes.
fn create_multipart(c: Client, bucket: str, key: str, content_type: str, deadline: until) -> result[str, str] stdlib/s3/s3.sl:225#Start a multipart upload; its upload id.
fn upload_part(c: Client, bucket: str, key: str, upload_id: str, n: int, data: bytes, deadline: until) -> result[str, str] stdlib/s3/s3.sl:244#Upload part number n (1 to 10000) of an upload; its ETag, for complete_multipart. Every part but the last must be at least 5 MiB.
fn complete_multipart(c: Client, bucket: str, key: str, upload_id: str, etags: [str], deadline: until) -> result[str, str] stdlib/s3/s3.sl:262#Finish an upload from its parts' ETags, in part order; the object's ETag.
fn abort_multipart(c: Client, bucket: str, key: str, upload_id: str, deadline: until) -> result[bool, str] stdlib/s3/s3.sl:287#fn put_large(c: Client, bucket: str, key: str, data: bytes, content_type: str, part_size: int, deadline: until) -> result[str, str] stdlib/s3/s3.sl:298#Upload data in parts of part_size bytes (at least 5 MiB), aborting the upload if any part fails, so no orphaned parts are left to be billed.
fn presign_get(c: Client, bucket: str, key: str, expires_seconds: int) -> result[str, str] stdlib/s3/s3.sl:359#A URL anyone can GET (or PUT, below) for expires_seconds (1 second to 7 days), signed now: for downloads and browser uploads that never see your keys.
fn presign_put(c: Client, bucket: str, key: str, expires_seconds: int) -> result[str, str] stdlib/s3/s3.sl:363#fn presign_at(c: Client, method: str, bucket: str, key: str, expires_seconds: int, amzdate: str) -> result[str, str] stdlib/s3/s3.sl:369#presign at a given time ("20130524T000000Z"), for tests and for URLs that must agree across servers.
fn sign_request(c: Client, method: str, host: str, path: str, query: map[str]str, headers: map[str]str, payload_hash: str, amzdate: str) -> str stdlib/s3/s3.sl:421#The Authorization header for a request (SigV4, signed headers are host, x-amz-content-sha256, x-amz-date, any x-amz-security-token and the caller's headers). headers holds lowercase names.
RFC 3986 percent-encoding as SigV4 wants it: everything but A-Z a-z 0-9 - _ . ~ (and / in paths, when keep_slash).