node.js 学习笔记3 HTTP
path模块
path模块主要用于操作路径。要使用path,首先需要引入path模块。require('path')
path.resolve
用于拼接规范的绝对路径。
如果想拼接一个路径,有时候是使用字符串手动拼接的,但由于系统的规范不同,路径中的\和/无法统一,有时候手动拼接的字符串路径并不标准,path.resolve可以提供一种标准的绝对路径。
path.resolve的参数是要拼接的路径。path.resolve中,相对路径可以省略./,但不能以/开头,以/开头的路径会被识别为绝对路径,作为根路径与其他路径参数进行拼接。
const path = require('path');let myPath = path.resolve(__dirname,'./demo.txt');
path.sep
获取操作系统的路径分隔符
windows的分隔符是\
linux的分隔符是/
const tmp = path.sep;
path.parse
解析路径并返回对象
__filename:和__dirname一样是全局变量,里面保存的是文件所在文件夹的绝对路径。
const path = require('path');const tmp = path.parse(__filename);
console.log(tmp);
root:文件盘符;dir:文件所在文件夹的路径;base:文件名;ext:扩展名;name:文件名。
path.basename
获取文件名,对应parse里的base。
path.dirname
获取文件所在文件夹的路径,即parse里的dir。
path.extname
获取扩展名,即parse里的ext。
HTTP协议
HTTP:Hypertext Transfer Protocol超文本传输协议。
HTTP是互联网应用最广泛的协议之一。
协议:双方必须遵守的一组约定。
HTTP协议:对浏览器和服务器之间的通信进行了约束。
请求:浏览器向服务器发起的通信。
响应:服务器给浏览器返回的结果。
请求报文:浏览器给服务器发送的数据。
响应报文:服务器给浏览器返回的数据。
请求报文和响应报文都是HTTP报文。
当在浏览器中敲入URL,按回车时,浏览器就给对应的服务器发送了请求。
HTTP报文:
想要查看HTTP报文,可以借助工具,一个可以查看HTTP报文的工具是fiddler。fiddler的原理是,在安装了fiddler之后,浏览器会把请求发给fiddler,fidder把请求转发给服务器。服务器响应时,也把响应传给fiddler,fiddler转发给浏览器。
fiddler的下载:Download Fiddler Web Debugging Tool for Free by Telerik
官网填一下信息就能下载了。下载一路Next和agree就可以。
打开软件后,点击导航栏Tools下的options,勾选Decrypt HTTPS traffic按钮,并在弹窗的时候选择yes。然后重启fiddler。
重新进入后,可以选择只看浏览器请求,在左下角点击All Processes,选择Web Browsers。
如果想清空列表,可以点击上面导航栏里的X,选择Remove all。
想查看一个报文时,双击列表里的报文,就能在右侧看到报文信息:
信息上面是请求报文Request,下面是响应报文Response。
原始请求/响应报文,可以点击Raw查看。
按照提示点击Response body is encoded.Click to decode,可以自动解码,让响应报文的信息不再是乱码。
HTTP请求报文的结构:
以请求百度为例,点击请求报文的raw,获取如下信息:
GET https://www.baidu.com/ HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
sec-ch-ua: "Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Sec-Purpose: prefetch;prerender
Purpose: prefetch
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9
Cookie: BDUSS_BFESS=tJWE0tSExLdERyYzd-WnJYY29TY3E4NjBHbWkzUG9PWDY3TXNWbHNZeUVQS3htRVFBQUFBJCQAAAAAAAAAAAEAAAB5dRPqza~E6sPmsPx0b3BhegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAISvhGaEr4RmeG; PSTM=1733825412; BIDUPSID=04F444AAF0FB821D9DB875112E6F48D0; BD_UPN=12314753; MAWEBCUID=web_SEdQpQMWNWJYnmjuCByJwhagZSZOaTHKPCtmgSwfuNPUFTOFxs; BAIDUID=BE4A6D99C0702A98AA324A96A68ED83E:SL=0:NR=10:FG=1; H_WISE_SIDS_BFESS=62325_62969_63144_63195_63210_63243_63253_63357_63378_63383_63186_63394_63393_63390_63404; BDRCVFR[feWj1Vr5u3D]=I67x6TjHwwYf0; delPer=0; BD_CK_SAM=1; PSINO=2; BAIDUID_BFESS=BE4A6D99C0702A98AA324A96A68ED83E:SL=0:NR=10:FG=1; channel=baidusearch; baikeVisitId=f15c073a-0ffd-49e0-b898-40144ff8ef9c; BA_HECTOR=0gal21800k858k8k2ha50ha02gal271k9c5bl25; ZFY=L5NO43JVWWU3MBZjCkIsDKlDxVFGdwgXI2KmQMmDaeI:C; H_PS_PSSID=62325_63144_63947_64011_64127_64173_64248_64245_64258_64260_64269_64308_64318_64326_64366_64362_64363_64403_64413_64427; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; H_WISE_SIDS=62325_63144_63947_64011_64127_64173_64248_64245_64258_64260_64269_64308_64318_64326_64366_64362_64363_64403_64413_64427; sug=3; sugstore=0; ORIGIN=0; bdime=0; H_PS_645EC=49earUefVsN%2ByYhzjL%2FWcXOSkQUH045XqWgq7TUmq1bdcgOP3TfA%2Bf%2FF7XUPvfcklN8e; Hm_lvt_aec699bb6442ba076c8981c6dc490771=1754359961,1754490996,1754610718,1754750394; Hm_lpvt_aec699bb6442ba076c8981c6dc490771=1754750394; HMACCOUNT=4DF589D7A7F85E98; BDSVRTM=0; COOKIE_SESSION=34_0_6_8_10_8_0_0_6_7_0_1_42_0_42_0_1754750434_0_1754750392%7C9%232283914_3_1754268230%7C2
HTTP报文由三部分组成:请求行,对应上面的Get https://www.baidu.com/ HTTP/1.1。请求行是报文的第一行内容。
第二行开始到空行处是请求头,对应上面Host行到Cookie行。
剩下的内容是请求体,本次请求目前请求体是空的,所以空行后没有内容。
请求头和请求体之间有一行空行。
请求行:
GET https://www.baidu.com/ HTTP/1.1
请求行由三部分组成:请求方法(GET)、URL(https://www.baidu.com/)、HTTP版本号(HTTP/1.1)。
请求方法:
HTTP请求常见的请求方法有:GET(获取数据)、POST(新增数据)、PUT/PATCH(更新数据)、DELETE(删除数据)。
还有一些平常比较少用的方法:HEAD、OPTIONS、CONNECT、TRACE。
GET的应用场景:在地址栏中直接输入URL访问、点击a链接、link标签引入css、script标签引入js、video与audio引入多媒体;img标签引入图片;form标签中method为get、ajax的get请求。
POST的应用场景:form标签中method为post;ajax的POST请求。
GET和POST的区别:
1.作用不同,GET主要用来获取数据,POST主要用来提交数据(但这不是绝对的,也可以通过POST获取数据,通过GET提交数据)。
2.参数的位置不同,GET的参数是将参数放在URL中,而POST请求的参数是在请求体中(但这也不是绝对的,GET请求也可以设置请求体,POST请求也可以在URL中传参,只是一般不会这么用)。
3.安全性不同:POST相对GET来说安全一些,因为GET请求时,参数会暴露在地址栏中。
4.GET请求的大小有限制,一般为2K,而POST请求的大小没有限制。
URL:
Uniform Resource Locator 统一资源定位符
URL是一个字符串,用于定位服务器中的资源。服务器中有很多资源,URL用于定位其中的某一个资源,服务器找到URL对应的资源后返回结果。
URL的组成:
以下面的路径为例:
https://mp.csdn.net:80/mp_blog/creation/editor?spm=1001.2014.3001.4503
https://mp.csdn.net:80/mp_blog/creation/editor?spm=1001.2014.3001.4503
协议名:https
主机名:mp.csdn.net
端口号:80
路径:/mp_blog/creation/editor
查询字符串:?spm=1001.2014.3001.4503
URL中,端口号之后,?之前的部分是路径,路径用于定位服务器中的某一个资源。
查询字符串是?及后面的部分,格式是key1=value1&key2=value2&key3=value3……,查询字符串用于向服务器传递参数,是对请求资源额外的一种描述。
URL有绝对路径和相对路径两种形式。
绝对路径:绝对路径有三种形式,一种是整个完整的路径;一种是省略协议的URL(省略协议时,会把当前页面的协议和URL拼接形成完整的URL);一种是省略URL、IP地址、端口号的URL,以\开头(省略URL、IP地址、端口号时,会把当前页面的URL、IP地址、端口号和以\开头的URL进行拼接,形成完整的URL)。
在使用省略URL、IP、端口号的绝对路径时,可以避免当前请求服务器主机名改变时,需要修改URL的问题。
相对路径:
相对路径也有多种形式,以./开头的路径;直接就是文件路径没有./;../开头的路径。
使用相对路径时,需要与当前URL页面路径进行计算,得到完整的URL之后,再发送请求。
比如当前的目录是http://www.text.com/login/myLogin,如果请求的URL是../img/login.png,最终的请求路径是http://www.text.com/login/img/login.png。
如果请求URL是text.css,或./text.css最终的请求路径是http://www.text.com/login/myLogin/text.css。
相对路径是不可靠的,在实际开发中,很少使用相对路径。
URL使用场景:a href、link href、script src、img src、video src、audio src、form action、ajax url等,在这些地方,URL可以是相对路径,也可以是绝对路径。
HTTP版本号:
HTTP版本:1.0(1996年发布)、1.1(1999年发布)、2(2015年发布)、3(2018年发布)
请求头:
请求头是以一系列的key(键名):value(键值)对组成。
请求头中可以记录浏览器相关的一些信息:
User-Agent:记录了浏览器的平台、版本号等。
Accept:记录了浏览器能够处理的数据类型。
Accept-Encoding:记录了浏览器支持的压缩方式。
Accept-Language:记录浏览器支持的语言。
Cookie:用于会话控制。
除了浏览器信息,请求头中还有交互的行为信息:
Connection: Keep-alive。设置保持连接通道。
Upgrade-Insecure-Requests:1。设置升级HTTP请求为HTTPS,提高通信的安全性。
请求头中还可以记录和请求体相关的信息。
请求体:
请求体的内容格式非常灵活,可以设置任意内容。
请求体使用比较多的格式,是一个数组或者对象,且请求体是json格式。
HTTP响应报文:
HTTP/1.1 200 OK
Connection: keep-alive
Content-Security-Policy: frame-ancestors 'self' https://chat.baidu.com http://mirror-chat.baidu.com https://fj-chat.baidu.com https://hba-chat.baidu.com https://hbe-chat.baidu.com https://njjs-chat.baidu.com https://nj-chat.baidu.com https://hna-chat.baidu.com https://hnb-chat.baidu.com http://debug.baidu-int.com https://sai.baidu.com https://mcpstore.baidu.com https://mcpserver.baidu.com https://www.mcpworld.com https://platform-openai.now.baidu.com;
Content-Type: text/html; charset=utf-8
Date: Sat, 09 Aug 2025 14:50:32 GMT
Server: BWS/1.1
Set-Cookie: H_PS_PSSID=62325_63144_63947_64011_64127_64173_64248_64245_64258_64260_64269_64308_64318_64326_64366_64362_64363_64403_64413_64427; path=/; expires=Sun, 09-Aug-26 14:50:32 GMT; domain=.baidu.com
Traceid: 1754751032349825665010009797973434738610
X-Ua-Compatible: IE=Edge,chrome=1
X-Xss-Protection: 1;mode=block
Content-Length: 654507<!DOCTYPE html><!--STATUS OK--><html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta content="origin-when-cross-origin" name="referrer"><meta name="theme-color" content="#ffffff"><meta name="description" content="全球领先的中文搜索引擎、致力于让网民更便捷地获取信息,找到所求。百度超过千亿的中文网页数据库,可以瞬间找到相关的搜索结果。"><link rel="shortcut icon" href="https://www.baidu.com/favicon.ico" type="image/x-icon" /><link rel="search" type="application/opensearchdescription+xml" href="/content-search.xml" title="百度搜索" /><link rel="stylesheet" data-for="result" href="https://pss.bdstatic.com/r/www/static/font/cosmic/pc/cos-icon_3ff597f.css"/><link rel="icon" sizes="any" mask href="https://www.baidu.com/favicon.ico"><link rel="dns-prefetch" href="//dss0.bdstatic.com"/><link rel="dns-prefetch" href="//dss1.bdstatic.com"/><link rel="dns-prefetch" href="//ss1.bdstatic.com"/><link rel="dns-prefetch" href="//sp0.baidu.com"/><link rel="dns-prefetch" href="//sp1.baidu.com"/><link rel="dns-prefetch" href="//sp2.baidu.com"/><link rel="dns-prefetch" href="//pss.bdstatic.com"/><link rel="apple-touch-icon-precomposed" href="https://psstatic.cdn.bcebos.com/video/wiseindex/aa6eef91f8b5b1a33b454c401_1660835115000.png"><title>百度一下,你就知道</title><style index="newi" type="text/css">#form .bdsug{top:39px}.bdsug{display:none;position:absolute;width:535px;background:#fff;border:1px solid #ccc!important;_overflow:hidden;box-shadow:1px 1px 3px #ededed;-webkit-box-shadow:1px 1px 3px #ededed;-moz-box-shadow:1px 1px 3px #ededed;-o-box-shadow:1px 1px 3px #ededed}.bdsug li{width:519px;color:#000;font:14px arial;line-height:25px;padding:0 8px;position:relative;cursor:default}.bdsug li.bdsug-s{background:#f0f0f0}.bdsug-store span,.bdsug-store b{color:#7A77C8}.bdsug-store-del{font-size:12px;color:#666;text-decoration:underline;position:absolute;right:8px;top:0;cursor:pointer;display:none}.bdsug-s .bdsug-store-del{display:inline-block}.bdsug-ala{display:inline-block;border-bottom:1px solid #e6e6e6}.bdsug-ala h3{line-height:14px;background:url(//www.baidu.com/img/sug_bd.png?v=09816787.png) no-repeat left center;margin:6px 0 4px;font-size:12px;font-weight:400;color:#7B7B7B;padding-left:20px}.
.......
*** FIDDLER: RawDisplay truncated at 262144 characters. Right-click to disable truncation. ***
HTTP响应报文也由三部分组成:响应行、响应头、响应体。
在响应头和响应体之间有空行。
响应行:
HTTP/1.1 200 OK
由三部分组成:HTTP版本号(HTTP/1.1)、响应状态码(200)、响应状态的描述(OK)
响应状态码:
是三位的数字,用来标识响应的结果状态。
常见响应状态码:200(请求成功)、403(禁止请求)、404(找不到资源)、500(服务器内部错误)。
响应状态码的分类:1xx(信息响应)、2xx(成功响应)、3xx(重定向消息)、4xx(客户端错误响应)、5xx(服务端错误响应)。
通过状态码,可以得知请求的结果。
响应状态的描述:
响应状态的描述是一个字符串,他与响应状态码是保持一一对应的。
200(OK)、403(Forbidden)、404(Not Found)、500(Internal Server Error)。
响应状态码的具体含义可以到MDN文档上查询。
响应头:
记录了与服务器相关的信息:
Server:记录了服务器使用的技术。
Data:记录了响应的时间。
记录了与响应体相关的信息:
Content-Type:声明响应体应用的格式与字符集。
Content-Length:记录响应体的长度,单位是字节。
响应头同样可以到MDN上查询。如果某个响应头在MDN上无法搜到,这个响应头是服务器自定义的,用于传输个性化数据。
响应体:
响应体与请求体类似,格式十分灵活,可以传递各种信息。
常见的响应体有:HTML、CSS、JavaScript、图片、视频、JSON等。
IP
IP类似于快递的收件地址,在网络世界中,IP地址用于寻找网络设备。
IP也叫IP地址,是一个数字标识。
IP是32位的二进制数字,IP的表示方式,是把每8位(一字节)分为一组,再把8位二进制转换为十进制数字,各组之间用点分隔。
IP地址的作用:标识网络中的设备,实现设备间的通信。
每一个接入网络的设备,手表、音响、电视、主机等,都有自己的IP地址。有了IP地址,通信双方就可以通过IP找到对方。设备和设备之间才能通信。
IP共享技术:
32位二进制数据最多能表示2的32次方的IP地址,即42(亿)94967296个。
但这个数字远远小于世界人口,如果人手一个设备,IP地址是不够的,尤其是现在社会下,很多人往往拥有多个设备。
在32位IP不够用的情况下,提出了一些共享IP的技术。
区域共享:在一个区域下的设备,所有设备共享一些IP。
家庭共享:家庭里的设备,所有设备共用一个IP。
比如在家庭里,所有的设备(手机、电脑、打印机、显示器)都连接(通过WIFI或者物理线路)到路由器,路由器给每个设备都分配一个IP地址,而路由器本身也有一个IP地址,就形成了一个局域网。路由器分配的IP地址叫局域网IP,也叫私网IP。在局域网中,设备之间可以互相通信。如果想和其他网络通信,路由器就需要接入互联网。通过到通信公司(连通、移动)等办理业务,可以接入互联网。会从外部拉一根线到路由器上,接到这根线之后,路由器会有另一个IP,这个IP被称为广域网IP(或叫公网IP),有了公网IP,就可以和外部的网络通信。
每一个家庭都可以按照同样的方式分配IP,每个家庭内部都可以按同样的IP分配IP地址,进行IP的复用(实现局域网复用)。再通过不同的公网IP,就可以实现相互的通信。
本地回环IP地址:
这个IP一直指向当前的设备本身。
IP分类:
本机回环IP地址:127.0.0.1~127.255.255.254
局域网IP(私网IP):192.168.0.0~192.168.255.255、172.16.0.0~172.31.255.255、10.0.0.0~10.255.255.255
广域网IP(公网IP):除本机回环IP地址和局域网IP的IP
以上分类只是一个笼统的分类,如果想了解IP的分类细节,需要去学习IP标准分类。
端口
端口是应用程序的数字标识。
一台计算机有65536个端口,端口号的范围是0~65535。
一个应用程序可以使用一个或多个端口。
端口的作用:实现不同主机应用程序之间的通信。也就是说,当需要与一台计算机通信时,通过IP可以找到这台计算机,但是数据具体应该传输给哪个程序,是通过端口确定的。
HTTP模块
通过node.js创建一个HTTP服务:有了HTTP服务,就可以处理浏览器发送过来的HTTP请求,并且给浏览器发送响应。
HTTP服务创建步骤:
1.导入HTTP模块
const http = require('http')
2.创建服务对象
http.createServer() 用于创建服务对象,返回结果是一个对象。
http.createServer接收一个参数,该参数是一个函数,函数的参数是request(请求)、response(响应)。request是一个对象,是浏览器发来的请求报文的封装对象。借助于这个对象,可以获取请求报文的信息。response是对响应报文的封装对象,可以用response给浏览器设置响应信息。该函数当服务器端接收到HTTP请求时执行。
const server = http.createServer((request,response)=>{
response.end('Hello world')
})
response.end()用于设置响应体,并结束响应。
3.监听端口,启动服务
server.listen()。
参数是(端口号,回调函数),端口号是给当前服务器端设置的端口,回调函数在服务器端启动成功时调用。
server.listen(5005,()=>{
console.log('server start');
})
const http = require('http');const server = http.createServer((request,response)=>{response.end('hello world');}
)server.listen(5005, ()=>{console.log('server start!');
} )
当有人往服务器IP:5005发送请求时,这个请求会被交给当前开启的服务器处理。在http.createServer中配置的参数函数(回调函数)会被执行。
用fiddler查看这个请求:
启动服务之后如何停止服务:在命令行敲Ctrl+c
当服务器端的代码有更新,需要保存代码,重新启动服务器之后,才能生效。
响应体中如果有中文,会出现乱码:
这是由于字符集设置有问题,可以通过response.setHeader设置响应头,这个方法接收两个参数,第一个参数是响应头的名字,第二个参数是该key的value值。
在这里需要设置content-type为text/html;charset=utf-8。这个响应头content-type可以告知浏览器响应体内容是text/html,字符集是utf-8。
const http = require('http');const server = http.createServer((request,response)=>{response.setHeader('content-type','text/html;charset=utf-8');response.end('你好世界');}
)server.listen(5005, ()=>{console.log('server start!');
} )
如果当前服务器设置的端口号已经被其他程序使用了,服务器无法正常启动,启动会报错。可以把占用端口的程序停掉,空出这个端口解决问题。也可以更换当前服务器的端口为一个没有程序使用的端口。
借助资源监视器,可以找到占用端口的程序。在开始里面搜索资源监视器并打开:
点击网络,在侦听端口界面根据找到对应的程序,记住对应程序的PID:
打开任务管理器,找到对应的PID,把程序停掉:
HTTP协议的默认端口是80。在给服务器发送请求时,如果不添加端口号,则端口号是默认端口80。
HTTPS协议默认端口是443。
HTTP开发常用的端口:3000、8080、8090、9000等。
浏览器查看HTTP报文:
在浏览器中点击F12,打开开发者窗口。点击网络(NetWork),可以查看网站使用过程中的所有报文信息。
favicon.ico:该请求是谷歌浏览器的默认行为,每次打开网页时,都会发送这个请求去获取网页图标。网页图标:网页页签上的图标。
点击想查看的请求,右边会出现请求报文和响应报文信息:
请求体内容可以在载荷(payload)中查看。GET请求没有payload。
URL中的查询字符串(?key1=value1&key2-value2&……)也可以在载荷里查看。
响应体在响应(Response)里查看。
在实际开发中,一般都是通过浏览器查看报文,不需要使用额外的工具。
提取HTTP请求报文:
本小节介绍如何在node.js服务器中提取报文信息。在浏览器把请求发送给后端时,后端服务器为了给浏览器返回正确的结果,就需要根据请求报文获取相关信息。
在http.createServer回调函数的参数request中,获取请求报文的相关信息。
request.method
可以获取请求报文中,请求行的请求方法,也就是GET、POST等。
request.httpVersion
可以获取请求报文中,请求行的HTTP协议版本号。
request.headers
可以获取请求报文的请求头。
返回的结果是一个对象,对象中包含请求头中的所有内容。
对象中,key的字母都是小写字母。
如果只想获取某个具体的请求头,可以通过request.headers.key获取。
提取URL:
request.url
可以获取请求报文中,请求行的URL。
URL中只包含URL路径和查询字符串。
如果想获取请求中的URL信息,虽然也可以使用request的url属性获取,但这种方式获取的路径有时候并不能满足开发需求,可以使用Url模块,更好地解析URL路径。
url模块
1.导入Url模块
const url = require('url');
2.使用url模块
url.parse(request.url); 用于提取request.url中的URL信息。
url.parse接收两个参数,第一个参数是需要解析的URL,第二个参数是设置URL中的query属性如何解析,如果是true,query属性是一个对象。
const http = require('http');
const url = require('url');
const server = http.createServer((request,response)=>{console.log(url.parse(request.url));}
)server.listen(9000, ()=>{console.log('server start!');
} )
pathname:URL路径。
query:查询字符串。如果调用url.parse时,第二个参数不传,或者为false,则返回的是字符串。如果是true,则返回对象。
设置url.parse第二个参数为true:
[Object:null prototype]只是一个提示,表示这个对象的原型指向null。
通过res.query.key,可以获取query的value。
除了通过url模块获取URL,还可通过实例化URL对象获取URL数据:
实例化URL对象
let url = new URL(URL地址)
URL地址可以有两种形式:1.一个完整的URL地址;2.分成两个参数,第一个参数是路径和查询字符串,第二个参数是协议、IP和端口号。
const http = require('http');const server = http.createServer((request,response)=>{const url = new URL('http://127.0.0.1:9000'+request.url);const url2 = new URL(request.url,'http://127.0.0.1:9000');console.log(url);console.log(url2);}
)server.listen(9000, ()=>{console.log('server start!');
} )
在URL中,serch和serchParams存储着查询字符串,search是字符串的形式,而searchParams是一种类对象的形式,如果想通过searchParams获取key对应的value,语法是url.searchParams.get(key)。
获取请求体
写一个简单的HTML,给服务器发送POST请求:
<body><form action="http://127.0.0.1:9000" method="post"><input type="text" name="name"><input type="password" name="pawd"><input type="submit" value="提交"></form>
</body>
1.给request绑定事件:
request.on('data',回调函数)
request请求体对象是一个可读流对象,可以通过可读流把request中的数据读取。
let bodyData = '';
request.on('data',(chunk)=>{
bodyData += chunk;
});
chunk本身是一个Buffer,但是当使用+操作时,chunk会自动转换为字符串。
2.绑定end事件
end事件在可读流读完后会进行回调。可以在函数中设置响应等。
request.on('end',()=>{
response.end('....');
})
const http = require('http');const server = http.createServer((request,response)=>{let bodyData = '';request.on('data',(chunk)=>{bodyData += chunk;})request.on('end',()=>{response.end(bodyData);})}
)server.listen(9000, ()=>{console.log('server start!');
} )
根据URL路径不同返回不同的结果:
在以上的例子中,不管请求路径是什么,都默认返回http.createServer的回调函数。但在实际开发中,需要根据不同的请求路径,返回不同的数据。
实际上,如果需要返回不同的内容,也可以都写在createServer函数中。
如果需要通过method方法,以及请求地址判断返回的内容,先通过request.method获取方法,再通过request.url获取请求路径。然后再进行判断,就能实现根据不同的路径返回不同的结果。
由于网站会自动请求favicon.ico,所以最好在服务器端配置一下对favicon.ico的处理,否则浏览器发给服务器端请求,服务器端没有处理的逻辑,请求也不会返回,会一直和服务器端建立联系,占用资源。
完整代码:
const http = require('http');const server = http.createServer((request,response)=>{let url = request.url;let method = request.method;console.log(method);console.log(url);if(method=='GET' && url=='/login'){response.end('login page');}else if(method=='GET' && url=='/register'){response.end('register page');}else{response.end('404 not found');}}
)server.listen(9000, ()=>{console.log('server start!');
} )
设置HTTP响应报文
本小节主要介绍response的属性及方法。
通过response来设置响应结果。
response.statusCode
设置响应状态码,比如response.statusCode = 203;
statusCode默认是200。
response.statusMessage
设置响应状态的描述,比如response.statusMessage = 'Not Found';
不过,一般情况下,响应状态的描述和响应状态码都是一一对应的,不需要手动设置。
response.setHeader()
设置响应头,第一个参数是响应头的key,第二个参数是value。
response.setHeader('Server','Node.js');
key是大小写都可以。
除了设置标准的响应头,也可以设置自定义响应头。
response.setHeader('demo','123');
setHeader也可以设置同名响应头response.setHeader('demo',[1,2,3]);在这种情况下,会返回三个key都是demo的响应头,value分别为1,2,3。
response.write()
设置响应体。
里面的参数就是响应体本身。在返回时,会把response.end和response.write的内容拼接在一起返回。
response.write可以多次调用。返回的响应体是多个write的拼接。
const http = require('http');const server = http.createServer((request,response)=>{response.write('123');response.write('456');response.write('123');response.end('89');}
)server.listen(9000, ()=>{console.log('server start!');
} )
response.end()
可以用于设置响应体。
在回调函数中,一次返回只能设置一个response.end。
在响应内容中,可以设置响应HTML代码。
可以把以下HTML代码放在end中返回:
<!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><table><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>4</td><td>5</td><td>6</td></tr><tr><td>7</td><td>8</td><td>9</td></tr></table><style>table,td,th{border: 1px solid black;}table,td{border-collapse: collapse;}td{padding: 10px 20px;}table tr:nth-child(odd){background-color: burlywood;}table tr:nth-child(even){background-color: blueviolet;}</style><script>let tds = document.querySelectorAll('td');tds.forEach(item=>{item.onclick = function(){this.style.background = 'blue';}})</script>
</body>
</html>
const http = require('http');const server = http.createServer((request, response) => {response.end(`<!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><table><tr><td>1</td>...}})</script></body></html>`);}
)server.listen(9000, () => {console.log('server start!');
})
但通过这种方法返回HTML数据时,返回的HTML没有代码高亮和语法提示,在开发时十分不便,可以把HTML代码写在HTML文件中,然后在response.end()中,返回HTML文件。
思路是通过fs模块读取HTML文件,然后作为end的内容返回。
const http = require('http');
const fs = require('fs');const server = http.createServer((request, response) => {let html = fs.readFileSync(__dirname + '/demo.html');response.end(html);}
)server.listen(9000, () => {console.log('server start!');
})
通过这种方式返回HTML数据,一个是可以更好地编写返回的HTML数据,一个是当修改HTML中的内容时,不需要重启服务器也可以返回新的页面,因为每次请求读取的是HTML文件中的数据,而HTML文件中的数据已经更新了。
通过fs.readFileSync读取的数据格式是Buffer,response.end也可以处理Buffer数据,因此不需要手动做类型转换。
错误类型设置:
当请求发生错误时,如何返回准确的错误类型:
可以根据读取文件回调函数的err判断:
fs.readFile(filePath,(err,data)=>{
if(err){
response.setHeader('content-type','text/html;charset=utf-8');
switch(err.code){
case 'ENOENT':
response.statusCode = 404;
response.end(404错误页面对应的HTML文件);
case 'EPERM':
response.statusCode = 403;
response.end(403禁止访问页面对应的HTML文件);
...
default:
reponse.statusCode = 500;
response.end(500对应页面的HTML);
}
}
})
网页资源加载的过程:
当通过浏览器访问一个网页时,网页会向服务器端发送多次请求。
在输入完URL之后,敲击回车,首先浏览器会向服务器发送一个请求当前URL页面的请求:
返回后,浏览器会对返回的HTML进行解析,当解析到外部的资源时,比如css资源等,浏览器会向服务器再次发送请求,请求CSS资源等。
当CSS的数据返回后,浏览器会对CSS进行解析,让CSS中设置的样式控制浏览器页面的样式。
当碰到图片资源请求时,浏览器也会向服务器请求图片资源,当服务器端返回图片之后,浏览器会渲染并呈现图片。
当碰到外部JS资源时,浏览器会向服务器端发起请求,请求JS资源。
也就是说,当浏览器请求数据时,并不是一次性就把数据全部请求到,而是在解析内容时,发现有资源需要向服务器请求时,多次发送请求。且多个请求之间并不是串行的,是并行的,也就是请求某个资源时,并不是一定要等前面的资源请求完毕才可以,如果资源之间没有关联,可以并行请求多个资源。
当一个HTML页面中引用了外部CSS和JS,如何通过response.end返回:
如果一个HTML中通过link ref等方法引入了外部CSS,通过javascript src引入了外部JS,在response.end中,就不能直接返回HTML文件了。因为在这种情况下,即使是浏览器向服务器请求css或Js数据,服务器也会返回HTML文件。
如果想正确返回数据,应该调整createServer的逻辑:
当页面请求css时,请求路径会是一个css文件;请求js时,请求路径会是一个js文件。通过请求URL,可以对响应数据进行判断。
const http = require('http');
const fs = require('fs');const server = http.createServer((request, response) => {let html = fs.readFileSync(__dirname + '/demo.html');let { pathname } = request.url;if(pathname == '/'){response.end(html);}else if(pathname == './index.css'){response.end(__dirname + '/index.css');}else if(pathname == './index.js'){response.end(__dirname + '/index.js');}else{response.end('404 not found');}}
)server.listen(9000, () => {console.log('server start!');
})
但通过这种方法返回请求资源,需要编写多个if else,这种写法比较复杂。
静态资源
静态资源是指内容长时间不会改变的资源,图片、视频、CSS、JS、HTML、字体等一般都是静态资源。
动态资源
动态资源是指内容经常发生更新的资源,比如搜索列表页、首页等,每次访问返回的数据可能各不相同。
搭建静态资源服务
静态资源服务:用于给浏览器返回静态资源。
在上面的例子中,通过if else判断返回何种资源,是十分复杂和冗余的。
如果想完成下列响应:
GET /index.html 响应pages/index.html
GET /css/demo.css 响应pages/css/demo.css
GET /image/demo.jpg 响应pages/image/demo.jpg
一般URL的请求路径,和文件路径之间是有关联的,所以可以通过通用关联拼接文件路径,而不需要每次都通过if else判断。
拼接完路径之后,通过fs读取文件,再返回就可以了。
const http = require('http');
const fs = require('fs');const server = http.createServer((request, response) => {let { pathname } = request.url;let filePath = __dirname + '/page' + pathname;fs.readFile(filePath, (err,data)=>{if(err){response.statusCode = 500;response.end('fail');return ;}response.end(data);})
)server.listen(9000, () => {console.log('server start!');
})
静态资源目录:是指静态资源存放的文件夹的目录。静态资源目录,也叫网站根目录。
在配置静态资源服务时,就是拼接静态资源目录,再拼接静态资源目录和请求路径,去这个路径读取文件。
...
let root = __dirname + '/page';
let filePath = root + pathname;
...
MIME类型
Multipurpose Internet Mail Extensions ,MIME。称为媒体类型,或资源类型。
是一种标准,用来表示文档、文档、字节流的性质和格式。
MIME类型的结构:主类型/子类型。
text/html、text/css、image/jpeg、application/json。
在响应头中,可以通过Content-Type来表明响应的MIME类型。浏览器会根据Content-Type类型来决定如何处理响应体资源。
常见的MIME类型对应:
html文件:text/html、css文件:text/css、JS文件:text/javascript、png图片:image/png、jpg图片:image/jpg、gif图片:image/gif、MP3文件:audio/mpeg、MP4文件:video/mp4、json文件:application/json。
在服务端返回资源时,可以通过文件的后缀名决定返回的Content-Type类型:
1.通过path.extname(filePath).slice(1)获取当前文件路径的后缀。
2.声明MIME类型对象mimes。
3.根据类型返回Context-Type值:response.setHeader('content-type',mimes[ext]);
对于未知的资源类型,可以设置Context-Type为application/octet-stream,浏览器碰到这种响应设置时,会对响应体的内容进行独立存储,也就是下载的效果。
当前,Content-Type的设置其实也可以不手动进行,因为浏览器有MIME嗅探功能,浏览器会根据响应体自动分析MIME类型,但是返回准确的MIME类型,是一种比较规范的开发模式。
为了防止中文乱码问题,在设置Content-Type值时,一般需要再拼接一下';charset=uft-8'。
除了设置Content-Type,如果资源类型是HTML,在HTML中,通过<meta charset='UTF-8'>也可以给资源设置charset,也不会产生乱码。
meta charset和Content-Type的优先级:Content-Type的优先级高于meta charset。
对于CSS、JS、图片等资源,一般不需要给资源单独设置字符集,他们的字符集一般是根据资源所在的网页的字符集处理的。
所以一般判断ext(文件后缀)是否是HTML,是HTML再设置charset。