分类 WEB开发 下的文章

nodejs(二)

  上一篇文章谈了一些基本的nodejs,这次学写“路由器”,首先加入新的模块requestHandlers.js。这个模块里面可以处理两个请求,分别是start和upload,并各自向浏览器返回了一些字符串。

function start() {
    console.log("Request handler 'start' was called.");
    return "Hello Start";//在浏览器上显示
}

function upload() {
    console.log("Request handler 'upload' was called.");
    return "Hello Upload";
}
exports.start = start;
exports.upload = upload;

  处理请求的模块好了,我们这是总需要一个路由器吧,不然怎么把请求分配到对应的函数上呢?所以router.js应运而生。

function route(handle, pathname) {
    console.log("About to route a request for" + pathname);
    if (typeof handle[pathname] === 'function') {
        return handle[pathname]();
    } else {
        console.log("No request handle found for " + pathname);
        return "404 Not found";
    }
}
exports.route = route;

  以上代码,先检查给定的路径对应的请求处理程序是否存在(即requestHandlers.js中是否存在),如果存在的话直接调用相应的函数。我们可以用从关联数组中获取元素一样的方式从传递的对象中获取请求处理函数,因此就有了简洁流畅的形如handle\[pathname\]();的表达式,这个感觉就像在前方中提到的那样:“请帮我处理了这个路径”。

  然后就是改写server.js的时候了。

var http = require("http");
var url = require("url");

function start(route, handle) {
    function onRequest(request, response) {
        var pathname = url.parse(request.url).pathname;
        console.log("Request for " + pathname + " received.");
        response.writeHead(200, {
            "Content-Type": "text/plain"
        });
        var content = route(handle, pathname);//在此处开启路由
        response.write(content);//在浏览器输出对应函数返回的字符串
        response.end();
    }

    http.createServer(onRequest).listen(8888);
    console.log("Server running");
}
exports.start = start;

最后修改index.js

var server = require("./server");
var router = require("./router");
var requestHandlers = require("./requestHandlers");

var handle = {} //处理程序的集合
handle["/"] = requestHandlers.start;
handle["/start"] = requestHandlers.start;
handle["/upload"] = requestHandlers.upload;
server.start(router.route, handle);

最后 >node index.js

我们就可以在浏览器输入:

http://localhost/

http://localhost/start

http://localhost/upload

http://localhost/1234

看到不同的结果了。

nodejs(一)

  Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven(事件驱动), non-blocking I/O model(无锁I/O模型) that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.

  首先是从官网(http://nodejs.org/)下载对应的系统版本,然后安装,设置环境path。

  找个地方作为工作区,建立example.js。

var http = require('http');

http.createServer(function (request, response) {
    console.log('Request received.');
    response.writeHead(200, {'Content-Type': 'text/plain'});
    response.end('Hello World\n');
}).listen(8080);

  console.log('Server running at http://127.0.0.1:8080');
其中匿名函数作为createServer的参数,匿名函数有两个参数,request和response,即请求和响应。然后在收到请求后返回http头信息和"hello world"。listen监听8080端口,最后一行打印server运行状态。而这个过程是异步的。

  在我们在终端运行 > node example.js 时,会显示 Server running at 。。。 表示运行到最后一行了。而当我们浏览器访问http://localhost:8080时,会显示hello world。而终端里显示出Request received。这个结果就是因为nodejs是异步的和基于事件驱动的,很好理解。

  再来一个例子: server.js

var http = require("http");

function start() {
    function onRequest(request, response) {
        console.log("Request received.");
        response.writeHead(200, {"Content-Type": "text/plain"});
        response.write("Hello world.");
        response.end();
    }

    http.createServer(onRequest).listen(8080);
    console.log("Server has started.");
}
exports.start = start

  主文件:index.js

var server = require("./server");

server.start();

在终端里运行 > node index.js

  会出来和上一个例子一样的结果。这里 server.js就是一个模块。模块的作用就是启动一个server。onRequest作为一个函数传递给了http模块的createServer方法。用export导出start外部才可以使用。

  我觉得简单,也很有趣。

BigPipe认识(转)

所谓BigPipe,指的是Facebook开发的用来改善客户端响应速度的技术。本质上讲,其实它并不是新事物,原理上等同于Yahoo在Best Practices for Speeding Up Your Web Site里提出的Flush the Buffer Early,不过BigPipe的实现更灵活,所以有必要了解一二。

我们平常浏览网页时的体验通常是串行的:浏览器发起请求,服务器收到后渲染页面,在此期间,浏览器除了等待别无选择,演示代码如下:

<?php
sleep(1);
$header = 'header';

sleep(1);
$content = 'content';

sleep(1);
$footer = 'footer';
?>
<html>
<head>
<title>test</title>
</head>
<body>

<div id="header"><?php echo $header; ?></div>

<div id="content"><?php echo $content; ?></div>

<div id="footer"><?php echo $footer; ?></div>

</body>
</html>

注:代码里用sleep模拟服务端耗时的操作。

如果我们把串行改成并行的方式呢?每当服务器生成新的内容立刻发送给浏览器,浏览器立刻渲染,不必等到接收到全部数据再处理,毫无疑问会提升用户体验,代码如下:

提醒一下,代码最后运行在Apache + Mod PHP环境,旧版本Apache可能需要关闭GZip。如果是Nginx + PHP FastCGI环境,因为fastcgi_buffers的存在,运行效果会非你所愿。

<html>
<head>
<title>test</title>
</head>
<body>

<?php sleep(1); ?>
<div id="header"><?php echo str_pad('header', 1024); ?></div>
<?php ob_flush(); flush(); ?>

<?php sleep(1); ?>
<div id="content"><?php echo str_pad('content', 1024); ?></div>
<?php ob_flush(); flush(); ?>

<?php sleep(1); ?>
<div id="footer"><?php echo str_pad('footer', 1024); ?></div>
<?php ob_flush(); flush(); ?>

</body>
</html>

注意:某些浏览器必须接收到一定长度的内容才开始渲染,所以代码里用到了str_pad。

更新:新版Nginx增加了一个fastcgi_buffering指令,可以用来关闭fastcgi的buffer功能。

代码里用到ob_flush和flush把页面分块刷新缓存到浏览器,此时如果使用Firebug查看响应头的话,会发现:Transfer-Encoding=chunked,如此一来浏览器就可以分块渲染了。

BigPipe在此基础上更进一步,演示代码如下:

<html>
<head>
<title>test</title>
</head>
<body>

<div id="header"></div>

<div id="content"></div>

<div id="footer"></div>

<?php ob_flush(); flush(); ?>

<?php sleep(1); $header = str_pad('header', 1024); ?>
<script>
document.getElementById("header").innerHTML = "<?php echo $header; ?>";
</script>
<?php ob_flush(); flush(); ?>

<?php sleep(1); $content = str_pad('content', 1024); ?>
<script>
document.getElementById("content").innerHTML = "<?php echo $content; ?>";
</script>
<?php ob_flush(); flush(); ?>

<?php sleep(1); $footer = str_pad('footer', 1024); ?>
<script>
document.getElementById("footer").innerHTML = "<?php echo $footer; ?>";
</script>
<?php ob_flush(); flush(); ?>

</body>
</html>

使用BigPipe,先刷新布局(Layout),然后按块(header,content,footer)刷新相应的Javascript代码,从而实现页面内容的填充。

BigPipe之所以使用Javascript渲染页面,是因为这样一来渲染页面的时候,就不会被块的位置束缚住,如果我们的服务器支持多线程,那么就可以同时处理多块内容,哪块先处理好就把哪块刷新到浏览器,即便不支持多线程,服务器也可以按照内容的重要程度分主次先后渲染,不必拘泥于HTML代码的物理顺序。

此外还应注意一下BigPipe和Ajax二者的区别,对于一个分成若干个块的页面而言,如果使用Ajax的话,每一块都需要单独发送一个HTTP请求,而如果使用BigPipe的话,不管有多少块,都仅有一个HTTP请求。所以Ajax对服务器造成的压力会是BigPipe的若干倍。

注:BigPipe不利于SEO,应用时可通过User Agent判断请求是人还是搜索引擎,如果是人的话,则应用BigPipe渲染模式,如果是搜索引擎的话,则应用传统渲染模式。

使用PHP VLD扩展查看opcode

  在php的开发中,我们时常需要查看这段php代码运行的效率怎么样?而效率又和这段php代码被解析成了多少行opcode直接相关,所以,在这里推荐一款查看php生成opcode的扩展---VLD(Vulcan Logic Disassembler)。

  先来看官方的描述,http://pecl.php.net/package/vld,The Vulcan Logic Disassembler hooks into the Zend Engine and dumps all the opcodes (execution units) of a script. 意思就是在Zend引擎中,以挂钩的方式实现的用于输出PHP脚本生成的中间代码(执行单元)的扩展。

  首先,需要下载并安装这个扩展,linux下请下载tgz源代码包编译安装,windows下请在http://pecl.php.net/package/vld/0.12.0/windows下载相应的dll文件。

  怎么使用呢?举一个例子。

test.php
<?php
    $arr = array(1,2,3,4,5);
    print_r($arr);

  然后我们在命令行或终端里执行  

> php -dvld.active=1 test.php

  然后就能输出类似的结果
opcode.png
这就是一张opcode图,我们能看到这段php代码被解析的流程。
抛砖引玉,更加高级的用法请google之。

PHP 5.3 和 PHP 5.4 在字符串的区别

自 PHP 5.4 起字符串下标必须为整数或可转换为整数的字符串,否则会发出警告。之前例如 "foo" 的下标会无声地转换成 0。

<?php
$str = 'abc';

var_dump($str['1']);
var_dump(isset($str['1']));

var_dump($str['1.0']);
var_dump(isset($str['1.0']));

var_dump($str['x']);
var_dump(isset($str['x']));

var_dump($str['1x']);
var_dump(isset($str['1x']));
?>

以上例程在PHP 5.3中的输出:

string(1) "b"
bool(true)
string(1) "b"
bool(true)
string(1) "a"
bool(true)
string(1) "b"
bool(true)

以上例程在PHP 5.4中的输出:

string(1) "b"
bool(true)
Warning: Illegal string offset '1.0' in /tmp/t.php on line 7
string(1) "b"
bool(false)
Warning: Illegal string offset 'x' in /tmp/t.php on line 9
string(1) "a"
bool(false)
string(1) "b"
bool(false)

Note:

用 [] 或 {} 访问任何其它类型(不包括数组或具有相应接口的对象实现)的变量只会无声地返回 NULL。

Note:

PHP 5.5 增加了直接在字符串原型中用 [] 或 {} 访问字符的支持。