#4 Jak zbudować REST API dla prostego formularza kontaktowego wykorzystujący AJAX

https://www.youtube.com/watch?v=USaxePzTmTs

Gdy nie chcemy opierać naszej aplikacji o jeden wielki monolity, chcemy rozdzielić odpowiedzialność kodu, a także ułatwić przyszłą integracje z innymi aplikacjami.
Nic  innego nam  nie pozostaje , jak zbudować  naszą aplikację w oparciu o mikroserwisy. Czyli  małe niezależne programy, które zasilają dużą aplikacje. Plusy i minusy takiego rozwiązania to:

+ Lepsza elastyczność przy podziale prac programistycznych.
+ Wdrążenie nowej wersji może zostać wykonane do pojedynczego komponentu(serwisu).
+ Można lepiej monitorować i daje lepszy podgląd sytuacji na to gdzie coś może działać źle.  
+ Elastyczność w utrzymaniu, migrowanie małych kawałków jest łatwiejsze.

- Większy nakład pracy na oprogramowanie.
- Aplikacja musi być odporna na błędy sieciowe, większy nakład pracy na drożność komunikacji.
- Jest potrzebny większy zakres wiedzy przy wdrażaniu takiej aplikacji.

  Kolorowo nie jest, przede wszystkim  ten model wymaga od nas  większego nakładu pracy,  ale  za to daje nam ogromną elastyczność.  Chcąc oprzeć się o ten model przy budowie formularza  kontaktowego najlepiej  jest sięgnąć po  REST API i AJAX.

1.1 Co to w ogóle jest ten cały REST API i AJAX?

REST API – Program/mikroserwis odpowiadający na żądania wysłane przez aplikacje w celu uzyskania metadanych/treści/dostępu z danego zasobu(uri)  i w zależności od zaprogramowanej logiki w API, odpowiada ona jednorazową informacją, która kończy całą komunikacje( asynchroniczność, bez stanowość). Komunikacja odbywa się przez protokół HTTP, zasoby mają być jednoznaczne(uri), dobrym zwyczajem jest wysłania danych w dobrze znanym formacie danych(JSON lub XML).

AJAX – Zbiór funkcji do synchronicznego i asynchronicznego pobierania danych przez javascript, kod jest wykonywany w przeglądarce, nie potrzebuje odświeżenia się strony w celu manipulacji elementów w załadowanej stronie.  

2.1 Cel

Mamy za zadnie stowrzyć:

Formularz kontaktowy zwierający 4 pola( imię, email, wiadomość, pytanie), wszystkie pola muszą przejść standardowa walidacje pól z jquery oraz częściową  w api, pole pytanie ma służyć jako pole sprawdzające czy nie jesteśmy robotem, jest one sprawdzane przez api. Po poprawnej walidacji w przeglądarce, formularz ma wysłać dane do api, a api  ma odpowiedzieć  przeglądarce o statusie jego żądania, w przypadku poprawnej walidacji ma wysłać także maila.

2.2 Architektura aplikacji

Do wykonania założonego celu potrzebujemy stworzyć  3 rzeczy:

  • Statyczny formularz  w html do którego będą wprowadzane  dane. 
  • Skrypt javascriptowy, który po stronie przeglądarki  będzie  wstępnie walidował dane z formularza, wysłał je do api i na podstawie odpowiedzi od api, generował odpowiednią akcję w formularzu.  
  • Api generujące kod odpowiedzi w zależności od żądania i podanych danych:

Czyli jak komunikacja będzie wyglądać? 

Przykładowe curle:

## GET /api/contact 200 curl -X GET localhost/api/contact -H 'content-type: application/json; charset=UTF-8' Odpowiedz: {"state": "200", "reason": "Validation Ok, your request added"} ## POST /api/contact 511 curl -X POST localhost/api/contact -H 'content-type: application/json; charset=UTF-8' -d '{"name":"Blog","email":"ilovespam@uchacz.it","answer":"5","msg":"ada to zaba"}' Odpowiedz: {"state":"511","reason":"bad answer"} ## POST /api/contact 200 curl -X POST localhost/api/contact -H 'content-type: application/json; charset=UTF-8' –d '{"name":"Blog","email":"ilovespam@uchacz.it","answer":"5","msg":"ada"}' Odpowiedz: {"state": "200", "reason": "Validation Ok, your request added"}

2.3 Wykorzystane technologie:

Falcon 1.4.1 – Biblioteka pythonowa do szybkiego budowania API.
Gunicorn 19.6.0 – Serwer http dla skryptów napisanych  w pythonie oparte o biblioteke WSGI .
jQuery v3.3.1 – Bibliotek javascriptowa, posiada najbardziej potrzebne w swiecie funckje, w tym standardowa walidacja pól i żądania  ajaxowe.
Boostrap v4.1.3 – Bibliotek stylów css, żeby świat był jeszcze piękniejszy 🙂
fontawsome 5.3.1  – Bibliotek ikonek w css3 lub svg, żeby świat był jeszcze bardziej piękniejszy :))

Także cały projekt jest realizowany w oparciu o system Debian Stretch(9.5), ale z powodzeniem ten projekt  może zostać uruchomiony pod windowsem 🙂 

3. Realizacja:

3.1 Formularz HTML:
Prosty form, wzbogacony o  klasy bootstrapa i fontawsome( gotowe schematy css), najważniejsze jego elementy to:

  •  id=”alert_contact_text” : to tutaj będą sie pojawiały komunikaty odpowiedzi od api.   
  • button type=”submit”…id=”button_contact” : ważny jest typ submit,  przy tym typie biblioteka jquerty automatycznie będzie walidowała w przeglądarce pola input według podanego typu przed wysłaniem ich do api.
  • id button_loadinganswer_loading – w tych klasach będzie się pojawiało kółeczko( spin jest z fontawsome) w momencie oczekiwania na odpowiedz z api. 
<!DOCTYPE html> <html> <head> <script src="js/jquery-3.3.1.min.js"></script> <script type="text/javascript" src="contact.js"></script> <script type="text/javascript" src="js/bootstrap.min.js"></script> <link href="css/bootstrap.min.css" rel="stylesheet"> <link rel="stylesheet" href="css/fontawesome-free-5.3.1-web/css/all.min.css"> </head> <body> <div class="cotainer jumbotron mx-auto"> <div class="alert alert-primary" id="alert_contact" role="alert" style="display: none;"> <i id="alert_contact_text"></i> </div> <form id="form_contact" class="form-vertical" role="form" autocomplete="on" method="POST" action="/api/contact"> <div class="form-group"> <label for="name">Imie:</label> <div class="input-group"> <input id="name" name="name" type="text" class="form-control" placeholder="Leszek Uchacz" required="required" /> </div> </div> <div class="form-group"> <label for="email">E-mail:</label> <div class="input-group"> <input id="email" name="email" type="email" class="form-control" placeholder="ilovespam@uchacz.it" required="required" /> </div> </div> <div class="form-group"> <label id="label_answer"><i class="fa fa-spinner fa-spin" id="answer_loading"></i></label> <div class="input-group"> <input id="answer" name="answer" type="number" class="form-control" placeholder="66" required="required" /> </div> </div> <div class="form-group"> <label for="name">Wiadomosc:</label> <textarea id="msg" name="msg" class="form-control" rows="12" cols="25" required="required" placeholder="placeholder"></textarea> </div> <button type="submit" class="btn btn-primary text-center pull-right" id="button_contact"> <i class="fa fa-spinner fa-spin" id="button_loading" style="display: none;" ></i><i id="button_text">Wyslij</i> </button> </form> </div> </html>
contact.html

3.2 Javascript:
Wykorzystujemy tutaj dwa wydarzenia:

  • ready – kiedy element będzie już gotowy do użycia( strona została namalowana przez przeglądarkę).
  • submit – klikniecie przycisku przez użytkownika “Wyslij”.

w obu przypadkach jest generowane zapytanie do api przez funckje ajax i zależnie o statusu odpowiedzi jest generowana informacja dla użytkownika.

$(document).ready(function() { // GET contact $("#label_answer").ready(function(event) { $('#answer_loading').show(); $.ajax({ url: '/api/contact', type: 'GET', timeout:8000, data: '', cache: false, contentType: 'application/json; charset=UTF-8', success: function(response){ $("#label_answer").html(response.question); $('#answer_loading').hide(); }, error: function(){ $('#answer_loading').hide(); $("#label_answer").html("API ERROR"); } }); }); // POST $("#form_contact").submit(function(event) { event.preventDefault(); var json = {} $.each($('#form_contact').serializeArray(), function() { json[this.name] = this.value; }); json = JSON.stringify(json); $('#button_loading').show(); $.ajax({ url: '/api/contact', type: 'POST', timeout:8000, dataType: 'json', data: json, cache: false, contentType: 'application/json; charset=UTF-8', success: function(response){ $('#button_loading').hide(); $.each($('#form_contact').serializeArray(), function() { document.getElementById(this.name).classList.remove("is-invalid"); document.getElementById(this.name).classList.add("is-valid"); document.getElementById(this.name).disabled=true; }); document.getElementById('button_contact').disabled=true; document.getElementById('button_contact').classList.remove("btn-primary"); document.getElementById('button_contact').classList.remove("btn-warning"); document.getElementById('button_contact').classList.add("btn-success"); $('#button_text').html("Done"); $('#alert_contact').show(); $('#alert_contact_text').html("Your request added!"); }, error: function(xhr){ if (xhr.status == 511){ document.getElementById('answer').classList.add("is-invalid"); $('#button_loading').hide(); document.getElementById('button_contact').classList.remove("btn-primary"); document.getElementById('button_contact').classList.remove("btn-success"); document.getElementById('button_contact').classList.add("btn-warning"); $('#alert_contact').show(); $('#alert_contact_text').html("Bad answer!"); }else{ $('#alert_contact').show(); $('#alert_contact_text').html("API ERROR"); $('#button_loading').hide(); $("#button_text").html("API ERROR"); } } }); }); });
contact.js

3.3 Python API:
Falcon przede wszystkim daje ogromną przejrzystość kodu, przy normalnym python cgi  byłoby tu  x 4 więcej linijek kodu i bardziej zawiłej logiki, a tutaj:  

  • definiujemy klasę
  • budujemy logikę aplikacje dla danego żądania(POST/GET)
  • podpinamy ją pod wskazany zasób
  • Uruchamiamy! 
import falcon import smtplib question = "2+2=?" answer = "4" class ContactResource: def on_get(self, req, resp): response = { 'state': ( "200" ), 'question': question } resp.media = response def on_post(self,req,resp): if req.media['answer'] != answer: resp.media = { 'state': "511" , 'reason': 'bad answer'} resp.status = falcon.HTTP_511 elif not req.media['name'] or not req.media['msg'] or not req.media['email']: resp.media = { 'state': "512" , 'reason': 'name or msg or email is emapty'} resp.status = falcon.HTTP_512 else: smtplib.SMTP('localhost').sendmail( 'ilovespam@uchacz.it', 'ilovespam@uchacz.it', req.media['name']+"\n "+ req.media['email']+"\n "+req.media['msg']) resp.media = { 'state': "200" , 'reason': 'Validation Ok, your request added'} api = falcon.API() api.add_route('/api/contact', ContactResource())
app.py

W przykładzie jako serwer smtp został użyty smarthost skonfigurowany na localhoscie, który dopuszcza takie użycie przesłanie wiadomośći, bez problemu można go zmienić na smtp gmaila. 

4. Uruchomienie 

Dla plików html/css/js może być jakikolwiek serwer http( Apache2, NginX, Python, IIS):
Natomiast program napisany w bibliotece Falcon  muszą zostać uruchomiony przez serwer http dla skryptów WSGI,  jednym z nich jest gunicorn:

apt-get install python-pip //debian pip install gunicorn pip install falcon pip install json cd api gunicorn app:api

Wynik:

5. Repo dla zadania

TODO: za niedługo, muszę zrobić porządki w repo 🙂 

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

This site uses Akismet to reduce spam. Learn how your comment data is processed.