summaryrefslogtreecommitdiff
path: root/posts/2010-09-17-spore.md
diff options
context:
space:
mode:
authorFranck Cuny <franck.cuny@gmail.com>2016-08-13 09:04:37 -0700
committerFranck Cuny <franck.cuny@gmail.com>2016-08-13 09:04:37 -0700
commit44b0a689cdd2ae2f3f34451910eb1f09fd4d65dd (patch)
tree5a3588c8928bbd56f426b091b68f17cc533795d1 /posts/2010-09-17-spore.md
parentMove assets around (diff)
downloadlumberjaph-44b0a689cdd2ae2f3f34451910eb1f09fd4d65dd.tar.gz
some more clean up.
Diffstat (limited to '')
-rw-r--r--posts/2010-09-17-spore.md116
1 files changed, 57 insertions, 59 deletions
diff --git a/posts/2010-09-17-spore.md b/posts/2010-09-17-spore.md
index a9becc9..47513d0 100644
--- a/posts/2010-09-17-spore.md
+++ b/posts/2010-09-17-spore.md
@@ -1,5 +1,4 @@
-Specification to a POrtable Rest Environment
---------------------------------------------
+# Specification to a POrtable Rest Environment
More and more web services offer [ReST API](http://en.wikipedia.org/wiki/Representational_State_Transfer). Every time you want to access one of these services, you need to write a client for this API, or find an existing one. Sometimes, you will find the right library that will do exactly what you need. Sometimes you won't, and you end up writing your own.
@@ -15,8 +14,7 @@ With SPORE, I propose a better solution for this. SPORE is composed of two parts
- a specification that describes an API
- a "framework" for clients (think this as [WSGI](http://www.python.org/dev/peps/pep-0333/), [Plack](http://plackperl.org/), [Rack](http://rack.rubyforge.org/), [JSGI](http://jackjs.org/jsgi-spec.html), ...)
-API Specifications
-------------------
+## API Specifications
I know, at this point, you're thinking "what ? isn't it just what [WSDL](http://en.wikipedia.org/wiki/Web_Services_Description_Language) does ?". Well, yes. But it's (in my opinion) simpler to write this than to write a WSDL. And when you say "WSDL" people think "[SOAP](http://en.wikipedia.org/wiki/SOAP_(protocol))", and that's definitly not a good thing.
@@ -28,15 +26,15 @@ The first part is the specification to API. A ReST request is mainly :
This is **easy to describe**. For this example, I will use the [twitter API](http://dev.twitter.com/doc/get/statuses/public_timeline). So, if i want to describe the user timeline method, we will get something like this:
-``` example
- public_timeline:
- method: GET
- path: /statuses/public_timeline.:format
- params:
- - trim_user
- - include_entities
- required:
- - format
+``` yaml
+public_timeline:
+ method: GET
+ path: /statuses/public_timeline.:format
+ params:
+ - trim_user
+ - include_entities
+ required:
+ - format
```
Whatever your language of choice is, you'll always need this informations. The idea with the API description, is that it can be reused by every language. If the API provider publishes this file, everyone can easily use it. It's very similar to a documentation (for the twitter description, all I needed to do was to copy/paste the documentation, it's really that simple) but it can be used by a framework to generate a client.
@@ -50,8 +48,7 @@ There is many advantages to do it this way:
I've started to write some specifications for a few services ([twitter](https://github.com/SPORE/api-description/blob/master/services/twitter.json), [github](https://github.com/SPORE/api-description/blob/master/services/github.json), [backtype](https://github.com/franckcuny/spore/blob/master/services/backtype.json), [backtweet](https://github.com/franckcuny/spore/blob/master/services/backtweet.json), ...) and applications ([couchdb](https://github.com/franckcuny/spore/blob/master/apps/couchdb.json), [presque](https://github.com/franckcuny/spore/blob/master/apps/presque.json)). They are not complete yet, so you're welcomed to [fork the repository](https://github.com/franckcuny/spore), add missings methods, and add your own specifications! :)
-Client Specification
---------------------
+## Client Specification
Now that we have a simple description for the API, we want to have [an easy solution to use it](https://github.com/franckcuny/net-http-spore/blob/master/spec/spore_implementation.pod). I will describe [the Perl implementation](https://github.com/franckcuny/net-http-spore), but there is also one for Ruby (will be published soon), and a early version for [Clojure](http://github.com/ngrunwald/clj-spore) and [Python](http://github.com/elishowk/pyspore).
@@ -70,14 +67,14 @@ The core will create the appropriate functions using the previous description. T
For each method declared in the description, I build a new Moose method I will attach to my class. Basicaly, the code looks like this:
``` perl
- foreach my $method_name ( keys %$methods_spec ) {
- $class->meta->add_spore_method(
- "user_timeline",
- path => '/statuses/public_timeline.:format',
- required => [qw/format/],
- params => [qw/trim_user include_entities/]
- );
- }
+foreach my $method_name ( keys %$methods_spec ) {
+ $class->meta->add_spore_method(
+ "user_timeline",
+ path => '/statuses/public_timeline.:format',
+ required => [qw/format/],
+ params => [qw/trim_user include_entities/]
+ );
+}
```
The code of the `user_timelime` method will be generated via the `add_spore_method`.
@@ -91,36 +88,38 @@ Middlewares are the nice part of it. By default, the core only creates a request
- proxying
- ...
+![char](../assets/chart.webp)
+
The most obvious middleware is the one that handles the format. When you load the middleware Format::JSON, it will set various headers on your request. In case of a GET method, the **Accept** header will be set to **application/json**. For a POST, the **Content-Type** will be also set. Before returning the result to the client, the content will be transformed from JSON to a Perl structure.
For twitter, I can have a client with this few lines:
``` perl
- my $client = Net::HTTP::Spore->new_from_spec('twitter.json');
- $client->enable('Format::JSON');
-
- my $timeline = $client->public_timeline( format => 'json', include_rts => 1 );
- my $tweets = $timeline->body;
- foreach my $tweet (@$tweets) {
- say $tweet->{user}->{screen_name} . " says " . encode_utf8($tweet->{text});
- }
+my $client = Net::HTTP::Spore->new_from_spec('twitter.json');
+$client->enable('Format::JSON');
+
+my $timeline = $client->public_timeline( format => 'json', include_rts => 1 );
+my $tweets = $timeline->body;
+foreach my $tweet (@$tweets) {
+ say $tweet->{user}->{screen_name} . " says " . encode_utf8($tweet->{text});
+}
```
Now, I want to use my friends timeline, which requires OAuth ? easy
``` perl
- $client->enable(
- 'Auth::OAuth',
- consumer_key => $consumer_key,
- consumer_secret => $consumer_secret,
- token => $token,
- token_secret => $token_secret,
- );
- my $friends_timeline = $client->friends_timeline(format => 'json', include_rts => 1);
- my $tweets = $friends_timeline->body;
- foreach my $tweet (@$tweets) {
- print $tweet->{user}->{screen_name} . " says " . encode_utf8($tweet->{text}) . "\n";
- }
+$client->enable(
+ 'Auth::OAuth',
+ consumer_key => $consumer_key,
+ consumer_secret => $consumer_secret,
+ token => $token,
+ token_secret => $token_secret,
+);
+my $friends_timeline = $client->friends_timeline(format => 'json', include_rts => 1);
+my $tweets = $friends_timeline->body;
+foreach my $tweet (@$tweets) {
+ print $tweet->{user}->{screen_name} . " says " . encode_utf8($tweet->{text}) . "\n";
+}
```
Middlewares are easy to write. They should implement a *call* method, which receive a request object as argument. The middleware can return:
@@ -132,26 +131,25 @@ Middlewares are easy to write. They should implement a *call* method, which rece
A simple middleware that add a runtime header to the response object will look like this:
``` perl
- sub call {
- my ( $self, $req ) = @_;
-
- my $start_time = [Time::HiRes::gettimeofday];
-
- $self->response_cb(
- sub {
- my $res = shift;
- my $req_time = sprintf '%.6f',
- Time::HiRes::tv_interval($start_time);
- $res->header( 'X-Spore-Runtime' => $req_time );
- }
- );
- }
+sub call {
+ my ( $self, $req ) = @_;
+
+ my $start_time = [Time::HiRes::gettimeofday];
+
+ $self->response_cb(
+ sub {
+ my $res = shift;
+ my $req_time = sprintf '%.6f',
+ Time::HiRes::tv_interval($start_time);
+ $res->header( 'X-Spore-Runtime' => $req_time );
+ }
+ );
+}
```
I've tried to mimic as much as possible Plack's behavior. The result of a request is a Response object, but you can also use it as an arrayref, with the following values \[http\_code, \[http\_headers\], body\].
-Conclusion
-----------
+## Conclusion
The real target for this are not API developers (even if it's useful to have this when you write your own API, I will show some examples soon), neither client developers (even it's really easier to do with this), but people who want to play immediatly with an API to fetch data, without the coding skill or knowledge of what an HTTP request is, how to define headers, what is OAuth, ... As it was suggested to me, an implementation for [R](http://en.wikipedia.org/wiki/R_(programming_language)) would be really usefull to a lot of people.