Demystifying RPC: From OSI Layers to Node.js JSON‑RPC Implementation
This article explains the fundamentals of Remote Procedure Call (RPC), explores how data moves across OSI layers, compares HTTP and RPC, and provides a complete Node.js JSON‑RPC example with code, error handling, and real‑world use cases such as Chrome DevTools Protocol.
Background
Some team members work with hardware devices and need to understand device control and inter‑device communication; others often hear about RPC from backend developers, prompting a deeper look.
Understanding RPC
RPC (Remote Procedure Call) lets programmers invoke methods on remote services as easily as local function calls, abstracting away network communication.
How Data Travels Between Applications
Review of the OSI Model
The OSI model provides a layered view of network protocols, though not every communication uses all layers.
Typical user actions (browser navigation, messaging) follow the TCP/IP four‑layer model, with data passing through addressing, connection establishment, and transmission stages.
Typical Communication Steps
Domain name resolution (DNS)
TCP connection establishment (three‑way handshake)
Data exchange
Connection termination (four‑way handshake) with safeguards for lost ACKs
Why Use RPC?
In a scenario where a product service needs to verify inventory from an inventory service, RPC enables direct method calls between services, offering better performance than HTTP for internal communication.
HTTP/1.1 adds many headers and formatting overhead unnecessary for service‑to‑service calls, whereas RPC can be more lightweight and customizable.
RPC typically resides at the session layer of the OSI model and can be built on HTTP/WebSocket, TCP/UDP, or even the physical layer; most implementations use TCP, with gRPC built on HTTP/2.
Implementing RPC Over HTTP (JSON‑RPC)
JSON‑RPC is a lightweight RPC protocol that uses JSON for data exchange over HTTP or other transports.
JSON‑RPC is a lightweight remote‑call protocol that uses JSON as the data format, supporting simple, easy‑to‑use communication.
Key differences from a normal HTTP request:
POST body contains a JSON object instead of key/value pairs.
Server parses the JSON and dispatches based on method and params.
Response header Content‑Type becomes application/json-rpc.
Node.js + HTTP + JSON‑RPC Example
Error Object Design
type ErrorBody = {
code: number; // error code
message: string; // description
data?: any; // optional additional info
};Request Object Design
type RequestBody = {
jsonrpc: string; // version, must be "2.0"
method: string; // method name, prefixed with "rpc"
params?: any; // structured parameters
id: number | string; // unique identifier
};Response Object Design
type ResponseBody = {
jsonrpc: string; // must be "2.0"
result?: any; // present on success
error?: ErrorBody; // present on failure
id: number | string; // matches request ID
};JSON‑RPC Class Implementation
export default class JSONRPC {
version: string = "2.0";
errorMsg = {
[-32700]: "Parse Error.",
[-32600]: "Invalid Request.",
[-32601]: "Method Not Found.",
[-32602]: "Invalid Params.",
[-32603]: "Internal Error.",
};
methods = {};
normalize(rpc, obj) {
obj.id = rpc && typeof rpc.id === "number" ? rpc.id : null;
obj.jsonrpc = this.version;
if (obj.error && !obj.error.message) {
obj.error.message = this.errorMsg[obj.error.code] || obj.error.message;
}
return obj;
}
/**
* Handle a JSON‑RPC request
*/
handleRequest(rpc, response) {
const method = this.methods[rpc.method];
if (typeof method !== "function") {
return response(this.normalize(rpc, { error: { code: -32601 } }));
}
try {
response(this.normalize(rpc, { result: method.apply(this, rpc.params) }));
} catch (error) {
if (error instanceof Error) {
response(this.normalize(rpc, { error: { code: -32000, message: error.message } }));
} else {
response(this.normalize(rpc, { error: { code: 0, message: "unknown error" } }));
}
}
}
}Starting an HTTP Server
import { createServer } from "http";
import url from "url";
import JSONPRC from "./rpc";
const HOST = "localhost";
const PORT = 8080;
const RPC = new JSONPRC();
RPC.methods = {
rpcDivide(a, b) {
if (b === 0) throw Error("Not allow 0");
return a / b;
},
};
const routes = {
"/rpc-divide": (request, response) => {
if (request.method === "POST") {
let data = "";
request.setEncoding("utf8");
request.addListener("data", chunk => { data += chunk; });
request.addListener("end", () => {
RPC.handleRequest(JSON.parse(data), obj => {
const body = JSON.stringify(obj);
response.writeHead(200, {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(body),
});
response.end(body);
});
});
} else {
response.end("hello nodejs http server");
}
},
};
const server = createServer((request, response) => {
const pathname = url.parse(request.url || "").pathname || "";
const route = routes[pathname];
if (route) {
route(request, response);
} else {
response.end("hello nodejs http server");
}
});
server.listen(PORT, HOST, 0, () => {
console.log(`server is listening on http://${HOST}:${PORT} ...`);
});Running Result
Postman shows the JSON‑RPC response; malformed requests trigger the defined error handling.
RPC in the Front‑End World
Chrome DevTools Protocol (CDP)
CDP is Chrome's debugging protocol built on JSON‑RPC; the frontend communicates with the backend via WebSocket.
JSON‑RPC Use Case in Retail
Tablets used by store staff communicate with devices and modules through JSON‑RPC, enabling seamless control.
Summary
RPC abstracts network programming, allowing remote calls to feel like local method invocations.
Implementations can be based on HTTP/1.1, HTTP/2, WebSocket, TCP, UDP, etc.
RPC and HTTP serve different purposes; each fits specific scenarios.
JSON‑RPC leverages the JSON standard, offering high compatibility, lightweight design, and ease of use.
Goodme Frontend Team
Regularly sharing the team's insights and expertise in the frontend field
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
