从零开始建立一个Dart服务器

无框架的服务器端Dart系列中的第1部分。

我真的很喜欢能够使用和我写Flutter应用时一样的语言来编写服务器代码。做到这一点的两个主要框架是 Aqueduct 和 Angel 。不幸的是, Angel正在被废弃 ,Aqueduct已经有一段时间没有发布稳定的版本了(截至2020年12月)。

既然A的都出来了,那就到了B计划的时候了。

框架虽好,但总有一点魔力。作为Dart核心库之一的dart:io库,已经包含了制作HTTP服务器所需的低级类和函数。所以本文将教你如何自己创建这样一个服务器。

如果成功的话,这可能会变成一系列的文章。或者如果Aqueduct或者其他Dart框架重新上线,那么我可能会给你指出这个方向。

让我们开始吧。完整的代码在文章的最后,如果你迷路的话。

设置

我想你已经 安装了Dart 。我正在使用Dart 2.12,这样我就可以习惯使用 不可空类型 进行编码。目前它自带Flutter的测试版。

flutter channel beta
复制代码

或者使用测试版的 Dart SDK 。

现在在命令行中创建一个新的Dart项目,名为my_server(或任何你喜欢的)。

dart create my_server
复制代码

用你喜欢的IDE打开那个文件夹。 VS Code 和 IntelliJ 都有一个Dart插件。如果你还没有安装,请安装它。

创建一个服务器

用下面的代码替换my_server.dart。

import 'dart:io';
Future<void> main() async {
  final server = await createServer();
  print('Server started: ${server.address} port ${server.port}');
}
Future<HttpServer> createServer() async {
  final address = InternetAddress.loopbackIPv4;
  const port = 4040;
  return await HttpServer.bind(address, port);
}
复制代码

这将创建一个监听本地主机IP地址(127.0.0.1)、端口为4040的服务器。

你可以像这样从终端启动你的服务器。

dart run bin/my_server.dart
复制代码

你应该看到以下的打印输出。

Server started: InternetAddress('127.0.0.1', IPv4) port 4040
复制代码

恭喜你!你已经做了一个Dart服务器。你已经建立了一个Dart服务器。这很容易,不是吗?

你已经启动了服务器,但是它还没有真正做任何事情。按Control+C键强制关闭你的程序。

处理HTTP请求

HTTP请求包括GET、POST、PUT和DELETE等。如果你对它们不熟悉,那么你可以在 Becoming a backend developer - Part 1: 基础概念 阅读更多。

用下面的代码替换主函数。

Future<void> main() async {
  final server = await createServer();
  print('Server started: ${server.address} port ${server.port}');
  await handleRequests(server);
}
复制代码

并在my_server.dart中添加handleRequests作为顶层函数。

Future<void> handleRequests(HttpServer server) async {
  await for (HttpRequest request in server) {
    request.response.write('Hello from a Dart server');
    await request.response.close();
  }
}
复制代码

注释。

Stream<HttpRequest>

再次运行你的程序,然后在浏览器中打开以下地址。

你应该看到以下结果。

你的浏览器为此发出了一个GET请求。接下来你将看到如何路由不同类型的请求。

路由不同类型的请求

我们不要对每个请求都一视同仁,而是按类型进行路由。用下面的代码替换handleRequests方法。

Future<void> handleRequests(HttpServer server) async {
  await for (HttpRequest request in server) {
    switch (request.method) {
      case 'GET':
        handleGet(request);
        break;
      case 'POST':
        handlePost(request);
        break;
      default:
        handleDefault(request);
    }
  }
}
复制代码

现在,对于每一个进来的请求,你都要把它路由到一个不同的方法。接下来的几节将探讨如何处理GET、POST和其他任何方法。

我们将首先实现handleGet,所以暂时注释掉handlePost和handleDefault。

case 'POST':
        // handlePost(request);
        break;
      default:
        // handleDefault(request);
复制代码

处理GET请求

在my_server.dart中添加以下顶层代码。

var myStringStorage = 'Hello from a Dart server';
void handleGet(HttpRequest request) {
  request.response
    ..write(myStringStorage)
    ..close();
}
复制代码

注释。

  • myStringStorage全局变量在这里代表一个数据库。我们在这里从这个变量中读取数据,并将在下一节中向它写入数据。
  • GET请求不应该改变服务器状态,所以我们只需要在响应中传回myStringStorage的值。

保存你的工作,重新启动服务器。然后在浏览器中再次打开以下地址。

你应该看到和之前一样的结果。

处理POST请求

POST请求的目的是向服务器添加新资源。

将handleRequests中的handlePost行取消标注,然后在my_server.dart中添加以下顶层函数。

Future<void> handlePost(HttpRequest request) async {
  myStringStorage = await utf8.decoder.bind(request).join();
  request.response
    ..write('Got it. Thanks.')
    ..close();
}
复制代码

你还需要添加以下导入。

import 'dart:convert';
复制代码

注释:

  • handlePost主体中的第一行从请求中获取传入的数据块,将它们转换为UTF-8格式的字符串,并将它们连接成一个单一的字符串。
  • 一旦有了这个字符串,这个方法就用它来更新全局变量myStringStorage的值。这象征着向数据库写入数据。
  • 因为我们实际上只是更新一个现有的值,而不是创建一个新的值,所以定义REST API使用PUT而不是POST可能更有意义。但对于我们的例子来说,POST是可以的。
  • 还没有任何安全性。如果你把这个服务器放在网上,世界上任何人都可以更新myStringStorage。在未来的文章中,我想谈谈认证和授权。现在你可以阅读 服务器端Dart的认证 。

保存你的工作并重新启动服务器。

你不能用浏览器进行POST请求,所以你需要另一种工具,比如 curl 或 Postman 。你可以通过阅读 这篇文章 来了解这些和其他的方法来进行POST请求。

我将使用Postman来进行POST请求。打开Postman,执行以下步骤。

  • 选择POST作为请求类型。
  • 在地址栏中写上 http://localhost:4040。
  • 选择Body选项卡。
  • 写上任何字符串,例如Hello。
  • 点击发送按钮。

你应该会收到来自服务器的200 OK响应,并在响应的正文中写上Got it. Thanks.在响应的正文中。

在Postman中看到的来自服务器的响应

如果你运行另一个GET请求(无论是在你的浏览器或Postman),你应该看到myStringStorage的更新值。

很好!你成功地从客户端更新了服务器。

处理其他请求

客户端还可以发出很多其他的HTTP请求--比如PUT、PATCH、DELETE等等。你可以在你的路由器中添加更多的方法来处理它们中的任何一个。然而,在这里我们只想说,在我们的服务器上不允许有其他请求。 取消对handleDefault这一行的标注,然后添加下面的顶层方法。

void handleDefault(HttpRequest request) {
  request.response
    ..statusCode = HttpStatus.methodNotAllowed
    ..write('Unsupported request: ${request.method}.')
    ..close();
}
复制代码

注释:这次你将状态码设置为methodNotAllowed。

  • 这次你把状态代码设置为 methodNotAllowed. 这相当于一个405的代码。
  • 你在响应中写一个错误,然后把它发回给客户端。

保存你的工作并重新启动服务器。

在Postman中发送一个PUT请求来测试它。

Postman中看到的服务器错误响应

干得好 Good work. 你已经为建立自己的服务器开了个好头。你会在下面找到完整的代码。不过首先,看看接下来要采取的一些步骤。

继续

你可以到官方的 Dart服务器文档 中详细阅读本文所涉及的大部分概念。你也应该看看Dart团队维护的 http_server 和 shelf 包。

除非你只支持少量公共数据的简单GET请求,否则在你使用Dart作为一个真正的应用的后端之前,有几个主要的缺失部分需要解决。Dart是一个真正的应用程序的后端:

  • 数据库。你需要能够从Dart与数据库进行通信,以存储和检索资源。
  • 认证:你需要能够从Dart与数据库进行通信以存储和检索资源。你需要能够隐藏私人数据,只允许授权用户更新服务器上的资源。
  • 部署:你需要能够隐藏私人数据,并且只允许授权用户更新服务器上的资源。在你的本地机器上运行服务器是很好的,但它最终需要从外部世界访问。

虽然在技术上并不是必须的,但以下主题也会是有用的知道。

  • 测试: 如果你不测试你的服务器代码,你就不能确定做一个改变不会破坏它。
  • 文件。你要返回给客户的可能不仅仅是数据库中的字符串。最终你也需要服务于文件。
  • 并发性。如果你的服务器有一个以上的核心,你可能会使用它。为此你要在另一个隔离区上启动你的服务器。
  • CI/CD:手动上传服务器代码的变化,过一段时间就会变得有点老。如果能建立一个系统,在服务器有变化时自动运行测试和更新,那就更好了。

没有承诺,但我想继续写关于这些主题的文章,这样你就可以学会如何自己做这些事情,而不需要依赖一个框架。但即使你真的使用了框架,知道事情是如何工作的,也会让你更有效率。

完整的代码

import 'dart:convert';
import 'dart:io';

Future<void> main() async {
  final server = await createServer();
  print('Server started: ${server.address} port ${server.port}');
  await handleRequests(server);
}

Future<HttpServer> createServer() async {
  final address = InternetAddress.loopbackIPv4;
  const port = 4040;
  return await HttpServer.bind(address, port);
}

Future<void> handleRequests(HttpServer server) async {
  await for (HttpRequest request in server) {
    switch (request.method) {
      case 'GET':
        handleGet(request);
        break;
      case 'POST':
        handlePost(request);
        break;
      default:
        handleDefault(request);
    }
  }
}

var myStringStorage = 'Hello from a Dart server';

void handleGet(HttpRequest request) {
  request.response
    ..write(myStringStorage)
    ..close();
}

Future<void> handlePost(HttpRequest request) async {
  myStringStorage = await utf8.decoder.bind(request).join();
  request.response
    ..write('Got it. Thanks.')
    ..close();
}

void handleDefault(HttpRequest request) {
  request.response
    ..statusCode = HttpStatus.methodNotAllowed
    ..write('Unsupported request: ${request.method}.')
    ..close();
}
复制代码

www.deepl.com 翻译

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值