Interpretarea API-urilor cu șine

În prezent, este o practică obișnuită să se bazeze foarte mult pe API-uri (interfețe de programare a aplicațiilor). Nu numai serviciile mari cum ar fi Facebook și Twitter le folosesc - API-urile sunt foarte populare datorită răspândirii unor cadre de tip client, cum ar fi React, Angular și multe altele. Ruby on Rails urmărește această tendință, iar cea mai recentă versiune prezintă o nouă caracteristică care vă permite să creați numai aplicații API. 

Inițial, această funcționalitate a fost împachetată într-o bijuterie separată numită șine-api, dar de la lansarea Rails 5, aceasta este acum parte a nucleului cadrului. Această caracteristică împreună cu ActionCable a fost probabil cea mai anticipată, deci astăzi o vom discuta.

Acest articol se referă la modul de creare a aplicațiilor Rails numai pentru API și explică modul de structurare a rutelor și controlerelor, de a răspunde cu formatul JSON, de a adăuga serializatori și de a configura CORS (Sharing Resource Sharing). Veți afla, de asemenea, despre unele opțiuni pentru securizarea API-ului și protecția acestuia împotriva abuzului.

Sursa pentru acest articol este disponibilă la GitHub.

Crearea unei aplicații API numai

Pentru a începe, rulați următoarea comandă:

șine noi RailsApiDemo --api

Se va crea o nouă aplicație Rails numai API numită RailsApiDemo. Nu uitați că sprijinul acordat --api opțiunea a fost adăugată numai în Rails 5, deci asigurați-vă că ați instalat această versiune sau o versiune mai nouă.

Deschide Gemfile și rețineți că este mult mai mic decât de obicei: pietre prețioase cafea șine, turbolinks, și Sass-șine sunt plecati.

 config / application.rb fișierul conține o linie nouă:

config.api_only = true

Aceasta înseamnă că Rails va încărca un set mai mic de middleware: de exemplu, nu există suporturi pentru cookie-uri și sesiuni. În plus, dacă încercați să generați o schemă, vizualizările și activele nu vor fi create. De fapt, dacă verificați vizualizari / aspecte director, veți observa că application.html.erb fișier lipsește, de asemenea.

O altă diferență importantă este că ApplicationController moșteneste de la ActionController :: API, nu ActionController :: Base.

Este destul de mult - totul, este o aplicație de bază Rails pe care ați văzut-o de mai multe ori. Acum, să adăugăm câteva modele pentru a avea ceva de lucru:

rails g model Numele utilizatorului: string rails g model Titlul postului: corp de caractere: text user: belongs_to rails db: migrate

Nimic nu se întâmplă aici: un post cu un titlu și un corp aparține unui utilizator.

Asigurați-vă că sunt înființate asociațiile corespunzătoare și oferă, de asemenea, niște verificări simple de validare:

modele / user.rb

 has_many: posturile validează: nume, prezență: true

modele / post.rb

 belongs_to: validează utilizatorul: titlu, prezență: valid valide: corp, prezență: adevărat

Sclipitor! Următorul pas este să încărcați câteva înregistrări de mostre în tabelele nou create.

Încărcarea datelor demo

Cea mai ușoară metodă de încărcare a datelor este folosirea funcției seeds.rb fișier în interiorul db director. Cu toate acestea, sunt leneș (cât de mulți programatori sunt) și nu doresc să se gândească la orice eșantion de conținut. Prin urmare, de ce nu profităm de bijuteria faker care poate produce date aleatorii de diferite tipuri: nume, e-mailuri, cuvinte hipster, texte "lorem ipsum" și multe altele.

Gemfile

grup: development do gem 'faker' end

Instalați bijuteria:

instalare pachet

Acum tweak seeds.rb:

db / seeds.rb

5.Permite utilizator = User.create (name: Faker :: Nume.name) user.posts.create (title: Faker :: Book.title, corp: Faker :: Lorem.sentence) sfarsit

În cele din urmă, încărcați datele:

șine db: semințe

Răspunzând cu JSON

Acum, bineînțeles, avem nevoie de niște rute și controlori pentru a realiza API-ul nostru. Este o practică obișnuită de a cuișa rutele API în cadrul api / cale. De asemenea, dezvoltatorii oferă de obicei versiunea API în cale, de exemplu api / v1 /. Mai târziu, dacă trebuie introduse unele modificări de rupere, puteți să creați pur și simplu un nou spațiu de nume (v2) și un controler separat.

Iata cum pot arata rutele tale:

config / routes.rb

namespace 'api' do namespace 'v1' face resurse: postări resurse: end users end

Aceasta generează rute ca:

api / v1 / posts # create api_v1_post GET / api / v1 / posts /: id (.: format) api / v1 / posts # show

Puteți utiliza a domeniu în loc de Spațiu de nume, dar în mod implicit va căuta UsersController și PostsController în interiorul controlere director, nu în interiorul controlere / api / v1, deci fii atent.

Creați api folder cu directorul imbricat v1 în interiorul controlere. Utilizați-l cu controlorii dvs.:

controlere / api / v1 / users_controller.rb

Modul Modul Api Modul V1 UtilizatoriController < ApplicationController end end end

controlere / api / v1 / posts_controller.rb

modul Modul Api Modul V1 PostsController < ApplicationController end end end

Rețineți că nu trebuie doar să cuipeți fișierul controlerului sub api / v1 calea, dar clasa în sine, de asemenea, trebuie să fie namespaced în interiorul Api și V1 module.

Următoarea întrebare este cum să răspundeți corespunzător cu datele în format JSON? În acest articol vom încerca aceste soluții: pietrele jBuilder și active_model_serializers. Deci, înainte de a trece la următoarea secțiune, aruncați-le în Gemfile:

Gemfile

gem 'jbuilder', '~> 2.5' gem 'active_model_serializers', '~> 0.10.0'

Apoi rulați:

instalare pachet

Utilizând gemul jBuilder

jBuilder este o bijuterie populară susținută de echipa Rails care oferă un simplu DSL (limbaj specific domeniului) care vă permite să definiți structurile JSON în vizualizările dvs..

Să presupunem că am vrut să afișăm toate postările atunci când un utilizator lovește index acțiune:

controlere / api / v1 / posts_controller.rb

 def index @posts = Post.order ('created_at DESC') sfârșit

Tot ce trebuie să faceți este să creați vizualizarea numită după acțiunea corespunzătoare cu .json.jbuilder extensie. Rețineți că vizualizarea trebuie plasată sub api / v1 de asemenea:

vizualizari / api / v1 / posturi / index.json.jbuilder

json.array! @posts do | post json.id post.id json.title post.title json.body post.body sfârșit

json.array! traversează @posts mulțime. json.id, json.title și json.body generează cheile cu numele corespunzător care stabilește argumentele ca valori. Dacă navigați la http: // localhost: 3000 / api / v1 / posts.json, veți vedea o ieșire similară cu aceasta:

"id": 1, "titlul": "Titlul 1", "corpul": "Corpul 1", "]

Dacă am vrea să afișăm autorul pentru fiecare post? E simplu:

json.array! @posts do | post json.id post.id json.title post.titul json.body post.body json.user face json.id post.user.id json.name post.user.name sfârșit sfârșit

Ieșirea se va schimba la:

"id": 1, "nume": "nume de utilizator"]

Conținutul mesajului .JBuilder fișierele sunt simple Ruby code, astfel încât să puteți utiliza toate operațiile de bază ca de obicei.

Rețineți că jBuilder acceptă paralele ca orice vizualizare obișnuită Rails, deci puteți spune și: 

json.partial! parțial: "posturi / postare", colecție: @posts, as:: post

și apoi creați vizualizari / api / v1 / posturi / _post.json.jbuilder fișier cu următorul conținut:

json.id post.id json.title post.titul json.body post.body json.user face json.id post.user.id json.name post.user.name sfarsit

Deci, după cum vedeți, jBuilder este ușor și convenabil. Cu toate acestea, ca o alternativă, puteți rămâne cu serializatorii, deci hai să le discutăm în secțiunea următoare.

Utilizarea serializatorilor

Gemul rails_model_serializers a fost creat de o echipă care inițial a gestionat șinele-api. După cum se precizează în documentație, rails_model_serializers aduce convenție asupra configurației generației dvs. JSON. Practic, definiți câmpurile care trebuie utilizate la serializare (adică generarea JSON).

Aici este primul serializator nostru:

serializers / post_serializer.rb

clasa PostSerializer < ActiveModel::Serializer attributes :id, :title, :body end

Aici afirmăm că toate aceste câmpuri ar trebui să fie prezente în JSON-ul rezultat. Acum, metode cum ar fi to_json și as_json apelat la o postare va folosi această configurație și va returna conținutul corespunzător.

Pentru ao vedea în acțiune, modificați index acțiune ca aceasta:

controlere / api / v1 / posts_controller.rb

indexul def @posts = Post.order ('created_at DESC') reda json: @posts end

as_json va fi automat apelat la @posts obiect.

Ce zici de utilizatori? Serializatorii vă permit să indicați relații, la fel ca și modelele. Mai mult, serialele pot fi imbricate:

serializers / post_serializer.rb

clasa PostSerializer < ActiveModel::Serializer attributes :id, :title, :body belongs_to :user class UserSerializer < ActiveModel::Serializer attributes :id, :name end end

Acum, când serializați postarea, acesta va conține în mod automat imaginea imbricată utilizator cheie cu numele și numele său. Dacă mai târziu creați un serializator separat pentru utilizatorul cu : id atribut exclus:

serializers / post_serializer.rb

clasa UserSerializer < ActiveModel::Serializer attributes :name end

atunci @ user.as_json nu va returna ID-ul utilizatorului. Încă, @ post.as_json va întoarce atât numele utilizatorului, cât și codul de identificare al acestuia, deci țineți minte.

Asigurarea API-ului

În multe cazuri, nu vrem ca nimeni să efectueze orice acțiune folosind API-ul. Așadar, să prezentăm un simplu test de securitate și să îi forțăm pe utilizatori să trimită jetoanele atunci când creează și șterge postări.

Tokenul va avea o durată de viață nelimitată și va fi creat după înregistrarea utilizatorului. Mai întâi de toate, adăugați un nou jeton coloana la utilizatori masa:

rails g migrare add_token_to_users token: string: index

Acest indice ar trebui să garanteze unicitatea, deoarece nu pot exista doi utilizatori cu același simbol:

db / migrate / xyz_add_token_to_users.rb

add_index: utilizatori,: token, unic: true

Aplicați migrarea:

șinele db: migrează

Acum adaugati before_save suna inapoi:

modele / user.rb

înainte_creați -> self.token = generate_token

generate_token metoda privată va crea un simbol într-un ciclu nesfârșit și va verifica dacă este unic sau nu. De îndată ce se găsește un jeton unic, returnați-l:

modele / user.rb

privat def generate_token buclă do token = SecureRandom.hex return token dacă User.exists? (token: token) end end

Puteți utiliza un alt algoritm pentru a genera tokenul, de exemplu bazat pe hash-ul MD5 al numelui utilizatorului și o anumită sare.

Înregistrare utilizator

Desigur, trebuie să permitem utilizatorilor să se înregistreze, pentru că în caz contrar, aceștia nu vor putea să obțină tokenul lor. Nu vreau să introduc în aplicația noastră nicio vizualizare HTML, așadar să adăugăm o nouă metodă API:

controlere / api / v1 / users_controller.rb

def create @user = User.new (user_params) dacă @ user.save render starea:: creat altceva render json: @ user.errors, status:: unprocessable_entity end end private def user_params params.require (: user) .permit (: nume) sfârșit

Este o idee bună să returnați coduri de stare HTTP relevante, astfel încât dezvoltatorii să înțeleagă exact ce se întâmplă. Acum puteți oferi fie un serializator nou pentru utilizatori, fie un stick .json.jbuilder fişier. Prefer această variantă (de aceea nu trec : JSON opțiune la face metoda), dar sunteți liber să alegeți oricare dintre ele. Rețineți, totuși, că tokenul nu trebuie sa fie întotdeauna serializate, de exemplu atunci când returnați o listă a tuturor utilizatorilor - ar trebui păstrată în siguranță!

vizualizari / api / v1 / utilizatori / create.json.jbuilder

json.id @ user.id json.name @ user.name json.token @ user.token

Următorul pas este să testați dacă totul funcționează corect. Puteți folosi fie răsuci comandați sau scrieți un cod Ruby. Deoarece acest articol este despre Ruby, voi merge cu opțiunea de codare.

Testarea înregistrării utilizatorului

Pentru a efectua o solicitare HTTP, vom folosi gemul Faraday, care oferă o interfață comună asupra mai multor adaptoare (implicit este Net :: HTTP). Creați un fișier Ruby separat, includeți Faraday și configurați clientul:

api_client.rb

solicitați clientul "faraday" = Faraday.new (url: 'http: // localhost: 3000') nu | config | config.adapter Faraday.default_adapter sfârșitul răspunsului = client.post do | req | req.url '/ api / v1 / req.headers utilizatorilor [' Content-Type '] =' application / json 'req.body =' user ': 

Toate aceste opțiuni sunt destul de explicative: alegem adaptorul implicit, setăm URL-ul solicitării la http: // localhost: 300 / api / v1 / users, schimbăm tipul de conținut în application / json, și să pună la dispoziție corpul cererii noastre.

Răspunsul serverului va conține JSON, așa că pentru al analiza o să folosesc bijuteria Oj:

api_client.rb

cereți clientul "oj" # aici ... pune Oj.load (răspuns.body) pune response.status

În afară de răspunsul analizat, afișăm și codul de stare pentru depanare.

Acum, puteți rula doar acest script:

ruby api_client.rb

și stoca semnul primit undeva - o vom folosi în secțiunea următoare.

Autentificarea cu Tokenul

Pentru a impune autentificarea token, authenticate_or_request_with_http_token poate fi utilizată. Este o parte din modulul ActionController :: HttpAuthentication :: Token :: ControllerMethods, deci nu uitați să îl includeți:

controlere / api / v1 / posts_controller.rb 

clasa PostsController < ApplicationController include ActionController::HttpAuthentication::Token::ControllerMethods #… end

Adăugați un mesaj nou before_action și metoda corespunzătoare:

controlere / api / v1 / posts_controller.rb 

before_action: autentifica, numai: [: crea,: distruge] # ... privat # ... def autentifica authenticate_or_request_with_http_token do | token, options | @user = User.find_by (token: token) sfârșitul final

Acum, dacă tokenul nu este setat sau dacă un utilizator cu astfel de token nu poate fi găsit, o eroare 401 va fi returnată, oprind acțiunea de la executarea.

Rețineți că comunicarea dintre client și server trebuie făcută prin HTTPS, deoarece altfel tokenurile pot fi ușor falsificate. Desigur, soluția oferită nu este ideală și în multe cazuri este preferabil să se utilizeze protocolul OAuth 2 pentru autentificare. Există cel puțin două pietre care simplifică foarte mult procesul de susținere a acestei caracteristici: Doorkeeper și oPRO.

Crearea unui mesaj

Pentru a vedea autentificarea în acțiune, adăugați crea acțiune la adresa PostsController:

controlere / api / v1 / posts_controller.rb 

def crea @post = @ user.posts.new (post_params) dacă @ post.save render json: @post, status:: creat altceva render json: @ post.errors, status:: unprocessable_entity end end

Folosim serializatorul aici pentru a afișa JSON-ul potrivit. @utilizator a fost deja stabilit în interiorul before_action.

Acum, testați totul folosind acest cod simplu:

api_client.rb

client = Faraday.new (url: 'http: // localhost: 3000') nu | config | config.adapter Faraday.default_adapter config.token_auth ('127a74dbec6f156401b236d6cb32db0d') sfârșitul răspunsului = client.post do | req | req.url '/ api / v1 / posts' req.headers ['Content-Type'] = 'application / json' req.body = ' "Text" "Sfârșit

Înlocuiți argumentul transmis către token_auth cu jetonul primit la înregistrare și executați scriptul.

ruby api_client.rb

Ștergerea unui mesaj

Ștergerea unui post se face în același mod. Adaugă distruge acțiune:

controlere / api / v1 / posts_controller.rb 

def distruge @post = @ user.posts.find_by (params [: id]) dacă @post @ post.destroy altceva face json: post: "not found", status:: not_found end end

Permitem utilizatorilor doar să distrugă posturile pe care le dețin. Dacă postarea este eliminată cu succes, codul de stare 204 (fără conținut) va fi returnat. Alternativ, puteți răspunde cu id-ul postului care a fost șters, deoarece va fi disponibil din memorie.

Iată bucata de cod pentru a testa această nouă caracteristică:

api_client.rb

răspuns = client.delete do | req | req.url '/ api / v1 / posts / 6' req.headers ['Content-Type'] = capăt 'application / json'

Înlocuiți id-ul postului cu un număr care funcționează pentru dvs..

Configurarea CORS

Dacă doriți să activați alte servicii web pentru a accesa API-ul dvs. (din partea clientului), CORS (Sharing Resource Sharing) trebuie să fie setat corect. Practic, CORS permite aplicațiilor web să trimită cereri AJAX către serviciile terților. Din fericire, există o bijuterie numită rack-cors care ne permite să setăm cu ușurință totul. Adăugați-o în Gemfile:

Gemfile

gem "rack-cors"

Instalați-l:

instalare pachet

Apoi asigurați configurația în interior config / initializatori / cors.rb fişier. De fapt, acest fișier este deja creat pentru dvs. și conține un exemplu de utilizare. Puteți găsi, de asemenea, o documentație destul de detaliată pe pagina bijuteriei.

Următoarea configurație, de exemplu, va permite oricui să acceseze API-ul dvs. utilizând orice metodă:

config / initializatori / cors.rb

Rails.application.config.middleware.insert_before 0, Rack :: Cors nu permite resurselor origini '*' '/ api / *', anteturi :: orice, metode: [: get,: post,: put,: patch, : șterge,: opțiuni,: cap] capăt sfârșit

Prevenirea abuzului

Ultimul lucru pe care îl voi menționa în acest ghid este cum să vă protejați API-ul de atacurile de abuz și de refuz al serviciilor. Există o bijuterie frumos numită rack-attack (creat de oameni de la Kickstarter) care vă permite să listați clienți negri sau listați în alb, preveniți inundarea unui server cu cereri și multe altele.

Puneți bijuteria înăuntru Gemfile:

Gemfile

gem "rack-atac"

Instalați-l:

instalare pachet

Apoi asigurați configurația în interiorul rack_attack.rb fișier inițializator. Documentația bijuteriei conține toate opțiunile disponibile și sugerează câteva cazuri de utilizare. Iată configurația de eșantioane care restricționează pe oricine, cu excepția dvs., accesul la serviciu și limitează numărul maxim de solicitări la 5 pe secundă:

config / initializatori / rack_attack.rb

clasa Rack :: Atac safelist ('allow from localhost') nu | req | # Solicitările sunt permise dacă valoarea returnată este truthy '127.0.0.1' == req.ip || ':: 1' == req.ip sfârșitul accelerației ('req / ip',: limit => 5,: period => 1.second) do | req | req.ip end end

Un alt lucru care trebuie făcut este includerea RackAttack ca middleware:

config / application.rb

config.middleware.use Rack :: Atac

Concluzie

Am ajuns la sfârșitul acestui articol. Din fericire, până acum vă simțiți mai încrezători în elaborarea API-urilor cu Rails! Rețineți că aceasta nu este singura opțiune disponibilă - o altă soluție populară care a fost în jur de ceva timp este cadrul de struguri, deci este posibil să fiți interesat și să îl verificați.

Nu ezitați să vă postați întrebările dacă ceva părea neclar pentru dvs. Vă mulțumesc că ați rămas împreună cu mine și că ați fost fericit de codat!

Cod