node:http 协议:请求响应报文-web服务-url与对象的转化-ajax-cors跨域
1 梳理重点
1.1搭建服务
注意:
① 站点创建完成后,需要通过node命令启动
② 创建站点的代码如果发生变化,需要重启服务
③ 当用户输入域名:端口号/a/b,会请求到createServer接收的回调函数,回调函数需要给予响应,如果没有响应那么会一直请求下去
④ response.end():end 传入的信息全部在该对象内
const http = require("http");// 内置模块
const url = require("url");// 内置模块
// request:请求对象,与请求相关的信息全部在该对象内。
const server = http.createServer(function(request,response){
// request.url; 请求的网址
//假如:http://127.0.0.1:8090/a/b/c?one=1 那么req.url === /a/b/c?one=1
// request.url可以帮助我们获得资源: url.parse("/a/b/c?one=1") ====>{pathname:"/a/b/c",query:"one=1"}
const pathname = url.parse(request.url).pathname;// /a/b/c
if(pathname === "/a/b/c"){
// 200:成功 404:页面找不到 500:服务端异常 304:缓存
// 这个状态码是可以自定义的。
response.writeHead(200,{
// 文件是一个html类型,编码为utf-8
// "content-type":"text/html;charset=utf-8;"
// "content-type":"text/plain;charset=utf-8;"
})
response.end();
}
})
// http 默认端口号80 ,https默认端口号443.
// 127.0.0.1:指定访问该站点的IP,如果省略则不限制
// 第三个参数是一个回调函数,当服务搭建完成以后会执行。
server.listen(80,"127.0.0.1",function() {
console.log("站点创建成功");
})
1.2 fs
const fs = require("fs");
fs.readFile("请求地址",function(err,result){result.toString()});// 异步读取
const result = fs.readFileSync("请求地址");// 同步读取,将结果直接返回
const rs = fs.createReadStream("请求地址");
rs.on("data",function(thunk){});// 第次读取64k
rs.on("end",function(){});// 寻数据接收完毕之后执行
fs.writeFile();// 异步读取
fs.writeFileSync();// 同步读取
const ws = fs.createWriteStream("");
ws.on("open",function(){});
ws.write();
ws.on("close",function(){});
ws.close();
fs.unlink()// 删除
fs.unlinkSync()// 同步删除
fs.rename();// 实现重命名或移动
fs.renameSync();// 同步实现重命名或移动
fs.mkdir();// 创建文件夹目录
fs.readdir();// 读取文件夹的信息
fs.rmdir();// 移除文件夹
fs.exists();// 判断是否存在
fs.stat();// 判断是否为一个文件或目录。
1.3 模块化
将一个大的项目,分成若干个模块,在node中,每一个JS即是一个模块,优化之前的代码
// 获取当前时间:封装到一个名字为utils.js的模块当中。
// 2012/02/09 utils.end(res,1,"添加成功")
// 比如: res.end(JSON.stringify({ok,mag}))
module.exports.end = function(res,ok=-1,msg="网络连接错误"){
res.end(JSON.stringify({
ok,
msg
}))
}
2 HTTP 协议介绍
HTTP(hypertext transport protocol)协议也叫超文本传输协议,是一种基于 TCP/IP 的应用层通信协议,这个协议详细规定了浏览器和万维网服务器之间互相通信的规则。
协议主要规定了两方面的内容
- 客户端向服务器发送数据,称之为请求报文
- 服务器向客户端返回数据,称之为响应报文
3 请求报文
HTTP 请求报文包括四部分
- 请求行 请求方式 协议以及版本 http1.1 url(资源定位符,俗称网址)
- 请求头 传递 token session cookie
- 空行
- 请求体 传递数据。(post:添加 put修改 delete删除) get(获取) http://www.baidu.com?a=1&b=2
// 请求行
GET https://www.baidu.com/?tn=80035161_1_dg HTTP/1.1
// 请求头
Accept: text/html, application/xhtml+xml, image/jxr, */*
Accept-Language: zh-Hans-CN,zh-Hans;q=0.5
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: www.baidu.com
Connection: Keep-Alive
Cookie: COOKIE_SESSION=0_0_0_1_0_0_0_0_0_0_0_0_0_0_0_0_0_0_1606723864%7C1%230_0_1606723864%7C1; BD_UPN=1126314751; BD_HOME=1; BAIDUID=A613B163177AE92B8F2EA3C53D3B8227:FG=1; BIDUPSID=3A8FF4428ACCEE4CB183D3184E180560; PSTM=1587224822; BDRCVFR[k2U9xfnuVt6]=mk3SLVN4HKm; H_PS_PSSID=31253_26350; BA_HECTOR=2g040k8k0184ak216k1g9mdhd0r
// 空行
// 请求体:请求的数据。
/*1、请求行*/
// * GET:请求的方式
// * https://www.baidu.com/?tn=80035161_1_dg: url(资源定位符,俗称网址)
// * HTTP/1.1 : 请求的协议以及版本号。
/*2、请求头*/
// Accept:指定了接收资源的类型。
// Accept-Language: zh-Hans-CN,zh-Hans;q=0.5
// Accept-Encoding: gzip, deflate。
// User-Agent:将浏览器的信息进行说明。代理。
// Host:主机名
// Connection: Keep-Alive 指定连接的状态一般值 为 Keep-Alive(长连接) close(关闭)
- GET http://localhost:3000/hello.html HTTP/1.1:GET请求,请求服务器路径为http://localhost:3000/hello.html,?后面跟着的是请求参数(查询字符串),协议是HTTP 1.1版本
- Host: localhost:3000:请求的主机名为localhost,端口号3000
- Connection: keep-alive:处理完这次请求后继续保持连接,默认为3000ms
- Pragma: no-cache:不缓存该资源,http 1.0的规定
- Cache-Control: no-cache: 不缓存该资源 http 1.1的规定,优先级更高
- Upgrade-Insecure-Requests: 1:告诉服务器,支持发请求的时候不用 http 而用 https
- User-Agent: Mozilla/5.0 (…:与浏览器和OS相关的信息。有些网站会显示用户的系统版本和浏览器版本信息,这都是通过获取User-Agent头信息而来的
- Accept: text/html,…:告诉服务器,当前客户端可以接收的文档类型。q 相当于描述了客户端对于某种媒体类型的喜好系数,该值的范围是 0-1。默认为1
- Accept-Encoding: gzip, deflate, br:支持的压缩格式。数据在网络上传递时,服务器会把数据压缩后再发送
- Accept-Language: zh-CN,zh;q=0.9:当前客户端支持的语言,可以在浏览器的工具选项中找到语言相关信息
4 响应报文
HTTP 响应报文也包括四个部分
- 响应行
- 响应头
- 空行
- 响应体
// 响应行
HTTP/1.1 200 OK
// 响应头
Bdpagetype: 1
Bdqid: 0x8e8199c40002ddd4
Cache-Control: private
Connection: keep-alive
Content-Type: text/html;charset=utf-8
Date: Wed, 12 May 2021 01:58:21 GMT
Expires: Wed, 12 May 2021 01:57:55 GMT
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Server: BWS/1.1
Set-Cookie: BDRCVFR[k2U9xfnuVt6]=mk3SLVN4HKm; path=/; domain=.baidu.com
Set-Cookie: BDSVRTM=0; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=31253_26350; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Traceid: 1620784701057381274610268657692474269140
X-Ua-Compatible: IE=Edge,chrome=1
Content-Length: 303863
// 空行
// 响应体
<!DOCTYPE html><!--STATUS OK-->
<html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"><
/*1、响应行*/
// HTTP/1.1:协议以及版本号
// 200:响应状态码
// OK:状态码的说明
/*2、响应头*/
// Cache-Control: private
// Content-Type:指定响应的类型以及编码格式
// Date:响应的时间
// Strict-Transport-Security: max-age=172800 在172800秒内允许使用https
// Content-Length:指定文本长度
- HTTP/1.1 200 OK:协议是HTTP 1.1版本,请求响应成功
- X-Powered-By: Express:自定义的头,表示用的框架,一般不返回容易造成安全漏洞。
- Accept-Ranges: bytes:告诉浏览器支持多线程下载
- Cache-Control: public, max-age=0:强制对所有静态资产进行缓存,即使它通常不可缓存。max-age指定多久缓存一次
- Last-Modified: Wed, 21 Mar 2018 13:13:13 GMT:这个资源最后一次被修改的日期和时间
- ETag: W/“a9-16248b12b64”:请求资源的标记/ID
- Content-Type: text/html; charset=UTF-8:返回响应体资源类型
- Content-Length: 169:响应体的长度
- Date: Thu, 22 Mar 2018 12:58:41 GMT:提供了日期的时间标志,标明响应报文是什么时间创建的
5 WEB 服务
使用 nodejs 创建 HTTP 服务器
//1. 引入 http 内置模块
var http = require('http');
//2. 创建服务
var server = http.createServer(function(request, response){
response.end('hello world!!');
});
//3. 监听端口
server.listen(8000);
- request 是对请求报文的封装对象
- response 是对响应的封装对象
6 获取请求
//请求行的信息
//获取请求方法
console.log(request.method); //get
//获取http版本
console.log(request.httpVersion); // 1.1
//获取请求路径
console.log(request.url);// user
//获取请求头
console.log(request.headers);//请求头所有信息
//获取请求体
let body = "";
req.on("data",function(thunk){
body+=thunk;
})
req.on("end",function(){
console.log(body);// 响应体的内容。
})
// 获取请求体
if (req.method === "POST") {
let body = "";
//post 一般是将数据放置到请求体
// 读取请求体的数据
req.on("data",function(thunk) {
body += thunk;
})
req.on ("end",function() {
console.log(body.toString()) //user=100&password=200
//将user=100&password=200转换成对象,已封装好,直接调用
// console.log(utils.querystring(body.toString()))//自己封装
// 也可直接用内置的
console.log(myquerystring.parse(body.toString()))
res.end("post响应的")
})
} else {
res.end("get请求的")
}
7 设置响应
//设置状态码
response.statusCode = 200;
res.statusMessage; // 设置状态码的内容
//设置响应头
response.setHeader('content-type','text/html;charset-utf-8');
res.setHeader("Access-Control-Allow-Origin","*");
// 设置响应行以及响应头
res.writeHead(200,"success",{
userName:"laotie",
age:100,
"content-type":"text/html;charset=utf-8"
})
//设置响应体
response.write('body');
response.end();
// 1、结束响应 2、可以指定响应体的内容,异步结束后再响应,否则得不到响应的内容
GET 和 POST 的区别
GET 和 POST 是 HTTP 协议请求的两种方式
- GET 主要用来获取数据, POST 主要用来提交数据
- GET 带参数请求是将参数缀到 URL 之后, 在地址栏输入url访问网站就是 GET 请求, POST 带参数请求是将参数放到请求体中
- POST 请求相对 GET 安全一些, 因为在浏览器中参数会暴露在地址栏.
- GET 请求大小有限制, 一般为 2k, 而 POST 请求则没有大小限制
- GET 类型报文请求方法为 GET , POST 类型报文请求方法为 POST
// text/plain
const xhr = new XMLHttpRequest();
xhr.open("post","/postLogin");
// x-www-form-urlencoded
// xhr.setRequestHeader("content-type","application/x-www-form-urlencoded")
// xhr.setRequestHeader("content-type","application/json")
xhr.send("userName="+this.value+"&a=1&b=2&c=3");// 写字符串
xhr.onload = function () {
console.log(xhr.responseText);
}
Chrome 查看请求报文
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ukpgoWqe-1621071772124)(D:/尚硅谷/尚硅谷课堂/03阶段-项目和 jQuery/Day19-http/课件/assets/网络控制台查看报文的方式.png)]
8 url与对象的互相转化
8.1 将url转换为对象
user=100&password=200 [user,100] [user,200]
function querystring(str) {
//module.exports.querystring = function (str){可暴露出去,后面直接调用
const arr = str.split("&");
let obj = {};
arr.forEach(v => {
const [key,value] = v.split("=") //[user,100]
});
return obj;
}
console.log(querystring("a=1&b=2"))
也可直接用内置的:querystring.parse(body.toString())
module.exports.myquerystring = {
parse(str){
const arr = str.split("&");
let obj = {};
arr.forEach(v=>{
const [key,value] = v.split("=");// [user,100]
obj[key] = value;
})
return obj;
}
}
8.2 将对象转换为url
const obj = {a:"1",b:"2"}
console.log(Object.keys(obj))
console.log(Object.keys(obj).map(v=>v+"="+obj[v]));
// 将对象的key转换为数组 [ 'a=1', 'b=2' ]
console.log(Object.keys(obj).map(v=>v+"="+obj[v]).join("&"));
// 将对象的key转换为数组a=1&b=2
9 ajax(get post)
不用form来请求,用ajax来请求,当键盘抬起,提交信息
注意
打开的地址要写本机的地址,而不是本地的地址
// 1、如果地址有问题,请将下面的代码尝试一下。
// const xhr = new XMLHttpRequest();
// http://localhost:10241/code/2%E3%80%81ajax/index.html?_ijt=1unt3qpf99q89n7iupl6qqv4v1
// xhr.open("get","/login");// // http://localhost:10241/login
// xhr.open("get","./login");// // http://localhost:10241/code/2%E3%80%81ajax/login
// xhr.open("get","login");// // http://localhost:10241/code/2%E3%80%81ajax/login
// xhr.open("get","../login");// // http://localhost:10241/code/login
// xhr.send();
写正确的问题后会产生跨域行为 xhr.open(“get”,“http://127.0.0.1/login”)
9.1 get 请求
① 请求方式 ② 地址 ③ 传递的参数:?后面 ④ 得到的结果
const xhr = new XMLHttpRequest();
xhr.open("get","/getLogin?userName="+this.value);
xhr.send();
xhr.onload = function () {
console.log(xhr.status);// 200 响应的状态码
console.log(xhr.statusText);// ok 响应的状态码说明
console.log(xhr.getResponseHeader("age"));// 指定响应头属性,返回的是其值。
console.log(xhr.getAllResponseHeaders());// 得到响应头的内容
console.log(xhr.responseText);// 响应体的内容
}
else if(pathname === "/getLogin"){
const {userName} = url.parse(req.url,true).query;
res.setHeader("userName","laotie");
res.setHeader("age",100);
if(userName === "zhangsan"){
res.end(JSON.stringify({
ok:1,
msg:"登陆成功"
}))
}else{
res.end(JSON.stringify({
ok:-1,
msg:"登陆失败"
}))
}
9.2 post 请求1
① 请求方式 ② 地址 ③ 传递的参数:send ④ 得到的结果
const xhr = new XMLHttpRequest();
xhr.open("post","/postLogin");
xhr.send("userName="+this.value+"&a=1&b=2&c=3");// 写字符串
xhr.onload = function () {
console.log(xhr.responseText);
}
else if(pathname === "/postLogin"){
// 接收传的数据,放置数据过大,用模块
let body = "";
req.on("data",function (thunk) {
body+=thunk;
})
req.on("end",function (thunk) {
console.log(JSON.parse(body));// {}
// console.log(querystring.parse(body));
const {userName} = querystring.parse(body);
if(userName === "zhangsan"){
res.end(JSON.stringify({
ok:1,
msg:"登陆成功"
}))
}else{
res.end(JSON.stringify({
ok:1,
msg:"登陆失败"
}))
}
})
9.3 post 请求2
//plain
const xhr = new XMLHttpRequest();
xhr.open("post","/postLogin");
xhr.send("userName="+this.value+"&a=1&b=2&c=3");// 写字符串
xhr.onload = function () {
console.log(xhr.responseText);
}
当数据格式为:x-www-form-urlencoded
const xhr = new XMLHttpRequest();
xhr.open("post","/postLogin");
xhr.setRequestHeader("content-type","application/x-www-form-urlencoded") //要在open和send中间设置
xhr.send("userName="+this.value+"&a=1&b=2&c=3");// 写字符串
xhr.onload = function () {
console.log(xhr.responseText);
}
当数据格式为:application/json
const xhr = new XMLHttpRequest();
xhr.open("post","/postLogin");
xhr.setRequestHeader("content-type","application/json");
// send的内容是什么,请求体里面的内容就是什么
xhr.send(JSON.stringify({
userName:this.value,
a:1,
b:2,
c:3
}));// 写字符串 传到body中
xhr.onload = function () {
console.log(xhr.responseText);
}
在什么时候会用到xhr。
当前端页面获取服务端数据时。删除数据,修改数据,添加数据。
10 解决cors 跨域
cors error:跨域 当你看到异常中:Access-Control-Allow-Origin 说明跨域
不符合同源策略:同端口,同协议,同域名。
当前的站点:http://localhost:10241
请求的地址:http://127.0.0.1/login
方法一:jsonp
jsonp的核心原理就是目标页面回调本地页面的方法,并带入参数
JSONP:不是一种新的技术,一种解决跨域的方案。指定了script标签,src指定一个地址,提供了一个函数名。一般都是在公司内部或是合作公司用的较多
const input = document.querySelector("input[name=user]")
function zhang(value) {
console.log(value)
}
input.onkeyup = function(e) {
if (e.keyCode === 13 ) {
const script = document.createElement("script");
//script.src = "http://127.0.0.1/login?userName"+this.value;
// 如果名字冲突,可加cb,将定会的全局函数fn改为zhang
script.src = "http://127.0.0.1/login?userName="+this.value+"&cb=zhang";
document.body.appendChild(script)
}
}
在node环境中
if (pathname === "/login") {
const {userName,cb} = url.parse(req.url,true).query;
if (username === "zhangsan") {
//res.end("登录成功");把代码作为js运行,报错
res.end(fn('登录成功'));//说明是个JS 没有fn,定义在html的全局
// cb接收放置名字重复重新定义的cb=zhang
res.end(cb+"({ok:1,msg:'登陆成功'})"); //传对象
} else{
res.end(cb+"({ok:-1,msg:'登陆失败'})");
}
}else {
res.end("404")
}
方法二:cors
使用cors:可以将自己的域名设置在白名单当中
const xhr = new XMLHttpRequest();
xhr.open("get","http://127.0.0.1/corsLogin?userName"+this.value)
xhr.send();
xhr.onload = function() {
consoole.log(xhr.responseText);//响应体的内容
}
else if (pathname === "/corsLogin") {
// * 不限制来源
//res.setHeader("Access-Control-Allow-Origin","*");
// 只允许来源http://192.168.17.27 访问。 指定某一个站点进入白名单
// res.setHeader("Access-Control-Allow-Origin","http://192.168.17.27");
// 指定多个
const arrIp = ["http://192.168.17.27","http://localhost:10241"]
// console.log(req.headers.origin);// 来源的主机
if(arrIp.includes(req.headers.origin)){
res.setHeader("Access-Control-Allow-Origin",req.headers.origin);
}
const {userName} = url.parse(req.url,true).query;
if(userName === "zhangsan"){
res.end(JSON.stringify({
ok:1,
msg:"登陆成功"
}))
}else{
res.end(JSON.stringify({
ok:-1,
msg:"登陆失败"
}))
}
}
方法三:服务代理
1 下载axios
npm install axios -S
2 通过axios发送请求:拉钩地址https://m.lagou.com/listmore.json?pageNo=2&pageSize=15
引入axios,会到源码里找 访问拉钩的数据,跨域了
// 通过axios请求拉钩数据
axios.interceptors.response.use(res=>{
return res.data;
})
//产生跨域
axios.interceptors.response.use(res=>{
return res.data;
})
axios.get("https://m.lagou.com/listmore.json",{
params:{
pageNo:2,
pageSize:15
}
}).then(data=>{
console.log(data);
})
//或者
axios.interceptors.request.use(config=>{
config.url = "/api"+config.url;// /api/listmore.json
return config;
})
// 2、使用代理 /api/listmore.json?pageNo=2&pageSize=15
axios.get("/listmore.json",{
params:{
pageNo:2,
pageSize:15
}
}).then(data=>{
console.log(data);
})
3 在webpack.config.js—>devServer中创建服务
devServer: {
proxy:{
//请求的地址:api/listmore.json
//http://m.lagou/listmore.json
"api":{ //代理的标识 请求的地址以/api开头会使用该代理
target:"http:/m.lagou.com", //为代理的服务器 和地址进行拼接
changeOrigin:true, //开启服务
pathRewrite:{ //将地址进行重写
"^/api":"" //将/api开头的用空格替换
}
}
}
}
4 发送请求更改为
// 2、使用代理 /api/listmore.json?pageNo=2&pageSize=15
axios.get("/api/listmore.json",{
params:{
pageNo:2,s
pageSize:15
}
}).then(data=>{
console.log(data);
})
**注意:**webpack-config.js代码修改后一定一定要重启,否则看不到效果
附录
响应状态码
响应状态码是服务器对结果的标识,常见的状态码有以下几种:
-
200:请求成功,浏览器会把响应体内容(通常是html)显示在浏览器中;
-
301:重定向,被请求的旧资源永久移除了(不可以访问了),将会跳转到一个新资源,搜索引擎在抓取新内容的同时也将旧的网址替换为重定向之后的网址;
-
302:重定向,被请求的旧资源还在(仍然可以访问),但会临时跳转到一个新资源,搜索引擎会抓取新的内容而保存旧的网址;
-
304:(Not Modified)请求资源未被修改,浏览器将会读取缓存;
-
403:forbidden 禁止的
-
404:请求的资源没有找到,说明客户端错误的请求了不存在的资源;
-
500:请求资源找到了,但服务器内部出现了错误;
res.end(JSON.stringify({ ok:1, msg:"登陆成功" })) }else{ res.end(JSON.stringify({ ok:-1, msg:"登陆失败" })) }
}
### 方法三
服务代理
## 附录
### 响应状态码
响应状态码是服务器对结果的标识,常见的状态码有以下几种:
- 200:请求成功,浏览器会把响应体内容(通常是html)显示在浏览器中;
- 301:重定向,被请求的旧资源永久移除了(不可以访问了),将会跳转到一个新资源,搜索引擎在抓取新内容的同时也将旧的网址替换为重定向之后的网址;
- 302:重定向,被请求的旧资源还在(仍然可以访问),但会临时跳转到一个新资源,搜索引擎会抓取新的内容而保存旧的网址;
- 304:(Not Modified)请求资源未被修改,浏览器将会读取缓存;
- 403:forbidden 禁止的
- 404:请求的资源没有找到,说明客户端错误的请求了不存在的资源;
- 500:请求资源找到了,但服务器内部出现了错误;