Web Services Interoperability via REST
When developing modern web services, the question often arises of how to provide a simple and transparent interaction of several heterogeneous systems. Fortunately, the choice is wide: here are SOAP , CORBA , DCE / RPC , and, of course, REST . The creation of a cross-platform API based on it will be discussed.
Indeed, why "fence the garden" and use heterogeneous systems if you can choose one tool once - either Perl (+ a framework to your liking) or Rails, and do everything on it? For about the same reason we use a slotted screwdriver for a slotted screw, and a Phillips screwdriver for a Phillips screw, and not vice versa (this is possible, of course, but it is not effective). Each tool is better suited for a particular set of tasks.
Suppose we have a web service that collects some information using remotely installed agents. Suppose that this is not a botnet (and they work differently now), but a system for downloading video content from online resources such as YouTube.
Channels are not always good, and operators sometimes do throttling. But to give the task to the “agents” in order to download ready-made files through regular HTTP / FTP at a high speed is nice.
That is why, for simplicity and convenience, the main web service makes sense to develop on Rails, and make agents very "thin", in the language that is available on almost all Unix and on some Windows servers: Perl.
As I mentioned at the beginning of the article, there are now many protocols for implementing APIs between services. Earlier in this case, I would not have thought, and used the classic SOAP (essentially: XML + HTTP). Fortunately, there are good implementation tools for both Perl and Rails .
But now the RESTful API is gaining more and more popularity, and for good reason. It does not require any schemes, definitions, additional WSDL files and other difficulties. The essence of the approach is to use HTTP commands (GET, PUT, POST, DELETE) in combination with the corresponding URI . Depending on the command and URI, an action is performed. The response comes using the same HTTP Response. More details, with signs and examples, can be found here..
In our example, Perl will act as the server, and Rails as the client.
So, with what to implement?
Perl itself, without modules, is a very limited tool. Therefore, to use all its strength and convenience, we need the Mojolicious module , positioning itself as “A next generation web framework for the Perl programming language”.
Based on it, you can make both a RESTful server and a RESTful client.
Until recently, Rails did not have a normal mechanism for REST interaction with third-party services, despite the fact that this ideology permeates the framework from head to toe. Therefore, various GEMs were made to solve this problem with varying degrees of success.
Fortunately, now you can fully use the built-in Rails mechanisms, namely the ActiveResource class , which allows you to work with a remote service in much the same way as ActiveRecord .
Suppose we have many Downloads objects on the Perl side, and on the Rails side we want to do the following with them:
Using Mojolicious, it’s very easy to make a RESTful web service for our task:
We start the service. We use port 3001 , since the standard 3000 is likely to conflict with your Rails installation:
As part of this example, all Rails will be reduced to checking the functionality of the ActiveResource class with our RESTful Perl server.
Create the desired class:
Now we can perform the actions normal for Rails with this class.
Search all objects:
Search for a specific object:
Search for a nonexistent object. Notice how render_not_found works :
Object Creation:
Object change:
Delete object:
Invocation of a non-standard function:
This example can be developed in the following directions:
Why do it?
Indeed, why "fence the garden" and use heterogeneous systems if you can choose one tool once - either Perl (+ a framework to your liking) or Rails, and do everything on it? For about the same reason we use a slotted screwdriver for a slotted screw, and a Phillips screwdriver for a Phillips screw, and not vice versa (this is possible, of course, but it is not effective). Each tool is better suited for a particular set of tasks.
Suppose we have a web service that collects some information using remotely installed agents. Suppose that this is not a botnet (and they work differently now), but a system for downloading video content from online resources such as YouTube.
Channels are not always good, and operators sometimes do throttling. But to give the task to the “agents” in order to download ready-made files through regular HTTP / FTP at a high speed is nice.
That is why, for simplicity and convenience, the main web service makes sense to develop on Rails, and make agents very "thin", in the language that is available on almost all Unix and on some Windows servers: Perl.
What to do?
As I mentioned at the beginning of the article, there are now many protocols for implementing APIs between services. Earlier in this case, I would not have thought, and used the classic SOAP (essentially: XML + HTTP). Fortunately, there are good implementation tools for both Perl and Rails .
But now the RESTful API is gaining more and more popularity, and for good reason. It does not require any schemes, definitions, additional WSDL files and other difficulties. The essence of the approach is to use HTTP commands (GET, PUT, POST, DELETE) in combination with the corresponding URI . Depending on the command and URI, an action is performed. The response comes using the same HTTP Response. More details, with signs and examples, can be found here..
In our example, Perl will act as the server, and Rails as the client.
So, with what to implement?
Perl side
Perl itself, without modules, is a very limited tool. Therefore, to use all its strength and convenience, we need the Mojolicious module , positioning itself as “A next generation web framework for the Perl programming language”.
Based on it, you can make both a RESTful server and a RESTful client.
On the Rails side
Until recently, Rails did not have a normal mechanism for REST interaction with third-party services, despite the fact that this ideology permeates the framework from head to toe. Therefore, various GEMs were made to solve this problem with varying degrees of success.
Fortunately, now you can fully use the built-in Rails mechanisms, namely the ActiveResource class , which allows you to work with a remote service in much the same way as ActiveRecord .
How to do?
Perl side
Suppose we have many Downloads objects on the Perl side, and on the Rails side we want to do the following with them:
- Create
- receive
- Change
- Delete
Using Mojolicious, it’s very easy to make a RESTful web service for our task:
#!/usr/bin/perl -w
use Mojolicious::Lite;
# Создаем массив с тестовыми данными
# В нашем примере при создании/изменении используется
# только один параметр: URI видео файла для скачивания
my $downloads = [];
foreach my $id (0..10) {
$downloads->[$id] =
{ 'id' => $id,
'uri' => "http://site.com/download_$id",
'name' => "Video $id",
'size' => (int(rand(10000)) + 1) * 1024
};
}
# Непосредственное описание методов веб-сервиса
# Создание (create)
post '/downloads/' => sub {
my $self = shift;
# Мы получаем от Rails все параметры в JSON
# Поэтому, их надо распарсить
my $params = Mojo::JSON->decode($self->req->body)->{'download'};
# При создании в качестве уникального id выступает
# последний индекс нашего тестового массива
my $id = $#{ $downloads } + 1;
my $uri = $params->{'uri'} || 'http://localhost/video.mp4';
my $name = $params->{'name'} || "Video $id";
my $size = $params->{'size'} || (int(rand(10000)) + 1) * 1024;
$downloads->[$id] =
{ 'id' => $id,
'uri' => $uri,
'name' => $name,
'size' => $size
};
# Отправляем в качестве ответа созданный объект
$self->render_json($downloads->[$id]);
};
# Список всех объектов (index)
get '/downloads' => sub {
my $self = shift;
$self->render_json($downloads);
};
# Поиск и получение информации объекта (find/show)
get '/downloads/:id' => sub {
my $self = shift;
my $id = $self->param('id');
if (!exists($downloads->[$id])) {
# Если нет такого объекта - 404
$self->render_not_found;
} else {
# Иначе - отдаем объект
$self->render_json($downloads->[$id]);
}
};
# Редактирование (update)
put '/downloads/:id' => sub {
my $self = shift;
my $params = Mojo::JSON->decode($self->req->body)->{'download'};
my $id = $self->param('id');
my $uri = $params->{'uri'} || 'http://localhost/video.mp4';
my $name = $params->{'name'} || "Video $id";
my $size = $params->{'size'} || (int(rand(10000)) + 1) * 1024;
if (!exists($downloads->[$id])) {
$self->render_not_found;
} else {
$downloads->[$id] =
{ 'id' => $id,
'uri' => $uri,
'name' => $name,
'size' => $size
};
$self->render_json($downloads->[$id]);
}
};
# Удаление (delete)
del '/downloads/:id' => sub {
my $self = shift;
my $id = $self->param('id');
if (!exists($downloads->[$id])) {
$self->render_not_found;
} else {
delete $downloads->[$id];
# Посылаем HTTP 200 OK - объект успешно удален
$self->rendered;
}
};
# Пример нестандартной функции - старт загрузки
post '/downloads/:id/start' => sub {
my $self = shift;
my $id = $self->param('id');
if (!exists($downloads->[$id])) {
$self->render_not_found;
} else {
$self->rendered;
}
};
# Непосредственный запуск сервера
app->start;
We start the service. We use port 3001 , since the standard 3000 is likely to conflict with your Rails installation:
./restful-server.pl daemon --listen=http://*:3001
On the Rails side
As part of this example, all Rails will be reduced to checking the functionality of the ActiveResource class with our RESTful Perl server.
Create the desired class:
class Download < ActiveResource::Base
# Адрес Perl-сервера
self.site = 'http://localhost:3001'
end
Now we can perform the actions normal for Rails with this class.
Search all objects:
> Download.find(:all)
=> [#"Video 0", "id"=>"0", "size"=>7654400, "uri"=>"http://site.com/download_0"}, @prefix_options={}, @persisted=true>, #"Video 1", "id"=>"1", "size"=>8672256, "uri"=>"http://site.com/download_1"}, @prefix_options={}, @persisted=true>, #"Video 2", "id"=>"2", "size"=>5931008, "uri"=>"http://site.com/download_2"}, @prefix_options={}, @persisted=true>, #"Video 3", "id"=>"3", "size"=>2273280, "uri"=>"http://site.com/download_3"}, @prefix_options={}, @persisted=true>, #"Video 4", "id"=>"4", "size"=>8466432, "uri"=>"http://site.com/download_4"}, @prefix_options={}, @persisted=true>, #"Video 5", "id"=>"5", "size"=>7057408, "uri"=>"http://site.com/download_5"}, @prefix_options={}, @persisted=true>, #"Video 6", "id"=>"6", "size"=>2351104, "uri"=>"http://site.com/download_6"}, @prefix_options={}, @persisted=true>, #"Video 7", "id"=>"7", "size"=>5640192, "uri"=>"http://site.com/download_7"}, @prefix_options={}, @persisted=true>, #"Video 8", "id"=>"8", "size"=>9701376, "uri"=>"http://site.com/download_8"}, @prefix_options={}, @persisted=true>, #"Video 9", "id"=>"9", "size"=>9717760, "uri"=>"http://site.com/download_9"}, @prefix_options={}, @persisted=true>, #"Video 10", "id"=>"10", "size"=>6734848, "uri"=>"http://site.com/download_10"}, @prefix_options={}, @persisted=true>]
Search for a specific object:
> Download.find(5)
=> #"Video 5", "id"=>"5", "size"=>7057408, "uri"=>"http://site.com/download_5"}, @prefix_options={}, @persisted=true>
Search for a nonexistent object. Notice how render_not_found works :
> Download.find(100)
ActiveResource::ResourceNotFound: Failed. Response code = 404. Response message = Not Found.
Object Creation:
> download = Download.new
=> #
> download.name = "New Video"
=> "New Video"
> download.uri = "http://site.com/video.mp4"
=> "http://site.com/video.mp4"
> download.size = 23452363
=> 23452363
> download.save
=> true
> Download.last
=> #"New Video", "id"=>11, "size"=>23452363, "uri"=>"http://site.com/video.mp4"}, @prefix_options={}, @persisted=true>
Object change:
> download = Download.find(5)
=> #"Video 5", "id"=>"5", "size"=>7057408, "uri"=>"http://site.com/download_5"}, @prefix_options={}, @persisted=true>
> download.name = "New Video 5"
=> "New Video 5"
> download.save
=> true
> Download.find(5)
=> #"New Video 5", "id"=>"5", "size"=>7057408, "uri"=>"http://site.com/download_5"}, @prefix_options={}, @persisted=true>
Delete object:
> Download.find(5).destroy
=> #
> Download.find(5)
ActiveResource::ResourceNotFound: Failed. Response code = 404. Response message = Not Found.
Invocation of a non-standard function:
> Download.find(1).post(:start)
=> #
What's next?
This example can be developed in the following directions:
- ActiveResource does not have a rigid data model, but you can optionally set it using schema . This will allow, for example, to exclude the manual assignment of id string value.
- Perl as a RESTful client using the Mojo :: UserAgent module
- Authentication / Authorization
Used Versions
- CentOS Linux 6.2
- Perl 5.10.1
- Mojolicious 2.97
- Ruby 1.9.3p125
- Rails 3.2.1