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

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

Mogą Cię również zainteresować

comments powered by Disqus