mongo express MongoError: command listCollections requires authentication

为了连接一个MongoDB server 省事, 不想装本地app, 于是想使用docker 装一个 Web 版本的 Mongo express. 在启动的时候, 总是报这个错: MongoError: command listCollections requires authentication

我的连接URL是: mongodb://user1:pwd1@mymongo.tianxioahui.com:27017/test_db. 可是根据官方的说明, 不论怎么写 docker command 都不行.

$docker run --rm -e ME_CONFIG_MONGODB_SERVER=mymongo.tianxioahui.com \
                 -e ME_CONFIG_BASICAUTH_USERNAME=user1 \
                 -e ME_CONFIG_BASICAUTH_PASSWORD=pwd1 \
                 -e ME_CONFIG_MONGODB_ENABLE_ADMIN=false \
                 -e ME_CONFIG_MONGODB_AUTH_DATABASE=test_db
                 -p 8083:8081 --name myMongo  mongo-express

(node:6) UnhandledPromiseRejectionWarning: MongoError: command listCollections requires authentication
    at Connection.<anonymous> (/node_modules/mongodb/lib/core/connection/pool.js:453:61)
    at Connection.emit (events.js:314:20)
    at processMessage (/node_modules/mongodb/lib/core/connection/connection.js:456:10)
    at Socket.<anonymous> (/node_modules/mongodb/lib/core/connection/connection.js:625:15)
    at Socket.emit (events.js:314:20)
    at addChunk (_stream_readable.js:297:12)
    at readableAddChunk (_stream_readable.js:272:9)
    at Socket.Readable.push (_stream_readable.js:213:10)
    at TCP.onStreamRead (internal/stream_base_commons.js:188:23)

可是不论怎么调可用的参数, 总是报这个错. Google 了一下, 发现2021年6月就有人报这个错: https://github.com/mongo-express/mongo-express/issues/720

解决方式也很简单, 直接用一个连接URL 替换其他环境变量:

sudo docker run --rm -e ME_CONFIG_MONGODB_URL=mongodb://user1:pwd1@mymongo.tianxioahui.com:27017/test_db  -p 8083:8081 --name myMongo mongo-express

可是, 可是, 这个环境变量ME_CONFIG_MONGODB_URLhttps://hub.docker.com/_/mongo-express 竟然没有, 可是能用, 还很管用.

解密 Java 的 https 流量

最近有个同事发现生产环境中有些服务比较慢, 检查一番后, 发现一个服务调用在tcp 3次握手和 ssl client hello 包之间竟然有200ms 左右的延迟. 接下来, 该同事就想弄清楚: 为什么在3次握手和 client hello之间有200ms 延迟. 他知道有些语言是直接调用的 OpenSSL 的库, 于是问我 Java 里面是不是也是使用 OpenSSL的封装?

我之前写过2篇 Windows 上解密 chrome 的 https 流量 & MAC 上解密 chrome 的 https 流量, 当时就想: 是不是Java 也可以自动把 ssl key 劫持下来, 然后后边自动揭秘. 当时就找到了这个已经持续好多年的项目: jsslkeylog, 这个项目就是通过 Java Agent的方式, 注入相关的代码, 把ssl key 劫持下来.

所以, 当同事问我是不是 OpenSSL 封装的时候, 我立马想到这个项目. 在这个项目里面, 它通过注入的方式把 ssl key 记录下来, 那么肯定是Java 自己原生写的 SSL 的代码处理.

一些核心的代码类: https://github.com/jsslkeylog/jsslkeylog/tree/master/src/main/java/net/sf/jsslkeylog

exec user process caused: exec format error

今天开发的app 做了一个新的docker image, 发布到K8S之后, 就报下面的错:

standard_init_linux.go:228: exec user process caused: exec format error

还以为自己写的代码配置出错了, 想登上去看看, 发现进程压根就没起来.

Google 一把, 有人说是entrypoint的shell 文件的 shebang 不对, 我这里没有这个问题.

后来看到有人说是可执行格式的错误, 发现还真是.
首先, 我在本地Mac Pro (ARM) 上去执行这个image, 很正常的执行起来了, 然后我在另外一个Ubuntu 上面执行这个image, 就给我报下面的错误了:

WARNING: The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64) and no specific platform was requested

所以, 很明显, 这个image 是给 ARM64/v8 用的. 用 docker image inspect <img>去查看, 能看到这个image 是给什么平台机构的.
arch.png

我的 docker build 环境是 MAC ARM 机器, 之前一直没有错, 今天为啥出错了, 原因很有可能是我今天重启了 Docker Desktop.

去看 docker 官方文档: https://docs.docker.com/engine/reference/commandline/build/ 发现他们最近(20220914) 对于 docker build 新加了一个参数 (--platform)
platform.png

到它 change 去看, 发现最新的 v141
https://docs.docker.com/engine/api/version-history/#v140-api-changes

所以, 要在 Mac Pro ARM 芯片上做 x86_64/amd64 的image, 要给一个新参数 --platform linux/amd64

WebSocket 协议

最近有个项目要做一个Slack Robot, 就是根据用户发到Slack 某个Channel的内容, 给出智能回复.

与Slack的交互方式有2种: HTTP连接的方式和WebSocket的方式. HTTP 连接的方式又有2种实现方式:

  1. 我们提供一个公开的endpoint, 当Slack的Channel 有消息的时候, Slack 以webhook的方式及时通知我们, 我们的app 给出回复.
  2. 我们不提供endpoint, 我们连接Slack的endpoint, 以固定时间间隔的方式去poll 消息, 如果有新消息, 我们的app 给出回复.

以上这2种方式都不是很好的方式, 对于第一种, 我们要提供一个公开的endpoint, 在我们的生产环境基本不可能, 对于第二种方式, 不是基于事件的, 不管有没有消息, 都要固定的去poll, 对于一个app 里面有多个实例的情况, 还要控制那个实例去poll.

所以 WebSocket 的方式是最好的, 不过当时就有开发人员提出了意见: 我们的生产环境时不能连外网的, 要连只能通过http 代理, 而我们的生产环境的代理只支持 http 代理, 不支持sock 方式, 所以这种 WebSocket 行不通. 是真的吗?

在我们看看 WebSocket 到底是什么之前, 先做一个 WebSocket 的例子: 使用Node.js 开一个WebSocket 服务, 然后在浏览器开一个 WebSocket 客户端.

WebSocket 服务端

  1. 新建一个文件夹

    mkdir wsServer
    cd wsServer
  2. 新建一个 package.json

    vim package.json

    输入如下代码

    {
      "name": "wsServer",
      "version": "0.0.1",
      "type": "module",
      "dependencies": {
          "ws":"8.8.1"
      }
    }
  3. 新建一个服务端代码文件

    vim server.js

    输入如下代码:

    var WebSocketServer = require('ws').Server
    var fs = require('fs')
    
    const wss = new WebSocketServer({ port: 8080 });
    
    wss.on('connection', function connection(ws) {
      ws.on('message', function message(data) {
        console.log('received: %s', data);
        ws.send(data + " -> server ack");
      });
    
      fs.watch('/tmp/', (eventType, fileName) => {
        console.log('Get event on file ' + fileName + ', type: ' + eventType);
        ws.send('file -> ' + fileName + ' -> ' + eventType);
      })
    
      ws.send('welcome to WebSocket world!');
    });

上面这段代码做3件事情:

  1. 启动 WebSocket 服务器在8080 端口上, 当有人来连接的时候, 发送欢迎消息;
  2. 当收到客户端消息的时候, 打印收到的消息, 并且发送给客户端 ack 消息;
  3. 监听本地 /tmp 文件夹的文件变动事件, 打印日志, 并推送给客户端;

启动服务端代码:

node server.js

WebSocket 客户端

在 Chrome 浏览器打开任意页面的控制台, 输入如下JavaScript 代码

const ws = new WebSocket('ws://10.249.64.103:8080');

// Listen for messages
ws.addEventListener('message', (event) => {
    console.log('Message from server: ', event.data);
});

document.addEventListener('click', (event) => {
    console.log("just clicked");
    ws.send('click on (' + event.x + ', ' + event.y + ')');
});

上面的代码做下面的事情:

  1. 连接服务端 WebSocket;
  2. 然后当收到服务器端消息的时候, 打印收到的消息;
  3. 当页面上收到点击事件的时候, 推送给服务端点击事件的坐标;

运行效果

客户端效果:
client.png

服务器端效果:
server.png

使用代理

因为客户端使用的是浏览器, 可以设置在chrome 设置对于这个IP 启用代理, 使用http 代理, 依然能联通server 端, 正常运转.

使用curl

使用curl 只能被动的接受服务器传来的消息, 不能发送任何消息. (这个 Sec-WebSocket-Key 是从浏览器刚才发送的历史中复制过来的)

curl \
    --include \
    --no-buffer \
    --header "Connection: Upgrade" \
    --header "Upgrade: websocket" \
    --header "Host: 10.249.64.103:8080" \
    --header "Origin: http://10.249.64.103:8080" \
    --header "Sec-WebSocket-Key: 1TFTcjPQ7iG2XvsZ83WgZg==" \
    --header "Sec-WebSocket-Version: 13" \
    http://10.249.64.103:8080

效果(curl 对回应消息只能拼接, 不换行):
server.png

JavaScript WebSocket API 文档

官方文档在这里: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket
这个 WebSocket 类很简单:

  1. 构造函数给出服务器的地址, 包括ws协议, host 加 port;
  2. 有几个字段: 比如 url, readyState, protocol, bufferedAcmount, binaryType等, 有些是只读的;
  3. 客户端只有发送 send() 和 close() 方法;
  4. event handler:

    1. message: 当收到消息时;
    2. open: 当连接建立时;
    3. error: 当发送错误时;
    4. close: 当关闭连接时;

Node.js 的 WebSocket 实现库 ws 的文档

官方文档: https://www.npmjs.com/package/ws#sending-and-receiving-text-data
它不仅仅包含一个server端的API实现, 还包含一个做为 Node.js 客户端端代码实现

WebSocket 协议

官方文档: https://www.rfc-editor.org/rfc/rfc6455
当我们看过上面的 WebSocket 的例子之后, 再来看这个RFC 文档, 就不是那么难了.

为什么需要 WebSocket 协议

在 web 通信等某些场景下, 如果服务端发生了某些事件, 需要实时推送给客户端. 在传统的基于web的技术下, 需要客户端不断的去poll消息, 不管服务端到底有没有事件更新, 每次poll 都需要客户端发送一个http request, 并且如果发送的频率过低, 可能不能及时收到服务端事件更新, 如果频率过高, 又会对网络和服务端造成一些压力.

所以, 如果建立一个连接的情况下, 服务端事件变更主动推送客户端, 客户端只要等待就好了, 就完美解决了这个问题, 于是就有了 WebSocket 协议.

WebSocket 基本介绍

  1. 它可以使客户端和服务端在一个连接里面双向不间断通信;
  2. 通信过程分2阶段, 先是使用http协议握手连接, 然后以数据帧的方式双向发送数据;
  3. 广泛使用在游戏, 股票等需要实时消息通讯等软件中;
  4. WebSocket 可以使用现有web的 proxy和认证等成熟的机制;
  5. 通常开在80或443, 可以通过防火墙;

通常的 连接握手协议:

The handshake from the client looks as follows:

        GET /chat HTTP/1.1
        Host: server.example.com
        Upgrade: websocket
        Connection: Upgrade
        Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
        Origin: http://example.com
        Sec-WebSocket-Protocol: chat, superchat
        Sec-WebSocket-Version: 13

   The handshake from the server looks as follows:

        HTTP/1.1 101 Switching Protocols
        Upgrade: websocket
        Connection: Upgrade
        Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
        Sec-WebSocket-Protocol: chat

xxx Is Damaged and Can’t Be Opened. You Should Move It To The Trash

最近拿到公司 ARM 芯片的 Mac Pro, 一番设置, 可是新新下载的软件, 比如JDK, 总是报下面的错, 无法运行:
“xxx Is Damaged and Can’t Be Opened. You Should Move It To The Trash“
damage.png

如何修复

google 到这个修复方法: https://discussions.apple.com/thread/253714860

$ xattr -c <path/to/application.app>

使用上面的方法对 java 做上述操作, 还是一样的错误, 一度怀疑这个不行. 但是通过 xattr 查询它的属性, 发现又是相关. 最终发现这么解决: 对目录里面每层文件都做这个操作:

eric@Q67J490MY0 bin % pwd
/Users/eric/work/tools/jdks/jdk17.0.3.1/bin
eric@Q67J490MY0 bin % xattr -c *
eric@Q67J490MY0 bin % cd ..
eric@Q67J490MY0 jdk17.0.3.1 % xattr -c *
eric@Q67J490MY0 jdk17.0.3.1 % ./bin/java

上面的操作是对每个文件都去掉xattr的那些属性.

更多

xattr -h #查看帮助