用http 模块写一个简单的web服务器
http模块在线文档:nodejs.org/dist/latest…
目标
用http模块写一个简单的服务器
要点
-
引入http核心模块
-
使用createServer来创建服务
-
使用listen来启动服务
操作
第一步:新建一个文件,名为 d:/src/http.js( 文件名及路径名可以自行设置,建议均不使用中文字符), 内容如下
// 创建我们第一个web服务器
// 1.0 导入http模块
const http = require('http');
// 2.0 创建一个服务
let server = http.createServer((req, res) => {
console.log('接收到了客户端请求');
res.end('OK');
})
// 3.0 监听8001端口并启动web服务器等待客户端请求
server.listen(8001, () => {
console.log('web服务器准备就绪:127.0.0.1:8001可以访问');
})
第二步:运行js代码。
在小黑窗中进入到d盘根目录,键入命令 node http.js,此时会弹出一个小黑窗,不要关。
第三步:本地验收
打开一个浏览器页面,输入'http://localhost:8081',观察效果:
- 浏览器中的效果
- 小黑窗中的效果
第四步:共享地址
把localhost改成你自己电脑的ip地址,再把这个路径发你的同学(同一个局域网)来访问。
第五步:停止服务
ctrl + c
如果不能访问,有可能你需要手动关闭你自己计算机的防火墙。
工作原理
使用http模块在本机上创建一个虚拟服务器,它来接收浏览器的请求,并给出响应。
注意:
- 小黑窗不要关,它就是服务器,它不会有主动行为(看起来没有任何变化),它在时刻等待客户端的访问。
代码解析
-
引入核心模块,得到的http是一个对象。
-
http.createServer方法创建一个http服务。参数是一个回调函数:当有http请求进来时,它会自动被调用。
请求一次,它就被调用一次。-
第一个参数:
客户端的请求。 -
第二个参数:
设置对本次请求的响应。- res.end() :设置响应体,结束请求。
-
-
server.listen() 用来监听端口。
-
格式:server.listen(端口号,[回调函数]) 。回调是可选的。
-
说明:
- 如果监听成功,则回调函数会执行一次。
- 如果不成功(例如端口被占用),会报错。
-
修改代码后要重启
更改res.end()的内容,重启后,再次观察。
- 停止服务: 在小黑窗中按下ctrl+c 停止服务。
- 重启服务:就是重新运行程序(按下向上的箭头,再回车)。
修改了服务器的代码要重启。
理解请求和响应
请求
当web服务器就绪之后,如果没有客户端来访问它,它也是不会有任何效果的。也就是说回调函数不会执行。
而每一次的请求,都会导致回调函数要执行一次。
服务器的响应内容格式
res.end()的格式只能是buffer或者是String
练习
写一个服务器,让你的同学们来访问一下。
随机返回内容
开启多台服务器
复制一份代码,并启动,更改端口号,模拟多台服务器。
不同URL返回不同的内容-认识URL
全称
Uniform Resource Locator,统一资源定位符。
作用
定位资源(css,html,js,png, avi,接口......)。
格式
协议://主机地址[:端口]/路径?查询字符串#锚点
-
协议: http 或者是 https
-
主机地址: IP地址 或者 域名
-
端口号
- http请求,默认端口80(可以省略)
- https请求,默认端口443(可以省略)
- MySQL默认端口3306
-
路径
- 服务器文件夹上的资源。(.html/.css/.images/.js/接口)
-
参数(查询字符串)
- ? 后面的部分,是键值对的形式
-
锚点
- 网页内部的锚点链接
经典用法:访问文件时传递参数。
// index.html
<a href='detail.html?id=1'>新闻1</a>
<a href='detail.html?id=2'>新闻2</a>
上面的两个地址在指向detail.html时,分别传入了不同的参数
// detail.html
<script>
// 1. 获取id
// 2. 根据id值去获取这个新闻的详情
</script>
不同的URL返回不同的内容-问题分析
思路:在服务器端收到客户端发的请求之后,分析url是什么,然后分别对应处理
不同的URL返回不同的内容-req.url
目标
通过 req.url来获取当前请求的url地址 ,并做出相应处理
req.url属性
首先就需要知道浏览器请求的url是什么。
涉及到和请求相关的信息,都是通过请求响应处理函数的第一个参数完成的。
代码示例
const http = require('http');
// 创建服务
const server = http.createServer(function(req, res) {
console.log(req.url)
res.end(req.url)
});
// 启动服务
server.listen(8081, function() {
console.log('success');
});
req.url用来获取本次请求的资源地址。在请求行中可以看到这一点。
小结
| 序号 | 浏览器中的url | 服务器中的req.url |
|---|---|---|
| 1 | http://localhost:8080 | / |
| 2 | http://localhost:8080/a.html | /a.html |
| 3 | http://localhost:8080/js/jquery.js | /js/jquery.js |
| 4 | http://localhost:8080/1.jpg | /1.jpg |
| 5 | http://localhost:8080/api?a=1&b=2 | /api?a=1&b=2 |
req.url一定是以/开头的。
在现代浏览器中,它们会自动去请求服务器上的favicon.ico
不同的URL返回不同的内容-读取文件内容并返回
目标
用户在访问服务器上不同的url时,能返回不同的内容
目录结构
|-index.html
|-style.css
|-01.png
|-js/jquery.js
|-server.js
请求与响应的对应的关系
| 用户的请求地址 | 服务器的动作 |
|---|---|
| http://localhost:8000 | 读出index.html的内容并返回 |
| http://localhost:8000/index.html | 读出index.html的内容并返回 |
| http://localhost:8000/style.css | 读出style.css的内容并返回 |
| http://localhost:8000/01.png | 读出01.png的内容并返回 |
| http://localhost:8000/js/jquery.js | 读出jquery.js的内容并返回 |
| http://localhost:8000/xxxx 或者不是上面的地址 | 返回404 |
示例代码
// 1.0 导包
const http = require('http');
const fs = require('fs');
const path = require('path');
// 2.0 创建服务器对象并且设置好回调函数
let server = http.createServer((req, res) => {
// 1.0 根据不同路径响应不同的数据内容
// 如果浏览器请求的是 /index.html 这个路径,那么就读取这个index.html中的内容通过res.end()响应即可
// 如果浏览器请求的是 /style.css 这个路径,那么久读取style.css通过res.end()响应即可
// 获取路径
let urlPath = req.url;
console.log('urlPath==>', urlPath);
if (urlPath === '/index.html') {
// 读取index.html文件的内容响应回去
let htmlString = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');
// 防止低版本浏览器不能自动解析内容,则手动设置
res.setHeader('content-type', 'text/html;charset:utf8');
// res.end() 响应
res.end(htmlString)
} else if (urlPath === '/style.css') {
// 读取到了当前目录下的style.css的内容
let styleString = fs.readFileSync(path.join(__dirname, 'style.css'), 'utf8');
// 防止低版本浏览器不能自动解析内容,则手动设置
res.setHeader('content-type', 'text/css;charset:utf8');
// 响应
res.end(styleString);
} else {
// 资源找不到,状态码应该设置为404,同时响应一段文本告诉用户资源找不到
res.statusCode = 404;
res.setHeader('content-type', 'text/html;charset=utf8')
res.end('您要的资源去了火星,找不到咯');
}
})
// 3.0 启动服务器
server.listen(8000, () => {
console.log('服务器已经在8000端口准备就绪,等待客户端访问');
})
综合使用fs, path, __dirname
注意
-
url地址与服务器上的文件地址并不是一一对应的。url的作用是确定用户要访问的资源的位置,在地址栏中输入回车之后,这个请求会到web服务器中来,然后由web服务器来决定此时返回什么数据给用户。但是,我们能够根据url来推测服务器会返回什么信息吗?
url:http://nodejs.cn/api/querystring.html 请求一个页面,名是querystring.html url:http://oa.itcast.cn/seeyon/main.do?method=main url:https://mail.qq.com/cgi-bin/frame_html?sid=aLqnlljMxF54DgtW&r=d281ced83329f34caae9786fcb5d4934显然,不能,你能从服务器上获得什么,完全是由服务器决定的。
-
如果用户请求的是静态资源(静态资源指的是html文件中链接的外部资源,如.html, css、js、image文件等等),服务器的处理方法就是:读出文件内容,返回给用户。
不同的URL返回不同的内容-设置content-type
目标
- 理解content-type的作用
- 会根据不同的文件类型来设置不同的content-type
content-type的作用
在http协议中,content-type用来告诉对方本次传输的数据的类型是什么。
- 在请求头中设置content-type来告诉服务器,本次请求携带的数据是什么类型的
- 在响应头中设置content-type来告诉服务器,本次返回的数据是什么类型的
通过使用res对象中的setHeader方法,我们可以设置content-type这个响应头。这个响应头的作用是告诉浏览器,本次响应的内容是什么格式的内容,以方便浏览器进行处理。
常见的几种文件类型及content-type
- .html:
res.setHeader('content-type', 'text/html;charset=utf8') - .css:
res.setHeader('content-type', 'text/css;charset=utf8') - .js:
res.setHeader('content-type', 'application/javascript') - .png:
res.setHeader('content-type', 'image/png') - json数据:
res.setHeader('content-type', 'application/json;charset=utf-8')
其它类型,参考这里:developer.mozilla.org/en-US/docs/…
如果读出来的是.html的文件,但是content-type设置为了css。则浏览器将不会当作是html页面来渲染了。
示例代码
不同的URL返回不同的内容-设置statusCode
目标:了解statusCode的作用
会正确使用statusCode
res.statusCode = 301;
// res.setHeader('location','http://www.qq.com')
res.statusCode = 404
res.statusCode = 500
res.end()
处理.html文件中的二次请求
二次请求
从服务器获取html文件之后,如果这个html文件中还引用了其它的外部资源(图片,样式文件等),则浏览器会重新再发请求。
假设在index.html中还引入了 style.css 1.png 或者 .js文件,则:浏览器请求localhost:8000/index.html之后,得到的从服务器反馈的内容,解析的过程中还发现有外部的资源,所以浏览器会再次发出第二次请求,再去请求相应的资源。
思路
一个最朴素的想法是枚举不同的请求来返回不同的文件。
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
h1{
text-align: center;
}
</style>
+ <link rel="stylesheet" href="./style.css">
</head>
<body>
<h1>index.html</h1>
+ <img src="./01.png" alt="">
</body>
</html>
上面的+号只是表示这里的代码需要修改。
const http = require('http');
const fs = require('fs');
const path = require('path');
//创建服务器
const app = http.createServer((req, res) => {
if (req.url === '/index.html') {
let htmlString = fs.readFileSync(path.join(__dirname, 'index.html'));
res.end(htmlString);
}
else if (req.url === '/style.css') {
let cssString = fs.readFileSync(path.join(__dirname, 'style.css'));
res.setHeader('content-type', 'text/css');
res.end(cssString);
} else if (req.url === '/1.png') {
let pngString = fs.readFileSync(path.join(__dirname, '/1.png'));
res.end(pngString);
} else {
res.setHeader('content-type', 'text/html;charset=utf-8');
res.statusCode = 404;
res.end('<h2>可惜了, 找不到你要的资源' + req.url + '</h2>');
}
});
//启动服务器,监听8082端口
app.listen(8082, () => {
console.log('8082端口启动');
});
作业
const http = require('http');
const fs = require('fs');
const path = require('path');
let server = http.createServer((req, res) => {
let urlPath = req.url;
if (urlPath === '/index.html') {
let htmlString = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');
// 防止低版本浏览器不能自动解析内容,则手动设置
res.setHeader('content-type', 'text/html;charset=utf8');
// res.end() 响应
res.end(htmlString)
} else if (urlPath === '/style.css') { // 读取css文件
// 读取到了当前目录下的style.css的内容
let styleString = fs.readFileSync(path.join(__dirname, 'style.css'), 'utf8');
// 防止低版本浏览器不能自动解析内容,则手动设置
res.setHeader('content-type', 'text/css;charset=utf8');
// 响应
res.end(styleString);
} else if (urlPath === '/images/bg.jpg') {
// 读取图片内容 ,img 格式:
let img = fs.readFileSync(path.join(__dirname, '/images/bg.jpg'));
res.setHeader('content-type', 'image/jpg');
// 将img响应回去
res.end(img);
}
else if (urlPath === '/images/fm.jpg') {
// 读取图片内容 ,img 格式:
let img = fs.readFileSync(path.join(__dirname, '/images/fm.jpg'));
res.setHeader('content-type', 'image/jpg');
// 将img响应回去
res.end(img);
}
else {
res.statusCode = 404;
res.setHeader('content-type', 'text/html;charset=utf8')
res.end('您要的资源去了火星,找不到咯');
}
})
server.listen(8002, () => {
console.log('启动');
})
批量处理请求
问题分析
由于我们无法事先得知一个.html文件中会引用多少个静态资源(.png, .css, .js....),所以,我们不能像处理某个页面一样去处理它们。
我们的解决办法是:
-
把所有的静态资源(.html,.png,.css,.js)全放在一个指定的目录里;
-
收到用户的请求之后,去指定的目录下去找对应的文件
- 找到,把内容读出来返回给用户。
- 找不到,报404。
目录如下:
|-public
|-public/index.html
|-public/stye.css
|-public/01.png
|-server.js
在上面的目录结构中,我们把所有的静态资源全放在public下面,然后使用server.js来启动web服务器。
参考代码
// 当收到用户的请求之后
// 在public下去读这个文件
// 读到:返回
// 读不到:返回404
const http = require('http')
const path = require('path')
const fs = require('fs')
// 放一个对象,其中保存所有的 后缀名与content-type的一一对应关系
const mapExtToContentType = {
'.html': 'text/html;charset=utf8',
'.jpg': 'image/jpg',
'.css': 'text/css;charset=utf8',
'.js': 'application/javascript',
'.ico': 'image/ico'
}
const server = http.createServer((req, res) => {
const { url } = req
// 获取后缀名
const ext = path.extname(url)
console.log(url, ext)
// 1. 拼接地址
const filePath = path.join(__dirname, 'public', url)
// 2. 读资源文件
try{
const content = fs.readFileSync(filePath)
console.log(content)
// 根据不同的后缀名,补充不同的content-type
// .html ---> text/html;charset=utf8
// .jpg ---> image/jpg
if(mapExtToContentType[ext]) {
res.setHeader('content-type', mapExtToContentType[ext])
}
// mapExtToContentType[ext] && res.setHeader('content-type', mapExtToContentType[ext])
res.end(content)
} catch (err) {
console.log(err)
res.statusCode = 404
res.end('404')
}
})
server.listen(8001, () => {
console.log('你的服务器在8001就绪');
})
理解静态资源与接口的区别
服务器上有很多的资源,每个资源都有自己的url。客户端浏览器想要访问某个资源就要向服务器发起对应的请求。
资源的分类
-
静态资源
- 它们一般表现为一个一个的文件。例如index.html, style.css, index.js, mp4, .png....。
- 处理请求静态资源时,服务器一般就直接读出资源的内容,再返回给客户端浏览器
-
动态资源:接口
- 它们不是以某个具体的文件存在的,而是服务器上的一段代码,访问接口时,服务器会执行这段代码,然后把代码的执行结果返回给客户端浏览器。
发送请求的途径
- 在地址栏中直接访问这个url
- 通过某个a标签进行进行跳转
- 通过表单进行提交
- 通过ajax技术访问这个url
发送请求的类型
- get:在地址栏中直接访问这个url就是get方式。对于静态资源,我们通过的处理方式就是get请求。
- post: 通过表单提交,可以设置form的method为post
- delete
- put
- patch
- options
- ......
写一个不带任何参数的get类型接口
目录结构
|-db
|---data.json # 数据
|-server.js # 你的代码
目标
在server.js中写代码,提供一个名为getList的接口(http://localhost:8083/getList),它以json字符串格式返回db/data.json的内容。并通过使用postman软件进行测试。
参考代码
// server.js
const http = require('http');
const app = http.createServer((req, res) => {
if (req.url === '/getmsg' && req.method=== 'GET') {
const filePath = path.join(__dirname, 'db', 'data.json')
let content = fs.readFileSync(filePath, 'utf8')
res.end(content);
} else {
res.end('error');
}
});
app.listen(8083, () => {
console.log(8083);
});
说明:
- 注意:类型
- req.method 可以判断请求的类型
- res.end()的参数只能是字符串(或者是buffer),而不能是对象
postman的使用说明
postman的作用
前端人员一定要会用它!它用来去测试接口。
下载地址+汉化:gitee.com/hlmd/Postma…
带参数的get请求-获取查询字符串中的数据
目标
提供一个名为getList的接口(http://localhost:8083/getList?name=xxxx),它以json字符串格式返回db/data.json中name为xxxx的数据
使用postman软件进行测试。
目录结构
|-db
|---data.json # 数据
|-server.js # 你的代码
参考代码
// server.js
const http = require('http');
const app = http.createServer((req, res) => {
const [url, queryStr] = req.url.split('?')
if (url === '/getmsg' && req.method=== 'GET') {
// 从queryStr中拆解出来name
// ....
} else {
res.end('error');
}
});
app.listen(8083, () => {
console.log(8083);
});
思路: 1、首先测试的请求的地址是: http://127.0.0.1:8004/getHeroSkin?name=后裔
为了实现上面的测试地址能得到正确结果,需要编写
-
三个步骤开启nodejs服务器
-
在第二步的回调函数中编写逻辑
- 解析出url中的查询参数字符串 let [pathname,query] = req.url.split('?')
- 利用 let urlsearch = new URLSearchParams(query) 实例化参数对象
- 利用 urlsearch.get() 方法获取参数值
-
读取data.json文件内容,使用JSON.parse() 转换成js数组
-
使用filter方法结合2.3中获取到的参数过滤出需要的数据通过res.end()响应回去
nodejs中的querystring模块
用来对url中的查询字符串这部分进行处理。nodejs中提供了querystring这个核心模块来帮助我们处理这个需求。
示例
const qs= require('querystring');
let obj = qs.parse('id=18&name=zs');
console.log(obj) // {id:18, name:"zs"}
URLSearchParams 解析参数
URLSearchParams可以将查询字符串解析成一个对象,通过get方法获取到指定参数值
参考网址:developer.mozilla.org/zh-CN/docs/…
示例
let query = 'name=后裔'
// 通过urlSearchParams解析参数值
let urlSearch = new URLSearchParams(query);
// 通过get方法获取到查询字符串name参数的值
let namevalue = urlSearch.get('name');
post接口
目录结构
|-db
|---data.json # 数据 [{"name": "小李"},{"name": "小张"}]
|-server.js # 你的代码
目标
在server.js中写代码,提供一个名为add的接口(http://localhost:8083/add),它以post的方式请求接口,并传入name值,把数据保存到db/data.json中去。
使用postman软件进行测试。
预备知识
post类型与get类型的接口区别较大,主要在两个方面:
-
类型不同: 可以通过 req.method 来获取(GET,POST)
-
传参不同
- get请求参数在
请求行中(附加在url后面),内容比较少。 - post请求参数在
请求体中。内容比较大(上传图片,上传文件....)。
- get请求参数在
对于获取post参数就相对复杂一些。它的特点是:
-
参数在请求体中发给后端
-
后端是一段一段接收数据的,并不像get在请求行中传递的数据:直接写在url中的查询字符串内,可以立即通过req.url来解析出来。
-
在接收参数的过程中,会涉及req对象的两个事件
data,end。- data事件,每次收到一部分参数数据就会触发一次这个事件。
- end事件,全部的参数数据接收完成之后会执行一次。
基本流程
基本流程是:
- 定义一个空容器result来装参数。
- 在req对象上监听data事件。这个事件触发一次,就把当次收到的数据向result中填充一些。
- 在req对象上监听end事件。这个事件触发时,就表示整个参数数据接收完成,此时取出容器result中的内容,进行解析,以取出参数。