Talking Heads - writing an API does not need to be a "psycho killer"
-
Upload
theo-van-hoesel -
Category
Internet
-
view
203 -
download
0
description
Transcript of Talking Heads - writing an API does not need to be a "psycho killer"
Talking HeadsREST api’s don’t need to be your “psycho-killer”
Dancer 2A Lightweight WEB Framework
… that does nothing wrong
Dancer 2A Lightweight WEB Framework
… that does nothing you don’t want
Dancer 2A Lightweight WEB Framework … does it do what it should do?
RESTful API’s Dancer 2
HTTP protocols and RFC’s
Overview• HTTP 1.1 Protocol
• Let’s Dance
• Caching
• Conditional Requests
• Content Negotiation
• Authorization
HTTP/1.1 Protocol
• Requests
• Responses
• Request Methods
• Header Fields
• Status codes
HTTP 1.1 Protocol Request
GET /some_resource HTTP/1.1 Host: server.tst User-Agent: insane_client/0.1 Accept: text/html; q=0.8, image/gif; q=0.3 Accept-Charset: iso8509-1 Content-Length: 123 Here can come some content, for what ever purpose
HTTP 1.1 Protocol Request
GET /some_resource HTTP/1.1 Host: server.tst User-Agent: insane_client/0.1 Accept: text/html; q=0.8, image/gif; q=0.3 Accept-Charset: iso8509-1 Content-Length: 123 Here can come some content, for what ever purpose
HTTP 1.1 Protocol Request
GET /some_resource HTTP/1.1 Host: server.tst User-Agent: insane_client/0.1 Accept: text/html; q=0.8, image/gif; q=0.3 Accept-Charset: iso8509-1 Content-Length: 123 Here can come some content, for what ever purpose
HTTP 1.1 Protocol Request
GET /some_resource HTTP/1.1 Host: server.tst User-Agent: insane_client/0.1 Accept: text/html; q=0.8, image/gif; q=0.3 Accept-Charset: iso8509-1 Content-Length: 123 Here can come some content, for what ever purpose
HTTP 1.1 Protocol Request
GET /some_resource HTTP/1.1 Host: server.tst User-Agent: insane_client/0.1 Accept: text/html; q=0.8, image/gif; q=0.3 Accept-Charset: iso8509-1 Content-Length: 123 Here can come some content, for what ever purpose
HTTP 1.1 Protocol Response
HTTP/1.1 200 OK Server: dancer/2.0 Date: Tue, 14 Oct 2014 12:34:56 GMT Content-Type: text/plain Charset: iso8509-1 Content-Length: 456 You asked for something, here it is!
HTTP 1.1 Protocol Response
HTTP/1.1 200 OK Server: dancer/2.0 Date: Tue, 14 Oct 2014 12:34:56 GMT Content-Type: text/plain Charset: iso8509-1 Content-Length: 456 You asked for something, here it is!
HTTP 1.1 Protocol Response
HTTP/1.1 200 OK Server: dancer/2.0 Date: Tue, 14 Oct 2014 12:34:56 GMT Content-Type: text/plain Charset: iso8509-1 Content-Length: 456 You asked for something, here it is!
HTTP 1.1 Protocol Response
HTTP/1.1 200 OK Server: dancer/2.0 Date: Tue, 14 Oct 2014 12:34:56 GMT Content-Type: text/plain Charset: iso8509-1 Content-Length: 456 You asked for something, here it is!
HTTP 1.1 Protocol Response
HTTP/1.1 200 OK Server: dancer/2.0 Date: Tue, 14 Oct 2014 12:34:56 GMT Content-Type: text/plain Charset: iso8509-1 Content-Length: 456 You asked for something, here it is!
HTTP 1.1 Protocol Request Methodes
• GET
• POST
• PUT
• DELETE
• HEAD
• OPTIONS
• PATCH
HTTP 1.1 Protocol Header Fields
• Host
• User-Agent
• If-Modified-Since
• Content-Type
• Content-Length
• Location
• Server
• Last-Modification
• Content-Type
• Content-Length
HTTP 1.1 Protocol Status Codes
• 1—— Informational
• 2—— Succes
• 3—— Redirection
• 4—— Client Error
• 5—— Server
Status: 404Not Found
Status: 404Not Found
Status: 500Internal Server Error
HTTP 1.1 Protocol Status 1—— Informational
• 100 Continue
HTTP 1.1 Protocol Status 2—— Succes
• 200 OK
• 201 Created
• 202 Accepted
• 204 No Content
HTTP 1.1 Protocol Status 3—— Redirection
• 300 Multiple Choices
• 301 Moved Permanently
• 304 Not Modified
HTTP 1.1 Protocol Status 4—— Client Error
• 400 Bad Request
• 401 Unauthorized
• 403 Forbidden
• 404 Not Found
• 405 Method Not Allowed
• 407 Not Acceptable
• 410 Gone
• 412 Precondition Failed
HTTP 1.1 Protocol Status 5—— Server Error
• 500 Internal Server Error
• 501 Not Implemented
• 503 Service Unavailable
It takes Two
Dancer 2 & LWP
Client Server Let’s Dance
use Dancer2; get ’/user/:id’ => { my $user_info = resultset(’users’)->find(id=>params(’id’)); template ’user.tt’, $user_info; }; dance; 1;
Client Server Let’s Dance
use Dancer2; get ’/user/:id’ => { my $user_info = resultset(’users’)->find(id=>params(’id’)); template ’user.tt’, $user_info; }; dance; 1;
Client Server Let’s Dance
use Dancer2; get ’/user/:id’ => { my $user_info = resultset(’users’)->find(id=>params(’id’)); template ’user.tt’, $user_info; }; dance; 1;
Client Server Let’s Dance
use Dancer2; get ’/user/:id’ => { my $user_info = resultset(’users’)->find(id=>params(’id’)); template ’user.tt’, $user_info; }; dance; 1;
Client Server Let’s Dance
use Dancer2; get ’/user/:id’ => { my $user_info = resultset(’users’)->find(id=>params(’id’)); template ’user.tt’, $user_info; }; dance; 1;
Client Server Let’s Dance
use Dancer2; get ’/user/:id’ => { my $user_info = resultset(’users’)->find(id=>params(’id’)); template ’user.tt’, $user_info; }; dance; 1;
Client Server LWP::UserAgent
use LWP; use LWP::UserAgent; my $agent = LWP::UserAgent->new; my $response = $agent->get(’/user/999-999’);
Caching Temporary Storage
• Reduce cost
• Improve Responsivness
Caching Temporary Storage
• Client Side
• Public Proxy
• Server Side
Caching If-Modified-Since
use LWP; use LWP::UserAgent; my $agent = LWP::UserAgent->new; my $request = HTTP::Request->new( GET => ’/user/999-999-999’); $request->header( If-Modified-Since => Tue, 14 Oct 2014 12:34:56 GMT ); my $response = $agent->request($request);
Caching Response
• Response Status: 304 Not Modified
• Last-Modified: Tue, 07 Oct 2014 12:34:56 GMT
• Age: 3600
• Expire: Tue, 21 Oct 2014 12:34:56 GMT
• Cache-Control: public | private | max-age | no-cache
Conditional Requests
• Stateless
• No Resource Locking
• Only execute request if not modified since last
• Only execute request if the resource is still the same
Conditional Requests If-Unmodified-Since
use LWP; use LWP::UserAgent; my $agent = LWP::UserAgent->new; my $request = HTTP::Request->new( DELETE => ’/user/999-999-999’); $request->header( If-Unmodified-Since => ’Tue, 14 Oct 2014 12:34:56 GMT’); my $response = $agent->request($request);
Conditional Requests If-Match
use LWP; use LWP::UserAgent; my $agent = LWP::UserAgent->new; my $request = HTTP::Request->new( DELETE => ’/user/999-999-999’); $request->header( If-Match => ’a23dfe4532dab7b21a83d3e0f4c2a6f1’ ); my $response = $agent->request($request);
Conditional Requests Response
• Response Status: 412 Precondition Failed
• Response Status: 428 Precondition Required
• Last-Modified: Tue, 07 Oct 2014 12:34:56 GMT
• ETag: a23dfe4532dab7b21a83d3e0f4c2a6f1
Content Negotiation
• the same resource
• another representation
• the same URL
• client can let the server know what type is preferred
• server will try to deliver in requested content
• caches need to know that this is another variant
• the same URL
Content Negotiation Resources & Representation• Uniform Resouse Identifier
• Does not say anything about representation:
• Charset
• Encoding
• Language
• Format
Content Negotiation Accept & friends
• Accept: text/html, text/plain, image/png
• Accept-Language: nl, en, fr
• Accept-Charset: iso_
• Accept-Encoding: gzip
Content Negotiation Accepting Preferences
• The client can have some nice preferences
• it might like some representation above the other
• it might not like anything else
• The server can only deliver some representations
• it might deliver something it prefers
• it might give a list of options
Content Negotiation Mutable Serializer
use Dancer2; use Dancer2::Plugin::Mutable::Serializer;get ’/user/:id’ => { my $user_info = resultset(’users’)->find(id=>params(’id’)); return $user_info; }; 1;
Content Negotiation Mutable Serializer
use LWP; use LWP::UserAgent; my $agent = LWP::UserAgent->new; my $request = HTTP::Request->new( GET => ’/user/999-999-999’); $request->header( Accept => ’application/json’ ); my $response = $agent->request($request);
Content Negotiation Mutable Serializer
use LWP; use LWP::UserAgent; my $agent = LWP::UserAgent->new; my $request = HTTP::Request->new( GET => ’/user/999-999-999’); $request->header( Accept => ’application/json’ ); $request->header( Content-Type => ’application/xml’ ); my $response = $agent->request($request);
Content Negotiation Let’s do things differently
use Dancer2; get ’/user/:id’ => { my $user_info = resultset(’users’)->find(id=>params(’id’)); if (grep $_ eq ’text/html’, header(’Accept’)) { template ’user.tt’, $user_info; } if (grep $_ eq ’application/json’, header(’Accept’’)) { return to_json $user_info; } if (grep $_ eq ’application/xml’, header(’Accept’’)) { return to_xml $user_info; } }; 1;
Content Negotiation Let’s do things differently
use LWP; use LWP::UserAgent; my $agent = LWP::UserAgent->new; my $request = HTTP::Request->new( GET => ’/user/999-999-999’); $request->header( Accept => ’application/json’ ); my $response = $agent->request($request);
Content Negotiation Let’s do things differently
use Dancer2; get ’/user/:id’ => { my $user_info = resultset(’users’)->find(id=>params(’id’)); if (grep $_ eq ’text/html’, header(’Accept’)) { template ’user.tt’, $user_info; } if (grep $_ eq ’application/json’, header(’Accept’’)) { return to_json $user_info; } if (grep $_ eq ’application/xml’, header(’Accept’’)) { return to_xml $user_info; } }; 1;
Content Negotiation Let’s do things differently
use LWP; use LWP::UserAgent; my $agent = LWP::UserAgent->new; my $request = HTTP::Request->new( GET => ’/user/999-999-999’); $request->header( Accept => ’application/xml; q=0.1, text/html’ ); my $response = $agent->request($request);
Content Negotiation Let’s do things differently
use Dancer2; get ’/user/:id’ => { my $user_info = resultset(’users’)->find(id=>params(’id’)); if (grep $_ eq ’text/html’, header(’Accept’)) { template ’user.tt’, $user_info; } if (grep $_ eq ’application/json’, header(’Accept’’)) { return to_json $user_info; } if (grep $_ eq ’application/xml’, header(’Accept’’)) { return to_xml $user_info; } }; 1;
Content Negotiation Let’s get Messy
Accept: text/plain; q=0.3, text/html; q=0.5, */*; q=0.0
text/plainq=0.3
text/htmlq=0.5
*/*q=0.0
«I’m fine with plain-txt, but like html more… but if it’s anything else, I DON’T WANT THAT»
Content Negotiation Let’s get Messy
use Dancer2; use Dancer2::Plugin::HTTP::ContentNegotiation;get ’/user/:id’ => { my $user_info = resultset(’users’)->find(id=>params(’id’)); http_choose_accept ( ’text/html’ => sub {template ’user.tt’, $user_info}, ’application/json’ => sub {to_json $user_info}, ’application/xml’ => sub {to_xml $user_info}, ); };
Content Negotiation Let’s get Messy
use Dancer2; use Dancer2::Plugin::HTTP::ContentNegotiation;get ’/user/:id’ => { my $user_info = resultset(’users’)->find(id=>params(’id’)); http_choose_accept ( [’image/png’, ’image/jpg’, ’image/gif’] => sub {magick(http_accept->minor)}, ); };
Content Negotiation Resources & Representation• Status 300: Multiple Choices
• format at the end of the URL
• language at the beginning of the URL
• Status 406: Not Acceptable
• Vary: Accept, Aceept-Language . . .
Auth Resource Access
• Stateless
• Submit credentials with every request
• Authentication
• Username & Password
• Authentication Scheme
• Authorisation
• Rolebased access
Auth Usual WEB handling
1. Attempt to acces some page
2. Not Authorised ?
3. Go to /login
4. Send credentials
5. Authenticated Now ?
6. Setup session cookie
7. Go back to original requested page
Auth REST api
1. Attempt to acces some page
2. Not Authenticated ?
3. Status: 401 “Not Authorized”
4. Resend same request including credentials
5. Authorised ?
• Continue processing
• Status: 403 “Forbidden”
Auth HTTP Auth::Extensible
use Dancer2; use Dancer2::Plugin::HTTP::Auth::Extensible; get '/realm' => http_require_authentication sub { "You are logged in using realm: " . http_realm }; get '/vodka' => http_require_role HardDrinker => sub { "Only hard drinkers get vodka"; };
Auth HTTP Auth::Extensible
use Dancer2; use Dancer2::Plugin::HTTP::Auth::Extensible; get '/realm' => http_require_authentication sub { "You are logged in using realm: " . http_realm }; get '/vodka' => http_require_role HardDrinker => sub { "Only hard drinkers get vodka"; };
Auth HTTP Auth::Extensible
use Dancer2; use Dancer2::Plugin::HTTP::Auth::Extensible; get '/realm' => http_require_authentication sub { "You are logged in using realm: " . http_realm }; get '/vodka' => http_require_role HardDrinker => sub { "Only hard drinkers get vodka"; };
Auth HTTP Auth::Extensible
plugins: 'HTTP::Auth::Extensible': realms: example: provider: Config users: - user: ‘beerdrinker' pass: ‘password' name: 'Beer drinker’ roles: - BeerDrinker
Auth Resource Access
• Status 401: Unauthorized
• You should return a WWW-Authenticate also
• HTTP Request Header Field: Authorize
• Status 403: Forbidden
Dancer2::Plugin::HTTP Family
• Dancer2::Plugin::HTTP::ContentNegotiation
• Dancer2::Plugin::HTTP::Auth::Extensible
• Dancer2::Plugin::HTTP::Conditional
• Dancer2::Plugin::HTTP::Cache
Dancer2::Plugin::HTTP HTTP::Header::ActionPack
• Authentication / Authorisation
• MIME-types
• DateTime conversion
Dancer2::Plugin::HTTP HTTP::Header::ActionPack
• Authentication / Authorisation
• MIME-types
• DateTime conversion
Net::WebMachine HTTP::Header::ActionPack
• Basho schema
• Ruby implementation
• Stevan Little / Dave Rolsky