Ngrok开源版本http+https协议配置的调试过程

背景

问题的发生是在我正在做的Bullet项目,需要实现http和https的双协议支持,能够通过相同的域名去兼容访问http和https方式。如下所示:

https://www.joggle.cn
http://www.joggle.cn

由于目前仅支持http或者https单选的方式,我需要去研究Ngrok的源码。于是我就这样配置:

tunnels:
  mp_160:
    hostname: ''
    proto: {
      'http+https': '192.168.1.3:5000'
    }
    inspect: false
    subdomain: www
    bind_tls: false
...

http+https 是命令行start方式启动支持的,但是通过配置文件启动方式并不支持。

bin/ngrok -config=conf/domain/3weixin.yml -log=stdout start mp_160

这样能运行成功,但是因为不支持,所以直接访问域名www.joggle.cn 是访问不到的。

Ngrok 源码初探

前方沈略很多步骤,我开启了Idea的golang调试模式,于是跟踪到一段代码

client/model.go 328行左右

case *msg.NewTunnel:
    if m.Error != "" {
        emsg := fmt.Sprintf("Server failed to allocate tunnel: %s", m.Error)
        c.Error(emsg)
        c.ctl.Shutdown(emsg)
        continue
    }
    // 这里是新的通道会从这里初始化一个Tunnel配置,其中LocalAddr为空字符串
    tunnel := mvc.Tunnel{
        PublicUrl: m.Url,
        LocalAddr: reqIdToTunnelConfig[m.ReqId].Protocols[m.Protocol],
        Protocol:  c.protoMap[m.Protocol],
    }

    c.tunnels[tunnel.PublicUrl] = tunnel
    c.connStatus = mvc.ConnOnline
    c.Info("Tunnel established at %v", tunnel.PublicUrl)
    c.update()

注意:LocalAddr是ngrok决定将请求转发到哪个内网的地址IP和端口的配置,格式如:192.168.1.3:5000

如何让LocalAddr有值就成为了下一个解决的问题,于是继续跟踪找到了

config.go 117行

// 这里在遍历协议,t.Protocols map的key就是http、https、tcp、http+https
for k, addr := range t.Protocols {
    tunnelName := fmt.Sprintf("for tunnel %s[%s]", name, k)
    if t.Protocols[k], err = normalizeAddress(addr, tunnelName); err != nil {
        return
    }

    if err = validateProtocol(k, tunnelName); err != nil {
        return
    }
}

改造代码如下:

// 这里在遍历协议,t.Protocols map的key就是http、https、tcp、http+https
for k, addr := range t.Protocols {
    tunnelName := fmt.Sprintf("for tunnel %s[%s]", name, k)

    // 切分协议(http+https多协议的情况)
    for _, proto := range strings.Split(k, "+"){

        // 校验协议是否正确
        if err = validateProtocol(proto, tunnelName); err != nil {
            return
        }

        if t.Protocols[proto], err = normalizeAddress(addr, tunnelName); err != nil {
            return
        }
    }

}

image

然后run起来后,达到了我想要的效果,注入了多个协议的配置,然后就报错了!!!

Server failed to allocate tunnel: The tunnel http://weixin.joggle.cn is already registered.
Server failed to allocate tunnel

相当于重复注册了http协议。

{"Type":"ReqTunnel","Payload":{"ReqId":"141ac667322a442a","Protocol":"http+https+http+https","Hostname":"","Subdomain":"weixin","HttpAuth":"","RemotePort":0}}
[17:06:27 CST 2020/09/30] [DEBG] (ngrok/log.(*PrefixLogger).Debug:79) [ctl:bc18aa6] 

相当于协议变了http+https+http+https叠加了好多,产生这个问题的原因是在这里。

client/model.go 291 行

reqTunnel := &msg.ReqTunnel{
    ReqId:      util.RandId(8),
    Protocol:   strings.Join(protocols, "+"),
    Hostname:   config.Hostname,
    Subdomain:  config.Subdomain,
    HttpAuth:   config.HttpAuth,
    RemotePort: config.RemotePort,
}

方向走错了, 最后找到了正确的配置方式,真实曲折的路线。

下面是http+https的正确配置方式:

tunnels:
  mp_160:
    hostname: ''
    proto: {
      'http': '192.168.1.3:5000',
      'https': '192.168.1.3:5000'
    }
    inspect: false
    subdomain: www
    bind_tls: false
...

接下来就是改造Java的实现代码来支持http和https访问了,当然这些改动会在下一个大版本发布了。

总结

开源的东西大部分是文档不全导致了没有办法真正了解产品的功能,它就像一个黑匣子,你不知道他里面要不断的尝试后得到最终的结果,然后就了解它的特性。

所以开源的项目一定要把文档写好,目前Bullet的文档还是很欠缺没有描述得很完善,所以还需要花一部分时间了完善它。

来源: 雨林博客(www.yl-blog.com)