找我做事
2011-06-14

    最近很多人找我做项目,发现还是有必要写个日志来说明下我的情况。

    我现在是一个自由职业者。主要在Elance平台上接国外的项目。Elance上项目数量充足,报酬可观,导致了我的收入比较稳定。相比国内的项目来说,我更喜欢做国外的项目。原因有二:需求清晰和报酬可观。所以如果你想找我做东西,请先想好需求,我一般不会接只有 “我就想做个网站” 这种需求的项目。

    我的报价是按照我的预计工作时间来计算的,一般是1000RMB每天(3小时)。当然,因为我自认为经验还是比较丰富,所以做东西的速度是比较快的(质量当然有保证)。

    我比较喜欢的项目包括:NodeJS项目,前端(Javascript, JQuery, ExtJS等)项目,应用型flash项目等。小型PHP网站我不太喜欢接,因为会PHP的人很多,价格压得比较低。

    另外我在学校有各种团队资源,可以帮你介绍专业网站设计+前端、高级SEO咨询、高级PHP/Java网站制作等。

完。

阅读:4474 评论: 11 💬
如何提高NodeJS程序的稳定性
2011-05-16

   当我们写了个NodeJS程序的时候,一般用node yourjsfile.js命令启动该程序。但是如果程序中有东西出错,这个进程就会退出。我们写程序不可能保证万无一失,肯定有些没有处理的错误,这就让很多人觉得NodeJS不稳定,容易产生很多故障。 下面我就讲讲几种方法增加你的NodeJS程序的稳定性。

   1.使用 try{...} catch(error){...} 来执行容易出错的代码段。比如解析一个外来的json字符串等。
   2.使用 process.on('uncaughtException', function(err){...}); 来处理未被捕捉的错误。
   3.试用奶妈进程来启动你的程序,检测子进程的退出,然后自动重启该进程。比如 mother.js :


start();
function start()
{
    console.log('Mother process is running.');
    var ls = require('child_process').spawn('node', ['yourjsfile.js']);
    ls.stdout.on('data', function (data)
    {
        console.log(data.toString());
    });
    ls.stderr.on('data', function (data)
    {
        console.log(data.toString());
    });
    ls.on('exit', function (code)
    {
        console.log('child process exited with code ' + code);
        delete(ls);
        setTimeout(start,5000);
    });
}

    4.使用 nohup 让nodejs进程在后台运行。 比如运行"nohup node yourjsfile.js > /dev/null &"

完。

阅读:16984 评论: 16 💬
NodeJS实现HTTP/HTTPS代理
2011-05-06

    身在天朝,难免会用到代理的时候。 比如在学校内网用代理免费上外网,在墙内用代理上404网站等。

    现在使用的代理大部分为HTTP和Socket代理。 Socket代理更底层,需要本地解析域名,而HTTP代理则是基于HTTP协议之上的,不需要本地解析域名。下面我讲讲HTTP(S)代理的设计思路以及NodeJS代码实现。

HTTP协议

    HTTP协议简单说来就是浏览器把一串字符串发送到目标服务器,然后把目标服务器返回回来的一串字符串显示给用户。

    浏览器发送的这串字符主要分为两个部分,一部分是头,里面包含目标服务器域名,当前请求的文件路径等信息。另一部分是正文,一般的GET请求没有正文。

    服务器返回来的字符串也分为头和正文。

HTTP代理原理

    HTTP代理需要做的事情就是接收浏览器发来的请求字符串,再从请求字符串的头部分找出浏览器请求的目标主机,然后直接把这串请求字符串发给目标主机,再把目标主机返回的数据发给浏览器。 “什么?就这么简单?” “呃。。是啊,但这还没完。。”

    现代浏览器一般都是默认采用HTTP/1.1版本,并且默认会发送Connection: keep-alive请求。 这些信息是写在请求的头部的,意思是通知目标服务器采用keep-alive技术继续处理后续的请求。 但是我们做的代理程序要想支持keep-alive是比较麻烦的。所以干脆就把这个篡改成Connection: close。 这样就可以保证浏览器请求的每个文件都会单独发送一个HTTP请求。

下面是NodeJS代码实现

 

var net = require('net');
var local_port = 8893;

//在本地创建一个server监听本地local_port端口
net.createServer(function (client)
{
    
    //首先监听浏览器的数据发送事件,直到收到的数据包含完整的http请求头
    var buffer = new Buffer(0);
    client.on('data',function(data)
    {
        buffer = buffer_add(buffer,data);
        if (buffer_find_body(buffer== -1return;
        var req = parse_request(buffer);
        if (req === falsereturn;
        client.removeAllListeners('data');
        relay_connection(req);
    });

    //从http请求头部取得请求信息后,继续监听浏览器发送数据,同时连接目标服务器,并把目标服务器的数据传给浏览器
    function relay_connection(req)
    {
        console.log(req.method+' '+req.host+':'+req.port);
        
        //如果请求不是CONNECT方法(GET, POST),那么替换掉头部的一些东西
        if (req.method != 'CONNECT')
        {
            //先从buffer中取出头部
            var _body_pos = buffer_find_body(buffer);
            if (_body_pos < 0_body_pos = buffer.length;
            var header = buffer.slice(0,_body_pos).toString('utf8');
            //替换connection头
            header = header.replace(/(proxy\-)?connection\:.+\r\n/ig,'')
                    .replace(/Keep\-Alive\:.+\r\n/i,'')
                    .replace("\r\n",'\r\nConnection: close\r\n');
            //替换网址格式(去掉域名部分)
            if (req.httpVersion == '1.1')
            {
                var url = req.path.replace(/http\:\/\/[^\/]+/,'');
                if (url.path != urlheader = header.replace(req.path,url);
            }
            buffer = buffer_add(new Buffer(header,'utf8'),buffer.slice(_body_pos));
        }
        
        //建立到目标服务器的连接
        var server = net.createConnection(req.port,req.host);
        //交换服务器与浏览器的数据
        client.on("data", function(data){ server.write(data); });
        server.on("data", function(data){ client.write(data); });

        if (req.method == 'CONNECT')
            client.write(new Buffer("HTTP/1.1 200 Connection established\r\nConnection: close\r\n\r\n"));
        else
            server.write(buffer);
    }
}).listen(local_port);

console.log('Proxy server running at localhost:'+local_port);


//处理各种错误
process.on('uncaughtException', function(err)
{
    console.log("\nError!!!!");
    console.log(err);
});



/**
* 从请求头部取得请求详细信息
* 如果是 CONNECT 方法,那么会返回 { method,host,port,httpVersion}
* 如果是 GET/POST 方法,那么返回 { metod,host,port,path,httpVersion}
*/
function parse_request(buffer)
{
    var s = buffer.toString('utf8');
    var method = s.split('\n')[0].match(/^([A-Z]+)\s/)[1];
    if (method == 'CONNECT')
    {
        var arr = s.match(/^([A-Z]+)\s([^\:\s]+)\:(\d+)\sHTTP\/(\d\.\d)/);
        if (arr && arr[1] && arr[2] && arr[3] && arr[4])
            return { method: arr[1], host:arr[2], port:arr[3],httpVersion:arr[4] };
    }
    else
    {
        var arr = s.match(/^([A-Z]+)\s([^\s]+)\sHTTP\/(\d\.\d)/);
        if (arr && arr[1] && arr[2] && arr[3])
        {
            var host = s.match(/Host\:\s+([^\n\s\r]+)/)[1];
            if (host)
            {
                var _p = host.split(':',2);
                return { method: arr[1], host:_p[0], port:_p[1]?_p[1]:80, path: arr[2],httpVersion:arr[3] };
            }
        }
    }
    return false;
}




/**
* 两个buffer对象加起来
*/
function buffer_add(buf1,buf2)
{
    var re = new Buffer(buf1.length + buf2.length);
    buf1.copy(re);
    buf2.copy(re,buf1.length);
    return re;
}

/**
* 从缓存中找到头部结束标记("\r\n\r\n")的位置
*/
function buffer_find_body(b)
{
    for(var i=0,len=b.length-3;i<len;i++)
    {
        if (b[i] == 0x0d && b[i+1] == 0x0a && b[i+2] == 0x0d && b[i+3] == 0x0a)
        {
            return i+4;
        }
    }
    return -1;
}

 

另外,可以用 "nohup node some.js > /dev/null &" 命令让nodejs程序在后台运行。

完。

阅读:41615 评论: 32 💬
无需smtp服务器直接发送邮件
2011-01-20

 

    以前发邮件都是连接smtp服务器然后再发送的。比如先用自己的gmail账号连接到smtp.gmail.com,认证之后再发送邮件。但是这种方式常常会有限制,发送速度慢不说,每日发送数量还有限制。

    后来一直在想邮件服务器之间是怎么发送的呢,如果可以直接连接对方mail服务器就好了。 经过很多尝试都不行,现在知道是因为各个邮件服务器都有反垃圾邮件机制,所以要做很多预备工作才可以。

    下面我以 longbill@php.js.cn 为发送者发送邮件为例,讲讲这种直接连对方服务器发送邮件的方式。

    首先需要设置 php.js.cn 域名的 txt 记录为:v=spf1 ip4:173.230.145.228 ~all 。意思是增加一个spf记录,允许173.230.145.228ip发送邮件。邮件服务器收到邮件的时候会检查这个。

    然后设置 php.js.cn 的 MX记录为 173.230.145.228 。这点按理说是不需要的,但是我觉得还是加上比较好。 意思是别人发送邮件到 xxx@php.js.cn 的时候,会被连接到这个IP。     

    然后就可以用php直接发送邮件了。 php要做的事情如下:

  • 拿到对方email后,首先检查域名的mx记录。例如 longbill.cn@gmail.com ,要用getmxrr函数得到gmail.com的mx记录,一般取第一条就可以了。
  • 用fsockopen连接刚刚得到的服务器域名
  • 按照SMTP协议写命令,取得返回结果。

    看起来像这样: 绿色的是服务器返回的信息,红色的是php发送的信息。每个换行都是\r\n

 

220 126.com Anti-spam GT for Coremail System (126com[20101010])

HELO php.js.cn

250 OK

MAIL FROM:<longbill@php.js.cn>

250 Mail OK

RCPT TO:<lclgg@126.com>

250 Mail OK

DATA

354 End data with <CR><LF>.<CR><LF>

MIME-Version: 1.0

Delivered-To: lclgg@126.com

Subject: =?UTF-8?B?6L+Z6YeM5piv5rWL6K+V6YKu5Lu25qCH6aKY?=

From: Longbill <longbill@php.js.cn>

To:  lclgg@126.com

Content-Type: text/plain; charset=UTF-8

Content-Transfer-Encoding: base64


6L+Z6YeM5piv5rWL6K+V6YKu5Lu25YaF5a6544CC

.

250 Mail OK queued as mx9,J8mowLDb7+xoCDdNCIe9AA--.240S2 1295452266

QUIT

221 Bye


就是这样。  源代码在这里。 http://php.js.cn/down/sample/mail.php.txt

完。

阅读:21803 评论: 17 💬
Javascript进阶教程2
2010-11-19

请使用非IE浏览器访问!

优米网内部JS教程。

上期讲了JS里面变量、函数、变量作用域等。 这次讲一下浏览器环境下的一些实用的知识

Event 事件

如何绑定事件

可以在HTML源代码中用DOM元素的属性直接声明。例如:

<input type="text" size="30" id="email" onchange="checkEmail()" >

也可以在JS里绑定,例如:

document.getElementById('email').onchange=function()
{
	checkEmail();
}

有什么区别呢?

先说一下JS里可以用new Function("inner code")来创建一个函数。即可以把字符串形式的源代码转换成可执行的code。 写到HTML源代码上的事件代码可以理解成事件属性的值被new Function()了。 所以onchange="return true;" 就相当于 dom.onchange=function(){ return true; }

值得注意的是dom.onxxxx=function 的绑定方式是粗暴式的,后绑定的函数会覆盖掉前面绑定的函数。如果需要同一个事件绑定多个函数, 可以使用dom.addEventListener(w3c)和dom.attachEvent(IE)。(又是讨厌的IE与众不同!)

查看全文...  

阅读:8189 评论: 15 💬
用PHP直接读取二进制doc文件的文本内容
2010-11-14

    要实现搜索doc文档,第一步就要先从二进制doc文件中提取出文本内容。现在一般的做法是用微软的com组件,或者用第三方的软件来提取,都不太方便。 所以我想用php直接提取其中的文本。

    而微软早年搞的doc二进制标准实在是太复杂了,即使微软在2008年公布了doc文件的二进制编码格式,但是要想从中按照正规的方法提取出文本内容仍然是一件非常困难的事情。我的感觉就像是小时候玩RPG游戏寻宝一样,必须一步步按照微软的寻宝图路线走,任何一步错误都拿不到宝藏。。。

    研究正规途径提取文本并取得一定成功的是Ethan同学,他通过相关文献资料,找到一条可以用PHP分析doc二进制文件并从中提取出文本内容的方法。但是只能提取出一少部分doc文档的内容(可能是实验做的太少)。我后来在他的基础上又研究了几天,发现要想走正规路线拿到文本非常困难,因为doc文档编码中文和英文不一样,还分为1Table和0Table等。

    昨天突发奇想,能不能用暴力破解的方式拿到文本呢? 经过一天艰苦奋战,终于取得了可喜的成绩。 这里是DEMO。

    暴力法的大概思路是: 每512个字节分为一个sector,每个sector为一个基本单位。然后发现,如果某个sector是用来存储文本的,那么此sector里面就只有文本内容,没有其他数据。 那怎样判断是否为文本sector呢? 我引入了一个常用汉字和英文字母以及标点符号组成的数组。判断此sector里面常用文本占sector总长度(512字节)的比例(根据大量测试,比例在0.3以上,基本上可以确定是文本sector),就可以确定整个文件的文本内容。 另外,doc文件里,汉字是用2个字节来存储的,而且用的little endian方式(就是字节顺序是反的)存储的,但是英文是用1个字节。 后来又想了一些办法来判断是中文还是英文。 最终经过大量的测试,绝大部分文件可以正常提取绝大部分文本(偶尔可能最有一个sector文字太少无法识别)。 对于索引doc文件这个简单的需求来说,已经足够了。

     源代码在这里下载

完。

阅读:20014 评论: 12 💬
Javascript进阶教程(一)
2010-11-03

    昨天给公司同事做了一下JS的进阶技术交流,由于是进阶,所以基础的东西会少点,但是也不是太深奥。有兴趣的同学可以点这里去看看。 请使用Chrome或Safari浏览器,以获得流畅的体验。 因为用了一些HTML5的标签。

完。

阅读:6750 评论: 4 💬
无法删除Cookie的烦恼。。
2010-10-29

    今天遇到一个cookie的问题: 名叫username的,域为www.umiwi.com的cookie无法删除,导致用户登录以后无法退出。搞了半天终于发现问题所在:原来是我关于cookie的基础知识没搞清楚null 。。。。

    Cookie不仅仅有名字和值两个属性,还有域(domain),过期时间(expires),路径(path)等属性。 其中,不同的域、不同的路径下可以存在同样名字的cookie。 比如这个页面:  用鼠标戳我

    一般我们删除cookie的方法是用一个同样名字、过期时间为过去某个时候的Cookie覆盖之。 这时就一定要搞清楚你要删除的cookie的域和路径,Cookie域和路径要一样才能被覆盖。 否则产生的效果就是那个想要被删除的Cookie具有神奇的生命力,无法被清除~~~

    另外,我发现在设置Cookie的时候,如果没有指定域名,那么设置的这个cookie的域默认为当前域,比如www.umiwi.com。 如果设置的时候指定了某个域,那么浏览器存的时候会自动在前面加.。比如PHP代码: setcookie('test','a',0,'www.umiwi.com');  那么 浏览器里面存的Cookie的域为.www.umiwi.com。 所以,最好的方式是设置Cookie的时候怎么写的,删除的时候就怎么写。  要不就会出现我遇到的情况: 明明浏览器里看有 username=longbill,域为www.umiwi.com的Cookie,但是在php里写 setcookie('username','',time()-1000,'www.umiwi.com','/'); 无法删除那个Cookie。 原因就是我删Cookie的操作其实上是发了一个新的名叫username、值为空、过期时间为过去1000秒、域为.www.umiwi.com、路径为/的Cookie。这个Cookie发到浏览器马上就过期了,什么也看不到。而我要删除的在www.umiwi.com上的cookie还活的好好的。。。

    补充一下HTTP的知识: php里面的header函数发送的header信息默认是不重复的,后面发的会自动覆盖前面的内容。一般说来HTTP请求的header部分也是没有重复信息的,除了Set-Cookie头!  所以,如果我们使用header函数来发送cookie,请注意加第二个参数true。 详见header函数

完。

阅读:42030 评论: 6 💬
Google JavaScript Style Guide 中文翻译
2010-10-19

    昨天不小心看到了Google Javascript Style Guide,Google在里面约定了一些名字的书写规则,也给出一些更好的建议。 下面我翻译一段个人认为很有价值的东西:

布尔表达式

以下代码在布尔表达式里都等于false:

  • null
  • undefined
  • '' 空字符串
  • 0 数字

但是请注意,以下这些等于true:

  • '0' 字符串
  • [] 空的数组
  • {} 空的对象

这就意味着本来这样的代码:

while (x != null) {

你可以这样来写(当你不希望x为0、空字符串、false时):

while (x) {

当你希望检查一个字符串是否为空的时候你可以这样:

if (y != null && y != '') {

但是这样会更简洁更高效:

if (y) {

注意: 有很多容易搞错的布尔表达式(以下表达式的值都为true):

  • Boolean('0') == true
    '0' != true 
    /* Longbill注释:
     * if ('0') alert('yes');
     * if ('0' != true) alert('yes');
     * if ('0' == false) alert('yes');
     * 以上三个都会弹出yes!!
     */
  • 0 != null
    0 == []
    0 == false
  • Boolean(null) == false
    null != true
    null != false
  • Boolean(undefined) == false
    undefined != true
    undefined != false
  • Boolean([]) == true
    [] != true
    [] == false
  • Boolean({}) == true
    {} != true
    {} != false
  • Longbill 补充下:
    NaN != true
    NaN != false
    Boolean(NaN) == false

三目运算符

这样的代码:

if (val != 0) {
  return foo();
} else {
  return bar();
}

可以被简化成这样:

return val ? foo() : bar();

三目运算符在生成html字符串的时候也很有用:

var html = '<input type="checkbox"' +
    (isChecked ? ' checked' : '') +
    (isEnabled ? '' : ' disabled') +
    ' name="foo">';

&& 和 ||

由这两个逻辑运算符连接的表达式会从左到右依次执行,直到遇到满足条件为止。

所以"||" 又被称作 '缺省值' 操作符,因为如下代码:

/** @param {*=} opt_win */
function foo(opt_win) {
  var win;
  if (opt_win) {
    win = opt_win;
  } else {
    win = window;
  }
  // ...
}

可以被简写成:

/** @param {*=} opt_win */
function foo(opt_win) {
  var win = opt_win || window; 
  // ...
}

"&&" 同样很有用。比如,如下代码:

if (node) {
  if (node.kids) {
    if (node.kids[index]) {
      foo(node.kids[index]);
    }
  }
}

可以被简写成:

if (node && node.kids && node.kids[index]) {
  foo(node.kids[index]);
}

或者这样:

var kid = node && node.kids && node.kids[index];
if (kid) {
  foo(kid);
}

甚至可以这样:

node && node.kids && node.kids[index] && foo(node.kids[index]);


使用 join() 来创建字符串

一般来说,累加字符串是很常见的:

function listHtml(items) {
  var html = '<div class="foo">';
  for (var i = 0; i < items.length; ++i) {
    if (i > 0) {
      html += ', ';
    }
    html += itemHtml(items[i]);
  }
  html += '</div>';
  return html;
}

但是这种方式在万恶的IE浏览器下会很慢,通常以下代码会更快:

function listHtml(items) {
  var html = [];
  for (var i = 0; i < items.length; ++i) {
    html[i] = itemHtml(items[i]);
  }
  return '<div class="foo">' + html.join(', ') + '</div>';
}

注意:指定数组的下标来赋值会比直接用数组的push()方法更高效。


遍历Node List

在遍历Node list 的时候,常常会用到length属性来当循环的边界条件。因为循环的时间复杂度是O(n),而每循环一次,都要检查一下边界条件(length属性),这样时间复杂度变为O(n^2):

var paragraphs = document.getElementsByTagName('p');
for (var i = 0; i < paragraphs.length; i++) {
  doSomething(paragraphs[i]);
}

这样做会更高效(时间复杂度应该是O(n) ):

var paragraphs = document.getElementsByTagName('p');
for (var i = 0, paragraph; paragraph = paragraphs[i]; i++) {
  doSomething(paragraph);
}

以上这个方法同样适用于对象和数组(当数组不含有会被当成是false的值的时候)。

/* 原文出自:Google,翻译: Longbill (http://php.js.cn) 2010-10-19 */

另外,当你遍历一个Node的所有子Node的时候,可以用firstNode和nextSibling属性,就像这样:

var parentNode = document.getElementById('foo');
for (var child = parentNode.firstChild; child; child = child.nextSibling) {
  doSomething(child);
}

完。

阅读:8822 评论: 6 💬
TextMate 开发AS3应用
2010-10-18

    记得08年flashplayer10刚出来的时候,我看到过一个视频 ,Adobe的人就是用Textmate开发的Flash。 他是用Flex SDK里面的mxmlc命令编译的。当年我还用Mac OS的Automation功能做了一个应用程序,功能是把AS文件拖动到上面就自动给你编译成swf文件。 今年再回头看的时候,发现其实有更简单的方法。那就是用TextMate的Actionscript3 Bundle。然后我就尝试去配置这个环境,结果发现还很不容易。 下面把我的过程跟大家分享一下:

  1. 当然你要有一台Mac
  2. 你得先安装 TextMate。
  3. 安装上面提到过的actionscript 3 bundle。
  4. 到http://www.adobe.com/cfusion/entitlement/index.cfm?e=flex3sdk 下载flex sdk
  5. 将sdk解压,放到一个方便找到的位置
  6. 然后打开Textmate->prefrences->advanced->shell variables,添加一个PATH变量,值是你的flex sdk里bin的位置,比如:/Developer/SDK/flex_sdk_3.5/bin。 如果已经存在PATH变量,那么请不要改动原来的数据,在原来数据的后面加冒号(:),然后再加上bin目录的位置。
  7. 同样是在Shell Variables里面,添加一个LC_ALL变量,值是en_US.UTF-8。因为我发现flex会根据系统语言来显示错误信息,但是显示出来的是乱码,所以还是统一用英文的错误信息算了
  8. 打开flex sdk的目录,进入frameworks,编辑flex-config.xml,把<target-player>9xxx</target-player>替换成<target-player>10.0.0</target-player>

然后就爽把,新建一个as文件,写一些东西,然后按苹果键+B,就会自动调用mxmlc编译你的as文件,生成swf。 当然actionscript3 bundle的功能还有很多,自己去发觉吧~

2010年10月18日更新

    今天搞了一下flash player的debug版本。可以把as3程序trace出来的信息记录到一个文件。步骤大概是这样的:

  1. 新建一个文件  ~/mm.cfg ,写上TraceOutputFileEnable=1
  2. 打开textmate的 Bundles >> Bundle Editor >> Edit Commands
  3. 打开左边的ActionScript 3 这个Bundle,然后点击左下角的+按钮,选择New Command
  4. 然后在右边的详细信息区域做如下选择:Save:Nothing,   Input: None, Output: Show as HTML, Activation: Key Equivalent (然后点击右边区域,再按Apple键+D),Scope Selector: source.actionscript.3
  5. 在Command(s)区域,写:
    echo "<pre>"
    cat ~/Library/Preferences/Macromedia/Flash\ Player/Logs/flashlog.txt
    echo "</pre>"
    

    好了,现在在as3的代码界面按Apple+D就可以看到刚刚运行的flash的trace信息了。 上图:

    运行效果如下:

完。

阅读:10499 评论: 23 💬
Copyright © Longbill 2008-2026