今天是伟大的爵士乐大师法兰克.辛纳区(Frank Sinatra)诞辰一百周年。天时地利人和,正是翻译这篇文章的好日子,错过再等一百年。

杭州此刻在下雨,阴冷潮湿。耳边是摇曳的爵士乐,我把猫关进了阳台,打开电脑,开始胡说八道。

(可以跳过正文直接看最后的完整代码)

原文链接:https://robots.thoughtbot.com/lets-build-a-sinatra


构建一个Sinatra

Sinatra是一个基于Ruby的快速开发Web应用程序基于特定域(domain-specific)语言。在一些小项目中使用过后,我决定一探究竟。

Sinatra是由什么组成的?

Sinatra的核心是Rack。我写过一篇文章关于Rack,如果你对Rack的工作原理有些困惑,那篇文章值得一读。Sinatra在Rack之上提供了一个给力的DSL。来看个例子:

get "/hello" do[200, {}, "Hello from Sinatra!"]
endpost "/hello" do[200, {}, "Hello from a post-Sinatra world!"]
end

当这段代码执行的时候,我们发送一个GET/hello,将看到Hello from Sinatra!;发送一个POST请求给/hello将看到Hello from a post-Sinatra world!。但这个时候,任何其他请求都将返回404.

结构

Sinatra 的源码,我们一起提炼出一个类似Sinatra的结构。

我们将创造一个基于Sinatra那种可继承可扩展的类。它保存请求路由表(GET /hello handleHello),当收到GET /hello请求的时候,能去调用handleHello函数。事实上,它能很好的处理所有的请求。当收到请求的时候,它会遍历一遍路由表,如果没有合适的请求,就返回404。

OK,开搞。

就叫它Nancy吧,别问我为什么。

第一步要做的事是:创建一个类,它有一个get方法,能捕获请求的path并找到对应的函数。


# nancy.rbrequire "rack"module Nancyclass Basedef initialize@routes = {}endattr_reader :routesdef get(path, &handler)route("GET", path, &handler)endprivatedef route(verb, path, &handler)@routes[verb] ||= {}@routes[verb][path] = handlerendendend

route函数接收一个动词(HTTP请求方法名),一个路径和一回调方法并保存到一个Hash结构中。这样设计可以让POST /helloGET /hello不会混乱。

然后在下面加一些测试代码:

nancy = Nancy::Base.newnancy.get "/hello" do[200, {}, ["Nancy says hello"]]
endputs nancy.routes

可以看到,Nancy使用了nancy.get替代了Sinatra的get显得没那么简洁,本文最后会解决这个问题。

如果我们这时执行程序,会看到:


{ "GET" =\> { "/hello" =\> \#\<Proc:0x007fea4a185a88@nancy.rb:26\> } }

这个返回结果,我们的路由表工作的很好。

引入了 Rack 的 Nancy

现在我们给Nancy增加调用Rack的call方法,让它成为一个最小的Rack程序。这些代码是我的另一篇Rack文章中的:

# nancy.rb
def call(env)@request = Rack::Request.new(env)verb = @request.request_methodrequested_path = @request.path_infohandler = @routes[verb][requested_path]handler.call
end

首先,我们从Rack的请求的env环境变量参数中的得到请求方法(HTTP/GET等)和路径(/the/path),然后根据这些信息去路由表中招对应的回调方法并调用它。回调方法需返回一个固定的结构,这个结构包含状态码、HTTP Header和返回的内容,这个结构正是Rack的Call所需要的,它会经由Rack返回给用户。

我们增加一个这样的回调给Nancy::Base

nancy = Nancy::Base.newnancy.get "/hello" do[200, {}, ["Nancy says hello"]]
end# This line is new!
Rack::Handler::WEBrick.run nancy, Port: 9292

现在这个Rack App已经能运行了。我们使用WEBrick作为服务端,它是Ruby内置的。

nancy = Nancy::Base.newnancy.get "/hello" do[200, {}, ["Nancy says hello"]]
end# This line is new!
Rack::Handler::WEBrick.run nancy, Port: 9292

执行ruby nancy.rb,访问http://localhost:9292/hello,一切工作的很好。需要注意,Nancy不会自动重新加载,你所做的任何改动都必须重新启动才会生效。Ctrl+C能在终端中停止它。

错误处理

访问路由表中处理的路径它能正常的工作,但是访问路由表中不存在的路径比如http://localhost:9292/bad你只能看到Rack返回的默认错误信息,一个不友好的Internal Server Error页面。我们看下如何自定义一个错误信息。

我们需要修改call方法

def call(env)@request = Rack::Request.new(env)verb = @request.request_methodrequested_path = @request.path_info-  handler = @routes[verb][requested_path]
-
-  handler.call
+  handler = @routes.fetch(verb, {}).fetch(requested_path, nil)+  if handler
+    handler.call
+  else
+    [404, {}, ["Oops! No route for #{verb} #{requested_path}"]]
+  endend

现在,如果请求一个路由表中没有定义的路径回返回一个404状态码和错误信息。

从 HTTP 请求中得到更多信息

nancy.get现在只能得到路径,但要想正常工作,它需要得到更多的信息,比如请求的参数等。有关请求的环境变量被封装在Rack::Requestparams中。

我们给Nancy::Base增加一个新的方法params

module Nancyclass Base## ...other methods....#def params@request.paramsendend
end

需要这些请求信息的回调处理中,可以访问这个params方法来得到。

访问 params

再来看一下刚刚添加的这个params实例方法。

修改调用回调这部分代码:

 if handler
-  handler.call
+  instance_eval(&handler)else[404, {}, ["Oops! Couldn't find #{verb} #{requested_path}"]]end

这里面有一些小把戏让人困惑,为啥要用instance_eval替代call呢?

  • handler是一个没有上下文的lambda

  • 如果我们使用call去调用这个lambda,它是没法访问Nancy::Base的实例方法的。

  • 使用instance_eval替代call来调用,Nancy::Base的实例信息会被注入进去,它可以访问Nancy::Base的实例变量和方法(上下文)了。

所以,现在我们能访问params在handler block中了。试试看:

nancy.get "/" do[200, {}, ["Your params are #{params.inspect}"]]
end

访问http://localhost:9292/?foo=bar&hello=goodbye,有关请求的信息,都会被打印出来。

支持任意的 HTTP 方法

到目前为止,nancy.get能正常的处理GET请求了。但这还不够,我们要支持更多的HTTP方法。支持它们的代码和get很相似:

# nancy.rb
def post(path, &handler)route("POST", path, &handler)
enddef put(path, &handler)route("PUT", path, &handler)
enddef patch(path, &handler)route("PATCH", path, &handler)
enddef delete(path, &handler)route("DELETE", path, &handler)
end

通常在POSTPUT请求中,我们会想访问请求的内容(request body)。既然现在在回调中,我们已经可以访问Nancy::Base的实例方法和变量了,让@request变得可见就好(迷糊的去翻上面的call方法代码):

attr_reader :request

访问requrest实例变量在回调中:

nancy.post "/" do[200, {}, request.body]
end

访问测试:

$ curl --data "body is hello" localhost:9292
body is hello

现代化进程

我们来做以下优化:

  1. 使用params实例方法来替代直接调用request.params

def paramsrequest.params
end
  1. 允许回调方法返回一个字符串

   if handler
-    instance_eval(&handler)
+    result = instance_eval(&handler)
+    if result.class == String
+      [200, {}, [result]]
+    else
+      result
+    endelse[404, {}, ["Oops! Couldn't find #{verb} #{requested_path}"]]end

这样处理回调就简化很多:

nancy.get "/hello" do"Nancy says hello!"
end

使用代理模式继续优化 Nancy::Application

在使用Sinatra的时候,我们使用getpost来进行请求处理优雅强大又直观。它是怎么做到的呢?先考虑Nancy的结构。它执行的时候,我们调用Nancy::Base.new得到一个新的实例,然后添加处理path的函数,然后执行。那么,如果有一个单例,就可以实现Sinatra的效果,将文件中处理路径的方法添加给这个单例并执行即可。(译者注:这段的译文和原文没关系,纯属杜撰。如果迷惑,请参考原文)

是时候考虑将nancy.get优化为get了。
增加Nancy::Base单例:

module Nancyclass Base# methods...endApplication = Base.new
end

增加回调:

nancy_application = Nancy::Applicationnancy_application.get "/hello" do"Nancy::Application says hello"
end# Use `nancy_application,` not `nancy`
Rack::Handler::WEBrick.run nancy_application, Port: 9292

增加代理器(这部分代码来自Sinatra的源码):

module Nancymodule Delegatordef self.delegate(*methods, to:)Array(methods).each do |method_name|define_method(method_name) do |*args, &block|to.send(method_name, *args, &block)endprivate method_nameendenddelegate :get, :patch, :put, :post, :delete, :head, to: Applicationend
end

引入Nancy::DelegateNancy模块:

include Nancy::Delegator

Nancy::Delegator提供代理如getpatchpost,等一系列方法。当在Nancy::Application中调用这些方法的时候,它会按图索骥找到代理器的这些方法。我们实现了和Sinatra一样的效果。

现在可以删掉那些创建Nancy::Base::newnancy_application的代码啦!Nancy的使用已经无限接近Sinatra了:

t "/bare-get" do"Whoa, it works!"
endpost "/" dorequest.body.read
endRack::Handler::WEBrick.run Nancy::Application, Port: 9292

还能使用rackup来进行调用:

# config.ru
require "./nancy"run Nancy::Application

Nancy的完整代码:

# nancy.rb
require "rack"module Nancyclass Basedef initialize@routes = {}endattr_reader :routesdef get(path, &handler)route("GET", path, &handler)enddef post(path, &handler)route("POST", path, &handler)enddef put(path, &handler)route("PUT", path, &handler)enddef patch(path, &handler)route("PATCH", path, &handler)enddef delete(path, &handler)route("DELETE", path, &handler)enddef head(path, &handler)route("HEAD", path, &handler)enddef call(env)@request = Rack::Request.new(env)verb = @request.request_methodrequested_path = @request.path_infohandler = @routes.fetch(verb, {}).fetch(requested_path, nil)if handlerresult = instance_eval(&handler)if result.class == String[200, {}, [result]]elseresultendelse[404, {}, ["Oops! No route for #{verb} #{requested_path}"]]endendattr_reader :requestprivatedef route(verb, path, &handler)@routes[verb] ||= {}@routes[verb][path] = handlerenddef params@request.paramsendendApplication = Base.newmodule Delegatordef self.delegate(*methods, to:)Array(methods).each do |method_name|define_method(method_name) do |*args, &block|to.send(method_name, *args, &block)endprivate method_nameendenddelegate :get, :patch, :put, :post, :delete, :head, to: Applicationend
endinclude Nancy::Delegator

Nancy的使用代码:

# app.rb
# run with `ruby app.rb`
require "./nancy"get "/" do"Hey there!"
endRack::Handler::WEBrick.run Nancy::Application, Port: 9292

我们来回顾一下都发生了什么:

  • 起名为N(T)an(i)c(r)y Sinatra(别问为什么)

  • 实现一个以来Rack的Web App

  • 简化nancy.getget

  • 支持子类化Nancy::Base来实现更丰富的自定义。

课外阅读

Sinatra的代码几乎全部都在base.rb。代码密度有点大,阅读完本文再去看,更容易理解一些了。从call!开始是个不错的选择。然后是Response类,它是Rack::Response的子类,请求返回的信息封装在这里。还有Sinatra是基于类的,Nancy是基于对象,有些在Nancy中的示例方法,在Sinatra中是作为类方法实现的,这也是需要注意的一点。

如何构建Sinatra?相关推荐

  1. 使用docker构建并测试一个基于Sinatra的Web应用程序

    内容来自<第一本Docker书>5.2节和博文整理而成 使用Docker构建并测试Web应用程序 在这个例子里,我们将创建一个应用程序,它接收输入的URL参数,并以JSON散列的结构输出到 ...

  2. 关于《第一本Docker书》5.2.1 sinatra

    构建Sinatra应用程序 参考:https://blog.csdn.net/liusai_soso/article/details/80360751 dockerfile如下 FROM ubuntu ...

  3. 使用Docker构建并测试web应用程序

    使用Docker构建并测试web应用程序 构建Sinatra应用程序 构建Dockerfile FROM ubuntu:lastest MAINTAINER James Turnbull james@ ...

  4. Docker学习笔记五 在测试中使用Docker

    2019独角兽企业重金招聘Python工程师标准>>> 5.1 使用Docker测试静态网站(Nginx) 将项目命名为Sample 首先建立构建环境 mkdir sample cd ...

  5. Docker测试环境笔记

    构建Sinatra应用程序 [root@dockerWEBrickDocker]# vim Dockerfile FROM ubuntu MAINTAINER wyfTurnbull huisebug ...

  6. 第一本Docker书pdf

    下载地址:网盘下载 内容简介  · · · · · · 全球第一本Docker技术图书中文版,Docker中文社区鼎力支持! Docker核心团队成员权威著作,在技术圈中很有影响力. 既是第一本Doc ...

  7. Grape和Sinatra结合使用

    Grape && Sinatra Grape(https://github.com/intridea/grape) is a REST-like API micro-framework ...

  8. 完整全面的Java资源库(包括构建、操作、代码分析、编译器、数据库、社区等等)...

    构建 这里搜集了用来构建应用程序的工具. Apache Maven:Maven使用声明进行构建并进行依赖管理,偏向于使用约定而不是配置进行构建.Maven优于Apache Ant.后者采用了一种过程化 ...

  9. spark restful_使用Spark构建简单的RESTful API

    spark restful 免责声明 :这篇文章是关于名为Spark的Java微型Web框架的,而不是关于数据处理引擎Apache Spark的 . 在此博客文章中,我们将看到如何使用Spark构建简 ...

最新文章

  1. pytorch中load和load_state_dict区别
  2. relu与maxpool
  3. sparksql dataframe变成csv保存_Spark大数据分析(三):DataFrame和SQL
  4. excel合并两列内容_不要再粘粘粘,合并Excel表格数据,3秒完成
  5. 本地项目上传到gitlab上
  6. 信息学奥赛一本通 1190:上台阶 | OpenJudge NOI 2.3 3525:上台阶
  7. kendoGrid动态列的实现-高级查询结果展示优化过程
  8. 关于wps公式编辑器的大括号里面空白很大
  9. oracle asm 日志,oracle 11g RAC 下ASM实例的alert日志告诉我们什么
  10. android高德地图热力图,2D 热力图-热力 HeatmapLayer-示例中心-Loca API 示例 | 高德地图API...
  11. 夜神模拟器和虚拟机(docker) 在windows上设置不兼容
  12. 利用python选股的思路
  13. linux系统 详细解析 cpu 信息
  14. Day 5:自己编写的mysql类
  15. 福特汉姆计算机专业,福特汉姆大学计算机如何
  16. 推荐与广告 中的概念和指标
  17. Life Restart 人生模拟器 网址
  18. 小米5s plus 刷机 国际版
  19. 复数基础——负数的虚数根,复共轭,复数加法、减法、乘法、除法_6
  20. 公司章程违反了公司法该怎么办

热门文章

  1. 传感器自学笔记第十二章——火焰传感器+HC-SR501人体感应模块+按键模块+红绿双色LED(共阴)模块+按键开关模块+三色LED
  2. 微型计算机周明德课后答案,周明德微机原理答案
  3. 茶的文化我宣传|茶叶储存
  4. ESP8266+oled连接心知天气显示时间和实时天气
  5. SMI BATCAM2.0 超声波相机(声学成像仪)介绍
  6. CA6140车床拨叉831003课程设计(说明书+CAD图纸+工序卡)
  7. js/javascript正则表达式中/g /i /m的用法,以及实例
  8. 1616网址导航php源码,1616上网导航_1616网址导航_我的个性化主页
  9. musicxml文档笔记(待续)
  10. max模型怎么导入ue4_3dsMax模型转UE4