Puppeteer 踩坑笔记

Puppeteer 是最常见的服务端 Node.js 网页转 PDF 工具库之一,原理是启动 Chromium 无头模式,打开网页,输出PDF,由于其原理是直接操控浏览器,导出的 PDF 几乎和网页效果一致。本篇记录一下踩坑经历。

我的 Puppeteer 版本:19.4.1

无法安装 puppeteer

  1. Node.js 要求 >=14.1.0
  2. Puppeteer 安装过程中默认访问 Google 服务器下载 Chromium 安装包,建议切换到阿里源进行安装。
1
npm config set PUPPETEER_DOWNLOAD_HOST=https://registry.npmmirror.com/-/binary
阅读更多

JDBC 连接 Azure Database for MySQL 报错 SSL peer shut down incorrectly 的解决

这个问题找了一天的原因,如果你也遇到了这个问题,希望能帮到你。

完整错误信息:

点击展开 >folded
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
03-Jun-2020 07:52:22.772 SEVERE [main] org.apache.tomcat.jdbc.pool.ConnectionPool.init Unable to create initial connections of pool.
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

The last packet successfully received from the server was 384 milliseconds ago. The last packet sent successfully to the server was 376 milliseconds ago.
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1117)
at com.mysql.jdbc.ExportControlled.transformSocketToSSLSocket(ExportControlled.java:105)
at com.mysql.jdbc.MysqlIO.negotiateSSLConnection(MysqlIO.java:5126)
at com.mysql.jdbc.MysqlIO.proceedHandshakeWithPluggableAuthentication(MysqlIO.java:1666)
at com.mysql.jdbc.MysqlIO.doHandshake(MysqlIO.java:1244)
at com.mysql.jdbc.ConnectionImpl.coreConnect(ConnectionImpl.java:2397)
at com.mysql.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:2430)
at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2215)
at com.mysql.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:813)
at com.mysql.jdbc.JDBC4Connection.<init>(JDBC4Connection.java:47)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
at com.mysql.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:399)
at com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:334)
at org.apache.tomcat.jdbc.pool.PooledConnection.connectUsingDriver(PooledConnection.java:319)
at org.apache.tomcat.jdbc.pool.PooledConnection.connect(PooledConnection.java:212)
at org.apache.tomcat.jdbc.pool.ConnectionPool.createConnection(ConnectionPool.java:744)
at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:676)
at org.apache.tomcat.jdbc.pool.ConnectionPool.init(ConnectionPool.java:483)
at org.apache.tomcat.jdbc.pool.ConnectionPool.<init>(ConnectionPool.java:154)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.pCreatePool(DataSourceProxy.java:118)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.createPool(DataSourceProxy.java:107)
at org.apache.tomcat.jdbc.pool.DataSourceFactory.createDataSource(DataSourceFactory.java:560)
at org.apache.tomcat.jdbc.pool.DataSourceFactory.getObjectInstance(DataSourceFactory.java:244)
at org.apache.naming.factory.FactoryBase.getObjectInstance(FactoryBase.java:96)
at java.naming/javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:325)
at org.apache.naming.NamingContext.lookup(NamingContext.java:857)
at org.apache.naming.NamingContext.lookup(NamingContext.java:160)
at org.apache.naming.NamingContext.lookup(NamingContext.java:843)
at org.apache.naming.NamingContext.lookup(NamingContext.java:174)
at org.apache.catalina.core.NamingContextListener.addResource(NamingContextListener.java:1017)
at org.apache.catalina.core.NamingContextListener.createNamingContext(NamingContextListener.java:557)
at org.apache.catalina.core.NamingContextListener.lifecycleEvent(NamingContextListener.java:253)
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:123)
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:924)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
at org.apache.catalina.startup.Catalina.start(Catalina.java:633)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:344)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:475)
Caused by: javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake
at java.base/sun.security.ssl.SSLSocketImpl.handleEOF(SSLSocketImpl.java:1322)
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1160)
at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1063)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:402)
at com.mysql.jdbc.ExportControlled.transformSocketToSSLSocket(ExportControlled.java:90)
... 44 more
Suppressed: java.net.SocketException: Broken pipe (Write failed)
at java.base/java.net.SocketOutputStream.socketWrite0(Native Method)
at java.base/java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:110)
at java.base/java.net.SocketOutputStream.write(SocketOutputStream.java:150)
at java.base/sun.security.ssl.SSLSocketOutputRecord.encodeAlert(SSLSocketOutputRecord.java:81)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:351)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:263)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:405)
... 45 more
Caused by: java.io.EOFException: SSL peer shut down incorrectly
at java.base/sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:167)
at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:108)
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1152)
... 47 more

提取关键信息:

1
2
Caused by: javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake
Caused by: java.io.EOFException: SSL peer shut down incorrectly

得知 SSL 握手失败,于是我找了 Azure 的示例代码(https://docs.azure.cn/zh-cn/mysql/connect-java)来测,也是同样的问题。

阅读更多

记录 Drupal 配置 monolog 遇到的坑

在开发 Drupal CMS 网站时,我们发现 Drupal 默认的日志记录在 watchdog 数据表中,为了把日志记录在文件、控制台输出,可以使用 monolog 模块。这个模块提供的文档较少,在此记录一下遇到的问题。

TOC

  1. 启用模块报错 The service “monolog.handler.rotating_file” has a dependency on a non-existent parameter “monolog.level.debug”.
  2. 修改日志格式为 JSON
  3. 输出到控制台(在 Drupal 运行在 Docker 容器中时非常有用)
  4. arguments 的配置
  5. processors 的配置
  6. 分离 INFO 和 ERROR
阅读更多

理解 Javascript,PHP,SQL 匹配中文姓名正则表达式的区别

如何定义合法姓名

中文姓名非常多样化,想要真正区别出姓名和非姓名非常困难,但在这篇文章中,作为前提,我们认为合法的中文姓名由 1~10 个中文字符组成。

Javascript

知道了这个前提,容易得出 JS 的正则表达式——

姓名正则
1
/^([\u4e00-\u9fa5]{1,10})$/
阅读更多

PHP imagepng 输出损坏的图片问题解决

问题发现

我和同事的两套本地开发环境,相同的一段返回随机验证码图片的代码,验证码代码参考的是 php实现的Captcha验证码类实例 里的代码。在同事的电脑上调用,出来的是裂开的图片,在我的电脑上调用,出来的就是正常的图片,这是为什么呢?解决这个问题花费了三四个小时,记录一下过程。

解决过程

因为代码上没有区别,所以开始怀疑环境问题,两台开发机都是相同型号,配置也相同,PHP 运行在 docker 容器下,检查 PHP 版本相同。

查了一下 PHP 文档,怀疑可能是因为什么特性没有启用,测试以下代码:

test.php
1
2
3
4
5
<?php
if (imagetypes() & IMG_PNG) {
echo "PNG Support is enabled";
}
?>

测试结果 imagetypes() & IMG_PNG 的值大于零,看起来两个环境都是支持 PNG 图的。

阅读更多

体验 Azure 应用服务:可以白嫖的 OneIndex 空间

之前已经写过一篇如何使用 Azure DevOps 实现自动部署 Hexo 的文章,这个月,受疫情影响在家,就把自己的 Azure 新用户体验账号开了,获得了限时 1 个月 200 刀的体验额度。

之后研究中发现了 Azure 提供的应用服务还挺好用的,当然重点是——可以白嫖!

免费套餐当然是有限制的——

  1. 配置上,1G内存,每天最多1小时CPU时间(计算量不大24小时开着没问题)
  2. 330M 左右的出站流量
  3. 无法绑定自定义域名,只能使用 *.azurewebsites.net
  4. 没有数据库,可以另建 MySQL,但不是免费的,想搭建 Typecho 和 Wordpress 的还是别想了
  5. 无国内节点,可选日本节点
  6. 需要 Azure 账号,也就是需要 VISA 或者 Master 卡

我们可以在上面部署 .Net Core, ASP.NET, Java, Node.js, PHP, Python, Ruby 语言的项目。

之前有访问过别人的 PyOne,发现下载速度还挺快,就一直想用 OneDrive 搭建一个自己的网盘,机会来了。

主流的 OneDrive 第三方 Directory Index,有老牌的 oneindex,还有后起之秀 PyOneOLAINDEX,虽然 Azure 的 PHP 环境可以自动识别 composer 安装依赖,也支持执行部署脚本、执行 php 命令,但是由于 PyOne 还要依赖 Python 环境,OLAINDEX 还要依赖 Nginx,别想了,搭建不了。

当我尝试在 Azure 应用服务上面部署 OLAINDEX 时,在执行安装脚本 php artisan od:install 时出错,遇到的错误是 SQLite General error: 5 database is locked,由于完全不会 PHP,就放弃了。

好了,就决定是 OneIndex 了!高手看到这里基本就可以自己去尝试了,下面是我的步骤。

阅读更多

Netlify 初体验——真就是几秒部署呗

Hexo 部署依赖 node 环境就让很多专注写作的人放弃这个框架,劝退萌新,并且难以随时随地写作。好在其 CLI 比较简单,前面我也介绍过如何利用免费的微软 Azure 持续部署博客

不过今天看 Ant Design Pro 官网的 footer 的时候发现了 Netlify 这个东西,试了以后,强烈安利!!!

来到它的首页,里面有这么几段话——

大字:

在几秒钟内部署您的网站*

小字:

*不,这里没有陷阱。
真的只需点击几下。

嗯……嗯?哼哼……有免费套餐,试试吧!

  1. 首先用 GitHub 授权登录
  2. 选择 New site from Git
  3. 选择项目托管的位置:GitHub
  4. 授权 GitHub 访问私有项目
  5. 输入构建命令……等等,你怎么给我自动输入了啊喂?
  6. 点 Deploy site

然后构建就启动了,1分钟后我的站点就到了 Netlify 上。

当时我的表情是 (⊙o⊙)?

我连部署命令都没打;
我连环境都没提供;
我甚至全程没有摸一下键盘……(你要问我注册登录不摸键盘?真就没摸,GitHub 是已登录的状态,点一下授权按钮就登录了。)
敢情 hexo 的部署配置,Azure 的 CICD 都白写了呗?

……

😓,流批……

这个网站还支持绑定域名,自动申请 HTTPS,配 Web Hook(就不用手动点部署了),还有很多很多真 · 自动化的功能,大家一起发掘吧!

Bitnami MariaDB 重启失败记录

自动部署的 Magento 附带的 MariaDB 镜像第一次启动能够成功,但 stop 之后就无法再次启动。启动命令 docker-compose up -d,重启命令 docker-compose restart

关键 log:

1
2
3
InnoDB: Assertion failure in file /bitnami/blacksmith-sandox/mariadb-10.2.28/storage/innobase/dict/dict0dict.cc line 1467
InnoDB: Failing assertion: table->can_be_evicted
[ERROR] mysqld got signal 6 ;

详细的 stdout 如下——

阅读更多

Windows 下通过 Vagrant 简单搭建 Magento

准备软件

  1. VirtualBox 5.2.8
  2. Vagrant 2.0.1

官网下载安装,比我的新即可,安装路径自选。

创建 Vagrantfile

新建文件夹并创建 Vagrantfile 放在里面,我配置的环境是 Ubuntu 16.04,内存8G,可以换成自己熟悉的环境。
映射端口80,443(Web),3306(数据库),9200,9300(搜索引擎)。

Vagrantfile
1
2
3
4
5
6
7
8
9
10
11
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/xenial64"
config.vm.network "forwarded_port", guest: 80, host: 80
config.vm.network "forwarded_port", guest: 443, host: 443
config.vm.network "forwarded_port", guest: 3306, host: 3306
config.vm.network "forwarded_port", guest: 9200, host: 9200
config.vm.network "forwarded_port", guest: 9300, host: 9300
config.vm.provider "virtualbox" do |vb|
vb.memory = "8192"
end
end
阅读更多