با اعمال فشار معکوس، از غرق شدن برنامهتان در پیامهای WebSocket یا هجوم پیامها به سرور WebSocket جلوگیری کنید.
پیشینه
رابط برنامهنویسی کاربردی وبسوکت (WebSocket API) یک رابط جاوااسکریپت برای پروتکل وبسوکت فراهم میکند که امکان ایجاد یک جلسه ارتباط تعاملی دوطرفه بین مرورگر کاربر و سرور را فراهم میکند. با استفاده از این رابط برنامهنویسی کاربردی (API)، میتوانید پیامهایی را به یک سرور ارسال کنید و پاسخهای رویدادمحور را بدون نیاز به نظرسنجی از سرور برای دریافت پاسخ، دریافت کنید.
API استریمها
API مربوط به Streams به جاوااسکریپت اجازه میدهد تا به صورت برنامهنویسی شده به جریانهایی از تکههای داده دریافتی از طریق شبکه دسترسی پیدا کرده و آنها را به دلخواه پردازش کند. یک مفهوم مهم در زمینه Streams، backpressure است. این فرآیندی است که توسط آن یک Stream یا یک زنجیره Pipe سرعت خواندن یا نوشتن را تنظیم میکند. هنگامی که خود Stream یا یک Stream بعدی در زنجیره Pipe هنوز مشغول است و هنوز آماده پذیرش تکههای بیشتر نیست، سیگنالی را به عقب از طریق زنجیره ارسال میکند تا تحویل را به طور مناسب کند کند.
مشکل API فعلی WebSocket
اعمال فشار معکوس بر پیامهای دریافتی غیرممکن است
با API فعلی WebSocket، واکنش به یک پیام در WebSocket.onmessage اتفاق میافتد، یک EventHandler که هنگام دریافت پیام از سرور فراخوانی میشود.
فرض کنید برنامهای دارید که نیاز دارد هر زمان که پیام جدیدی دریافت میکند، عملیات سنگین پردازش دادهها را انجام دهد. احتمالاً جریان کار را مشابه کد زیر تنظیم میکنید و از آنجایی که await نتیجه فراخوانی process() هستید، باید مشکلی نداشته باشید، درست است؟
// A heavy data crunching operation.
const process = async (data) => {
return new Promise((resolve) => {
window.setTimeout(() => {
console.log('WebSocket message processed:', data);
return resolve('done');
}, 1000);
});
};
webSocket.onmessage = async (event) => {
const data = event.data;
// Await the result of the processing step in the message handler.
await process(data);
};
اشتباه است! مشکل API فعلی WebSocket این است که هیچ راهی برای اعمال فشار معکوس وجود ندارد. وقتی پیامها سریعتر از آنچه متد process() میتواند آنها را مدیریت کند، میرسند، فرآیند رندر یا با بافر کردن آن پیامها حافظه را پر میکند، یا به دلیل استفاده ۱۰۰٪ از CPU، پاسخگو نخواهد بود، یا هر دو.
اعمال فشار معکوس به پیامهای ارسالی غیر ارگونومیک است
اعمال فشار معکوس بر پیامهای ارسالی امکانپذیر است، اما شامل نمونهبرداری از ویژگی WebSocket.bufferedAmount میشود که ناکارآمد و غیر ارگونومیک است. این ویژگی فقط خواندنی، تعداد بایتهای دادهای را که با استفاده از فراخوانیهای WebSocket.send() در صف قرار گرفتهاند، اما هنوز به شبکه منتقل نشدهاند، برمیگرداند. این مقدار پس از ارسال تمام دادههای در صف، به صفر بازنشانی میشود، اما اگر به فراخوانی WebSocket.send() ادامه دهید، به افزایش خود ادامه خواهد داد.
رابط برنامهنویسی کاربردی WebSocketStream چیست؟
API وبساکتاستریم با ادغام استریمها با API وبساکت، مشکل عدم وجود یا عدم ارگونومیِ فشار برگشتی را برطرف میکند. این بدان معناست که فشار برگشتی میتواند «به صورت رایگان» و بدون هیچ هزینه اضافی اعمال شود.
موارد استفاده پیشنهادی برای API WebSocketStream
نمونههایی از سایتهایی که میتوانند از این API استفاده کنند عبارتند از:
- برنامههای WebSocket با پهنای باند بالا که نیاز به حفظ تعامل، به ویژه ویدیو و اشتراکگذاری صفحه نمایش دارند.
- به طور مشابه، ضبط ویدیو و سایر برنامههایی که دادههای زیادی را در مرورگر تولید میکنند که باید در سرور بارگذاری شوند. با فشار معکوس، کلاینت میتواند به جای انباشت دادهها در حافظه، تولید دادهها را متوقف کند.
وضعیت فعلی
| قدم | وضعیت |
|---|---|
| ۱. توضیحدهنده ایجاد کنید | کامل |
| ۲. پیشنویس اولیه مشخصات را ایجاد کنید | در حال انجام |
| ۳. بازخورد جمعآوری کنید و روی طراحی تکرار کنید | در حال انجام |
| ۴. آزمایش مبدا | کامل |
| ۵. راهاندازی | شروع نشده |
نحوه استفاده از API وبساکتاستریم
API مربوط به WebSocketStream مبتنی بر promise است که کار با آن را در دنیای مدرن جاوا اسکریپت طبیعی میکند. شما با ساخت یک WebSocketStream جدید و ارسال URL سرور WebSocket به آن شروع میکنید. در مرحله بعد، منتظر opened اتصال هستید که منجر به یک ReadableStream و/یا یک WritableStream میشود.
با فراخوانی متد ReadableStream.getReader() ، در نهایت یک ReadableStreamDefaultReader به دست میآورید که میتوانید تا زمان اتمام استریم، یعنی تا زمانی که شیءای به شکل {value: undefined, done: true} را برگرداند، از آن دادهها را read() .
بر این اساس، با فراخوانی متد WritableStream.getWriter() ، در نهایت یک WritableStreamDefaultWriter به دست میآورید که میتوانید دادهها را در آن write() .
const wss = new WebSocketStream(WSS_URL);
const {readable, writable} = await wss.opened;
const reader = readable.getReader();
const writer = writable.getWriter();
while (true) {
const {value, done} = await reader.read();
if (done) {
break;
}
const result = await process(value);
await writer.write(result);
}
فشار معکوس
در مورد ویژگی backpressure که وعده داده شده چطور؟ شما آن را "رایگان" دریافت میکنید، هیچ مرحله اضافی لازم نیست. اگر process() زمان بیشتری طول بکشد، پیام بعدی فقط زمانی مصرف میشود که خط لوله آماده باشد. به همین ترتیب، مرحله WritableStreamDefaultWriter.write() فقط در صورتی ادامه مییابد که انجام آن ایمن باشد.
مثالهای پیشرفته
آرگومان دوم WebSocketStream یک گزینه برای امکان توسعههای بعدی است. تنها گزینه protocols است که مانند آرگومان دوم سازنده WebSocket رفتار میکند:
const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;
protocol انتخاب شده و همچنین extensions بالقوه، بخشی از دیکشنری موجود از طریق وعده WebSocketStream.opened هستند. تمام اطلاعات مربوط به اتصال زنده توسط این وعده ارائه میشود، زیرا در صورت عدم موفقیت اتصال، این اطلاعات مرتبط نیستند.
const {readable, writable, protocol, extensions} = await chatWSS.opened;
اطلاعات مربوط به اتصال بسته WebSocketStream
اطلاعاتی که از رویدادهای WebSocket.onclose و WebSocket.onerror در WebSocket API در دسترس بود، اکنون از طریق Promise WebSocketStream.closed در دسترس است. Promise در صورت بسته شدن نامناسب، رد میشود، در غیر این صورت به کد و دلیل ارسال شده توسط سرور پاسخ میدهد.
تمام کدهای وضعیت ممکن و معنی آنها در لیست کدهای وضعیت CloseEvent توضیح داده شده است.
const {code, reason} = await chatWSS.closed;
بستن اتصال WebSocketStream
یک WebSocketStream میتواند با یک AbortController بسته شود. بنابراین، یک AbortSignal به سازنده WebSocketStream ارسال کنید. AbortController.abort() فقط قبل از handshake کار میکند، نه بعد از آن.
const controller = new AbortController();
const wss = new WebSocketStream(URL, {signal: controller.signal});
setTimeout(() => controller.abort(), 1000);
به عنوان یک جایگزین، میتوانید از متد WebSocketStream.close() نیز استفاده کنید، اما هدف اصلی آن امکان تعیین کد و دلیل ارسال به سرور است.
wss.close({closeCode: 4000, reason: 'Game over'});
بهبود تدریجی و قابلیت همکاری
کروم در حال حاضر تنها مرورگری است که API WebSocketStream را پیادهسازی میکند. برای قابلیت همکاری با API کلاسیک WebSocket، اعمال backpressure به پیامهای دریافتی امکانپذیر نیست. اعمال backpressure به پیامهای ارسالی امکانپذیر است، اما شامل نمونهبرداری از ویژگی WebSocket.bufferedAmount میشود که ناکارآمد و غیر ارگونومیک است.
تشخیص ویژگی
برای بررسی اینکه آیا WebSocketStream API پشتیبانی میشود، از دستور زیر استفاده کنید:
if ('WebSocketStream' in window) {
// `WebSocketStream` is supported!
}
نسخه آزمایشی
در مرورگرهای پشتیبانیکننده، میتوانید API مربوط به WebSocketStream را در عمل مشاهده کنید یا iframe تعبیهشده را ببینید.
بازخورد
تیم کروم میخواهد از تجربیات شما در استفاده از API وبساکتاستریم مطلع شود.
در مورد طراحی API به ما بگویید
آیا چیزی در مورد API وجود دارد که آنطور که انتظار داشتید کار نمیکند؟ یا متدها یا ویژگیهایی وجود ندارند که برای پیادهسازی ایده خود به آنها نیاز دارید؟ در مورد مدل امنیتی سؤال یا نظری دارید؟ یک مشکل مربوط به مشخصات را در مخزن مربوطه GitHub ثبت کنید، یا نظرات خود را به یک مشکل موجود اضافه کنید.
گزارش مشکل در پیادهسازی
آیا در پیادهسازی کروم اشکالی پیدا کردید؟ یا پیادهسازی با مشخصات متفاوت است؟ یک اشکال را در new.crbug.com ثبت کنید. حتماً تا حد امکان جزئیات، دستورالعملهای ساده برای بازتولید را ذکر کنید و Blink>Network>WebSockets در کادر Components وارد کنید.
نمایش پشتیبانی از API
آیا قصد دارید از API WebSocketStream استفاده کنید؟ حمایت عمومی شما به تیم کروم کمک میکند تا ویژگیها را اولویتبندی کنند و به سایر فروشندگان مرورگر نشان میدهد که پشتیبانی از آنها چقدر حیاتی است.
با استفاده از هشتگ #WebSocketStream یک توییت به @ChromiumDev ارسال کنید و به ما بگویید که کجا و چگونه از آن استفاده میکنید.
لینکهای مفید
- توضیح دهنده عمومی
- نسخه آزمایشی API WebSocketStream | منبع نسخه آزمایشی API WebSocketStream
- اشکال ردیابی
- ورودی ChromeStatus.com
- کامپوننت چشمک زن:
Blink>Network>WebSockets
تقدیرنامهها
WebSocketStream API توسط آدام رایس و یوتاکا هیرانو پیاده سازی شد.