请求响应原理以及HTTP协议

 1. 服务器端基础概念

1.1 网站的组成

网站应用程序主要分为两大部分:客户端和服务器端

客户端:在浏览器中运行的部分,就是用户看到并与之交互的界面程序,使用HTML、css、JavaScript构建

服务器端:在服务器中运行的部分,负责存储数据和处理应用逻辑

1.2 Node网站服务器

能够提供网站服务的机器就是网站服务器,能够接收客户端的请求,能够对请求作出响应

1.3 IP地址

互联网中设备的唯一标识

IP:Internet Protocol Address,互联网协议地址

1.4 域名

由于IP地址难以记忆,产生了域名的概念,域名就是平时上网时用的网址

虽然在地址栏中输入的是网址,但是最终还是会将域名转换为ip才能访问到指定的网站服务器

1.5 端口

端口是计算机与外界通讯交流的出口,用来区分服务器电脑中提供的不同服务

同一台服务器电脑上,可以提供不同的服务,比如web服务、邮件服务等等

1.6 URL 

统一资源定位符,URL

传输协议://服务器IP或域名:端口/资源所在位置标识

http://www.itcast.cn/news/2018018/09152238514.html

但在平时输入网址的时候我们并没有输入端口,那是因为我们访问的是服务器的web服务,默认是80端口,浏览器会自动帮我们添加上去

http:超文本传输协议,提供了一种发布和接收HTML页面的方法 

1.7 开发过程中客户端和服务器端说明

在开发阶段,客户端和服务器端使用同一台电脑,即开发人员电脑

2. 在Node.js中创建web服务器

网站服务器实际上就是一台电脑,在这台电脑上我们要安装Node这个软件

要使用Node这个软件创建请求对象和响应对象

(1)需要用到系统模块http,需要先引入

(2)下面有一个createServer方法,作用是创建服务器对象,返回值就是服务器对象

(3)app.on('第一个参数', '第二个参数')

第一个参数是事件的名称,比如request

第二个参数是事件处理函数,当请求来的时候就会执行这个处理函数,函数中有两个参数,req和res

req是请求对象,存储了请求相关的一些信息,比如请求地址、请求ip

res是响应对象,使用这个对象下面的一些方法对客户端发来的请求做出响应

(4)这个服务器需要监听一个端口才能对外界的请求作出响应,才能向外界提供对应的服务:app.listen(端口号),一般可以写成3000,也可以写成其他端口号,只要这个端口号没有被占用即可

// 用于创建网站服务器的模块
const http = require('http');
// app 对象就是网站服务器对象
const app = http.createServer();
// 当客户端有请求来的时候
app.on('request', (req, res) => {
// 结束这次请求,并响应了括号中的内容 res.end(
'<h2>hello,user</h2>'); }); // 监听端口 app.listen(3000); console.log('网站服务器启动成功');

访问本机的服务器:域名:端口号   localhost:3000   (代表想要访问本机上3000端口提供的服务)

3. HTTP协议

3.1 HTTP协议的概念

超文本传输协议(HyperText Transfer Protocol, 缩写:HTTP)规定了如何从网站服务器传输超文本到本地浏览器,它基于客户端服务器架构工作,是客户端(用户)和服务器端(网站)请求和应答的标准

3.2 报文

在http请求和响应的过程中传递的数据块就叫报文,包括要传送的数据考核一些附加信息,并且要遵守规定好的格式

3.3 请求报文

1. 请求方法

  • GET     请求数据
  • POST   发送数据

2. 请求地址

  • req.headers 获取请求报文  
    req.headers['accept']   获取报文具体的某一项
  • req.url   获取请求地址
  • req.method   获取请求方法
    // 获取请求地址
    // req.url
    // console.log(req.url);
    // 根据客户端请求地址的不同,服务器端响应不同的内容
    if (req.url == '/index' || req.url == '/') {
        res.end('welcome to homepage');
    } else if (req.url == '/list') {
        res.end('welcome to listpage');
    } else {
        res.end('not found');
    }
    // 获取请求报文信息
    // req.headers
    console.log(req.headers);
    // 获取某一项的具体信息
    console.log(req.headers['accept']);

    // 获取请求方式
    // console.log(req.method);
    if (req.method == 'POST') {
        res.end('post')
    } else if (req.method == 'GET') {
        res.end('get')
    }

3.4 响应报文

1. HTTP状态码

  • 200 请求成功
  • 404 请求的资源没有被找到
  • 500 服务器端错误
  • 400 客户端请求有语法错误
// 设置状态码
res.writeHead(500);

2. 内容类型

  • text/html
  • text/css
  • application/javascript
  • image/jpeg
  • application/json
res.writeHead(200, {
        'content-type': 'text/html;charset=utf-8'
    });

如果没有设置content-type这一项,就会默认设置为纯文本,如果响应的内容里面有HTML标签就不会显示出来,全部以纯文本的形式显示

res.end('<h1>welcome to homepage</h1>');

比如上面这行响应,会连标签一块显示出来,如果把content-type设置为text.html,他就可以正确显示为一级标题

charset=utf-8  作用是将文字正常显示,如果不写就会显示为乱码

4. HTTP请求与响应处理

4.1 请求参数

客户端向服务器端发送请求时,有时需要携带一些客户信息,客户信息需要通过请求参数的形式传递到服务器,比如登录操作

4.2 GET请求参数

参数被放置在浏览器地址栏中:例如:

参数获取需要借助系统模块url,url模块用来处理url地址

const url = require('url');
//
返回一个对象,把url的各个部分以对象的形式返回 // 参数一:要解析的url地址 // 参数二:将查询的参数解析成对象形式 true/false url.parse(参数一, 参数二)

如果输入的网址是 http://localhost:3000/index?age=20&username=ctt 

    url.parse(req.url, true)

返回以下内容:

Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?age=20&username=ctt',
  query: [Object: null prototype] { age: '20', username: 'ctt' },
  pathname: '/index',
  path: '/index?age=20&username=ctt',
  href: '/index?age=20&username=ctt'
}

通过下面的代码就可以解析出url中携带的参数

url.parse(req.url, true).query

如果我们使用 req.url来判断不同地址相应的的内容,如果携带了参数,根绝我们下面写的判断就会找不到匹配的地址,就会响应not found

if (req.url == '/index' || req.url == '/') {
        res.end('welcome to homepage');
    } else if (req.url == '/list') {
        res.end('welcome to listpage');
    } else {
        res.end('not found');
    }

这个时候我们就可以使用url对象中的pathname

使用解构赋值的方式获取到query和pathname,进而进行判断

// http://localhost:3000/index?age=20&username=ctt
let { query, pathname } = url.parse(req.url, true); // 解构赋值

// 获取请求参数
console.log(query.age);
console.log(query.username);

// 获取请求路径
if (pathname == '/index' || pathname == '/') {
        res.end('<h2>welcome to homepage回家</h2>');
    } else if (pathname == '/list') {
        res.end('welcome to listpage');
    } else {
        res.end('not found');
    }

4.3 POST请求参数

  • post请求参数被放在了请求报文(form data)中,get请求参数放在了url地址栏中
  • 获取post参数需要使用data和end事件
  • 使用querystring系统模块将参数转换为对象格式
// 用于创建网站服务器的模块
const http = require('http');
// app 对象就是网站服务器对象
const app = http.createServer();
// 处理请求参数模块
const querystring = require('querystring');

// 当客户端有请求来的时候
app.on('request', (req, res) => {
    // post 参数是通过事件的方式接受的
    // data  当有请求参数传输的时候就会触发data事件
    // end  当请求参数传递完成以后就会触发end事件
    // post参数在理论上是可以无限的,post请求参数不是一次就接收完的

    //  由于不是一次传递完的,需要把每次传递的参数拼接起来
    let postParams = '';
    // params 当前传递过来的参数
    req.on('data', params => {
        postParams += params; // 这里的postParams是一个字符串
    });
    req.on('end', () => {
        // 通过querystring.parse()将字符串转换为了对象格式
        console.log(querystring.parse(postParams)); 

    });
    res.end('ok');
});
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');

4.4 静态资源

服务器端不需要处理,可以直接响应给客户端的资源就是静态资源,例如CSS、JavaScript、images文件

4.5 动态资源

相同的请求地址不同的响应资源,这种资源就是动态资源

const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');

const app = http.createServer();

app.on('request', (req, res) => {
    // 获取不带参数的请求路径
    let pathname = url.parse(req.url).pathname;

    // 当地址栏中没有输入文件时,默认跳转到default.html
    pathname = pathname == '/' ? '/default.html' : pathname;

    // 将用户的请求路径转换为实际的服务器硬盘路径
    let realPath = path.join(__dirname, 'public' + pathname);

    // 获取请求文件的类型
    let type = mime.getType(realPath);

    // 读取文件
    fs.readFile(realPath, (error, result) => {
        // 如果文件读取失败
        if (error !== null) {
            // 将中文正常显示出来,如果没有出现error是不需要写charset=utf-8的,因为HTML文件头部已经写了
            res.writeHead(404, {
                'content-type': 'text/html;charset=utf-8'
            })
            res.end('文件读取失败');
            return;
        }
        res.writeHead(200, {
            // 如果不指定返回资源的类型,对于旧版本的浏览器可能就会出现问题
            'content-type': type
        })
        res.end(result)
    });
});
app.listen(3000);
console.log('服务器启动成功');

5. Node.js 异步编程

5.1 同步API,异步API

同步API:只有当前API执行完成以后,才能继续执行下一个API

异步API:当前API的执行不会阻塞后续待拿的执行

5.2 同步API,异步API的区别(获取返回值)

同步API可以从返回值中拿到API执行的结果,但是异步API不可以

5.3 回调函数

自己定义函数让别人去调用

5.4 使用回调函数获取异步API执行结果

 在异步API中,不能通过返回值得形式拿到执行结果,会默认返回undefined

5.5 同步API,异步API的区别(代码执行顺序)

同步API从上到下依次执行,前面代码会阻塞后面代码的执行

异步API不会等待API执行完成后再向下执行代码

5.6 Node.js中的异步API

回调地狱的问题:

const fs = require('fs');

fs.readFile('./1.txt', 'utf8', (err, result1) => {
    console.log(result1)
    fs.readFile('./2.txt', 'utf8', (err, result2) => {
        console.log(result2)
        fs.readFile('./3.txt', 'utf8', (err, result3) => {
            console.log(result3)
        })
    })
});

5.7 Promise

解决Node.js异步编程中出现回调地域的问题

原文地址:https://www.cnblogs.com/ccv2/p/13211091.html