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

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=keywordpage=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 --watch 启动 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:

相关推荐
y先森3 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy3 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189113 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿4 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡5 小时前
commitlint校验git提交信息
前端
虾球xz6 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇6 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒6 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员6 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐6 小时前
前端图像处理(一)
前端