ใช้ SDK และ API

SDK สำหรับ Node.js, Python และ Go บน REST API ธรรมดาที่เซ็นทุก Request ตามมาตรฐาน IETF

SDK

SDK สำหรับ Node.js, Python และ Go เซ็น Request ให้ ลองใหม่เองเมื่อระบบไม่ว่างชั่วครู่ และมี worker สำเร็จรูปที่รอรับข้อความ รันโค้ดของคุณ แล้วลบข้อความที่ทำเสร็จให้ ติดตั้งตามภาษาที่ใช้:

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

ให้ client รู้ Region ของ Queue และ Access key จาก Console (เก็บไว้ใน environment variable ไม่ใช่ในโค้ด) และใช้ client ตัวเดียวทั้งโปรแกรม

Node.js
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 });
Python
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)
Go
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 รวมถึงรับและลบข้อความเองก็ได้:

Node.js
// 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

Node.js 18+
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

Python 3, standard library only
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

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

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