文章目录
NodeJs WEB服务器 静态文件托管、 路由、EJS模板引擎、GET、POST
NodeJs WEB服务器
前面已经完成了一个简单的web服务器,是node最简单的形式
// 表示引入http模块
var http = require('http');
// 创建一个服务器,回调函数表示接收到请求之后做的事情
http.createServer(function (request, response) {
// req参数表示请求,res表示响应
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Hello World'); // End 方法使 Web 服务器停止处理脚本并返回当前结果
}).listen(8081);
console.log('Server running at http://127.0.0.1:8081/');
Nodejs 静态文件托管
现在需要把上面的代码修改一下,让其能够访问服务器static下的html文件,如果没有则写出"404这个页面不存在"(加入没有加上.html,则默认找index.html)
var http = require('http');
var fs = require('fs'); // 返回数据内容需要
http.createServer(function (req, res) {
//1、获取地址
let pathname = req.url;
pathname = pathname == '/'?'/index.html' : pathname;
//2、通过fs模块读取文件
if(pathname!='/favicon.ico'){
fs.readFile('./static'+pathname,(err, data) => {
if(err){
res.writeHead(404, {'Content-Type': 'text/html;charset="utf-8"'});
res.end('404这个页面不存在');
}
res.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
res.end(data);
});
}
}).listen(8081);
console.log('Server running at http://127.0.0.1:8081/');
但是这样有一个问题, 当访问js和css代码的时候因为有
Content-Type': 'text/html
而在页面渲染时候失败
所以需要一个动态判断的函数,新建一个node_modules文件夹,创建一个common.js文件,内容如下
exports.getMime = function (extname) {
switch (extname) {
case '.html':
return 'text/html';
case '.css':
return 'text/css';
case '.js':
return 'text/javascript';
default:
return 'text/html';
}
};
再来修改一下上面的内容
var http = require('http');
var fs = require('fs');
var common = require('common');
var url = require('url');
var path = require('path');
http.createServer(function (req, res) {
//1、获取地址
// let pathname = req.url;
// pathname = pathname == '/'?'/index.html' : pathname;
let pathname = url.parse(req.url).pathname; // 遇到类似http://127.0.0.1:8081/json/all.json?788351035676484的,可以直接去掉?后面的一串
pathname = pathname == '/' ? '/index.html' : pathname;
//可以获取后缀名path.extname()
let extname = path.extname(pathname);
//2、通过fs模块读取文件
if(pathname!='/favicon.ico'){
fs.readFile('./static'+pathname,(err, data) => {
if(err) {
res.writeHead(404, {'Content-Type': 'text/html;charset="utf-8"'});
res.end('404这个页面不存在');
}
let mime = common.getMime(extname);
// res.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
res.writeHead(200, {'Content-Type': '' + mime + ';charset="utf-8"'});
res.end(data);
});
}
}).listen(8081);
console.log('Server running at http://127.0.0.1:8081/');
由于页面有json的请求,请求格式是
http://127.0.0.1:8081/json/all.json?8757847100278133
因此使用了url模块进行分割
但是现在对图片的响应还是没有对,所以继续来修改,新建一个data文件夹,里面有一个mime.json,里面是各种文件后缀对应的响应头
在common.js文件中,新增一个函数getFileMime,内容如下
exports.getFileMime = function(extname) {
return new Promise((resolve, reject) => {
fs.readFile('./data/mime.json', (err, data)=>{
if(err){
console.log(err);
reject(err);
return;
}
let mimeObj=JSON.parse(data.toString());
// console.log(mimeObj[extname]);
resolve(mimeObj[extname]);
});
});
};
因为getFileMime是异步函数,所以在readFile的回调函数中需要加上async,这时的app.js内容如下
var http = require('http');
var fs = require('fs');
var common = require('common');
var url = require('url');
var path = require('path');
http.createServer(function (req, res) {
//1、获取地址
// let pathname = req.url;
// pathname = pathname == '/'?'/index.html' : pathname;
let pathname = url.parse(req.url).pathname; // 遇到类似http://127.0.0.1:8081/json/all.json?788351035676484的,可以直接去掉?后面的一串
pathname = pathname == '/' ? '/index.html' : pathname;
//可以获取后缀名path.extname()
let extname = path.extname(pathname);
//2、通过fs模块读取文件
if(pathname!='/favicon.ico'){
fs.readFile('./static'+pathname, async (err, data) => {
if(err) {
res.writeHead(404, {'Content-Type': 'text/html;charset="utf-8"'});
res.end('404这个页面不存在');
}
// let mime = common.getMime(extname);
let mime = await common.getFileMime(extname);
// res.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
res.writeHead(200, {'Content-Type': '' + mime + ';charset="utf-8"'});
res.end(data);
});
}
}).listen(8081);
console.log('Server running at http://127.0.0.1:8081/');
再来执行一下,访问8081端口,可以看到访问图片的响应头已经正确了
接下來,还可以使用readFile的异步版本,这样就可以就可以在主函数中省略很多代码了,当然也可以选择修改主函数的readFile
exports.getFileMimeSync = function(extname) {
var data = fs.readFileSync('./data/mime.json'); // 同步方法
let mimeObj=JSON.parse(data.toString());
return mimeObj[extname];
};
路由
官方解释:路由(Routing)是由一个 URI(或者叫路径)和一个特定的 HTTP 方法(GET、POST 等)组成的,涉及到应用如何响应客户端对某个网站节点的访问。
非官方解释: 路由指的就是针对不同请求的URL,处理不同的业务逻辑。
上面已经完成了一个静态的web服务器创建,下面对他进行修改,封装一下想用一句话都创建web服务
新建一个route.js,内容如下,主要是把原来在app.js中的内容移过去了
var url = require('url');
var path = require('path');
var fs = require('fs');
let getFileMimeSync = function(extname) {
var data = fs.readFileSync('./data/mime.json'); // 同步方法
let mimeObj=JSON.parse(data.toString());
return mimeObj[extname];
};
exports.static = function (req, res, staticPath) {
//1、获取地址
let pathname = url.parse(req.url).pathname;
pathname = pathname == '/' ? '/index.html' : pathname;
let extname = path.extname(pathname);
//2、通过fs模块读取文件
if (pathname != '/favicon.ico') {
try {
let data = fs.readFileSync('./' + staticPath + pathname);
if (data) {
let mime = getFileMimeSync(extname);
res.writeHead(200, { 'Content-Type': '' + mime + ';charset="utf-8"' });
res.end(data);
}
} catch (error) {
}
}
};
现在app.js的内容如下
var http = require('http');
var routes = require('routes');
http.createServer(function (req, res) {
routes.static(req, res, 'static');
}).listen(8081);
console.log('Server running at http://127.0.0.1:8081/');
整个文件夹存放如下图
初识EJS模块引擎
我们学的EJS是后台模板,可以把我们数据库和文件读取的数据显示到Html页面上面。它是一个第三方模块,
需要通过npm安装https://www.npmjs.com/package/ejs,安装:
npm init --yes
npm install ejs -save
Nodejs中使用:
ejs.renderFile(filename, data, options, function(err, str){ }
// str => Rendered HTML string });
EJS常用标签
- 流程控制标签
- 输出标签(原文输出HTML标签)
- 输出标签(HTML会被浏览器解析)
使用例子如下,在上面已经完成的web封装服务器中,修改app.js代码如下
var http = require('http');
var routes = require('routes');
var url = require('url');
var ejs = require('ejs');
http.createServer(function (req, res) {
routes.static(req, res, 'static');
//路由
let pathname=url.parse(req.url).pathname;
if(pathname=='/login') {
let msg="数据库里面获取的数据";
let list=[
{
title:'新闻111'
},
{
title:'新闻222'
},
{
title:'新闻3333'
},
{
title:'新闻4444'
},{
title:'新闻5555'
}
];
ejs.renderFile('./views/login.ejs', {
msg:msg,
list:list
},(err, data) => {
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end(data);
});
}
}).listen(8081);
console.log('Server running at http://127.0.0.1:8081/');
新建一个views文件夹,里面存放一个login.ejs(在项目中已经安装了ejs依赖),内容如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>这是一个登录页面</h2>
<h3><%=msg%></h3>
<br>
<ul>
<%for(var i=0; i<list.length;i++) {%>
<li><%=list[i].title%></li>
<%}%>
</ul>
</body>
</html>
执行此app.js,浏览器中访问链接,显示的内容如下
Get、Post
超文本传输协议(HTTP)的设计目的是保证客户端机器与服务器之间的通信。
在客户端和服务器之间进行请求-响应时,两种最常被用到的方法是:GET 和 POST。
GET - 从指定的资源请求数据。(一般用于获取数据)
POST - 向指定的资源提交要被处理的数据。(一般用于提交数据)
获取GET传值:
// 获取get传值----http://127.0.0.1:8081/news?username=zeng&age=47
var query = url.parse(req.url,true).query; // 获取请求对象加上true
console.log("req.url:", req.url, " query.username", query.username); // zeng
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('get传值获取成功');
获取POST传值:
else if(pathname == '/doLogin'){
//获取post传值,post触发data事件
let postData = '';
// var buffers = [];
req.on('data', (chunk)=>{
postData += chunk;
});
// 读取完data数据触发一个end事件
req.on('end', ()=>{
console.log(postData, "post");
res.end(postData);
});
整个测试例子,views文件夹中新建一个form.ejs,内容如下,接收表单数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="/doLogin" method="post">
用户名:<input type="text" name="username" />
<br>
<br>
密 码: <input type="password" name="password" />
<br>
<br>
<input type="submit" value="提交">
</form>
</body>
</html>
app.js现在的内容如下
var http = require('http');
var routes = require('routes');
var url = require('url');
var ejs = require('ejs');
var querystring = require('querystring');
http.createServer(function (req, res) {
routes.static(req, res, 'static');
// 路由
let pathname = url.parse(req.url).pathname;
// 获取请求类型
console.log(req.method);
if(pathname == '/news') {
// 获取get传值----http://127.0.0.1:8081/news?username=zeng&age=47
var query = url.parse(req.url,true).query; // 获取请求对象加上true
console.log("req.url:", req.url, " query.username", query.username); // zeng
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('get传值获取成功');
}
else if(pathname == '/login') {
//post演示
ejs.renderFile("./views/form.ejs",{},(err, data)=>{
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end(data);
});
}
else if(pathname == '/doLogin'){
//获取post传值,post触发data事件
let postData = '';
// var buffers = [];
req.on('data', (chunk)=>{
postData += chunk;
// buffers.push(chunk);
});
// 读取完data数据触发一个end事件
req.on('end', ()=>{
console.log(postData, "post");
res.end(postData);
// 解析post参数
// console.log(buffers, "post");
// let requestBody = Buffer.concat(buffers).toString();
// requestBody = querystring.parse(requestBody)
// console.log(requestBody.password);
// res.end(requestBody.username);
});
}
else{
res.writeHead(404, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end("页面不存在");
}
}).listen(8081);
console.log('Server running at http://127.0.0.1:8081/');
测试页面分别为:
http://127.0.0.1:8081/news?username=zeng&age=47
http://127.0.0.1:8081/login
Nodejs路由封装封装一个类似express的路由
模块化的方式封装
整体思想是把上面在app.js的内容都放到route.js中,让其在app.js中能按照指定的目标页进行返回数据
修改route.js的内容
var url = require('url');
var path = require('path');
var fs = require('fs');
var ejs = require('ejs');
let app = {
static: (req, res, staticPath) => {
//1、获取地址
let pathname = url.parse(req.url).pathname;
pathname = pathname == '/' ? '/index.html' : pathname;
let extname = path.extname(pathname);
//2、通过fs模块读取文件
if (pathname != '/favicon.ico') {
try {
let data = fs.readFileSync('./' + staticPath + pathname);
if (data) {
let mime = getFileMime(extname);
res.writeHead(200, { 'Content-Type': '' + mime + ';charset="utf-8"' });
res.end(data);
}
} catch (error) {
}
}
},
login: (req, res) => {
ejs.renderFile('./views/form.ejs',{},(err, data)=>{
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end(data)
})
},
news: (req, res) => {
res.end('news');
},
doLogin: (req, res) => {
//获取post传值
let postData = '';
req.on('data',(chunk)=>{
postData+=chunk;
})
req.on('end',()=>{
console.log(postData);
res.end(postData);
})
},
error: (req, res) => {
res.end('error');
}
}
module.exports=app; // 模块接口的暴露
那么此时app.js就变得简单了
var http = require('http');
var routes = require('routes');
var url = require('url');
http.createServer(function (req, res) {
routes.static(req, res, 'static');
// 路由
// let pathname = url.parse(req.url).pathname;
let pathname=url.parse(req.url).pathname.replace("/","");
// 获取请求类型
console.log(req.method);
try {
routes[pathname](req, res);
} catch (error) {
routes['error'](req, res);
}
}).listen(8081);
console.log('Server running at http://127.0.0.1:8081/');
封装仿照express的路由一
第一个版本
这是一个最普通的函数调用的例子
let app=function(){
console.log('调用app方法')
}
app.get=function(){
console.log('get方法')
}
app.post=function(){
console.log('post方法')
}
app.get();
最终目标时以这样的方式配置路由
app.get("/", function(req, res){
res.send('hello world.')
})
第二个版本
修改一下代码,在一个全部变量G中注册一下函数++++++++++++暂时留着一会回来看
let G = {};
let app = function(req,res){
console.log('调用app方法')
if(G['/login']){
G['/login'](req, res); //执行方法
}
}
app.get = function(str, cb){
// 注册方法
console.log('注册方法')
G[str]=cb;
// 实际操作就是
// G["/login"] = function (req, res) {
// // res.send('hello world')
// console.log("执行login方法");
// })
}
// 方法的具体实现,上面只是注册对应的函数
app.get("/login", function (req, res) {
// res.send('hello world')
console.log("执行login方法");
})
app('req','res');
第三个版本
再来修改一下,app.js可以更简单,目前只是支持/login,修改后让他支持更多路由,新加一个routes.js,放在node_modules文件夹下,内容如下
var url = require("url");
let G = {};
let app = function(req, res) {
let pathname = url.parse(req.url).pathname;
if(G[pathname]) {
G[pathname](req,res); //执行方法
}
else {
res.writeHead(404, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('页面不存在');
}
}
app.get = function(str, cb){
// 注册方法
console.log('注册方法');
G[str]=cb;
}
module.exports = app; // 模块接口的暴露
这是app.js代码修改为
var http = require("http");
var app = require('routes.js')
//注册web服务
http.createServer(app).listen(8081);
//配置路由
app.get('/', function(req,res){
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('首页');
})
//配置路由
app.get('/login', function(req,res){
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('执行登录操作');
})
//配置路由
app.get('/news', function(req,res){
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('新闻页面');
})
这样在浏览器就能支持如下链接了
http://127.0.0.1:8081/
http://127.0.0.1:8081/login
http://127.0.0.1:8081/news
封装仿照express的路由二
继续让封装的代码支持post
在views文件夹下新建一个form.ejs,内容如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="/doLogin" method="post">
用户名:<input type="text" name="username" />
<br>
<br>
密 码: <input type="password" name="password" />
<br>
<br>
<input type="submit" value="提交">
</form>
</body>
</html>
routes.js代码如下,主要是提供注册对应的post和get函数到let G中(还能解决post与get路由重名被覆盖的问题),如果请求为已经注册的路由名,则调用之前注册function
var url = require("url");
let server = function () {
let G = {};
//把get 和 post分开
G._get={};
G._post={};
let app = function (req, res) {
let pathname = url.parse(req.url).pathname;
//获取请求类型
let method=req.method.toLowerCase();
console.log(method);
if (G['_'+method][pathname]) {
if(method == "get"){
G['_'+method][pathname](req, res); //执行方法
}else{
//post 获取post的数据 把它绑定到req.body
let postData = '';
req.on('data', (chunk)=>{
postData+=chunk;
})
req.on('end', ()=>{
req.body=postData;
G['_'+method][pathname](req, res); //执行方法
})
}
} else {
res.writeHead(404, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('页面不存在');
}
}
app.get = function (str, cb) {
//注册方法
G._get[str] = cb;
}
app.post = function (str, cb) {
//注册方法
G._post[str] = cb;
}
return app;
}
module.exports = server(); // 模块接口的暴露
app.js内容如下,大部分的内容为所注册路由的具体实现
var http = require("http");
var app=require('routes2');
var ejs = require("ejs");
//注册web服务
http.createServer(app).listen(8081);
//配置路由
app.get('/', function(req, res){
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('首页');
})
//配置路由
app.get('/login',function(req, res){
ejs.renderFile("./views/form.ejs",{},(err,data)=>{
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end(data);
})
})
app.post('/doLogin',function(req, res){
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('执行登录操作');
})
加上req.body
在响应是已经有了on触发的data、end事件,只需要把他转发一下
let postData = '';
req.on('data', (chunk)=>{
postData+=chunk;
})
req.on('end', ()=>{
req.body=postData;
G['_'+method][pathname](req, res); //执行方法
})
在具体的dologin路由中输出一下,看是否正确
app.post('/doLogin',function(req, res){
console.log(req.body);
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('执行登录操作');
})
将固定writeHead封装起来
在routes.js中新加一个function,在server中进行注册,注册后,res就能扩展使用send函数了
function changeRes(res){
res.send = (data)=>{
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end(data);
}
}
在原来的server中添加注册
let server = function () {
...
let app = function (req, res) {
// 扩展res的方法
changeRes(res);
...
}
...
}
把app.js中调用writeHead和end的部分都替换成send
var http = require("http");
var app=require('routes2');
var ejs = require("ejs");
//注册web服务
http.createServer(app).listen(8081);
//配置路由
app.get('/', function(req, res){
// res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
// res.end('首页');
res.send("首页"); // 调用扩展后的res.send方法
})
//配置路由
app.get('/login',function(req, res){
ejs.renderFile("./views/form.ejs",{},(err,data)=>{
res.send(data);
})
})
app.post('/doLogin',function(req, res){
res.send(req.body);
})
封装仿照express的路由三—支持css等静态路由
想要让服务器支持css、js等目录,就需要封装静态web服务,把之前的data文件夹和里面的mime.json文件放到文件中,在routes.js代码中进行整合
var url = require("url");
var path = require("path");
var fs = require('fs');
function changeRes(res) {
res.send = (data) => {
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end(data);
}
}
//根据后缀名获取文件类型
function getFileMime(extname) {
var data = fs.readFileSync('./data/mime.json'); //同步方法
let mimeObj = JSON.parse(data.toString());
return mimeObj[extname];
}
//静态web服务的方法
function initStatic(req, res, staticPath) {
//1、获取地址
let pathname = url.parse(req.url).pathname;
pathname = pathname == '/' ? '/index.html' : pathname;
let extname = path.extname(pathname);
//2、通过fs模块读取文件
try {
let data = fs.readFileSync('./' + staticPath + pathname);
if (data) {
let mime = getFileMime(extname);
res.writeHead(200, { 'Content-Type': '' + mime + ';charset="utf-8"' });
res.end(data);
}
} catch (error) {
}
}
let server = function() {
let G = {
_get : {},
_post : {},
staticPath : 'static' //,默认静态web目录
};
let app = function(req, res) {
// 扩展res的方法
changeRes(res);
initStatic(req, res, G.staticPath)
let pathname = url.parse(req.url).pathname;
//获取请求类型
let method=req.method.toLowerCase();
console.log(method);
if (G['_'+method][pathname]) {
if(method == "get"){
G['_'+method][pathname](req, res); //执行方法
}else{
//post 获取post的数据 把它绑定到req.body
let postData = '';
req.on('data', (chunk)=>{
postData+=chunk;
})
req.on('end', ()=>{
req.body=postData;
G['_'+method][pathname](req, res); //执行方法
})
}
} else {
res.writeHead(404, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('页面不存在');
}
}
app.get = function (str, cb) {
//注册方法
G._get[str] = cb;
}
app.post = function (str, cb) {
//注册方法
G._post[str] = cb;
}
// 配置静态web服务目录
app.static = function(staticPath){
G.staticPath = staticPath;
}
return app;
}
module.exports = server(); // 模块接口的暴露
新建一个static文件夹存放style.css文件,内容如下
h2 {
color: red;
}
在form.ejs中引入上面的css文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./css/style.css" />
<title>Document</title>
</head>
<body>
<h2>登录页面</h2>
<form action="/doLogin" method="post">
用户名:<input type="text" name="username" />
<br>
<br>
密 码: <input type="password" name="password" />
<br>
<br>
<input type="submit" value="提交">
</form>
</body>
</html>
这时app.js文件内容如下
var http = require("http");
var app=require('routes2');
var ejs = require("ejs");
// 注册web服务
http.createServer(app).listen(8081);
// 如果静态资源不在默认的static文件夹下,则使用
// app.static("public")
// 配置路由
app.get('/', function(req, res){
res.send("首页"); // 调用扩展后的res.send方法
})
// 配置路由
app.get('/login',function(req, res){
ejs.renderFile("./views/form.ejs",{},(err, data)=>{
res.send(data);
})
})
app.post('/doLogin',function(req, res){
console.log(req.body);
res.send(req.body);
})
现在css已经能使用了