NodeJs-02-http及模块化
http模块
绝对路径
| 形式 | 特点 | 场景 |
|---|---|---|
| https://www.baidu.com(注意是完整的 协议://域名: 端口) | 直接向目标资源发送请求,容易理解。网站的外链会用到此形式 | 一般用于网页外部的链接,如网页最下部分 |
| //jd.com/web | 与当前页面 URL 的协议拼接形成完整 URL 再发送请求。比如当前协议是 http + 目标地址 | 大型网站:天猫商品的链接 |
| /web | 与当前页面 URL 的协议、主机名、端口拼接形成完整 URL 再发送请求。如 当前协议 https://saddyfire.cn/ + 目标地址 | 中小型网站 |
相对路径
| 形式 | 最终的 URL |
|---|---|
| ./css/app.css | 相当于当前页面所在目录的 ./css/app.css |
| js/app.js | 同上 |
| …/img/logo.png | 相当于往上走一级 |
| …/…/mp4/show.mp4 | 相当于往上走两级 |
但是相对路径使用较少,是参照当前页面的,所以一旦当前页面路径不正常的时候,使用相对路径获取资源就会出现异常
网页中使用 URL 的场景
- a 标签 href
- link 标签 href
- script 标签 src
- img 标签 src
- video audio 标签 src
- form 中的 action
- AJAX 请求中的 URL
设置mime类型
媒体类型(通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型 )是一种标准,用来表示文档、文件或字节流的性质和格式。
mime 类型结构: [type]/[subType]
例如: text/html text/css image/jpeg image/png application/json
HTTP 服务可以设置响应头 Content-Type 来表明响应体的 MIME 类型,浏览器会根据该类型决定如何处理资源
下面是常见文件对应的 mime 类型
html: 'text/html',
css: 'text/css',
js: 'text/javascript',
png: 'image/png',
jpg: 'image/jpeg',
gif: 'image/gif',
mp4: 'video/mp4',
mp3: 'audio/mpeg',
json: 'application/json'
对于未知的资源类型,可以选择 application/octet-stream 类型,浏览器在遇到该类型的响应时,会对响应体内容进行独立存储,也就是我们常见的 下载 效果
可以做如下判断
// 根据文件扩展名设置正确的Content-Type
const extname = path.extname(filePath)
let contentType = ''
switch (extname) {
case '.html':
contentType = 'text/html; charset=utf-8'
break
case '.css':
contentType = 'text/css'
break
case '.js':
contentType = 'application/javascript'
break
case '.png':
contentType = 'image/png'
break
case '.jpg':
case '.jpeg':
contentType = 'image/jpeg'
break
case '.gif':
contentType = 'image/gif'
break
default:
contentType = 'application/octet-stream'
break
}
res.setHeader('content-type', contentType)
一些常见的网页错误及响应码
进来的时候可以校验请求方式
if (req.method !== 'GET') {
res.statusCode = 405
res.end('<h1>405 Method Not Allowed</h1>')
return
}
404文件不存在,403无访问权限,500服务端错误
if (err) {
if (err.code === 'ENOENT') {
res.statusCode = 404
res.end('<h1>404 Not Found</h1>')
return
} else if (err.code === 'EPERM') {
res.statusCode = 403
res.end('<h1>403 Forbidden</h1>')
} else {
console.error('文件读取错误:', err)
res.statusCode = 500
res.end('<h1>500 Internal Server Error</h1>')
return
}
}
完整代码示例
import http from 'http'
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
// 在ES模块中获取__dirname和__filename
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const server = http.createServer((req, res) => {
if (req.method !== 'GET') {
res.statusCode = 405
res.end('<h1>405 Method Not Allowed</h1>')
return
}
let url = new URL(req.url, 'http://127.0.0.1:9999')
let { pathname } = url
if (pathname === '/') {
pathname = '/index.html'
}
// 修复路径拼接问题,使用path.join而不是字符串拼接
let filePath = path.join(__dirname, 'page', pathname.slice(1)) // 移除开头的斜杠
fs.readFile(filePath, (err, data) => {
if (err) {
if (err.code === 'ENOENT') {
res.statusCode = 404
res.end('<h1>404 Not Found</h1>')
return
} else if (err.code === 'EPERM') {
res.statusCode = 403
res.end('<h1>403 Forbidden</h1>')
} else {
console.error('文件读取错误:', err)
res.statusCode = 500
res.end('<h1>500 Internal Server Error</h1>')
return
}
}
// 根据文件扩展名设置正确的Content-Type
const extname = path.extname(filePath)
let contentType = ''
switch (extname) {
case '.html':
contentType = 'text/html; charset=utf-8'
break
case '.css':
contentType = 'text/css'
break
case '.js':
contentType = 'application/javascript'
break
case '.png':
contentType = 'image/png'
break
case '.jpg':
case '.jpeg':
contentType = 'image/jpeg'
break
case '.gif':
contentType = 'image/gif'
break
default:
contentType = 'application/octet-stream'
break
}
res.setHeader('content-type', contentType)
res.end(data)
})
})
server.listen(9999, () => {
console.log('静态资源服务启动成功, 请访问 http://127.0.0.1:9999')
})
模块化
暴露数据
模块暴露两种方式
- module.exports = value
- exports.name = value
如何正确使用
-
初始关系:在每一个
Node.js模块文件的最开始,隐藏着这样一行关系声明:var exprots = module.exports。此时,它们指向内存中的同一个空对象{}。因此,当使用exports.name = 'value'时,实际上等同于module.exports.name = 'value',是在向同一个对象添加属性。 -
当执行
exports = 'value'时,只是将变量exports本身重新赋值,指向了一个新的值。切断了exports与module.exports之间的引用关系。模块系统认的是module.exports,它并没有受到影响,因此require()得到的仍然是module.exports的初始值(一个空对象{}) -
结论a:需要导出一个函数、一个类、一个字符串或数字等单一实体时:务必使用
module.exportsmodule.exports = function() {}; module.exports = MyClass; module.exports = 'Hello, world'; module.exports = 123; -
结论b:需要到处对象添加多个属性或方法时:可以使用
exports.xxx的简便写法exports.method1 = function() {}; exports.property1 = 'some value'
导入(引入)模块
require的注意事项
- 对于自己创建的模块,导入时路径建议写 相对路径 ,且不能省略
./和../ js和json文件导入时可以不用写后缀,c/c++编写的node扩展文件也可以不写后缀,但是一般用不到- 如果导入其他类型的文件,会以
js文件进行处理 - 如果导入的路径是个文件夹,则会首先检测该文件夹下
package.json文件中main属性对应的文件,如果存在则导入,反之如果文件不存在会报错。如果main属性不存在,或者package.json不存在,则会尝试导入文件夹下的index.js和index.json,如果还是没找到,就会报错 - 导入
node.js内置模块时,直接require模块的名字即可,无需加./和../
导入模块的基本流程
require 导入自定义模块的基本流程
- 将相对路径转为绝对路径,定位目标文件
- 缓存检测
- 读取目标文件代码
- 包裹为一个函数并执行(自执行函数)。通过
arguments.callee.toString()查看自执行函数 - 缓存模块的值
- 返回
module.exports的值
通过伪代码来展示 require 导入流程
/**
* require的流程
* 伪代码
* 1. 检查模块是否已经被导入
* 2. 如果没有被导入,创建一个新的模块对象
* 3. 执行模块的代码,将导出的内容添加到模块对象中
* 4. 返回模块对象
*/
function require(file) {
// 1. 将相对路径转换为绝对路径
let absolutePath = path.resolve(__dirname, file)
// 2. 检查模块是否已经被导入
if (require.cache[absolutePath]) {
return require.cache[absolutePath]
}
// 3. 读取文件
let code = fs.readFileSync(absolutePath).toString()
// 4. 包裹为一个函数,然后执行
let module = {} // 内部声明
let exports = module.exports = {} // 内部声明
(function (exports, require, module, __filename, __dirname) {
eval(code) // 执行模块的代码
})(exports, require, module, absolutePath, path.dirname(absolutePath))
// 5. 缓存模块对象
require.cache[absolutePath] = module.exports
}
CommonJs 规范
module.exports、exports 以及 require 这些都是 CommonJS 模块化规范中的内容。
而 Node.js 是实现了 CommonJS 模块化规范,二者关系有点像 JavaScript 与 ECMAScript
