REST这个名词已经听过许久,在javaeye的ruby版上也看到不少的讨论,一开始是搞不明白的,似乎跟webservice有关。今天读了《RESTfull Rails Development》和几篇介绍REST的文章开始有点明白。REST 是英文 Representational State Transfer 的缩写,有中文翻译为“具象状态传输”。读这篇文章《
学习REST》对于初次接触REST的人来说更好理解。
我们在 Web 应用中处理来自客户端的请求时,通常只考虑 GET 和 POST 这两种 HTTP 请求方法。实际上,HTTP 还有
HEAD、PUT、DELETE 等请求方法。而在 REST 架构中,用不同的 HTTP 请求方法来处理对资源的
CRUD(创建、读取、更新和删除)操作:
- POST: 创建
- GET: 读取
- PUT: 更新
- DELETE: 删除
经过这样的一番扩展,我们对一个资源的 CRUD 操作就可以通过同一个 URI 完成了。需要注意的是REST的核心就是资源(resources)这个概念。我们所说的webservice是一种建立在http协议上的远程调用,而REST就是把远程调用抽象成对远程资源的CRUD的操作,正好可以用HTTP的PUT GET POST DELETE来对应,而不是重新发明一个协议(比如soap,简单对象访问协议)。REST与AJAX的流行,甚至远至设计模式的兴起,都充分说明一个现象,在成熟的应用的基础上创新而非扩展出复杂所谓“创新性”架构在软件行业是更为可靠。
实战体验REST可以从IBM Developer的这篇文章开始《跨越边界:Rest On Rails》。这篇文章是在Rails1.2发布之前出来的,有些地方已经可以修改的更简练,我把我的练习过程记录下,并添加了C#调用REST风格web service的例子。
首先,你的机器上需要安装rails1.2,并且假设你对rails有基本的了解,建立一个应用叫service,命令行执行:
rails service
rails自动帮你生成应用的基本结构和基础代码,然后编辑config下面的database.yml设置数据库,并建立数据service_development,我用的是mysql数据库。
利用rails1.2新的scaffold命令:
ruby script/generate scaffold_resource person
这个命令将自动生成ActiveRecord,Controller以及View,在\app\models下可以发现自动生成的Model——person.rb。打开service\db\migrate下面的001_create_people.rb,编辑如下:
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
利用rake命令自动建表,执行
rake db:migrate
rails默认表明是Model的复数形式,也就是这里将自动建立一张名叫people的表。
OK,一切就绪,启动WEBric,访问http://localhost:3000/people,显示:
scaffold已经帮我们自动生成了一个对person资源的crud操作,增删改查似乎跟传统的rails没有什么不同嘛。如果你认真观察在操作过程中URL的变化情况就会发现在操作过程中URL的变化很小,而且与传统rails的URL路由相比,省去了action名称。出现的变化在/people、/people/1、/people/1;edit和/people/new这几个之中。在/people的URL中隐藏这可能是http的POST或者GET的方法,前者用于create操作,而GET用于show操作,具体你可以查看app/controllers/目录下的PeopleController类,每个action的前面都注释了它们将对应哪个HTTP方法。而/people/1中的1指的是资源的标志符,比如这里person的id,通过这个ID来进行资源的操作,也许是PUT方法(更新),也许是DELETE方法(删除)。rails实现PUT和Delete是通过隐藏字段来实现的,查看编辑页面生成的html源代码,你将发现一个_method的隐藏字段,值为PUT。而另外两个URL:/people/1;edit和/people/new,这两个并非严格意义上的RESTful URL,它们只是为了显示用,显示form表单用于新建和编辑。关于RESTful风格的URL的详细讨论请见《RESTfull Rails Development》文档。
如果rails只是这样的威力,那就有点小提大做了,看看PeopleController的show action,它对应于http的GET请求,返回people列表:
# GET /people/1
# GET /people/1.xml
def show
@person = Person.find(params[:id])
respond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @person.to_xml }
end
end
神奇的地方在respond_to方法中,根据请求文件类型(http Header的ContentType),显示html格式,或者xml格式(还有其他支持,比如json、RSS、Atom等等)。比如你添加了一个person,通过http://localhost:3000/people/1访问,可以看到这个人员的具体信息:
我们再通过http://localhost:3000/people/3.xml访问看到的却是一个xml文件:
不仅如此,我们也可以通过其他语言编写客户端来调用http://localhost:3000/people/1这个url,慢着,这不正是web service远程调用吗?没错,REST风格的web service相比于wsdl、soap定义的web service简单了太多太多,也更加实用。我们来编写一个java类调用http://localhost:3000/people获得所有的人员列表:
package example;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
public class RESTDemo {
/**
* @param args
*/
public static void main(String[] args) {
RESTDemo restDemo = new RESTDemo();
restDemo.get();
}
void get() {
try {
URL url = new URL("http://localhost:3000/people");
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);
}
}
}
我们没有什么服务端接口class,我们也不用生成什么stub,我们调用的最常见最常见的http协议,发送的是默认的GET请求,rails自动将该请求转发给show action。注意,我们这里把
accept设置为text/xml,show方法根据此格式返回一个xml文档,下面是输出:
<?xml version="1.0" encoding="UTF-8"?>
<people>
<person>
<email>killme2008@gmail.com</email>
<first-name>dennis</first-name>
<id type="integer">1</id>
<last-name>zane</last-name>
<phone>1355XXXXXXX</phone>
</person>
</people>
如果仅仅是GET请求是不够的,我们说过,把远程调用抽象成对远程资源的CRUD操作,那么如何create、delete和update远程资源呢?同样很简单,比如我们通过C#远程调用,创建一个新person,还记的我说过吗?/people可以是POST请求,他将调用PeopleController的create方法:
using System;
using System.Net;
using System.IO;
using System.Text;
namespace demo
{
class RESTDemo
{
static void Main(string[] args)
{
string xmlText = "<person> " + "<first-name>jordan</first-name>"
+ "<last-name>jordan</last-name>"
+ "<email>maggie@tate.com</email>"
+ "<phone>010-XXXXXXXX</phone>" + "</person>";
Uri address = new Uri("http://localhost:3000/people");
// 创建web请求
HttpWebRequest request = WebRequest.Create(address) as HttpWebRequest;
// 设置请求类型为POST,调用create action
request.Method = "POST";
request.ContentType = "application/xml";
byte[] xmlBytes = Encoding.ASCII.GetBytes(xmlText);
using (Stream reqStream = request.GetRequestStream())
{
reqStream.Write(xmlBytes, 0, xmlBytes.Length);
}
using (WebResponse wr = request.GetResponse())
{
wr.
//打印返回的http头
Console.WriteLine(wr.Headers.ToString());
}
}
}
}
执行此程序,刷新http://localhost:3000/people,可以看到新建了一个人员如下
好极了,GET和POST都有了,那么PUT对应的更新和DELETE对应的删除又该怎么做呢,唯一的区别就是设置请求类型不同而已,java调用如下:
void put() {
try {
String xmlText = "<person> " + "<first-name>test</first-name>"
+ "<last-name>test</last-name>"
+ "<email>maggie@tate.com</email>"
+ "<phone>010-XXXXXXXX</phone>" + "</person>";
URL url = new URL("http://localhost:3000/people/1");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
//设置请求为PUT
conn.setRequestMethod("PUT");
conn.setRequestProperty("Content-Type", "text/xml");
OutputStreamWriter wr = new OutputStreamWriter(conn
.getOutputStream());
wr.write(xmlText);
wr.flush();
wr.close();
} catch (Exception e) {
System.out.println("Error" + e);
}
}
void delete() {
try {
URL url = new URL("http://localhost:3000/people/2");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
//设置请求为DELETE
conn.setRequestMethod("DELETE");
conn.setRequestProperty("Content-Type", "text/xml");
if(conn.getResponseCode()==200)
System.out.println("删除成功!");
}catch (Exception e) {
System.out.println("Error" + e);
}
}
这里的put方法将第一个人员的名字改了,而delete方法干脆将刚才C#添加的人员删除掉。异构系统的远程调用变的如此简单很轻松,把什么EJB、CORBA、SOAP统统忘掉吧。想象这样的场景,所有的网站都提供REST风格的API,这个世界将是什么模样?
REST带来的不仅仅是web service的改变,对MVC架构同样具有很重要的意义,过去我们的复用通常在MODEL层,我们一直希望复用业务逻辑层,却没有想过是否能复用Controller甚至View呢?REST为我们提供了可能,比如以一个很经常被提到的例子来说,用户加入某个圈子这个操作跟圈子的管理员将用户加入圈子的操作是一样,但是操作成功后的跳转显示的页面也许不同,过去也许我们是通过写两个不同的Action来实现,而现在,同一个Action(加入圈子这个操作)只负责发送数据(XML格式的文档),而页面的展示将留给客户端去选择,从而复用了Controller,减少了Action和View层的代码量。进一步,请你想象,REST与AJAX的技术结合产生多么有趣的画面。
REST仅用于提供数据,展现更多的交给了客户端。
本文仅仅是我接触REST这两天的学习总结,对于REST的应用才刚刚起步,需要更多的探讨和实践。其实java实现REST也是相当简单的,servlet本身就是很好的模型,恐怕没有多人注意到HttpServlet类中的doPut和doDelete方法,我们过去太强调GET和POST,反而忽视了PUT和DELETE可能带来的改变。java开源世界中已经有了REST风格的框架,比如
cetia4,这是一个servlet-base的REST框架,值的关注。