📜 ⬆️ ⬇️

Interaction of web services through REST

When developing modern web services, the question often arises how to ensure the simple and transparent interaction of several heterogeneous systems. Fortunately, the choice is great: here are SOAP , and CORBA , and DCE / RPC , and, of course, REST . The creation of a cross-platform API based on it will be discussed.

Why do?


Indeed, why “make a fuss” and use disparate systems if you can select one tool once — either Perl (+ framework to taste) or Rails, and do everything on it? Approximately for the same, for which we use a slotted screwdriver for a slotted screw, and a cross for a cross screw, and not vice versa (this is possible, of course, but this is not effective). Each tool is better suited for one or another, a specific set of tasks.

Suppose that we have a web service that distributes information in a distributed manner using remotely installed agents. Suppose that this is not about a botnet (and they work differently now), but about a system for downloading video content from online resources like YouTube.

Channels are not always good, and operators sometimes do throttling . But to give the task to “agents”, in order to download ready-made files through ordinary HTTP / FTP at a high speed, this is nice.
')
That's why it makes sense to develop the main web service for simplicity and convenience on Rails, and make agents very “thin”, in the language that almost all Unix and some Windows servers have: Perl.

What to do?


As I mentioned at the beginning of the article, there are now a lot of protocols for implementing API between services. Previously, in this case, I would not have thought, and used classic SOAP (in fact: XML + HTTP). Fortunately, there are some good implementation tools for Perl and for Rails .

But now the RESTful API is becoming more and more popular, and for good reason. It does not require any schemes, definitions, additional WSDL files and other complexities. 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 the URI, an action is taken. The answer comes with the help of the same HTTP Response. In more detail, with signs and examples, you can read here .

In our example, Perl will be the server, and Rails will be the client.
So, with what to implement?

Perl side

Perl itself, without modules, is a very limited tool. Therefore, in order to use all of its strength and convenience, we need a Mojolicious module, positioning itself as “A next generation web framework for the Perl programming language”.

On its base you can do both a RESTful server and a RESTful client.

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, you can now fully use the built-in Rails mechanisms, namely the ActiveResource class, which allows you to work with a remote service in almost the same way as ActiveRecord .

How to do?



Perl side

Suppose we have a lot of Downloads objects on the Perl side, and from the side of Rails we want to do the following with them:

Using Mojolicious to make a RESTful web service for our task is very simple:

#!/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 , as the standard 3000 will most likely conflict with your Rails installation:
 ./restful-server.pl daemon --listen=http://*:3001 


Rails side

In this example, the entire Rails will be reduced to testing 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 common Rails actions with this class.

Search all objects:
 > Download.find(:all) => [#<Download:0x00000004b77060 @attributes={"name"=>"Video 0", "id"=>"0", "size"=>7654400, "uri"=>"http://site.com/download_0"}, @prefix_options={}, @persisted=true>, #<Download:0x0000000446f740 @attributes={"name"=>"Video 1", "id"=>"1", "size"=>8672256, "uri"=>"http://site.com/download_1"}, @prefix_options={}, @persisted=true>, #<Download:0x0000000446d300 @attributes={"name"=>"Video 2", "id"=>"2", "size"=>5931008, "uri"=>"http://site.com/download_2"}, @prefix_options={}, @persisted=true>, #<Download:0x0000000446c888 @attributes={"name"=>"Video 3", "id"=>"3", "size"=>2273280, "uri"=>"http://site.com/download_3"}, @prefix_options={}, @persisted=true>, #<Download:0x000000045c7c50 @attributes={"name"=>"Video 4", "id"=>"4", "size"=>8466432, "uri"=>"http://site.com/download_4"}, @prefix_options={}, @persisted=true>, #<Download:0x000000045c6ee0 @attributes={"name"=>"Video 5", "id"=>"5", "size"=>7057408, "uri"=>"http://site.com/download_5"}, @prefix_options={}, @persisted=true>, #<Download:0x000000045c5d60 @attributes={"name"=>"Video 6", "id"=>"6", "size"=>2351104, "uri"=>"http://site.com/download_6"}, @prefix_options={}, @persisted=true>, #<Download:0x00000004116058 @attributes={"name"=>"Video 7", "id"=>"7", "size"=>5640192, "uri"=>"http://site.com/download_7"}, @prefix_options={}, @persisted=true>, #<Download:0x00000004114320 @attributes={"name"=>"Video 8", "id"=>"8", "size"=>9701376, "uri"=>"http://site.com/download_8"}, @prefix_options={}, @persisted=true>, #<Download:0x0000000411b080 @attributes={"name"=>"Video 9", "id"=>"9", "size"=>9717760, "uri"=>"http://site.com/download_9"}, @prefix_options={}, @persisted=true>, #<Download:0x00000004a46330 @attributes={"name"=>"Video 10", "id"=>"10", "size"=>6734848, "uri"=>"http://site.com/download_10"}, @prefix_options={}, @persisted=true>] 


Search for a specific object:
 > Download.find(5) => #<Download:0x00000004aa5420 @attributes={"name"=>"Video 5", "id"=>"5", "size"=>7057408, "uri"=>"http://site.com/download_5"}, @prefix_options={}, @persisted=true> 


Search for a non-existent object. Notice how render_not_found works:
 > Download.find(100) ActiveResource::ResourceNotFound: Failed. Response code = 404. Response message = Not Found. 


Creating an object:
 > download = Download.new => #<Download:0x00000004802380 @attributes={}, @prefix_options={}, @persisted=false> > 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 => #<Download:0x000000049408f0 @attributes={"name"=>"New Video", "id"=>11, "size"=>23452363, "uri"=>"http://site.com/video.mp4"}, @prefix_options={}, @persisted=true> 


Change object:
 > download = Download.find(5) => #<Download:0x0000000473ee30 @attributes={"name"=>"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) => #<Download:0x000000043dade8 @attributes={"name"=>"New Video 5", "id"=>"5", "size"=>7057408, "uri"=>"http://site.com/download_5"}, @prefix_options={}, @persisted=true> 


Deleting an object:
 > Download.find(5).destroy => #<Net::HTTPOK 200 OK readbody=true> > Download.find(5) ActiveResource::ResourceNotFound: Failed. Response code = 404. Response message = Not Found. 


Calling a non-standard function:
 > Download.find(1).post(:start) => #<Net::HTTPOK 200 OK readbody=true> 


What's next?


This example can be developed in the following directions:


Versions Used


Source: https://habr.com/ru/post/148492/


All Articles