likes
comments
collection
share

全栈实现 HTTP 传输数据的 5 种形式(后端 Nest)

作者站长头像
站长
· 阅读数 8

http 数据传输的 5 种方式

URL 参数(URL Params)

主要用于 GET 请求,以传递简单的数据,比如:

https://example.com/api/users/123

在这个例子中,123 是一个 URL 参数,它指定了用户的 ID。

查询字符串(Query String)

query 也是在 url 传递额外的数据,以键值对形式出现,并且以问号 (?) 开头,多个参数之间用和号 (&) 分隔。

https://example.com/search?query=keyword&page=2

这里,query=keywordpage=2 是查询字符串参数,用于指定搜索关键词和页码。其中非英文的字符和一些特殊字符要经过编码,可以使用 encodeURIComponent 的 api 来编码:

const query = "?name=" + encodeURIComponent('云牧') + "&age=" + encodeURIComponent(20)

或者使用 query-string 库来处理:

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 请求。例如,提交表单时,数据可能会被编码为:

username=johndoe&password=123456

这种格式在 HTTP 请求体中发送,适合发送简单的文本数据。如果传递大量的数据,比如上传文件的时候就不是很合适了,因为文件 encode 一遍的话太慢了,这时候就可以用 form-data。

multipart/form-data(Form-data)

这种编码类型用于发送表单数据,尤其是包含文件上传的表单,需指定的 content-type 为 multipart/form-data。在这种情况下,每个表单项都作为请求的一个部分发送,允许二进制数据(如文件内容)和大块数据的传输。例如,HTTP 请求体可能会包含这样的内容:

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 数据:

{
  "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 接口的服务就跑起来了。全栈实现 HTTP 传输数据的 5 种形式(后端 Nest)

静态资源访问

main.ts 是负责启动 Nest 的 ioc 容器的,调用下 useStaticAssets 来支持静态资源的请求:

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 参数:

@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 里:

<!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 装饰器来获取查询字符串参数:

@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 请求:

<!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,是用于封装传输数据的对象:

export class CreatePersonDto {
  name: string;
  age: number;
}
@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:

<!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。

@Controller('api/person')
export class PersonController {
  @Post()
  bodyDemo(@Body() personDto: CreatePersonDto) {
    return `received: ${JSON.stringify(personDto)}`;
  }
}

前端代码使用 axios 发送 post 请求,默认传输 json 就会指定 content-type 为 application/json,不需要手动指定:

<!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 装饰器来获取:

@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:

 npm i -D @types/multer

前端代码使用 axios 发送 post 请求,指定 content-type 为 multipart/form-data:

<!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:全栈实现 HTTP 传输数据的 5 种形式(后端 Nest)服务端接收到了 file:全栈实现 HTTP 传输数据的 5 种形式(后端 Nest)