NodeJS 实现静态 webserver
所谓静态资源就是指 图片,js文件,css文件等常见的文件
在浏览器中显示的内容有 HTML、有 js、有 jpg等等,这些都叫静态资源 ……那么,浏览器是如何区分它们,决定什么内容用什么形式来显示呢?答案是 MIME Type,也就是该资源的媒体类型。
媒体类型通常是通过 HTTP 协议,由 Web 服务器告知浏览器的,更准确地说,是通过 Content-Type 来表示的,例如:
Content-Type: text/HTML
常见的资料类型
"css": "text/css",
"gif": "image/gif",
"html": "text/html",
"ico": "image/x-icon",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"js": "text/javascript",
"json": "application/json",
"pdf": "application/pdf",
"png": "image/png",
"svg": "image/svg+xml",
"swf": "application/x-shockwave-flash",
"tiff": "image/tiff",
"txt": "text/plain",
"wav": "audio/x-wav",
"wma": "audio/x-ms-wma",
"wmv": "video/x-ms-wmv",
"xml": "text/xml"
静态资源服务器的核心:如果服务器收到客户端请求,就去服务器上查找文件,如果存在这个文件就返回这个文件(返回这个文件的时候需要设置资源类型,让浏览器识别这个文件的数据类型)。
需要用的模块
使用到path和fs、http模块的api
path.join([path1][, path2][, ...])
用于连接路径。该方法的主要用途在于,会正确使用当前系统的路径分隔符,Unix系统是"/",Windows系统是"\"。
path.extname(p)
返回路径中文件的后缀名,即路径中最后一个'.'之后的部分。如果一个路径中并不包含'.'或该路径只包含一个'.' 且这个'.'为路径的第一个字符,则此命令返回空字符串。
fs.exists() //判断文件是否存在
fs.readFile()//读取文件数据
let server = http.createServer()//创建服务器
server.listen()//启动并且监听服务器上某个端口
静态服务器核心思想:
1:创建服务器
2:收到客户端请求之后判断有没有个文件。如果有,就去读取文件,然后把文件数据返回给客户端;如果没有这个文件,就会返回404
var http = require('http');
var fs = require('fs')
var url = require('url');
var path = require('path')
var mineType = {
"css": "text/css",
"gif": "image/gif",
"html": "text/html",
"ico": "image/x-icon",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"js": "text/javascript",
"json": "application/json",
"pdf": "application/pdf",
"png": "image/png",
"svg": "image/svg+xml",
"swf": "application/x-shockwave-flash",
"tiff": "image/tiff",
"txt": "text/plain",
"wav": "audio/x-wav",
"wma": "audio/x-ms-wma",
"wmv": "video/x-ms-wmv",
"xml": "text/xml"
}
var server = http.createServer((req, res) => {
console.log('请求的url', req.url)
var pathname = url.parse(req.url).pathname;//获取请求路径
var realpath = path.join('public', pathname);//设置服务器静态资源目录
var ext = path.extname(realpath)//获取文件扩展名 .png .js
ext = ext ? ext.slice(1) : 'unknow'////获取文件扩展名 png js
fs.exists(realpath, (exists) => {//判断这个文件存不存在
if (!exists) {
console.log('不存在这个文件')
res.statusCode == 404;
res.setHeader('content-type', 'text/plain;charset=utf-8')
res.write('找不到这个文件')
res.end()
} else {
var content = mineType[ext]
fs.readFile(realpath, (err, result) => {
res.statusCode == 200;
res.setHeader('content-type', content)
res.write(result);
res.end()
})
}
})
})
server.listen(3000, '127.0.0.1', function () {
console.log('服务器启动成功 http://127.0.0.1:3000')
})
前后端分离与耦合架构
前后端分离“已经成为互联网项目开发的业界标杆,通过Tomcat+Ngnix(也可以中间有个Node.js),有效地进行解耦。并且前后端分离会为以后的大型分布式架构、弹性计算架构、微服务架构、多端化服务(多种客户端,例如:浏览器,车载终端,安卓,IOS等等)打下坚实的基础。
前后端分离(解耦)的核心思想是:前端Html页面通过Ajax调用后端的RestFul API并使用Json数据进行交互。
在互联网架构中,一般有两种服务器
web服务器:一般指像nginx,apache这类的服务器,他们一般只能解析静态资源。
应用服务器:一般指像tomcat,jetty,resin这类的服务器可以解析动态资源也可以解析静态资源,但解析静态资源的能力没有web服务器好。( 一般只有Web服务器才能被外网访问,应用服务器只能内网访问。)
为什么前后端分离?
一般公司后端开发人员直接兼顾前端的工作,一边实现API接口,一边开发页面,两者互相切换着做,而且根据不同的url动态拼接页面,这也导致后台的开发压力大大增加。前后端工作分配不均。不仅仅开发效率慢,而且代码难以维护。而前后端分离的话,则可以很好的解决前后端分工不均的问题,将更多的交互逻辑分配给前端来处理,而后端则可以专注于其本职工作,比如提供API接口,进行权限控制以及进行运算工作。而前端开发人员则可以利用nodejs来搭建自己的本地服务器,直接在本地开发,然后通过一些插件来将api请求转发到后台,这样就可以完全模拟线上的场景,并且与后台解耦。前端可以独立完成与用户交互的整一个过程,两者都可以同时开工,不互相依赖,开发效率更快,而且分工比较均衡
从MVC到前后端分离
未分离时代(各种耦合mvc)
MVC 是一种经典的设计模式,全名为 Model-View-Controller,即 模型-视图-控制器。
大致就是: 模型和视图需要通过 控制器 来进行粘合。例如,用户发送一个 HTTP 请求,此时该请求首先会进入控制器,然后控制器去获取数据并将其封装为模型,最后将模型传递到视图中进行展现。
需要说明的是,这个View还可以采用 Velocity、Freemaker 等模板引擎。使用了这些模板引擎,可以使得开发过程中的人员分工更加明确,还能提高开发效率
早期主要使用MVC框架,在java-web项目中,使用Jsp+Servlet的结构图如下:
那么,在这个时期,开发方式一般采用入下图这种。
ps:如果公司没有前端,那么‘前端工程师根据设计图做成html页面’这一步需要后端人员做。
前后端分离
前端负责开发页面,通过接口(Ajax)获取数据,采用Dom操作对页面进行数据绑定,最终是由前端把页面渲染出来。这也就是Ajax与SPA应用(单页应用)结合的方式,其结构图如下:
步骤如下:
(1)浏览器请求,CDN返回HTML页面;
(2)HTML中的JS代码以Ajax方式请求后台的Restful接口;
(3)接口返回Json数据,页面解析Json数据,通过Dom操作渲染页面;
后端提供的都是以JSON为数据格式的API接口供Native端使用,同样提供给WEB的也是JSON格式的API接口。
现在还有一种前后端分离的模型
前端的范围被扩展,controller层也被认为属于前端的一部分。在这一时期:
前端:负责View和Controller层。
后端:只负责Model层,业务/数据处理等。
可是服务端人员对前端HTML结构不熟悉,前端也不懂后台代码呀,controller层如何实现呢?这就是node.js的妙用了,node.js适合运用在高并发、I/O密集、少量业务逻辑的场景。最重要的一点是,前端不用再学一门其他的语言了,对前端来说,上手度大大提高。
也就是层架nodejs作为中间层(这种方式很多大公司会采用)
从经典的JSP+Servlet+JavaBean的MVC时代,到SSM(Spring + SpringMVC + Mybatis)和SSH(Spring + Struts + Hibernate)的Java 框架时代,再到前端框架(KnockoutJS、AngularJS、vueJS、ReactJS)为主的MV*时代,然后是Nodejs引领的全栈时代,技术和架构一直都在进步。虽然“基于NodeJS的全栈式开发”模式很让人兴奋,但是把基于Node的全栈开发变成一个稳定,让大家都能接受的东西还有很多路要走。创新之路不会止步,无论是前后端分离模式还是其他模式,都是为了更方便得解决需求,但它们都只是一个“中转站”。前端项目与后端项目是两个项目,放在两个不同的服务器,需要独立部署,两个不同的工程,两个不同的代码库,不同的开发人员。前端只需要关注页面的样式与动态数据的解析及渲染,而后端专注于具体业务逻辑。
Express 基础
Express 介绍
Express 是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。
使用 Express 可以快速地搭建一个完整功能的网站。
Express 框架核心特性:
可以设置中间件来响应 HTTP 请求。
定义了路由表用于执行不同的 HTTP 请求动作。
可以通过向模板传递参数来动态渲染 HTML 页面。
express不对node.js本身的特性进行二次抽象 而是在基本功能上进行扩充
express完全是由路由和中间件构成的框架
从本质上来说一个express应用就是为了调用各种中间件
简单的说express可以很快速的让我们使用mvc的方式创建一个web应用
http://www.expressjs.com.cn/ 中文官网
Express 创建服务器
-
全局安装 npm i express -g
-
新建项目文件夹 expressDemo1,并且进入项目,使用npm init 初始化项目
-
本地安装 npm i express -D
创建 app.js
const express = require('express');
const app = express();
//客户端发起get请求 如果请求的路径是/,返回hello world字符串
app.get('/', (req, res) =>
res.send('Hello World!哈哈')
)
//启动服务器 然后我就可以通过IP地址和端口号访问这台服务器了 127.0.0.1:3000 或者 localhost:3000 或者自己电脑的ip:3000
app.listen(3000, () =>
console.log('Example app listening on port 3000!')
)
通过编写一个后端程序需要配置很多东西,配置起来会非常麻烦。每次后后端项目都需要做很多配置,更加麻烦,所以出现了脚手架
使用generator 生成器初始化项目(express脚手架创建项目)
通过应用生成器工具 express-generator 可以快速创建一个应用的骨架。
express-generator 包含了 express 命令行工具。也称脚手架
通过如下命令即可安装
npm install express-generator -g
使用脚手架创建项目
express -e express-demo3
提示如下内容,说明已经创建好一个项目骨架了
create : express-demo3\
create : express-demo3\public\
create : express-demo3\public\javascripts\
create : express-demo3\public\images\
create : express-demo3\public\stylesheets\
create : express-demo3\public\stylesheets\style.css
create : express-demo3\routes\
create : express-demo3\routes\index.js
create : express-demo3\routes\users.js
create : express-demo3\views\
create : express-demo3\views\error.ejs
create : express-demo3\views\index.ejs
create : express-demo3\app.js
create : express-demo3\package.json
create : express-demo3\bin\
create : express-demo3\bin\www
change directory:
$ cd express-demo3
install dependencies:
$ npm install
run the app:
$ DEBUG=express-demo3:* npm start
创建好的骨架中包含的很多东西
目录介绍
create : project_name\public\ 存放静态资源css,图片等
create : project_name\routes\ 存放路由模块
create : project_name\views\ 存放html模板
create : project_name\app.js express 配置
create : project_name\package.json
create : project_name\bin\www 服务器启动配置
然后进入项目
输入npm install 安装依赖
输入 npm start 启动项目
注意执行npm start 会执行 package.json 里scripts 属性里,start对应的命令。
这个叫npm scripts 你也可以自己在终端中输入 node ./bin/www 启动项目,不同的项目执行的文件可能不一样,别人接手你的项目可能不知道到底要执行那个文件,所以我们可以启动项目的命令,放在start属性后面。这样拿到你一个项目,只要你发现里面有start脚本,就可以执行输入npm start 启动项目了
通常情况下你也可以在这里加一些属性,属性值就是自己的想要执行的命令
不过这个时候,你需要输入 npm run dev ,才能执行后面的命令
app.js
app.js 里放express 的配置,是我们需要关注的,大部分都已经生成完毕。我们需要添加和改动的是路由配置
// http错误中间件
var createError = require('http-errors');
var express = require('express');
// 生成路径
var path = require('path');
// 解析cookie中间件
var cookieParser = require('cookie-parser');
// 日志
var logger = require('morgan');
// 路由配置
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// view engine setup 视图(模板)引擎,在views文件夹中查询html模板
app.set('views', path.join(__dirname, 'views'));
// 模板类型 esj
app.set('view engine', 'ejs');
// 日志中间件
app.use(logger('dev'));
// 解析json 中间件
app.use(express.json());
// 解析 编码字符 中间件
app.use(express.urlencoded({ extended: false }));
// 解析cookie 中间件
app.use(cookieParser());
// 静态资源中间件,设置静态资源根目录
app.use(express.static(path.join(__dirname, 'public')));
// 挂载 / 路由
app.use('/', indexRouter);
// 挂载/users路由
app.use('/users', usersRouter);
// 捕获错误
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// 错误处理
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
// 渲染error模板
res.render('error');
});
module.exports = app;//导出模块
静态资源托管
create : express-demo3\public\
create : express-demo3\public\javascripts\
create : express-demo3\public\images\
create : express-demo3\public\stylesheets\
create : express-demo3\public\stylesheets\style.css
静态资源中间件express.static,通过他设置设置静态资源根目录
app.use(express.static(path.join(__dirname, 'public')));
public文件下面存放静态资源,js,css,img 都有对应的目录
路由与中间件
app.use (path,fn)用来设置路由
后面可以设置两个参数,请求的路径如果匹配到了路由,就会执行后面的函数
第一个参数设置路由的路径,不传参数是*默认
第二个参数是回调函数,如果匹配到路由,就会执行后的函数。很多时候后面都是第三方封装好的一个函数,这个函数我们称之为中间件。
app.use(function(req, res, next){
console.log('我是app路由 *')
next()
})
回调函数中的req就是请求,res是服务器的相应,next一旦调用,会把请求交给下一个中间件去处理,如果不调用next请求到这里就结束了,不会把请求传递给其他的中间件了。
一旦用户访问了/users,这个路径,app就把这个路由交给usersRouter模块去处理
这里用到了express内置的路由模块,用来封装路由。
/routes/users.js文件的代码
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
module.exports = router;
用户访问了/users,这个路径,app就把这个路由交给usersRouter模块去处理,也是就是/routes/users.js这个文件去处理,文件内部又定义了 一个 / 路由,其实意思是/users后面的路由地址,如 用户访问/users/ 就会进入这个路由
在文件了增加
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
router.get('/login', function(req, res, next) {
res.send('login');
});
router.get('/resiger', function(req, res, next) {
res.send('reg');
});
module.exports = router;
用户访问/users/login 这个路由 会到 users.js文件的 /login 路由里
用户访问/users/register这个路由 会到 users.js文件的/reg路由里
模板引擎 EJS
什么是模板引擎
模板引擎(Template Engine)是一个将页面模板和要显示的数据结合起来生成 HTML 页面的工具。 如果说上面讲到的 express 中的路由控制方法相当于 MVC 中的控制器的话,那模板引擎就相当于 MVC 中的视图。
模板引擎的功能是将页面模板和要显示的数据结合起来生成 HTML 页面。它既可以运 行在服务器端又可以运行在客户端,大多数时候它都在服务器端直接被解析为 HTML,解析完成后再传输给客户端,因此客户端甚至无法判断页面是否是模板引擎生成的。有时候模板引擎也可以运行在客户端,即浏览器中。目前的主流还是由服务器运行模板引擎。 在 MVC 架构中,模板引擎包含在服务器端。控制器得到用户请求后,从模型获取数据,调用模板引擎。模板引擎以数据和页面模板为输入,生成 HTML 页面,然后返回给控制器,由控制器交回客户端
使用模板引擎
前面我们通过以下两行代码设置了模板文件的存储位置和使用的模板引擎
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
注意:我们通过 express -e projec_name 初始化了一个使用 ejs 模板引擎的工程,比如 node_modules 下添加了 ejs 模块,views 文件夹下有 index.ejs
router.js 放的是路由配置
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
//get请求 / 路径时候 使用 { title: 'Express' } 渲染 views目录下的模板 index模板,把渲染后的数据 返回给客户端
res.render('index', { title: 'Express' });
});
module.exports = router;
在 routes/index.js 中通过调用 res.render() 渲染模版,并将其产生的页面直接返回给客户端。它接受两个参数,第一个是模板的名称,即 views 目录下的模板文件名,扩展名 .ejs 可选。第二个参数是传递给模板的数据对象,用于模板翻译。
index.ejs
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
</body>
</html>
当我们 res.render('index', { title: 'Express' }); 时,模板引擎会把 <%= title %> 替换成 Express,然后把替换后的页面显示给用户。
渲染后生成的页面代码为:
<!DOCTYPE html>
<html>
<head>
<title>Express</title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1>Express</h1>
<p>Welcome to Express</p>
</body>
</html>
注意:我们通过 app.use(express.static(path.join(__dirname, 'public'))) 设置了静态文件目录为 public 文件夹,所以上面代码中的 href='/stylesheets/style.css' 就相当于 href='public/stylesheets/style.css' 。
ejs 语法
ejs 的标签系统非常简单,它只有以下三种标签:
-
<% code %>:JavaScript 代码。
-
<%= code %>:显示替换过 HTML 特殊字符的内容。
-
<%- code %>:显示原始 HTML 内容。
注意: <%= code %> 和 <%- code %> 的区别,当变量 code 为普通字符串时,两者没有区别。
当 code 比如为
<h1>hello<h1>
这种字符串时,<%= code %> 会原样输出
<h1>hello<h1>
,而 <%- code %> 则会显示 H1 大的 hello 字符串。
hello
我们可以在 <% %> 内使用 JavaScript 代码。下面是 ejs 的官方示例:
supplies: ['mop', 'broom', 'duster']
模板
<ul>
<% for(var i=0; i<supplies.length; i++) {%>
<li><%= supplies[i] %></li>
<% } %>
</ul>
渲染结果
<ul>
<li>mop</li>
<li>broom</li>
<li>duster</li>
</ul>
页面布局
使用简单灵活的include(可以一个ejs文件中可以使用include引入另一个ejs)。include 的简单使用如下
index.ejs
<%- include a %>
hello,world!
<%- include b %>
a.ejs
this is a.ejs
b.ejs
this is b.ejs
结果
this is a.ejs
hello,world!
this is b.ejs
Express 文档
https://github.com/expressjs/express github地址
http://www.expressjs.com.cn/ 中文官网
预习MongoDB,安装 MongoDB 数据库软件