SNI 问题及探究

最近 Android 一直报一个错误,错误显示为:

Hostname in certificate didn’t match

1
2
javax.net.ssl.SSLException: hostname in certificate didn't match: 
<hs.hjf.cn> != <*.b0.upaiyun.com> OR <*.b0.upaiyun.com>

简言之,就是你请求服务器中的证书集中未包含当前域名的证书。

初判断

吐槽下,国内的 CDN 全是问题,写出来都是一本厚厚的书了,出这个问题时,刚好所有端都碰上 CDN 问题,所以先是从这方面着手,随着时间演进,慢慢的成了问题只出现在 Android 上。

才开始想着从 Android 上找问题,后来证实是 Android 上用的一个 Network Library 不支持 SNI 所导致。

SNI 的来历

Server Name Indication (SNI) is an extension to the TLS computer networking protocol[1] by which a client indicates which hostname it is attempting to connect to at the start of the handshaking process.

简单来说,就是如果一台机器上部署多个 HTTPS 站点,对应多张证书,应用服务器在响应时,无法确定用哪一张证书来对数据进行签名。为解决这个问题就提出了 SNI,即在 Client Hello 时,告诉用哪一张证书。

这个协议于 2004 年提出,2006 年被 OpenSSL 所加入,次年发布。

支持

目前 SNI 几乎被所有的软件和库所支持。

- Desktop Browsers

  • Internet Explorer 7 and later
  • Firefox 2
  • Opera 8 with TLS 1.1 enabled
  • Google Chrome: Chrome 6 and later
  • Safari 2.1 and later (requires OS X 10.5.6 and later or Windows Vista and later).

- Mobile Browsers

  • Mobile Safari for iOS 4.0
  • Android 3.0 (Honeycomb) and later
  • Windows Phone 7

- Libraries

这个具体就不列了,可以具体去查。提一下本次 Android 上的库吧,5.0 之前,Android 上没有自己实现网络库,用的是 Apache HttpClient,项目中用的是 async-http,另一套封装。

android-async-http

从 Issue 和源码中确实没有关于 SNI 的实现,建议使用这个库的项目尽快升级。

okhttp

okhttp 是现在 Android 上的主流,系统的 HttpURLConnection 使用不是很高。不过可以看到它也是 2014 年,才支持 SNI 的。

解决方案

除了让服务器默认使用这张证书进行签名,还能做的就是让客户端禁止 HTTPS 校验。

这样的坏处就是任何证书签名的内容就都是被信任的,数据依然存在被修改的可能性,HTTPS 的意义也就不在了,可以说花了精力接入了个假的 HTTPS。

SNI 调试方法

SNI 发生在 OSI 模型中的第六层,在 TLSV2 中。可以通过 Wireshark 这样的抓包工具来进行分析。

如果想验证这个过程,可以通过 openssl 命令来进行:

1
$ openssl s_client -servername hs.hjf.cn -tlsextdebug -msg -connect hs.hjf.cn:443

安全原因,这里的域名是假的,如果需要真实数据,可自己配置域名

1
2
3
4
5
6
7
---
Certificate chain
0 s:/C=CN/ST=shanghai/L=shanghai/O=Shanghai xxx Communications Co.,Ltd/OU=Administration Department/CN=*.hjf.cn
i:/C=US/O=GeoTrust Inc./CN=GeoTrust SSL CA - G3
1 s:/C=US/O=GeoTrust Inc./CN=GeoTrust SSL CA - G3
i:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
---

如果取消 servername 这个参数,会发现服务端用了一张默认的证书来进行签发了。这张证书上所包含的域名是 b0.upaiyun.com,这是又拍云提供的 CDN 服务。

1
2
3
4
5
6
7
8
9
---
Certificate chain
0 s:/C=CN/ST=zhejiang/L=hangzhou/O=Hangzhou Weiju Network Ltd./OU=\xE6\x8A\x80\xE6\x9C\xAF\xE9\x83\xA8/CN=*.b0.upaiyun.com
i:/C=US/O=GeoTrust Inc./CN=GeoTrust SSL CA - G3
1 s:/C=US/O=GeoTrust Inc./CN=GeoTrust SSL CA - G3
i:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
2 s:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
i:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
---

不平均进步的团队

起源

昨天团队发生的一件交互让开发调整细节的小事,让我看到了一些问题。

首先,种种原因,这个项目是由一个资深的开发来写的。和这个资深的开发相配的,也是一个多年的设计。

交互不停的让开发调整细节,差不多就是那种字体颜色啊,边距啊,有时还会牵涉到一些无关痛痒的交互行为。

我竟然有点为这个开发感到伤感,原因是这样工作太耗费精力,并且不会有任何能力的提升,觉得对不住。但由此也在思考什么样是一个好的团队。

现代软件开发的结

现代软件开发,关键点是软件越来越复杂,人也会越来越多,所有的流程,方法,都是围绕着这个话题进行的。

组件是一个很大的成果,将复杂的东西包括其中,对外提供简单可用的接口,从而将开始周期大幅度缩小,并大大降低复杂度。

现实中大多数时间也都是组件的拼装,不会从零开始造轮子。

在这家公司里,这多年的耕耘,在技术的标准之下,这些组件已经很完备了。但问题是,围绕在这些技术人员的标准周边,并没有形成产品、设计对这些标准的认知。

技术团队中的产品、交互和设计

一个技术公司的产品、设计和交互如果不能编码这个是没问题的,但如果都长年不对技术的一些知识有了解,这个就可悲了。

如果他们知道开发的一些模式,一些知识。就可以在设计时,去和技术一起,想办法在产品、设计时,也遵循一些技术的模式。

比如这个场景中,设计师在设计时,考虑到已有的组件,尽量的去复用。可不是每一次的随意一笔,这一笔自己设计只要一小时,而带给开发的可能需要不只一天,但设计师没有这个成本的概念。

技术认知造成的成本的不对等性

刚提到了一点,就是因为不了解技术,就造成认知的困难,这其中就是时间和成本。

产品可能一分钟就能想到一个方案,设计要花一天来补全这其中的细节,但对于开发来说,可能就是几个人的一周。

抛开业务的模式不说,从技术上,如果有

这其中一个重大的原因就是成本的不对等,

从这个问题,又联想到另一件事,就是团体与进步的关系。

团体与进步

逻辑思维中有好多节是关于教育,阶层等进步相关的,最后的方法都是加入一个好的团体。

而本案就是一个很好的说明。如果在一个好的团体里,这些琐碎的小事,大家会同步进步,用一些合理的方法论来简化掉了。这样对于个人,则可以更多的发挥自己的能力,让自己在更高的点上,得以提升。

总结

这个解来自管理层,而中国式管理的人都不看技术,这和硅谷的公司很不一样。所以,也就是无解。只能靠团队中的这个成员自身的提升意愿。

但恰恰还是有很多人愿意去做技术型产品、设计和交互。

找到一个这样的组织,对个人的幸福度和能力的提高都很有必要。

计算机的语言和生态

世界上的计算机语言已经数不清了,回想自己这一路 VB > C# > Python > Ruby > Object C & Swift > JavaScript,这些都是吃饭的家伙。但有时细想起来,每什么会有那么多语言,是什么决定一个语言的兴衰的呢?

生态

一直在讲,语言不重要,语言之所以有力量,是因为它有一个生态。

先不说哪个语言只能解决特定的问题,大部分语言的设定都是通用的。都有基本的类型、网络、IO、安全、的几部分组成。一些模式,都是语言进化的事,并不影响语言的功能。不同的就也就生态。语言会影响着生态,生态会反作用于语言。

生态是什么概念,比如说 Web,它要有对应的库,对应的解决方案,对应的大量的初中高级文章,来更多的人来入门,扩大自己的影响。

那些语言们

- 微软系

不过话说,自我第一门语言就是 VB,这是我在生活的那个小地方能买到的语言类的书了。

从大学到毕业,一直是微软系的语言用来吃饭。C# 真的伴我走过了很长一段路。

- Web 2.0 - 打败的 Python 的 Ruby

后来在 MSN 时,觉定要学一门新的语言,那时是互联网发展的好时候,当时选的是 Python,原因是 douban 在中国的推广。

等学会了语法,开始上手项目时,就被难住了。当时的流行是 2.7,但学习时是 3.0,而且 Django 这类主流的框架都不支持 3.x。这就痛苦了,在 2.7 和 3.x 间纠结了好久,就放弃了。

是放弃了 Python,而 Ruby on Rails 简直就是万能的神,所有之前的痛都烟消云散,这,就是 Web 2.0。

这是真的,DHH 是一个很聪明的人,他们所有对 Web 的理解都浓缩在这个全栈的框架里,无论是数据库层,业务层,Web 层,都巧夺天工,有着 ASP.NET 的完备,又有着完整的优雅。

后来用 Rails 做了很多的小事情,也理解了生态的意义,特别有趣的是上海的 Ruby 社区,这是我到目前仍参加的唯一的一个社区,风气特别好。

- Web 3.0 之 JavaScript

JavaScript 还是那个 JavaScript,但环境已经不是那个环境了。

2.0 的时候很快就过去了,大量的小企业的起起落落,网站也被 APP 代替了。API 成了主体。

这时 Rails 这种全栈就显的太重了,它的渲染流程太长,而且 Ruby 的性能问题依旧。

Node 这颗新星冉冉升起,有着 V8 的性能,语法被 ES5, ES6重定义,未来有 Promise, Async 的支持。而且 JS 本来就是前端的语言。

于是很多公司组成大前端,将业务 API 层划分到前端。

对应 Rails 的冗长,JS 反而是轻量的,适合 API 的。

于是 Node 取代了 Ruby,成了主要语言。

- Apple 的 OC 和 Swift

在微软离职后,转行做了 iOS,语言也自然换成了 OC,第一次写代码像写作。

后来就是 Swift,上了这个贼船,从 1.0,2.x,3.x,到目前的 4。一路艰辛。

不过 Swift 和 OC 不同,它是一个开源的语言,应用领域也会更多,比如在 Android, 后端都有人进行尝试。

- Java - 老企的最爱

Java 呢,有着大厂的持续投入,有着历史和标准,成为了企业的首选。

早年一直想用 Java,觉得它和 Linux 一样,是自由的象征,后来的 Java 已经是另一个天地了。

- 运维和科学的专属 - Python

Python 比较有意思,它是 Linux 内必带语言,每一个发行版本都会有这个,所以,它就成了运维的首选。毕竟和 Shell 比,Python 还是好用太多的。

除了运维,Python 还在科学领域一支独秀。就是这些初先的使用者,在主导着这个语言的方向。

于是 Python 也成为了 AI 的首先语言。

关于 Python 为什么成为科学的语言呢。
Python 社区早年投入精力开发了Numeric,使其像 Matlab 那样自然地支持数值分析。Python在类Matlab的数组操作和绘图功能上的支持,是它比 Perl 和 Ruby 更受到科学青睐的主要原因。

- golang 云计算的新兴

Golang 已经在 云计算领域,有了发展。这得益于它的友好的多线程。

并且有着像 Docker 一类的旗舰级应用。

其它

Java 有学过完整的语法体系和模式到工程,Go 就是兴趣看看。

像其它的 Scala 等,更是没有接触过了。就不做讨论了。

应不应该等 LTS

为什么要用 LTS

https://stackoverflow.com/questions/33661274/what-are-the-differences-between-long-term-support-lts-and-stable-versions-of

要理解差异,您需要了解为什么长期支持(LTS)版本的节点存在。

节点LTS主要面向企业使用,可能会有更多的抵抗频繁更新,广泛的采购程序和冗长的测试和质量要求。

来自Rod Vagg的Node LTS工作组成员:

为节点建立LTS计划的重点是建立在现有的稳定发布周期之上,通过在可预测的时间表上提供新的新版本,该计划具有明确定义的扩展支持生命周期。虽然这可能与“早期发布,经常发布”的开源传统似乎是不一致的,但它是企业应用开发和运营团队的基本要求。它也影响到为Node.js提供专业支持的公司。
https://medium.com/@nodesource/essential-steps-long-term-support-for-node-js-8ecf7514dbd#.za353bn08

在其中一个评论中提到的官方Node文章也做了很好的解释:

https://nodejs.org/en/blog/community/node-v5/

有时会误解奇怪的版本号码版本(v5,v7)是下一个LTS版本的“beta”。这是在其他项目中使用的惯例,但不是在Node中使用,有点误导。事实上,下一个LTS版本是从当前版本的特定点发行版中选择的,通常是一个偶数版本版本(v6,v8)。这不是最新的版本是贝塔 - 这是LTS发布满足一些组织具体的需求。

有关参考资料,请参阅Firefox的扩展支持版本(https://www.mozilla.org/en-US/firefox/organizations/faq/)或Ubuntu的LTS版本(https://wiki.ubuntu.com/LTS) 。我不认为任何人会建议最新版本的Firefox是beta版,大多数用户应该坚持使用较旧的ERS版本。

一般来说,如果你能够跟上最新的稳定和未来的节点版本,你应该这样做。这些都是稳定和生产就绪的版本,具有出色的社区支持。不稳定和实验功能保留在构建和运行时标志之后,不应影响您的日常操作。

一般来说,我总是倾向于最新版本的功能和性能(正如我现在分配的ES6 / ES7)
这将给我建议,稳定版本和更高版本将适合您的需求。这将为您提供访问底层JavaScript引擎(V8或Chakra)提供的最新和最大的语言功能

值得注意的是,v4(和更早版本)和v5之间的显着差异在于v4配有npm v2,而v5则带有npm v3。 v3的npm对于您的项目如何处理peerDependencies有一些潜在的突破性变化。 npm v3现在尝试安装一个依赖的自己的依赖关系尽可能平坦,并避免重复可能会影响您的项目,但对于Windows用户来说,这是一个很大的福音,因此这也可能会影响您的决策。

webpack build in think

项目的发布

项目现在项目有

  • react
  • react-router
  • ant.design
  • third antd component
  • app component

App

在之前的项目中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
framework_and_overrides.css.scss

// import the CSS framework
@import "bootstrap";
@import "jasny-bootstrap";

// make all images responsive by default
//img {
// @extend .img-responsijve;
// margin: 0 auto;
//}

// override for the 'Home' navigation link
.navbar-brand {
font-size: inherit;
}

在后面的配置:

1
2
Rails.application.config.assets.precompile += %w( framework_and_overrides.css app_mobile.css app_admin.css app_error.css )
Rails.application.config.assets.precompile += %w( app_web.js app_mobile.js app_admin.js )

在前端的引用:

1
= stylesheet_link_tag 'app_web', media: 'all', 'data-turbolinks-track' => true

但 React.js 改变了这一些。

JS/CSS/HTML 单独的情况被改变了。它变成了一个合成的包了。叫 component。

所以,理论上 React 打包,每个包是

react
react-router
redux

JS only

ant.design
基于 redux 的一些组件库

JS & css

APP 中包含 css/jsx。

Js css

所以,可以打成3个包。

How We Build the Complex Web

这个标题被翻译成当下的话,就是当我们在构建大型 Web App 时,我们应该怎么做?


- 选一套好用的框架,最佳实践(技术框架)

和配图一致,当今世界已三分天下,分别是

选择哪一套都没有明显的优劣,都能完成一些特定的任务。无论是社区、生态和各自的语法都已经比较完善,余下怎么选,就看个人和团队的偏爱了。

我们的选择是 React,原因有以下:

  • Component 的理念比较能接受
  • 有着强大的社区和背后的 Facebook,这是信心的保障
  • Ant.Design 等组件库的支持。

但要说明的 React 并不是一个构建前端的框架,它只是一个视图的库,如果想构建大型的项目,依然需要一个框架来支持,像 Flux 模式的轻量实现 Redux。

  • React
  • ReactRouter v4
  • Redux

- 选一套好的 UI 组件库

一个复杂的前端系统,是需要一整套完整且统一风格的组件库的。这个组件库会大大降低整个项目的复杂度,并带来统一的风格和交互体验。

UI 组件库一般都会提供布局 Layout (Grid/Flexbox),浏览器兼容,常用组件库等功能。

当然从 bootstrap 这个伟大的产品开始,业界在这方面的努力就没有停止过。目前主流的有:

BBootstarp 和 Bulma 只是传统的 JS 和 CSS 的库,但它们在每个平台上都有对应的封装,对应 React 的:

Ant.Design 不一样的是,它就是为了 React 而生,而且由蚂蚁金服提供,相比前两个的 React 版本,这个更能获得信任。


- 选一套好用的方案, 最佳实践(流程)

项目结构

构建流程

一般,我们的开发都会包括对应的多套环境,像:

  • Dev
  • Test
  • Production

在开发环境时,需要启动。在其它环境时,还需要进行构建,发布的操作。

  • Debug
  • Build

对应这些需求,常见的工具则表现为:

  • Glup
  • ⭐️ Webpack

Webpack 2 * js css, html, image, import, using as name.
Glup


- 还有最后一点。有好的案例参考为佳。

Bootstrap

  • Rubix,这是一套收费的组件库,口碑不错。

Ant Design

  • 也有 antd - Admin,这是个人开源的,仅供参考用。

REF::


这里的参考来自我的同事,从刚开始使用 React + antd,到现在完能一些大的项目,她个人对这个组合已经是完全的推荐了。

标准与创新

昨天和一个做 HR 的朋友聊天,谈到了一件事,就是互联网公司的管理的混乱。我也谈了下自己的看法,互联网是个新兴的行业,不像传统企业,一种模式都运行了上百年,有着成熟的管理方案,像麦肯锡,精益管理,Six Sigma 这些耳熟能详的经典。而新兴的行业总是带有着一些创新性的,特别是在这个技术和文化大爆炸的时代,对传统更是提出了挑战。而我个人,是喜欢这种创新的,因为只有在创新中,才能有更多的机遇,有更好玩的事,去思考,去探索。

回到技术上,也有这样的情景。

Native & HTML

一个是创新,一个是标准。

原生就是一座城,HTML 不停的突围,当年 Broswer 打败了原生,成了一时主流,也成就了 Web 2.0 时代的辉煌,但迅速的在 Mobile 的战场又败北给 Native App,时过境迁,随着 Cordova 等一批先驱的尝试,在 Native 世界中,HTML 又一次被带上热点,Hybrid App, React Native 等新一批的统一标准又再一次登上舞台。

回首 2017,Native 的招聘热度骤降,也能深刻的体现这一点。

这不就是标准与创新吗?

  • HTML 是多个平台,多个厂商,利益集团的标准,一个新的事物总是要反复的讨论,才能形成标准。
  • 而 Native 则通常是一个平台,一个厂商的产物,可以方便的添加新事物,也就容易产生创新。

标准永远是跟随着创新走的,除非创新变慢了、稳定了,形成了标准。

所以,回过头再看移动领域的 HTML 标准 之所以有一些突破,就是移动端的创新也在变慢的反映。

那当下的创新是什么呢?2017,当然是智能穿戴硬件,AR、VR,不信你看,这些依然是各家纷争,没有标准的,但预见不久也一定会有标准产生的。

规律就是这样

所以,作为开发者,是困惑,是抱怨,还是积极面对?

  • 如果你还在抱怨原生不行了,那一定是你太固化了,在一个老旧的领域没有去思考,去创新。

  • 如果你在抱怨新的东西不断在更新,学起来太累,那一定是你处于一个好的领域,在成长,你可以去面对它,也可以逃避。

  • 也或许,你好久没有感受过新的东西,那可能是处于行业领域周期的中、尾端了,可以考虑抬头看一下这个飞奔的世界。

REF::


How I Do Node Deploy

Docker Docker Docker

  • 不需要在服务器上做太多的事
  • node 的版本,global 库的版本,都可以自己控制。
  • 安全

## Docker base image

base image 就像设计模式的模板模式,好处是,给你一个标准的流程,且最大限度的帮你完成一些重复的操作,同时,也限定了这个容器的操作范围,不会让你出错。

于是我们就做了一个

这个 base image onbuild 做了以下几件事情

  • 限定工作目录 /app/
  • 安装指定版本 pm2
  • 复制 package.json 和 yarn.lock,并进行 production 的安装。
  • 复制项目到 /app
  • 导出端口 80 443
  • 指定启动脚本 pm2-docker(脚本参数可改)

## boilerplate

https://github.com/lanvige/koa2-boilerplate

对应这个 image 还有一个完整个 node 项目,是基于 koa2 的。

项目的目录结构:

1
2
3
4
5
6
7
8
9
10
.
├── app
├── bin
├── config
├── lib
├── docker-compose.yml
├── Dockerfile
├── package.json
├── process.yml
└── yarn.lock

dockerfile

其中 dockerfile 最简单,因为 base image 中做了全部的事情,这里只需要一个空的设置就可以了。

因为是 base image 是 onbuild 的,所以还是需要一个这个的文件。

docker-compose.yml

compose 文件,是构建 docker image 和 启动的一些设置。像容器名字,端口绑定,环境设置

process.yml

process.yml 这是 pm2 启动的配置。


## 使用
  • yarn build,将代码进行编译,生成目录 dist
  • app config build,创建使用环境的 config 到 dist/config 目录
  • docker-compose up -d, 在 dist 目录下,运行 docker compose 命令构建镜像并启动。

简单到无聊~


## 线上发布

理论上好的流程是,一台 docker build server 负责制作 image,然后推到 registry,线上服务器拉取镜像,再启动。

但实际中,这个成本有点高,首先需要一台 build server,其实,镜像很大,占流量。

于是,在个人项目中,直接使用 shipit 来进行发布,在本地做项目的构建,然后,将 dist 的代码同步到服务器,服务器直接本机构建 docker image,然后启动。

通过 shipit-deploy,可以方便的完成 前面的代码构建,同步到服务器上的操作。
通过 shipit remote 的功能,也很方便的扩展 docker 的一些命令。


## 演进

NODE_ENV: 之前将 node_env 放在了 Node 构建时进行设置,后来发现,这样可能就导致一个容器只能在一种环境下启动,想修改,必须在启动后,通过 exec 进入容器后才能修改。

于是将这个配置向后移,放到了 pm2 的配置中。

这样,在启动容器时,可以通过修改 docker cmd 来进行配置:

1
command: start --env production /app/process.yml

## REF:: ---
  • [none]

Node 包管理的一些心得

自从 node 变成了最主要的语言,用的多了,经验也会多一些。

node 的包管理一直是一个很烦的事情,它没有一个本地的中央仓库,所有的依赖都是项目级别的,更早之前还是以依赖进行嵌套放置的,导致大量重复的安装。

使用 nvm

node 的版本变化很快,特别是一些新的特性,一些特性在新的版本中可能就不需要使用 babel 来配置了,所以经常要切换版本。

同事就发生过一直在 5.x 下进行开发,结果当 node 换成了 6.x 或以上版本时,整个项目都无法编译,导致无法上线。

经常的将自己的代码放到新的版本里去测试,也是一个必要的事。

nvm 是最好的助手。

  • nvm install (v) - 安装一个新的版本
  • nvm use (v) - 临时切换一个版本,关闭 terminal 后失效
  • nvm alias default (v) - 设置系统默认版本

第一原则是推荐 yarn

它解决了:

  • 本地代理+缓存,这样,最少解决了速度问题,不需要每次安装都从服务器上去接,这样太慢,效率大大提升。
  • 使用多线程去下载,更快。
  • 更主要的是版本锁定,这样,就避免了因版本不一致而出现的各种问题。

我们已经在生产环境使用上了

开发环境的 global bin 迁移问题

nvm 虽然解决了 node 的版本问题,但每次升级都要对 global 的命令进行迁移,也是一件很烦的事情。

yarn 很好的解决了这个问题,在 macOS 上, yarn 的配置和文件目录被放置了在 ~/.config/yarn 下,global 的目录则对应在 ~/.config/yarn/global,这样,就和 nvm 脱离了关系。每次升级不再有 global 迁移的苦恼。

发布

发布时,要带上两个文件

  • pakcage.json
  • yarn.lock

Docker image build

如果使用 docker 时,一定要把这两个文件先进行复制,然后安装其依赖。再复制项目。

包依赖的变化不是特别频繁,这样可以重复的使用已有的 docker image layer,节约空间,提升效率。