Cap'n Web ระบบ RPC ใหม่สำหรับเบราว์เซอร์และเว็บเซิร์ฟเวอร์
(blog.cloudflare.com)- Cap'n Web คือ โปรโตคอล RPC ใหม่ที่พัฒนาด้วย TypeScript โดยออกแบบมาให้เหมาะกับสภาพแวดล้อมเว็บและทำงานได้บน JavaScript runtime ที่หลากหลาย
- โดยไม่ต้องมีสคีมาหรือ boilerplate ที่ยุ่งยาก ก็สามารถใช้ การซีเรียลไลซ์แบบอิง JSON และ รูปแบบข้อมูลที่มนุษย์อ่านได้
- ด้วย โมเดลแบบ object-capability จึงรองรับ การเรียกแบบสองทาง, การส่งต่อ reference ของฟังก์ชันและอ็อบเจ็กต์, promise pipelining และการสร้างแพตเทิร์นด้านความปลอดภัย
- รองรับสภาพแวดล้อมเครือข่ายหลากหลาย เช่น WebSocket, HTTP, postMessage และเป็นโอเพนซอร์สน้ำหนักเบาขนาดต่ำกว่า 10kB
- ไม่เพียงช่วยแก้ปัญหา waterfall แบบเดียวกับ GraphQL เท่านั้น แต่ยังทำให้สามารถออกแบบ RPC ได้อย่างเป็นธรรมชาติคล้าย JavaScript API ทั่วไป
Cap'n Web คืออะไร
- Cap'n Web คือ ระบบ RPC โอเพนซอร์สบนพื้นฐาน TypeScript ที่พัฒนาโดย Cloudflare
- ได้แรงบันดาลใจจาก Cap'n Proto แต่ทำงานได้โดย ไม่ต้องกำหนดสคีมาแยกต่างหาก และใช้ JSON เป็นวิธี ซีเรียลไลซ์ที่เป็นมิตรต่อมนุษย์
- ทำงาน ผสานกับ TypeScript ช่วยยกระดับประสบการณ์นักพัฒนา เช่น autocomplete และ type check ส่วนการตรวจสอบ type ตอนรันไทม์สามารถจัดการแยกต่างหากได้ เช่น type guard
- รองรับ โปรโตคอลเครือข่ายอย่าง HTTP, WebSocket, postMessage และทำงานได้บนเบราว์เซอร์หลัก, Cloudflare Workers, Node.js เป็นต้น
- มีโครงสร้างแบบเบาโดยไม่มี dependency และมีขนาด ต่ำกว่า 10kB เมื่อ minify + gzip
โมเดล object-capability (OCap) ของ Cap'n Web
- ใช้ โมเดลแบบ object-capability ทำให้แสดงรูปแบบการทำงานได้หลากหลายกว่าระบบ RPC แบบเดิม
- การเรียกแบบสองทาง: ไคลเอนต์และเซิร์ฟเวอร์สามารถเรียกฟังก์ชันของกันและกันได้
- การส่งต่อ reference ของฟังก์ชันและอ็อบเจ็กต์: เมื่อส่งฟังก์ชันหรืออ็อบเจ็กต์ผ่าน RPC ฝั่งตรงข้ามจะได้รับสตับและเมื่อเรียกใช้งานจะไปทำงานที่ต้นทาง
- Promise Pipelining: เมื่อเชื่อมหลาย RPC เป็น chain สามารถประมวลผลได้ด้วยการรับส่งข้อมูลไป-กลับบนเครือข่ายเพียงครั้งเดียว
- แพตเทิร์นด้านความปลอดภัย: สามารถสร้างกลไกควบคุมด้านความปลอดภัย เช่น การมอบสิทธิ์และการจัดการเซสชัน ได้อย่างเป็นธรรมชาติ
วิธีใช้งานพื้นฐาน
-
ตัวอย่างไคลเอนต์
import { newWebSocketRpcSession } from "capnweb" let api = newWebSocketRpcSession("wss://example.com/api") let result = await api.hello("World") console.log(result) -
ตัวอย่างเซิร์ฟเวอร์ (อิง Cloudflare Worker)
import { RpcTarget, newWorkersRpcResponse } from "capnweb" class MyApiServer extends RpcTarget { hello(name) { return `Hello, ${name}!` } } export default { fetch(request, env, ctx) { let url = new URL(request.url) if (url.pathname === "/api") { return newWorkersRpcResponse(request, new MyApiServer()) } return new Response("Not found", {status: 404}) } } -
สามารถเพิ่มเมธอดให้ API, ส่ง callback function จากไคลเอนต์, รวมถึงกำหนดและนำ TypeScript interface ไปใช้ได้อย่างง่ายดาย
RPC คืออะไร และจุดเด่นของ Cap'n Web
- RPC (Remote Procedure Call) คือแนวคิดที่ทำให้โปรแกรมสองตัวบนเครือข่ายสื่อสารกันได้ราวกับเป็นการเรียกฟังก์ชัน
- ต่างจากโปรโตคอล HTTP/REST แบบดั้งเดิม RPC ใช้ abstraction แบบการเรียกฟังก์ชัน ทำให้เขียนโค้ดได้ สอดคล้องกับวิธีคิดของนักพัฒนา
- Cap'n Web เข้ากันได้ดีกับแนวทางของ JavaScript ยุคใหม่ เช่น async/await, Promise, Exception
- จากเดิมที่ RPC เคยมีข้อถกเถียงทางประวัติศาสตร์ เช่น การเรียกแบบ synchronous และข้อผิดพลาดเครือข่าย ในสภาพแวดล้อม JS สมัยใหม่สามารถใช้งานได้อย่างปลอดภัยและมีประสิทธิภาพมากขึ้น
สถานการณ์การใช้งานของ Cap'n Web
- เหมาะกับทุกสภาพแวดล้อมที่ต้องการ การสื่อสารผ่านเครือข่ายระหว่าง JavaScript application สองฝั่ง
- เช่น การเรียกระหว่างไคลเอนต์-เซิร์ฟเวอร์ หรือระหว่างไมโครเซอร์วิส
- โดยเฉพาะเว็บแอป ทำงานร่วมกันแบบเรียลไทม์ และการโต้ตอบที่ต้องข้ามขอบเขตความปลอดภัยซับซ้อน
- ยังอยู่ในระยะทดลอง จึงเหมาะยิ่งกับนักพัฒนาที่เปิดรับเทคโนโลยีใหม่
ความสามารถหลากหลาย
โหมด HTTP batch
-
หากไม่จำเป็นต้องเชื่อมต่อแบบต่อเนื่อง สามารถใช้ โหมด HTTP batch เพื่อรวมหลาย RPC call แล้วประมวลผลพร้อมกันได้
import { newHttpBatchRpcSession } from "capnweb" let batch = newHttpBatchRpcSession("https://example.com/api") let result = await batch.hello("World") console.log(result) -
สามารถรันหลายคำสั่งเรียกภายใน batch เดียวพร้อมกัน และรับผลลัพธ์แบบขนานได้
let promise1 = batch.hello("Alice") let promise2 = batch.hello("Bob") let [result1, result2] = await Promise.all([promise1, promise2])
Promise Pipelining (การเรียกแบบ chain)
-
รองรับการ ใช้ผลลัพธ์เป็นอาร์กิวเมนต์ของการเรียกถัดไปได้ทันที โดยไม่ต้องรอผลจากคำสั่งก่อนหน้า
-
ตัวอย่าง) ส่ง Promise ของผลลัพธ์จาก
getMyName()เข้าhello()ได้ทันที และประมวลผลด้วยการรับส่งข้อมูลไป-กลับเพียงครั้งเดียวlet namePromise = batch.getMyName() let result = await batch.hello(namePromise) -
Promise ของ Cap'n Web ทำงานเป็น อ็อบเจ็กต์ proxy จึงสามารถ chain การเรียกเมธอดเพิ่มเติมได้โดยไม่เกิดความล่าช้า
let sessionPromise = batch.authenticate(apiKey) let name = await sessionPromise.whoami()
ความปลอดภัย: การยืนยันตัวตนและ object-capability
- ผ่านเมธอด authenticate สามารถ มอบอ็อบเจ็กต์สิทธิ์ใช้งาน (session) เมื่อสำเร็จ และหลังจากนั้นเรียกใช้ความสามารถต่าง ๆ ได้โดยไม่ต้องผ่านขั้นตอนยืนยันตัวตนเพิ่ม
- ต่างจาก RPC แบบเดิม ไม่สามารถปลอมแปลง session object ได้ และไม่อาจเข้าถึงเมธอดที่ต้องใช้สิทธิ์โดยไม่มีการยืนยันตัวตน
- ช่วยก้าวข้ามข้อจำกัดเชิงโครงสร้างของ WebSocket ได้อย่างเป็นธรรมชาติ และทำให้ตรรกะการยืนยันตัวตนมีความสอดคล้องกัน
- เมื่อประกาศ API interface ด้วย TypeScript ก็สามารถนำไปใช้กับทั้งไคลเอนต์และเซิร์ฟเวอร์ได้โดยอัตโนมัติ พร้อมรองรับ autocomplete และ type safety
เปรียบเทียบกับ GraphQL และจุดแตกต่างของ Cap'n Web
-
GraphQL ช่วยบรรเทาปัญหา waterfall แบบ REST แต่ต้องนำ ภาษา, สคีมา, toolchain ใหม่ เข้ามาใช้
-
Cap'n Web แก้ปัญหา waterfall ได้ด้วยโค้ด JavaScript เพียงอย่างเดียว และ
- ด้วยการรองรับ promise pipelining/object reference จึงสามารถโมเดล nested call หรือ logic ของธุรกรรมที่ซับซ้อนได้อย่างเป็นธรรมชาติ
let user = api.createUser({ name: "Alice" }) let friendRequest = await user.sendFriendRequest("Bob") -
สามารถใช้งานได้คล้าย JavaScript API โดยไม่ต้องแบกรับ ความซับซ้อนและต้นทุนในการเรียนรู้หรือดูแลรักษา แบบ GraphQL
การทำงานกับอาร์เรย์ (array.map เป็นต้น) และการเพิ่มประสิทธิภาพ
-
ใน Cap'n Web สามารถทำ map กับแต่ละสมาชิกของอาร์เรย์ได้โดยไม่ต้องเพิ่มการรับส่งข้อมูลไป-กลับบนเครือข่าย
-
callback function ของ
mapจะถูกรันหนึ่งครั้งที่ฝั่งไคลเอนต์เพื่อบันทึกเนื้อหาการคำนวณแบบ record-replay จากนั้นส่งไปยังเซิร์ฟเวอร์เพื่อประมวลผลแบบรวมชุดlet friendsWithPhotos = friendsPromise.map(friend => { return {friend, photo: api.getUserPhoto(friend.id)} }) let results = await friendsWithPhotos -
ผ่าน DSL เฉพาะโดเมนที่จำกัด ทำให้เขียนได้เหมือนฟังก์ชัน JavaScript แต่เบื้องหลังใช้โปรโตคอลของ Cap'n Web เพื่อเพิ่มประสิทธิภาพการเรียกหลายครั้ง
โครงสร้างโปรโตคอลภายในและลำดับการสื่อสาร
- ส่งข้อมูลแบบมีโครงสร้างด้วย JSON + การประมวลผลล่วงหน้าแบบพิเศษ และรองรับชนิดข้อมูลพิเศษ เช่น อาร์เรย์และวันที่
- เป็นโปรโตคอลแบบสมมาตร จึงรองรับ การสื่อสารสองทางโดยไม่แบ่งแยกไคลเอนต์/เซิร์ฟเวอร์
- แต่ละฝั่ง (เช่น Alice และ Bob) จะจัดการตาราง export/import และแยก reference ของอ็อบเจ็กต์กับฟังก์ชันด้วย ID
- ด้วยข้อความแบบ push/pull และการกำหนด Promise ID ทำให้สามารถสะท้อนหลายคำสั่งเรียกได้ภายในการรับส่งข้อมูลรอบเดียว
สถานะปัจจุบันและกรณีใช้งานจริง
- ขณะนี้ Cap'n Web ยังเป็น โอเพนซอร์สเชิงทดลอง และถูกนำไปใช้ในบริการจริงแล้ว เช่น remote bindings ของ Cloudflare Wrangler
- มีแผนจะเผยแพร่บล็อกโพสต์เพิ่มเติมและทำการทดลองกับฝั่งฟรอนต์เอนด์อีกหลากหลายรูปแบบ
- เผยแพร่ภายใต้สัญญาอนุญาต MIT และ ทุกคนนำไปใช้ได้อย่างอิสระ
- ไปยัง GitHub repository
ยังไม่มีความคิดเห็น