SDK
SDK สำหรับ Node.js, Python และ Go เซ็น Request ให้ ลองใหม่เองเมื่อระบบไม่ว่างชั่วครู่ และมี worker สำเร็จรูปที่รอรับข้อความ รันโค้ดของคุณ แล้วลบข้อความที่ทำเสร็จให้ ติดตั้งตามภาษาที่ใช้:
| ภาษา | ต้องใช้ | ติดตั้ง |
|---|---|---|
| Node.js | 18.17+ | npm install @thailandhosting/queue-service |
| Python | 3.9+ | pip install thailandhosting-queue-service |
| Go | 1.23+ | go get github.com/mrzack99s/thailandhosting-queue-service-sdk-go |
ให้ client รู้ Region ของ Queue และ Access key จาก Console (เก็บไว้ใน environment variable ไม่ใช่ในโค้ด) และใช้ client ตัวเดียวทั้งโปรแกรม
import { Client } from '@thailandhosting/queue-service';
const client = new Client({
region: '<region>',
accessKeyId: process.env.TH_QUEUE_ACCESS_KEY_ID,
secretAccessKey: process.env.TH_QUEUE_SECRET_ACCESS_KEY,
});
const orders = client.queue('orders');
// Send
await orders.sendJSON({ orderId: 42 });
await orders.send('plain text', { attributes: { kind: 'email' }, delay: 30 });
// A worker: waits for messages, runs the handler, deletes what it finished
const stop = new AbortController();
process.on('SIGTERM', () => stop.abort());
await orders.process(async (m) => {
const { orderId } = m.json();
await ship(orderId); // throwing leaves the message to be tried again
}, { concurrency: 8, retryDelay: 30, signal: stop.signal });import os
from thailandhosting_queueservice import Client
client = Client(
access_key_id=os.environ["TH_QUEUE_ACCESS_KEY_ID"],
secret_access_key=os.environ["TH_QUEUE_SECRET_ACCESS_KEY"],
region="<region>",
)
orders = client.queue("orders")
# Send
orders.send_json({"order_id": 42})
orders.send("plain text", attributes={"kind": "email"}, delay=30)
# A worker: waits for messages, runs the handler, deletes what it finished
def handle(m):
ship(m.json()["order_id"]) # raising leaves the message to be tried again
orders.process(handle, concurrency=8, retry_delay=30)client, err := queueservice.New(queueservice.Config{
Region: "<region>",
AccessKeyID: os.Getenv("TH_QUEUE_ACCESS_KEY_ID"),
SecretAccessKey: os.Getenv("TH_QUEUE_SECRET_ACCESS_KEY"),
})
if err != nil {
log.Fatal(err)
}
orders := client.Queue("orders")
// Send
_, err = orders.SendJSON(ctx, map[string]any{"orderId": 42}, nil)
// A worker: waits for messages, runs the handler, deletes what it finished.
// It runs until ctx ends.
err = orders.Process(ctx, func(ctx context.Context, m queueservice.Message) error {
var order struct{ OrderID int }
if err := m.JSON(&order); err != nil {
return err
}
return ship(ctx, order.OrderID) // an error leaves the message to be tried again
}, &queueservice.ProcessOptions{Concurrency: 8, RetryDelay: 30 * time.Second})สร้างและแก้ Queue รวมถึงรับและลบข้อความเองก็ได้:
// Create a queue or change its settings (the key must manage queues)
await client.putQueue('orders', { visibilityTimeout: 120, maxReceives: 5, deadLetterQueue: 'orders-dlq' });
// Receive by hand
for (const m of await orders.receive({ max: 10, wait: 20 })) {
await handle(m.body);
await orders.delete(m.receipt);
}
const info = await client.getQueue('orders'); // info.messages: visible, inFlight, delayedข้อความหนึ่งอาจมาถึงมากกว่าหนึ่งครั้ง (เช่น worker หยุดก่อนลบ) เขียนโค้ดให้รันซ้ำได้โดยไม่เสียหาย เช่น ตรวจก่อนว่าออเดอร์นี้ตัดเงินไปแล้วหรือยัง
REST API
Endpoint ของแต่ละ Region คือ https://queues.thailandhosting.com/<region> และ API อยู่ที่ …/<region>/v1/queues ตัว body และคำตอบเป็น JSON และระยะเวลาทุกค่าเป็นวินาที
| Request | ใช้ทำอะไร |
|---|---|
GET /v1/queues | รายชื่อ Queue ที่ Key เข้าถึงได้ |
PUT /v1/queues/{name} | สร้าง Queue หรือแก้การตั้งค่า (Key ต้องจัดการ Queue ได้) เช่น {"visibilityTimeout":60,"maxReceives":5,"deadLetterQueue":"orders-dlq"} |
GET /v1/queues/{name} | การตั้งค่าและจำนวนข้อความ (รอรับ กำลังประมวลผล รอ Delay) |
POST /v1/queues/{name}/messages | ส่ง 1 ข้อความ {"body":"…","attributes":{…},"delay":0} หรือสูงสุด 10 ข้อความ {"messages":[…]} |
POST /v1/queues/{name}/receive | รับสูงสุด 10 ข้อความ {"max":10,"wait":20,"visibilityTimeout":120} ได้ id, body, receipt, receives |
POST /v1/queues/{name}/delete | ลบข้อความที่ทำเสร็จ {"receipts":["…"]} (สูงสุด 10) |
POST /v1/queues/{name}/visibility | ขอเวลาเพิ่ม หรือคืนข้อความให้คนอื่นรับทันที {"receipt":"…","timeout":0} |
POST /v1/queues/{name}/purge | ลบข้อความทั้งหมดใน Queue |
DELETE /v1/queues/{name} | ลบ Queue พร้อมข้อความ |
การเซ็น Request
ทุก Request เซ็นด้วย HTTP Message Signatures (RFC 9421) โดยใช้ Secret ของคุณ (HMAC-SHA256) ครอบคลุม method, host, path, query และ Content-Digest (RFC 9530) ของ body Secret จึงไม่ถูกส่งไปกับ Request เวลาของเครื่องต้องห่างจากของเราไม่เกิน 5 นาที Error เป็น Problem Details (RFC 9457) ที่มี code
ไม่ใช้ SDK: Node.js
import crypto from 'node:crypto';
const KEY_ID = 'YOUR_KEY_ID', SECRET = 'YOUR_SECRET';
const ENDPOINT = 'https://queues.thailandhosting.com/<region>';
async function call(method, path, body) {
const url = new URL(ENDPOINT + path);
const data = body ? JSON.stringify(body) : '';
const headers = { 'Content-Type': 'application/json' };
let covered = '"@method" "@authority" "@path" "@query"';
let base = `"@method": ${method}\n"@authority": ${url.host}\n"@path": ${url.pathname}\n"@query": ?${url.search.slice(1)}\n`;
if (data) {
headers['Content-Digest'] = `sha-256=:${crypto.createHash('sha256').update(data).digest('base64')}:`;
covered += ' "content-digest"';
base += `"content-digest": ${headers['Content-Digest']}\n`;
}
const params = `(${covered});created=${Math.floor(Date.now() / 1000)};keyid="${KEY_ID}";alg="hmac-sha256"`;
base += `"@signature-params": ${params}`;
headers['Signature-Input'] = `th=${params}`;
headers['Signature'] = `th=:${crypto.createHmac('sha256', SECRET).update(base).digest('base64')}:`;
const res = await fetch(url, { method, headers, body: data || undefined });
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
return res.status === 204 ? null : res.json();
}
await call('POST', '/v1/queues/orders/messages', { body: JSON.stringify({ orderId: 42 }) });
const { messages } = await call('POST', '/v1/queues/orders/receive', { max: 10, wait: 20 });
for (const m of messages) {
// ... do the work ...
await call('POST', '/v1/queues/orders/delete', { receipts: [m.receipt] });
}ไม่ใช้ SDK: Python
import base64, hashlib, hmac, json, time, urllib.request, urllib.parse
KEY_ID, SECRET = "YOUR_KEY_ID", "YOUR_SECRET"
ENDPOINT = "https://queues.thailandhosting.com/<region>"
def call(method, path, body=None):
url = urllib.parse.urlsplit(ENDPOINT + path)
data = json.dumps(body).encode() if body is not None else b""
headers = {"Content-Type": "application/json"}
covered = '"@method" "@authority" "@path" "@query"'
base = f'"@method": {method}\n"@authority": {url.netloc}\n"@path": {url.path}\n"@query": ?{url.query}\n'
if data:
headers["Content-Digest"] = "sha-256=:" + base64.b64encode(hashlib.sha256(data).digest()).decode() + ":"
covered += ' "content-digest"'
base += f'"content-digest": {headers["Content-Digest"]}\n'
params = f'({covered});created={int(time.time())};keyid="{KEY_ID}";alg="hmac-sha256"'
base += f'"@signature-params": {params}'
headers["Signature-Input"] = "th=" + params
headers["Signature"] = "th=:" + base64.b64encode(hmac.new(SECRET.encode(), base.encode(), hashlib.sha256).digest()).decode() + ":"
req = urllib.request.Request(url.geturl(), data=data or None, method=method, headers=headers)
with urllib.request.urlopen(req) as res:
return json.loads(res.read() or "null")
call("POST", "/v1/queues/orders/messages", {"body": json.dumps({"orderId": 42})})
for m in call("POST", "/v1/queues/orders/receive", {"max": 10, "wait": 20})["messages"]:
# ... do the work ...
call("POST", "/v1/queues/orders/delete", {"receipts": [m["receipt"]]})ข้อความหนึ่ง (body รวม attributes) ไม่เกิน 256 KiB ส่งครั้งละไม่เกิน 10 ข้อความรวม 1 MiB รับครั้งละไม่เกิน 10 ข้อความ ข้อความที่รับแล้วยังไม่ลบได้ไม่เกิน 120,000 ข้อความต่อ Queue Project หนึ่งมีได้สูงสุด 1,000 Queue และ 50 Access key
ถ้าเจอปัญหา
ขึ้น 401 “invalid_signature”
ตรวจ Key ID และ Secret ว่าเซ็นจาก path เต็มรวม /<region> และนาฬิกาของเครื่องตรง (เปิด NTP)
ขึ้น 409 “invalid_receipt” ตอนลบ
ข้อความเลย Visibility timeout ไปแล้วและถูกส่งให้ Worker อื่น receipt เก่าจึงใช้ไม่ได้ ให้เพิ่ม Visibility timeout และทำงานให้ทำซ้ำได้
ขึ้น 429 “queue_full” หรือ “too_many_in_flight”
Queue มีข้อความมากเกินไป หรือรับไปแล้วยังไม่ลบเกิน 120,000 ข้อความ ให้เพิ่ม Worker และลบข้อความที่ทำเสร็จ
ขึ้น 421 “wrong_region”
เรียกผิด Region ใช้ Endpoint ที่แสดงในคำตอบ หรือเรียกโดยไม่ใส่ Region ใน path