Moving Menu

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

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

ZACZYNAMY!

Mam nadzieję, że ostatnim razem pobraliście moją przykładową plikację z Github'a (jeżeli nie to zróbcie to szybko :). Należy pobrać kod aplikacji w C++ stąd oraz kod aplikacji RoR stąd. W tej części wykorzystamy drugi przykład z repozytorium C++.

Generowanie JSON w Railsach.

Zakomentujcie na chwilę całą metodę as_json z app/model/entry.rb (linie 3-14). Następnie uruchomcie lokalny serwer Ruby oraz przejdźcie na stronę http://localhost:3000/entries/1.json

{
id: 1,
name: "sample entry",
text: "sample text",
file: 
{
url: "/uploads/entry/file/1/4728b84afe507895df20497b6e5b0c21.jpg"
},
created_at: "2014-07-04T12:48:52.954Z",
updated_at: "2014-07-04T12:48:52.954Z"
}


Powyższy JSON został wygenerowany z użyciem domyślnej metody i pokazuje wszystkie pola danego modelu. W celu jego dostosowania należy nadpisać metodę as_json(options).

Ale jak wygenerować odpowiedź JSON? To naprawdę proste!

respond_to do |format|
      format.html
      format.json { render :json => @entry }
      format.xml  {render :xml => @entry}
end

Teraz zależności od sufiksu w adresie (.json, .xml albo brak dla htmla) Railsy dostarczą odpowiedź w odpowiednim formacie.

Przekazywanie plików binarnych wewnątrz JSON.

Jak mogliście zauważyć wcześniej domyślny JSON z Railsach przechowuje link do pliku w "plikowej" części odpowiedzi. To czego naprawdę potrzebujemy to umieszczenie całego pliku wewnątrz odpowiedzi! Niestety nie jest możliwe przechowywanie pliku binarnego wewnątrz JSON w czystej postaci. Z pomocą przychodzi tutaj kodowanie Base64, które pozwala na przekształcenie binarnego pliku w ciąg znaków. Ruby posiada wbudowaną metodę Base64.encode64 umozliwijącą zrobienie tego. Otworzenie pliku i przetworzenie go do Base64 za pomocą tej metody można wykonać za pomocą poniższego kodu:

def get_file_base64
    Base64.encode64(open(file.current_path){ |io| io.read })
end

Żeby mieć pewność ,że po pobraniu naszego JSON'a otrzymamy własciwy obraz bez żadnych uszkodzonych danych powinniśmy porównać sumy kontrolne odebranego pliku z tym zawartym wewnątrz JSON'a. W celu stworzenia sumy kontrolnej i umieszczenia jej wewnątrz JSON'a możemy użyć algorytmu SHA-2.

def generate_file_checksum
  Digest::SHA256.hexdigest self.get_file_base64
end

I wreczcie parsowanie formatu JSON w C++

Do sparsowania tego w prosty sposób możemy użyć biblioteki jsoncpp.

 

bool JsonParser::parseJson(const QString &pureJson)
{
    Json::Value root;
    Json::Reader reader;
    bool parsingSuccesful = reader.parse(pureJson.toStdString(), root);
    if(!parsingSuccesful)
        return false;
    entryId = root.get("id", -1).asInt();
    name = QString::fromStdString(root.get("name", "").asString());
    created_at = QDateTime::fromString(
                QString::fromStdString(root.get("created_at", "").asString()),
                Qt::ISODate);
    updated_at = QDateTime::fromString(
                QString::fromStdString(root.get("updated_at", "").asString()),
                Qt::ISODate);
    obtainImage(root["file"]);
    return true;
}

Jak widzicie parsowanie JSON'a z użyciem tej biblioteki jest naprawdę proste. Metoda ta zwraca prawdę jeżeli parsowanie się powiodło, a w innym razie fałsz.

 Jedyną problematyczna częścią w powyższym kodzie jest wywołanie root.get(std::string, std::string). PIerwszy argument wskazuje na nazwę elementu JSON, drugi natomiast jest domyślną wartością. W sytuacji, gdy wartość elementu nie zostanie znaleziona funkcja użyje wartości domyślnej.

Oddzielną kwestią jest parsowanie dat.

updated_at = QDateTime::fromString(
                QString::fromStdString(root.get("updated_at", "").asString()),
                Qt::ISODate);

Dlatego użyjemy tutaj funkcji fromString ponieważ daty w Railsach generowane są domyślnie w formacie ISO_8601. Biblioteka Qt jest dobrze przygotowania do obsługi tego formatu - jedyne co musimy to ustawić Qt::ISODate jako używany format.

Parsowanie obrazków zapisanych w kodzie Base64

void JsonParser::obtainImage(const Json::Value &fileRoot)
{
    QString base64encoded = QString::fromStdString(fileRoot.get("base64encoded", "").asString());
    QString checksum = QString::fromStdString(fileRoot.get("checksum", "").asString());
    if(!compareChecksum(base64encoded, checksum))
        file = QImage();
    else
        file = decodeImage(base64encoded);
}

Funkcja ta przyjmuje ciąg znaków zakodowany w Base64 oraz sumę kontrolną w naszego JSON'a. Zanim przekonwertujemy nasz ciąg znaków z powrotem do obrazka powinniśmy porównać sumy kontrolne. W celu wykonania tego możemy użyć biblioteki cryptopp.

 

bool JsonParser::compareChecksum(const QString &encodedImage, const QString &orginal) const
{
    CryptoPP::SHA256 hash;
    byte digest[ CryptoPP::SHA256::DIGESTSIZE ];
    std::string message = encodedImage.toStdString();
    hash.CalculateDigest( digest, (byte *)message.c_str(), message.length() );
    CryptoPP::HexEncoder encoder;
    std::string output;
    encoder.Attach( new CryptoPP::StringSink( output ) );
    encoder.Put( digest, sizeof(digest) );
    encoder.MessageEnd();
    return QString::fromStdString(output).toUpper() == orginal.toUpper();
}

Należy pamiętać, że w celu prawidłowego porównania używamy funkcji .toUpper() albo .toLower() z racji tego, że ciąg generowany przez Cryptopp jest opisywany wielkimi znakami, natomiast ciąg generowany przez Railsy małymi znakami.

Dekodowanie obrazka

QImage JsonParser::decodeImage(const QString &encodedImage)
{
    QByteArray imgData = QByteArray::fromBase64(encodedImage.toAscii());
    QImage img;
    img.loadFromData(imgData);
    return img;
}

Jak widzicie możemy tutaj użyć QByteArray, która konwertuje kod Base64 z powrotem do postaci binarnego pliku. Mozemy również użyć QPixmap, niestety użycie QPixmap poza głównym wątkiem naszej aplikacji może prowadzić do problemów.

Podsumowanie

Mam nadzieję, że poznaliście sposób w jaki można uzyskać i sparsować format JSON w aplikacji C++. W następnej części zajmiemy się metodą POST.

 

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