ब्राउज़र और वेब सर्वर के लिए नया RPC सिस्टम Cap'n Web
(blog.cloudflare.com)- Cap'n Web एक TypeScript में इम्प्लीमेंट किया गया नया RPC प्रोटोकॉल है, जो वेब वातावरण के लिए ऑप्टिमाइज़ किया गया है और विभिन्न JavaScript runtimes में काम करता है
- यह schema या झंझटीले boilerplate के बिना JSON-आधारित serialization और इंसानों के पढ़ने योग्य data format प्रदान करता है
- object-capability आधारित मॉडल के जरिए bidirectional calls, function·object references पास करना, promise pipelining, और security patterns को इम्प्लीमेंट करना संभव है
- यह WebSocket, HTTP, postMessage जैसे विभिन्न नेटवर्क वातावरणों को सपोर्ट करता है और 10kB से कम आकार का lightweight open source है
- यह केवल GraphQL जैसे waterfall problem को हल नहीं करता, बल्कि सामान्य JavaScript API जैसी natural RPC modeling भी संभव बनाता है
Cap'n Web क्या है
- Cap'n Web Cloudflare द्वारा विकसित TypeScript-आधारित open source RPC (protocol) system है
- यह Cap'n Proto से प्रेरित है, लेकिन अलग से schema definition के बिना काम करता है और JSON का उपयोग करने वाला human-friendly serialization अपनाता है
- यह TypeScript के साथ इंटीग्रेट होता है, जिससे autocomplete, type check आदि के जरिए developer experience बेहतर होता है, जबकि runtime type validation को अलग से (जैसे type guard) संभाला जा सकता है
- यह HTTP, WebSocket, postMessage जैसे network protocols को सपोर्ट करता है और प्रमुख browsers, Cloudflare Workers, Node.js आदि पर चलता है
- dependency-free lightweight structure के साथ यह minify + gzip के बाद 10kB से कम आकार में उपलब्ध है
Cap'n Web का object-capability model (OCap)
- यह object-capability (OCap) आधारित मॉडल अपनाता है, जिससे पारंपरिक RPC systems की तुलना में कहीं अधिक अभिव्यक्ति संभव होती है
- bidirectional calls: client और server एक-दूसरे के functions को कॉल कर सकते हैं
- function·object reference passing: किसी function या object को RPC के जरिए पास करने पर, सामने वाला एक stub पाता है और उसे कॉल करने पर वह मूल स्थान पर execute होता है
- Promise Pipelining: कई RPC calls को chain में जोड़ने पर उन्हें एक ही network round-trip में प्रोसेस किया जा सकता है
- security patterns: authorization और session management जैसे security controls को स्वाभाविक रूप से इम्प्लीमेंट किया जा सकता है
बुनियादी उपयोग
-
client example
import { newWebSocketRpcSession } from "capnweb" let api = newWebSocketRpcSession("wss://example.com/api") let result = await api.hello("World") console.log(result) -
server example (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 में methods जोड़ना, client की callback function पास करना, और TypeScript interface को define व apply करना आसानी से किया जा सकता है
RPC क्या है, और Cap'n Web में इसकी खासियतें
- RPC(Remote Procedure Call) एक ऐसा कॉन्सेप्ट है, जिसमें नेटवर्क पर मौजूद दो प्रोग्राम आपस में ऐसे communicate कर सकते हैं जैसे वे function calls कर रहे हों
- पारंपरिक HTTP/REST protocols से अलग, RPC function call abstraction के जरिए ऐसा code लिखने देता है जो developer की सोच के अधिक करीब हो
- Cap'n Web async/await, Promise, Exception support आदि के साथ आधुनिक JavaScript flow में अच्छी तरह फिट बैठता है
- RPC से जुड़ी ऐतिहासिक बहसों (synchronous calls, network errors) के विपरीत, आधुनिक JS environment में इसका उपयोग अधिक सुरक्षित और कुशल तरीके से किया जा सकता है
Cap'n Web के उपयोग के परिदृश्य
- यह उन सभी environments में उपयोगी है जहाँ दो JavaScript applications के बीच network communication की आवश्यकता हो
- client-server, microservices के बीच calls आदि
- खासकर real-time collaboration web apps और जटिल security boundaries को पार करने वाली interactions के लिए उपयुक्त है
- यह अभी experimental stage में है, इसलिए नई तकनीकों को अपनाने के लिए खुले developers के लिए अधिक उपयोगी हो सकता है
विभिन्न फीचर्स
HTTP batch mode
-
जब लगातार connection की आवश्यकता न हो, तब HTTP batch mode में कई RPC calls को एक साथ बाँधकर प्रोसेस किया जा सकता है
import { newHttpBatchRpcSession } from "capnweb" let batch = newHttpBatchRpcSession("https://example.com/api") let result = await batch.hello("World") console.log(result) -
एक ही batch के भीतर कई calls को साथ में execute कर, results को parallel में प्राप्त किया जा सकता है
let promise1 = batch.hello("Alice") let promise2 = batch.hello("Bob") let [result1, result2] = await Promise.all([promise1, promise2])
Promise Pipelining (chain calls)
-
पिछले call के result का इंतज़ार किए बिना, उस result को सीधे अगले call के argument के रूप में उपयोग करने का समर्थन है
-
उदाहरण)
getMyName()के result Promise को सीधेhello()में पास करके एक ही network round-trip में प्रोसेस किया जा सकता हैlet namePromise = batch.getMyName() let result = await batch.hello(namePromise) -
Cap'n Web का Promise एक proxy object की तरह काम करता है, जिससे अतिरिक्त method calls भी बिना देरी के chain में प्रोसेस हो सकते हैं
let sessionPromise = batch.authenticate(apiKey) let name = await sessionPromise.whoami()
सुरक्षा: authentication और object-capability
authenticatemethod के जरिए सफलता पर एक permission (session) object दिया जाता है, जिसके बाद अतिरिक्त authentication steps के बिना features को कॉल किया जा सकता है- पारंपरिक RPC से अलग, session object को forge नहीं किया जा सकता, और authentication के बिना permission-आधारित methods तक पहुँचना संभव नहीं है
- यह WebSocket की संरचनात्मक सीमाओं को स्वाभाविक रूप से पार करता है और authentication logic की consistency सुनिश्चित करता है
- TypeScript में API interface declare करने पर, इसे client~server पर अपने-आप लागू किया जा सकता है, जिससे autocomplete और type safety मिलती है
GraphQL से तुलना और Cap'n Web की अलग पहचान
-
GraphQL REST की waterfall problem को कम करता है, लेकिन इसके लिए नई language·schema·toolchain अपनानी पड़ती है
-
Cap'n Web केवल JavaScript code के जरिए waterfall problem को हल करता है,
- promise pipelining/object references के support से nested calls या complex transaction logic को स्वाभाविक रूप से model किया जा सकता है
let user = api.createUser({ name: "Alice" }) let friendRequest = await user.sendFriendRequest("Bob") -
GraphQL की complexity और learning/management cost के बिना, इसे JavaScript API की तरह इस्तेमाल किया जा सकता है
array operations (array.map आदि) और optimization
-
Cap'n Web में array के प्रत्येक element पर अतिरिक्त network round-trip के बिना map operation संभव है
-
mapcallback function को client पर एक बार चलाकर operation की सामग्री record-replay की जाती है, फिर उसे server पर भेजकर server-side पर bulk processing की जाती हैlet friendsWithPhotos = friendsPromise.map(friend => { return {friend, photo: api.getUserPhoto(friend.id)} }) let results = await friendsWithPhotos -
सीमित domain-specific language (DSL) के जरिए इसे JavaScript function जैसा व्यक्त किया जाता है, लेकिन वास्तव में Cap'n Web protocol का उपयोग करके multiple calls को optimize किया जाता है
आंतरिक protocol structure और communication flow
- JSON + विशेष preprocessing के जरिए structured data transfer किया जाता है, और arrays, dates जैसे विशेष types का support मिलता है
- symmetric protocol होने के कारण client-server का भेद बिना bidirectional communication संभव है
- प्रत्येक party (उदाहरण: Alice और Bob) export/import tables को मैनेज करती है और object·function references को ID के आधार पर अलग करती है
- push/pull messages और Promise ID allocation आदि के जरिए, एक ही round trip में कई calls को शामिल किया जा सकता है
मौजूदा स्थिति और उपयोग के उदाहरण
- Cap'n Web अभी भी experimental open source है, और Cloudflare Wrangler के remote bindings जैसे वास्तविक services में उपयोग किया जा रहा है
- आगे और blog posts तथा विभिन्न frontend experiments की योजना है
- यह MIT license के तहत जारी है, इसलिए कोई भी इसे स्वतंत्र रूप से अपना सकता है
- GitHub repository पर जाएँ
1 टिप्पणियां
Hacker News की राय
दो बातें जानने की जिज्ञासा है
grpc/avroआदि) इस समस्या को सीधे हल करने की कोशिश करते हैंमुझे यह सचमुच बहुत innovative काम लगता है
अगर callbacks वाला कोई subscription object है, तो API को इस तरह डिजाइन करना चाहिए कि start करते समय ‘आखिरी बार देखा गया message’ specify किया जा सके। तब data वहीं से तुरंत फिर मिल सकता है और बीच में कुछ miss नहीं होगा
लगता है ऐसे design patterns पर कभी blog post series लिखनी चाहिए
array problem को कैसे हल किया गया, इस पर section सचमुच दिलचस्प है और साथ ही थोड़ा डरावना भी ब्लॉग लिंक
.map()के मामले में, सीधे server को JavaScript code नहीं भेजा जाता, लेकिन “code” जैसी कोई चीज भेजी जाती है, जो एक सीमित domain-specific language (DSL) का इस्तेमाल करती है। client side पर callback को placeholder values के साथ एक बार चलाया जाता है, फिर उसके behavior को record-replay तरीके से trace करके server को instruction set भेजा जाता है। server उन instructions को लेकर array के हर member पर execute करता है।यानी developer ने बस सामान्य js method इस्तेमाल किया, लेकिन असल में इसे एक संकीर्ण DSL में बदलने वाली trick लागू होती है। callback केवल synchronous होना चाहिए,
awaitनहीं कर सकता। उसकी जगह सिर्फ promise pipelining की अनुमति है ताकि उस पूरे flow को capture करके server को भेजा जा सके, और server जरूरत पड़ने पर उसे दोबारा execute कर सकेC# में इस तरह की समस्या के लिए expression trees हैं। Entity Framework जब lambda expressions लेकर उन्हें SQL queries में बदलता है, तो वही उपयोग करता है। यानी code को execute किए बिना scan या transform करते हुए इस्तेमाल किया जा सकता है
उदाहरण के लिए,
db.People.Where(p => p.Name == "Joe")मेंWhereवास्तव में predicate function नहीं बल्कि expression लेता है, इसलिए वह दिए गए code को scan करके देखता है कि Name field"Joe"से match करती है या नहीं, और उसे SQLWHEREclause में बदल देता हैJavaScript में ऐसा mechanism नहीं है, इसलिए placeholder values डालकर वह कैसे behave करता है इसे एक-एक करके record करके उसकी नकल की जाती है
हाल ही में Tanstack DB का query DSL बनाते समय भी यही record-replay trick इस्तेमाल की गई थी गाइड लिंक।
where/select/joincallbacks कोRefProxyobject दिया जाता है, और उस object पर कौन से prop/operations होते हैं, यह trace किया जाता है।js में सामान्य operators (
==,>आदि) को सीधे intercept नहीं किया जा सकता, इसलिएeq/gt/notजैसी छोटी traceable functions बनाई जाती हैं, callback को सिर्फ एक बार चलाकर जुड़े हुए expressions को पकड़कर IR बनाया जाता हैदिलचस्प बात यह है कि js spread operator को भी trace करने में सफलता मिली
Kenton, क्या इस concept को
capnwebमें भीfake operators(eq,gt,inआदि) के साथ जोड़कर remote tracing feature दिया जा सकता है?लगता है conditionals पर रोक है (कुछ-कुछ React hooks rules की तरह), तो यह constraint कैसे लागू किया जाता है, यह जानना चाहता हूँ
यह project दिलचस्प है
इसमें ML compiler libraries (
TensorFlow 1,JAX jit,PyTorch compileआदि) जैसी समानताएं हैं। tracing के ज़रिए operation graph बनता है, फिर उसे compile किया जाता है या VM के हिसाब से बदलकर execute किया जाता हैअभी dynamic language को frontend बनाकर नया DSL define करने के बजाय, existing scripting language के भीतर AST transformation छिपा दिया गया है
ML में GPU/linalg kernels चलाने में देरी करके kernels को merge किया जाता है, जबकि Cap'n Web जैसे RPC में network requests को टालकर कई network calls को merge किया जा सकता है
आखिरकार instruction/data plane को अलग करना ही मुख्य बात है, और बहुत छोटे scale पर एक single CPU भी distributed system जैसी संरचना रखता है (instruction/data cache separation)
Cap'n Web में RPC graph खुद instruction की भूमिका निभाता है
यह pattern सचमुच बहुत रोचक है, हालांकि कभी-कभी लगता है कि stack structure (compiler के ऊपर interpreter, interpreter के ऊपर compiler...) अंतहीन दोहराई जा रही है। यह Lispy
code is data, data is codepattern के एक और रूप जैसा महसूस होता है। लगता है इसके पीछे कोई और गहरी कहानी हैअब dynamic languages नए DSLs के frontend बन रहे हैं, और नए syntax तय करने के बजाय scripting के भीतर AST generation घोल दिया जाता है
मुझे लगता है TypeScript यहाँ game changer है। यह JavaScript की runtime flexibility (जैसे Cap'n Web में
Proxyका चतुर उपयोग) और type safety दोनों को एक साथ पकड़ सकता हैइन दिनों ORM की दुनिया में मैं इसी concept में डूबा हुआ हूँ। ज़्यादातर ORMs serial और eager होते हैं, इसलिए query execute होने से ठीक पहले ही उन्हें manipulate किया जा सकता है
मेरा मानना है कि सचमुच composable ORM को compiler की तरह काम करना चाहिए: TypeScript से SQL के ऊपर पूरी तरह type-safe DSL define करो, query AST बनाओ, और आखिर में ही उसे SQL में compile करो
मैं जो Typegres बना रहा हूँ, वह इसी विचार पर आधारित है। अगर यह pattern आपको दिलचस्प लगता है, तो देख सकते हैं
RPC libraries की मुख्य समस्या यह है कि वे यह छिपाने की कोशिश करती हैं कि round-trip कहाँ और कैसे हो रहा है
Cap'n Web की array
.map()को ही देखें, तो यह समझना मुश्किल है कि वास्तव में network round-trip कहाँ हो रहा है।मुझे लगता है यह ‘feature’ नहीं बल्कि ‘bug’ है—code देखकर behavior तुरंत समझ में आना चाहिए, और इसे छिपाना ठीक नहीं है
संदर्भ लिंक
awaitइस्तेमाल करने पर होता हैpromise pipelining में कई statements को बिना
awaitके लगातार सेट किया जा सकता है, इसलिए बीच में कोई अतिरिक्त network round-trip नहीं होता। अंत में एक बारawaitकरेंगे, वही सब कुछ होगाअगर आपने gRPC और web के साथ काम किया है, तो आपको पता होगा कि Protobuf को web पर लागू करना कितना painful है
Cap'n Web की simplicity सचमुच बहुत अच्छी लगती है capnproto दस्तावेज़
Cap'n Web, Cap'n Proto से अलग, पूरी तरह schemaless है। लगभग कोई बेकार boilerplate नहीं है, इसलिए इसमें Cloudflare Workers के JavaScript-native RPC जैसा feel आता है
github संदर्भ
kentonvकी नई library देखकर तुरंत यहाँ आयाGitHub पर code देखकर हैरानी हुई कि इसका आकार उम्मीद से काफी छोटा है। क्या सच में बस इतना ही है?
सैद्धांतिक रूप से server-side हिस्से को दूसरी languages में port करना बहुत कठिन नहीं होना चाहिए, और मैं इसे Elixir server तथा JS/TS frontend में आज़माना चाहूँगा
किसी LLM से इस तरह का language port करवाना भी मज़ेदार होगा। सोच रहा हूँ क्या इस repo में LLM-generated code शामिल है। कुछ महीने पहले
kentonvके बारे में पढ़ा था कि उन्होंने AI-generated (मानव द्वारा review किया गया) POC बनाया थामौजूदा समय में LLM के लिए यह library बनाना मुश्किल होता। इसकी internal structure बहुत बारीकी से interlock होने वाली puzzle की तरह डिजाइन की गई है
असली code लिखने से ज़्यादा समय design पर सोचने में गया
यह
workers-oauth-providerlibrary जैसी चीज़ से पूरी तरह अलग है, जहाँ well-known spec को novel तरीके से implement किया जाता हैcode structure dynamic languages, जैसे Python, में port करना आसान हो सकता है, लेकिन statically typed languages में मुश्किल होगा। यह arbitrary object types पर काफी निर्भर करता है
OCapN के साथ कुछ समानताएं और कुछ महत्वपूर्ण अंतर भी हैं संदर्भ
दोनों capability transfer, promise pipelining और schemaless model को support करते हैं
Cap'n Web में OCapN के
sturdyref(restore किए जा सकने वाले URI) जैसी out-of-band capability नहीं है। शायद इसी वजह से API key authentication की ज़रूरत पड़ती है।sturdyrefएक तरह का unguessable token होता है, जिसे रखने पर उस endpoint तक पहुंच का अधिकार मिलता हैइसके अलावा Cap'n Web में वह three-party handoff feature नहीं है जिसमें Alice, Bob को Carol से मिलवाती है। distributed apps में यह ज़रूरी होता है, इसलिए Cap'n Web पारंपरिक SaaS-style client-server उपयोग के करीब लगता है, बस उसमें ocap की कुछ विशेषताएँ जुड़ी हुई हैं
SturdyRefका restore behavior हर platform पर अलग होता है, इसलिए इसे RPC protocol level पर रखने के बजाय platform-specific तरीके से implement करना बेहतर लगता हैउदाहरण के लिए Cloudflare Workers में जल्द ही Durable Object storage से capability persistence संभव होगी, लेकिन उसका implementation worker platform के लिए खास होगा
Sandstorm में भी persistent capability है, लेकिन वह internal services तक सीमित है
इसी कारण Cap’n Proto से persistent capability की अवधारणा पूरी तरह हटा दी गई, और web standards में उससे मिलती-जुलती सबसे नज़दीकी चीज़ OAuth है
OAuth refresh token आधारित sturdyref की कल्पना की जा सकती है, लेकिन वह ऐसी रचना नहीं होगी जो हर platform पर उपयोगी हो
जल्दी से देखने पर लगता है कि यह system server side पर import/export tables या object state को stateful तरीके से store करने की मांग करता है (या कम से कम प्रोत्साहित करता है)
पारंपरिक RPC में हर call top-level पर आती है, और हर call के साथ key आदि भेजे जाते हैं, इसलिए request कई servers में बंट जाए तो भी दिक्कत नहीं होती, लेकिन Cap’n Web ऐसा नहीं है
क्या tables को serialize करके DB में store किया जा सकता है ताकि उसी तरह server distribution संभव हो, या फिर server affinity या Durable Objects जैसी व्यवस्था अनिवार्य है?
state केवल एक RPC session के भीतर ही बनी रहती है
अगर WebSocket इस्तेमाल हो रहा है, तो WebSocket connection जीवित रहने तक state भी बनी रहती है
अगर HTTP batch transport इस्तेमाल हो, तो session एक पूरे HTTP request तक सीमित होता है, और उसके भीतर सभी calls एक साथ process होती हैं
इसलिए Cap’n Web में कई HTTP requests/connections के पार state बनाए रखने की आवश्यकता नहीं है
लेकिन अगर session टूटने पर सभी capabilities खो जाती हों, तो ऐसा design टालना चाहिए। connection reset होने के बाद भी capabilities restore की जा सकें, ऐसा होना चाहिए
docs पढ़कर लगता है कि यह WebSocket के साथ affinity बनाए रखने वाली संरचना है
HTTP batching में सभी requests एक साथ भेजी जाती हैं और फिर response का इंतज़ार किया जाता है
इस तरह load balancing मुश्किल हो जाती है। अगर chat clients बहुत हों, तो connections कुछ खास servers पर इकट्ठी हो सकती हैं। तब उन servers पर overload का खतरा रहेगा
server scale in/out भी झंझटभरा हो जाता है। long-lived connections बनाए रखते हुए कई requests को एक साथ process करना management को बहुत कठिन बना देता है
एक और बात, अगर client लगातार push events भेजता रहे लेकिन responses कभी receive न करे, तो server को उन responses को memory में बनाए रखना पड़ेगा, इसलिए DDOS attack आसान हो सकता है
Cap'n Proto docs मैंने पहले पढ़े थे, उसमें server और client peer stubs का आदान-प्रदान कर सकते हैं
अगर server C को client B के ज़रिए A पर बना stub मिल जाए, तो C सीधे A को call भी कर सकता है
“RPC” मूल रूप से एक programming paradigm है, जिसका लक्ष्य remote calls को internal function calls से अलग न दिखने देना है
स्वाभाविक है कि इसके लिए wire protocol, client/server libraries आदि की ज़रूरत होती है
हाल के समय में इसकी समझ काफी बदल गई है, और अब REST endpoints की तरह function signatures वाली संरचनाएँ ज़्यादा आम हैं
Future,Optionalजैसी programming language features आने से अब “यह operation delay हो सकता है” या “यह fail हो सकता है” जैसी विशेषताओं को साफ़-साफ़ अलग दिखाया जा सकता हैपुराने RPC systems में ये सारी बातें छिपा दी जाती थीं
आपका मतलब क्या है, यह जानना चाहता हूँ। asynchronous programming कई languages में मौजूद है। मैंने JavaScript, C++, Python, Rust, C# आदि लगभग सभी का उपयोग किया है
मूल बात यह है कि शुरुआती RPC systems network request चलने तक calling thread को block कर देते थे, और वह वाकई खराब design था, इसलिए अब async स्वाभाविक माना जाता है
यह देखकर बहुत उत्साह है कि Cap'n Web सिर्फ Cloudflare products से बंधा नहीं है, बल्कि अलग से भी मौजूद है
docs के इस हिस्से को पढ़कर एक सवाल है
बल्कि मुझे लगता है Cap'n Web, worker RPC से आगे भी निकल सकता है (वास्तव में pipelining features में यह पहले से आगे है)
Cap'n Web की संरचना कहीं अधिक सरल है, इसलिए नए features पर प्रयोग भी शायद पहले Cap'n Web में ही किए जाएंगे