مثال على تطبيق قائم على webhook القائم على الحدث في تخزين كائن S3 Mail.ru Cloud Solutions



تعمل آلة تحضير القهوة من Rube Goldberg



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



بضع كلمات حول تخزين الكائن وخطافات الويب. تسمح لك تخزين الكائنات بتخزين أي بيانات في السحابة ككائنات يمكن الوصول إليها عبر S3 أو API أخرى (حسب التنفيذ) عبر HTTP / HTTPS. تعتبر Webhooks بشكل عام عمليات رد HTTP مخصصة. يتم تشغيلها عادةً بواسطة حدث ، مثل إرسال رمز إلى مستودع أو تعليق منشور على مدونة. عند وقوع حدث ، يرسل الموقع الأصلي طلب HTTP إلى عنوان URL المحدد لخطوة الويب. ونتيجة لذلك ، يمكنك جعل الأحداث على موقع ما تؤدي إلى إجراءات على موقع آخر ( ويكي ). عندما يكون الموقع المصدر هو تخزين الكائن ، فإن الأحداث هي تغييرات في محتواه.



أمثلة للحالات البسيطة التي يمكن فيها استخدام هذه الأتمتة:



  1. . « », .
  2. , , .
  3. ( , , , ).
  4. , , Kubernetes, , .


كمثال ، سنقوم بعمل متغير من المهمة 1 ، عندما تتم مزامنة التغييرات في حزمة تخزين كائن Mail.ru Cloud Solutions (MCS) باستخدام رسائل الويب في تخزين كائن AWS. في حالة تحميل حقيقية ، يجب أن توفر العمل غير المتزامن عن طريق تسجيل الخطافات الإلكترونية في قائمة الانتظار ، ولكن بالنسبة للمهمة التعليمية ، سنقوم بالتنفيذ بدون ذلك.



مخطط العمل



يتم وصف بروتوكول الاتصال بالتفصيل في دليل S3 webhooks على MCS . يحتوي مخطط العمل على العناصر التالية:



  • خدمة نشر موجودة على جانب S3 وتنشر طلبات HTTP عند انطلاق webnhook.
  • خادم استقبال عبر الويب يستمع للطلبات من خدمة نشر HTTP ويتخذ الإجراء المناسب. يمكن كتابة الخادم بأي لغة ، في مثالنا سنكتب الخادم في Go.


إن خصوصية تنفيذ webhook في S3 API هي تسجيل خادم استقبال webhook في خدمة النشر. على وجه الخصوص ، يجب أن يؤكد خادم استلام الرد عبر الويب الاشتراك في رسائل خدمة النشر (في تطبيقات تنفيذ الرد التلقائي على الويب الأخرى ، لا يلزم عادةً تأكيد الاشتراك).



وفقًا لذلك ، يجب أن يدعم خادم استقبال الرد التلقائي عمليتين رئيسيتين:



  • الرد على طلب من خدمة النشر لتأكيد التسجيل ،
  • معالجة الأحداث الواردة.


تثبيت الخادم لتلقي الخطافات



لتشغيل خادم استقبال webhook ، تحتاج إلى خادم Linux. في هذه المقالة ، كمثال ، نستخدم مثيلًا افتراضيًا ننشره في MCS.



تثبيت البرنامج المطلوب وتشغيل خادم webhook.



ubuntu@ubuntu-basic-1-2-10gb:~$ sudo apt-get install git
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
  bc dns-root-data dnsmasq-base ebtables landscape-common liblxc-common 
liblxc1 libuv1 lxcfs lxd lxd-client python3-attr python3-automat 
python3-click python3-constantly python3-hyperlink
  python3-incremental python3-pam python3-pyasn1-modules 
python3-service-identity python3-twisted python3-twisted-bin 
python3-zope.interface uidmap xdelta3
Use 'sudo apt autoremove' to remove them.
Suggested packages:
  git-daemon-run | git-daemon-sysvinit git-doc git-el git-email git-gui 
gitk gitweb git-cvs git-mediawiki git-svn
The following NEW packages will be installed:
  git
0 upgraded, 1 newly installed, 0 to remove and 46 not upgraded.
Need to get 3915 kB of archives.
After this operation, 32.3 MB of additional disk space will be used.
Get:1 http://MS1.clouds.archive.ubuntu.com/ubuntu bionic-updates/main 
amd64 git amd64 1:2.17.1-1ubuntu0.7 [3915 kB]
Fetched 3915 kB in 1s (5639 kB/s)
Selecting previously unselected package git.
(Reading database ... 53932 files and directories currently installed.)
Preparing to unpack .../git_1%3a2.17.1-1ubuntu0.7_amd64.deb ...
Unpacking git (1:2.17.1-1ubuntu0.7) ...
Setting up git (1:2.17.1-1ubuntu0.7) ...


انسخ المجلد باستخدام خادم استقبال الرد التلقائي على الويب:



ubuntu@ubuntu-basic-1-2-10gb:~$ git clone
https://github.com/RomanenkoDenys/s3-webhook.git
Cloning into 's3-webhook'...
remote: Enumerating objects: 48, done.
remote: Counting objects: 100% (48/48), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 114 (delta 20), reused 45 (delta 18), pack-reused 66
Receiving objects: 100% (114/114), 23.77 MiB | 20.25 MiB/s, done.
Resolving deltas: 100% (49/49), done.


لنبدأ الخادم:



ubuntu@ubuntu-basic-1-2-10gb:~$ cd s3-webhook/
ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80


الاشتراك في خدمة النشر



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



  1. انتقل إلى قسم الجرافات في غرفة التحكم.
  2. نذهب إلى الدلو ، الذي سنقوم بإعداده عبر الويب ، وننقر على الترس:






انتقل إلى علامة التبويب Webhooks وانقر على إضافة:





املأ الحقول:







ID - اسم الرد التلقائي على الويب.



الحدث - ما الأحداث لإرسالها. لقد قمنا بتعيين نقل جميع الأحداث التي تحدث عند العمل مع الملفات (الإضافة والحذف).



URL - عنوان خادم استقبال webhook.



بادئة / لاحقة عامل التصفية هي عبارة عن عامل تصفية يسمح بإنشاء رسائل ويب فقط للكائنات التي تتطابق أسماؤها مع قواعد معينة. على سبيل المثال ، لجعل عمل webhook يعمل فقط مع الملفات ذات الامتداد .png ، اكتب "png" في لاحقة التصفية .



حاليًا ، لا يتم دعم سوى المنفذين 80 و 443 للوصول إلى خادم تلقي الرد عبر الويب.



انقر على إضافة خطاف وشاهد ما يلي:





تمت إضافة الخطاف.



يعرض الخادم الخاص باستقبال الخطافات الإلكترونية في السجلات تقدم عملية تسجيل ربط:



ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80
2020/06/15 12:01:14 [POST] incoming HTTP request from 
95.163.216.92:42530
2020/06/15 12:01:14 Got timestamp: 2020-06-15T15:01:13+03:00 TopicArn: 
mcs5259999770|myfiles-ash|s3:ObjectCreated:*,s3:ObjectRemoved:* Token: 
E2itMqAMUVVZc51pUhFWSp13DoxezvRxkUh5P7LEuk1dEe9y URL: 
http://89.208.199.220/webhook
2020/06/15 12:01:14 Generate responce signature: 
3754ce36636f80dfd606c5254d64ecb2fd8d555c27962b70b4f759f32c76b66d


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



وصف الخادم لتلقي الخطافات



في مثالنا ، الخادم مكتوب في Go. دعونا نحلل المبادئ الأساسية لعمله.



package main

// Generate hmac_sha256_hex
func HmacSha256hex(message string, secret string) string {
}

// Generate hmac_sha256
func HmacSha256(message string, secret string) string {
}

// Send subscription confirmation
func SubscriptionConfirmation(w http.ResponseWriter, req *http.Request, body []byte) {
}

// Send subscription confirmation
func GotRecords(w http.ResponseWriter, req *http.Request, body []byte) {
}

// Liveness probe
func Ping(w http.ResponseWriter, req *http.Request) {
    // log request
    log.Printf("[%s] incoming HTTP Ping request from %s\n", req.Method, req.RemoteAddr)
    fmt.Fprintf(w, "Pong\n")
}

//Webhook
func Webhook(w http.ResponseWriter, req *http.Request) {
}

func main() {

    // get command line args
    bindPort := flag.Int("port", 80, "number between 1-65535")
    bindAddr := flag.String("address", "", "ip address in dot format")
    flag.StringVar(&actionScript, "script", "", "external script to execute")
    flag.Parse()

    http.HandleFunc("/ping", Ping)
    http.HandleFunc("/webhook", Webhook)

log.Fatal(http.ListenAndServe(*bindAddr+":"+strconv.Itoa(*bindPort), nil))
}


دعونا ننظر في الوظائف الرئيسية:



  • Ping () هو مسار يستجيب بواسطة URL / ping ، وهو أبسط تنفيذ لمسبار حيوي.
  • Webhook () - المسار الرئيسي ومعالج URL / webhook:

    • تأكيد التسجيل في خدمة النشر (الانتقال إلى وظيفة SubscriptionConfirmation) ،
    • يعالج رسائل الويب الواردة (وظيفة Gotrecords).
  • دالات HmacSha256 و HmacSha256hex - تطبيقات خوارزميات التشفير HMAC-SHA256 و HMAC-SHA256 مع الإخراج كسلسلة من الأرقام السداسية العشرية لطرح التوقيع.
  • main هي الوظيفة الرئيسية ، وتعالج معلمات سطر الأوامر وتسجل معالجات URL.


معلمات سطر الأوامر المقبولة من قبل الخادم:



  • -port هو المنفذ الذي سيستمع إليه الخادم.
  • - العنوان هو عنوان IP الذي سيستمع إليه الخادم.
  • -script هو برنامج خارجي يتم استدعاؤه على كل خطاف يأتي.


دعونا نلقي نظرة فاحصة على بعض الوظائف:



//Webhook
func Webhook(w http.ResponseWriter, req *http.Request) {

    // Read body
    body, err := ioutil.ReadAll(req.Body)
    defer req.Body.Close()
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }

    // log request
    log.Printf("[%s] incoming HTTP request from %s\n", req.Method, req.RemoteAddr)
    // check if we got subscription confirmation request
    if strings.Contains(string(body), 
"\"Type\":\"SubscriptionConfirmation\"") {
        SubscriptionConfirmation(w, req, body)
    } else {
        GotRecords(w, req, body)
    }

}


تحدد هذه الوظيفة ما جاء - طلب تأكيد التسجيل أو الرد التلقائي على الويب. على النحو التالي من الوثائق ، في حالة تأكيد التسجيل ، يأتي هيكل Json التالي في طلب البريد:



POST http://test.com HTTP/1.1
x-amz-sns-messages-type: SubscriptionConfirmation
content-type: application/json

{
    "Timestamp":"2019-12-26T19:29:12+03:00",
    "Type":"SubscriptionConfirmation",
    "Message":"You have chosen to subscribe to the topic $topic. To confirm the subscription you need to response with calculated signature",
    "TopicArn":"mcs2883541269|bucketA|s3:ObjectCreated:Put",
    "SignatureVersion":1,
    "Token":«RPE5UuG94rGgBH6kHXN9FUPugFxj1hs2aUQc99btJp3E49tA»
}


يجب الرد على هذا الطلب:



content-type: application/json

{"signature":«ea3fce4bb15c6de4fec365d36bcebbc34ccddf54616d5ca12e1972f82b6d37af»}


حيث يحسب التوقيع على النحو التالي:



signature = hmac_sha256(url, hmac_sha256(TopicArn, 
hmac_sha256(Timestamp, Token)))


إذا وصل الرد على الويب ، فسيبدو هيكل طلب النشر كما يلي:



POST <url> HTTP/1.1
x-amz-sns-messages-type: SubscriptionConfirmation

{ "Records":
    [
        {
            "s3": {
                "object": {
                    "eTag":"aed563ecafb4bcc5654c597a421547b2",
                    "sequencer":1577453615,
                    "key":"some-file-to-bucket",
                    "size":100
                },
            "configurationId":"1",
            "bucket": {
                "name": "bucketA",
                "ownerIdentity": {
                    "principalId":"mcs2883541269"}
                },
                "s3SchemaVersion":"1.0"
            },
            "eventVersion":"1.0",
            "requestParameters":{
                "sourceIPAddress":"185.6.245.156"
            },
            "userIdentity": {
                "principalId":"2407013e-cbc1-415f-9102-16fb9bd6946b"
            },
            "eventName":"s3:ObjectCreated:Put",
            "awsRegion":"ru-msk",
            "eventSource":"aws:s3",
            "responseElements": {
                "x-amz-request-id":"VGJR5rtJ"
            }
        }
    ]
}


وفقًا لذلك ، بناءً على الطلب ، تحتاج إلى فهم كيفية معالجة البيانات. لقد اخترت السجل كمؤشر "Type":"SubscriptionConfirmation"، لأنه موجود في الطلب لتأكيد الاشتراك وغير موجود في الرد التلقائي على الويب. بناءً على وجود / عدم وجود هذا السجل في طلب POST ، يتم تنفيذ البرنامج الإضافي إما في وظيفة SubscriptionConfirmationأو في وظيفة GotRecords.



لن ننظر في وظيفة SubscriptionConfirmation بالتفصيل ، فقد تم تنفيذها وفقًا للمبادئ المنصوص عليها في الوثائق . يمكنك التحقق من شفرة المصدر لهذه الوظيفة في مستودع git للمشروع .



تحلل الدالة GotRecords الطلب الوارد ، وتستدعي لكل كائن سجل نصًا خارجيًا (تم تمرير اسمه في المعلمة -script) مع المعلمات:



  • اسم الدلو
  • مفتاح الكائن
  • فعل:

    • نسخ - إذا كان في الطلب الأصلي EventName = ObjectCreated | PutObject | PutObjectCopy
    • حذف - إذا كان في الطلب الأصلي EventName = ObjectRemoved | حذف الكائن


وبالتالي ، إذا وصل خطاف مع طلب Post ، كما هو موضح أعلاه ، والمعلمة -script = script.sh ، فسيتم استدعاء البرنامج النصي على النحو التالي:



script.sh  bucketA some-file-to-bucket copy


يجب أن يُفهم أن خادم تلقي الرد عبر الويب هذا ليس حلاً إنتاجيًا كاملاً ، ولكنه مثال مبسط على التنفيذ المحتمل.



مثال على العمل



لنقم بمزامنة ملفات المجموعة الرئيسية في MCS مع حزمة النسخ الاحتياطي في AWS. دلو رئيسي يسمى myfiles-ash ، والنسخ الاحتياطي هو myfiles-backup (تكوين دلو على AWS خارج نطاق هذه المقالة). وفقًا لذلك ، عند وضع ملف في المجموعة الرئيسية ، يجب أن تظهر نسخته في النسخة الاحتياطية ، عند حذفها من المجموعة الرئيسية ، يجب حذفها في النسخة الاحتياطية.



سنعمل مع الدلاء باستخدام الأداة المساعدة awscli ، التي تتوافق معها كل من MCS التخزين السحابي والتخزين السحابي AWS.



ubuntu@ubuntu-basic-1-2-10gb:~$ sudo apt-get install awscli
Reading package lists... Done
Building dependency tree
Reading state information... Done
After this operation, 34.4 MB of additional disk space will be used.
Unpacking awscli (1.14.44-1ubuntu1) ...
Setting up awscli (1.14.44-1ubuntu1) ...


لنقم بتكوين الوصول إلى S3 MCS API:



ubuntu@ubuntu-basic-1-2-10gb:~$ aws configure --profile mcs
AWS Access Key ID [None]: hdywEPtuuJTExxxxxxxxxxxxxx
AWS Secret Access Key [None]: hDz3SgxKwXoxxxxxxxxxxxxxxxxxx
Default region name [None]:
Default output format [None]:


لنقم بتكوين الوصول إلى AWS S3 API:



ubuntu@ubuntu-basic-1-2-10gb:~$ aws configure --profile aws
AWS Access Key ID [None]: AKIAJXXXXXXXXXXXX
AWS Secret Access Key [None]: dfuerphOLQwu0CreP5Z8l5fuXXXXXXXXXXXXXXXX
Default region name [None]:
Default output format [None]:


دعنا نتحقق من الوصول:



إلى AWS:



ubuntu@ubuntu-basic-1-2-10gb:~$ aws s3 ls --profile aws
2020-07-06 08:44:11 myfiles-backup


بالنسبة لـ MCS ، عند تشغيل الأمر ، أضف --endpoint-url:



ubuntu@ubuntu-basic-1-2-10gb:~$ aws s3 ls --profile mcs --endpoint-url 
https://hb.bizmrg.com
2020-02-04 06:38:05 databasebackups-0cdaaa6402d4424e9676c75a720afa85
2020-05-27 10:08:33 myfiles-ash


تم الوصول إليه.



الآن دعنا نكتب برنامجًا نصيًا للتعامل مع الخطاف الوارد ، فلنطلق عليه s3_backup_mcs_aws.sh



#!/bin/bash
# Require aws cli
# if file added — copy it to backup bucket
# if file removed — remove it from backup bucket
# Variables
ENDPOINT_MCS="https://hb.bizmrg.com"
AWSCLI_MCS=`which aws`" --endpoint-url ${ENDPOINT_MCS} --profile mcs s3"
AWSCLI_AWS=`which aws`" --profile aws s3"
BACKUP_BUCKET="myfiles-backup"

SOURCE_BUCKET="${1}"
SOURCE_FILE="${2}"
ACTION="${3}"

SOURCE="s3://${SOURCE_BUCKET}/${SOURCE_FILE}"
TARGET="s3://${BACKUP_BUCKET}/${SOURCE_FILE}"
TEMP="/tmp/${SOURCE_BUCKET}/${SOURCE_FILE}"

case ${ACTION} in
    "copy")
    ${AWSCLI_MCS} cp "${SOURCE}" "${TEMP}"
    ${AWSCLI_AWS} cp "${TEMP}" "${TARGET}"
    rm ${TEMP}
    ;;

    "delete")
    ${AWSCLI_AWS} rm ${TARGET}
    ;;

    *)
    echo "Usage: ${0} sourcebucket sourcefile copy/delete"
    exit 1
    ;;
esac


نبدأ الخادم:



ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80 -
script scripts/s3_backup_mcs_aws.sh


تحقق من كيفية عملها. من خلال واجهة الويب MCS ، أضف ملف test.txt في دلو myfiles-ash. في السجلات في وحدة التحكم ، يمكنك رؤية أنه تم تقديم طلب إلى خادم webhook:



2020/07/06 09:43:08 [POST] incoming HTTP request from 
95.163.216.92:56612
download: s3://myfiles-ash/test.txt to ../../../tmp/myfiles-ash/test.txt
upload: ../../../tmp/myfiles-ash/test.txt to 
s3://myfiles-backup/test.txt


دعونا نتحقق من محتويات دلو myfiles-backup في AWS:



ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ aws s3 --profile aws ls 
myfiles-backup
2020-07-06 09:43:10       1104 test.txt


الآن ، من خلال واجهة الويب ، احذف الملف من دلو myfiles-ash.



سجلات الخادم:



2020/07/06 09:44:46 [POST] incoming HTTP request from 
95.163.216.92:58224
delete: s3://myfiles-backup/test.txt


محتوى دلو:



ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ aws s3 --profile aws ls 
myfiles-backup
ubuntu@ubuntu-basic-1-2-10gb:~$


تم حذف الملف ، تم حل المشكلة.



الخلاصة و تودو



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



هذا الرمز ليس أكثر من مثال على كيفية استخدام الخطاف الإلكتروني S3 في أنشطتك. كما قلت في البداية ، إذا كنت تخطط لاستخدام مثل هذا الخادم في الإنتاج ، فيجب عليك على الأقل إعادة كتابة الخادم للعمل غير المتزامن: تسجيل رسائل الويب الواردة في قائمة انتظار (RabbitMQ أو NATS) ، ومن هناك قم بتفكيكها ومعالجتها بواسطة تطبيقات عاملة. خلاف ذلك ، مع الوصول الهائل للرسائل الإلكترونية ، قد تواجه نقصًا في موارد الخادم لأداء المهام. يسمح لك وجود قوائم الانتظار بنشر الخادم والعاملين ، بالإضافة إلى حل المشكلات المتعلقة بتكرار المهام في حالة الفشل. من المستحسن أيضًا تغيير التسجيل إلى تسجيل أكثر تفصيلاً وتوحيدًا.



حظا سعيدا!



اقرأ المزيد عن الموضوع:






All Articles