0
这个伪系列的第二篇,不过和之前的几篇是同一天写的.三分钟热度貌似还没过.
1 静态资源代理
上一篇,我们是通过判断请求的路径来直接返回结果的.简单粗暴,缺点明显:如果 url 后面加杂了 queryString,因为判断逻辑中没有处理,那么将直接返回 404 页面(其实也没有其他的页面).
难道要一个一个加 queryString 的处理?有可行性,但麻烦.
再者,如果要添加新的 html 页面,那么也要在处理逻辑中依次添加?html 页面还要引用 js 脚本,CSS 样式,样式表中还要引用图片,字体....难道就要这样无休止的添加下去?显然是不可能的.
借助于 NodeJS 提供的
url
API,我们可以提取请求 url 中的 pathName 部分,也就是忽略了域名以及 queryString 的部分,然后将他交给另一个
path
API.他负责将 pathName 解析为当前操作系统可读的路径.
至此,答案就很明显了:拿到路径后,利用
fs
模块读取,如果成功,则返回文件内容;否则直接 404 就好了.
这里我们只用到了
url
和
path
模块的很小一部分功能,我也就只写这一点了.具体的定义及应用请参考.
url.prase(urlString)
接收一个 url 字符串,将它转为 URL 对象.
url.parse('http://user:pass@host.com:8080/path/to/file?query=string#hash');
它的返回值将会是一个 Object:
Url {
protocol: 'http:',
slashes: true,
auth: 'user:pass',
host: 'host.com:8080',
port: '8080',
hostname: 'host.com',
hash: '#hash',
search: '?query=string',
query: 'query=string',
pathname: '/path/to/file',
path: '/path/to/file?query=string',
href: 'http://user:pass@host.com:8080/path/to/file?query=string#hash'
}
看看这个结构图,应该更容易理解:
┌─────────────────────────────────────────────────────────────────────────────┐│href│├──────────┬┬───────────┬─────────────────┬───────────────────────────┬───────┤│protocol││auth│host│path│hash││││├──────────┬──────┼──────────┬────────────────┤│││││hostname│port│pathname│search││││││││├─┬──────────────┤│││││││││query││" http: // user:pass @ host.com : 8080 /p/a/t/h ? query=string #hash "││││││││││└──────────┴┴───────────┴──────────┴──────┴──────────┴─┴──────────────┴───────┘
以上抄自:
所以,要拿到相对路径,我们只需要取
Url.pathname
就可以了.
path.resolve(path[, ...] )
接收一组按顺序排好的路径,并把它组合为一个当前操作系统可识别的绝对路径.
path.resolve('.', '/archive/my-first-article')
如果在 windows 中,当前工作目录为
C:\Users\rocka
,它将返回
'C:\Users\rocka\archive\my-first-article'
而在 linux 环境,
/home/rocka
目录中,它的返回值是
'/home/rocka/archive/my-first-article'
与当前操作系统直接关联.至于为何要跟当前工作目录关联,因为此时,它的第一个参数是
.
,不就是当前目录嘛.
path.join(path[, ...] )
接收一组路径字符串,然后将他们拼起来.可以用
..
这样的相对路径. 返回值仍是字符串哦.
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..')
将返回:
'/foo/bar/baz/asdf'
直接贴代码咯(代码片段,省略了 require 和 listen 等):
var root = path.resolve('.');
var server = http.createServer((request, response) = >{
console.log(` [Rocka Node Server] $ {
request.method
}: $ {
request.url
}`);
// path name in url
var pathName = url.parse(request.url).pathname;
// file path based on operation system
var filePath = path.join(root, pathName);
console.log(` [Rocka Node Server] pathName: $ {
pathName
},
filePath: $ {
filePath
}`);
if (request.method === 'GET') {
// try to find and read local file
fs.stat(filePath, (err, stats) = >{
// no error occured, read file
if (!err && stats.isFile()) {
response.writeHead(200, {
'Content-Type': 'text/html'
});
fs.createReadStream(filePath).pipe(response);
// cannot find file, but received index request
} else if (!err && pathName == '/') {
response.writeHead(200, {
'Content-Type': 'text/html'
});
fs.createReadStream('./page/index.html').pipe(response);
// file not found
} else if (!err && !stats.isFile()) {
response.writeHead(200, {
'Content-Type': 'text/html'
});
fs.createReadStream('./page/404.html').pipe(response);
// error :(
} else if (err) {
response.writeHead(500);
response.end(err.toString());
}
});
}
});
嗯,看起来很工整.下面我们来试着跑一下.
哦,等会,根据刚才 404 页面和首页的位置,我的目录结构应该重构了,就像这样
page
404.html
index.html
favicon.ico
package.json
Procfile
server.js
之后打开监听的端口,看到首页了,favicon 也正常加载,完成!
2 读取文章列表
上一篇我说过,暂时使用单独的文件来存放文章.所以文件名不仅要是文章的标题,还唯一标识着这篇文章对应的文件.
目录又要改了:
archive
my-first-article
my-second-article
page
404.html
index.html
script
index.js
favicon.ico
package.json
Procfile
server.js
读取目录内所有文件,又要用到
fs
模块了.这次用的是
fs.readdir(path[, options], callback)
:
它读取指定目录内所有文件,并把两个参数
(err, files)
传递给回调函数.
files
是一个包含目录内所有文件名的数组.
这样思路就很清晰了!我们读取
archive
下的所有文件,然后解析成 json 发出去就好.我说过要用 Ajax 的嘛. (先别管怎么接收)
这个请求不同于文件请求,是一个 "莫须有" 的路径,需要我们来定义一下.我说了:
/api/index - article - list
这就是 API 路径.好了,现在把他写到服务器中去:
var server = http.createServer((request, response) = >{
// parse the path
var pathName = url.parse(request.url).pathname;
var filePath = path.join(root, pathName);
if (request.method === 'GET') {
// this is a api request
if (pathName.indexOf('/api/') >= 0) {
switch (pathName) {
// api address
case '/api/index-article-list':
// read all archives
fs.readdir('./archive', (err, files) = >{
if (err) {
console.log(err);
} else {
response.writeHead(200, {
'Content-Type': 'application/json'
});
// parse the Array<String> to json string
response.end(JSON.stringify(files));
}
});
break;
default:
break;
}
} else {
// read local file
});
}
}
});
收到请求时,看看它是不是有定义的 API 请求.如果是的话,执行并返回,否则才去找文件.
服务端准备完成了,接下来是页面的动态加载:
贴一下我的
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>
Rocka's Node Blog
</title>
<script src="../script/index.js">
</script>
</head>
<body>
<h1>
Rocka's Node Blog
</h1>
<hr>
<h3>
Blog Archive
</h3>
<ul id="index-article-list">
</ul>
<blockquote id="index-article-content">
Article should be shown here.
</blockquote>
</body>
来源: http://www.cnblogs.com/rocket1184/p/nodejs-heroku-blog-2.html