معالجات Azure Functions المخصصة

يتم تنفيذ كل تطبيق وظيفة بواسطة معالج خاص بلغة معينة. بينما يتميز Azure Functions بالعديد من معالجات اللغة بشكل افتراضي، توجد حالات قد ترغب فيها في استخدام لغات أو نسخ تشغيل أخرى.

تعد المعالجات المخصصة عبارة عن خوادم ويب خفيفة تتلقى الأحداث من مضيف الوظائف. يمكن لأي لغة تدعم أساسيات HTTP تنفيذ معالج مخصص.

تعد المعالجات المخصصة هي الأنسب للمواقف التي تريد فيها أن تقوم بما يلي:

  • تنفيذ تطبيق وظيفي بلغة غير معروضة حالياً وغير جاهزة للاستخدام، مثل Go أو Rust.
  • تنفيذ تطبيق وظيفة في وقت تشغيل غير مميز حالياً بشكل افتراضي، مثل Deno.

باستخدام المعالجات المخصصة، يمكنك استخدام المشغلات وروابط الإدخال والإخراج عبر حزم الملحق.

ابدأ استخدام معالجات Azure Functions المخصصة من خلال قوالب التشغيل السريع لـ Go وRust.

نظرة عامة

يوضح الرسم التخطيطي التالي العلاقة بين مضيف الوظائف وخادم الويب الذي تم تنفيذه كمعالج مخصص.

Azure Functions custom handler overview

  1. يقوم كل حدث بتشغيل طلب يتم إرساله إلى مضيف الوظائف. يعد الحدث هو أي مشغل يدعمه Azure Functions.
  2. ومن ثَم يقوم مضيف الوظائف بإصدار بيانات طلب أساسية إلى خادم الويب. تحمل البيانات الأساسية بيانات المدخلات وربط المشغل وبيانات التعريف الأخرى للوظيفة.
  3. ينفذ خادم الويب الوظيفة الفردية، ويعيد بيانات الاستجابة الأساسية إلى مضيف الوظائف.
  4. يمرر مضيف الوظائف البيانات من الاستجابة إلى روابط إخراج الوظيفة لأجل المعالجة.

يجب أن يقوم تطبيق Azure Functions المنفذ كمعالج مخصص بتكوين ملفات host.json وlocal.settings.json وfunction.json وفقاً لبعض الاصطلاحات.

بنية التطبيق

لتنفيذ معالج مخصص، تحتاج إلى الجوانب التالية لتطبيقك:

  • ملف host.json في جذر تطبيقك
  • ملف local.settings.json في جذر تطبيقك
  • ملف function.json لكل وظيفة (داخل مجلد يطابق اسم الوظيفة)
  • أمر أو برنامج نصي أو برنامج قابل للتنفيذ يقوم بتشغيل خادم الويب

يوضح الرسم التخطيطي التالي كيف تبدو هذه الملفات على نظام الملفات لدالة تسمى "MyQueueFunction" ومعالج مخصص قابل للتنفيذ يسمى handler.exe.

| /MyQueueFunction
|   function.json
|
| host.json
| local.settings.json
| handler.exe

التكوين

تم تكوين التطبيق عبر ملفات host.json وlocal.settings.json.

host.json

يخبر host.json مضيف الوظائف بمكان إرسال الطلبات من خلال الإشارة إلى خادم ويب قادر على معالجة أحداث HTTP.

يتم تحديد معالج مخصص عن طريق تكوين ملف host.json بتفاصيل حول كيفية تشغيل خادم الويب عبر قسم customHandler.

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler.exe"
    }
  }
}

يشير قسم customHandler إلى هدف كما هو محدد بواسطة defaultExecutablePath. قد يكون هدف التنفيذ إما أمراً أو ملفاً قابلاً للتنفيذ أو ملفاً يتم فيه تنفيذ خادم الويب.

استخدم الصفيف arguments لتمرير أية وسيطات إلى الملف القابل للتنفيذ. تدعم الوسيطات توسيع متغيرات البيئة (إعدادات التطبيق) باستخدام منهج %%.

يمكنك أيضاً تغيير دليل العمل الذي يستخدمه الملف القابل للتنفيذ مع workingDirectory.

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "app/handler.exe",
      "arguments": [
        "--database-connection-string",
        "%DATABASE_CONNECTION_STRING%"
      ],
      "workingDirectory": "app"
    }
  }
}
دعم الروابط

تتوفر المشغلات القياسية بجانب روابط الإدخال والإخراج من خلال الرجوع إلى حزم الملحق في ملف host.json.

local.settings.json

يحدد local.settings.json إعدادات التطبيق المستخدمة عند تشغيل تطبيق الوظيفة محلياً. نظراً إلى أنه قد يحتوي على بيانات سرية، يجب استبعاد local.settings.json من التحكم في المصدر. في Azure، استخدم إعدادات التطبيق بدلاً من ذلك.

بالنسبة إلى المعالجات المخصصة، اضبط FUNCTIONS_WORKER_RUNTIME على Custom في local.settings.json.

{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "Custom"
  }
}

بيانات تعريف الوظيفة

عند استخدامها مع معالج مخصص، فإن محتويات function.json لا تختلف عن كيفية تعريف وظيفة ضمن أي سياق آخر. يتمثل الشرط الوحيد في أن ملفات function.json يجب أن تكون في مجلد مسمى لمطابقة اسم الوظيفة.

يعمل ملف function.json التالي على تكوين وظيفة بها مشغل قائمة انتظار وربط إخراج قائمة انتظار. ونظراً إلى وجودها في مجلد باسم MyQueueFunction، فإنها تحدد وظيفة تسمى MyQueueFunction.

MyQueueFunction/function.json

{
  "bindings": [
    {
      "name": "myQueueItem",
      "type": "queueTrigger",
      "direction": "in",
      "queueName": "messages-incoming",
      "connection": "AzureWebJobsStorage"
    },
    {
      "name": "$return",
      "type": "queue",
      "direction": "out",
      "queueName": "messages-outgoing",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

بيانات الطلب الأساسية

عند استلام رسالة قائمة انتظار، يرسل مضيف الوظائف طلب نشر HTTP إلى المعالج المخصص مع وجود بيانات أساسية في النص الأساسي.

تمثل التعليمات البرمجية التالية البيانات الأساسية لطلب عينة. تتضمن البيانات الأساسية بنية JSON مع عضوين: Data وMetadata.

يشتمل العضو Data على مفاتيح تطابق أسماء الإدخال والمشغل كما هو محدد في صفيف الروابط في ملف function.json.

ويشتمل العضو Metadata على بيانات تعريف تم إنشاؤها من مصدر الحدث.

{
  "Data": {
    "myQueueItem": "{ message: \"Message sent\" }"
  },
  "Metadata": {
    "DequeueCount": 1,
    "ExpirationTime": "2019-10-16T17:58:31+00:00",
    "Id": "800ae4b3-bdd2-4c08-badd-f08e5a34b865",
    "InsertionTime": "2019-10-09T17:58:31+00:00",
    "NextVisibleTime": "2019-10-09T18:08:32+00:00",
    "PopReceipt": "AgAAAAMAAAAAAAAAAgtnj8x+1QE=",
    "sys": {
      "MethodName": "QueueTrigger",
      "UtcNow": "2019-10-09T17:58:32.2205399Z",
      "RandGuid": "24ad4c06-24ad-4e5b-8294-3da9714877e9"
    }
  }
}

بيانات الاستجابة الأساسية

حسب الاصطلاح، يتم تنسيق استجابات الوظائف كأزواج مفتاح/قيمة. تتضمن المفاتيح المدعمة:

مفتاح البيانات الأساسية نوع البيانات ملاحظات
Outputs كائن يحمل قيم الاستجابة كما هو محدد بواسطة صفيف bindings في function.json.

على سبيل المثال، إذا تم تكوين وظيفة من خلال ربط إخراج قائمة انتظار باسم "myQueueOutput"، فإن Outputs يحتوي على مفتاح باسم myQueueOutput، والذي تم تعيينه بواسطة المعالج المخصص للرسائل المرسلة إلى قائمة الانتظار.
Logs صفيف تظهر الرسائل في سجلات استدعاء الوظائف.

عند التشغيل في Azure، تظهر الرسائل في Application Insights.
ReturnValue سلسلة تُستخدم لتقديم استجابة عند تكوين خرج مثل $return في ملف function.json.

هذا مثال على بيانات الاستجابة الأساسية.

{
  "Outputs": {
    "res": {
      "body": "Message enqueued"
    },
    "myQueueOutput": [
      "queue message 1",
      "queue message 2"
    ]
  },
  "Logs": [
    "Log message 1",
    "Log message 2"
  ],
  "ReturnValue": "{\"hello\":\"world\"}"
}

الأمثلة

يمكن تنفيذ معالجات مخصصة بأي لغة تدعم تلقي أحداث HTTP. توضح الأمثلة التالية كيفية تنفيذ معالج مخصص باستخدام لغة البرمجة Go.

الوظيفة مع الروابط

يتميز السيناريو المنفذ في هذا المثال بوظيفة تسمى order تقبل POST مع بيانات أساسية تمثل طلب منتج. أثناء ترحيل طلب إلى الوظيفة، يتم إنشاء رسالة تخزين قائمة الانتظار وإرجاع استجابة HTTP.

تنفيذ

في المجلد المسمى الطلب، يقوم ملف function.json بتكوين الوظيفة التي يتم تشغيلها بواسطة HTTP.

order/function.json

{
  "bindings": [
    {
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["post"]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    },
    {
      "type": "queue",
      "name": "message",
      "direction": "out",
      "queueName": "orders",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

يتم تعريف هذه الوظيفة على أنها وظيفة يتم تشغيلها بواسطة HTTP والتي تعرض استجابة HTTP وتخرج رسالة تخزين قائمة الانتظار.

في جذر التطبيق، تمت تكوين ملف host.json لتشغيل ملف قابل للتنفيذ باسم handler.exe (handler في Linux أو macOS).

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler.exe"
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[1.*, 2.0.0)"
  }
}

يعد هذا هو طلب HTTP المرسل إلى وقت تشغيل الوظائف.

POST http://127.0.0.1:7071/api/order HTTP/1.1
Content-Type: application/json

{
  "id": 1005,
  "quantity": 2,
  "color": "black"
}

سيرسل وقت تشغيل الوظائف بعد ذلك طلب HTTP التالي إلى المعالج المخصص:

POST http://127.0.0.1:<FUNCTIONS_CUSTOMHANDLER_PORT>/order HTTP/1.1
Content-Type: application/json

{
  "Data": {
    "req": {
      "Url": "http://localhost:7071/api/order",
      "Method": "POST",
      "Query": "{}",
      "Headers": {
        "Content-Type": [
          "application/json"
        ]
      },
      "Params": {},
      "Body": "{\"id\":1005,\"quantity\":2,\"color\":\"black\"}"
    }
  },
  "Metadata": {
  }
}

إشعار

تمت إزالة بعض أجزاء البيانات الأساسية لغرض الإيجاز.

يعد handler.exe عبارة عن برنامج معالج لغة Go المخصص للتحويل برمجياً والذي يقوم بتشغيل خادم ويب ويستجيب لطلبات استدعاء الوظيفة من مضيف الوظائف.

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
)

type InvokeRequest struct {
	Data     map[string]json.RawMessage
	Metadata map[string]interface{}
}

type InvokeResponse struct {
	Outputs     map[string]interface{}
	Logs        []string
	ReturnValue interface{}
}

func orderHandler(w http.ResponseWriter, r *http.Request) {
	var invokeRequest InvokeRequest

	d := json.NewDecoder(r.Body)
	d.Decode(&invokeRequest)

	var reqData map[string]interface{}
	json.Unmarshal(invokeRequest.Data["req"], &reqData)

	outputs := make(map[string]interface{})
	outputs["message"] = reqData["Body"]

	resData := make(map[string]interface{})
	resData["body"] = "Order enqueued"
	outputs["res"] = resData
	invokeResponse := InvokeResponse{outputs, nil, nil}

	responseJson, _ := json.Marshal(invokeResponse)

	w.Header().Set("Content-Type", "application/json")
	w.Write(responseJson)
}

func main() {
	customHandlerPort, exists := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT")
	if !exists {
		customHandlerPort = "8080"
	}
	mux := http.NewServeMux()
	mux.HandleFunc("/order", orderHandler)
	fmt.Println("Go server Listening on: ", customHandlerPort)
	log.Fatal(http.ListenAndServe(":"+customHandlerPort, mux))
}

في هذا المثال، يقوم المعالج المخصص بتشغيل خادم ويب لمعالجة أحداث HTTP ويتم تعيينه على الانصات للطلبات عبر FUNCTIONS_CUSTOMHANDLER_PORT.

على الرغم من أن مضيف الوظائف تلقى طلب HTTP الأصلي في /api/order، فإنه يستدعي المعالج المخصص باستخدام اسم الوظيفة (اسم المجلد الخاص به). في هذا المثال، يتم تحديد الوظيفة في مسار /order. يرسل المضيف إلى المعالج المخصص طلب HTTP في مسار /order.

نظراً إلى إرسال طلبات POST إلى هذه الوظيفة، تتوفر بيانات المشغل وبيانات تعريف الوظيفة عبر نص طلب HTTP الأساسي. يمكن الوصول إلى نص طلب HTTP الأساسي الأصلي في البيانات النافعة الأساسية Data.req.Body.

يتم تنسيق استجابة الوظيفة في أزواج مفتاح/قيمة حيث يحتفظ العضو Outputs بقيمة JSON حيث تتطابق المفاتيح مع المخرجات كما هو محدد في ملف function.json.

هذا مثال على البيانات الأساسية التي يقوم هذا المعالج بإرجاعها إلى مضيف الوظائف.

{
  "Outputs": {
    "message": "{\"id\":1005,\"quantity\":2,\"color\":\"black\"}",
    "res": {
      "body": "Order enqueued"
    }
  },
  "Logs": null,
  "ReturnValue": null
}

من خلال تعيين إخراج message مساوياً لبيانات الطلب الواردة من الطلب، تقوم الوظيفة بإخراج بيانات الطلب إلى قائمة الانتظار المكونة. يعمل مضيف الوظائف أيضاً على إرجاع استجابة HTTP المكونة في res إلى المستدعي.

وظيفة HTTP فقط

بالنسبة إلى الوظائف التي يتم تشغيلها بواسطة HTTP بدون روابط أو مخرجات إضافية، قد ترغب في أن يعمل المعالج مباشرةً مع طلب واستجابة HTTP بدلاً من بيانات الطلب والاستجابة الأساسية للمعالج المخصص. يمكن تكوين هذا السلوك في host.json باستخدام الإعداد enableForwardingHttpRequest.

هام

يتمثل الغرض الأساسي من ميزة المعالجات المخصصة في تمكين اللغات وأوقات التشغيل التي لا تحتوي حالياً على دعم من الدرجة الأولى في Azure Functions. في حين أنه قد يكون من الممكن تشغيل تطبيقات الويب باستخدام معالجات مخصصة، فإن Azure Functions ليس وكيلاً عكسياً قياسياً. لا تتوفر بعض الميزات مثل تدفق الاستجابة وHTTP/2 وWebSockets. قد يتم تقييد بعض مكونات طلب HTTP مثل بعض العناوين والمسارات. قد يواجه تطبيقك أيضاً بداية متأخرة مفرطة.

لمعالجة هذه الظروف، ضع في اعتبارك تشغيل تطبيقات الويب في Azure App Service.

يوضح المثال التالي كيفية تكوين وظيفة يتم تشغيلها بواسطة HTTP بدون روابط أو مخرجات إضافية. يتميز السيناريو الذي تم تنفيذه في هذا المثال بوظيفة تسمى hello تقبل GET أو POST.

تنفيذ

في المجلد المسمى hello، يقوم ملف function.json بتكوين الوظيفة المشغلة بواسطة HTTP.

hello/function.json

{
  "bindings": [
    {
      "type": "httpTrigger",
      "authLevel": "anonymous",
      "direction": "in",
      "name": "req",
      "methods": ["get", "post"]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}

تم تكوين الوظيفة لقبول كل من طلبات GET وPOST ويتم توفير قيمة النتيجة عبر وسيطة تسمى res.

في جذر التطبيق، تم تكوين ملف host.json لتشغيل handler.exe وتعيين enableForwardingHttpRequest إلى true.

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler.exe"
    },
    "enableForwardingHttpRequest": true
  }
}

عندما يكون enableForwardingHttpRequest هو true، يختلف سلوك وظائف HTTP فقط عن سلوك المعالجات المخصصة الافتراضية في هذه الطرق:

  • لا يحتوي طلب HTTP على بيانات طلب المعالجات المخصصة الأساسية. بدلاً من ذلك، يستدعي مضيف Functions المعالج بنسخة من طلب HTTP الأصلي.
  • يستدعي مضيف الوظائف المعالج بنفس مسار الطلب الأصلي، بما في ذلك أي معلمات سلسلة استعلام.
  • يُرجِع مضيف Functions نسخة من استجابة HTTP للمعالج كاستجابة للطلب الأصلي.

فيما يلي طلب POST إلى مضيف الوظائف. ثم يرسل مضيف الوظائف نسخة من الطلب إلى المعالج المخصص في نفس المسار.

POST http://127.0.0.1:7071/api/hello HTTP/1.1
Content-Type: application/json

{
  "message": "Hello World!"
}

يقوم ملف handler.go بتنفيذ خادم ويب ووظيفة HTTP.

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	if r.Method == "GET" {
		w.Write([]byte("hello world"))
	} else {
		body, _ := ioutil.ReadAll(r.Body)
		w.Write(body)
	}
}

func main() {
	customHandlerPort, exists := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT")
	if !exists {
		customHandlerPort = "8080"
	}
	mux := http.NewServeMux()
	mux.HandleFunc("/api/hello", helloHandler)
	fmt.Println("Go server Listening on: ", customHandlerPort)
	log.Fatal(http.ListenAndServe(":"+customHandlerPort, mux))
}

في هذا المثال، يقوم المعالج المخصص بإنشاء خادم ويب لمعالجة أحداث HTTP ويتم تعيينه على الانصات للطلبات عبر FUNCTIONS_CUSTOMHANDLER_PORT.

يتم معالجة طلبات GET من خلال إرجاع السلسلة، ويكون لطلبات POST حق الوصول إلى نص الطلب الأساسي.

يعد مسار وظيفة الطلب هنا هو /api/hello، مثل الطلب الأصلي.

إشعار

يعد FUNCTIONS_CUSTOMHANDLER_PORT ليس هو المنفذ العام المستخدم لاستدعاء الوظيفة. يُستخدم هذا المنفذ من قبل مضيف الوظائف لاستدعاء المعالج المخصص.

النشر

يمكن نشر معالج مخصص لكل خيار استضافة Azure Functions. إذا كان المعالج يتطلب تبعيات نظام التشغيل أو النظام الأساسي (مثل وقت تشغيل اللغة)، فقد تحتاج إلى استخدام حاوية مخصصة.

عند إنشاء تطبيق وظيفة في Azure للمعالجات المخصصة، نوصيك بتحديد .NET Core كمكدس.

لنشر تطبيق معالج مخصص باستخدام Azure Functions Core Tools، قم بتشغيل الأمر التالي.

func azure functionapp publish $functionAppName

إشعار

تأكد من أن جميع الملفات المطلوبة لتشغيل المعالج المخصص موجودة في المجلد ومضمنة في عملية النشر. إذا كان المعالج المخصص عبارة عن ملف ثنائي قابل للتنفيذ أو يحتوي على تبعيات خاصة بالنظام الأساسي، فتأكد من تطابق هذه الملفات مع النظام الأساسي للنشر المستهدف.

القيود

  • يجب أن يبدأ خادم الويب المخصص للمعالج في غضون 60 ثانية.

العينات

راجع عينات المعالج المخصص لـ GitHub repo للحصول على أمثلة حول كيفية تنفيذ الوظائف في مجموعة متنوعة من اللغات المختلفة.

استكشاف الأخطاء وإصلاحها والدعم

تتبع التسجيل

إذا فشلت عملية المعالج المخصص في البدء أو إذا كانت تواجه مشكلات في الاتصال بمضيف الوظائف، فيمكنك زيادة مستوى سجل تطبيق الوظيفة إلى Trace لرؤية المزيد من رسائل التشخيص من المضيف.

لتغيير مستوى السجل الافتراضي لتطبيق الوظيفة، قم بتكوين الإعداد logLevel في قسم logging لـ host.json.

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler.exe"
    }
  },
  "logging": {
    "logLevel": {
      "default": "Trace"
    }
  }
}

يعمل مضيف الوظائف على إخراج رسائل سجل إضافية، بما في ذلك المعلومات المتعلقة بعملية المعالج المخصص. استخدم السجلات لاستكشاف المشكلات في بدء تشغيل معالجة المعالج المخصص أو استدعاء الوظائف في المعالج المخصص.

بشكل محلي، تتم طباعة السجلات على وحدة التحكم.

في Azure، استعلم عن تتبعات Application Insights لعرض رسائل السجل. إذا كان تطبيقك ينتج عدداً كبيراً من السجلات، فسيتم إرسال مجموعة فرعية فقط من رسائل السجل إلى Application Insights. قم بتعطيل أخذ العينات لضمان تسجيل جميع الرسائل.

اختبار المعالج المخصص بمعزل عن غيره

تعد تطبيقات المعالج المخصص عبارة عن عملية خادم ويب، لذا قد يكون من المفيد أن تبدأ تشغيلها بمفردها واختبار استدعاءات الوظائف عن طريق إرسال طلبات HTTP وهمية باستخدام أداة مثل cURL أو Postman.

ويمكنك أيضاً استخدام هذه الإستراتيجية في مسارات التكامل المستمر والتسليم المستمر لإجراء اختبارات تلقائية على معالجك المخصص.

بيئة التنفيذ

تعمل المعالجات المخصصة في نفس البيئة مثل تطبيق Azure Functions النموذجي. اختبر المعالج للتأكد من أن البيئة تحتوي على جميع التبعيات التي تحتاجها للتشغيل. بالنسبة للتطبيقات التي تتطلب تبعيات إضافية، قد تحتاج إلى تشغيلها باستخدام صورة حاوية مخصصة مستضافة على Azure Functions خطة مميزة.

الحصول على الدعم

إذا كنت بحاجة إلى مساعدة بشأن تطبيق وظيفة مع معالجات مخصصة، فيمكنك إرسال طلب من خلال قنوات الدعم العادية. ولكن نظراً إلى التنوع الكبير في اللغات الممكنة المستخدمة لإنشاء تطبيقات معالجات مخصصة، فإن الدعم محدود.

يتوفر الدعم إذا واجه مضيف الوظائف مشاكل في بدء عملية المعالج المخصص أو الاتصال بها. بالنسبة للمشكلات الخاصة بالأعمال الداخلية لعملية المعالج المخصص، مثل المشكلات المتعلقة باللغة أو إطار العمل المختار، يتعذر على فريق الدعم لدينا تقديم المساعدة في هذا السياق.

الخطوات التالية

ابدأ في إنشاء تطبيق Azure Functions باستخدام لغة Go أو Rust من خلال التشغيل السريع المخصص للمعالجات.