诡异的iOS keep-alive bug

https://bugs.webkit.org/show_bug.cgi?id=155632

去年12月发现,偶发性,各种Charles抓包、rvictl流量复制等方式来定位分析,还恶补了TCP协议,都没找到原因,只是看上去觉得iOS这边处理HTTP请求的方式怪怪的。

终于尘埃落定,服务端根据UA识别出iOS,关掉HTTP的Keep-Alive功能来规避。

keep-alive是HTTP协议里面的

keepalive是TCP协议里面的

HTTP协议通过头部的 connection: keep-alive 来通知两端TCP建立一个keepalive链接,在keepalive有效期内,不需要重复3次握手动作,不需要重新慢启动

 

用PhantomJS来给AJAX站点做SEO优化

腾讯问卷所有动态内容,全部由Ajax接口提供。

众所周知,大部分的搜索引擎爬虫都不会执行JS,也就是说,如果页面内容由Ajax返回的话,搜索引擎是爬取不到部分内容的,也就无从做SEO了。

先来看看效果

QQ20160321-1

去年一整年,搜索引擎收录都少得可怜。

更致命的是,被收录的页面,其搜索引擎里面显示的标题是最原始的html标题,权重如此高的地方,却被收录了一个没什么用的标题。

在去年年底完成实施了预渲染服务后,收录量蹭蹭蹭的起来了,并且收录的标题也都全部正常了。

而这所有的一切,除了Nginx接入层的配置是需要改动业务代码外,其他全部都是旁路机制。也就是说,自己做一套,可以给所有同类型业务共用,同时不会影响现有业务的任何代码任何流程。

继续阅读“用PhantomJS来给AJAX站点做SEO优化”

mac命令代理

内网装个东西就是麻烦,临时给某个命令走代理可以通过如下配置实现

 

iOS抓包新姿势 – rvictl

最近遇到一个诡异的问题,出现在特殊网络情况下,一旦劫持抓包就无法重现,怎么破?

Google告诉我们,iOS有一个rvictl的神器。

rvictl总共就3个参数

  • -l 显示当前活跃设备
  • -s 开始监听
  • -x 结束监听

其原理是将iOS设备的流量,像打日志一样复制一份到Mac上,在Mac上再通过Wireshark就能进行分析。这种做法,不像代理,不会干扰iOS设备正常的网络访问。

步骤

1.将iPhone通过数据线,连上Mac,并打开iTunes,复制设备UDID。

2.打开Terminal,输入  rvictl -s 设备UDID

3.打开Wireshark,在捕获选项里面选择rvi0这个设备,这个时候,iPhone所有TCP和UDP流量,都会打印到Mac上。

4.在Wireshark里面输入合适的过滤器,便于追踪目标流量。

也来说说webpack

入门

webpack,官方定位是一个模块打包工具,基础命令极其简单

在CLI模式中,第一个参数是入口文件,第二个参数是输出文件,并读取当前cwd目录下面的webpack.config.js配置,根据配置生成对应的bundle.js文件。

其用法与RequireJS里面的r.js命令极其相似。

快速上手

如果一个新业务,想做一下JS的模块化管理,那么可以立即选择webpack了。

如果一个老业务,曾经用了RequireJS或者SeaJS,那么也可以选择切换webpack了。

如果想做一个库\框架去为生态提供服务,也可以立即选择webpack,他能自动配置最终生成的library.js文件支持AMD\CommonJS等模块化方案。

用好配置里面的resolve,改造一下原有的Grunt\Gulp流程,即可使用webpack,业务代码基本无需改造。

多种模块化打包加载方案对比:http://webpack.github.io/docs/comparison.html

其实对于老业务而已,仅仅将JS的模块化从RequireJS替换到webpack,其收益并不明显,仅仅是最后生成的JS文件要小一些而已。

进阶

如果单单从CLI模式中的提供的参数来看,webpack的能力也就到此为止了。但webpack的作者并非只想做一个AMD\CommonJS\ES6 Modules的协议实现。

webpack提供了一个Loader和Plugin的机制,让社区通过提交自己的Loader和Plugin,大大拓展了webpack的应用场景。

别忘了,webpack的REPL可是完整的nodejs,也就是说Grunt、Gulp能做的事情,webpack也能做(只是能做,不代表webpack擅长做)。

同时,通过各种Loader和Plugin,webpack还能打包样式、图片等资源文件,并按需将这些资源文件inline到html中。

与babel的勾搭

建议es-2015就先别折腾了,webpack本身编译速度,在我的MBP上面是50ms上下,但加入babel并使用es-2015语法转换后,编译耗时直接涨到700~800ms,这还仅仅是只有两个js文件的demo。

在webpack的roadmap里面,看到有对ES6 Modules进行支持的计划,我们还是静等吧。

欠成熟的Loader和Plugin列表

其最富有想象力、最能拓展的Loader和Plugin,她们的列表是竟然是人工维护的一份Github Pages。相对于其他社区来说,这块差了点。同时由于是手动维护的列表,其Loader和Plugin的质量,只能通过Github和npm中进行判断。

读书笔记《React-引领未来的用户界面开发框架》

React这么火,我们也来深入研究下吧。

买了本React相关的书籍,刚看了前十章,随手记一下读后感吧。

废话篇

这部分内容,人云亦云

垂直切分的组件

React对于配置多名前端开发的团队,在协作上有一定的优势。

其组件化思路,是一种垂直划分,每个组件高度自治。与我们习惯上的Html、JS、CSS三分离的水平划分思路不一样。

垂直划分,让每个组件自己决定自己的结构、行为、表现,调用方只需要拿来即可使用。使用JSX来定义组件结构,通过Sytle对象来inline样式属性。

这里有两个不爽的地方。

  • JSX语法太丑陋
  • style对象权重太高,外链样式难以做正常的样式覆盖

JSX语法问题,还好IDE能高亮,看上去稍微舒服点。内联style权重问题比较难解决,最近WebRebuild上萝卜分享过一个css_module的解决方案,挺暴力,但又十分有效。

枚举一切可变字段(state)

其实在Backbone的年代,已经有这样的东西,只是没有强调而已,且没有那么智能的去更新界面(Backbone需要手动监听字段变化,然后去执行对应的方法,一切都要手动)

在React的世界里面,一切的讨论都围绕着这些可枚举的state。为了能高效的实现刷新界面,大家都乐意去细化界面上每个可变元素,将其与组件的state映射起来(其实就是在JSX里面包个{this.state.something})

setState() => componentDidUpdate

由于所有可变因素(state)都被枚举出来了,且界面发生变化的原因有且仅有一个,这让React组件变得可预知性可测试性

大肚能容

ref、getDOMNode()、componentDidMount()、componentWillUnmount()让React有了兼容其他非React生态的JS库\组件的能力,这个还是挺赞的,对于已有业务,改造起来也稍微方便点。

高能篇

这部分脑洞比较大

论setter、getter的重要性

一个好框架\库,需要有一个统一的输入输出接口

在React里面,有一个很重要的概念,是一切改变,都必须通过setState()方法来传达。只有这样,所有的变化才是可控的、可预测的。

setter、getter这两个方法,其实在各种各样的库里面都有,但没有像React世界里面这么强调。

例如在某个中间环节,为了图快,时不时就出现直接修改原始对象属性的情况。这种变动,框架层面是没法侦测到的。

又例如读取某个带嵌套关系的对象,没用getter,一个不小心就把原始对象的引用给暴露的出来,然后极容易出现在某些边边角角发生引用被改动从而触发一些很隐晦的BUG。

论钩子的重要性

一个好框架\库,需要有丰富的外部钩子,便于拓展

WordPress占有率高吧,为啥?因为他易于定制、拓展,他有非常丰富完善的钩子机制来给各种主题、插件提供定制拓展能力。

React也有很多钩子,他强调的生命周期,其实就是一系列的钩子,给业务能非常容易的在想定制拓展的地方进行定制拓展。

Backbone有钩子吗?有,但少得可怜,没记错的话,Backbone.View默认只有initialize和render两个钩子,React组件单单存在期的钩子都比他多。

Marionette则弥补了Backbone.View在钩子上面的缺失,可惜太小众。

钩子要怎么做?简单来说就是在框架、库的生命周中埋下大量空函数供拓展的时候覆盖就好了。

未完待续

用madge来绘制项目的依赖图

https://www.npmjs.com/package/madge

今天早上刚发现,很犀利的一个模块依赖检查工具,并可以可视化的绘制出来,支持AMD\CommonJS\ES6等模块依赖。

安装

使用说明

绘制依赖图

用处

业务做优化、重构的时候,可以先跑一次依赖图,看看都有哪些边边角角藏着废弃代码。

业务做局部模块优化的时候,也可以跑一遍依赖图,看看修改的模块会导致哪些地方出现变动。

iOS9.1终于可以关闭讨厌的300ms延迟了

通过viewprot配置来关闭,FastClick快要完成历史使命了。

Fast-Tap on iOS

On Safari for iOS, the 350 ms wait time to detect a second tap has been removed to create a “fast-tap” response. This is enabled for pages that declare a viewport with either width=device-width or user-scalable=no. Authors can also opt in to fast-tap behavior on specific elements by using the CSS touch-action property, using the manipulation value.

See http://www.w3.org/TR/pointerevents/#the-touch-action-css-property.

 

完整特性更新列表

https://developer.apple.com/library/prerelease/mac/releasenotes/General/WhatsNewInSafari/Articles/Safari_9_1.html

NodeJS那些事

下半年做了挺多活动型需求,因为我们业务人力有限,我在业务的策略是不依赖NodeJS。

而这些活动型需求,是可以用NodeJS来练练手。

ExpressJS

一个Web服务框架,几经转手,现在应该是IBM旗下的产品了。

以前我们用PHP来开发Web服务,语言层面屏蔽了很多HTTP协议的东西,可以专心业务逻辑。

而NodeJS不同,本身就跑Web服务(不管前面是否加个Nginx反向代理),所以挺多HTTP协议的细节需要我们深入了解的。

ExpressJS通过大量中间件,来帮我们屏蔽掉这些HTTP协议的细节,例如body-parsercookie-parser,帮我们解析HTTP Body和Cookies部分的内容。

app.use([path],function(req, res, next){})

整个ExpressJS,最重要的部分就是app.use()。不论是中间件?还是我们常见的app.get()\app.post(),都是从app.use()衍生出来。

每一个请求到达ExpressJS后,其处理流程是按顺序进入各个app.use()传入的回调函数中。

如果该app.use()带有path参数,则匹配path参数才会执行该回调函数。

如果该app.use()的回调函数最后还调用了next方法,则这次请求的处理流程会继续流向下一个app.use()

正因为如此,每个回调函数,只有一次调用的机会,你要么用来处理req阶段,要么用来处理res阶段。(宣称是下一代Web框架的Koa,则是利用ES6里面的语法糖,实现了一个回调函数有多次执行的机会)。

难道ExpressJS就不能让回调函数既处理req又处理res吗?非也,app.get()\app.post()就能同时处理req和res,只是ExpressJS的把能同时处理req和res的称为路由(Routing),而只能处理其中一种的称为中间件(Middleware)。这样形成一个不成文的约定,用中间件来加工req,用路由来加工req和res。

其实中间件一次只能处理一个阶段是有好处的。HTTP有一个特性,是HTTP Header必须早于HTTP Body返回。如果中间件也用来处理res,就会有非常大几率出现res.send()早于res.header()而导致的故障。平常我们的精力关注在路由上,中间件触发的故障会比较难发现和定位。

winston

在ExpressJS官网的最佳实践里有提到日志这点,平时我们用的console.log()是一个同步的语法,开发阶段问题不大,但不适合生产环节,官方推荐winstonBunyan两个库,我这里用winston。

winston支持分级日志,自带info\warn\error三级

还可以传递自定义等级

我们可以在ExpressJS的最后一个app.use()里面,做一个兜底的异常处理回调。

这样就把我们没有预计到的异常,也兜底接住了,并记录在日志中便于回溯。

winston还有一个 winston.profile('name')的方法,用来记录两个点的时间间隔,可以做性能统计埋点。

Request & Promise

NPM依赖榜排行第七的库(不知道是第七还是第三),跑NodeJS服务经常能用到,用来调用第三方接口。

Request的具体用法和$.ajax()雷同。

由于Request是异步的,为了便于业务使用,最好用Promise对每个具体的API调用进行封装。NodeJS和IO.js合并后,已经完美支持Promise语法了。

Promise的语法这里不展开,直接说怎么封装Request。

业务调用的时候,就可以安心处理正常逻辑,异常已经被屏蔽了

你看,现在的业务逻辑就是渲染。而其他异常,则会被一路抛出,直到最后一个app.use()来做兜底异常处理。

child_process & PhantomJS

这里我要先先说说,NodeJS的到来,让我居然有机会学习进程\线程的编程。

这里贴一个ChildProces的官方栗子。

说完语法,说应用。

我们业务有两个地方用到了PhantomJS,PhantomJS支持CLI调用和Web服务两种方式,而其自身的Web服务是通过Mongoose实现的。

最初我们在使用PhantomJS的Web服务的时候,经常遇到其假死的状况。由于是假死,各种守护进程的策略没法实施(侦测不到进程的任何异常),最后同事采用暴力的定时kill后重启策略。

也许上面的不稳定,是我们PhantomJS的脚本写的有问题导致的,但不管怎么样,脚本问题导致服务不稳定是不能接受的,后来我们改用CLI的方式调用PhantomJS。

这里我们用ExpressJS替换了PhantomJS自带的Mongoose来实现Web服务,并每次通过子进程的方式唤起PhantomJS,而我们本身的ExpressJS由PM2来保障执行,这样就能彻底解决由于脚本质量导致的服务假死问题。

PM2

一个给NodeJS服务用的守护进程,支持API和静态配置,非常强大,我建议大部分用NodeJS跑持久化服务的地方都用PM2。

PM2有多种启动方式,通常情况,建议将PM2的启动配置静态化到一个pm2.json文件中,然后通过 pm2 start pm2.json来启动。

PM2支持将log重定向,这对于多硬盘\多分区的服务器是非常友好的,我们服务器的根目录容量就非常小,如稍加注意,就会被这些NodeJS的log给撑爆,需要重定向到大容量目录下。

原生OR模拟

我最近又陷入了模拟控件和原生控件的纠结中。

早期我们做PC端网页开发,就已经讨论过一次模拟控件(表单)和原生控件(表单),当初各种各样的理由,我们很推崇原生控件(标准化、语义化、渐进增强)

但是最近这两年接触Mobile端的网页开发后,我的立场摇摆了。

原生控件

优点:使用简单,”兼容性强”

使用简单很好理解,表单元素就那么几个,其DOM Event和DOM Attr也屈指可数,使用起来非常方便。

为什么要给兼容性强打上引号呢?因为单独使用,的确兼容性很强,Web标准。

缺点:“兼容性差”

为什么又是兼容性呢?主要体现在Mobile端的浏览器,对于可交互元素,尤其是表单元素,有很多特殊的处理。

FastClick好用吗?iScoll好用吗?都很好用,但为什么网上那么多负面的评论?

去仔细看内部的源码,里面由非常多可交互元素的hack,并且都留有issue单。就是因为不同浏览器、甚至同一款浏览器的不同版本,对这种可交互元素的处理机制不一,导致各种各样未知bug。而这种bug,只能case by case的修复。

这里我又想黑一下自己的公司,X5也有很多case by case的自认为的对可交互元素的处理机制优化。

模拟控件

优点:完美还原交互,兼容性强

设计还原能力,就不用说了,自己写的业务实现,可以完美实现各种交互效果(当然仅限于当前科技范畴了)

而兼容性,因为采用的是最基础的手段,所以满大街的设备都会支持,并且都当做普通的DIV\CSS\JS来处理,不会有什么特殊情况,也不需要case by case的去解决一些兼容问题。

缺点:不标准,复用性不高,开发成本大

这种控件针对性很强,也就导致了复用性不高。

因为不是标准控件,所以所有东西都需要自己实现,大量边界条件需要自己想清楚,开发成本很大。

结语

其实我也不知道该怎么结束这个话题、我也在纠结中~~~

WebComponent也好,以前的htc也罢,还有React Canvas,不也是为了解决这种问题嘛(看上去这三个好像差很远,尤其是React Canvas。但其实都一样,都是用自定义的东西去统一实现输入输出,从而避免浏览器的差异化处理)。

也许未来移动端浏览器对于可交互元素的处理趋向一致和稳定后,我们的想法又会变得不一样。

 

最后补一句,我纠结的问题,是移动端富交互的场景,普通的展示型页面,还是该怎么来怎么来,不受影响的。