之前有发生一个情况。就是我们有两个go程序服务A和B,其中A服务会调用B服务的http接口,而B服务是一个对外的长链接服务。两个服务在同一台服务器,其中在A服务的log中,发现在调用B服务接口的时候,会报这个错误:1
2[Error] [/data/code/go/src/stream-forward/phone_conn_pool.go 80] [2018-06-24 21:38:29]
[request phone(9a7b01445fd7ec85f122eadbdc07e084) create channel: Get https://xxx.airdroid.com:9088/create_channel?id=xxx&token=xxx&host=xx-bd.airdroid.com&port=9991: dial tcp xxx.xxx.xxx.xxx:9088: i/o timeout]
即请求B服务的接口出错了,而且这两个服务是在同一台服务器上的。后面直接在这一台机子上,试着curl 这个接口地址。发现也不行:1
2[kbz@ovm-orcobyj8 dataforwardsrv]$ curl 'https://xxx.airdroid.com:9088/create_channel?id=xxx&token=xxx&host=xxx-bd.airdroid.com&port=9991'
curl: (7) Failed connect to xxx.airdroid.com:9088; Connection timed out
golang 踩坑之 - https下开启http2会在Safari下报错(http2 stream closed)
之前项目的一个转发服务有发生过一个问题,就是在新版的Safari上(版本11),https 的链接会一直连不上。然后我看了一下log,发现一直在刷log,明显是Safari一直在重试请求,而且一直报这个错误:1
[id(12a0de8483dc83df60d2d0ac49118e57): io copy fail : http2: stream closed
这个错误就是http2的错误,但是为啥就新版的Safari会,旧版的Safari和chrome 和 Firefox都正常??
golang 踩坑之 - slice bounds out of range
之前有发生过一个现象,我们的一个go服务定时会出现重启的行为,时间间隔有可能是几个小时,也有可能是几天。 后面查看了一下supervisor的error log,发现:1
2
3
4
5
6
7
8
9panic: runtime error: slice bounds out of range
goroutine 7217 [running]:
panic(0x6f6a40, 0xc42000c0a0)
/usr/local/go1.7.6/src/runtime/panic.go:500 +0x1a1
main.phoneHandler(0x8b3ba0, 0xc4200ee088)
/data/code/go/src/stream-forward/forward_handlers.go:27 +0x902
created by main.phoneServe
/data/code/go/src/stream-forward/main.go:72 +0xeb
看情况好像是一个 切片数组越界的错误?? 我们定位到具体的代码:1
2
3
4buf := make([]byte, 512)
n := bytes.Index(buf, []byte{'\n'})
deviceId := string(buf[:n])
果然发现了一个 可能会导致slice切片越界的行为。如果 index 检索不到对应的字符,那么n就会为 -1,当 n为-1 的时候,就会报这个错误。 所以这边要改成:1
2
3
4
5
6n := bytes.Index(buf, []byte{'\n'})
// 这边如果返回-1的话,那么下面那个slice就会报一个数组越界的错误,导致程序退出
if n <= 0 {
return
}
deviceId := string(buf[:n])
这边只要提前判断就行了。
前端工具集(11) -- 实现短信输入区分多条
之前做过一个项目(web.airdroid.com的短信模块)是可以在web端的输入框输入短信内容,然后调用手机端的API来发送短信。这时候就需要做到跟手机端的短信app的一样的提示效果,即用户在输入的时候,要提示当前是否会拆分多条短信,还有就是当前这条短信的剩余输入字数。最后上线的部分效果图如下:
在实现之前,这边先科普以下,一条短信会有几个字符可以输入,还有就是什么情况下会需要拆分多条短信??
上世纪80年代无线传输的带宽不高,这就要求手机短信要言简意赅。短信发明人Hillebrand为遵从这一要求,经过试验后将160个字符作为短信长度上限,英文字母可发160个(参考文献)。
因为英文字母采用7位ASCII编码,而汉字则采用8位UCS-2编码并占2个字节,所以160个字符按照7位ASCII编码来换算,即160X7=1120位;而汉字是按照8位的UCS-2编码,即8位一个字符,一个汉字占2个字符,这样1120位换算成汉字数就是1120/8/2=70。如果换算成字节(byte)的话,那么一条短信就是 1120/8 = 140 个字节(一个字节8位(bit))
也就是说,如果是单条短信的话,如果只输入英文字母的话,那么可以输入160个字符,如果输入汉字的话,可以输入70个字符,如果是中英混输的话,就按照汉字来算(只要内容里面有一个汉字,那么整个内容的编码就全部按照汉字的编码来算)。
也就是说,当英文字母超过160个字符之后,这时候短信就会拆分成两条。 如果是汉字超过70个字符之后,也会拆成两条。
zoom 和 transform:scale的差别
之前在做 web.airdroid.com 截屏模块的时候, 有遇到一个问题。就是如果截屏全屏的时候,如果有些机子的屏幕图片的高度比浏览器当前的高度还大,就会出现左下角的AirDroid LOGO 消失不见。
截图如下:
使用input=file 上传的时候,选择同样的图片,第二次不会再触发onChange事件
之前在做一个项目的时候,有用到 input=file 上传, 并监听onchange事件。也就是当选择的文件有变化的时候,就会触发change事件。但是有一种,就是第二次选择的图片和第一次的图片一样。这时候,发现第二次选择之后,就不会再触发change事件了。
解决方法就是每次上传之后, 把这个 input 的 value 清空, 即 event.target.value = ‘’;但是这种方法在ie10下会有问题。所以更好的方法就是在input上面套一层form,然后使用 form.reset() 方法来清除。
比如这样:
前端工具集(10) -- 用js实现浏览器全屏 fullscreen
因为前段有做了一个功能是关于全屏显示图片和视频,因此这边稍微整理了一些关于在浏览器显示全屏的一些API。
HTML 5中的full screen 就是用来做全屏API的。不过还是会有一些浏览器兼容性问题。
可以看到IE下只有IE11才支持。而且其他支持的主流浏览器,也大部分都要使用前缀才行。主要有以下几个API:
前端工具集(9) -- 正则匹配文本中的链接并转化为A标签
前段时间在做pc内嵌页的时候,有遇到过一个需求,就是要把用户聊天记录中的链接匹配出来,并转化为A标签,这个就涉及到链接的正则匹配了。刚开始试着找了一下网上的正则匹配表达式,后面发现还是不全。
后面只好自己写了一个方法,总算可以满足大部分的情况,不过还是有一点不足的是,就是还是不能匹配ip地址的链接,刚开始有试着兼容了一下这个模式,后面发现要兼容这个东西的坑太多了,就算能兼容少部分的情况,但是很多情况下还是会有bug,再加上平时ip地址的链接还是比较少的,因此就先不兼容了。代码如下:
1 | function Linkify = function (text, opts) { |
调用的话:1
2
3
4var text = '我的网站 www.sample.com.欢迎光临。';
console.log(Linkify(text));
最后得到:
我的网站 <a class="linkable" href="http://www.sample.com" target="_blank">www.sample.com</a>.欢迎光临。
前端工具集(8) -- 兼容IE的脚本加载器
前段时间在做pc内嵌页的时候,有遇到过异步加载语言文件,并且因为有兼容到XP系统,即IE6内核。所以就写了一个兼容IE6文档模式打开的脚本加载器。
1 | /** |
调用的话:1
2
3
4var path = baseUrl + "lang/en.js";
LoadScript(path, function(){
console.log("load lang success");
});
ie8,ie9 使用 XDomainRequest 进行跨域
之前有写过一篇关于ajax的封装库:前端工具集(7) -- 原生js实现并扩展jquery的ajax功能 里面有提到如果是ie10以下的ajax请求,都是用ActiveXObject这个对象来进行异步请求的。而且只能进行同域下的异步请求。
ActiveXObject对象
ActiveXObject 这个对象是 ie5 才加进去的,也就是如果要在 ie5 及以上(ie5, ie6, ie7, ie8, ie9)要执行同域下的异步请求的话, 那么就用这个。
关于 XMLHttpRequest 对象需要注意的地方
这段时间重新理了一下ajax技术,并重新理了一下XMLHttpRequest 对象,发现还是有几个地方比较容易忘记或者是需要注意的几个地方。
Ajax和XMLHttpRequest不是同一个东西
Ajax不等同于XMLHttpRequest,细究起来它们两个是属于不同维度的2个概念。
ajax是一种技术方案。它依赖的是现有的CSS/HTML/Javascript,而其中最核心的依赖是浏览器提供的XMLHttpRequest对象,是这个对象使得浏览器可以发出HTTP请求与接收HTTP响应。
AJAX stands for Asynchronous JavaScript and XML. AJAX is a new technique for creating better, faster, and more interactive web applications with the help of XML, HTML, CSS, and Java Script.
用一句话来总结两者的关系:我们使用XMLHttpRequest对象来发送一个Ajax请求。
使用 git cherry-pick 来合并某一个分支
之前在做一个项目的4.0改版的时候,从项目中的dev分支拉了一个分支4.x作为新版4.x的分支,然后在这 4.x 分支做了很多修改,包括很多图片替换,修改了很多bug。其中发现有些bug的fix,其实也需要合并到dev去。这时候就麻烦了,因为不能直接把4.x的内容直接merge 到 dev 分支。因为会把之前4.x分支的很多修改都合并进去。这不是我们想要的,我们想要的只是要把当前4.x分支的这个bug fix 的commit 提交合并到 dev 分支,而其他的commit不用。这时候就要用 git 的 cherry-pick 来处理。
举个例子: 比如我们在 4.x 的分支上,对Base.js 进行了修改。
前端工具集(7) -- 原生js实现并扩展jquery的ajax功能
之前在做手机端页面的时候,为了减少体积,舍弃了jquery和zepto, 自己用原生写了一个ajax库,不仅可以实现jquery库的ajax功能,而且还进行了扩展,主要有以下功能:
- 支持ajax跨域请求
- 支持jsonp跨域请求
- 支持异步加载js文件
- 支持deferred用法或者直接传入回调参数的用法
- 支持同步调用
- 支持ajax请求失败的加载重试次数设置
- 兼容ie6-ie9 的异步同域请求,因为有兼容 window.ActiveXObject 对象
不支持的功能:
- xhr对象的上传和下载,这个是库没有,因为做这个库的初衷就是更好的加载js资源或者进行接口请求操作。 如果是要专门针对ajax上传和下载文件的,那么应该要重新封装一个库。
- 不支持ie8,ie9的跨域请求,因为没有封装XDomainRequest对象,可以参考这一篇: ie8,ie9 使用 XDomainRequest 进行跨域,至于ie8以下的异步跨域请求,那就不能用ajax的方式了,得用其他的方式,比如如果是get的话,那么就可以用jsonp来处理,可以参照这个:前端工具集(1) -- jsonp原生实现
- ie 10 开始支持xhr对象,不过没有withCredentials这个属性,所以如果是ie10的话,并且需要支持cookie跨域的话,那么就需要将cookie的内容读取出来,然后放到参数里面,最后由服务端来进行读取。
具体代码如下:
IE 6, IE 7 调试神器 firebug lite
之前在做一个pc端内嵌页项目的时候,因为要兼容XP系统,而XP系统的webview内核是 ie6,所以在调试IE 6, IE 7 的时候,发现调试非常难调,尤其是webview调试,根本没有所谓的调试窗口,后面发现有一个神器,就是firebug lite版,可以让你轻松的调试ie。
其实就是在html标签后面嵌入一段js:1
2
3
4
5
6
7
8
9
10
11
12
13<script type="text/javascript">
javascript:(function(F,i,r,e,b,u,g,L,I,T,E){
if(F.getElementById(b))return;
E=F[i+'NS']&&F.documentElement.namespaceURI;
E=E?F[i+'NS'](E,'script'):F[i]('script');
E[r]('id',b);
E[r]('src',I+g+T);
E[r](b,u);
(F[e]('head')[0]||F[e]('body')[0]).appendChild(E);
E=new Image;
E[r]('src',I+L);
})(document,'createElement','setAttribute','getElementsByTagName','FirebugLite','4','firebug-lite.js','releases/lite/latest/skin/xp/sprite.png','https://getfirebug.com/','#startOpened');
</script>
只要放在 html 闭合标签的后面,让其加载,然后就可以出现调试窗口框了
nginx 配置gzip以优化站点资源加载速度
将官网的一些静态资源,比如 js ,css, 图片 设置成 gzip 压缩的方式, 可以提高静态资源的加载速度
因为我们用的是nginx,所以可以通过nginx来配置,直接在nginx.conf添加一下代码:1
2
3
4
5
6
7
8
9
10# Gzip Settings
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_min_length 1000;
gzip_comp_level 4;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
实现效果的截图如下:
pc内嵌页取消IE "已限制此网页运行可以访问计算机的脚本" 的提示
之前有个项目是给pc端的桌面应用做内嵌页,也就是说这个项目的页面不是线上的页面,而是打包在pc端的安装包里面,而且pc端还没有开webserver来显示页面,而是直接用文档模式来打开html页面,后面有发生一种情况就是如果直接用文档模式打开html页面,并且有引入js脚本的话,会出现这种提示:
前端工具集(6) -- 上传的时候判断所选文件是否为文件夹
之前做项目,有做到一个就是上传的功能,其中Chrome和Firefox支持文件夹上传的,但是Safari还不支持文件夹上传,因此需要去判断当前用户选择上传的文件是不是一个文件夹,如果是文件夹的话,那么就不让他上传,因此才有这个方法。
代码如下:
前端工具集(5) -- 文件拖入禁止浏览器响应打开
之前项目中有出现一种情况,就是项目的网页上,有拖拽上传的操作,但是有时候用户drop的地方如果放错,比如我拖拽一张图片,然后放到页面放错了,不是放到指定的drop容器里面,而是其他元素,这时候浏览器就会将这张图片显示出来,以文档模式打开,并覆盖当前页面,这种体验是很糟糕的。因此我们需要禁止掉浏览器的这种默认行为。
具体代码如下:
前端工具集(4) -- 禁止backspace后退页面
之前有在项目中遇到按下Backspace键让浏览器后退的问题, 主要逻辑就是当敲Backspace键时,事件源类型为密码或单行、多行文本的,或者是可编辑DIV,并且readonly属性不为true和enabled属性不为false的,则退格键生效,其他情况都失效。
网上其实有很多解决方法,但是或多或少都不够全面,后面自己修改了以下,用的这个版本会比较全面一点:
前端工具集(3) -- 检测密码框输入有没有开启大写标记
之前做一个项目的时候,有个需求是用户在密码框输入密码的时候,如果当前是开启大写的话,那么就要给个提示。像这样子:
具体代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22/**
* 检测有没有开启大写标记
* @param e
* @param showHandle
* @param hideHandle
*/
detectCapsLock: function (e, showHandle, hideHandle) {
e = e || window.event;
// 获取按键的keyCode
var keyCode = e.keyCode || e.which;
var isShift = e.shiftKey || (keyCode === 16 ) || false;
// 判断shift键是否按住
if (((keyCode >= 65 && keyCode <= 90 ) && !isShift) || ((keyCode >= 97 && keyCode <= 122 ) && isShift)) {
if (_.isFunction(showHandle)) {
showHandle();
}
} else {
if (_.isFunction(hideHandle)) {
hideHandle();
}
}
}
ps: 后面我发现在我的mac book pro 中会失效,因为我的电脑的大写的方式是先按住shift键,然后再按下字母键。其实这个逻辑不在上面的判断中,所以会无效