http 数据传输的 5 种方式
URL 参数(URL Params)
主要用于 GET
请求,以传递简单的数据,比如:
ruby
https://example.com/api/users/123
在这个例子中,123
是一个 URL 参数,它指定了用户的 ID。
查询字符串(Query String)
query 也是在 url 传递额外的数据,以键值对形式出现,并且以问号 (?) 开头,多个参数之间用和号 (&) 分隔。
ini
https://example.com/search?query=keyword&page=2
这里,query=keyword
和 page=2
是查询字符串参数,用于指定搜索关键词和页码。
其中非英文的字符和一些特殊字符要经过编码,可以使用 encodeURIComponent 的 api 来编码:
javascript
const query = "?name=" + encodeURIComponent('云牧') + "&age=" + encodeURIComponent(20)
或者使用 query-string 库来处理:
javascript
const queryString = require('query-string');
queryString.stringify({
name: '云牧',
age: 20
});
application/x-www-form-urlencoded(Form-urlencoded)
这是 HTML 表单提交的默认编码格式。只需要指定的 content-type 是 application/x-www-form-urlencoded。
和使用 query 字符串的方式不同的是它放在了请求 body 里。
因为内容也是 query 字符串,所以也要最好用 encodeURIComponent 的 api 或者 query-string 库对内容编码下。
当表单设置为这种编码类型时,表单数据会被编码成键值对,类似于查询字符串的格式。这种类型通常用于 POST 请求。
例如,提交表单时,数据可能会被编码为:
ini
username=johndoe&password=123456
这种格式在 HTTP 请求体中发送,适合发送简单的文本数据。
如果传递大量的数据,比如上传文件的时候就不是很合适了,因为文件 encode 一遍的话太慢了,这时候就可以用 form-data。
multipart/form-data(Form-data)
这种编码类型用于发送表单数据,尤其是包含文件上传的表单,需指定的 content-type 为 multipart/form-data。
在这种情况下,每个表单项都作为请求的一个部分发送,允许二进制数据(如文件内容)和大块数据的传输。
例如,HTTP 请求体可能会包含这样的内容:
css
POST /your-server-endpoint HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryePkpFF7tjBAqx29L
------WebKitFormBoundaryePkpFF7tjBAqx29L
Content-Disposition: form-data; name="username"
johndoe
------WebKitFormBoundaryePkpFF7tjBAqx29L
Content-Disposition: form-data; name="userfile"; filename="example.txt"
Content-Type: text/plain
... file contents here ...
------WebKitFormBoundaryePkpFF7tjBAqx29L--
form data 不再是通过 & 这种 url 方式分隔数据,而是用 ------ 和很多数字做为 boundary 分隔符。
这里,boundary 分隔符,用于区分不同的表单项。
因为多了一些只是用来分隔的 boundary,所以请求体会增大。
application/json(JSON)
传输 json 数据的话,直接指定 content-type 为 application/json 就行。
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,也易于机器解析和生成。使用 application/json 类型时,数据以 JSON 格式发送。
例如,HTTP 请求体可能会包含这样的 JSON 数据:
json
{
"username": "johndoe",
"password": "123456"
}
这种格式适合发送复杂结构的数据,如对象或数组。
使用 Nest 实现 5 种传输方式
Nest 创建一个 crud 服务是非常快的,只需要这么几步:
- 安装 @nestjs/cli,使用 nest new xxx 创建 Nest 项目
- 在根目录执行 nest g resource person 快速生成 person 模块的 crud 代码
- nest start:dev 启动 Nest 服务
这样一个有 person 的 crud 接口的服务就跑起来了。
静态资源访问
main.ts 是负责启动 Nest 的 ioc 容器的,调用下 useStaticAssets 来支持静态资源的请求:
typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { join } from 'path';
import { NestExpressApplication } from '@nestjs/platform-express';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useStaticAssets(join(__dirname, '..', 'public'), { prefix: '/static' });
await app.listen(3000);
}
bootstrap();
api 接口和静态资源的访问都支持了,接下来就分别实现下 5 种前后端 http 数据传输的方式吧。
url-param
Nest 里通过 :参数名
的方式来声明,使用 @Param
装饰器来获取 URL 参数:
typescript
@Controller('api/person')
export class PersonController {
@Get(':id')
paramDemo(@Param('id') id: string) {
return `received: id=${id}`;
}
}
只有 /api/person/xxx 的 get 请求才会走到这个方法,因为 @Controller('api/person') 和 @Get(':id') 会拼成一个路由。
前端代码 get 请求,参数放在 url 里:
html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body>
<script>
async function urlParam() {
const res = await axios.get('/api/person/1');
console.log(res);
}
urlParam();
</script>
</body>
query
使用 @Query
装饰器来获取查询字符串参数:
javascript
@Controller('api/person')
export class PersonController {
@Get('find')
queryDemo(@Query('name') name: string, @Query('age') age: number) {
return `received: name=${name} age=${age}`;
}
}
注意:这个 find 的路由要放到 :id 的路由前面,因为 Nest 是从上往下匹配的。
前端代码也是通过 axios 发送一个 get 请求:
html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body>
<script>
async function query() {
const res = await axios.get('/api/person/find', {
params: {
name: '云牧',
age: 20,
},
});
console.log(res);
}
query();
</script>
</body>
</html>
参数通过 params 指定,axios 会做 url-encode,不需要自己做。
上面两种(url-param、query)是通过 url 传递数据的方式,下面 3 种是通过 body 传递数据。
form-urlencoded
form urlencoded 其实是把 query 字符串放在了 body 里来传输数据,所以需要做 url-encode:
用 Nest 接收的话,使用 @Body
装饰器,Nest 会解析请求体,然后注入到 dto 中。
dto 是 data transfer object,是用于封装传输数据的对象:
typescript
export class CreatePersonDto {
name: string;
age: number;
}
typescript
@Controller('api/person')
export class PersonController {
@Post()
bodyDemo(@Body() personDto: CreatePersonDto) {
return `received: ${JSON.stringify(personDto)}`;
}
}
前端代码使用 post 方式请求,指定 content type 为 application/x-www-form-urlencoded,用 qs 做下 url-encode:
html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
<script src="https://unpkg.com/qs@6.10.2/dist/qs.js"></script>
</head>
<body>
<script>
async function formUrlEncoded() {
const res = await axios.post(
'/api/person',
Qs.stringify({
name: '云牧',
age: 20,
}),
{
headers: { 'content-type': 'application/x-www-form-urlencoded' },
},
);
console.log(res);
}
formUrlEncoded();
</script>
</body>
</html>
其实比起 form-urlencoded,使用 json 来传输更常用一些。
json
对于 JSON 数据,Nest 也要使用 @Body
装饰器。确保你的前端请求的 Content-Type 设置为 application/json。
javascript
@Controller('api/person')
export class PersonController {
@Post()
bodyDemo(@Body() personDto: CreatePersonDto) {
return `received: ${JSON.stringify(personDto)}`;
}
}
前端代码使用 axios 发送 post 请求,默认传输 json 就会指定 content-type 为 application/json,不需要手动指定:
html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body>
<script>
async function json() {
const res = await axios.post('http://localhost:3000/api/person', {
name: '云牧',
age: 20,
});
console.log(res);
}
json();
</script>
</body>
</html>
json 和 form-urlencoded 都不适合传递文件,想传输文件要用 form-data。
form-data
form-data 是用 ------ 作为 boundary 分隔传输的内容的:
Nest 解析 form-data 使用 FilesInterceptor 的拦截器,用 @UseInterceptors 装饰器启用,然后通过 @UploadedFiles 来取。
通过 form-data 传输的非文件的内容,同样是通过 @Body
装饰器来获取:
typescript
@Controller('api/person')
export class PersonController {
@Post('file')
@UseInterceptors(
AnyFilesInterceptor({
dest: 'uploads/',
}),
)
formDataDemo(
@Body() personDto: CreatePersonDto,
@UploadedFiles() files: Array<Express.Multer.File>,
) {
console.log(files);
return `received: ${JSON.stringify(personDto)}`;
}
}
需要安装 multer:
javascript
npm i -D @types/multer
前端代码使用 axios 发送 post 请求,指定 content-type 为 multipart/form-data:
html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body>
<input id="fileInput" type="file" multiple />
<script>
const fileInput = document.querySelector('#fileInput');
fileInput.onchange = formData;
async function formData() {
const data = new FormData();
data.set('name', '云牧');
data.set('age', 20);
data.set('file1', fileInput.files[0]);
data.set('file2', fileInput.files[1]);
const res = await axios.post('/api/person/file', data, {
headers: { 'content-type': 'multipart/form-data' },
});
console.log(res);
}
</script>
</body>
</html>
客户端打印了 name 和 age:
服务端接收到了 file: