Bonus Xmas – Concurrent HTTP Server implementation and the scripter’s approach
首先,我想强调 Jonathan Worthington 在 Rakudo Raku 和 IO::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 头信息,如方法(GET 或 POST)和 URI(统一资源标识符),它确定所请求的资源是 webservice(来自 webservices 文件夹)还是静态文件(来自公共文件夹),从资源中获取数据(来自静态文件或 webservice)并返回到 wap 函数以将响应写入 Web 客户端,如我们以前所见。
Webservices
响应函数验证 $buf 并从请求头中提取 HTTP 方法,可以是 GET 或 POST(我认为将来它不会支持更多的 HTTP 方法)。使用 GET 方法时,它将 URL 参数(如果有的话)放入 $get-params。 POST 方法的情况下,它将主体请求放入 $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 生态系统,并从世界上最好的编程人员那里学习,我的意思是:Perl 和 Raku 程序员,当然:-)