Moving Menu

Dostęp do Rails API z poziomu C++ [Część 3: Zapytania POST]

corner Michał Brodecki
Michał Brodecki
2015-05-07
corner 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 www.binarapps.com/blog

comments powered by Disqus

Powiązane

photo corner
Pięć najlepszych gemów Ruby, które pokochasz!

Każdy programista jest inny - każdy posiada swój ulubiony edytor kodu i system operacyjny. Każdy słucha innego rodzaju muzyki w czasie pracy, bądź nie słucha jest wcale. Pomimo różnych osobowości i preferencji wszyscy posiadamy jedną wspólną rzecz, która nazywa się "Gemfile". Po przejrzeniu moich ostatnich kilku projektów zauważyłem, że kilka gemów powtarza się dla każdej z aplikacji. Chciałbym przedstawić Wam pięć gemów, które pokochacie.

Czytaj więcej
photo corner
Pięć stron, które pomogą Ci stać się lepszym programistą Ruby

Programowanie to nie tylko kwestia wykonywania swojej pracy. Ja widzę to jako nigdy nie kończące się zadanie w celu samodoskonalenia się. Musisz nadążyć nie tylko za wiadomościami odnośnie bezpieczeństwa, ale również trzeba poznawać różnorodne koncepcje i nowe rozwiązanie, które pomogą Ci stać się lepszym oraz lepiej rozumieć to co robisz. W dobie Internetu jest to prostsze niż kiedykolwiek. Można znaleźć wiele źródeł informacji - mniej lub bardziej wartościowych, ale każde z nich może dać dobre perspektywy albo pomysły do pracy. W niniejszym artykule przedstawiam pięć najlepszych według mnie miejsc do poszukiwania nowości w świecie Ruby.

Czytaj więcej
photo corner
Dostęp do Rails API z poziomu C++ [Część 2: Parsowanie formatu JSON]

W moim ostatnim wpisie stworzyłem prostą metodę GET. Teraz pokażę w jaki sposób przygotować Railsy do wysyłania odpowiedzi JSON po stronie serwera oraz w jaki sposób parsować je wewnątrz aplikacji C++.

Czytaj więcej