Aby wykonac to zadanie musialem zapoznac sie z wieloma nowymi dla mnie informacjami. Wiecej czytalem (dokumentacja, kod zrodlowy bibliotek, informacje o http, emscripten) niz kodowalem lub debugowalem. Rezultat :
Kompilacja 1go pliku:
CXX=g++
CPPFLAGS=-I/home/mat/asio-1.18.2/include -I/home/mat/json/include -Wall -std=c++14 -fcompare-debug-second
LIBS=-lpthread
all: asio_test
asio_test: asio_test.cpp
$(CXX) -o $@ $^ $(CPPFLAGS) $(LIBS)
clean :
rm -f *.o asio_test
//#define ASIO_STANDALONE
#include "asio.hpp"
#include <iostream>
#include <sstream>
#include <utility>
#include <string>
#include <map>
#include <array>
//#define NDEBUG
#include <cassert>
#include <tuple>
#include <chrono>
#include "nlohmann/json.hpp"
using namespace std;
template<class Type>
inline bool is_valid(const int INDEX, const Type& object) {
static constexpr size_t SIZE = tuple_size<Type>{};
if (INDEX < 0 || INDEX >= SIZE) {
assert(0 && "Unavailable index ");
return false;
}
return true;
}
enum class Cache_control { no_store, no_cache };
ostream& operator<<(ostream& os, const Cache_control& x) {
static const array<string, 2> AVAILABLE_CACHE_CONTROLS = { "no-store", "no-cache" };
const int INDEX = static_cast<int>(x);
if (is_valid(INDEX, AVAILABLE_CACHE_CONTROLS))
return os << AVAILABLE_CACHE_CONTROLS[INDEX];
return os << AVAILABLE_CACHE_CONTROLS[0];
}
enum class Connection { keep_alive, close };
ostream& operator<<(ostream& os, const Connection& x) {
static const array<string, 2> AVAILABLE_CONNECTIONS = { "keep-alive", "close" };
const int INDEX = static_cast<int>(x);
if (is_valid(INDEX, AVAILABLE_CONNECTIONS))
return os << AVAILABLE_CONNECTIONS[INDEX];
return os << AVAILABLE_CONNECTIONS[1];
}
enum class Method { get, options, post };
ostream& operator<<(ostream& os, const Method& x) {
static const array<string, 3> AVAILABLE_METHODS = { "GET", "OPTIONS", "POST" };
const int INDEX = static_cast<int>(x);
if (is_valid(INDEX, AVAILABLE_METHODS))
return os << AVAILABLE_METHODS[INDEX];
return os << AVAILABLE_METHODS[0];
}
void process_document(const string & CURRENCY, const string & DOC);
string get_document(const string & HOST, const Method & METHOD, const string & DIRECTORY,
const Cache_control & CACHE_CONTROL, const Connection & CONNECTION);
class Asio_IO_Stream_Exception : public exception {
string msg {"!!! error on the socket stream: "};
public:
Asio_IO_Stream_Exception() {}
Asio_IO_Stream_Exception(const string& message) { msg += message; }
const char* what() const noexcept {
return msg.c_str();
}
};
int main() {
try {
const string CURRENCY = "PLN";
const string HOST = "www.floatrates.com";
const Method METHOD = Method::get;
const string DIRECTORY = "/daily/" + CURRENCY + ".json";
//const string DIRECTORY = "/"; // "/" is root (main page of host) and "" has result 400 Bad Request
const Cache_control CACHE_CONTROL = Cache_control::no_store;
const Connection CONNECTION = Connection::close;
const string DOC = get_document(HOST, METHOD, DIRECTORY, CACHE_CONTROL, CONNECTION);
process_document(CURRENCY, DOC);
return 0;
}
catch (const nlohmann::json::exception & e) {
cerr << "!!! Error json exception: " << e.what() << '\n';
}
catch (const Asio_IO_Stream_Exception & e) {
cerr << e.what() << endl;
}
catch (const asio::system_error &e) {
cerr << "!!! System Error ! Error code = " << e.code()
<< "\n Message: " << e.what();
return e.code().value();
}
catch (const exception & e) {
cerr << "Exception: " << e.what() << endl;
}
catch (...) {
cerr << "Unrecognized Exception: " << endl;
}
return 1;
}
struct float_rates_info {
string code;
double rate;
double inverse_rate;
};
inline void from_json(const nlohmann::json& json_data, float_rates_info& info) {
info.code = json_data.at("code").get<string>();
info.rate = json_data.at("rate").get<double>();
info.inverse_rate = json_data.at("inverseRate").get<double>();
}
void modify_document(string & doc);
void process_response_headers(asio::ip::tcp::iostream & socket_iostream, const string& HTTP_VERSION);
string get_document(const string & HOST, const Method & METHOD, const string & DIRECTORY,
const Cache_control & CACHE_CONTROL, const Connection & CONNECTION) {
asio::basic_socket_iostream<asio::ip::tcp> socket_iostream;
const string HTTP_VERSION("HTTP/1.1");
socket_iostream.connect(HOST, "http");
socket_iostream.expires_from_now(chrono::seconds(10));
socket_iostream << METHOD << " " << DIRECTORY << " " << HTTP_VERSION << "\r\n";
socket_iostream << "Host: " + HOST + "\r\n";
socket_iostream << "Cache-Control: " << CACHE_CONTROL << "\r\n";
socket_iostream << "Connection: " << CONNECTION << "\r\n\r\n";
socket_iostream.flush();
process_response_headers(socket_iostream, HTTP_VERSION);
stringstream strstream;
strstream << socket_iostream.rdbuf();
socket_iostream.close();
if (! socket_iostream)
throw Asio_IO_Stream_Exception(socket_iostream.error().message());
string result = strstream.str();
modify_document(result);
return result;
}
void process_response_headers(asio::ip::tcp::iostream & socket_iostream, const string& HTTP_VERSION) {
string http_version;
socket_iostream >> http_version;
cout << " http_version = " << http_version;
unsigned int status_code;
socket_iostream >> status_code;
cout << "\n status code of response = " << status_code;
string status_message;
getline(socket_iostream, status_message);
cout << "\n status message = " << status_message;
if (socket_iostream.fail() || http_version != HTTP_VERSION)
throw Asio_IO_Stream_Exception("Invalid response ");
// Process the response headers, which are terminated by a blank line.
string header;
while (getline(socket_iostream, header) && header != "\r")
cout << header << "\n";
cout << "\n";
}
void process_document(const string & CURRENCY, const string & DOC) {
stringstream strstream;
strstream.str(DOC);
nlohmann::json json_data;
strstream >> json_data;
map<string, float_rates_info> rates = json_data;
for (const pair<string, float_rates_info> &p : rates)
cout << " 1 " << CURRENCY << " = " << p.second.rate << " " << p.second.code << " and "
<< " 1 " << p.second.code << " = " << p.second.inverse_rate << " " << CURRENCY << endl;
}
void erase(string & result, const char C, const char A) {
for (unsigned i = 0; i < result.size(); i++) {
if (C == result[i] || A == result[i]) {
result.erase(result.begin() + i);
if (A == result[i]) {
result.erase(result.begin() + i);
while (i < result.size() && result[i] != A)
result.erase(result.begin() + i);
if (i < result.size() && A == result[i])
result.erase(result.begin() + i);
}
}
}
}
void modify_document(string & doc) {
size_t first = doc.find("{");
doc = doc.substr(first);
size_t last = doc.rfind("}");
doc = doc.substr(0, last + 1);
erase(doc, '\r', '\n');
}
Kompilacja 2go pliku:
CXX=g++
LIBXMLNAME = pugixml
LIBXML = ${LIBXMLNAME}/lib${LIBXMLNAME}.a
LIBXML_HEADER_DIR = /home/mat/pugixml-1.11/src
CPPFLAGS= -Wall -std=c++14 -fcompare-debug-second
EXEC = curlcpp_test
LPATH=-L/home/mat/curlcpp/build/src -L${LIBXMLNAME}
LIBS=-lcurlcpp -lcurl -l${LIBXMLNAME}
build: directories ${EXEC}.o ${LIBXML}
${CXX} ${EXEC}.o ${LPATH} ${LIBS} -o ${EXEC}
directories:
mkdir -p ${LIBXMLNAME}
${LIBXML}: ${LIBXMLNAME}/${LIBXMLNAME}.o
ar rcs ${LIBXML} ${LIBXMLNAME}/${LIBXMLNAME}.o
${LIBXMLNAME}/${LIBXMLNAME}.o: ${LIBXML_HEADER_DIR}/${LIBXMLNAME}.cpp
${CXX} -c $(CPPFLAGS) -I${LIBXML_HEADER_DIR} $< -o $@
%.o: %.cpp
${CXX} -c $(CPPFLAGS) -I${LIBXML_HEADER_DIR} -I/home/mat/curlcpp/include $< -o $@
clean:
rm -rf ${LIBXMLNAME} *.o ${EXEC}
#include <iostream>
#include <string>
#include <array>
#include "curl_header.h"
#include "curl_easy.h"
#include "curl_exception.h"
#include "curl_ios.h"
#include "pugixml.hpp"
using namespace curl;
using namespace pugi;
using namespace std;
bool process_xml_document(const char * STR);
string get_xml_document(const char* URL);
int main() {
try {
static const array<const char*, 2> URL_ARRAY = { "http://api.nbp.pl/api/exchangerates/tables/a/",
"http://api.nbp.pl/api/exchangerates/tables/b/" };
string xml_doc;
for (const char* URL : URL_ARRAY) {
xml_doc = get_xml_document(URL);
if (xml_doc.empty()) {
cerr << "Error: Xml document can not be empty\n";
return 1;
}
if (! process_xml_document(xml_doc.c_str()))
return 1;
}
return 0;
}
catch (const exception & e) {
cerr << "Exception: " << e.what() << endl;
}
catch (...) {
cerr << "Unrecognized Exception: " << endl;
}
return 1;
}
stringstream get_document(const char* URL, const curl_header & HEADER) {
stringstream stream;
try {
curl_ios<stringstream> writer(stream);
curl_easy easy(writer);
easy.add<CURLOPT_HTTPHEADER>(HEADER.get());
easy.add<CURLOPT_URL>(URL);
easy.add<CURLOPT_TIMEOUT>(10);
easy.add<CURLOPT_DNS_CACHE_TIMEOUT>(0);
easy.add<CURLOPT_FOLLOWLOCATION>(1);
easy.perform();
}
catch (const curl_easy_exception & e) {
curlcpp_traceback errors = e.get_traceback();
e.print_traceback();
}
return stream;
}
string get_xml_document(const char* URL) {
curl_header header;
header.add("Accept: application/xml");
stringstream stream = get_document(URL, header);
return stream.str();
}
inline bool load_validate_xml(xml_document& xml_doc, const char * STR) {
xml_parse_result result = xml_doc.load_string(STR);
if (result.status != xml_parse_status::status_ok) {
cerr << "XML [" << STR << "] parsed with errors, attr value: ["
<< xml_doc.child("node").attribute("attr").value() << "]\n";
cerr << "Error description: " << result.description() << "\n";
cerr << "Error offset: " << result.offset << " (error at [..." << STR[result.offset] << "]\n\n";
}
return result;
}
bool print(const xpath_node_set & CURRENCIES, const xpath_node_set & CODES, const xpath_node_set & RATES) {
const size_t SIZE = CURRENCIES.size();
if (SIZE != CODES.size() || SIZE != RATES.size()) {
cerr << "Different size of xpath node sets:\n";
cerr << "CURRENCIES = " << SIZE << "\n";
cerr << "CODES = " << CODES.size() << "\n";
cerr << "RATES = " << RATES.size() << "\n";
return false;
}
for (size_t i = 0; i < SIZE; i++) {
xpath_node code_xpathnode = CODES[i];
xml_node code_xmlnode = code_xpathnode.node();
xml_text code_xmltext = code_xmlnode.text();
xpath_node rate_xpathnode = RATES[i];
xml_node rate_xmlnode = rate_xpathnode.node();
xml_text rate_xmltext = rate_xmlnode.text();
double rate = stod(rate_xmltext.as_string());
double inverse_rate = 1.0 / rate;
cout << " 1 PLN = " << inverse_rate << " " << code_xmltext.get() << " and"
<< " 1 " << code_xmltext.as_string() << " = " << rate << " PLN\n";
}
return true;
}
bool process_xml_document(const char * STR) {
xml_document doc;
if (! load_validate_xml(doc, STR))
return false;
try {
static constexpr char* CURRENCY_NODE = "/ArrayOfExchangeRatesTable/ExchangeRatesTable/Rates/Rate/Currency";
static constexpr char* CODE_NODE = "/ArrayOfExchangeRatesTable/ExchangeRatesTable/Rates/Rate/Code";
static constexpr char* MID_RATE_NODE = "/ArrayOfExchangeRatesTable/ExchangeRatesTable/Rates/Rate/Mid";
const xpath_node_set CURRENCIES = doc.select_nodes(CURRENCY_NODE);
const xpath_node_set CODES = doc.select_nodes(CODE_NODE);
const xpath_node_set RATES = doc.select_nodes(MID_RATE_NODE);
return print(CURRENCIES, CODES, RATES);
}
catch (xpath_exception const & e) {
cerr << e.result().description() << endl;
return false;
}
}