ใช้ API, SDK และเครื่องมือ S3

API เป็นไปตามมาตรฐาน HTTP ของ IETF และ Bucket เดียวกันใช้กับเครื่องมือ S3 ได้ด้วย

SDK

SDK สำหรับ Node.js, Python และ Go ทำทุกอย่างด้านล่างให้เอง ทั้งการเซ็น request การตรวจความถูกต้องของข้อมูล การลองใหม่ และการอัปโหลด/ดาวน์โหลดไฟล์ใหญ่เป็นส่วน ๆ พร้อมกัน ติดตั้งตามภาษาที่ใช้:

ภาษาต้องใช้ติดตั้ง
Node.js18.17+npm install @thailandhosting/object-storage
Python3.9+pip install thailandhosting-object-storage
Go1.23+go get github.com/mrzack99s/thailandhosting-object-storage-sdk-go

สร้าง client ด้วย Endpoint ของ Region ที่ Bucket อยู่ และ Access key จาก Console เก็บ Key ไว้ใน environment variable ไม่ใช่ในโค้ด และใช้ client ตัวเดียวตลอดโปรแกรม เพราะมันเปิดการเชื่อมต่อค้างไว้ให้ใช้ซ้ำ

Node.js
import { Client, NotFoundError } from '@thailandhosting/object-storage';

const client = new Client({
  endpoint: 'https://objects.thailandhosting.com/<region>',
  accessKeyId: process.env.TH_ACCESS_KEY_ID,
  secretAccessKey: process.env.TH_SECRET_ACCESS_KEY,
});
const bucket = client.bucket('my-bucket');

// Any size: large files go up and come down in parallel parts
await bucket.uploadFile('backups/db.dump', '/var/backups/db.dump');
await bucket.downloadFile('backups/db.dump', '/tmp/db.dump');

// Small files
await bucket.put('notes/hello.txt', 'Hello', { contentType: 'text/plain' });
console.log(await bucket.getText('notes/hello.txt'));

// Every file under a folder
for await (const obj of bucket.objects('backups/')) console.log(obj.key, obj.size);

// A link that works without a key for one hour
const link = await bucket.createLink('backups/db.dump', { expiresIn: 3600 });
console.log(link.url);

try {
  await bucket.head('missing.txt');
} catch (e) {
  if (e instanceof NotFoundError) console.log(e.code); // object_not_found
}
Python
import os
from thailandhosting_objectstorage import Client, NotFoundError

client = Client(
    endpoint="https://objects.thailandhosting.com/<region>",
    access_key_id=os.environ["TH_ACCESS_KEY_ID"],
    secret_access_key=os.environ["TH_SECRET_ACCESS_KEY"],
)
bucket = client.bucket("my-bucket")

# Any size: large files go up and come down in parallel parts
bucket.upload_file("backups/db.dump", "/var/backups/db.dump")
bucket.download_file("backups/db.dump", "/tmp/db.dump")

# Small files
bucket.put("notes/hello.txt", "Hello", content_type="text/plain")
print(bucket.get_bytes("notes/hello.txt").decode())

# Every file under a folder
for obj in bucket.iter_objects("backups/"):
    print(obj.key, obj.size)

# A link that works without a key for one hour
print(bucket.create_link("backups/db.dump", expires_in=3600).url)

try:
    bucket.head("missing.txt")
except NotFoundError as e:
    print(e.code)  # object_not_found
Go
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	objectstorage "github.com/mrzack99s/thailandhosting-object-storage-sdk-go"
)

func main() {
	client, err := objectstorage.New(objectstorage.Config{
		Endpoint:        "https://objects.thailandhosting.com/<region>",
		AccessKeyID:     os.Getenv("TH_ACCESS_KEY_ID"),
		SecretAccessKey: os.Getenv("TH_SECRET_ACCESS_KEY"),
	})
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()
	bucket := client.Bucket("my-bucket")

	// Any size: large files go up and come down in parallel parts
	if _, err := bucket.UploadFile(ctx, "backups/db.dump", "/var/backups/db.dump", nil); err != nil {
		log.Fatal(err)
	}
	if _, err := bucket.DownloadFile(ctx, "backups/db.dump", "/tmp/db.dump"); err != nil {
		log.Fatal(err)
	}

	// Every file under a folder
	for obj, err := range bucket.Objects(ctx, "backups/") {
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(obj.Key, obj.Size)
	}

	// A link that works without a key for one hour
	link, err := bucket.CreateLink(ctx, "backups/db.dump", &objectstorage.LinkOptions{Expires: time.Hour})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(link.URL)
}

SDK ยังตั้งโฟลเดอร์สาธารณะ บอกที่อยู่ของไฟล์ และเขียนไฟล์เฉพาะเมื่อยังไม่มี หรือเมื่อยังไม่มีใครแก้ตั้งแต่คุณอ่านได้ด้วย:

Node.js
// Anyone may read images/ without a key (the key must manage buckets)
await bucket.setPublicAccess([{ prefix: 'images/' }]);
console.log(bucket.publicUrl('images/logo.png'));

// Create a file only if it does not exist yet
await bucket.put('locks/job-42', 'me', { ifNoneMatch: '*' });

// Change it only if nobody changed it since you read it
const info = await bucket.head('config.json');
await bucket.put('config.json', next, { ifMatch: info.etag });

Request ที่ล้มเพราะระบบไม่ว่างชั่วครู่หรือเน็ตหลุดจะลองใหม่ให้เอง ทุกการอัปโหลดตรวจกับ digest และการอัปโหลดไฟล์ใหญ่ที่ล้มจะถูกยกเลิกให้ ส่วนที่ค้างจึงไม่ถูกคิดเงิน

เครื่องมือ S3

AWS CLI, rclone และแอปที่รองรับ S3 ใช้ได้ทันที แค่ชี้ไปที่ Endpoint ใช้ Access key ของคุณ และตั้ง region เป็น Region ของ Bucket

Shell
aws configure set aws_access_key_id YOUR_KEY_ID
aws configure set aws_secret_access_key YOUR_SECRET
aws s3 cp backup.tar.gz s3://your-bucket/ --endpoint-url https://objects.thailandhosting.com --region <region>

เรียก API โดยตรง

API เป็น HTTP และ JSON ธรรมดาอยู่ใต้ /v1 ของ Endpoint และใช้มาตรฐานของ IETF แทนรูปแบบที่คิดขึ้นเอง

เรื่องทำอย่างไร
การยืนยันตัวตนทุก request เซ็นด้วย HTTP Message Signatures (RFC 9421) โดยใช้ Secret ของคุณ Secret จึงไม่ถูกส่งออกไปเลย เวลาของเครื่องต้องห่างจากของเราไม่เกิน 5 นาที
ความถูกต้องของข้อมูลการอัปโหลดแนบ Content-Digest (RFC 9530) เราตรวจข้อมูลกับค่านี้และปฏิเสธถ้าไม่ตรง ส่วนการดาวน์โหลดจะได้ Repr-Digest กลับไปให้ตรวจได้เช่นกัน
อัปเดตอย่างปลอดภัยIf-None-Match: * สร้างไฟล์เฉพาะเมื่อยังไม่มี และ If-Match แก้หรือลบไฟล์เฉพาะเมื่อยังไม่มีใครแก้หลังจากที่คุณอ่าน (RFC 9110)
ErrorProblem Details (RFC 9457) เป็น JSON ที่มี code เช่น bucket_not_found

อัปโหลดครั้งเดียวหรือแต่ละส่วนได้ไม่เกิน 5 GB ไฟล์ใหญ่แบ่งได้สูงสุด 10,000 ส่วน แต่ละ Project มีได้สูงสุด 100 Bucket และ 50 Access key

ถ้าเจอปัญหา

Request ไม่ผ่านและขึ้น “invalid_signature” พูดถึงเวลาของเครื่อง

นาฬิกาของเครื่องเพี้ยนเกิน 5 นาที ให้เปิดตั้งเวลาอัตโนมัติ (NTP) แล้วลองใหม่

เครื่องมือ S3 ขึ้น “SignatureDoesNotMatch”

ตรวจ Secret และตั้ง region เป็น Region ของ Bucket ไม่ใช่ region ของ cloud อื่น

อัปโหลดไม่ผ่าน ขึ้น 412 “precondition_failed”

คุณขอให้สร้างไฟล์เฉพาะเมื่อยังไม่มี หรือแก้เฉพาะเมื่อยังไม่ถูกแก้ แต่ไฟล์มีอยู่แล้วหรือถูกแก้ไปแล้ว ให้อ่านไฟล์ใหม่แล้วตัดสินใจว่าจะเขียนทับหรือไม่

บางครั้ง request ไม่ผ่านด้วย 503 “busy” หรือ “SlowDown”

ระบบไม่ว่างชั่วครู่ ให้รอตามจำนวนวินาทีใน header Retry-After แล้วลองใหม่ SDK และเครื่องมือ S3 ทำให้เองอยู่แล้ว

ยังแก้ไม่ได้? ให้ทีมงานช่วย

เปิด ticket จากหน้าจัดการ บอกชื่อเว็บหรือบริการ สิ่งที่ทำ และข้อความที่เห็นบนจอ ทีมงานจะตอบกลับในเรื่องเดียวกัน ติดตามได้ในที่เดียว