Say ByeBye👋 to the ETA features in my WeChat miniprogram

Background

Last year, I have released a WeChat miniprogram which is an alternative to the KMB 1933 APP.

Why I still spent my still developing a mini-program? I have below pain points when using the KMB APP, the points were based on the KMB 1933 app version released around min-2019.

  1. The full-screen ads keep popping up on my iPhone.
  2. The app was so slow to open and crash occasionally.
  3. I just care about some bug routes and stops, don’t give me too much info I don’t need.

My solution

My miniprogram have these three key features to solve the pain point:

  1. Home screen: Show ETA of the bookmarked bus stops(Swipe left to delete the bookmark).
  2. Second screen: Show nearby stops ETA (Swipe left the row to bookmark the stop).
  3. Third screen: Search bus routes(announce, schedule, and map views of the routes)
miniprogram screenshots

Welcome to have a trial by scan this QRCode by WeChat:

The miniprogram QR Code

The ETA features

EAT(Estimated Arrival Time) is the key info of the app. There’re two ways we can try to get the ETA info:

  1. KMB official Web site: https://search.kmb.hk/KMBWebSite/index.aspx?lang=tc
  2. KMB 1933 APP

My miniprogram is using the KMB official Website as the data source.

KMB official website site ETA feature screenshoot.

KMB official Website ETA feature is a pure front-end function without any authentication. You can easily find out the js source code to inspect the logic of how it integrated with the API. I can tell you there’s not fanny encryption stuff.

However, the KMB official website has tried many ways to protect the API endpoint from abuse. The recent key improvement is that KMB introduced Google reCAPTCHA to protect the API.

The new captcha key for invoking the ETA API.

The captcha key will be generated when the user open the KMB website and bound with the KMB domain (I guess it will bind with the user IP as well). So I the captcha key one-off and can’t be reused.

I created a codesandbox demo to try the captcha.

https://codesandbox.io/embed/friendly-cartwright-dtp23?fontsize=14&hidenavigation=1&theme=dark

The sandbox site is using my own Google reCAPTCHA. If you replace with KMB key 6LdiOd8ZAAAAACukKcCRmmf_Ll2hgSIVya22YR99, you will get the error of “ERROR for site owner: Invalid domain for site key”.

Possible solutions

Since the KMB website is a pure front-end app, one possible solution is that we can simulate a browser in node.js runtime to get the google captcha token then invoke the ETA API. The headerless browser can be done by puppeteer or phantomjs.

To run a browser will be a huge overhead or it may require some daemon service to accelerate performance, so some serverless env such lambda or cloud function maybe not suitable to host this kind of service. (My miniprogram API is hosted by WeChat Cloud Function).

Another solution is to hack the KMB 1933 APP. For example, using some proxy apps such as Charles to monitor the APP traffic with backend API, hopefully, you can get the dedicated or more well-organized API of how the APP gets the ETA data.

Usually, the APP will use HTTPS protocol to secure communication. The good news is that Charles can use man-in-the-middle HTTPS proxy so that you’re able to view in plain text the communication between web browser and SSL web server. The bad news is that if the APP enables the HTTP Public Key Pinning (HPKP) the Charles will be useless for the proxy.

Thank you for reading.

《普希金》

本文旨在阐述严肃文学在流行音乐载体起到的大众传播效果。


听音乐也是会上瘾的。没得听音乐的感觉是这样,你可以问问烟鬼一天不抽烟是什么感觉。

最近在随机地听李志的各张live专辑,随机到了这首歌: 普希金 – 李志x丁薇丨live 2015 动静 https://www.youtube.com/watch?v=TXuLHJm9wsU

你听一听就感觉不一样,有种学院派的流行音乐风格,因为这是丁薇的歌。如果你对她熟悉,你可能听过她写的《女孩与四重奏》,早起是由歌手马格演唱。如果你对马格有点印象,可能你听过她的《远远的远,远远》。。。不过马格已经一早淡出了娱乐圈,在那个90年代,马格可能也是和一些乐队一样(我这里想说的是鲍家街43号),生存所迫,无奈解散或改行。


我应该要回到标题,有点离题了。

《普希金》这首歌是有点特别的,感觉像个钩子,你在100首歌里面随机到它,就会停下来,谷歌搜索一下看看发生什么情况。(这篇文章的诞生就是发生的情况之一。)

排除了这首歌旋律对我的吸引,起码我觉得,标题让这首歌加分不少。

歌词第一句:假如你不在我身边。。。

这难道不就是《假如生活欺骗了我》吗?

当然我还发现了一些来自普希金有趣的句子(以下是浮躁时代的快速阅读方式【谷歌关键字:普希金 名言】):

  1. 讀書和學習是在別人思想和知識的幫助下,建立起自己的思想和知識。 (多么好的勉励自己读书的理由)
  2. 读书是最好的学习,追随伟大人物的思想,是富有趣味的事情。(多么好的解析为什么读书的理由)
  3. 世界的設計創造應以人為中心,而不是以謀取金錢,人並非以金錢為對象而生活,人的對象往往是人。(多么好的指导产品设计的理由)(在各种IT新品发布会,引用这句话,格调马上上来了)
  4. 不管怎么说,不怀希望、不求报答的爱情肯定比一切工于心计的引诱更能打动一个女人的心。(多么好的打动女孩子的理由)
  5. 沒有幸福,只有自由和平靜。(多么好的让自己接受平庸平凡的理由)

你看伟大文豪普希金就被我几句话说完了。

完。

一个解决问题的思路 Didn’t find class “javax.net.ssl.SNIHostName”

TL;DR

Android开发,targetSdkVersion 23,任务是给APP集成AWS Android IoT SDK(2.16.12),Android Studio build APP过程中遇到了标题所示的问题。

原因是SDK其中的一个依赖'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.2' 有个bug

我的解决方式是:Downgrade AWS SDK Android IoT SDK 到2.14.2,因为这个版本依赖的是org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0,该版本没有兼容旧版本API Level 23的问题。

4天前( 2020Jun19) AWS SDK Android已经发布2.16.13 fix了上述的问题,Issue是: https://github.com/aws-amplify/aws-sdk-android/pull/1572

正文

这篇文章我想表达的点是,可以通过降低版本去解决新版可能导致的兼容性问题。

但是在我遇到标题所示的问题时,我并没有马上降低AWS SDK的版本,我的解决过程是:

1. Google Search Android Studio console the build error:

关键信息是: Didn’t find class “javax.net.ssl.SNIHostName”

2. 定位到这个Github Issue

https://github.com/eclipse/paho.mqtt.java/issues/633

3. 看了上述很长的Issue之后,发现是 org.eclipse.paho:org.eclipse.paho.client.mqttv3 的版本1.2.2的有Bug

但是1.2.3已经解决,而且release了。

https://github.com/eclipse/paho.mqtt.java/milestone/9?closed=1

4. 发现AWS SDK Android 2.16.12 Gradle 依赖的正是 1.2.2版本😂

SDK 2.16.12 的发布时间是11 Apr。其实只要AWS SDK更新一下 mqtt的版本到1.2.3即可解决问题。

https://github.com/aws-amplify/aws-sdk-android/blob/release_v2.16.12/aws-android-sdk-iot/build.gradle

5. 我当时想到的解决问题的方式

5.1 给AWS SDK 提交PR去更新mqtt的版本到1.2.3,然后等待review release。

5.2 自己把AWS SDK 源码下来了,然后本地修改,本地导入修改过的module。

我用的是5.2的方式,aws-android-sdk-iot只是整个sdk的一个gradle subproject。

这个类库的 gradle 依赖链是: aws-android-sdk-iot => aws-android-sdk-core & aws-android-sdk-testutils

也就是说,为了能修改aws-android-sdk-iot的gradle依赖,我要把相关类库的源码都放在自己的代码库里面,这个方式有点 overkill了。最后我还是放弃了这个方法。

我是如何想到Downgrade的?

问题没有解决,我就下班了。下班后一直还在想着怎么fix,洗澡过程中突然灵感乍现,为何不downgrade呢?

于是开电脑,检查AWS SDK android IoT Git file changes history找到了一个合适的版本后,修改build.gradle,sync gradle之后 build app 马上成功。👍

API服务监听 0.0.0.0 & 如何给本地localhost API使用HTTPS (API Gateway, SSH远程端口转发)

TL;DR

API 服务监听 0.0.0.0 IP地址,既可以被同一个局域网的主机通过改API主机的IP访问。

正文

后端开发中,各种API服务(比如,Django, Flask, Express.js)启动的时候,一般默认监听的都是127.0.0.1地址。

如果要使用DHCP分配的IP地址访问,可以把host改为0.0.0.0再启动API服务。这样同局域网的主机,比如说前端的同事就可以通过你的IP地址访问后端API了。

0.0.0.0的意思是分配给改主机的所有可能的IPv4地址。IPv6是::/0

可能分配给主机的IP地址是未知的,但是你启动API的时候是需要绑定一个IP地址的,如果你没有分配到IP地址,这个时候127.0.0.1就有用了,或者localhost也可以。

如果启动API的时候绑定不是你主机的IP会怎样?

你应该会有绑定失败,Django会抛出这个错误: Error: That IP address can't be assigned to.

所以,0.0.0.0就是有点类似通配符的效果。不需要指定Hard code 未知的地址。

竟然0.0.0.0这么方便,那些API框架怎么默认不使用?

我觉得是为了安全,0.0.0.0会直接让你服务器可能直接暴露在公网里面。一般开发过程中都是没有什么认证措施的,这个时候附近有个人端口扫描一下,可能就会泄露重要的资料。


为什么有了这个文章?

近期空闲时间在写Side project,需要暴露自己的API服务在公网,给一个SaaS服务回调。

当然我可以用ngrok,但是对比于4年前,这个服务现在越来越抠门。以前是可以单机多开,而且不限时长,现在玩了付费订阅制之后,限制越来越多。

我的解决方案是:

  1. API服务监听0.0.0.0端口,给路由器访问。
  2. 路由器做端口转发,让宽带公网IP的某个端口映射到本机API服务端口。
    1. 为什么不使用路由器DMZ?
    2. DMZ会直接暴露本机到公网。只使用端口转发所需的服务,这样安全点。

最后我把这个地址放到SaaS,即可访问http://58.123.123.123:8000/

延伸一下,如何给SaaS使用上HTTPS,而不是直接用本机公网地址?

方案一 AWS API Gateway Proxy :

SaaS Callback URL => AWS API Gateway => Public IP => Router Port forwarding => API (Listen 0.0.0.0)

  1. AWS API Gateway会给你分配一个HTTPS地址,你需要创建一个Stage才会有。
  2. 直接创建一个Resource,然后把所有路由都转发到本机的公网IP,比如上述的http://58.123.123.123:8000/
  3. 如果你不想使用AWS API Gateway分配的域名,你可以使用AWS ACM认证域名,然后在API Gateway创建一个漂亮的custom domain。
  4. 重要的是,上述的1 2 3都是免费的。如果超出了AWS free tier期限,AWS API Gateway是按需付费,零星的使用量基本上是免费的。
  5. ⚠️这个不是全程加密,API Gateway到本机的API服务之间的通信没有使用HTTPS。

方案二 本站Apache反向代理 + SSH远程转发(这个是我写这篇文章的时候想到的方法😂):

SaaS Callback URL => This WordPress Site Apache Reverse Proxy => SSH Remote forwarding => API (Listen on localhost)

  1. ✅ 这个是全程加密通信。
    1. SaaS到Apache是HTTPS,我还启用了Http2.0
    2. Apache到我本机的API服务是SSH。
  2. Apache的反向代理可以使用二级域名,然后创建一个VirtualHost。
  3. SSH使用的是远程转发。因为要远程主机访问本机服务,本机没有公网IP。

Apache VirtualHost代码:

<VirtualHost _default_:443>
  # 一个新的二级域名,记得配置DNS,还有SSL/TLS证书
  ServerName xx.adamliu.net
  SSLEngine on
  SSLCertificateFile "/opt/bitnami/apache2/conf/adamliu.net.crt"
  SSLCertificateKeyFile "/opt/bitnami/apache2/conf/adamliu.net.key"
  Protocols h2 h2c http/1.1

  ProxyPass "/"  "http://localhost:8000/"
  # 这句是让3xx之类的重定向地址可以被Apache捕获然后修改为apache的对外URL
  ProxyPassReverse "/"  "http://localhost:8000/"

  # Error Documents
  ErrorDocument 503 /503.html

</VirtualHost>

SSH远程端口转发:

# ServerAliveInterval和ServerAliveCountMax是为了保持session常驻
ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=5  -R localhost:8000:localhost:8000 ubuntu@18.123.123.123