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

Michał Brodecki 2015-05-07
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

Mogą Cię również zainteresować

comments powered by Disqus