`

REST On Rails

阅读更多

过去的 20 年间,一个趋势主导了商业软件工具的开发:用复杂性对抗复杂性。这一趋势在任何地方都没有比在分布式计算领域更明显。C 和 Java™ 社区已经看到一些惊人复杂的框架被构建出来支持分布式通信。分布式计算环境(DCE)支持用 C 语言编写的应用程序之间的远程过程调用。公共对象请求代理架构(CORBA)标准支持面向对象应用程序之间的通信。企业 JavaBean(EJB)规范提供安全性、持久性、事务、消息和远程的服务。对各个框架的宣传甚嚣尘上,但是这些框架都没有满足预期,有些甚至因为它们 的复杂性而成为灾难。在这些框架中,只有 EJB 3.0 属于大力简化的结果,有潜力在分布式应用程序上成功。市场可能给、也可能不给这个面临强敌的框架另一个空间,但 EJB 仍然需要交付使用。

最新的大型分布式框架是 Web 服务。Web 服务技术让应用程序可以用平台独立或编程语言独立的方式相互通信(请参阅 参考资料 )。Web 服务标准也受到复杂性恶魔的威胁,但是称作 REST 的替代策略承诺了更简单的方式。本文介绍了如何在 Ruby on Rails 中添加 REST 风格的 Web 服务,并从 Ruby 和 Java 代码调用服务。

关于本系列

跨越边界 系列中,作者 Bruce Tate 推动了这样一个概念:今天的 Java 程序员通过学习其他方法和语言,可以受益。编程阵营中 Java 技术是所有开发项目最佳选择的情况已经变了。其他框架正在改变 Java 框架构建的方式,从其他语言学到的概念有助于 Java 编程。编写的 Python(或 Ruby、Smalltalk 或 ……)代码可以改变 Java 编码的方式。

本系列介绍了与 Java 开发有根本不同,但是却直接适用的编程概念和技术。在某些情况下,需要集成这些技术以利用它。在其他情况下,将可以直接应用这些概念。单独的工具不如其他语言和框架中影响 Java 社区的开发人员、框架甚至基本方法的思想重要。

Web 服务领域

就像 EJB、CORBA 和 DCE 一样,Web 服务的核心抽象也是远程过程调用。Web 服务利用叫做 SOAP(最初,SOAP 代表简单对象存取协议,但是这个术语现在降级了)的协议,用 XML 表示消息的结构。这里有一个技巧:如果协议用代表简单的 S 开始,那它就不简单。Web 服务定义语言(WSDL)提供了服务的标准规范。像 SOAP 一样,WSDL 也是一个棘手而复杂的 API,而 SOAP 和 WSDL 仅仅涉及到了构成 Web 服务这个大怪物的众多 API 的表面(请参阅 参考资料 )。Web 服务需要一次大修,感谢 Roy Fielding 的一份有影响的博士论文,Web 服务得到了大修(请参阅 参考资料 )。

Fielding 的论文描述了 REST 应用程序联网策略。REST 与全堆栈 Web 服务根本不同,主要原因有三个:

  • REST 的核心抽象是远程资源而不是远程过程调用。
  • REST 没有发明一个详尽的标准列表,而是采用现有的 Internet 标准,包括 HTTP、XML 和 TCP/IP。
  • REST 没有覆盖每个可能场景,而是覆盖了最常见的问题。

请把 REST 想像成浏览。REST 客户使用与浏览器相同的 HTTP 命令访问资源。当 REST 客户访问到资源的表示时,客户转换到一个状态。使用不同的 HTTP 命令,REST 客户可以创建、读取、更新或删除资源的记录。

例如,以典型的博客为例。通过输入 URL,例如 blog.rapidred.com,得到贴子的列表。然后,如果想编辑博客条目,可以在 URL 中输入 HTTP 参数(例如 blog.rapidred.com/edit?article=12345),然后显示编辑表单。由于每个博客条目都有自己的 URL,所以点击链接或直接输入 URL,就可以用 HTTP 命令读取、修改或删除内容。

简而言之,REST 可以:

  • 用 TCP/IP 命名标准命名 Web 上的资源
  • 用 HTTP 查询和操纵这些资源
  • 使用基于文本的标准消息格式(例如 XML 或 HTML)来构造数据

Ruby on Rails 用 REST 对 Web 服务提供了优秀的支持。

 




回页首


Action Web Services 概述

Rails 用叫做 Action Web Services 的模块实现 Web 服务。许多开发框架鼓励视图和 Web 服务使用独立的控制器。这个策略可以维护控制器之间的风格一致。问题是针对所服务的每种内容,都需要一个新控制器。例如,Ajax 用户界面要求从控制器取得到 JavaScript 的远程 XML 调用。

不必为 Web 服务专门分配一个控制器,使用 Rails,可以通用地用同一个控制器向基于 HTML 的视图、基于 XML 的 Web 服务和基于 XML 的 JavaScript 组件提供内容。理解 Action Web Services 的最好方式就是在工作应用程序的环境下查看它的实际作用。

请用自己选择的数据库管理器创建一个叫做 service_development 的数据库。接下来,用以下命令创建 Rails 项目和模型:

> rails service 
> script/generate model Person

 

在生成模型之后,就有了一个叫做 db/migrate/001_create_people.rb 的迁移。请把这个迁移编辑成像清单 1 一样:


清单 1. people 表的迁移

class CreatePeople < ActiveRecord::Migration
  def self.up
    create_table :people do |t|
      t.column :first_name, :string, :limit => 40
      t.column :last_name, :string, :limit => 40
      t.column :email, :string, :limit => 40
      t.column :phone, :string, :limit => 15
    end
  end

  def self.down
    drop_table :people
  end
end

 

把 config/database.yml 中的数据库配置修改成与自己的数据库配置匹配,并输入 rake migrate 。最后,输入 script/generate scaffold Person People ,为 Person 模型和 People 控制器生成工作台。现在可以用 script/server 启动服务器了。请把浏览器指向 localhost:3000/people,以看到针对 Person 的经典的 Rails 脚手架。图 1 显示了带有标准 Rails 脚手架的应用程序:


图 1. 简单的 Rails 应用程序
映射框架

在我介绍 Rails 的 Web 服务之前,请查看控制器代码。编辑 app/controllers/people_controller.rb,使之与清单 2 的代码匹配:


清单 2. PeopleController 的控制器代码

class PeopleController < ApplicationController
  def index
    list
    render :action => 'list'
  end

  # GETs should be safe (see  
http://www.w3.org/2001/tag/doc/whenToUseGet.html)
  verify :method => :post, :only => [ :destroy, :create, :update 
],
         :redirect_to => { :action => :list }

  def list
    @person_pages, @people = paginate :people, :per_page => 10
  end

  def show
    @person = Person.find(params[:id])
  end

  def new
    @person = Person.new
  end

  def create
    @person = Person.new(params[:person])
    if @person.save
      flash[:notice] = 'Person was successfully created.'
      redirect_to :action => 'list'
    else
      render :action => 'new'
    end
  end

  def edit
    @person = Person.find(params[:id])
  end

  def update
    @person = Person.find(params[:id])
    if @person.update_attributes(params[:person])
      flash[:notice] = 'Person was successfully updated.'
      redirect_to :action => 'show', :id => @person
    else
      render :action => 'edit'
    end
  end

  def destroy
    Person.find(params[:id])。destroy
    redirect_to :action => 'list'
  end
end

 

如果跟着做过这个系列以前的 Ruby on Rails 项目,就会知道典型的控制器方法的一般流程是:

  1. 用户通过跟随链接或指定 URL,通过 HTTP 发送请求。
  2. Web 服务器根据域的配置把请求转给 Ruby on Rails。
  3. Rails 路由器根据 URL 模式把请求路由给控制器。默认模式是 http://主机名 /控制器 /动作 /参数
  4. 路由器用与动作相同的参数调用控制器上的方法。
  5. 动作参数为视图设置实例变量,并呈现视图。
  6. 动作方法把实例变量拷贝到视图。

例如,请看 清单 2 中的 show 方法。控制器设置视图使用的 @person 实例变量。因为方法没有指定视图的名称,所以 Rails 用与控制器动作相同的名称调用视图 —— 在这个示例中,视图位于 app/views/people/show.rhtml。

再来看 list 方法。如果想让这个方法呈现 XML,需要:

  • 删除分页
  • people 实例变量转换成 XML
  • 呈现 XML 而不是 HTML

Rails 使得处理 Web 服务和呈现来自同一 Web 服务的视图成为可能。实际上也不需要分页。为了把 Web 服务的 list 方法简化一些,可以把控制器中的 list 方法变成像清单 3 一样,清除分页。还需要删除靠近 app/views/people/list.rhtml 代码底部的 “Next Page” 和 “Previous Page” 链接。


清单 3. 简化 list

def list
   @people = Person.find_all
end

 

由于删除了分页,也就删除了让用户界面更健壮的一个特性,但是又得到了一些回报。可以用相同的代码来驱动 Web 服务和视图。如果日后发现需要分页,可以编写一些定制的助手。

现在基本应用程序出来了,可以添加一些 Web 服务了。

 




回页首


向 Rails 控制器添加 Web 服务

如果我想说大话,我可以说 “现在已经有了一个 Web 服务”。记得我对 REST 说过什么?这种风格的 Web 服务使用指定的资源。我的 Rails 应用程序也具有指定的资源:host_name/people/list 调用我的 list 服务。REST 风格的 Web 服务也使用 TCP/IP 和 HTTP。我的 Rails 应用程序就是这么做的。而且格式良好的 HTML 就是 XML 的子集,也满足最后一条 REST 要求。只需在 localhost:3000/people/list 上调用 HTTP get ,并解析结果,就可以得到人员列表。这就是关键。REST 的工作方式与 Internet 的工作方式一样。但这并不是真正基于 REST 的 Web 服务。理想情况下,应当提供反映 Person 含义的 XML 文档而不是用户界面的结构。

真正的服务应当产生纯数据的表示,一个专门针对服务的预期客户而构建的表示。但是示例应用程序有两个客户:终端用户和 REST 客户。要为两个目的重用相同的代码,需要给 Rails 提供更多信息。Rails 的设计者可能决定使用额外的 URL 参数,但是处理 URL 可是一项费劲的工作。Rails 不应当用这些细节增加用户负担。相反,HTTP 提供了指定更多信息的工具:HTTP 头。

要理解 Web 服务的 REST 模型,了解一点 HTTP 是有帮助的。curl (请把它想像成 查看 URL )命令允许用一个命令查询 URL,并查看响应。基于 Unix 的操作系统默认包含 curl ,可以为其他操作系统下载免费的 curl 工具。通过输入 curl http://some-url ,可以将请求限制成只输出默认的响应体(浏览器呈现的 HTML)。输入 curl -i http://some-url 可以得到更多信息。这个命令返回 HTTP 头,如清单 4 所示。可以看到头配置由表示每个请求的配置的键-值对组成。


清单 4. 用 curl 调用 HTTP 请求

> curl -i http://localhost:3000/people/list
HTTP/1.1 200 OK 
Cache-Control: no-cache
Connection: Keep-Alive
Date: Tue, 27 Jun 2006 14:54:49 GMT
Content-Type: text/html; charset=UTF-8
Server: WEBrick/1.3.1 (Ruby/1.8.4/2005-12-24)
Content-Length: 854
Set-Cookie: _session_id=216912045de52786f032b22755c903dd; path=/

 

后面将频繁地看到 HTTP getputpostdelete 命令。REST 利用达些命令执行经典的 CRUD(CRUD 是create, read、update 和 delete 的共同缩写)。HTTP 命令到 CRUD 的映射是这样的:

  • Create(创建):HTTP put
  • Read(读取):HTTP get
  • Update(更新):HTTP post
  • Delete(删除):HTTP delete

浏览器利用 HTTP 头,通过相同的服务器端代码来满足不同类型的请求。行为良好的应用程序提供正确处理文档的充足信息。其中一条信息叫做 HTTP Accept 头。只要多花一点力气,控制器就能利用一些助手,用 Accept 头决定如何响应进入的请求。然后,控制器可以呈现适当的响应。请把 PeopleController 中的 list 方法改成像清单 5 一样:


清单 5. 扩展 list方法以呈现 XML

def list
 # wants is determined by the http Accept header in the request
 @people = Person.find_all
 respond_to do |wants|
   wants.html
   wants.xml { render :xml => @people.to_xml }
 end    
end

 

在清单 5 中,可以看到完整的基于 REST 的 Web 服务。生成的代码是 Rails 中小型的特定于域的语句的优美示例,它扩展 Ruby 以构造一种 switch 语句。它的工作方式是这样的:

  1. respond_to 方法接受单个代码块,并传递一个实例变量(标为 wants )到代码块。
  2. wants 对每个可能的类型都有一个方法。控制器可以为控制器期望的每个类型指定一个代码块。
  3. 如果方法名称与 HTTP Accept 头中的类型匹配,wants 方法执行对应的代码块。
  4. 如果没有指定代码块(例如 wants.html ),Rails 就执行默认动作(在这个示例中,呈现 app/views/people/list.rhtml)。

这个策略允许在所有预期的客户之间共享相同的设置代码。如果需要添加期望 HTML 的 JavaScript 客户,以便让应用程序支持 Ajax,只需要添加 wants.js,如清单 6 所示:


清单 6. 为 JavaScript 客户呈现 HTML

  
def list
   # wants is determined by the http Accept header in the request
   @people = Person.find_all
   respond_to do |wants|
      wants.html
      wants.js
      wants.xml { render :xml => @people.to_xml }
   end    
end

 

现在已经看到了如何向只读的方法中添加 REST Web 服务。show 方法也类似,如清单 7 所示:


清单 7. 实现 show

def show
   @person = Person.find(params[:id])
   respond_to do |wants|
       wants.html
       wants.xml { render :xml => @person.to_xml }
   end    
end

 

您可能已经注意到,通过 REST 看到的只有只读服务。原因是:让应用程序处理提交和删除所需要的工作比较少。删除不需要额外的支持,因为当前的代码已经用 URL 指定了要删除的人的 ID。Rails 自动转换 post 请求中进入的 XML,所以不需要构建任何服务器端支持。实际上,应用程序不用变就能删除、更新和创建。可以修补每个方法呈现的 HTTP 响应,但是客户代码实际就在 HTTP 返回码之后。

现在是调用 Web 服务的时候了。

 




回页首


调用 Web 服务

使用现有 HTTP 协议这一策略使得调用变得简单。清单 8 显示了 Ruby 版本。请注意 HTTP Accept 头。记住,控制器根据这个头决定内容的类型。


清单 8. 从 Ruby 调用服务

require 'net/http'

Net::HTTP.start('localhost', 3000) do |http|
  response = http.get('/people/list', 'Accept' => 'text/xml')

  #Do something with the response.

  puts "Code: #{response.code}" 
  puts "Message: #{response.message}"
  puts "Body:\n #{response.body}"
end

 

清单 8 中的 Web 服务调用,在 http://localhost:3000/people/list 上调用 HTTP get 方法,并输出响应。Ruby 有很好的库可以处理生成的 XML,但是它们超出了本文的范围。不需要用 Ruby 调用这个服务。只需要 HTTP 的库。清单 9 显示这个服务的 Java 调用:


清单 9. 用 Java 代码调用服务

package com.rapidred.ws;

import java.net.*;
import java.io.*;

public class SimpleGet {

    void get() {

        try {
            URL url = new URL("http://localhost:3000/people/list");
            URLConnection urlConnection = url.openConnection();
            urlConnection.setRequestProperty("accept", "text/xml");
            BufferedReader in = 
              new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
            String str;

            while ((str = in.readLine()) != null) {
                System.out.println(str);
            }

            in.close();
        }
        catch (Exception e) {
            System.out.println(e);
        }
    }

 

像其 Ruby 等价物一样,这个代码打开一个 URL 连接,把 Accept 头设置成 text/xml ,发出 get ,并输出结果。Java 代码有许多 XML 框架(请参阅 参考资料 ),但是我在这个示例中硬编码了 XML,以保持示例简单。

post 的调用也相似。清单 10 显示了简单的 post


清单 10. 用 Java 代码调用 HTTP post

void post() {
try {
    String xmlText  = "<person> " +
            "<first-name>Maggie</first-name>" +
            "<last-name>Maggie</last-name>" +
            "<email>maggie@tate.com</email>" +
        "</person>";

    URL url = new URL("http://localhost:3000/people/create");
    HttpURLConnection conn = (HttpURLConnection)url.openConnection();
    conn.setDoOutput(true);
    conn.setRequestMethod("POST");
    conn.setRequestProperty("Content-Type", "text/xml");
    OutputStreamWriter wr = new 
OutputStreamWriter(conn.getOutputStream());
    wr.write(xmlText);
    wr.flush();

    BufferedReader rd = new BufferedReader(new 
InputStreamReader(conn.getInputStream()));
    String line;
    while ((line = rd.readLine()) != null) {
        System.out.println(line);
    }
    wr.close();
    rd.close();
    } catch (Exception e) {
        System.out.println("Error" + e);
    }
}

 

这个 HTTP post 通过在 http://localhost:3000/people/create 上调用 post ,并在 HTTP 文档体中传递一个 XML 文档,创建了一个新 Person 。(通常应当用 Java XML 库构建 XML 文档。这次我还是硬编码了 XML 文档,以保持示例简单。)Rails 支持自动把进入的 XML 转换成 Person 属性的 Ruby 散列表。

 




回页首


结束语

在本文中,已经看到只用少量代码,就使控制器支持基于 REST 的 Web 服务。动态类型化的 Internet 语句,例如 Ruby,大量地利用 REST 代替基于 SOAP 的 Web 服务。一些简单的调用,包括漂亮的 responds_to 语法和对进入提交的自动 XML 转换,使得可以容易地利用同一控制器处理 Web 服务、远程 JavaScript 请求或 HTML。

Java 语言对 REST 也有非常好的支持。毕竟,servlet 实际上是服务器端基于 REST 的 Web 服务。可以在 Java 端使用 servlet,在 Ruby 端使用 Rails 控制器,把利用两个平台优势的应用程序组合在一起。这就是 Web 服务的漂亮之处。您真正需要的所有东西就是超群出众的勇气。

分享到:
评论

相关推荐

    跨越边界:REST on Rails

    Ruby on Rails是一个突然流行起来的框架,充当着Ruby编程语言的催化剂。随着Ruby的经验不断成功,开发人员开始寻求把他们的...本文介绍了如何在Ruby on Rails中添加REST风格的Web服务,并从Ruby和Java代码调用服务。

    Rails上的API:使用Rails构建REST APIAPIs on Rails: Building REST APIs with Rails

    使用Rails构建可伸缩和可维护API的最佳方法

    mage-rest-on-rails:Magento 的 REST API on Rails 4 的简单示例

    Rails 4 上的 Magento REST API Magento 在 Rails 4 上的 REST API 的一个简单示例,其中还包括基准测试 + oauth 注释。安装克隆这个 repo 运行bundle install 运行rails server配置您需要修改的唯一文件是settings/...

    Rails之道,完整扫描版

    《Rails之道》按照Rails的各个子系统进行组织编排,分别介绍了Rails的环境、初始过程、配置和日志记录,Rails的分配器、控制器、页面生成和路由,REST、资源和Rails,ActiveRecord的基础、关联、验证和高级技巧,...

    The Rails 5 Way-Leanpub(2017).pdf

    Since the API documentation is liberally licensed (just like the rest of Rails), there are some sections of the book that draw from the API documentation. But in practically all of those cases, the ...

    jax-on-rails:Ruby on Rails实时博客+入门套件

    jax-on-rails Ruby on Rails实时博客+入门套件 入门 克隆存储库 $ git clone https://github.com/jacksonmccluskey/jax-on-rails.git 变更目录 $ cd ruby-live-blog 安装依赖项 $ bundle install OR Great tutorial...

    openbrewerydb-rails-api:使用Ruby on Rails构建的官方v1 Open Brewery DB REST API

    Open Brewery DB API服务器是一个Ruby on Rails应用程序,它连接到提供的PostgreSQL DB服务器。 :package: 依存关系 Ruby 2.6.5 PostgreSQL 9.4 弹性搜索(请参阅部分。)注:将来可能会删除弹性搜索。 :rocket...

    Rails 指南

    本文介绍如何开始使用Ruby on Rails,读完本文后,您将学到: 如何安装Rails,创建Rails应用,如何连接数据库; Rails应用的基本文件结构; MVC(模型,视图,控制器)和REST架构的基本原理; 如何快速生成Rails应用...

    jsgrid-rails:带有Ruby on Rails REST服务的jsgrid的示例项目

    jsgrid-rails 带有Ruby on Rails REST服务的jsgrid的示例项目

    Struts2-rest插件(有注释)

    从 Struts 2.1 开始,Struts 2 改为使用 Convention 插件来支持零配置。Convention 插件彻底地抛弃了配置...Struts 2.1 通过 REST 插件完全可以提供让人和机器客户端共同使用的资源,并支持 Ruby On Rails 风格的 URL。

    Laravel-REST:Ruby on Rails 的 RESTful 架构的示例实现,用 PHP 构建

    Laravel-REST 在 Laravel 中构建的 Ruby on Rails RESTful 架构的演示实现。 这个演示应用程序是完整的文档和一个 PHPUnit 测试套件(还没有)。安装4个简单步骤: 检查 Laravel; 克隆回购: $ git clone git://...

    rails 入门文档

    本文介绍如何开始使用 Ruby on Rails。 读完本文,你将学到: 如何安装 Rails,新建 Rails 程序,如何连接数据库; Rails 程序的基本文件结构; MVC(模型,视图,控制器)和 REST 架构的基本原理; 如何快速生成 ...

    rails-ranger::cowboy_hat_face:坚定的Ruby on Rails API的AJAX客户端

    护林员探索Ruby on Rails API的路由和路径| Rails Ranger是之上的薄薄一层,它为您提供了一个自以为是的界面,以查询使用Ruby on Rails构建的API。主要特点遵循Ruby on Rails路由约定的URL构建在前端和API之间交换...

    RoR_API_TDD:Udemy的REST API与Ruby on Rails课程

    带有TDD的Rails API Udemy的课程带有Ruby on Rails的REST API 您可能要讲的内容: Ruby版本:2.7.0 Rails版本:5.2.4.4 组态 数据库创建 数据库初始化 测试套件:Rspec 服务(作业队列,缓存服务器,搜索引擎等...

    alpha-blog:带有Ruby on Rails框架的音乐博客模板

    带有Ruby on Rails框架的教育博客。 Ruby 2.7版 Ruby on Rails 6.0.3 数据库Sqlite3(一对多和多对多关联) 代表性状态转移(REST) Bootstrap 4.4.1的前端样式 建议在Heroku平台上进行部署 预览 该博客包括...

    Gymify-Backend:这是一个使用Ruby on Rails框架构建的REST API。 该API实现基于令牌的身份验证和授权

    GYMIFY API 使用Ruby on Rails构建的RESTFUL API。 该应用程序公开了健身房前端应用程序的API端点。 该应用程序允许用户注册,登录,查看培训师并与培训师预约约会。终点此api公开了两个端点,可以使用...

    apidoco:Ruby on Rails API文档工具

    Apidoco-Ruby on Rails API文档工具 REST API的简单文档-。 屏幕截图 安装 将此行添加到您的应用程序的Gemfile中: gem 'apidoco' 然后执行: $ bundle 将此行添加到您的路线: mount Apidoco :: Engine , at :...

    Enterprise Rails

    * Tour an ideal enterprise systems layout: how Rails fits in, and which elements don't rely on Rails * Learn to structure a Rails 2.0 application for complex websites * Discover how plugins can ...

    apprun-backend:APPRUN-API REST在Ruby on Rails中分离

    AARCA后端-API REST API Rest desenvolvida ,utilizando作为seguintes Gem的: 特征 Autenticação(JWT) CRUD-乌苏里奥斯 CRUD-Modalidades CRUD-Patrocinadores CRUD-走廊 CRUD-参与 CRUD-签名 CRUD-Posiç...

    ruby-on-rails-rest-api-demo:一个简化的rest api演示,其中ruby on rails。

    自述文件该自述文件通常会记录启动和运行应用程序所需的所有步骤。 您可能要讲的内容: Ruby版本系统依赖配置数据库创建数据库初始化如何运行测试套件服务(作业队列,缓存服务器,搜索引擎等) 部署说明...

Global site tag (gtag.js) - Google Analytics