Dostęp do Rails API z poziomu C++ [Część 3: Zapytania POST]
W moich ostatnich dwóch wpisach omówiłem zapytania GET oraz parsowanie formatu JSON. W tym wpisie chciałbym pokazać, w jaki sposób przygotować zapytanie POST i stworzyć wpis wewnątrz naszej aplikacji Ruby.
Zanim zaczniemy zachęcam do pobrania przykładowego kodu, który stworzyłem na Githubie binar::apps': aplikacja w C++ oraz aplikacja w Ruby. Jeżeli pobieraliście te aplikacje wcześniej zalecam uaktualnienie ich do nowszych wersji.
Zacznijmy zatem!
Nasza aplikacja w C++ skomplikowała się odrobinę w momencie wykorzystania przeze mnie asynchronicznych metod - workerów. Rzucmy okiem jak wyglądają zapytania poprzez użycie poniższego prostego skryptu:
sudo tcpdump -s 0 -A -i lo0 'tcp port 3000 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'
Gdzie lo0 jest interfejsem pętli zwrotnej w systemie operacyjnym, a nasza aplikacja uruchomiona jest na porcie tcp/3000. Oczywiście będziecie musieli zmienić te ustawienia jeżeli chcecie w ten sposób sprawdzić rzeczywistą aplikację w Internecie. Poniżej wynik działania skryptu:
POST /entries HTTP/1.1 Host: localhost:3000 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:31.0) Gecko/20100101 Firefox/31.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: pl,en-US;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Referer: http://localhost:3000/entries/new Cookie: cookies_accepted=T; _ruszamysie_session=RVc3U1kyeWtoci9oMVhZT1lOQXp0WEh0R1BGTXdmcDM1RkNVNnpSQ1dsWER6NWRWNmdoZlgvclBwdGdPRTdBY29WdjY0ekhjZzA1aCtTcW5JeXlkd2Zpa0YvV25Ib3VzcTRSbWFXdzZTQ1FlbFZnaS8rdkpOS08yYXZuVy8zcituVk9KUUE1WTdwZkxNUlJDQnF3c2cyMDRpV29SaWRuMmg3MERMNi9EbGNqeUNKc3ZZbTdPdHRYdkRDbkpFZ1lHNFhscElGYjlrelAxZnpXWkQxR3JJeWw5QmIzbTd6aW9zbTdQUlhDUUppaVIzeFJoSGRQN2JDQU1vVGtQNm80MGI2Skw5MFU2ZVZ3aDZ1ZVZraFZXMTBrZGF2MERNcmhBMnUvT1p5OUJPK08yQklMWW5RWkRIUk55Z3lydk9wMGYrQWZzRmRBVE1ObFRSQk9ySCtZdnA3YXRxZjVtZWtnRkJjcmZsZGQrYWRWb0ZjdHdsZTdhV0VoT1dnWGJUbndNLS1BZ3lCN2FuWTA1eSs2VGU1WndsdFd3PT0%3D--771c7bff49ca8b8cb73b54b1b1e131010a7d78bb; remember_user_token=W1sxXSwiJDJhJDEwJG1VbDB4UGtMSjFmV0cvc2toMXZkQ2UiXQ%3D%3D--1438bb505f1e521ee078eddcb8ad038ffa3171a1; _api_session=WEZxVHlod0ptNGxkaG9jYmdDMUc0dEo1YXdOeTd4MDMwQ3N6UTI2RG9SL1RwQWx5dElxbWlhV1RZd0hLK3BZaFZJN0VhdCtLZ2dIdUhqc29pNUIxdlZURDVkQTdCTVYzQ3RXRjJkcWZqQmVtVXVLK2M1YTdYWXplQXJ4cjRzK2w1dVBLMW5QM2h5S0FkSCtIUG92cno5WUt2b2Jia2o0aDR5K0xvWEkwNjFhR0lkRDU4UzhzZjBRYVJzTENQYkxLLS1rak45WjUwNEZGTGtqeDRTVlRMZzlRPT0%3D--178fc9c960ce753c16eb69c9f74270257f614e4d Connection: keep-alive Content-Type: multipart/form-data; boundary=---------------------------475104419735770587885416283 Content-Length: 405174 -----------------------------475104419735770587885416283 Content-Disposition: form-data; name="utf8" ... -----------------------------475104419735770587885416283 Content-Disposition: form-data; name="authenticity_token" tjZJCxmxL+Flb+pc0YxYZ0eLCL43Vrc6ObI30EscrBk= -----------------------------475104419735770587885416283 Content-Disposition: form-data; name="entry[name]" sample entry -----------------------------475104419735770587885416283 Content-Disposition: form-data; name="entry[text]" sample text -----------------------------475104419735770587885416283 Content-Disposition: form-data; name="entry[file]"; filename="Zrzut ekranu 2014-08-27 o 09.38.54.png" Content-Type: image/png .PNG (Long Binary data) -----------------------------475104419735770587885416283 Content-Disposition: form-data; name="commit" Submit -----------------------------475104419735770587885416283--
Ten rodzaj zapytania POST nazywa się zapytaniem wieloczęściowym (multipart request). W celu stworzenia takiego zapytania wewnątrz naszej aplikacji C++ skorzystamy z QHttpMultiPart. Zapytania wieloczęściowe pozwalają np. na wgrywanie plików na serwer. Wystarczy przyjrzeć się przykładowi powyżej - wyraźnie widocznych jest 6 części zapytania.
Tworzenie prostego QHttpPart
Każda z części zawiera nagłówek (header) i ciało (body). Nagłówek przechowuje nazwę, typ itp. Z kolei w sekcji body zawarta jest właściwa treść. W celu stworzenia prostego QHttpPart (tesktowego, nie będącego częścią pliku) można skorzystać z poniższej funkcji.
QHttpPart DataSender::createPart(const QString &header, const QString &body) { QHttpPart part; part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(header)); part.setBody(body.toUtf8()); return part; }
W celu wywołania go we właściwy sposób należy spojrzeć na nagłówki wychwycone przez tcpdump, np. form-data; name="entry[name]" w celu wysłania wpisu w polu nazwy.
Tworzenie części pliku
Wysyłanie plików jest nieco bardziej skomplikowane od wysyłania czystego tekstu, ale ciągle relatywnie proste dzięki rozwiązaniom dostarczanym przez QT. Przyjrzyjmy się teraz nagłówkowi:
Content-Disposition: form-data; name="entry[file]"; filename="Zrzut ekranu 2014-08-27 o 09.38.54.png" Content-Type: image/png
Jak widać na powyższym przykładzie oprócz nazwy formularza należy również uwzględnic nazwę pliku, typ oraz plik sam w sobie.
QHttpPart DataSender::createFilePart(const QString &header, QFile * file) { QHttpPart part; QFileInfo info(*file); QString extension = info.suffix(); QString filename = info.fileName(); part.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(QString("image/%1").arg(extension))); part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant( QString("%1; filename="%2"").arg(header).arg(filename))); part.setBody(file->readAll()); return part; }
Zwróćcie uwagę, że w tym przykładzie wykorzystana została bardzo prosta metoda uzyskiwania informacji o typie pliku. MIanowicie wzięte zostało pod uwagę jego rozszerzenie.
Łączenie wszystkiego razem
Jak juz zostało wspomniane wcześniej - zapytanie typu multipart składa się z wielu części. Teraz należy je ze sobą połączyć. W tym celu użyta zostanie funkcja QHttpMultipart:
QHttpMultiPart * DataSender::prepareMultipart() { QHttpMultiPart * multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); multiPart->append(createPart("form-data; name="entry[name]"", this->name)); multiPart->append(createPart("form-data; name="entry[text]"", this->text)); QFile file(this->filepath); file.open(QIODevice::ReadOnly); if(file.isOpen()) multiPart->append(createFilePart("form-data; name="entry[file]"", &file)); return multiPart; }
Jak widać jest to bardzo proste - wystarczy stworzyć kilka części i połaczyć je ze soba w jedno duże zapytanie. W przypadku użycia aplikacji RoR w celu obsługi tego zapytania kolejność poszczególnych fragmentów zapytania nie ma znaczenia! Nie jestem pewiem jak to bedzie działać w przypadku użycia innych języków programowania i frameworków.
CSRF token
Railsy zabraniają domyślnie użycia metod post, delete i patch bez istnienia wcześniejszej sesji. Jest to swego rodzaju sposób ochrony przed atakami typu CSRF (Cross Site Request Forgery - przejęcie zapytania pomiędzy stronami). Więcej na ten temat można przeczytać tutaj.
Niestety w naszym przypadku konieczne będzie użycie metody POST w momencie, gdy nie ma aktywnej sesji. W celu wyłączenia ochrony przed CSRF dla jednej metody należy użyć skip_before_action :verify_authenticity_token wewnątrz kontrolera. Oczywiście można wyłączyć tę funkcję tylko na czas trwania pojedynczej zapytania poprzez dodanie only: [action] po przecinku.
Podsumowanie
Mam nadzieję, że ten wpis pomoże Wam w napisaniu własnej aplikacji, która używa zapytań POST. Dziękuję za uwagę!
BinarApps
Wpis został opublikowany dzięki współpracy z firmą BinarApps. Więcej wpisów o Ruby znajdziesz na ich blogu binarapps.com/blog
Szukasz szybkiego hostingu z dyskami SSD? Dobrze trafiłeś.
Pakiety hostingowe Kylos to sprawdzone i niezawodne rozwiązanie dla Twojej strony.
Darmowy okres próbny pozwoli Ci sprawdzić naszą ofertę, bez ponoszenia kosztów.
Sprawdź nas