本文是关于采用Mathoid和Restbase服务来实现Math扩展的,很可惜失败了。记录一下整个过程以供日后查阅。采用Mathoid本地接口实现Math扩展的可参考本站另一篇文章《MediaWiki安装Math扩展成功》。
MediaWiki的数学扩展中最知名的就是Math,虽然其他诸如的也能用,但是生成的时候会先显示代码,而后点中公式右键还会出现菜单(SimpleMathJax扩展),就这点就让带洁癖的我无法忍受。
但该插件的安装难度一点都不亚于当初的可视化编辑器VisualEditor。根据基金会的设想,Math扩展将优先考虑Mathoid服务,所以想要使用该扩展就必须安装该服务,当然你也可以用维基百科的,前提是你的服务器得连得上维基百科。通过官方的方式在本地虚拟机上试验成功(虚拟机可链接维基百科),代码如下:

// Set MathML as default rendering option
$wgDefaultUserOptions['math'] = 'mathml';
$wgMathFullRestbaseURL = 'https://en.wikipedia.org/api/rest_';
$wgMathMathMLUrl = 'https://mathoid-beta.wmflabs.org/';

渲染速度还是挺快的,基本上属于瞬间显示。
MediaWiki-Math-Render.png
可惜的是位于国内的阿里云服务器是没有办法连接到维基百科的,所以会提示如下错误:
MediaWiki_Math_Parsing_Failed.png
所以唯一能做的就是要安装自己的Mathoid服务。由此噩梦开始,就这一个扩展就用了我将近一周的时间,期间创建快照和回滚达N次,都没有解决。

一、安装Mathoid服务

官方文档其实写得算是很详细的,就拿我使用的CentOS系统来说,具体操作如下:

# 安装必须的依赖
sudo yum install -y epel-release
sudo yum install -y nodejs npm mocha librsvg2-devel pkgconfig git
sudo yum groupinstall -y 'Development Tools'

# 将Mathoid装入/etc目录或其他地方
cd /etc
sudo git clone https://github.com/wikimedia/mathoid
cd mathoid

# 使用npm安装和测试
sudo npm install 
npm test

# 运行服务
node server.js

如果无法克隆的话,可以把网址中的https替换成git
记得要完全按照该步骤来,否则可能会安装失败。之所以这么说是因为我有几次画蛇添足,出现npm audix,我都手欠地按照执行了,结果反而测试无法通过。
新建一个服务:

vim /etc/systemd/system/mathoid.service

在其中写入如下内容:

[Unit]
Description=MediaWiki Mathoid Service
Documentation=https://www.mediawiki.org/wiki/Mathoid
Wants=local-fs.target network.target
After=local-fs.target network.target

[Install]
WantedBy=multi-user.target

[Service]
Type=simple
User=mathoid
Group=mathoid
WorkingDirectory=/usr/local/lib/mathoid
ExecStart=/usr/bin/node /usr/local/lib/mathoid/server.js
KillMode=process
Restart=on-success
Nice=10
OOMScoreAdjust=900
PrivateTmp=true
StandardOutput=syslog

用户和用户组如果使用mathoid的话记得创建一个,还有就是mathoid服务安装的位置以及node所在位置。
启用服务请启动:

systemctl enable mathoid.service
systemctl start mathoid

运行状态如下,有一个错误信息和一个警告信息。
systemctl-status-mathoid.png
在浏览器中输入IP地址和端口号(10044或者10042,具体看config.yaml设置的端口)
methoid_test-page.png
暂时认为mathoid服务安装完毕吧。

二、安装Restbase服务

这个服务在低版本的MediaWiki中已经打过交道了,总之很麻烦。自打升级为1.35以后,可视化编辑器不再需要Parsoid之后,连同它就一起消失在了我的服务器中,没曾想又要安装了。
这个服务我安装十次估计只有一次成功的,而且运行服务还会报错(主要是sqlite3方面的)。
官方文档中的安装说明跟Mathoid差不多。

# 将Restbase装入/etc目录或其他地方
cd /etc
git clone https://github.com/wikimedia/restbase.git
cd restbase

# 使用npm安装和测试
sudo npm install 

npm报错,err code 128。
npm-install-err-code-128.png
当然也碰到过err code 1的情况,这里就不再贴图了,反正就是安装不成功了。
就在我怀疑是否是因为CentOS7的原因而打算投靠Ubuntu前,我还是打算在虚拟机上尝试一遍。
使用了网上提到的所有办法,诸如清除npm的缓存,删除node_mouldes目录等等,反正都没用。
不得已只能尝试安装nvm,然后切换各个版本的Nodejs,发文时的最新版本为16.x,安装失败。降到6.x(也就是官方提到的版本号要大于6)还是无效,最后在14.17.0版时终于安装成功。
测试一下服务

node server.js

错误信息跟之前的Mathoid基本相同,暂时不管了。
同样新建一个服务:

vim /etc/systemd/system/restbase.service

在其中写入如下内容:

[Unit]
Description=Mediawiki RESTBase Service
Documentation=https://www.mediawiki.org/wiki/RESTBase
Wants=local-fs.target network.target
After=local-fs.target network.target

[Install]
WantedBy=multi-user.target

[Service]
Type=simple
User=restbase
Group=restbase
WorkingDirectory=/etc/restbase
ExecStart=/usr/bin/node /etc/restbase/server.js
KillMode=process
Restart=on-success
PrivateTmp=true
StandardOutput=syslog

启用服务请启动:

systemctl enable restbase.service
systemctl start restbase

通过状态查询,直接报了一堆的错误,主要就是sqlite3的问题。

"Cannot find module '/etc/restbase/node_modules/restbase-mod-table-sqlite/node_modules/sqlite3/lib/binding/node-v88-linux-x64/node_sqlite3.node'

systemctl-status-restbase-error.png
进入/binding目录,下面只有node-v83-linux-x64这个文件夹,难怪找不到模块。
突然想起服务中的node位置还是原来系统自带的,而自己用nvm安装的node其实在其他位置,果不其然,它的路径为/root/.nvm/versions/node/版本号/bin/node,替换服务中的node路径,重新加载服务,再次重启服务,问题依然。
明明在终端中通过node server.js运行通过的,为什么加入到服务中就不行了呢?唯一的解释只有用户和用户组的问题,估计是权限不够,因为nvm被安装到了root的目录下。
将用户和用户组改为root,重新加载服务,再次重启,问题解决。
systemctl-status-restbase.png
这次的错误信息和Mathoid相似,暂且不管了。
备注:在我将node安装位置调整为全部用户可用(参见《如何在CentOS 7上安装NVM》一文),并将用户和用户组改回restbase之后,运行服务仍然会报错,这次是SQLITE_READONLY,这应该是跟restbase的目录用户为root有关(毕竟我全程都是用root在操作)。

"message":"SQLITE_READONLY: attempt to write a readonly database","name":"Error","stack":"Error: SQLITE_READONLY: attempt to write a readonly database","code":"SQLITE_READONLY"

这个问题也好解决,将restbase的安装目录拥有者改为restbase就可以了。

三、配置restbase服务

终于要迎来另一个大坑了!
首先,让我们来看看官方的配置说明。

1. 修改config.yaml

首先将config.example.yaml复制为config.yaml

cp config.example.yaml config.yaml

根据官方文档的说明逐步修改,基本如下:

# RESTBase config for small wiki installs
#
# - sqlite backend
# - parsoid at http://localhost:8142
# - wiki at http://localhost/w/api.php
#
# Quick setup:
# - npm install
#   If you see errors about sqlite, you might have to `apt-get install
#   libsqlite3-dev`.
# - cp config.example.yaml config.yaml
# - double-check and possibly modify lines marked with XXX, then start restbase with
#
#   node server
#
# - If all went well, http://localhost:7231/localhost/v1/page/html/Main_Page
# should show your wiki's [[Main Page]].

services:
  - name: restbase
    module: hyperswitch
    conf:
      port: 7231
      salt: secret
      default_page_size: 125
      user_agent: RESTBase
      ui_name: RESTBase
      ui_url: https://www.mediawiki.org/wiki/RESTBase
      ui_title: RESTBase docs
      spec:
        x-request-filters:
          - path: lib/security_response_header_filter.js
          - path: lib/normalize_headers_filter.js
        x-sub-request-filters:
          - type: default
            name: http
            options:
              allow:
                - pattern: http://YOUR_WIKI_WEBSITE/api.php
                  forward_headers: true
                - pattern: http://YOUR_WIKI_WEBSITE/rest.php
                  forward_headers: true
                - pattern: /^https?:\/\//
        paths:
          /{domain:YOUR_WIKI_WEBSITE}/{api:v1}:
            x-modules:
              - spec:
                  info:
                    version: 1.0.0
                    title: Wikimedia REST API
                    description: Welcome to your RESTBase API.
                  x-route-filters:
                    - path: ./lib/normalize_title_filter.js
                      options:
                        redirect_cache_control: 's-maxage=0, max-age=86400'
                  x-host-basePath: /api/rest_v1
                  paths:
                    /page:
                      x-modules:
                        - path: v1/content.yaml
                          options:
                            response_cache_control: 's-maxage=0, max-age=86400'
                        - path: v1/common_schemas.yaml # Doesn't really matter where to mount it.
                    /transform:
                      x-modules:
                        - path: v1/transform.yaml

          /{domain:localhost}/{api:sys}:
            x-modules:
              - path: projects/proxy.yaml
                options:
                  backend_host_template: '{{"/{domain}/sys/legacy"}}'
              - spec:
                  paths:
                    /table:
                      x-modules:
                        - path: sys/table.js
                          options:
                            conf:
                              backend: sqlite
                              dbname: db.sqlite3
                              pool_idle_timeout: 20000
                              retry_delay: 250
                              retry_limit: 10
                              show_sql: false
                              storage_groups:
                                - name: local
                                  domains: /./
                    /legacy/key_value:
                      x-modules:
                        - path: sys/key_value.js
                    /legacy/page_revisions:
                      x-modules:
                        - path: sys/page_revisions.js
                    /post_data:
                      x-modules:
                        - path: sys/post_data.js
                    /action:
                      x-modules:
                        - path: sys/action.js
                          options:
                            # XXX Check API URL!
                            apiUriTemplate: http://YOUR_WIKI_WEBSITE/api.php
                            # XXX Check the base RESTBase URI
                            baseUriTemplate: "{{'http://YOUR_WIKI_WEBSITE/api/rest_v1'}}"
                    /page_save:
                      x-modules:
                        - path: sys/page_save.js
                    /parsoid:
                      x-modules:
                        - path: sys/parsoid.js
                          options:
                            parsoidHost: http://YOUR_WIKI_WEBSITE/rest.php
                            response_cache_control: 's-maxage=0, max-age=86400'

# Finally, a standard service-runner config.
info:
  name: restbase

logging:
  name: restbase
  level: info

以上所有大写部分均改为自己的维基站点就可以了,最终的样式则跟维基百科的类似,http://YOUR_WIKI_WEBSITE/api/rest_v1/
注意:官方其实给出了两种方式,一种是http://YOUR_WIKI_WEBSITE/YOUR_WIKI_WEBSITE/v1/,另一种就是上面所用的。
配置成功后,重新启动服务。

2. 本地测试

3. 修改nginx配置

先来看看官方给出的解决方案,打开配置文件wiki.conf(根据实际情况来),在头部加入以下内容:

upstream restbase {
    server localhost:7231;
    keepalive 32;
}

# Hack so nginx doesn't decode the / character in page titles, causing
# errors on pages with that title. https://stackoverflow.com/a/20514632
map $request_uri $restbasequery {
        default "xx";
        #"~/YOUR_WIKI_WEBSITE/v1/(?<xrestbasequery>.*)$" "$xrestbasequery";
        # Alternate configuration like WMF to have URLS like //example.com/api/rest_v1/
        "~/api/rest_v1/(?<xrestbasequery>.*)$" "$xrestbasequery";
}

注意:为了跟上面的restbase服务相对应,所以我们采用了"~/api/rest_v1/(?<xrestbasequery>.*)$" "$xrestbasequery";注释掉上面另一行。
然后在server块内加入下面的内容:

# location /YOUR_WIKI_WEBSITE/v1/ {
# Alternate configuration like WMF to have URLS like //example.com/api/rest_v1/
location /api/rest_v1/ {

        proxy_pass http://restbase/YOUR_WIKI_WEBSITE/v1/$restbasequery;
}

我注释掉了第一行,同样是为了保持跟restbase配置一样。

4. 浏览器端测试

在浏览器中输入https://YOUR_WIKI_WEBSITE/api/rest_v1/,直接报错,提示Failed to load API definition.
browse_restbase_domain.png
如果是输入http://IP:7231/YOUR_WIKI_WEBSITE/v1/则可以出现API的界面。
browse_restbase_ip.png
就当我满心欢喜地以为离真相只有一步之遥时,我却在门口徘徊了很久。整个配置文件能改的地方基本上都改了,期间restbase服务也反复重装N遍(测试各个版本的node),结果还是一样。
打开维基百科的restbase页面,有一个细节引起了我的注意。
browse_restbase_wikipedia.png
就是上图框框中的内容,从我的界面来看,显示的是/域名/v1/?spec,而维基百科的是/api/rest_v1/?spec。按理说,我所有的配置都是按照/api/rest_v1/来的,为什么还是会出现/域名/v1/的情况?
最令人感到奇怪的是,如果我在终端中使用curl来测试网站,不管是https://YOUR_WIKI_WEBSITE/api/rest_v1/或是http://IP:7231/YOUR_WIKI_WEBSITE/v1/,都能返回正常值。
那么很有可能就是因为通过浏览器连接时,会加载很多的js文件,估计是哪一个js文件中存在判断条件。
打开配置文件,其中有一行指向名为normalize_title_filter.js的文件,打开后有这样一段代码:

            if (hyper._rootReq.headers['x-request-class'] !== 'external') {
                // For internal requests we need to use relative redirect.
                redirectPath = `/${mwUtil.extractDomain(siteInfo.sharedRepoRootURI)}/v1` +
                    `${redirectPath}${mwUtil.getQueryString(req)}`;
            } else {
                redirectPath = `${siteInfo.sharedRepoRootURI}/api/rest_v1` +
                    `${redirectPath}${mwUtil.getQueryString(req)}`;
            }

程序是通过判断x-request-class是否为external来决定输出域名/v1或者是/api/rest_v1的。只是这个内部和外部是如何判断的呢?
将代码中的!==改为==试了一下,结果还是一样。
好吧,一步之遥的距离还真的有点大。
转机出现在之后的某次操作,之前所有的测试都是用的http://IP:7231/YOUR_WIKI_WEBSITE/v1/形式,当我在浏览器中输入http://YOUR_WIKI_WEBSITE:7231/YOUR_WIKI_WEBSITE/v1/时,页面同样弹出错误信息。
browse_restbase_domain_port.png
这次未定义的跟之前https://YOUR_WIKI_WEBSITE/api/rest_v1/未定义竟然不一样,基本上可以肯定就是在判断x-request-class上了。
搜索相关信息,网上给出的答案是在nginx中加入:

proxy_set_header host $host;

重新测试,这下正常了。

四、启用Math扩展

启用扩展本身没有什么难度,毕竟在虚拟机上已经试过一遍,只要RestBase服务和Mathoid服务都能正常,配置也正常的话,应该就可以了。可事实上,这个问题非常麻烦,至少在我这里是这样。
按照虚拟机中的方法,修改$wgMathFullRestbaseURL$wgMathMathMLUrl地址,如下:

wfLoadExtension( 'Math' );
// Set MathML as default rendering option
$wgMathValidModes[] = 'mathml';
$wgDefaultUserOptions['math'] = 'mathml';
$wgMathFullRestbaseURL = 'https://wiki.mindseed.cn/api/rest_';
$wgMathMathMLUrl = 'http://wiki.mindseed.cn:10044/';

首先,检验一下数学公式状态,系统直接报错。
那么尝试在可视化编辑器中加入一个公式呢?
保存后无法解析,连不上RestBase服务(如果配置中的域名输入错误,系统自动会跳转到Mediawiki站点的API),提示“解析失败(带SVG或PNG备选的MathML(建议用于现代的浏览器和辅助工具):从服务器"/mathoid/八local/w1/"返回无效的响应(“Math extension cannot connect to Restbase.")”
mediawiki-math-local-no-respondence.png
注意:上图中连接不上的地址根据Math扩展的设置以及Restbase的设置不同可能有所区别。
尝试修改Restbase和Parsoid的设置(后者在1.35中已经被内置为可视化编辑器的一部分,默认无需任何设置),这次连接的地址变为本地了。
尝试编辑,结果先是导致可视化编辑器直接报错:
mediawiki-math-visualeditor-error.png
一番折腾后,编辑文字没有任何问题,唯独在保存公式时报错,提示404错误:
mediawiki-saving-math-visualeditor--404-error.png
至此,问题变得越发复杂,涉及到服务器配置、服务配置以及扩展的配置。

五、继续调试

回到https://YOUR_WIKI_WEBSITE/api/rest_v1/
Wikimedia_REST_API.png
其中有一项Math的接口,我们测试一下看看,提示400错误。

{
  "type": "https://mediawiki.org/wiki/HyperSwitch/errors/bad_request",
  "title": "Invalid parameters",
  "method": "post",
  "detail": "data.body should have required property 'q'",
  "uri": "/wiki.mindseed.cn/v1/media/math/check/tex"
}

让我很在意的一点是,传递的数据内容为

{
  "q": "string"
}

但在转换为curl过程中却变成了

curl -X 'POST' \
  'https://wiki.mindseed.cn/api/rest_v1/media/math/check/tex' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F '0={' \
  -F '1=
' \
  -F '2= ' \
  -F '3= ' \
  -F '4="' \
  -F '5=q' \
  -F '6="' \
  -F '7=:' \
  -F '8= ' \
  -F '9="' \
  -F '10=s' \
  -F '11=t' \
  -F '12=r' \
  -F '13=i' \
  -F '14=n' \
  -F '15=g' \
  -F '16="' \
  -F '17=
' \
  -F '18=}'

对比一下维基百科的,竟然也是一样的,它也报400错误!!!
我甚至为了它有开通了一个子域名用来提供Mathoid服务,结果还是出错。
算了,我决定彻底放弃了。

最后修改:2022 年 01 月 27 日
如果觉得我的文章对你有用,请随意赞赏