node.js教你写一个web服务器(保姆级教程)

2,125 阅读23分钟

服务器相关概念

服务器与客户端

服务器是提供网络服务的机器,通过安装特殊的软件(或者是运行某段特殊的代码)来提供服务。

服务器  = 电脑 + 能给其它电脑/设备提供服务的软件

客户端与服务器:提供服务的是服务器,享受服务的是客户端

服务器的类型

根据服务不同,服务器的类型也不同:

  • web服务器。安装apache, tomcat, iis, 或者在nodejs环境写代码 来提供:图片浏览,新闻浏览....等服务的服务器。
  • ftp服务器。安装serv-U软件,为其它电脑提供文件下载,共享服务。
  • 数据库服务器。安装mysql软件,为其它电脑提供数据库服务。
    ....

web服务器:

  • 用户通过浏览器来享受web服务器提供的服务
  • 我们用url地址来访问某个web服务器上的资源
  • 浏览器端发起请求,web服务器收到请求后,响应这个请求,并将处理结果返回给浏览器

  • 浏览器端与web服务器是通过http(或者是https)协议来进行请求和响应

ip地址

全称:Internet Protocol Address。

作用:标识一个网络设备(计算机、手机、电视)在某一个具体的网络当中的地址。要访问某个电脑上的资源,先要找到它的ip。

分类:ipV4   ipV6 (www.gov.cn/xinwen/2018…)

格式:[0-255].[0-255].[0-255].[0-255] 即为四个 0-255 的数字组成(以ip4为例)。在同一个网络中,计算机的IP是不允许相同的,都是唯一的。

127.0.0.1 特指本机ip地址。

这个 http://220.181.38.149/ 会指向哪里?

域名

域名:ip地址的别名,由于ip地址不好记忆,我就给它们取个好记的别名。localhost这个域名特指127.0.0.1这个地址。

域名解析系统:把域名翻译成Ip地址的系统。

端口

一个IP地址的端口可以有65536个,范围是从[0,65535])。不同的端口被不同的软件占用,以提供不同的服务。

一台电脑可以通过安装多个服务器端软件来提供服务,比如Web服务、FTP服务、SMTP服务等。显然,仅仅通过ip地址是无法区分不同的服务的,这里就需要用到 “IP地址+端口号”来区分不同的服务

理解

如果理解IP地址(一台服务器)是一栋大商场,端口就是商场中的商铺的编号。

如果理解IP地址(一台服务器)是公司的前台电话,端口就是公司中各个部门的分机号。

  1. 服务器要提供服务必须要通过指定的端口
  2. 服务器与客户端都需要通过端口要进行通信
  3. 端口是可以编程分配
  4. 有一些端口号是被预定了的。
  • http: 80
  • https:443
  • mysql:3306

通过netstat -a -n -o 查看端口使用情况

协议

制定客户端与服务器之间的通讯规则。不同的协议的作用也不同。

http协议:

  • HTTP(HyperText Transfer Protocol) 超文本传输协议。
  • 协议双方: 浏览器与web服务器
  • 请求由浏览器发起的
  • HTTP 协议中明确规定了请求数据响应数据的格式(报文)
    • 浏览器 请求 资源 要遵守 http 协议:  请求报文(请求行,请求头,请求体)
    • 服务器 返回 资源 要遵守 http 协议:  响应报文(响应行,响应头,响应体)

用http 模块写一个简单的web服务器

自己完成的网页,如果让其他人能够访问?

目标

用http模块写一个简单的服务器,让同学来访问本机的服务

要点

  • 引入nodejs中的核心模块:http
  • 使用createServer来创建服务
  • 使用listen方法来启动服务

操作

有三步:

    1. 手写代码,实现服务器功能
    1. 运行代码,启动服务
    1. 访问服务,测试功能

第一步:新建一个文件,名为  d:/src/01http.js( 文件名及路径名可以自行设置,建议均不使用中文字符), 内容如下

// 1. 引入http模块
const http = require('http');

// 2. 创建服务
const server = http.createServer(function(req, res) {
  console.log('有人来访问了')
  // 向客户端发送内容,并结束本次响应
  res.end('hello world');
});
// 3. 启动服务
server.listen(8081, function() {
  console.log('服务器启动成功,请在http://localhost:8081中访问....');
});

第二步:运行js代码,启动服务。

在小黑窗中进入到01http.js所在的目录,键入命令 node 01http.js,此时会弹出一个小黑窗,不要关。

第三步:测试功能

打开一个浏览器页面,输入地址:http://localhost:8081,观察效果:

  • 浏览器中的效果
  • 小黑窗中的效果

停止服务:ctrl + c

拓展

共享让同学来访问

把localhost改成你自己电脑的ip地址,例如:http://192.xxx.xxx.xxx:8081,再把这个路径发你的同学(同一个局域网)来访问。

如果不能被其他同学访问,有可能你需要手动关闭你自己计算机的防火墙

工作原理

使用http模块在本机上创建一个虚拟服务器,它来接收浏览器的请求,并给出响应。

注意:

  • 小黑窗不要关,它就是服务器
  • 服务器本身不会有主动行为(小黑窗看起来没有任何变化),它在时刻等待客户端的访问
  • 不要用鼠标选中小黑窗的内容,会导致程序假死
  • 修改代码后要重启
    • 更改res.end()的内容,重启后,再次观察。
    • 重启服务:就是重新运行程序(按下向上的箭头,再回车)。

代码解析-理解请求和响应

代码解析

  • 引入核心模块,得到的http是一个对象。
  • http.createServer方法创建一个http服务。参数是一个回调函数:当有http请求进来时,它会自动被调用。请求一次,它就被调用一次
  • 第一个参数:客户端的请求。 第二个参数:设置对本次请求的响应
  • res.end() :设置响应体,结束请求。
  • server.listen() 用来监听端口。
  • 格式:server.listen(端口号,[回调函数]) 。回调是可选的。 说明:
  •  如果监听成功,则回调函数会执行一次。
     如果不成功(例如端口被占用),会报错。
    

理解请求和响应

请求

当web服务器就绪之后,如果没有客户端来访问它,它也是不会有任何效果的。也就是说回调函数不会执行。

而每一次的请求,都会导致回调函数要执行一次。

响应

通过res.end来设置响应的内容,res.end()的格式只是能是buffer或者是String

====做练习====

  1. 写一个服务器,让你的同学们来访问一下。随机返回内容,让不同的访客看到不一样的内容
const strs = [
  "你是我见过的最好的合作伙伴",
  "我特别愿意和你一起工作",
  "今天天气很好,遇到你很开心"
]
// 1. 引入 http
const http = require('http')
// 2. 创建服务
// 每次收到请求,回调就会执行
const server = http.createServer((request, response) => {
  console.log(Date.now(), '有人访问')
  // 随机下标
  const idx = Math.floor(Math.random() * strs.length)

  // 指定浏览器解码字符
  response.setHeader("content-type", "text/html;charset=utf8")
  response.end(strs[idx])
})
// 3. 启动服务
server.listen(3001, () => {
  console.log('服务器启动, 3001端口')
})
  1. 修改代码中的端口号,开启多台服务器。复制一份代码,并启动,更改端口号,模拟多台服务器。

不同请求返回不同的内容-认识URL

以上的服务器无论用户的请求地址是什么,都只能返回一个固定的内容,这与我们平时上网的效果是不一样的。

全称

Uniform Resource Locator,统一资源定位符。

  • 作用:定位资源(css,html,js,png, avi,接口......)
  • 格式:协议://主机地址[:端口]/路径?查询字符串#锚点
  • 示例:baidu.com:80/schools/stu…
  • 协议: http 或者是 https
    • 主机地址: IP地址 或者 域名
    • 端口号

http请求,默认端口80(可以省略)

https请求,默认端口443(可以省略)

MySQL默认端口3306

  • 路径
  • 服务器文件夹上的资源。(.html/.css/.images/.js/接口)
  • 参数(查询字符串)
  • ? 后面的部分,是键值对的形式
  • 锚点
  • 网页内部的锚点链接

不同请求返回不同的内容-问题分析

思路:在服务器端收到客户端发的请求之后,分析url是什么,然后分别对应处理

不同请求返回不同的内容-req.url

目标

了解 req.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
1http://localhost:8080/
2http://localhost:8080/a.html/a.html
3http://localhost:8080/js/jquery.js/js/jquery.js
4http://localhost:8080/1.jpg/1.jpg
5http://localhost:8080/api?a=1&b=2/api?a=1&b=2

小结

  1. req.url一定是以/开头的
  2. 在现代浏览器中,它们会自动去请求服务器上的favicon.ico

不同的URL返回不同的内容-实操

目标

用户在访问服务器上不同的url时,能返回不同的内容

目录结构

根目录
├── css
│   └── index.css
├── img
│   └── bg.jpeg
├── js
│   └── axios.js
├── index.html
└── serve.js     # 服务器

请求与响应的对应的关系

用户的请求地址服务器的动作
http://localhost:8000读出index.html的内容并返回
http://localhost:8000/index.html读出index.html的内容并返回
http://localhost:8000/css/index.css读出index.css的内容并返回
http://localhost:8000/img/bg.jpeg读出图片的内容并返回
http://localhost:8000/js/axios.js读出axios.js的内容并返回
http://localhost:8000/xxxx 或者不是上面的地址返回404

示例代码

const http = require('http')
const fs = require('fs')
const path = require('path')
const server = http.createServer((req, res) => {
  // 1. 获取url
  const { url } = req // req.url 解构赋值
  console.log(url)
  if(url === '/' || url === '/index.html') {
    // 读出文件内容并返回
    // 1) 文件的位置
    const filePath = path.join(__dirname, 'index.html')
    // 2) 读出它的内容
    const content = fs.readFileSync(filePath, 'utf8')
    console.log(content)
    // 3) 返回
    res.end(content)
  } else if(url === '/css/index.css') {
    // 读出文件内容并返回
    // 1) 文件的位置
    const filePath = path.join(__dirname, 'css/index.css')
    // 2) 读出它的内容
    const content = fs.readFileSync(filePath, 'utf8')
    console.log(content)
    // 3) 返回
    res.end(content)
  } else {
    res.end('ok')
  }

})

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文件等等),服务器的处理方法就是:读出文件内容,返回给用户。

设置content-type

目标

  • 理解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页面来渲染了。

格式

res.setHeader('content-type', 值)

示例

const http = require('http')
const fs = require('fs')
const path = require('path')
const server = http.createServer((req, res) => {
  // 省略其他
  if(url === '/' || url === '/index.html') {
    // 读出文件内容并返回
    // 省略其他
    res.setHeader('content-type', 'text/html;charset=utf8');

    res.end(content)
  } 
}

server.listen(8000, ()=>{
  console.log('服务器已经在8000启动');
})

设置statusCode

目标

了解statusCode的作用,会正确使用statusCode

statusCode

就是http请求的状态码。 约定如下

设置格式

res.statusCode = 值

示例

res.statusCode = 301;
// res.setHeader('location','http://www.qq.com')
res.statusCode = 404
res.statusCode = 500
res.end()

处理.html文件中的二次请求

什么是二次请求

从服务器获取html文件之后,如果这个html文件中还引用了其它的外部资源(图片,样式文件等),则浏览器会重新再发请求,这个就是二次请求。

背景

假设在index.html中还引入了 index.css, bg.jpeg 或者 .js文件,则:浏览器请求localhost:8000/index.html之后,得到从服务器反馈的内容(index.html的代码),解析的过程中还发现有外部的资源(图片,样式,js),所以浏览器会再次发出第二次请求,再去请求相应的资源。

根目录
├── css
│   └── index.css
├── img
│   └── bg.jpeg
├── js
│   └── axios.js
├── index.html
└── serve.js     # 服务器

思路

一个最朴素的想法是针对所有不同的请求来各自返回不同的文件。

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端口启动');
});

Express基本介绍

Express 是一个基于 Node.js 平台,快速、开放、极简的 web 开发框架。

  • 框架:是一个半成品,用来快速解决一类问题;库就是工具集,使用非常灵活) (框架有:bootstrap, lay-ui, express, vue, react ; 库:zepto.js , jQuery,  day.js, underscore, lodash, art-template, axios, echart.....)

  • web 开发: 对不同的请求能够显示页面;提供接口服务; 参考链接

  • 我们前面用http模块来支持web服务,现在要用express来写web服务

  • 对于node.js来说,Express 是一个第三方模块,有丰富的 API 支持,强大而灵活的中间件特性

  • Express 不对 Node.js 已有的特性进行二次抽象,只是在它之上扩展了 Web 应用所需的基本功能

理解:中间件

  • 给用户提供更好的服务
  • 可方便的拆卸

运行第一个express程序

expresss 是一个第三方模块(在npm上可以下载),在使用它之前要先去下载它,在下载包之前要先创建项目,并通过npm init 创建package.json文件。

创建项目并初始化

创建一个全新的文件夹,假设名字为 learn-express(目录名不要有汉字,也不要某个包的名字),在此目录下运行npm init -y 命令来生成package.json文件

下载express包

参考文档:expressjs.com/en/starter/…

与安装其他的第三方包一致,本地安装

npm i express

注意:

  • 项目目录名字不要取中文,也不要取为express
  • 如果安装不成功:
    • 换个网络环境
    • 运行下npm cache clean -f,再重新运行下载命令试试

快速创建web服务器

参考文档:expressjs.com/en/starter/… 在项目根目录下新建一个js文件,例如app.js,其中输入代码如下:

// 0. 加载 Express
const express = require('express')

// 1. 调用 express() 得到一个 app
//    类似于 http.createServer()
const app = express()

// 2. 设置请求对应的处理函数
//    当客户端以 GET 方法请求 / 的时候就会调用第二个参数:请求处理函数
app.get('/', (req, res) => {
  res.send('hello world')
})

// 3. 监听端口号,启动 Web 服务
app.listen(3000, () => console.log('app listening on port 3000!'))

说明:

  • app.get('/')相当于添加事件监听:当用户以get方式求"/"时,它后面的回调函数会执行,其回调函数中的req,res与前面所学http模块保持一致。
  • res.send()是exprss框架给res对象补充提供的方法(http模块中的res是没有这个方法的),用于结束本次请求。类似的还有res.json(), res.sendFile() 。
  • express 框架会增强req,res的功能

托管静态资源-web服务器

参考文档:expressjs.com/en/starter/…

让用户直接访问静态资源是一个web服务器最基本的功能。

根目录
├── public
│   ├── css
│   │   └── index.css
│   ├── img
│   │   └── bg.jpeg
│   ├── js
│   │   └── axios.js
│   └── index.html
└── serve.js     # 服务器

例如,如上url分别是请求一张图片,一份样式文件,一份js代码。我们实现的web服务器需要能够直接返回这些文件的内容给客户端浏览器。

在前面学习http模块时,我们已经实现了这些功能了,但是要写很多代码,现在使用express框架,只需一句代码就可以搞定了,这句代码是  express.static('public')

忽略前缀

// 加载 Express
const express = require('express')

// 1. 调用 express() 得到一个 app
//    类似于 http.createServer()
const app = express();

// 2. 设置请求对应的处理函数
app.use(express.static('public'))


// 3. 监听端口号,启动 Web 服务
app.listen(3000, () => console.log('app listening on port 3000!'))

此时,所有放在public下的内容可以直接访问,注意,此时在url中并不需要出现public这级目录。在public下新建index.html,可以直接访问到。

限制前缀

// 限制访问前缀
app.use('/public', express.static('public'))

这意味着想要访问public下的内容,必须要在请求url中加上/public

===练习===

路由和接口

参考文档:expressjs.com/en/starter/…

路由(Routing)是由一个 URL(或者叫路径标识)和一个特定的 HTTP 方法(GET、POST 等)组成的,涉及到应用如何处理响应客户端请求。每一个路由都可以有一个或者多个处理器函数,当匹配到路由时,这些个函数将被执行。

格式

const app = express();

// 定义路由
app.METHOD(PATH, HANDLER)

其中:

  • app 是 express 实例 。(const app = express())
  • METHOD 是一个 HTTP 请求方法。 全小写格式。如:post,get,delete等
  • PATH 是请求路径(相当于在http模块中用到过的 url.parse(req.url).pathname
浏览器url服务端路径
http://localhost:8080/
http://localhost:8080/public/a/index.html/public/a/index.html
http://localhost:8080/index.html?a=1&b=2/index.html
  • HANDLER 是当路由匹配到时需要执行的处理函数。(req,res)=>{ }

写接口-整体说明

接口文档谁来定?

提问:接口是前端,还是后端来定?

接口传参

我们使用ajax请求向服务器接口传参,按http协议的约定,每个请求都有三个部分:

  • 请求: 保存了请求方式,地址,可以以查询字符串的格式附加一部分数据。
  • 请求:它可以附加很多信息,其中content-type用来约定请求体中保存的数据格式。
    content-type常见有三种取值:
content-type的值表示请求体的数据格式示例
application/x-www-form-urlencode普通键值对象a=2&c=1
application/jsonjson对象{a:1,b:{c:1}}
multipart/form-data上传文件file
  • 请求:  本次请求携带的参数。至于这些参数到了后端应该如何解析出来,由请求头中的content-type来决定。

  • 方法一:请求行。常见方式如下:
    • 使用ajax技术,通过get方式传参。
    • 在浏览器地址栏中输入接口地址并补充上查询字符串。
  • 方法二:请求体
    • ajax中的post, put, delete可以从请求体中进行传参。

另外,请求头中的content-type用来告之服务器应该以何种方式去解析请求体中的数据。

express写get接口

get无参数

const express = require('express');
const app = express();
app.get('/get', function(req, res) {
  // 直接返回对象
  res.json({ name: 'ceshi' });
});
app.listen('8088', () => {
  console.log('8088');
});

注意:

  • res.json()是express提供的方法,同时会结束请求(类似于res.end)。

get接口有参数

express框架会自动收集get类型的接口从url地址中传递的查询字符串参数,并自动保存在req对象的query属性中。我们直接来获取即可。

const express = require('express');
const app = express();
app.get('/get', function(req, res) {
  // 直接返回对象
  console.log(req.query);
  res.send({ name: 'abc' });
});
app.listen('8088', () => {
  console.log('8088');
});

注意:req.query属性是express框架额外提供的属性。

post接口-普通键值

post接口与get请求不同在于:它的参数一般是通过请求体来传递的。根据传递的参数的格式不同,分成三种情况来说

  • 传递普通键值对
  • 传递form表单(涉及文件上传)
  • 传递json

普通键值对参数

具体来说当请求头的content-type为x-www-form-urlencoded时,表示上传的普通简单键值对 。

步骤

// 1. 使用中间件
app.use(express.urlencoded());

app.post("/add",function(req,res){
// 2. 可以通过req.body来获取post传递的键值对	
// res.json是express提供的一个函数,用来返回一个json数据给客户端,同时会结束请求
// 类似于res.end, res.send()
    res.json(req.body)
})

注意:

  • app.use(....)之后,在res.body中就会多出一个属性res.body。
  • extended: false:表示使用系统模块querystring来处理传入的参数,也是官方推荐的
  • extended: true:表示使用第三方模块qs来处理传入的参数.

post接口-json格式的参数

在post传递参数时,如果要传入的参数比较复杂(多级嵌套),则可以使用json格式上传。

var data = {
 name:"abc",
 address:{
     "a":1,
     "b":2,
     "info":"c"
 }
}

后端

app.use(express.json());
// 会自动加入req.body属性,这个属性中就包含了post请求所传入的参数

// 用来处理JSON格式的数据
app.post('/postJSON',(req,res)=>{
    // 后端收到post传参
    console.log(req.body);
    
    res.send('/postJSON')
})

post接口-form-data文件上传

如果post涉及文件上传操作,则需要在服务器端额外使用第三方multer这个包(不属于express)来获取上传的信息。

Multer 是一个 node.js 中间件,用于处理 multipart/form-data 类型的表单数据,它主要用于上传文件。

enctype="multipart/form-data"

步骤

1.安装包

npm install multer

2.使用

// 1. 引入包
const multer = require('multer');
// 2. 配置
const upload = multer({dest:'uploads/'}) // 上传的文件会保存在这个目录下
// uploads表示一个目录名,你也可以设置成其它的

// 3. 使用
// 这个路由使用第二个参数 .upload.single表示单文件上传, 'cover' 表示要上传的文件在本次上次数据中的键名。对应于前端页面上的:
//  <input type="file" name='cover'/>

app.post("/postfile",upload.single('cover'), function(req,res){
    // req.file 记录了文件上传的信息
    // req.body 记录了其它普通参数(非文件)的信息
	  // 其它操作
})

说明:

  • 如果当前目录下没有uploads,它会自动创建uploads这个文件夹
  • upload.single只是处理了文件的上传。你仍可以通过req.body来获取其它参数

后端框架代码

传参方式前端 content-type后端框架express
请求行get方式req.query
请求体application/x-www-form-urlencodeapp.use(express.urlencoded()); req.body
请求体application/jsonapp.use(express.json() ); req.body
请求体multipart/form-data1. 引入包  const multer = require('multer'); 2. 配置app.post('/apiname', upload.single() , req.body)

接口传参-整体示例

目录结构

根目录
├── public
│   ├── js
│   │   └── axios.js
│   └── api.html     # 通过axios.js发请求调用接口
└── app.js           # 提供接口

要求:

  • localhost:3000/api.html。l可以访问public下的api.html文件
  • 在后端实现四个接口,分别来处理在api.html中发出的请求

前端

用axios来发请求

<!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>
</head>
<body>
    <button id="btn1_get">接口测试1:get请求带参数</button>
    <button id="btn2_post"> 接口测试2:post-传递普通键值对</button>
    <hr/>
    <button id="btn3_postJSON">接口测试3:post-传递json</button>
    <hr/>
    <form id="myform">
        <input type="text" name="title">
        <input type="file" name="cover">
    </form>
    <button id="btn4_formdata">接口测试4:post-传递formdata</button>
    <hr/>
    <script src="./js/axios.js"></script>
    <script>
    document.getElementById('btn1_get').addEventListener('click',() => {
        axios.get('http://localhost:3000/getapi', {params: {a:1,b:2}})
    })
    var obj = {
        "name":"abc",
        "address":{
            "a":1,
            "b":2,
            "info":"c"
        }
    }
    document.getElementById('btn2_post').addEventListener('click', () => {
        const params = new URLSearchParams();
        params.append('param1', 'value1');
        params.append('param2', 'value2');
        axios.post('http://localhost:3000/post', params, {
            headers: {"content-type":"application/x-www-form-urlencoded"}})
    })

    document.getElementById('btn3_postJSON').addEventListener('click', () => {
        axios.post('http://localhost:3000/postJSON', obj)
    })

    document.getElementById('btn4_formdata').addEventListener('click', () => {
        console.log(1)
        var fd = new FormData(document.getElementById('myform'));

        axios.post('http://localhost:3000/publish', 
            fd
        )
    })
    </script>
</body>
</html>

后端

// 实现get接口
const express = require('express')
const app = express();

app.use(express.static('public'))
// 引入bodyParse包
const bodyParser = require('body-parser')
// 使用包. 则在后续的post请求中
// 会自动加入req.body属性,这个属性中就包含了post请求所传入的参数
// 处理普通的键值对格式
// Content-Type: application/x-www-form-urlencoded
app.use(express.urlencoded())

// 处理JSON格式
// Content-Type: application/json;
app.use(express.json())

// 引入multer包
const multer = require('multer');

// 配置一下multer
// 如果本次post请求涉及文件上传,则上传到uploads这个文件夹下
// Content-Type: multipart/form-data;
var upload = multer({ dest: 'uploads/'})


// 实现接口1: get类型接口
// 返回所传入的参数,并附上上时间戳
app.get('/getapi',(req,res)=>{
    // 通过 req.query快速获取传入的参数
    console.log(req.query);
    let obj = req.query
    
    obj._t = Date.now(); 
    res.json( obj )
})

// 实现接口2:普通post 键值对
app.post('/post',(req,res)=>{
    // 希望在后端收到post传参
    console.log(req.body);

    let obj = req.body
    obj._t = Date.now();
    
    res.json(obj)
})

// 实现接口3:用来JSON格式的数据
// Content-Type: application/json;
app.post('/postJSON',(req,res)=>{
    // 希望在后端收到post传参
    console.log(req.body);
    
    // res.send('/postJSON')
    res.json( req.body )
})

// 实现接口4:接口formDate
app.post('/publish',upload.single('cover'),(req,res)=>{
    console.log('publish...')
    //upload.single('cover')
    // 这里的cover就是在页面中表单元素中的name
    // <input type="file" name="cover" />
    // 把要上传文件放在指定的目录
    console.log(req.file);
    // 其它参数,还是在req.body中找
    console.log(req.body);

    res.json({code:200,msg:'上传成功',info:req.file.path})
})

app.listen(3000,()=>{
    console.log('express应用在3000端口启动了'); 
})

拓展介绍-RESTful接口(了解)

网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备…)。因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致API构架的流行,甚至出现"APIFirst"的设计思想。RESTful API是目前比较成熟的一套互联网应用程序的API设计理论

REST(Representational State Transfer)表述性状态转换,REST指的是一组架构约束条件和原则。 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。REST本身并没有创造新的技术、组件或服务,而隐藏在RESTful背后的理念就是使用Web的现有特征和能力, 更好地使用现有Web标准中的一些准则和约束。

符合REST规范的设计,我们称之为RESTful设计。 它的设计哲学是将服务器端提供的内容实体看作一个资源,并表现在url上。

普通接口设计

例如:
接口名:localhost:8080/getarticle
类型:get
功能:获取文章信息

接口名:localhost:8080/addarticle
类型:post
功能:添加新文章

接口名:localhost:8080/delarticle
类型:post
功能:删除文章

接口名:localhost:8080/updatearticle
类型:post
功能:编辑文章

//------------下面是普通的api设计---------------

app.get('/getarticle',(req,res)=>{
    res.send('获取')
})

app.post('/addarticle',(req,res)=>{
    res.send('添加')
})

app.post('/delarticle',(req,res)=>{
    res.send('删除')
})
app.post('/updatearticle',(req,res)=>{
    res.send('编辑')
})

RESTful接口设计

大佬一般都在用, 懂我意思吧 区别上述功能,主要依靠接口名称和请求类型而在restful设计中,它们应该是这样的:
接口名:localhost:8080/articles
类型:get
功能:获取文章信息

接口名:localhost:8080/articles
类型:post
功能:添加新文章

接口名:localhost:8080/articles
类型:delete
功能:删除文章

接口名:localhost:8080/articles
类型:put
功能:编辑文章

RESTful设计是:

  • 通过URL设计资源。接口名一般都是名词,不包含动词。
  • 请求方式(get,post,delete,put)决定资源的操作类型

参考代码

const express = require('express')

const app = express();

app.get('/articles',(req,res)=>{
    res.send('获取')
})

app.post('/articles',(req,res)=>{
    res.send('添加')
})

app.delete('/articles',(req,res)=>{
    res.send('删除')
})
app.put('/articles',(req,res)=>{
    res.send('编辑')
})

app.listen(8080,()=>{
    console.log(8080); 
})