\usepackage{aws-lambda}

إنشاء PDF من LaTeX دون خوادم على Lambda

جمّع مستندات LaTeX إلى PDF داخل دوال AWS Lambda باستخدام FormaTeX API. دون طبقة TeX Live بحجم 500 ميغابايت ودون بيئة تشغيل مخصصة، فقط استدعاء HTTP واستجابة PDF.

\section{لماذا الحوسبة دون خوادم مع LaTeX}

إنشاء ملفات PDF دون أي بنية تحتية

يتجاوز حجم TeX Live 4 غيغابايت. وتضمينه كطبقة في Lambda بطيء ومكلف وهش. يتولى FormaTeX API عملية التجميع بالكامل، فتبقى دوالك صغيرة وسريعة.

دون طبقة TeX Live

أبقِ حزمة نشر Lambda أقل من 10 ميغابايت. يتولى FormaTeX بيئة TeX Live الكاملة على بنية تحتية مُدارة.

ادفع مقابل كل عملية تجميع

تحاسبك Lambda على كل استدعاء، ويحاسبك FormaTeX على كل عملية تجميع. تتقلص إلى الصفر عند الخمول وتتوسع إلى آلاف ملفات PDF تحت الحمل.

تنفيذ معزول

تعمل كل عملية تجميع في حاوية معزولة (sandbox) على خوادم FormaTeX. ولا تنفّذ دالة Lambda لديك أبدًا كود LaTeX عشوائيًا محليًا.

\section{معالج Python}

معالج Lambda بلغة Python

يستخدم المكتبة القياسية في Python فقط إضافة إلى boto3 (المثبّتة مسبقًا في كل بيئة تشغيل Lambda). يُخزَّن ملف PDF المجمّع في S3 ويُعاد مفتاح الدلو (bucket key).

  • بيئة التشغيل: Python 3.12 أو أحدث
  • التبعيات: boto3 فقط (مضمّنة في بيئة تشغيل Lambda)
  • المهلة: اضبطها على 30 ثانية على الأقل للمستندات المعقدة
import json
import os
import urllib.request
import boto3


def lambda_handler(event, context):
    """Compile LaTeX to PDF via FormaTeX API and store the result in S3."""
    source = event.get("source", "")
    engine = event.get("engine", "pdflatex")

    if not source:
        return {
            "statusCode": 400,
            "body": json.dumps({"error": "source is required"}),
        }

    # API key stored as a Lambda environment variable (or fetched from
    # Secrets Manager at cold-start — see the IAM section below).
    api_key = os.environ["FORMATEX_API_KEY"]

    payload = json.dumps({"latex": source, "engine": engine}).encode()
    req = urllib.request.Request(
        "https://api.formatex.io/api/v1/compile",
        data=payload,
        headers={
            "X-API-Key": api_key,
            "Content-Type": "application/json",
        },
        method="POST",
    )

    with urllib.request.urlopen(req, timeout=25) as resp:
        pdf_bytes = resp.read()

    s3 = boto3.client("s3")
    bucket = os.environ["OUTPUT_BUCKET"]
    key = f"pdfs/{context.aws_request_id}.pdf"
    s3.put_object(Bucket=bucket, Key=key, Body=pdf_bytes, ContentType="application/pdf")

    return {
        "statusCode": 200,
        "body": json.dumps({"bucket": bucket, "key": key}),
    }

\section{معالج Node.js}

معالج Lambda بلغة Node.js

معالج يعتمد TypeScript أولًا ويستخدم فقط المكونات المدمجة في Node.js وعميل S3 من AWS SDK v3. يُخزَّن مفتاح API مؤقتًا في نطاق الوحدة، لذا تتخطى الاستدعاءات الدافئة البحث عن متغير البيئة.

  • بيئة التشغيل: Node.js 20.x أو أحدث (ESM)
  • التبعيات: @aws-sdk/client-s3 (مضمّنة في بيئة تشغيل Lambda)
  • المهلة: اضبطها على 30 ثانية على الأقل
import https from "node:https"
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"

const s3 = new S3Client({})

// Cache the API key across warm invocations
let cachedApiKey: string | undefined

export const handler = async (event: {
  source: string
  engine?: string
  requestId?: string
}) => {
  const { source, engine = "pdflatex" } = event

  if (!source) {
    return { statusCode: 400, body: JSON.stringify({ error: "source is required" }) }
  }

  if (!cachedApiKey) {
    cachedApiKey = process.env.FORMATEX_API_KEY
  }
  if (!cachedApiKey) throw new Error("FORMATEX_API_KEY is not set")

  const pdfBuffer = await compilePdf(cachedApiKey, source, engine)

  const bucket = process.env.OUTPUT_BUCKET!
  const key = `pdfs/${event.requestId ?? Date.now()}.pdf`

  await s3.send(
    new PutObjectCommand({
      Bucket: bucket,
      Key: key,
      Body: pdfBuffer,
      ContentType: "application/pdf",
    }),
  )

  return { statusCode: 200, body: JSON.stringify({ bucket, key }) }
}

function compilePdf(apiKey: string, source: string, engine: string): Promise<Buffer> {
  return new Promise((resolve, reject) => {
    const body = JSON.stringify({ latex: source, engine })
    const options = {
      hostname: "api.formatex.io",
      path: "/api/v1/compile",
      method: "POST",
      headers: {
        "X-API-Key": apiKey,
        "Content-Type": "application/json",
        "Content-Length": Buffer.byteLength(body),
      },
    }
    const req = https.request(options, (res) => {
      const chunks: Buffer[] = []
      res.on("data", (c: Buffer) => chunks.push(c))
      res.on("end", () => {
        if (res.statusCode !== 200) {
          reject(new Error(`FormaTeX error: HTTP ${res.statusCode}`))
        } else {
          resolve(Buffer.concat(chunks))
        }
      })
    })
    req.on("error", reject)
    req.write(body)
    req.end()
  })
}

\section{أذونات IAM}

تخزين مفتاح API في Secrets Manager

لأحمال العمل في الإنتاج، احتفظ بمفتاح FormaTeX API في AWS Secrets Manager بدلًا من متغير بيئة عادي. أرفق السياسة أدناه بدور تنفيذ Lambda لديك واجلب السر عند البدء البارد.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadFormaTeXApiKey",
      "Effect": "Allow",
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:formatex/api-key-*"
    },
    {
      "Sid": "WriteOutputBucket",
      "Effect": "Allow",
      "Action": ["s3:PutObject"],
      "Resource": "arn:aws:s3:::your-output-bucket/pdfs/*"
    }
  ]
}

# Store the secret in Secrets Manager (one-time setup):
aws secretsmanager create-secret \
  --name formatex/api-key \
  --secret-string '{"FORMATEX_API_KEY":"your-key-here"}'

# Then fetch it at cold-start in Python:
# import boto3, json
# secret = boto3.client("secretsmanager").get_secret_value(SecretId="formatex/api-key")
# api_key = json.loads(secret["SecretString"])["FORMATEX_API_KEY"]

\section{اعتبارات البدء البارد}

قلّل زمن الاستجابة عند البدء البارد

خزّن الأسرار مؤقتًا في نطاق الوحدة

اجلب مفتاح API من Secrets Manager مرة واحدة، خارج دالة المعالج. تُعاد الاستفادة من القيمة في جميع الاستدعاءات الدافئة دون استدعاء شبكي إضافي.

استخدم التزامن المُجهَّز مسبقًا للمسارات الحساسة لزمن الاستجابة

فعّل التزامن المُجهَّز مسبقًا (provisioned concurrency) على إصدار Lambda للقضاء على البدء البارد تمامًا في نقاط إنشاء PDF الموجهة للمستخدمين. واستخدم التزامن عند الطلب لمهام الدفعات في الخلفية.

اضبط مهلة واقعية

قد تستغرق مستندات LaTeX المعقدة التي تتضمن TikZ أو قائمة مراجع أو عدة تمريرات تجميع من 10 إلى 20 ثانية. اضبط مهلة Lambda على 30 ثانية على الأقل واضبط مهلة تكامل API Gateway أو ALB لتتوافق معها.

\section{أدلة ذات صلة}

مزيد من أدلة SDK واللغات

FormaTeX API نقطة نهاية HTTP عادية. استخدمها من أي لغة أو بيئة تشغيل.

هل أنت مستعد لإنشاء ملفات PDF على Lambda؟

خطة مجانية: 15 عملية تجميع عبر API شهريًا. لا حاجة إلى بطاقة ائتمان.