收集slidershare高质量的ppt
http://www.slideshare.net/rainoxu/javascript-5464018 javascript性能优化
http://www.slideshare.net/fool2fish/js-12953769 js高级技巧 demo在http://fool2fish.aliapp.com/js-senior-skill/index.html
http://www.slideshare.net/iddcn/ss-12153414 高粒度模块化前端开发
http://www.slideshare.net/kejun/ss-9015786 前端开发理论热点面对面
http://www.slideshare.net/firt/mobile-web-high-performance 高性能移动端开发(E文)
http://www.slideshare.net/nzakas/writing-efficient-javascript 高效率书写javascript
http://v.youku.com/v_playlist/f15769239o1p4.html
http://v.youku.com/v_playlist/f15769239o1p5.html
http://v.youku.com/v_playlist/f15769239o1p6.html
http://www.docin.com/p-467150624.html
mongodb无主从集群配置
安装mongodb
首先增加yum的源
|
1 |
vi /etc/yum.repos.d/10gen.repo |
按照下面的写法书写
|
1 2 3 4 5 6 7 |
[10gen] name=10gen Repository baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64 gpgcheck=0 |
之后试试yum info mongo-10gen 能显示源信息就行了。
安装mongodb服务器端
|
1 |
yum install mongo-10gen-server |
安装mongodb客户端
|
1 |
yum install mongo-10gen |
安装都比较慢,耐心等待
启动时使用service mongod start即可。
无主从(简单的以一个机器作为一个分片)
集群: 3分片 3配置 config 1 mongos
1,2,3号机器的ip分别是115,116,117
创建分片,1号机器
|
1 2 3 4 5 |
mkdir -p /data/a/r0 //主 mkdir -p /data/logs/a //log mkdir -p /data/configServer/r0 //配置 mkdir -p /data/logs/configServer mkdir -p /data/logs/serverlogs |
启动
|
1 |
./mongod --shardsvr --port 10000 --dbpath /data/a/r0 --fork --logpath /data/logs/a/r0.log --directoryperdb |
此处端口设置的是10000
(每个数据库将储存在一个单独的目录(–directoryperdb))
其余机器也按照上面的操作进行
配置 配置服务器,配置服务器端口均为40000
所有机器执行
|
1 |
./mongod --configsvr --logpath /data/logs/configServer/r0.log --logappend --dbpath /data/configServer/r0 --port 40000 --shardsvr --fork --directoryperdb |
配置路由 mongos
找一台机器作为路由
|
1 |
./mongos --configdb 192.168.1.115:40000,192.168.1.116:40000,192.168.1.117:40000 --logpath /data/logs/serverlogs/mongos.log --logappend --port 50000 --fork |
此后50000就作为主访问服务器的入口。
开始配置分片
在路由机器上执行
|
1 2 3 |
./mongo admin --port 50000 db.runCommand({addshard:"192.168.1.115:10000"}) #添加分片1 |
以上操作有几个分片就执行几次,记得改ip
|
1 2 |
db.runCommand({enablesharding:"test"}) #设置test库开启分片 db.runCommand({shardcollection:"test.users",key:{_id:1}}) #设置test.users集合分片和分片的主键 |
测试:
|
1 2 3 |
use test for(var i=1;i<=5000000;i++) db.users.insert({age:i,name:"lulu",addr:"Shanghai"}) db.users.stats() |
插入数据,之后printShardingStatus()打印下
使用chrome调试工具来监听事件
http://www.briangrinstead.com/blog/chrome-developer-tools-monitorevents
福音啊,以前为了判断发生了什么事件,得手动绑定一堆莫名其妙的东西。现在可以直接使用
|
1 |
monitorEvents(document.body, 'mouse') |
这样,给某个元素绑定一个事件列表,从而监听事件发生。
如果不监听的话可以使用
|
1 |
unmonitorEvents(document.body) |
来解除绑定。
若开启火狐的firebug来说还支持一些其他事件
|
1 |
composition contextmenu drag focus form key load mouse mutation paint scroll text ui xul |
chrome也支持一些事件
|
1 |
mouse key touch control |
至于monitorEvents的第二个参数可以使用各种不同的事件列表
|
1 2 3 4 5 |
mouse: “mousedown”, “mouseup”, “click”, “dblclick”, “mousemove”, “mouseover”, “mouseout”, “mousewheel” key: “keydown”, “keyup”, “keypress”, “textInput” touch: “touchstart”, “touchmove”, “touchend”, “touchcancel” control: “resize”, “scroll”, “zoom”, “focus”, “blur”, “select”, “change”, “submit”, “reset” no arguments: all of the above + “load”, “unload”, “abort”, “error”, “select”, “change”, “submit”, “reset”, “focus”, “blur”, “resize”, “scroll”, “search”, “devicemotion”, “deviceorientation” |
效果如下:
centos6.2 bugfree的搭建及排错
最近测试部终于招起人了。。还是个蛮漂亮的妹纸(其实比我大而且不吃猪肉,你们懂的T_T)。。。
以之前在中创的一些测试经验,研究了下团队建设跟工具,最后决定用bugfree来进行bug管理。
然后在安装部署bugfree的时候也遇到了一些问题
首先下载bugfree
http://testing.etao.com/node/120
下载后传到ngix服务器下面。之后输入
http:///bugfree/install
在安装过程中会进行检查,如果提示没有读写权限的话,需要使用
|
1 2 3 |
chmod -R 777 bugfree 新建目录 mkdir BugFile chmod -R 777 BugFile |
最麻烦的其实就是安装检查,会提示你环境有问题。比如:
如果报没有pdo-mysql的话,需要进行以下步骤:
下载
http://pecl.php.net/get/PDO_MYSQL-1.0.2.tgz
安装
|
1 2 3 4 5 6 7 8 |
tar zxvf PDO_MYSQL-1.0.2.tgz cd PDO_MYSQL-1.0.2 cp /usr/local/mysql/bin/mysql_config /usr/bin #防止编译的时候找不到mysql_config /usr/local/php/bin/phpize ./configure --with-php-config=/usr/local/php/bin #指定php-config目录 yum install mysql-devel #包含mysql对应的头文件,防止编译出错 make make install |
配置php.ini
|
1 2 |
extension_dir = "/usr/local/php/lib/php/extensions/no-debug-non-zts-20060613/" extension = "pdo_mysql.so" |
但是在这其中可能会出现一些问题:
|
1 |
/usr/local/php/bin/phpize |
这时出现错误:
|
1 2 3 4 5 6 |
Configuring for: PHP Api Version: 20041225 Zend Module Api No: 20060613 Zend Extension Api No: 220060519 Cannot find autoconf. Please check your autoconf installation and the $PHP_AUTOCONF environment variable. Then, rerun this script. |
解决方法:
|
1 2 |
yum install m4 yum install autoconf |
在编译(make)的时候有可能会出现下面的错误:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
In file included from /data1/lnmp/PDO_MYSQL-1.0.2/pdo_mysql.c:31: /data1/lnmp/PDO_MYSQL-1.0.2/php_pdo_mysql_int.h:25:19: error: mysql.h: No such file or directory In file included from /data1/lnmp/PDO_MYSQL-1.0.2/pdo_mysql.c:31: /data1/lnmp/PDO_MYSQL-1.0.2/php_pdo_mysql_int.h:36: error: expected specifier-qualifier-list before ‘MYSQL’ /data1/lnmp/PDO_MYSQL-1.0.2/php_pdo_mysql_int.h:48: error: expected specifier-qualifier-list before ‘MYSQL_FIELD’ /data1/lnmp/PDO_MYSQL-1.0.2/php_pdo_mysql_int.h:53: error: expected specifier-qualifier-list before ‘MYSQL_RES’ make: *** [pdo_mysql.lo] Error 1 |
问题原因:
这是因为在编译时需要 MySQL 的头的文件。而它按默认搜索找不到头文件的位置,所以才出现这个问题。
解决方法:
将 /usr/local/mysql/include/ 目录下的 MySQL 头文件链接到 /usr/local/include/ 的目录下。
|
1 |
# ln -s /usr/local/mysql/include/* /usr/local/include/ |
检查完毕后,就可以继续安装了。安装中要输入数据库用户名与密码,之后稍等片刻。。。。。。跳转,然后404了。。
解决方法
(1)进入安装目录里面的/protected/config/,找到文件main.php
(2)在main.php中将下面的部分屏蔽掉,或者删除
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
'urlManager' => array ( 'urlFormat' => 'path', 'showScriptName' => false, 'rules' => array ( '<type:\\w+>/<id:\\d+>/<action:\\w+>' => 'info/edit', '<type:\\w+>/list/<product_id:\\d+>' => 'info/index', '<type:\\w+>/<id:\\d+>' => 'info/edit', '<controller:\\w+>/view/<id:\\d+>' => '<controller>/view', '<controller:\\w+>/<id:\\d+>' => '<controller>/view', '<controller:\\w+>/<action:\\w+>/<id:\\d+>' => '<controller>/<action>', '<controller:\\w+>/<action:\\w+>' => '<controller>/<action>', ), ), |
再次访问就可以正常登陆啦。
看了下bugfree的源码,是用的yii框架
bugfree提供了一个比较牛逼的功能叫做分步执行,但是仍然没法用。。
开启了ngix对php的报错模块后,发现是有个公有方法mb_detect_encoding不支持
这时候需要安装模块yum -y install php-mbstring
配置php.ini文件
添加extension=mbstring.so
另外注意在使用bugfree的时候,可以自己定义字段(很牛逼的功能啊),一些选择框(比如人名框)在使用的时候常常只能输入一两个名字
其实。。这玩意是浏览器与bugfree系统配合的记忆功能。。。只需要输入拼音就可以找到其他人了。。
总结起来,bugfree比之前用的td,jira什么牛逼多了,主要的感觉是,虽然轻量,但真正是一个比较重功能体验的工具。其实妹子说他们的公司以前用的是禅道,不过禅道就是bugfree的某作者出走搞的。。有空再了解下禅道
nodejs使用express,crypto配合validator实现用户登录逻辑
最近的项目使用express来写,这也是我尝试写的第一个nodejs的大型项目。今天实现了半天的登陆逻辑,还是很费劲的,在此记录一下。
大部分的知识都是从https://github.com/cnodejs/nodeclub 这份源码学到的,还有express官方的一个登陆验证示例
https://github.com/visionmedia/express/blob/master/examples/auth/app.js
当然还有api http://expressjs.jser.us/api.html#res.status 但是吐槽一下,express的api。。真心崎岖。。
首先最坑的一点。。。让session生效
|
1 2 3 4 5 |
app.use(express.cookieParser());//开启cookie app.use(express.session({//开启session secret: config.session_secret })); app.use(app.router); |
那么注意了,前两行一定要写在app.router这个中间件前面!否则session始终未定义!
之后我们开始书写session与cookie配合的逻辑。
基本逻辑:
- 进入网站后(此时没有session),从cookie中取加密的数据,进行验证,若验证成功,在session中存储user,用户免登陆。
- 进入首页时(需要登录的页面),若session中没有user,则自动跳到登陆页面。
- 登陆时,采用post方式提交用户信息,利用加密算法进行加密,存储登录信息到cookie中。
- 数据库采用的mysql,采用啥都无所谓。。因为我是通过http与后端写好的统一登陆接口进行通信的。登陆成功返回0,失败返回-9999
ok,下面来一条条的实现这些逻辑。
首先我们利用crypto这个牛逼的加密加密库进行加密解密操作
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var crypto = require('crypto'); //加密 function encrypt(str, secret) { var cipher = crypto.createCipher('aes192', secret); var enc = cipher.update(str, 'utf8', 'hex'); enc += cipher.final('hex'); return enc; } //解密 function decrypt(str, secret) { var decipher = crypto.createDecipher('aes192', secret); var dec = decipher.update(str, 'hex', 'utf8'); dec += decipher.final('utf8'); return dec; } |
他的作用主要是把用户名密码进行加密存到cookie,读取时解密。而密文我们在配置文件中配置即可。
第一个逻辑需要一个加密与解密的操作。而进入网站开始就进行操作的话,可以使用app.use中间件。
|
1 2 3 4 5 6 7 |
app.use(express.cookieParser());//开启cookie app.use(express.session({//开启session secret: config.session_secret })); app.use(require('./controller/site').auth_user); // 添加这个中间件,用来解析cookie app.use(app.router); |
然后controller/side中auth_user如下
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
exports.auth_user = function (req, res, next) { if (req.session.user) { return next();//若没有session,直接跳过此中间件 } else { var cookie = req.cookies[config.auth_cookie_name];//读cookie,通过配置文件中标识符读cookie if (!cookie) { return next();//若没有此站点的cookie,直接跳过此中间件 } var auth_token = decrypt(cookie, config.session_secret);//解密操作 var auth = auth_token.split('\t'); var user = auth[0], passwd = auth[1];//解密后拿到username与password var data = { parament:user, password:passwd, route:'checkLogin', } var userLoginConf = config.apiService.userLogin; postData.send(data, userLoginConf, function (result) {//这部分为post的api请求进行验证。 if (result == 0) { req.session.user = user.username;//存在此用户,开启session,存储user return next();//进行下一步 } else {//不存在此用户,进行下一步 return next(); } }) } }; |
第二个逻辑很简单。我们直接使用一个私有函数即可。
|
1 2 3 4 5 6 7 |
/*session无效验证*/ function noSession(req,res){ if (!req.session || !req.session.user) { res.redirect('login'); return; } } |
第三个逻辑,配合router进行表单提交的工作
router:
|
1 |
app.post('/login', site.login); |
controller:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
exports.login = function (req, res) { /*处理空值与注入*/ var param = handleParam(req.body) var data ={ parament : param.username, password: param.password, route: 'checkLogin', } var userLoginConf = config.apiService.userLogin; postData.send(data,userLoginConf,function(result){//与后端api接口通信 if(result ==0){//登陆成功 var user = { 'username':data.parament, 'password':data.password } gen_session(user, res);//生成cookie /*这个逻辑暂时没开,这是为了做那种“返回刚才页面”的需求 //check at some page just jump to home page var refer = req.session._loginReferer || 'home'; for (var i = 0, len = notJump.length; i !== len; ++i) { if (refer.indexOf(notJump[i]) >= 0) { refer = 'home'; break; } } res.redirect(refer); */ req.session.regenerate(function(){//写session。存入username // Store the user's primary key // in the session store to be retrieved, // or in this case the entire user object req.session.user = user.username; res.render('home',[]);//渲染数据,随便怎么渲染了。。 }); }else{ res.render('login',[]);//登录失败,跳转到login。这里我没有加提示信息。 } }) }; |
这里处理注入使用了validator模块
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var check = require('validator').check, sanitize = require('validator').sanitize; /*处理空值与注入*/ function handleParam(params){ var params = params || {}; var safeParam = {}; for(var key in params){ var trimed = sanitize(params[key]).xss(); var blockXssed = sanitize(trimed).xss(); safeParam[key] = blockXssed } return safeParam } |
当然最后我们也要有个登出逻辑了
|
1 2 3 4 5 |
exports.logout = function (req, res, next) {//代码很简单,就不解释了 req.session.destroy(); res.clearCookie(config.auth_cookie_name, { path: '/' }); res.redirect(req.headers.referer || 'login'); }; |
最后感谢写nodeclub https://github.com/cnodejs/nodeclub 的各位,在你们的源码里学到了很多很多的东西!
利用javascript记录用户行为(1)
最近隐私问题可是炒得火热啊,外行内行都搀和进来讲cookie。
前阵子去贝塔咖啡参加谷歌妹纸开发者大会的时候,问白鸦大大,对隐私问题怎么看。
他讲了好多,最终是围绕着一个核心:中国是个没有隐私的国度,只要问心无愧,不做非法的事情,隐私就不是问题。
咱是技术人,不过听了他的话多少也安心了一点,来写写最近玩的一个东东:javascript记录用户行为。
关于用户行为我觉得无非就那么几种:
- 点击行为
- 访问时间
- 用户ua
点击行为的记录
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
getMosPos:function (e) { var scrollx, scrolly; if (typeof(window.pageXOffset) == 'number') { scrollx = window.pageXOffset; scrolly = window.pageYOffset; } else { scrollx = document.documentElement.scrollLeft; scrolly = document.documentElement.scrollTop; } return { x:e.clientX + scrollx, y:e.clientY + scrolly } }, getMouseEl:function (x, y) { return document.elementFromPoint(x, y); } |
但其实在实际使用时,我们对点击元素可能并不是特别关心,而比较关心坐标(比如要进行热力图的绘制)。
这时候单纯的记录坐标是有问题的。因为你并不知道你的客户端是什么样的,页面在不同客户端的情况也不同。
举例来讲,一个居中的网页,在不同的分辨率下点击同一个元素,返回的X坐标是完全不一样的。因此我们需要做修正。
|
1 2 3 4 5 6 7 8 |
windowWidth:function () { var a = document.documentElement; return self.innerWidth || a && a.clientWidth || document.body.clientWidth }, fixZero : function(){ zero = that.align==='middle'?-this.windowWidth()/2:0; return zero } |
我们人为规定记录的原点,当页面居中时(非常常见),认为原点是屏幕的水平中心。上面返回的zero作为一个修正值。
比如1024的屏幕,原点是512,0 那么相应的当我们记录下横坐标时,就需要减去512.
然后我们当前点击时记录的坐标是300,在返回时返回-212.
这样在以后的操作中就可以更加准确的记录用户的点击坐标。
还有一种情况当然就是自适应布局,页面可以铺满整个屏幕,这时候我们就不返回坐标了,而返回百分比。
数据发送
其实有个很简单的方法可以发送数据,这个方法叫做beacons图片信标法
|
1 2 3 |
var url = '/abc.php' var params= ['step=2','time='121'] (new Image()).src = url + ? + params.join('&') |
通过src可以构造一个get请求,当我们打开浏览器时可以很明显的看到这个请求。之后就是数据组装了,类似这样
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
sendData:function (data, path) { var url = tools.addQueryUrlParam(data, path) var img = new Image(); img.src = url; img = null; }, addQueryUrlParam:function (url, data) { for (key in data) { if (url.indexOf("?") == -1) { url += "?"; } else { url += "&"; } url += encodeURIComponent(key) + "=" + encodeURIComponent(data[key]); } return url } |
这样我们就可以随便给后端发数据了~至于这个url,可以伪造一个,也可以使用一个现成的空图片。
另外还有一些细节问题,比如click事件会造成请求还没完成就跳转,要绑定mousedown,等等。
项目目前在https://github.com/jtyjty99999/99codingClub/tree/master/static 只是一个简单的雏形
nodejs项目小总结
1.url的处理
querystring.parse(urlObj.query)可以把url内的query参数转为字符串
JSON.parse可以把字符串转为json
2.异步与同步
我的项目大约是这样的
接受url请求并处理-解析-去拿到环境变量与参数。
等待上面的请求ok后,并发请求,使用上面的环境变量与参数去调用php与webredis
同步异步交织在一起,很蛋疼。
不过接下来使用了eventproxy进行处理,非常好用~
https://github.com/JacksonTian/eventproxy
一个非常典型的代码
|
1 2 3 4 5 6 7 8 9 10 |
ep.all('tpl', 'data', function (tpl, data) { // 在所有指定的事件触发后,将会被调用执行 // 参数对应各自的事件名 }); fs.readFile('template.tpl', 'utf-8', function (err, content) { ep.emit('tpl', content); }); db.get('some sql', function (err, result) { ep.emit('data', result); }); |
那么注意下面两个函数,一定要保证callback执行,emit才会触发。
当all内事件都触发后,返回值作为参数,触发ep.all内的callback
更多的使用方法可以参考api。
3.nodejs发送post
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
var http = require('http'); var querystring = require('querystring'); var post_data = querystring.stringify({ sys_text : '构造字符串”欢迎回来,道客'+cellphone.slice(cellphone.length-4,cellphone.length)+',您的语镜已经连接系统,请安全驾驶', interval : '1440', agent: '超级管理员', userid :userid, }); var options = { host:'192.168.1.3', port:8080, path:'/idts-1.0/httpservice/addweibo/php/add_sys_weibo.php', method:'post', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': post_data.length } }; var req = http.request(options, function (res) { console.log("Got response: " + res.statusCode); res.setEncoding('utf8'); res.on('error',function (e) { console.log("Got error: " + e.message); }).on('data', function (chunk) { console.log('BODY: ' + chunk); }); }); req.write(post_data + "\n"); req.end(); } |
上面是整个发送post的流程。那么注意一点一定要指定content-length头。否则会报错。ngix会报类似411 length required 这样的错误。
之后只需要利用req.write向接收端写消息体即可。
至于nodejs发送get,可以直接使用http.get方法
相关的api http://docs.cnodejs.net/cman/http.html
3月13日更新
经过大牛snoopy的对代码的斧正,发现了几个问题
1.异常处理,JSON.parse可能会抛异常,因此需要处理
|
1 2 3 4 5 6 7 |
try { var cellphone = JSON.parse(result).MGET[0] } catch (e) { // console.log(e.name); // "MyError" // console.log(e.message); // "MyError" console.log('数据格式不正确') } |
2. 拼接chunk
在接收服务端的数据时,若数据较长,直接在data监听可能会收到多次chunk。
利用字符串拼接chunk时,因为编码等问题,可能出现错误,因此建议用数组拼接
见这篇文章 http://cnodejs.org/topic/4faf65852e8fb5bc65113403
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var chunks = []; var size = 0; res.on('data', function (chunk) { chunks.push(chunk); size += chunk.length; }); res.on('end', function () { var data = null; switch(chunks.length) { case 0: data = new Buffer(0); break; case 1: data = chunks[0]; break; default: data = new Buffer(size); for (var i = 0, pos = 0, l = chunks.length; i < l; i++) { var chunk = chunks[i]; chunk.copy(data, pos); pos += chunk.length; } break; } }); |
思路就是上面的代码,我也使用了文中提到的bufferHelper。
另外snoopy说可以使用0.10的stream2,等下次尝试。
3.多核
nodejs里内置了cluster模块,可以大大提高机器资源的使用
利用cluster做的多核
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
function start(handle) { var http = require("http"); var cluster = require('cluster'); var http = require('http'); var numCPUs = require('os').cpus().length; if (cluster.isMaster) { for (var i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on('death', function (worker) { console.log('worker ' + worker.pid + ' died'); cluster.fork(); }); } else { function onRequest(request, response) { var options = { host:'192.168.1.6', port:7379, path:'/SADD/' + 'niaAOVU2lg' + ':config/' + '2013-03-09' + Math.random(), method:'get' }; var req = http.get(options, function (res) { // console.log("Got response: " + res.statusCode); res.on('error',function (e) { // console.log("Got error: " + e.message); }).on('data', function (chunk) { // console.log('BODY: ' + chunk); }); }); req.on('error', function (e) { // console.log("Got error: " + e.message) }) req.end() response.writeHead(200, {'Content-Type':'text/html'}); response.end() } var server = http.createServer(onRequest).listen(8888); } } exports.start = start;//定义模块给外面的函数 |
经过性能测试,8核的机器跑满了能达到以前的八倍。。。(8个同时跑嘛。。)
还有个问题
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var req = http.request(o, function (res) { var rec_leng = 0; var rec_ary = []; // res.setEncoding('utf8'); res.on('error',function (e) { console.log("Got res error: " + e.message); }).on('data', function (chunk) { rec_leng += chunk.length; rec_ary.push(chunk); /* * if(rec_leng > postLimit){ rec_ary = null; req.connection.destroy() } * */ }).on('end', function () { var buf = Buffer.concat(rec_ary, rec_leng); var result = buf.toString(); callback.call(this, result); }); }) |
注意setEncoding转换的时候,会把2进制转成string buffer的concat会出错,所以两个不要一起用。
javascript循环异步调用的解决办法
我们经常有这样一个场景,干完某件事情,再干另一件事情。比如我准备依次输出 1,2,3
|
1 2 3 4 |
var names = ['1', '2', '3']; names.forEach(function(i){ setTimeout(function(){alert(i)},1000) }) |
首先我们利用forEach方法规避了循环中,异步调用的闭包问题。 但如果这么书写,会发现同时弹出1,2,3。 这里我们可以这么处理。把内部执行的这个函数叫做loop。 首先执行loop(0),当执行完毕后,在loop(0)的回调函数中执行loop(1) 而loop(1)的回调函数则包含loop(2)…
|
1 2 3 4 5 6 7 |
function loop(i){ setTimeout(function(){ alert(names[i]); if (i < names.length - 1) loop(i + 1); },1000); } |
假设现在有个异步的服务,他可以返回一个值。但是你需要第一次处理完毕后再处理第二次请求,上面的思路就用得到了。
有木有方法可以封装一个函数,实现此类函数转换为循环的异步函数呢?
比如我们现在有个服务提供了api接口
|
1 |
<script latype="text/javascript" src="http://coolshell.cn/t.js" charset="utf-8"> |
他的api是这样的:xss_rpc_call(n, callback)
n为输入,返回这个数字的十六进制。
第一种方案:递归,也就是我们上面的思路实现,需要api callback的支持。
具体实现代码
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20] var asyncEach = function (fn, arr) { var l = arr.length, i = -1; var run = function() { i += 1; if (i === l) return; fn(arr[i], run); }; run(); } asyncEach(function (key,callback) { xss_rpc_call(key, function (result) { hello.innerHTML+=result+' ' callback(); }) }, arr) |
demo http://jsbin.com/itazec/28/edit
这里我们使用了一个callback函数,作为递归的“触发器”。
方案二,优化方案。微博上 尤小右的方案 来自 http://sketch.evanyou.me/async_asap.html 我做下研究。
递归式解决方案有个问题,那就是在api端返回延迟波动很大的情况下,用户会要等待很久才能看到结果。理想的方案应该既保证数据显示的顺序,又保证以最快的速度显示能显示的所有数据。
作者提供了此方案的实现,称之为ASAP (as soon as possible)。
算法的思路简单来说就是每个异步请求都要遵循两条规则:
- 接收到返回的时候,只有当我前一个请求已经返回并输出了我才能输出,不然就先等着。
- 按照规则1,在我输出之前,我后面的请求都不可能输出。所以在我输出之后,如果我的后一个请求已经返回,它也应该输出。这条规则是递归的。
ok。那么我们按照作者所说,实现一下。这样其实规避了文初的问题,不需要进行异步的一维化,只需要处理结果即可。反正用户最后看到的结果顺序对就可以了。(其实在我看来这就是一种“视觉”或者显示策略上的优化。)
主要代码
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
var total = 30,responses = [],displayed = [],i=0; for (i; i < total; i++) { (function (i) { var now = Date.now(); xss_rpc_call2(i, function (res) { responses[i] = { res: res, sent: now }; //当这是第一条记录,或符合第一个原则,之前的一个请求已经显示时,显示 if (i == 0 || displayed[i-1]) { _display(i); } }) })(i) } function _display (i) { box.innerHTML+=responses[i].res+'\n' displayed[i] = true; if (i === total - 1) { // 显示完毕 } else if (responses[i+1]) { // 当符合第二个原则,发现下一个请求已经来了,显示 _display(i+1); } } |
demo http://jsbin.com/uhukot/10
参考http://blog.jcoglan.com/2010/08/30/the-potentially-asynchronous-loop/
http://coolshell.cn/t.html
python利用beautifulSoup写爬虫
以前讲过利用phantomjs做爬虫抓网页 http://99jty.com/?p=1058 是配合选择器做的
利用 beautifulSoup(文档 :http://www.crummy.com/software/BeautifulSoup/bs4/doc/)这个python模块,可以很轻松的抓取网页内容
|
1 2 3 4 5 6 7 8 9 10 11 |
# coding=utf-8 import urllib from bs4 import BeautifulSoup url ='http://www.baidu.com/s' values ={'wd':'网球'} encoded_param = urllib.urlencode(values) full_url = url +'?'+ encoded_param response = urllib.urlopen(full_url) soup =BeautifulSoup(response) alinks = soup.find_all('a') |
上面可以抓取百度搜出来结果是网球的记录。
beautifulSoup内置了很多非常有用的方法。
几个比较好用的特性:
构造一个node元素
|
1 2 3 4 |
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>') tag = soup.b type(tag) # <class 'bs4.element.Tag'> |
属性可以使用attr拿到,结果是字典
|
1 2 |
tag.attrs # {u'class': u'boldest'} |
或者直接tag.class取属性也可。
也可以自由操作属性
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
tag['class'] = 'verybold' tag['id'] = 1 tag # <blockquote class="verybold" id="1">Extremely bold</blockquote> del tag['class'] del tag['id'] tag # <blockquote>Extremely bold</blockquote> tag['class'] # KeyError: 'class' print(tag.get('class')) # None |
还可以随便操作,查找dom元素,比如下面的例子
1.构建一份文档
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
html_doc = """ <html><head><title>The Dormouse's story</title></head> <p><b>The Dormouse's story</b></p> <p>Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a> and <a href="http://example.com/tillie" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p>...</p> """ from bs4 import BeautifulSoup soup = BeautifulSoup(html_doc) |
2.各种搞
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
soup.head # <head><title>The Dormouse's story</title></head> soup.title # <title>The Dormouse's story</title> soup.body.b # <b>The Dormouse's story</b> soup.a # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a> soup.find_all('a') # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] head_tag = soup.head head_tag # <head><title>The Dormouse's story</title></head> head_tag.contents [<title>The Dormouse's story</title>] title_tag = head_tag.contents[0] title_tag # <title>The Dormouse's story</title> title_tag.contents # [u'The Dormouse's story'] len(soup.contents) # 1 soup.contents[0].name # u'html' text = title_tag.contents[0] text.contents for child in title_tag.children: print(child) head_tag.contents # [<title>The Dormouse's story</title>] for child in head_tag.descendants: print(child) # <title>The Dormouse's story</title> # The Dormouse's story len(list(soup.children)) # 1 len(list(soup.descendants)) # 25 title_tag.string # u'The Dormouse's story' |
python服务器端收发请求
最近学习了python的一些服务器端编程,记录在此。
发送get/post请求
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# coding:utf-8 import httplib,urllib #加载模块 #urllib可以打开网站去拿 #res = urllib.urlopen('http://baidu.com'); #print res.headers #定义需要进行发送的数据 params = urllib.urlencode({'param':'6'}); #定义一些文件头 headers = {"Content-Type":"application/x-www-form-urlencoded", "Connection":"Keep-Alive",'Content-length':'200'}; #与网站构建一个连接 conn = httplib.HTTPConnection("localhost:8765"); #开始进行数据提交 同时也可以使用get进行 conn.request(method="POST",url="/",body=params,headers=headers); #返回处理后的数据 response = conn.getresponse(); print response.read() #判断是否提交成功 if response.status == 200: print "发布成功!^_^!"; else: print "发布失败\^0^/"; #关闭连接 conn.close(); |
利用urllib模块可以方便的实现发送http请求.urllib的参考手册
http://docs.python.org/2/library/urllib.html
建立http服务器,处理get,post请求
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# coding:utf-8 from BaseHTTPServer import HTTPServer,BaseHTTPRequestHandler class RequestHandler(BaseHTTPRequestHandler): def _writeheaders(self): print self.path print self.headers self.send_response(200); self.send_header('Content-type','text/html'); self.end_headers() def do_Head(self): self._writeheaders() def do_GET(self): self._writeheaders() self.wfile.write("""<!DOCTYPE HTML> <html lang="en-US"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <p>this is get!</p> </body> </html>"""+str(self.headers)) def do_POST(self): self._writeheaders() length = self.headers.getheader('content-length'); nbytes = int(length) data = self.rfile.read(nbytes) self.wfile.write("""<!DOCTYPE HTML> <html lang="en-US"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <p>this is put!</p> </body> </html>"""+str(self.headers)+str(self.command)+str(self.headers.dict)+data) addr = ('',8765) server = HTTPServer(addr,RequestHandler) server.serve_forever() |
注意这里,python把response的消息体记录在了rfile中。
但是要注意,发送端必须指定content-length.若不指定,程序就会卡在rfile.read()上,不知道读取多少。
如何写一个在线编辑器
前阵子玩了下很火的编辑器高亮引擎 http://ace.ajax.org/#nav=about 是cloud9,github等的编辑器内核。
ace的使用指南 : http://ace.ajax.org/#nav=howto
他跟codemirror相比感觉api,易用性方面都提升很多,而且提供漂亮的皮肤,与简单扩展api及快捷键的方法。(不过估计是codemirror的封装,不管了。。)
然后我就写了个在线编辑器练了下手。
https://github.com/jtyjty99999/99codingClub/blob/master/editer/editer.html
demo http://debug.cnodejs.net/html/1362562396609.html
可以代码高亮,实现运行,也可以拦截console,
但不能持久化,也不能window.onload触发(因为本来就没load…),也木有快捷键。。
代码高亮非常简单,我们定义一个容器,给样式
|
1 |
position:absolute |
注意,如果你不给的话,ace引擎也会自动帮你加上的。所以要考虑绝对定位的情况。
之后给一个id,就可以跑了,demo1
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<!DOCTYPE html> <html lang="en"> <head> <title>ACE in Action</title> <style type="text/css" media="screen"> #editor { position: absolute; top: 0; right: 0; bottom: 0; left: 0; } </style> </head> <body> <div id="editor">function foo(items) { var x = "All this is syntax highlighted"; return x; }</div> <script src="http://d1n0x3qji82z53.cloudfront.net/src-min-noconflict/ace.js" type="text/javascript" charset="utf-8"></script> <script> var editor = ace.edit("editor"); editor.setTheme("ace/theme/monokai"); editor.getSession().setMode("ace/mode/javascript"); </script> </body> </html> |
ace引擎挂在亚马逊云计算服务器上,所以速度很快。
ace.edit初始化高亮容器
setTheme方法可以设置皮肤
getSession().setMode(‘ace/mode/javascript’)可以选择语言。目前貌似支持50种语言。。。
写完了之后,我们就完成了最简单的一个代码高亮。但是如何完成编辑器的基本功能:书写css,html,js并执行呢?
第一步:
首先我们建立三个容器作为html,js,css代码容器
|
1 2 3 4 5 6 7 8 9 |
<div id="editorjs">function foo(items) { var x = "All this is syntax highlighted"; return x; } </div> <div id="editorhtml"> </div> <div id="editorcss">*{margin:0px} </div> |
初始化三个高亮容器
|
1 2 3 4 5 6 7 8 9 |
var editorjs = ace.edit("editorjs"); editorjs.setTheme("ace/theme/monokai"); editorjs.getSession().setMode("ace/mode/javascript"); var editorcss = ace.edit("editorcss"); editorcss.setTheme("ace/theme/monokai"); editorcss.getSession().setMode("ace/mode/css"); var editorhtml = ace.edit("editorhtml"); editorhtml.setTheme("ace/theme/monokai"); editorhtml.getSession().setMode("ace/mode/html"); |
那么这里有一个很方便的api可以使用。
|
1 |
editorhtml.setValue('<!DOCTYPE HTML>\n<html lang="en-US">\n<head>\n<meta charset="UTF-8">\n<title>\n</title>\n</head>\n<body>\n</body>\n</html>') |
我们利用setValue方法,可以给容器写值,并建立一个模板。
第二步
我们需要拼合css,js,html并运行,如何做呢,这里我选择iframe来进行操作。
|
1 |
<iframe src="" frameborder="0" id="show"></iframe> |
如何操作iframe呢,有个技巧
|
1 2 3 4 5 6 |
var iObj = document.getElementById(id).contentWindow; iObj.document.designMode = 'On'; iObj.document.contentEditable = true; iObj.document.open(); iObj.document.writeln('test'); iObj.document.close(); |
这样就可以向iframe里写数据了 来源支付宝ued
那么css与js如何“拼合”呢,我纠结了很久。。后来经过sunnylost提示可以使用动态加载
代码
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function changeIframe(id, htmlContent,cssContent,jsContent) { var iObj = document.getElementById(id).contentWindow; unconnect(iObj,'console.log') connect(iObj,'console.log',publishConsole) iObj.document.designMode = 'On'; iObj.document.contentEditable = true; iObj.document.open(); iObj.document.writeln(htmlContent); iObj.document.close(); //动态加载css,注意document方法的global对象切换到iframe上 var style = document.createElement("style"); style.id = 'newStyle'; (iObj.document.getElementsByTagName("head")[0] || iObj.document.body).appendChild(style); if (style.styleSheet) { //for ie style.styleSheet.cssText = cssContent; } else { //for w3c style.appendChild(document.createTextNode(cssContent)); } //动态加载jss,注意document方法的global对象切换到iframe上 var node = document.createElement("script"); setInnerText(node,jsContent); iObj.document.body.appendChild(node); } |
这里一定要注意加载时的document是iframe里的contentWindow.document。
加载完毕后就大功告成了!!可以跑html+css+js了。
这里我们使用了一个很有用的方法
|
1 |
changeIframe('show',editorhtml.getValue(),editorcss.getValue(),editorjs.getValue()) |
getValue()方法可以拿到容器内的代码,可以写到编辑器里。
最后一个功能,拦截console.log
用过jsbin的朋友都知道,jsbin是可以显示console.log的,这是如何做到的呢。。其实以前研究过~
http://99jty.com/?p=961
http://99jty.com/?p=955
实现自由的连接函数。我们可以通过AOP技术,拦截console.log,并用别的函数覆盖之。
当然,拦截也有取消,可以直接用delete方法就可以删除被我们覆盖的console.log了~
附拦截器
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
function connect(scope, fnFrom, fnTo) { "use strict" var objFn = fnFrom.split('.');//处理传入的函数名 var deepth = objFn.length; var scope = scope || window; var j = deepth,i=j; var _obj = scope,__obj=_obj ; while (i > 0) {//以window.console.log为例,这里利用迭代最终拿到log _obj = _obj[objFn[deepth - i]]; i -= 1; }; var t = function () { var ret = _obj.apply(this, arguments);//此时this指向console 注意console.log log函数上下文必须在console那里,否则会报错 fnTo.apply(this, arguments); return ret; } while (j > 1) {//仍然利用迭代,找出window.console.log前一级window.console的引用 __obj = __obj[objFn[deepth - j]]; j -= 1; } __obj[objFn[deepth - 1]] = t;//覆盖window.console.log } function unconnect(scope,fn){ var objFn = fn.split('.');//处理传入的函数名 var deepth = objFn.length; var scope = scope || window; var j = deepth,i=j; var _obj = scope; while (i > 0) {//以window.console.log为例,这里利用迭代最终拿到log _obj = _obj[objFn[deepth - i]]; i -= 1; }; delete _obj } function publishConsole(inner){ document.getElementById('console').innerHTML=inner } unconnect(iObj,'console.log') connect(iObj,'console.log',publishConsole) |
