构建自己的MVC框架(Ruby语言实现)-- 2. 创建ApplicationController

简介: 构建自己的MVC框架(Ruby语言实现)-- 2. 创建ApplicationController

在这章我们将动手写我们自己的第一个控制器,通过学习,我们也可以理解Rails是怎样实现路由请求的。

我们现在已经有了一个很基础的gem和应用程序,gem也已经安装到本地。假如你没有这些,或者你不喜欢你在第一章写的代码,你可以下载示例代码。

我们将依据本书的每章依次在版本号上加1。假如你正用你自己的代码,你可以修改它,或者 不修改,都可以。

为了改变gem的版本,打开rulers/lib/rulers/version.rb,把常数从“0.0.1”改到“0.0.2”。

下次你重新安装你的gem的时候,你需要输入:

$ gem build rulers.gemspec
$ gem insall rulers-0.0.2.gem

你应该删除rulers/rulers-0.0.1.gem,这样你就不会错误地安装和运 行旧的版本。

示例源代码

所有章节的示例源代码均保存在Github上:http://github.com/noahgibbs/rulershttp://github.com/noahgibbs/best_quotes

一旦你克隆了仓库,在每个项目的目录下面运行

$ git checkout chapter2
$ git checkout -b chapter_2_mine

来创建一个新的名为“chapter_2_mine”的分支。

关于Rack

上一章提到的Rack的复杂的返回值可能需要好好解释一番。

所以让我们解释一下吧。

这是其一:

[200, {'Content-Type' => 'text/html'},
['Hello!']]

让我们把它分解开来。第一个数字,200,是HTTP状态码。假如你返回了404,然后浏览器将 显示404信息--找不到页面。假如你返回500,浏览器将提示服务器错误。

下一个hash是header。你可以返回所有的header来设置cookie,改变安全设置以及其他别的东西。现在对我们来说最重要的一个是'Content-Type',它必须是‘text/html’。它告诉浏览器按照HTML来渲染文本,而不是JSON、XML、RSS,或者其他别的东西。

最后,是Body。这里我们只是包含字符串的部分。所以浏览器将显示“Hello!”

后面我们将解释Rack的“env”对象,它是一串有趣的hash值。现在,你需要知道的是它们之中一个名为PATH_INFO的值,它是URL中服务器名称后面不包括查询参数的的那部分。它也是Rails应用程序里告诉Rails使用哪个控制器的那个动作的部分。

路由

一个请求到达你的网页服务器或应用程序服务器,Rack把它传递给你的代码。Rulers将需要 路由请求 -- 那意味着它从那个请求了取走URL,回答了这个问题,“什么控制器和什么动作处理这个请求?”我们将从简单的路由的开始。

具体来说,我们准备从Rails的默认路由开始。那意味着形如“http://yourhost.com/category/action”的URL将被路由到CategoryController#action。(译者注:按照Rails的惯例,通常控制器使用控制器名称最后的名词的复数形式,即CategoriesController。)

在Rulers的目录下,打开lib/rulers.rb

# rulers/lib/rulers.rb
require "rulers/version"
require "rulers/routing"
module Rulers
  class Application
    def call(env)
      klass, act = get_controller_and_action(env)
      controller = klass.new(env)
      text = controller.send(act)
      [200, {'Content-Type' => 'text/html'},
        [text]]
    end
  end
  class Controller
    def initialize(env)
      @env = env
    end
    def env
      @env
    end
  end
end

我们的Application#call方法现在根据URL获取到了控制器和动作,然后创建了一个新的控制器,然后把它发送给动作。对于像“http://mysite.com/people/create”这样的URL,你会希望klass值为PeopleControlleraction值为create

我们会在rulers/routing.rb里实现如下。

控制器只是保存了我们给出它的环境。我们将在后面使用它。

现在在lib/rulers/routing.rb里:

# rulers/lib/rulers/routing.rb
module Rulers
  class Application
    def get_controller_and_action(env)
      _, cont, action, after =
        env['PATH_INFO'].split('/', 4)
      cont = cont.capitalize # 'People'
      cont += 'Controller'   # 'PeopleController'
      [Object.const_get(cont), action]
    end
  end
end

这是很简单的路由,因为我们只是让控制器和动作尽可能的简单。我们把URL从“/”分开。“4” 的意思是“分开不要超过4次”。所以split从第一个斜线前分开,给“_”分配了空的字符串,然后是Controller,然后是action,剩下的一起分给最后一个变量after。现在我们把第二个“/”后面的全扔了 - 但是它仍然在环境里,所以它不是真正的被抛弃了。

“const_get”方法是Ruby魔法的一员 - 它的意思是查找任何大写字母开头的名称(译者注:在Ruby中,首字母大写意味着是常量)- 这里,就是指你的控制器类。

另外,有时你会看见下划线,常常表示“一个我不关心的值”, 就像上面我所做的那样。它 实际上是个普通的变量,如果你喜欢,你可以使用它,但是许多Rubyist(Ruby程序员的爱 称)喜欢用它表示“一些我正忽视或不想要的东西”。

现在,你要在best_quotes里创建一个控制器。在app/controllers的目录下,创建一个名 为quotes_controller.rb的文件:

# best_quotes/app/controllers/quotes_controler.rb
class QuotesController < Rullers::Controller
  def a_quote
    "There is nothing eigher good or bad" +
      "but thinking makes it so."
  end
end

虽然看起来像一个Rails App 的控制器,但是我们需要手 动把它添加到App里,因为你还没有为Controller实现Rails那样魔法般的自动加载文件功能。打开best_quotes/config/application.rb。你要在require 'rulers'后面添加下面一行:

# best_quotes/config/application.rb (片段)
$LOAD_PATH << File.join(File.dirname(__FILE__),
                        '..', 'app',
                        'controllers')
require 'quotes_controller'

LOAD_PATH让你从“app/controllers”加载文件,只通过查询他们的名称就可以了,就像Rails一样。

然后你需要新控制器。

现在,进入rulers的目录,输入“git add .;gem build rulers.gemspec; gem install rulers-0.0.2.gem”。然后在best_quotes目录下,输入“rackup -p 3001”。最后,打开你的 浏览器,在地址栏输入“http://localhost:3001/quotes/a_quote”查看效果。

假如你之前没出错,你应该看到哈姆莱特的名言。你也正看见你的第一个控制器的第一个动 作。

假如你没有看到,请确定你在URL地址里包含了“quotes/a_quote”,就像你上面看见的一样。

几乎搞定了!

现在,看看你运行rackup的控制台。看看屏幕。看见那个错误了吗?在有些浏览器上可能看 不到,但是你也可能有像这样一个错误:

NameError: wrong constant name
   Favicon.icoController
   .../gems/rulers-0.0.3/lib/rulers/routing.rb:9:in
   `const_get'
    .../gems/rulers-0.0.3/lib/rulers/routing.rb:9:in
   `get_controller_and_action'
   .../gems/rulers-0.0.3/lib/rulers.rb:7:in `call'
   .../gems/rack-1.4.1/lib/rack/lint.rb:48:in
`   _call'
   (...more lines...)
   127.0.0.1 - - [21/Feb/2012 19:46:51] "GET /
   favicon.ico HTTP/1.1" 500 42221 0.0092

这是浏览器请求一个文件失败的错误信息...嗯...看看最后一行...哦, favicon.ico。

大部分的浏览器会自动请求这个文件。最终我们会让我们的框架或者网页服 务器负责服务像这样的静态文件。但是现在,我们将只是很糟糕的实现一下。

打开rulers/lib/rulers.rb,看看Rulers::Application#call

我们检查一下PATH_INFO是不是/favicon.ico,如果是的话就返回404:

# rulers/lib/rulers.rb
module Rulers
  class Application
    def call(env)
      if env['PATH_INFO'] == '/favicon.ico'
        return [404,
          { 'Content-Type' => 'text/html' }, []]
      end
      klass, act = get_controller_and_action(env)
      controller = klass.new(env)
      text = controller.senc(act)
      [200, {'Content-Type' => 'text/html'},
        [text]]
    end
  end
end

非常糟糕的hack?肯定了。不过最后我们会解决它的。现在,我们就能看见真正的错误,没 有了这种因为浏览器特性引起的错误了。

复习

你刚刚建立了一个功能很基础的路由,还有你可以路由到Controller的action。假如添加更多的控制器动作,你就可以得到更多路由。rulers 0.0.2只是刚刚创建的一个超级简单的gem。后面我们将添加更多特性。

你已经学习了一点关于Rack的知识 -- 在这章的“Rails”一节还会看到一些。你已经通过LOAD_PATH和const_get见识了Rails的一点魔法,我们在后面也还会看到它们。

练习

练习1. 调试Rack环境

打开app/controllers/quotes_controller.rb,修改成下面这样:

# best_quotes/app/controllers/quotes_controller.rb
class QuotesController < Rulers::Controller
  def a_quote
    "There is nothing either good or bad" +
      "but thinking makes it so."         +
      "\n<pre>\n#{env}\n</pre>"
  end
end

现在,重启服务器 -- 假如你只是修改了应用程序(best_quotes),你不需要重建gem. 刷新一下浏览器,你应该看见一个大的hash表,充满了好多有趣的信息。它只有一行,所以为了 更清晰,我为你重新把它格式化了:

{"GATEWAY_INTERFACE"=>"CGI/1.1", "PATH_INFO"=>"/
    quotes/a_quote", "QUERY_STRING"=>"",
    "REMOTE_ADDR"=>"127.0.0.1",
    "REMOTE_HOST"=>"localhost",
    "REQUEST_METHOD"=>"GET",
    "REQUEST_URI"=>"http://localhost:3001/quotes/a_quote",
    "SCRIPT_NAME"=>"",
    "SERVER_NAME"=>"localhost", "SERVER_PORT"=>"3001",
    "SERVER_PROTOCOL"=>"HTTP/1.1",
    "SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.9.3/2012-11-10)",
    "HTTP_HOST"=>"localhost:3001",
    "HTTP_CONNECTION"=>"keep-alive",
    "HTTP_CACHE_CONTROL"=>"max-age=0",
    "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
    "HTTP_ACCEPT"=>"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "HTTP_ACCEPT_ENCODING"=>"gzip,deflate,sdch",
    "HTTP_ACCEPT_LANGUAGE"=>"en-US,en;q=0.8",
    "HTTP_ACCEPT_CHARSET"=>"ISO-8859-1,utf-8;q=0.7,*;q=0.3",
    "rack.version"=>[1, 1],
    "rack.input"=>#>,
    "rack.errors"=>#>>,
    "rack.multithread"=>true,
    "rack.multiprocess"=>false,
    "rack.run_once"=>false,
    "rack.url_scheme"=>"http",
    "HTTP_VERSION"=>"HTTP/1.1",
    "REQUEST_PATH"=>"/quotes/a_quote"}

看起来内容很多,对不对?你的程序从Rack返回的所有信息都有了。何时你的Rails控制器支持像“post”这样的动作?,通过查看Rack的环境变量揭开它的面纱。

假如你的控制器检查是否env['REQUEST_METHOD'] == 'POST',你可以很容易的实现你自己的“post”方法?

现在好了,你现在能看见Rails必须用到的每个内容。Rails需要知道的的进来的请求中的每 一项都是从这个hash中抽取出来的。

练习2. 调试异常

让我们给我们的控制器中添加一个新方法,让它抛出异常:

# best_quotes/app/controllers/quotes_controller.rb
class QuotesController < Rulers::Controller
  def exception
    raise 'It's a bad one!'
  end
end

重新运行rackup,然后在浏览器里访问“http://localhost:3001/quotes/exception”,它应该抛出一个一次。

你可能看到美化了的页面,在quotes/exception有一个RuntimeError和 一大的堆栈跟踪信息。

在第八章,我们将深入Rack中间件,你看到那些的内容的原因。它不是内建在你的浏览器的, 你可以通过设置RACK_ENVproduction(生产环境)来关闭它。一般来说,

它是开发环境才有的调试工具,你正受益于此。

然而,用它不是必须的。你可以在你的Rulers应用程序里添加begin/rescue/end块来处理异常。

打开rulers/lib/rulers.rb,然后在你的call方法里,把controller.send()函数添加进begin/rescue/end块里。现在你必须决定怎么处理抛出的异常 -- 你可以从一个简单的错误页面开始,或者自定义500页面,或者其他你喜欢的方式。

你的框架还可以对错误作出哪些处理呢?

练习3. 根路径和路由

你不能通过直接访问“http://localhost:3001”看程序工作正不正常, 这点很不方便。只是得到异常不能告诉你是否程序出错。

回到rulers/lib/rulers.rb。在检查favicon.ico的代码下面,你可以再添加一个判断,看看PATH_INFO是不是“/”。

首先,试着硬编码,让它总是重定向到“/quotes/a_quote” 然后再你的浏览器里测试。然后,试试下面的这个方法:

  • 返回已知文件的内容 -- 比如public/index.html?
  • 查找HomeController和index动作
  • 额外得分:试试浏览器重定向。这个请求返回200或404以外的代码,然后设置header。

在第9章,我们将建立更多配置的路由,让Rulers更像真正的Rails。到那时,在你的框架里用Ruby hack你的路由就非常容易了。

在Rails里

在Rails里ActionPack包含控制器。Rails也有ApplicationController,它继承了控制器基类,然后每个控制器继承它。

你的框架也可以!

不同的Rails版本的路由实现有实质上的不同。你可以通过以下网址了解一下现在的 “http://guides.rubyonrails.org/routing.html”,“Rails路由从外到内”。Rulers现在的路由和旧版本Rails1.2的路由(也是Rails2.3默认的方式)相似。它会自动查找控制器和动作,不需要指定单个的路由。

虽然不太安全,但是对于开始来说并不太差。Rails 3默认的路由通过生成许多单个的匹配的路由来实现,所以和这个不是很一样。

Rails把Rack信息打包到“request”对象,而不是只把hash包含到相应的控制器。当你想要抽象一点的时候,这个是个好主意--为某个特殊的变量一般化值,例如,或者读和设置cookie来储存session数据。

Rails做了许多工作,在第6章,你会看见Rulers怎么实现它。Rails在底层也使用Rack,所以用到的基础信息和你是一样的。

Rack是简单的、CGI一样的接口,比你想象的功能要少。

假如你好奇,看看“http://rack.rubyforge.org/doc/SPEC.html”,包含了所有细节。

虽然有点晦涩,但是它可以告诉你你想要了解的的所有知识。

随着后面的学习,你将会了解更多关于Rack的知识。但是对于没有耐心的读者,Rails包含了 一篇关于怎样使用Rack的指南“http://guides.rubyonrails.org/rails_on_rack.html”。

它里面满满都是你可以应用在自己的框架里的内容。有些在本书的后面内容里也会添加。

相关文章
|
5月前
|
设计模式 前端开发 数据库
深入理解MVC设计模式:构建高效Web应用程序的基石
【7月更文挑战第4天】在软件工程领域,设计模式是解决常见问题的一系列经过验证的方法。其中,Model-View-Controller(MVC)设计模式自诞生以来,便成为了构建用户界面,特别是Web应用程序的黄金标准。MVC通过将应用程序逻辑分离为三个核心组件,提高了代码的可维护性、可扩展性和重用性。本文将深入探讨MVC设计模式的原理,并通过一个简单的代码示例展示其应用。
232 0
|
7月前
|
前端开发 Java 测试技术
Java一分钟之Spring MVC:构建Web应用
【5月更文挑战第15天】Spring MVC是Spring框架的Web应用模块,基于MVC模式实现业务、数据和UI解耦。常见问题包括:配置DispatcherServlet、Controller映射错误、视图解析未设置、Model数据传递遗漏、异常处理未配置、依赖注入缺失和忽视单元测试。解决这些问题可提升代码质量和应用性能。注意配置`web.xml`、`@RequestMapping`、`ViewResolver`、`Model`、`@ExceptionHandler`、`@Autowired`,并编写测试用例。
343 3
|
2月前
|
前端开发 Java
【案例+源码】详解MVC框架模式及其应用
【案例+源码】详解MVC框架模式及其应用
163 0
|
3月前
|
前端开发 安全 Java
技术进阶:使用Spring MVC构建适应未来的响应式Web应用
【9月更文挑战第2天】随着移动设备的普及,响应式设计至关重要。Spring MVC作为强大的Java Web框架,助力开发者创建适应多屏的应用。本文推荐使用Thymeleaf整合视图,通过简洁的HTML代码提高前端灵活性;采用`@ResponseBody`与`Callable`实现异步处理,优化应用响应速度;运用`@ControllerAdvice`统一异常管理,保持代码整洁;借助Jackson简化JSON处理;利用Spring Security增强安全性;并强调测试的重要性。遵循这些实践,将大幅提升开发效率和应用质量。
74 7
|
4月前
|
设计模式 存储 前端开发
MVC 框架的主要问题是什么?
【8月更文挑战第30天】
99 0
|
6月前
|
安全 前端开发 测试技术
安全开发-PHP应用&模版引用&Smarty渲染&MVC模型&数据联动&RCE安全&TP框架&路由访问&对象操作&内置过滤绕过&核心漏洞
安全开发-PHP应用&模版引用&Smarty渲染&MVC模型&数据联动&RCE安全&TP框架&路由访问&对象操作&内置过滤绕过&核心漏洞
|
7月前
|
前端开发 JavaScript 开发者
深入理解MVC和MVVM:构建现代Web应用的利器
深入理解MVC和MVVM:构建现代Web应用的利器
|
7月前
|
前端开发 Java Spring
Java Web ——MVC基础框架讲解及代码演示(下)
Java Web ——MVC基础框架讲解及代码演示
76 1
|
4月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
60 0
|
7月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
214 0