Î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.
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:
has_many: posturile validează: nume, prezență: true
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.
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.
grup: development do gem 'faker' end
Instalați bijuteria:
instalare pachet
Acum tweak 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
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:
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.:
Modul Modul Api Modul V1 UtilizatoriController < ApplicationController end end end
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:
gem 'jbuilder', '~> 2.5' gem 'active_model_serializers', '~> 0.10.0'
Apoi rulați:
instalare pachet
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:
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:
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.
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:
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:
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:
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:
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.
Î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:
add_index: utilizatori,: token, unic: true
Aplicați migrarea:
șinele db: migrează
Acum adaugati before_save
suna inapoi:
î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:
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.
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:
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ță!
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.
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:
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:
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.
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:
clasa PostsController < ApplicationController include ActionController::HttpAuthentication::Token::ControllerMethods #… end
Adăugați un mesaj nou before_action
și metoda corespunzătoare:
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.
Pentru a vedea autentificarea în acțiune, adăugați crea
acțiune la adresa PostsController
:
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:
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 post se face în același mod. Adaugă distruge
acțiune:
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ă:
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..
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:
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ă:
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
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:
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ă:
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.middleware.use Rack :: Atac
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!