首页 > 编程语言 >odoo16前端框架源码阅读——rpc_service.js

odoo16前端框架源码阅读——rpc_service.js

时间:2023-11-11 15:04:25浏览次数:58  
标签:const service odoo16 request RPC 源码 params error data

odoo16前端框架源码阅读——rpc_service.js 先介绍点背景知识,这样方便阅读代码。

一、 JSONRPC的规范 https://www.jsonrpc.org/specification

中文翻译版本:https://wiki.geekdream.com/Specification/json-rpc_2.0.html

JSON-RPC是一个无状态且轻量级的远程过程调用(RPC)协议。 本规范主要定义了一些数据结构及其相关的处理规则。它允许运行在基于socket,http等诸多不同消息传输环境的同一进程中。其使用JSON(RFC 4627)作为数据格式。

它为简单而生!

由于JSON-RPC使用JSON,它具有与其相同的类型系统(见http://www.json.org或RFC 4627)。JSON可以表示四个基本类型(String、Numbers、Booleans和Null)和两个结构化类型(Objects和Arrays)。 规范中,术语“Primitive”标记那4种原始类型,“Structured”标记两种结构化类型。任何时候文档涉及JSON数据类型,第一个字母都必须大写:Object,Array,String,Number,Boolean,Null。包括True和False也要大写。

1、请求对象 发送一个请求对象至服务端代表一个rpc调用, 一个请求对象包含下列成员:

jsonrpc

指定JSON-RPC协议版本的字符串,必须准确写为“2.0”

method

包含所要调用方法名称的字符串,以rpc开头的方法名,用英文句号(U+002E or ASCII 46)连接的为预留给rpc内部的方法名及扩展名,且不能在其他地方使用。

params

调用方法所需要的结构化参数值,该成员参数可以被省略。

id

已建立客户端的唯一标识id,值必须包含一个字符串、数值或NULL空值。如果不包含该成员则被认定为是一个通知。该值一般不为NULL[1],若为数值则不应该包含小数[2]。

服务端必须回答相同的值如果包含在响应对象。 这个成员用来两个对象之间的关联上下文。

没有包含“id”成员的请求对象为通知, 作为通知的请求对象表明客户端对相应的响应对象并不感兴趣

const data = {
    id: rpcId,
    jsonrpc: "2.0",
    method: "call",
    params: params,
};

1 2 3 4 5 6 2、响应对象 当发起一个rpc调用时,除通知之外,服务端都必须回复响应。响应表示为一个JSON对象,使用以下成员:

jsonrpc

指定JSON-RPC协议版本的字符串,必须准确写为“2.0”

result

该成员在成功时必须包含。

当调用方法引起错误时必须不包含该成员。

服务端中的被调用方法决定了该成员的值。

error

该成员在失败是必须包含。

当没有引起错误的时必须不包含该成员。

该成员参数值必须为5.1中定义的对象。

id

该成员必须包含。

该成员值必须于请求对象中的id成员值一致。

若在检查请求对象id时错误(例如参数错误或无效请求),则该值必须为空值。

响应对象必须包含result或error成员,但两个成员必须不能同时包含。

3、错误对象 当一个rpc调用遇到错误时,返回的响应对象必须包含错误成员参数,并且为带有下列成员参数的对象:

code

使用数值表示该异常的错误类型。 必须为整数。

message

对该错误的简单描述字符串。 该描述应尽量限定在简短的一句话。

data

包含关于错误附加信息的基本类型或结构化类型。该成员可忽略。 该成员值由服务端定义(例如详细的错误信息,嵌套的错误等)。

code message meaning -32700 Parse error语法解析错误 服务端接收到无效的json。该错误发送于服务器尝试解析json文本 -32600 Invalid Request无效请求 发送的json不是一个有效的请求对象。 -32601 Method not found找不到方法 该方法不存在或无效 -32602 Invalid params无效的参数 无效的方法参数。 -32603 Internal error内部错误 JSON-RPC内部错误。 -32000 to -32099 Server error服务端错误 预留用于自定义的服务器错误。 1 2 3 4 5 6 7 二、rpc_service.js 路径:addons\web\static\src\core\network\rpc_service.js

1、引入相关模块, 新建了相关的Error类 导入了两个js模块, browser估计是跟浏览器相关

registry 是前端的注册表

然后是定义了四个Error继承自己标准类Error

RPCError ConnectionLostError ConnectionAbortedError HTTPError /** @odoo-module **/

import { browser } from "../browser/browser"; import { registry } from "../registry";

// ----------------------------------------------------------------------------- // Errors // ----------------------------------------------------------------------------- export class RPCError extends Error { constructor() { super(...arguments); this.name = "RPC_ERROR"; this.type = "server"; this.code = null; this.data = null; this.exceptionName = null; this.subType = null; } }

export class ConnectionLostError extends Error {}

export class ConnectionAbortedError extends Error {}

export class HTTPError extends Error {}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 2、Error对象 根据响应值来返回一个RPCError,这句结构赋值挺有意思

const { code, data: errorData, message, type: subType } = reponse; 1 我猜是吧reponse中的4个属性 code,data,message,type 分别赋值给了code,errorData,message,subType,有两个变量名称做了替换。有啥必要吗?

// ----------------------------------------------------------------------------- // Main RPC method // ----------------------------------------------------------------------------- export function makeErrorFromResponse(reponse) { // Odoo returns error like this, in a error field instead of properly // using http error codes...

const error = new RPCError();
error.exceptionName = errorData.name;
error.subType = subType;
error.data = errorData;
error.message = message;
error.code = code;
return error;

}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 3、定义相关变量 5个输入参数

env 前端环境,似乎在前端js代码中,直接可以使用这个对象。 rpcId id, 为了跟响应对应起来,所以需要这个id url 地址 params 参数值 setting 默认是一个空对象 export function jsonrpc(env, rpcId, url, params, settings = {}) { const bus = env.bus; const XHR = browser.XMLHttpRequest; const data = { id: rpcId, jsonrpc: "2.0", method: "call", params: params, }; const request = settings.xhr || new XHR(); let rejectFn; 1 2 3 4 5 6 7 8 9 10 11 参数看着有点多,其实在后面封装成service的时候, 前两个参数是不用传的,只需要传递后面三个就行, 其实params和setting都可以不用传,必须要传的只有url。

bus: 总线, 这里要通过总线发送一些信号,rpc毕竟是远程调用,难免有意外情况发生,所以需要bus进行通信。

XHR: 浏览器发起request请求

data: 标准的jsonrpc数据格式

rejectFn, 这里值得一提,在这里声明这个变量,但是并没有赋值,是为了后面使用。

4、复习promise promise是ES6引入的异步编程的新解决方案,语法上Promise是一个构造函数,用来封装异步操作并可以获取成功或失败的结果。

// new Promise 生成一个异步任务,参数是具体执行任务的函数,接收两个参数 // 一般叫resolve和reject都是函数,异步任务执行成功调用前者,否则调用后者 // 这两个方法将改变p对象的状态,同时给下一步处理传递数据 // 然后调用p.then ,接收两个函数型参数,分别对应异步任务成功的回调和失败的回调 const p = new Promise(function(resolve,reject){ setTimeout(function () { // let data="数据库中的用户数据"; // resolve(data); let err="数据读取失败"; reject(err); },1000); })

p.then(function(value){ console.log(value); },function(reason){ console.error(reason); })

// 其实promise解决的也是回调地狱,嵌套过多的问题,将异步任务封装成对象了

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 其实,promise分了两步来完成,对象本身只是执行了一个异步任务,并没有处理异步返回的结果。 异步任务返回的结果是在p.then函数中处理的。 异步任务的返回值会影响promise对象本身的状态,会决定p.then中执行哪个回调函数。

另外,就是promise可以链式调用,执行串行的异步任务。

5、promise 中的Bus 在promise里面往总线里发送了很多消息

if (!settings.silent) {
        bus.trigger("RPC:REQUEST", data.id);
    }

1 2 3 这里既然发了,那么就一定有地方接收,在odoo中搜索一下RPC:REQUEST

addons\web\static\src\webclient\loading_indicator\loading_indicator.js

这个文件代码不长,直接贴过来好了。注释中大概的意思是:

加载指示器:

当用户执行一个动作,最好是给他一些反馈说明当前有些事情正在发生。 这个指示器的作用就是在屏幕的右下角显示一个小的矩形框,里面有Loading字样,并且还有rpc的id。 3秒之后,如果rpc依然没有完成,我们将阻塞整个UI。 回头测试一下。

/** @odoo-module **/

import { browser } from "@web/core/browser/browser"; import { registry } from "@web/core/registry"; import { useService } from "@web/core/utils/hooks"; import { Transition } from "@web/core/transition";

import { Component, onWillDestroy, useState } from "@odoo/owl";

/**

  • Loading Indicator

  • When the user performs an action, it is good to give him some feedback that
  • something is currently happening. The purpose of the Loading Indicator is to
  • display a small rectangle on the bottom right of the screen with just the
  • text 'Loading' and the number of currently running rpcs.

  • After a delay of 3s, if a rpc is still not completed, we also block the UI. */ export class LoadingIndicator extends Component { setup() { this.uiService = useService("ui"); this.state = useState({ count: 0, show: false, }); this.rpcIds = new Set(); this.shouldUnblock = false; this.startShowTimer = null; this.blockUITimer = null; this.env.bus.addEventListener("RPC:REQUEST", this.requestCall.bind(this)); this.env.bus.addEventListener("RPC:RESPONSE", this.responseCall.bind(this)); onWillDestroy(() => { this.env.bus.removeEventListener("RPC:REQUEST", this.requestCall.bind(this)); this.env.bus.removeEventListener("RPC:RESPONSE", this.responseCall.bind(this)); }); }
    requestCall({ detail: rpcId }) { if (this.state.count === 0) { browser.clearTimeout(this.startShowTimer); this.startShowTimer = browser.setTimeout(() => { if (this.state.count) { this.state.show = true; this.blockUITimer = browser.setTimeout(() => { this.shouldUnblock = true; this.uiService.block(); }, 3000); } }, 250); } this.rpcIds.add(rpcId); this.state.count++; }
    responseCall({ detail: rpcId }) { this.rpcIds.delete(rpcId); this.state.count = this.rpcIds.size; if (this.state.count === 0) { browser.clearTimeout(this.startShowTimer); browser.clearTimeout(this.blockUITimer); this.state.show = false; if (this.shouldUnblock) { this.uiService.unblock(); this.shouldUnblock = false; } } } }

LoadingIndicator.template = "web.LoadingIndicator"; LoadingIndicator.components = { Transition };

registry.category("main_components").add("LoadingIndicator", { Component: LoadingIndicator, });

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 6、request 绑定load事件 request绑定了一个load事件,也就是请求返回的时候触发的操作

request.addEventListener("load", () => {
        if (request.status === 502) {
            // If Odoo is behind another server (eg.: nginx)
            if (!settings.silent) {
                bus.trigger("RPC:RESPONSE", data.id);
            }
            reject(new ConnectionLostError());
            return;
        }
        let params;
        try {
            params = JSON.parse(request.response);
        } catch (_) {
            // the response isn't json parsable, which probably means that the rpc request could
            // not be handled by the server, e.g. PoolError('The Connection Pool Is Full')
            if (!settings.silent) {
                bus.trigger("RPC:RESPONSE", data.id);
            }
            return reject(new ConnectionLostError());
       
        const { error: responseError, result: responseResult } = params;
        if (!settings.silent) {
            bus.trigger("RPC:RESPONSE", data.id);
        }
        if (!responseError) {
            return resolve(responseResult);
        }
        const error = makeErrorFromResponse(responseError);
        reject(error);
    });

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 6.1、status = 502 是个什么鬼? 502 Bad Gateway错误是指代理或网关从上一个服务器接收到的响应无效或不完整。通常,这种情况发生在文件太大或处理速度太慢的高流量网站上。例如,当您访问一个具有高流量的网站时,您的请求将被发送到它的代理服务器。如果代理服务器在尝试访问网站时无法从上游服务器获取完整的响应,则会生成502错误代码。

502错误代码通常是由代理服务器、网关或负载均衡器等设备导致的,而不是由您的计算机或网络连接引起的。这意味着您只能为自己的网络连接做些有限的调整,但无法修复网关响应错误。

注释中也写的明白,502可能是因为使用了nginx反向代理,而 502错误是nginx和odoo通讯不佳造成的,这种情况rpc执行失败,

执行这一句

reject(new ConnectionLostError()); 1 6.2 解析返回值 如果返回的不是json格式的数据,也会触发错误

try { params = JSON.parse(request.response); } catch (_) { return reject(new ConnectionLostError()); } 1 2 3 4 5 catch 后面的这个下划线是什么鬼? 可能是并不关心发生了什么错误,只要解析错误,就调用reject

6.3 解构赋值 const { error: responseError, result: responseResult } = params; 1 根据jsonrpc规范, error和result 必须并且只能返回一个。

后面也做了判断

if (!responseError) {
        return resolve(responseResult);
    }

1 2 3 如果没有错误,那就调用resolve,并返回。否则说明有错误发生,先生成一个error,然后调用reject

const error = makeErrorFromResponse(responseError);
        reject(error);

1 2 7、request绑定 error事件 request.addEventListener("error", () => { if (!settings.silent) { bus.trigger("RPC:RESPONSE", data.id); } reject(new ConnectionLostError()); }); 1 2 3 4 5 6 8、 该干正事了 request.open("POST", url); request.setRequestHeader("Content-Type", "application/json"); request.send(JSON.stringify(data)); 1 2 3 三行代码:

1、用post方法请求的url,为什么不用get? 因为post更安全

2、指定了Content-Type为json, 这个很重要,如果不指定,服务器端不知道怎么解析数据

3、将data转成字符串并发送出去。(忙活半天,就是为了这句)

9、定义了promise.abort Promise只有三种状态:pending、resolve、reject,一个异步的承诺一旦发出,经历等待(pending)后,最终只能为成功或者失败,中途无法取消(abort)。

这里定义promise.abort,注释也讲的明白,允许用户取消被忽略的rpc请求来接触对UI的阻塞并且不要显示错误。

/**
 * @param {Boolean} rejectError Returns an error if true. Allows you to cancel
 *                  ignored rpc's in order to unblock the ui and not display an error.
 */
promise.abort = function (rejectError = true) {
    if (request.abort) {
        request.abort();
    }
    if (!settings.silent) {
        bus.trigger("RPC:RESPONSE", data.id);
    }
    if (rejectError) {
        rejectFn(new ConnectionAbortedError("XmlHttpRequestError abort"));
    }
};

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 10、jsonrpc 这个函数就干了一件事,定义了一个promise对象来发送rpc请求,并把它返回。

export function jsonrpc(env, rpcId, url, params, settings = {}) { const bus = env.bus; const XHR = browser.XMLHttpRequest; const data = { id: rpcId, jsonrpc: "2.0", method: "call", params: params, }; const request = settings.xhr || new XHR(); let rejectFn; const promise = new Promise((resolve, reject) => { rejectFn = reject; // handle success request.addEventListener("load", () => {

});
    // handle failure
    request.addEventListener("error", () => {

    });

    request.open("POST", url);
    request.setRequestHeader("Content-Type", "application/json");
    request.send(JSON.stringify(data));
});

promise.abort = function (rejectError = true) {

};
return promise;

}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 11、定义RPC服务 这里对jsonrpc做了进一步封装,并且注册为服务,看来每个服务都有个start函数,而且将env作为参数传进去。

// ----------------------------------------------------------------------------- // RPC service // ----------------------------------------------------------------------------- export const rpcService = { async: true, start(env) { let rpcId = 0; return function rpc(route, params = {}, settings) { return jsonrpc(env, rpcId++, route, params, settings); }; }, };

registry.category("services").add("rpc", rpcService); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 附录: odoo16 rpc_service.js /** @odoo-module **/

import { browser } from "../browser/browser"; import { registry } from "../registry";

// ----------------------------------------------------------------------------- // Errors // ----------------------------------------------------------------------------- export class RPCError extends Error { constructor() { super(...arguments); this.name = "RPC_ERROR"; this.type = "server"; this.code = null; this.data = null; this.exceptionName = null; this.subType = null; } }

export class ConnectionLostError extends Error {}

export class ConnectionAbortedError extends Error {}

export class HTTPError extends Error {}

// ----------------------------------------------------------------------------- // Main RPC method // ----------------------------------------------------------------------------- export function makeErrorFromResponse(reponse) { // Odoo returns error like this, in a error field instead of properly // using http error codes... const { code, data: errorData, message, type: subType } = reponse; const error = new RPCError(); error.exceptionName = errorData.name; error.subType = subType; error.data = errorData; error.message = message; error.code = code; return error; }

export function jsonrpc(env, rpcId, url, params, settings = {}) { const bus = env.bus; const XHR = browser.XMLHttpRequest; const data = { id: rpcId, jsonrpc: "2.0", method: "call", params: params, }; const request = settings.xhr || new XHR(); let rejectFn; const promise = new Promise((resolve, reject) => { rejectFn = reject; if (!settings.silent) { bus.trigger("RPC:REQUEST", data.id); } // handle success request.addEventListener("load", () => { if (request.status === 502) { // If Odoo is behind another server (eg.: nginx) if (!settings.silent) { bus.trigger("RPC:RESPONSE", data.id); } reject(new ConnectionLostError()); return; } let params; try { params = JSON.parse(request.response); } catch (_) { // the response isn't json parsable, which probably means that the rpc request could // not be handled by the server, e.g. PoolError('The Connection Pool Is Full') if (!settings.silent) { bus.trigger("RPC:RESPONSE", data.id); } return reject(new ConnectionLostError()); } const { error: responseError, result: responseResult } = params; if (!settings.silent) { bus.trigger("RPC:RESPONSE", data.id); } if (!responseError) { return resolve(responseResult); } const error = makeErrorFromResponse(responseError); reject(error); }); // handle failure request.addEventListener("error", () => { if (!settings.silent) { bus.trigger("RPC:RESPONSE", data.id); } reject(new ConnectionLostError()); }); // configure and send request request.open("POST", url); request.setRequestHeader("Content-Type", "application/json"); request.send(JSON.stringify(data)); }); /** * @param {Boolean} rejectError Returns an error if true. Allows you to cancel * ignored rpc's in order to unblock the ui and not display an error. */ promise.abort = function (rejectError = true) { if (request.abort) { request.abort(); } if (!settings.silent) { bus.trigger("RPC:RESPONSE", data.id); } if (rejectError) { rejectFn(new ConnectionAbortedError("XmlHttpRequestError abort")); } }; return promise; }

// ----------------------------------------------------------------------------- // RPC service // ----------------------------------------------------------------------------- export const rpcService = { async: true, start(env) { let rpcId = 0; return function rpc(route, params = {}, settings) { return jsonrpc(env, rpcId++, route, params, settings); }; }, };

registry.category("services").add("rpc", rpcService);

标签:const,service,odoo16,request,RPC,源码,params,error,data
From: https://blog.51cto.com/u_15319978/8317578

相关文章

  • k8s service ipvs模式下nodePort实现
    部署nodePort+StatefulSetapiVersion:v1kind:Servicemetadata:name:nginxspec:ports:-port:80selector:app:nginxtype:NodePort---apiVersion:apps/v1kind:StatefulSetmetadata:name:nginxspec:podManagementPolicy:Parallels......
  • milvus向量数据库源码编译
    (milvus源码编译)编译环境os:ubuntu22.04.6live-server,x64gcc:9.4.0cmake:3.24.0go:1.18.10milvus:v2.3.2操作系统建议使用ubuntu。在centos7上未编译成功。在root账户下操作。安装gcc因为后面需要安装cmake,因此先安装如下依赖:aptinstallg++gccmakelibssl-dev......
  • 【Java】智慧工地云平台源码支持多端展示(PC端、手机端、平板端)
    实现工地的数字化、精细化、智慧化生产和管理。一、智慧工地发展趋势1.更加智能未来的智慧工地系统将逐步植入人工智能和虚拟现实等高科技技术以更为智慧的方式,来实现岗位人员与工地现场的交互与配合。智慧工地系统能够在工程全生命周期管理的过程中发挥巨大效用,运用信息化手段......
  • UWB定位技术源码:实现微米级精度的人员定位系统
    UWB定位技术源码 超宽带技术的人员定位系统源码UWB人员定位系统是一种基于超宽带技术的人员定位系统,它通过发送和接收超短脉冲信号,在测距方面可以达到微米级精度。这种系统通常需要具备高精度的定位能力,通常需要达到微米级别,这样可以在室内和室外的复杂环境中精确定位,为人员的个人......
  • #yyds干货盘点#react的useState源码分析
    简单说下为什么React选择函数式组件,主要是class组件比较冗余、生命周期函数写法不友好,骚写法多,functional组件更符合React编程思想等等等。更具体的可以拜读dan大神的blog。其中Functioncomponentscapturetherenderedvalues这句十分精辟的道出函数式组件的优势。但是在16.8之......
  • 医学影像系统源码(MRI、CT三维重建)
    一、MRI概述核磁共振成像(英语:NuclearMagneticResonanceImaging,简称NMRI),又称自旋成像(英语:spinimaging),也称磁共振成像(MagneticResonanceImaging,简称MRI),台湾又称磁振造影,香港又称磁力共振扫描,是利用核磁共振(nuclearmagneticresonance,简称NMR)原理,依据所释放的能量在物质内部不......
  • Qt源码解析——元对象系统热身
    关键词:Qt源码QObjectQMetaObject元对象系统属性事件信号槽概述原系列文章地址官方文档第二章内容就是元对象系统,它在介绍里描述到:Qt的元对象系统提供了信号和槽机制(用于对象间的通信)、运行时类型信息和动态属性系统。元对象系统基于三个要素:QObject类为那些可以利......
  • Linux软件包(源码包和二进制包)
    Linux下的软件包众多,且几乎都是经GPL授权、免费开源(无偿公开源代码)的。这意味着如果你具备修改软件源代码的能力,只要你愿意,可以随意修改。GPL,全称GeneralPublicLicense,中文名称“通用性公开许可证”,简单理解GPL就是一个保护软件自由的一个协议,经GPL协议授权的软件必须开源......
  • Vue源码学习(十六):diff算法(三)暴力比对
    好家伙,这是diff的最后一节了 0.暴力比对的使用场景 没有可复用的节点:当新旧虚拟DOM的结构完全不同,或者某个节点不能被复用时,需要通过暴力比对来创建新的节点,并在真实DOM上进行相应的插入操作。0.1.例子一://创建vnodeletvm1=newVue({data:{name:'张三'......
  • APISIX源码安装问题解决
    官网手册的安装语句:curlhttps://raw.githubusercontent.com/apache/apisix/master/utils/install-dependencies.sh-sL|bash-执行install-dependencies.sh报如下错误:Transactioncheckerror:file/usr/share/gcc-4.8.2/python/libstdcxx/v6/printers.pyfrominstal......