Slide 1

Slide 1 text

RAILS PARA APIS NANDO VIEIRA @fnando

Slide 2

Slide 2 text

http://nandovieira.com.br

Slide 3

Slide 3 text

http://hellobits.com

Slide 4

Slide 4 text

http://howtocode.com.br howto.

Slide 5

Slide 5 text

http://codeplane.com.br

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

O Rails mudou o modo como criamos aplicações web.

Slide 8

Slide 8 text

Não precisamos nos preocupar mais como será a organização dos arquivos.

Slide 9

Slide 9 text

Nem como definir as rotas que nosso app vai reconhecer.

Slide 10

Slide 10 text

Mas isso tem um custo que para APIs pode não ser muito bom.

Slide 11

Slide 11 text

APIs tem que ser rápidas.

Slide 12

Slide 12 text

Você tem outras alternativas se quiser continuar com Ruby, como Sinatra.

Slide 13

Slide 13 text

No Sinatra, você volta ao problema de ter que fazer tudo o que o Rails já fazia por você.

Slide 14

Slide 14 text

Rails para API = Rails - Web app stuff

Slide 15

Slide 15 text

Instalando

Slide 16

Slide 16 text

$ gem install rails --pre

Slide 17

Slide 17 text

$ rails new codeplane -TJ -d postgresql

Slide 18

Slide 18 text

Dependências

Slide 19

Slide 19 text

Remova tudo o que você não precisa do Gemfile.

Slide 20

Slide 20 text

ruby "2.0.0" source "https://rubygems.org" gem "rails", "4.0.0.rc1" gem "pg" gem "rack-cache", require: "rack/cache" gem "dalli"

Slide 21

Slide 21 text

Middlewares

Slide 22

Slide 22 text

O Rails possui uma série de middlewares que não são necessários para APIs.

Slide 23

Slide 23 text

# config/initializers/middleware.rb Rails.configuration.middleware.tap do |middleware| middleware.delete ActionDispatch::Cookies middleware.delete ActionDispatch::Flash middleware.delete ActionDispatch::Session::CookieStore middleware.delete ActionDispatch::Static middleware.delete Rack::MethodOverride end

Slide 24

Slide 24 text

Recebendo parâmetros

Slide 25

Slide 25 text

Desabilite o CSRF token.

Slide 26

Slide 26 text

# app/controllers/application_controller.rb class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. # protect_from_forgery with: :exception end

Slide 27

Slide 27 text

No Rails, estamos acostumados a receber hashes como POST data.

Slide 28

Slide 28 text

User.new(params[:user])

Slide 29

Slide 29 text

{ name: "John Doe", email: "john@example.org", password: "[FILTERED]", password_confirmation: "[FILTERED]" }

Slide 30

Slide 30 text

Isso não é comum em outras linguagens/frameworks.

Slide 31

Slide 31 text

Receber os parâmetros na raíz pode ser um problema.

Slide 32

Slide 32 text

post "/:name", to: "application#index"

Slide 33

Slide 33 text

$ http POST localhost:3002/hello name="John Doe"

Slide 34

Slide 34 text

params[:name] #=> hello

Slide 35

Slide 35 text

request.request_parameters

Slide 36

Slide 36 text

# app/services/body_data.rb class BodyData def initialize(params) @params = params end def only(*attrs) @params.reduce({}) do |buffer, (key, value)| buffer[key] = value if attrs.include?(key.to_sym) buffer end end end

Slide 37

Slide 37 text

# app/controllers/application_controller.rb class ApplicationController < ActionController::Base def body_data @body_data ||= BodyData.new(request.request_parameters) end end

Slide 38

Slide 38 text

repo = Repo.new body_data.only(:name)

Slide 39

Slide 39 text

Representando dados

Slide 40

Slide 40 text

Existem inúmeras maneiras de representar os dados em uma API.

Slide 41

Slide 41 text

JSON JavaScript Object Notation

Slide 42

Slide 42 text

# app/controllers/repos_controller.rb class ReposController < ApplicationController respond_to :json def index repos = RepoList.new(current_user).all respond_with repos end end

Slide 43

Slide 43 text

Crie uma camada de abstração dos dados que serão representados.

Slide 44

Slide 44 text

http://rubygems.org/gems/active_model_serializers

Slide 45

Slide 45 text

# app/models/repo.rb class Repo < ActiveRecord::Base def active_model_serializer RepoSerializer end end

Slide 46

Slide 46 text

# app/serializers/repo_serializer.rb class RepoSerializer < ActiveModel::Serializer attributes :name, :created_at end

Slide 47

Slide 47 text

http localhost:3002/repos

Slide 48

Slide 48 text

{ "repos": [ { "created_at": "2013-05-18T13:48:11.006Z", "name": "codeplane" }, { "created_at": "2013-05-18T13:48:17.109Z", "name": "howto" } ] }

Slide 49

Slide 49 text

Schemaless é bom, mas é ruim.

Slide 50

Slide 50 text

http://stateless.co/hal_specification.html

Slide 51

Slide 51 text

http://jsonapi.org

Slide 52

Slide 52 text

JSON com significado e Hypermedia permitem criar ferramentas automatizadas.

Slide 53

Slide 53 text

https://github.com/apotonick/roar

Slide 54

Slide 54 text

# app/controllers/repos_controller.rb class ReposController < ApplicationController include Roar::Rails::ControllerAdditions respond_to :json def index respond_with Repo.all end end

Slide 55

Slide 55 text

# app/representers/repos_representer.rb module ReposRepresenter include Roar::Representer::JSON::HAL include Roar::Representer::Feature::Hypermedia collection :repos, :extend => RepoRepresenter link :self do repos_url end def repos each end end

Slide 56

Slide 56 text

# app/representers/repo_representer.rb module RepoRepresenter include Roar::Representer::JSON::HAL include Roar::Representer::Feature::Hypermedia property :name property :created_at link :self do repo_url(represented) end end

Slide 57

Slide 57 text

{ "_links": { "self": { "href": "http://127.0.0.1:3000/repos" } }, "repos": [ { "_links": { "self": { "href": "http://127.0.0.1:3000/repos/1" } }, "created_at": "2013-05-18T13:48:11.006Z", "name": "codeplane" } ] }

Slide 58

Slide 58 text

O Rails possui suporte às convenções REST desde o Rails 2.

Slide 59

Slide 59 text

Mas isso não significa que seu app será Hypermedia.

Slide 60

Slide 60 text

Não implementa o método HTTP OPTIONS.

Slide 61

Slide 61 text

# config/initializers/action_dispatch.rb class ActionDispatch::Routing::Mapper module HttpHelpers def options(*args, &block) map_method(:options, args, &block) end end end

Slide 62

Slide 62 text

http http://localhost:3000/

Slide 63

Slide 63 text

{ "_links": { "locales": { "href": "http://localhost:3000/help/locales" }, "self": { "href": "http://localhost:3000/" }, "timezones": { "href": "http://localhost:3000/help/timezones" }, "users": { "href": "http://localhost:3000/users" } } }

Slide 64

Slide 64 text

http http://localhost:3000/help/locales

Slide 65

Slide 65 text

{ "_links": { "self": { "href": "http://localhost:3000/help/locales" } }, "locales": [ { "code": "en", "name": "English" }, { "code": "pt-BR", "name": "Brazilian Portuguese" } ] }

Slide 66

Slide 66 text

http OPTIONS http://localhost:3000/users

Slide 67

Slide 67 text

HTTP/1.1 200 OK Allow: POST Cache-Control: max-age=0, private, must-revalidate Connection: close Content-Encoding: gzip Content-Type: text/html ETag: "bfdba25abe4e2efd6ba82155b8aa0719" Server: thin 1.5.1 codename Straight Razor Vary: Accept-Language,Accept-Encoding,Origin X-Request-Id: ca7031fd-5190-4d9c-87b5-fac42dff926a X-Runtime: 0.036035

Slide 68

Slide 68 text

# config/routes.rb Codeplane::Application.routes.draw do controller :users do post "/users", action: :create options "/users", to: allow(:post), as: false end end

Slide 69

Slide 69 text

# config/initializers/action_dispatch.rb class ActionDispatch::Routing::Mapper module HttpHelpers def allow(*verbs) AllowedMethods.new(*verbs) end end end

Slide 70

Slide 70 text

# config/middleware/allowed_methods.rb class AllowedMethods attr_reader :verbs def initialize(*verbs) @verbs = verbs end def call(env) [ 200, { "Content-Type" => "text/html", "Allow" => verbs.map(&:upcase).join(",") }, [] ] end end

Slide 71

Slide 71 text

http POST http://localhost:3000/users

Slide 72

Slide 72 text

{ "errors": { "email": [ "is invalid" ], "name": [ "can't be blank" ], "password": [ "can't be blank" ], "username": [ "is invalid" ] } }

Slide 73

Slide 73 text

http POST http://localhost:3000/users \ Accept-Language:pt-BR

Slide 74

Slide 74 text

{ "errors": { "email": [ "é inválido" ], "name": [ "não pode ficar em branco" ], "password": [ "não pode ficar em branco" ], "username": [ "não é válido" ] } }

Slide 75

Slide 75 text

class Locale attr_reader :app, :i18n def initialize(app, i18n = I18n) @app = app @i18n = i18n end def call(env) i18n.locale = env["HTTP_ACCEPT_LANGUAGE"] app.call(env) end end

Slide 76

Slide 76 text

Autenticação

Slide 77

Slide 77 text

Aqui você também possui diversas alternativas.

Slide 78

Slide 78 text

Autenticação por query string, header, OAuth, Basic Auth, e muito mais.

Slide 79

Slide 79 text

Comece simples e mude a autenticação no futuro se for necessário.

Slide 80

Slide 80 text

O Rails possui seu próprio mecanismo de autenticação por token.

Slide 81

Slide 81 text

authenticate_or_request_with_http_token(realm, &block)

Slide 82

Slide 82 text

# app/controllers/application_controller.rb class ApplicationController < ActionController::Base before_action :require_authentication attr_reader :current_user def require_authentication authenticate_or_request_with_http_token do |token, options| @current_user = Authorizer.authorize(token) end end end

Slide 83

Slide 83 text

http localhost:3002/repos Authorization:"Token token=abc"

Slide 84

Slide 84 text

Exija HTTPS.

Slide 85

Slide 85 text

Codeplane::Application.configure do config.force_ssl = true end

Slide 86

Slide 86 text

http://www.cheapssls.com

Slide 87

Slide 87 text

Veracidade

Slide 88

Slide 88 text

Muitas APIs precisam garantir a veracidade das informações que estão sendo enviadas.

Slide 89

Slide 89 text

Assinatura

Slide 90

Slide 90 text

secret + timestamp + url + data

Slide 91

Slide 91 text

require "openssl" # Request time timestamp = Time.now # Requested url url = "https://codeplane.com/repos" # API secret secret = "MYSUPERSECRET" # POST data options = {name: "myrepo"} # Hashing algorithm digest = OpenSSL::Digest::Digest.new("sha512")

Slide 92

Slide 92 text

components = [url, secret, timestamp] components += options .keys .sort .reduce(components) do |buffer, key| buffer.concat [key, options[key]] end

Slide 93

Slide 93 text

signature = OpenSSL::HMAC.hexdigest( digest, secret, components.join("") )

Slide 94

Slide 94 text

https://gist.github.com/fnando/5885956b831ea2c9b31f

Slide 95

Slide 95 text

Caching

Slide 96

Slide 96 text

Além das técnicas de caching usuais da aplicação, você pode implementar HTTP caching.

Slide 97

Slide 97 text

O Rack::Cache permite usar HTTP caching com headers de tempo de expiração (Expires, Cache- Control) ou validação (Etag, Last- Modified). https://github.com/rtomayko/rack-cache

Slide 98

Slide 98 text

# config/initializers/middleware.rb Rails.configuration.middleware.tap do |middleware| # ... middleware.use ::Rack::Cache, :verbose => true, :metastore => "memcached://localhost:11211/meta", :entitystore => "memcached://localhost:11211/body" end

Slide 99

Slide 99 text

http http://localhost:3000/

Slide 100

Slide 100 text

HTTP/1.1 200 OK Cache-Control: max-age=0, private, must-revalidate Connection: close Content-Encoding: gzip Content-Type: application/json; charset=utf-8 ETag: "1358b2b2117fd019f60598f7a5514da2" Server: thin 1.5.1 codename Straight Razor Vary: Accept-Language,Accept-Encoding,Origin X-Request-Id: 4e7b55af-236c-4fcd-ae09-18d88e9019c0 X-Runtime: 0.071640

Slide 101

Slide 101 text

GZIP

Slide 102

Slide 102 text

Sirva compressão GZIP para quem entende.

Slide 103

Slide 103 text

http GET http://localhost:3000/ \ Accept-Encoding:none

Slide 104

Slide 104 text

http GET http://localhost:3000/ \ Accept-Encoding:gzip

Slide 105

Slide 105 text

HTTP/1.1 200 OK Cache-Control: max-age=0, private, must-revalidate Connection: close Content-Encoding: gzip Content-Type: application/json; charset=utf-8 ETag: "1358b2b2117fd019f60598f7a5514da2" Server: thin 1.5.1 codename Straight Razor Vary: Accept-Language,Accept-Encoding,Origin X-Request-Id: 4e7b55af-236c-4fcd-ae09-18d88e9019c0 X-Runtime: 0.071640

Slide 106

Slide 106 text

Finalizando...

Slide 107

Slide 107 text

O Rails é uma excelente alternativa para APIs.

Slide 108

Slide 108 text

Você tira todas as decisões que não são importantes da frente.

Slide 109

Slide 109 text

Obrigado!