通过 Socket 获取网站 SSL 证书及公钥

通过 php curl 请求网页并不能获取到证书信息,此时需要使用 ssl socket 获取证书内容。

// 创建 stream context
$context = stream_context_create([
    'ssl' => [
        'capture_peer_cert' => true,
        'capture_peer_cert_chain' => true,
    ],
]);

$resource = stream_socket_client("ssl://$domain:$port", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context);
$cert = stream_context_get_params($resource);

$ssl = $cert['options']['ssl'];
$resource = $ssl['peer_certificate'];

// 网站证书中只有公钥,通过 openssl_pkey_get_details 导出公钥

$ret = [
    'crt' => '',
    'pub' => '',
];

$pkey = openssl_pkey_get_public($resource);
$ret['pub'] = openssl_pkey_get_details($pkey)['key'];

openssl_x509_export($resource, $pem);
$ret['crt'] = $pem;

foreach ($ssl['peer_certificate_chain'] as $resource)
{
    openssl_x509_export($resource, $pem);
    $ret['crt'] .= "\n" . $pem;
}

// 保存 $ret['crt'] 为 domain.crt
// 保存 $ret['pub'] 为 domain.pub

return $ret;

验证证书中的公钥A是否正确,通过私钥导出公钥B,比较两者发现一致。

$domain = 'blog.zhengxianjun.com';
$port = '443';
// ...
$pub_a = $ret['pub'];

$private_key_path = '/conf/ssl/blog.zhengxianjun.com.key';

// 证书没有设置密码,$passphrase 为空字符串
$pkey = openssl_pkey_get_private(file_get_content($private_key_path), $passphrase = '');
$pub_b = openssl_pkey_get_details($pkey)['key'];

// 两者一致
var_dump($pub_a === $pub_b);

函数 stream_socket_client 还有一个用途是当知道服务器 IP 时,能获取到服务器可能可以使用的域名。

$resource = stream_socket_client("ssl://$ip:$port", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context);
$cert = stream_context_get_params($resource);

// 解析 X.509 格式证书
$info = openssl_x509_parse($cert['options']['ssl']['peer_certificate']);

// 获取证书中的可信域名列表
$domain = str_replace('DNS:', '', $info['extensions']['subjectAltName']);

以上可以看到获取网站证书并不能获得私钥。

在一些使用 CDN 的站点,如果使用了 HTTPS 同时又希望使用自有域名,是否需要将自己的私钥提供给 CDN 厂商呢?实际上证书路径与使用者名称(支持 https 的域名)并不需要一致。

也就是使用自有域名并进行 CDN 加速时不需要使用自有的 ssl 证书,只需将自己的 CDN 域名加到厂商证书的域名列表即可。

Closure-Compiler 指定 charset 减少输出文件大小

Google 的 Closure-Compiler  压缩 JavaScript 文件默认采用 UTF-8 作为输入编码,US_ASCII 作为输出编码。

–charset VAL : Input and output charset for all files
. By default, we accept UTF-8 as input
and output US_ASCII

因此,压缩汉字(或者日韩文字)之后,文件会变大。

执行命令 java -jar $dir/compiler.jar --js $cache_file --js_output_file $output_file

// 输入文件
var address = '上海';

// 输出文件
var address="\u4e0a\u6d77";

源文件较小时可以忽略这个问题,但源文件有大量汉字,可以指定 charset 避免输出文件过大。

执行命令 java -jar $dir/compiler.jar --js $cache_file --js_output_file $output_file --charset=UTF-8

// 输入文件
var address = '上海';

// 输出文件
var address="上海";

在开发中遇到一个 1.5MB 的地址文件,压缩之后变成 3MB,再经 gzip 压缩变成 780 KB 左右。而源文件经 gzip 之后在 590 KB。

TortoiseSVN 图标不显示

最近突然遇到 TortoiseSVN 图标不显示,文件夹和文件都不显示对应状态,只能在 commit 窗口查看文件修改列表,非常不方便。

网上查到两种解决方法:1,修改注册表;2,修改 Status cache。

本文再提供一种解决方法:修改 Icon Set

1 没有尝试。

2 右键 TortoiseSVN -> Settings -> Icon Overlays -> Status Cache,修改为 Shell。这种方式有一个明显缺点——“Status cache only for one folder, no recurcive overlays”,就是只显示文件状态,不显示文件夹状态。因此只能看到绿色打勾的文件夹。

还是将 Status cache 改回 Default —— “Status cache kept in an external process, can show the overlay recurcively”。

最后尝试修改 Icon Set,希望能强制更新图标缓存,在 Icon Overlays -> Icon Set,选择任意样式,确定并重启电脑,图标更新成功

如果不习惯新的样式,可以再次修改 Icon Set,选择之前的样式,比如 XPStyle,确定并重启电脑,可以恢复到最早正确显示的图标。

TortoiseSVN Icon Set

如何修改 npm-debug.log 路径,无解

目前没有找到修改 npm-debug.log 路径的方法,除非修改源码。

npm 版本是 3.3.12

这是 2014 年 stackoverflow 上的提问 Change npm-debug.log location,以及在 github 上讨论的 issue
Put npm-debug.log file in the cache folder, not cwd #6744
npm-debug.log could be written to /tmp or similar #1548
以及假想解决方案
Make npm-debug.log path configurable. #5252
将 log 文件写入 app 目录的风险显而易见,尤其正式环境一般都不会开放 app 目录写权限。

在源文件 npm/lib/util/error-handler.js 中 writeLogFile() 内部已经 hardcode writeStream(‘npm-debug.log’),当初是如何构思的?

紧接着想到是否可以禁用 node-debug.log, 不过这并不是一个很好的解决方案.

 

npm 命令行参考 官方 Shorthands and Other CLI Niceties

 

npm –prefix 指定目录

最新上线一个 nodejs 项目,使用 pm2 做进程管理器。

因为 pm2 更适合用作正式环境进程管理,不像开发环境使用 nodemon 实时监听文件改动重启 node 进程,所以每次发布之后需要执行 pm2 reload all 零延时载入代码。

pm2 reload all 被配置到 package.json 的 scripts,执行命令 npm run pm2_reload 即可载入代码。

现在的问题是 node 项目部署在 /www/ran-api,每次需要执行 cd /www/ran-api 进入该目录,才能执行 npm 命令,否则就会提示找不到 package.json 文件。

后来终于在 npm 官方文档 https://docs.npmjs.com/misc/config 找到 –prefix 配置,也可以直接查看 npm 自带的 Markdown 教程,一般位于 /nodejs 安装目录/node_modules/npm/doc/misc/npm-config.md

执行下述命令就不用再进入指定目录了:

/nodejs/npm –prefix=/www/ran-api run pm2_reload

其他命令也可以设置 –prefix,比如 npm config list

[~]$ /nodejs/bin/npm config list –prefix=/www/ran-api
; cli configs
; 这是增加的命令行参数 prefix,会被设置到 npm.localPrefix
prefix = “/www/ran-api”
user-agent = “npm/3.3.12 node/v5.5.0 linux x64”

; node bin location = /nodejs/bin/node
; cwd = /home/ran
; HOME = /home/ran
; “npm config ls -l” to show all defaults.

win10升级后字体异常解决方案

今天 win10 自动更新之后,chrome 下使用了微软雅黑的网页字体看着非常难受,不论如何修改 font-family,就是无法显示正常的微软雅黑。

想起刚启动 chrome 时,发现默认浏览器不是 chrome,而是 edge。尝试用 edge 打开网页,显示正常。猜想不是字体的原因。

百度之后,找到是 chrome 默认开启 DirectWrite 渲染。

用 chrome 在地址栏打开 chrome://flags/ ,停用 DirectWrite 即可显示正常。

对比一下开启和停用效果图

开启效果图(默认状态)

停用效果图

Linux 安装 memcache

下载 memcache

wget http://memcached.org/latest

如果没有安装 wget 命令,可以先通过 yum 命令安装 wget 命令

yum install wget

解压并安装

tar -zxvf latest
cd memcached-1.x.x
./configure && make && make test && sudo make install

memcache 依赖 libevent,安装中如果出现

for libevent directory… configure: error: libevent is required.

系统会默认安装 libevent,memcache 找不到是因为没有找到相应头文件

可以通过 yum 安装 libevent-devel,安装 libevent 相关开发使用的头文件

yum install libevent-devel

再次执行

./configure && make && make test && sudo make install

此时可能出现错误

prove ./t
make: prove: Command not found
make: *** [test] Error 127

这是执行 make test 命令抛出的错误,可以忽略,重新执行

sudo make install

安装完成

当以 root 身份安装,可以不使用 sudo 获取权限

JavaScript 获取两个字符串最长公共子串 (Longest Common Subsequence)

获取两个字符串 a 和 b 的最长公共子串,时间复杂度 O(mn),空间复杂度 O(n),其中 a 的字符串长度为 m,b 的字符串长度为 n。

function lcs(a, b) {
    var ai, al = a.length,
        bi, bl = b.length - 1, maxBi,
        max = 0, val,
        row = [],
        ret = [];

    for (ai = 0; ai < al; ai++) {
        for (bi = bl; bi > -1; bi--) {
            val = a[ai] == b[bi] ? 1 : 0;
            if (val) {
                if (row[bi - 1]) {
                    val += row[bi - 1];
                }
                if (val > max) {
                    max = val;
                    maxBi = bi;
                }
            }
            row[bi] = val;
        }
    }

    for (; max > 0; max--, maxBi--) {
        ret.unshift(b[maxBi]);
    }

    return ret.join('');
}

详细分析 setInterval 和 setTimeout 区别

理解两者区别需要先知道:

  1. JavaScript 是单线程
  2. setInterval 是周期性地调用一个函数(一段可执行代码)
  3. setTimeout 是在一定延迟之后调用一个函数(一段可执行代码)
function cost150ms(){
    // 该函数执行 150ms
}
function testTimeout() {
    // 函数在 150ms 之后执行
}
function testInterval() {
    // 函数在 150ms、200ms、300ms、400ms... 执行
}

setTimeout(testTimeout, 100);
setInterval(testInterval, 100);
cost150ms();

结论

setTimeout(callable, 100);
otherCode();

callable 是周期性执行,不管 otherCode 执行多长时间。
如果到达下一个周期,otherCode 仍然在执行,则 callable 被推迟到 otherCode 执行完毕,即产生“跳过”。
所以上面的例子中能看到 callable 第一次执行是 150ms,第二次执行时 200ms,两次执行只相差 50ms。
假如在某个周期 otherCode 仍在在执行,则该周期 callable 继续被推迟到 otherCode 执行完毕。

setTimeout(callable, 100);
otherCode();

callable 调用时间是 otherCode 执行时间与 100ms 最大值。

setInterval 和 setTimeout 都有可能出现推迟执行,setInterval 由于是周期性执行,表现上更像是“跳过”,setTimeout 更像是推迟。

实际场景不会是刚刚等于 150ms、200ms,但接近该值。

经典战争片《危机13小时》

爆炸贝导演的《危机13小时》中的经典台词:

他战死在一个本不该去的地方,
He died in a place he didn’t need tobe,

死于一场不明缘由的战役,
in a battle over something he doesn’t understand,

死于一个对他来说毫无意义的国度。
in a country that meant nothind to hime.

影片改变自2012年9月11日美国驻利比亚大使史蒂文斯遇袭身亡

同样值得推荐的电影《孤独的生还者》。