第二十五天-圣诞奖金 - 并发HTTP服务器实施和scripter的方法

Bonus Xmas – Concurrent HTTP Server implementation and the scripter’s approach

Bonus Xmas – Concurrent HTTP Server implementation and the scripter’s approach

首先,我想强调 Jonathan WorthingtonRakudo RakuIO::Socket::Async 中的工作。谢谢 Jon!

我喜欢制作脚本;编写组织良好的动作序列,获得结果并对它们进行处理

当我从 Raku 开始时,我发现了一个壮观的生态系统,我可以按照自己喜欢的方式实践我的想法:脚本方式。其中一个想法是实现一个小型的 HTTP 服务器来玩玩。查看与 Raku,HTTP 和套接字相关的其他项目和模块,我发现背后的作者是具有面向对象编程经验的程序员。

Raku 范式

Raku 支持三种最流行的编程范式:

  • 面向对象
  • 函数式
  • 过程式

我认为,当你设计一个将会增长的应用程序或服务时,面向对象的范式是很好的,它会做许多不同的事情并且会有很多变化。但我不喜欢那些变化太大,会有很多变化的东西;这就是为什么我喜欢使用原生过程式方法的脚本,因为它能够快速提升简单性和有效性。我喜欢小(一步一步)但能快速完成伟大东西的事物。

函数式范式在我看来非常棒;你可以使用一个函数,并像 var 一样使用它,以及其他令人惊讶的事情。

Raku Supplies 就像一个 V12 引擎

在我开始将 rakuintro.com 翻译成西班牙语后不久,我开始使用 Raku。看看 Raku 的文档,我发现了 Raku 巨大的并发潜力。 Raku在并发方面比我想象的更加强大。

我使用 Raku 的 HTTP 服务器的思想始于 Raku Supplies(具有多个订阅者的异步数据流),具体来说就是 IO::Socket::Async类。所有的套接字管理,数据传输和并发性实际上都是自动且易于理解的。制作并玩一玩小并发但强大的服务是极好的。

基于 IO::Socket::Async 文档的示例,我开始在 mini-http-cgi-server 项目中实现一个支持 pseudoCGI 的小型 HTTP 服务器,并且按照我的预期工作。当我得到我想要的东西时,我很满意,我离开了这个项目一段时间。我不喜欢事情发展太多。

但之后,为马德里 Perl Workshop 2017 做了一次演讲(感谢马德里 Perl Mongers巴塞罗那 Perl Mongers 团队为这次活动提供的支持),我有足够的动力去做更实际的事情,让网络前端编码人员可以完成他们的工作并且与 Raku正在等待的后端进行交流。一方面是典型的公共 html 静态结构,另一方面是一个包含多个 web 服务的 Raku 模块,用于等待来自前端人员的 web 请求。

然后 Wap6 诞生了(Web App Raku)。

Wap6 的结构

我喜欢 Wap6 实现的 Web 应用程序的结构:

  • public
  • webservices

公共文件夹包含友好的前端东西,比如静态 html,javascript,css 等,也就是前端开发者空间。 webservices 文件夹包含后端的东西:一个 Raku 模块,包括每个 webservice 的一个函数。

相同的文件夹级别包含解决方案入口点,一个 Raku 脚本,其中包括初始化服务器参数,其中包含路由和 webservices 之间的映射:

my %webservices =
  '/ws1' => ( &ws1, 'html' ),
  '/ws2' => ( &ws2, 'json' )
;

正如你所看到的,不仅路由被映射到相应的 webservices,而且还指定 webservice 的返回内容类型(content-type )(如 HMTL 或 JSON)。也就是说,在 Web 浏览器中键入 http://domain/ws1,ws1 函数会返回具有相应内容类型的响应数据,我们将在稍后看到。

所有到 webservices 的路由都在 %webservices 散列中,并通过其他有用的命名参数传递给主函数 wap

wap(:$server-ip, :$server-port, :$default-html, :%webservices);

Wap6 的核心

wap 函数位于 Wap6 使用的核心 lib 模块的外面,并包含并发和优雅的 V12 引擎:

react {   
  whenever IO::Socket::Async.listen($server-ip,$server-port) -> $conn {
    whenever $conn.Supply(:bin) -> $buf {
      my $response = response(:$buf, :$current-dir, :$default-html, :%webservices);
      $conn.write: $response.encode('UTF-8');
      $conn.close;
    }
  }
}

这是一个三分(react – whenever – IO::Socket::Async)响应式,并发和异步的上下文。当传输从Web客户端($conn)到达时,它将被放置在 bin 类型的新 Supply $buf ($conn.Suply(:bin))中,$buf%webservices 哈希等其他内容被发送到运行 HTTP 逻辑的响应函数。最后,响应函数的返回被写回到 Web 客户端。

响应函数(也位于核心库 lib 中)包含 HTTP 解析器的东西:它将传入数据(HTTP 实体)分割为头和主体,它执行验证,它需要基本的 HTTP 头信息,如方法(GETPOST)和 URI(统一资源标识符),它确定所请求的资源是 webservice(来自 webservices 文件夹)还是静态文件(来自公共文件夹),从资源中获取数据(来自静态文件或 webservice)并返回到 wap 函数以将响应写入 Web 客户端,如我们以前所见。

Webservices

响应函数验证 $buf 并从请求头中提取 HTTP 方法,可以是 GETPOST(我认为将来它不会支持更多的 HTTP 方法)。使用 GET 方法时,它将 URL 参数(如果有的话)放入 $get-paramsPOST 方法的情况下,它将主体请求放入 $body

然后是时候检查 Web 客户端是否请求了 webservice。 $get-params 包含了 URI 并用 URI 模块提取,最终结果放在 $path

given $path {
  when %webservices{"$_"}:exists {
    my ( &ws, $direct-type ) = %webservices{"$_"};
    my $type = content-type(:$direct-type);
    return response-headers(200, $type) ~ &ws(:$get-params, :$body);
  }
  ..
}

如果 %webservices 哈希中存在 $path,则客户端需要一个 webservice。然后它从 %webservices 散列(是的,我也喜欢函数式范式:-))和对应的内容类型中提取相应的 webservice 可调用函数 &ws。然后它使用 $get-params 和请求 $body 参数调用 webservice 函数 &ws。最后它返回连接的 HTTP 响应实体:

  • 具有状态 HTTP 200 OK 和给定内容类型(来自内容类型函数)的响应头。
  • webservice 输出。

可调用 webservice &ws 可以是 ws1,位于 webservices 文件夹的 Raku 模块中:

sub ws1 ( :$get-params, :$body ) is export {
  if $get-params { return 'From ws1: ' ~ $get-params; }
  if $body { return 'From ws1: ' ~ $body; }
}

在这个演示上下文中,webservice 简单地返回输入,即 $get-params(当 GET)或 $body(POST时)。

当客户端请求静态文件时

放弃所有其他可能性后,如果客户端请求公用文件夹中托管的静态文件(如html,js,css等),则:

given $path {
..
  default {
    my $filepath = "$current-dir/public/$path";
    my $type = content-type(:$filepath);
    return response-headers(200, $type) ~ slurp "$current-dir/public/$path";
  }
}

它返回包含匹配内容类型和请求文件内容的响应头。

这就是所有的了!以脚本过程式方式使用并发 Web服务:Wap6

结语

我很满意 Wap6 的结果。我并不假装它增长很多,但我总是想继续添加更多功能:SSL支持(完成!),会话管理(进行中),Cookie,文件上传等。

Raku 为表执行并发网络操作提供了非常强大的方法:IO::Socket::Async,一个杰作。另外,使用 Raku,您可以根据需要混合使用面向对象,过程式和函数式范式。借助这些功能,您可以设计一个并发异步服务并快速实现。

如果您希望在 Raku 生态系统中使用 HTTP 服务和并发性更严肃的方法,请看看 Cro,它代表了一个很好的机会,可以将 Raku 作为 HTTP 服务空间中的强大实体。Jonathan Worthington 在同样的 Advent Calendar 的第九天写的就是关于 Cro

同时,我将继续使用 Wap6,以脚本的方式,贡献 Raku 生态系统,并从世界上最好的编程人员那里学习,我的意思是:PerlRaku 程序员,当然:-)

Raku 

comments powered by Disqus