I am new to C++ REST ('Casablanca'). I read the tutorial here. After than, I took a sample code from there and tried to run it on my machine.
Below is the code
std::map<utility::string_t, utility::string_t> dictionary;
void handle_get(http_request request)
{
TRACE(L"\nhandle GET\n");
web::json::value::field_map answer;
for (auto const & p : dictionary)
{
answer.push_back(std::make_pair(json::value(p.first), json::value(p.second)));
}
request.reply(status_codes::OK, json::value::object(answer));
}
int main()
{
http_listener listener(L"http://127.0.0.1:8080/stockData");
listener.support(methods::GET, handle_get);
return 0;
}
In this code, I am getting error as below
I checked the header file json.h and could not find a member (class/struct) named field_map
Please help
I think below code can replace your code and should be compile latest stable version cpprestsdk v2.8.0
std::map<utility::string_t, utility::string_t> dictionary;
void handle_get(http_request request)
{
TRACE(L"\nhandle GET\n");
json::value obj;
for ( auto const & p : dictionary )
{
obj[p.first] = json::value::string(p.second);
}
// this is just for debugging
utility::stringstream_t stream;
obj.serialize(stream);
std::wcout << stream.str();
request.reply( status_codes::OK, obj);
}
Related
The following piece of test code is causing me trouble. It compiles ok and tests are passed, but when debugging and trying to step into function AddPizza in line (*) or (**) , it takes me to allocator.h to line allocator() throw() { } and then it continues below. So it doesn't take me inside the method to inspect if everything is all right. This doesn't happen for instance with the previous line with the method AddIngredient. What is going on, is there something wrong with my implementation of AddPizza or some other method that is causing this behavior?
By the way I am using Qtcreator on Windows 10.
TEST(TestPizzeria, TestPizza)
{
Pizzeria pizzeria;
try
{
pizzeria.AddIngredient("Tomato", "Red berry of the plant Solanum lycopersicum", 2);
pizzeria.AddIngredient("Mozzarella", "Traditionally southern Italian cheese", 3);
pizzeria.AddPizza("Margherita", vector<string> { "Tomato", "Mozzarella" });//(*)steping into it takes me to allocator.h to line `allocator() throw() { }` and then it continues below
pizzeria.AddPizza("Marinara", vector<string> { "Tomato" });
}
catch (const exception& exception)
{
FAIL();
}
try
{
pizzeria.AddPizza("Margherita", vector<string> { "Tomato", "Mozzarella" });// (**)also here
FAIL();
}
catch (const exception& exception)
{
EXPECT_THAT(std::string(exception.what()), Eq("Pizza already inserted"));
}
}
I report here both AddPizza and AddIngredient methods and all necessary methods just in case:
class Ingredient {
public:
string Name;
int Price;
string Description;};
class Pizza {
vector<Ingredient> ingredients;
public:
string Name;
void AddIngredient(const Ingredient& ingredient){ingredients.push_back(ingredient);}
};
class Pizzeria {
map<string, Ingredient> mapNameToIngredient;
map<string, Pizza> mapNameToPizza;
void AddPizza(const string &name, const vector<string> &ingredients)
{
if(mapNameToPizza.find(name) != mapNameToPizza.end())
{
throw runtime_error("Pizza already inserted");
}
else
{
Pizza pizza;
pizza.Name = name;
vector<string> ingredientss = ingredients;
for(vector<string>::iterator it = ingredientss.begin(); it != ingredientss.end(); it++)
{
Ingredient ingredient;
ingredient = FindIngredient(*it);
pizza.AddIngredient(ingredient);
}
mapNameToPizza[name] = pizza;
}
}
void AddIngredient(const string &name, const string &description, const int &price)
{
if(mapNameToIngredient.find(name) != mapNameToIngredient.end())
{
throw runtime_error("Ingredient already inserted");
}
else
{
Ingredient ingredient;
ingredient.Name = name;
ingredient.Price = price;
ingredient.Description = description;
mapNameToIngredient[name] = ingredient;
}
}
const Ingredient &FindIngredient(const string &name) const
{
auto it = mapNameToIngredient.find(name);
if(it != mapNameToIngredient.end())
{
return it->second;
}
else
{
throw runtime_error("Ingredient not found");
}
}
};
GTest use macros and global variables heavily, it's the logic of the GTest framework itself, if you try debugging with step-in, you will most likely enter the assist code other than your test code.
So it's recommended to add a breakpoint in the first line of TEST body, as #1201ProgramAlarm mentioned in the comment.
You can use g++ -E to see the code after preprocessing of the original c++ code:
#include <gtest/gtest.h>
TEST(foo, bar) { ASSERT_EQ(true, true); }
Even for this one-line test, we get one 73533 lines file after preprocessing. I have extracted the tail part and removed some file name and line number information, then we get code snippet as below(It may be different from yours since the compiler and GTest version may be different)
static_assert(sizeof("foo") > 1, "test_suite_name must not be empty");
static_assert(sizeof("bar") > 1, "test_name must not be empty");
class foo_bar_Test : public ::testing::Test {
public:
foo_bar_Test() {}
private:
virtual void TestBody();
static ::testing::TestInfo* const test_info_ __attribute__((unused));
foo_bar_Test(foo_bar_Test const&) = delete;
void operator=(foo_bar_Test const&) = delete;
};
::testing::TestInfo* const foo_bar_Test ::test_info_ =
::testing::internal::MakeAndRegisterTestInfo(
"foo", "bar", nullptr, nullptr,
::testing::internal::CodeLocation("a.cpp", 3),
(::testing::internal::GetTestTypeId()),
::testing::internal::SuiteApiResolver<
::testing::Test>::GetSetUpCaseOrSuite("a.cpp", 3),
::testing::internal::SuiteApiResolver<
::testing::Test>::GetTearDownCaseOrSuite("a.cpp", 3),
new ::testing::internal::TestFactoryImpl<foo_bar_Test>);
void foo_bar_Test ::TestBody() {
switch (0)
case 0:
default:
if (const ::testing::AssertionResult gtest_ar =
(::testing::internal::EqHelper::Compare("true", "true", true,
true)))
;
else
return ::testing::internal::AssertHelper(
::testing::TestPartResult::kFatalFailure, "a.cpp", 3,
gtest_ar.failure_message()) = ::testing::Message();
}
We can see that a class is defined by the TEST macro and a global variable is defined, so a direct step in may goes into GTest's internal code or generated code from GTest's macro.
I am on the latest commit of spdlog (there is an issue regarding std output, apparently resolved), and am switching my output from std::cout to spdlog.
My google tests redirect std::cout so I can check the output of stub functions:
class MyTest : public testing::Test
{
protected:
void SetUp() override
{
sbuf = std::cout.rdbuf();
std::cout.rdbuf(buffer.rdbuf());
auto console = spdlog::stdout_color_mt("console");
auto err_logger = spdlog::stderr_color_mt("stderr");
}
void TearDown() override
{
std::cout.rdbuf(sbuf);
}
std::stringstream buffer;
std::streambuf* sbuf;
}
then use as follows inside a test;
doSomethingThatWritesToStdOut();
std::string teststr = buffer.str();
EXPECT_TRUE(teststr.find("Some output string") != std::string::npos);
This doesn't work when I change the content of doSomethingThatWritesToStdOut to
spdlog::get("console")->debug("Some output string\n");
The teststr value is empty..
If I do the following
spdlog::get("console")->debug("Some output string\n");
std::cout << "Some output string\n";
Then I can see one instance of "Some output string" in teststr. How can I capture the output of this logger (or change the logger) so I can test in google tests?
As I've mentioned in the comments, I had thought spdlog output to std::cout due to an earlier issue, but in fact that was related to stdout.. (facepalm)
This is nice and easy, it turns out! By using an ostream_sink, the output can be sent to a specified ostream;
I set a logger up in my test SetUp() function as follows
auto ostream_logger = spdlog::get("gtest_logger");
if (!ostream_logger)
{
auto ostream_sink = std::make_shared<spdlog::sinks::ostream_sink_st>(_oss);
ostream_logger = std::make_shared<spdlog::logger>("gtest_logger", ostream_sink);
ostream_logger->set_pattern(">%v<");
ostream_logger->set_level(spdlog::level::debug);
}
spdlog::set_default_logger(ostream_logger);
where _oss is a std::ostringstream.
Then my tests just look at the contents of _oss, and clear it after each check:
std::string test = _oss.str();
// check the derived class is constructed
EXPECT_TRUE(test.find("Constructing test class") != std::string::npos);
_oss.str("");
The existing code using spdlog::debug, spdlog::trace etc doesn't need changing at all.
For me, the accepted answer didn't work, because some functions would get the logger by name, instead of the default logger.
If you don't need to worry about thread safety, and you're in a similar situation, then you can simply change your logger's sink, instead of creating a new logger:
struct LoggerState {
spdlog::sink_ptr oldSink {nullptr};
spdlog::level::level_enum oldLevel;
};
LoggerState redirectLogger(
std::shared_ptr<spdlog::logger>& log,
std::ostringstream& oss,
spdlog::level::level_enum newLevel
){
LoggerState ls;
ls.oldLevel = log->level();
std::vector<spdlog::sink_ptr>& sinks { log->sinks() };
assert(sinks.size() == 1);
ls.oldSink = std::move(sinks[0]);
sinks[0] = std::make_shared<spdlog::sinks::ostream_sink_st>(oss);
log->set_pattern( "[%l] %v" );
log->set_level(newLevel);
return ls;
}
void resetLogger(
std::shared_ptr<spdlog::logger>& log,
const LoggerState& ls
){
log->sinks()[0] = std::move(ls.oldSink);
log->set_level(ls.oldLevel);
}
Use it in your code as such:
std::shared_ptr<spdlog::logger> yourLogger { /* get your logger */ };
std::ostringstream oss;
LoggerState oldState = redirectLogger(yourLogger, oss, spdlog::level::warn);
/* do something that produces logger output */
resetLogger(yourLogger, oldState);
/* now oss.str() holds the captured output,
* and your old logger should be good as new */
I have a fairly good template (as in snippet of code) I pull out whenever I need a singleton class. I am now trying to apply it within my project to allow me to control a single instance of a web server. I can make a web server without encasing it in my class. When I try to encase it within the class I'm apparently too unskilled to pull it off.
I've tried the obvious Googling and searching here. I've read relevant posts. I am sure this does not mean I have a unique problem, just that I've not figured out the right way to fix it. Here's what I am working with:
webserver.h:
#include <ESP8266WebServer.h>
#include <FS.h>
class WebServer {
private:
// Singleton Declarations
static bool instanceFlag;
static WebServer *single;
WebServer() {}
// Other Declarations
FS *filesystem;
ESP8266WebServer server();
String getContentType(String);
bool handleFileRead(String);
public:
// Singleton Declarations
static WebServer* getInstance();
~WebServer() {instanceFlag = false;}
// Other Declarations
void initialize(int);
void handleLoop();
};
webserver.cpp:
#include "webserver.h"
bool WebServer::instanceFlag = false;
WebServer* WebServer::single = NULL;
WebServer* WebServer::getInstance() {
if(!instanceFlag) {
single = new WebServer();
instanceFlag = true;
return single;
} else {
return single;
}
}
void WebServer::initialize (int port) {
ESP8266WebServer server(port);
FS *filesystem;
filesystem->begin();
Serial.print("Open: http://");
Serial.print(WiFi.hostname().c_str());
Serial.println(".local");
server.onNotFound([]() {
if (!single->handleFileRead(single->server.uri())) {
single->server.send(404, "text/plain", "404: File not found.");
}
});
server.begin();
Serial.print("HTTP server started on port ");
Serial.print(port);
Serial.println(".");
}
String WebServer::getContentType(String filename) {
if (single->server.hasArg("download")) {
return "application/octet-stream";
} else if (filename.endsWith(".htm")) {
return "text/html";
} else if (filename.endsWith(".html")) {
return "text/html";
} else if (filename.endsWith(".css")) {
return "text/css";
} else if (filename.endsWith(".js")) {
return "application/javascript";
} else if (filename.endsWith(".png")) {
return "image/png";
} else if (filename.endsWith(".gif")) {
return "image/gif";
} else if (filename.endsWith(".jpg")) {
return "image/jpeg";
} else if (filename.endsWith(".ico")) {
return "image/x-icon";
} else if (filename.endsWith(".xml")) {
return "text/xml";
} else if (filename.endsWith(".pdf")) {
return "application/x-pdf";
} else if (filename.endsWith(".zip")) {
return "application/x-zip";
} else if (filename.endsWith(".gz")) {
return "application/x-gzip";
} else {
return "text/plain";
}
}
bool WebServer::handleFileRead(String path) {
Serial.println("handleFileRead: " + path);
if (path.endsWith("/")) {
path += "index.htm";
}
String contentType = getContentType(path);
String pathWithGz = path + ".gz";
if (filesystem->exists(pathWithGz) || filesystem->exists(path)) {
if (filesystem->exists(pathWithGz)) {
path += ".gz";
}
File file = filesystem->open(path, "r");
single->server.streamFile(file, contentType);
file.close();
return true;
}
return false;
}
void WebServer::handleLoop() {
single->server.handleClient();
}
The errors I am getting are all similar to the following:
src\webserver.cpp: In member function 'bool WebServer::handleFileRead(String)':
src\webserver.cpp:81:23: error: 'WebServer::single->WebServer::server' does not have class type
single->server.streamFile(file, contentType);
I get the idea of "does not have a class type", I just have no idea what it means here. In my mind, "single" is a pointer to the class so I'm unclear what that reference is not working.
Obviously, there are ample examples out there how to do a web server without encapsulating it. Other things I need to do for this project lend itself to creating that requirement.
There are some mistake in your code.
In webserver.h:
...
private:
// Singleton Declarations
static bool instanceFlag;
static WebServer *single;
WebServer() {}
// Other Declarations
FS *filesystem;
ESP8266WebServer *server; // <--- remove the parentheses and make it a pointer
String getContentType(String);
bool handleFileRead(String);
...
In webserver.cpp:
In WebServer::initialize I am guessing you want to initialize the class server and filesystem not locals, so it should probably look like this:
void WebServer::initialize (int port) {
server = new ESP8266WebServer(port);
filesystem = new FS();
...
}
And now everywhere you use the server you have to use the -> operator.
For example:
void WebServer::handleLoop() {
single->server->handleClient();
}
Please keep in mind that server and filesystem objects have to be deleted to avoid memory leaks.
EDIT:
You get the new error because FS has no constructor without arguments.
FS's constructor looks like this: FS(FSImplPtr impl) : _impl(impl) { }, here you can see that FSImplPtr is a typedef for std::shared_ptr<FileImpl>, so you need to provide this as a parameter.
It works your way, because SPIFFS's existence is declared here and is of type FS.
If you want to use SPIFFS, you have to use it like this: filesystem = &SPIFFS;, not like you mentioned in the comments (FS* filesystem = &SPIFFS;) because your way creates a new temporary variable named filesystem, and probably you expect to initiate the filesystem in the class, not a local one.
I have this C++ function that downloads S3 files as istreams using the AWS SDK C++:
std::istream& s3read(std::string bucket, std::string key) {
Aws::Client::ClientConfiguration aws_conf;
aws_conf.region = Aws::Environment::GetEnv("AWS_REGION");
aws_conf.caFile = "/etc/pki/tls/certs/ca-bundle.crt";
Aws::S3::S3Client s3_client(aws_conf);
Aws::S3::Model::GetObjectRequest object_request;
object_request.WithBucket(bucket.c_str()).WithKey(key.c_str());
auto get_object_outcome = s3_client.GetObject(object_request);
if (get_object_outcome.IsSuccess()) {
std::istream& res = get_object_outcome.GetResult().GetBody();
return res;
} else {
...
};
};
I call it from main.cpp and try to parse it with Jsoncpp:
std::istream& stream = s3read(bucket, key);
Json::Value json;
Json::Reader reader;
reader.parse(stream, json);
However, I keep getting segmentation fault. Why?
I think that the problem is that reader.parse needs binary data and the istream isn't. But, if I'm right, how can I parse the stream as binary?
The issue you'r have is classical returning reference to temporary
You can re-design your code a little, to avoid this. For example:
static Json::Value parse_json(std::istream& src) {
Json::Value ret;
Json::Reader reader;
reader.parse(src, ret);
return ret;
}
// Aws::String is actually same thing to std::string except the allocator
// in case of Android, otherwise this is std::string as it is.
// You can use function like s3read("foo","bar");
Json::Value s3read_json(const Aws::String& bucket,const Aws::String& key) {
static constexpr const char *FILE_NAME = "/etc/pki/tls/certs/ca-bundle.crt";
Aws::Client::ClientConfiguration aws_conf;
aws_conf.region = Aws::Environment::GetEnv("AWS_REGION");
aws_conf.caFile = FILE_NAME;
Aws::S3::S3Client s3_client(aws_conf);
Aws::S3::Model::GetObjectRequest object_request;
object_request.WithBucket( bucket ).WithKey( key );
auto object_outcome = s3_client.GetObject(object_request);
if (object_outcome.IsSuccess()) {
auto result = object_outcome.GetResult();
// destructor of object_outcome is not yet called
return parse_json( result.GetBody() );
} else {
...
// throw std::runtime_error("S3 connection failed");
};
};
I want to rewrite all messages in my code,
I need replace only selectors, but I need be able to replace nested expressions
f. e. :
[super foo:[someInstance someMessage:#""] foo2:[someInstance someMessage2]];
I tried do it with clang::Rewriter replaceText and just generate new string,
but there is a problem: It would not be work if I change selectors length, because I replace nested messages with those old positions.
So, I assumed that I need to use clang::Rewriter ReplaceStmt(originalStatement, newStatement);
I am using RecursiveASTVisitor to visit all messages, and I want to copy those messages objects, and replace selectors:
How can I do that?
I tried use ObjCMessageExpr::Create but there is so meny args, I don't know how to get ASTContext &Context and ArrayRef<SourceLocation> SeLocs and Expr *Receiver parameters from the original message.
What is the proper way to replace selectors in nested messages using clang tool (clang tooling interface)?
Update:
Should I use ReplaceStmtWithStmt callback and ASTMatchFinder ?
Update:
I am using following function to rewrite text in file:
void ReplaceText(SourceLocation start, unsigned originalLength, StringRef string) {
m_rewriter.ReplaceText(start, originalLength, string);
m_rewriter.overwriteChangedFiles();
}
And I want to replace all messageExpr in code with new selector f.e:
how it was:
[object someMessage:[object2 someMessage:obj3 calculate:obj4]];
how it should be:
[object newSelector:[object2 newSelector:obj3 newSelector:obj4]];
I am using ReqoursiveASTVisitor:
bool VisitStmt(Stmt *statement) {
if (ObjCMessageExpr *messageExpr = dyn_cast<ObjCMessageExpr>(statement)) {
ReplaceMessage(*messageExpr)
}
return true;
}
I created method for generating new message expr string:
string StringFromObjCMessageExpr(ObjCMessageExpr& messageExpression) {
std::ostringstream stringStream;
const string selectorString = messageExpression.getSelector().getAsString();
cout << selectorString << endl;
vector<string> methodParts;
split(selectorString, ParametersDelimiter, methodParts);
stringStream << "[" ;
const string receiver = GetStringFromLocations(m_compiler, messageExpression.getReceiverRange().getBegin(), messageExpression.getSelectorStartLoc());
stringStream << receiver;
clang::ObjCMessageExpr::arg_iterator argIterator = messageExpression.arg_begin();
for (vector<string>::const_iterator partsIterator = methodParts.begin();
partsIterator != methodParts.end();
++partsIterator) {
stringStream << "newSelector";
if (messageExpression.getNumArgs() != 0) {
const clang::Stmt *argument = *argIterator;
stringStream << ":" << GetStatementString(*argument) << " ";
++argIterator;
}
}
stringStream << "]";
return stringStream.str();
}
void ReplaceMessage(ObjCMessageExpr& messageExpression) {
SourceLocation locStart = messageExpression.getLocStart();
SourceLocation locEnd = messageExpression.getLocEnd();
string newExpr = StringFromObjCMessageExpr(messageExpression);
const int exprStringLegth = m_rewriter.getRangeSize(SourceRange(locStart, locEnd));
ReplaceText(locStart, exprStringLegth, newExpr);
}
The problem occurs when I try to replace nested messages, like that:
[simpleClass doSomeActionWithString:string3 andAnotherString:string4];
[simpleClass doSomeActionWithString:str andAnotherString:str2];
[simpleClass doSomeActionWithString:#"" andAnotherString:#"asdasdsad"];
[simpleClass setSimpleClassZAZAZAZAZAZAZAZA:[simpleClass getSimpleClassZAZAZAZAZAZAZAZA]];
the result is:
[simpleClass newSelector:string3 newSelector:string4 ];
[simpleClass newSelector:str newSelector:str2 ];
[simpleClass newSelector:#"" newSelector:#"asdasdsad" ];
[simpleClass newSelector:[simpleClass getSimp[simpleClass newSelector]];
because messageExpression has "old" value of getLocStart(); and getLocEnd(); How can I fix it?
You can rewrite selector name by replacing only continuous parts of selector name. For example, replace only underlined parts
[object someMessage:[object2 someMessage:obj3 calculate:obj4]];
^~~~~~~~~~~ ^~~~~~~~~~~ ^~~~~~~~~
To achieve this you require only
number of selector parts - ObjCMessageExpr::getNumSelectorLocs()
their locations - ObjCMessageExpr::getSelectorLoc(index)
their lengths - ObjCMessageExpr::getSelector().getNameForSlot(index).size().
Overall, you can rewrite ObjCMessageExpr with the following RecursiveASTVisitor:
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Rewrite/Core/Rewriter.h"
namespace clang_tooling
{
using clang::SourceLocation;
class RewritingVisitor : public clang::ASTConsumer,
public clang::RecursiveASTVisitor<RewritingVisitor>
{
public:
// You can obtain SourceManager and LangOptions from CompilerInstance when
// you are creating visitor (which is also ASTConsumer) in
// clang::ASTFrontendAction::CreateASTConsumer.
RewritingVisitor(clang::SourceManager &sourceManager,
const clang::LangOptions &langOptions)
: _sourceManager(sourceManager), _rewriter(sourceManager, langOptions)
{}
virtual void HandleTranslationUnit(clang::ASTContext &context)
{
TraverseDecl(context.getTranslationUnitDecl());
_rewriter.overwriteChangedFiles();
}
bool VisitObjCMessageExpr(clang::ObjCMessageExpr *messageExpr)
{
if (_sourceManager.isInMainFile(messageExpr->getLocStart()))
{
clang::Selector selector = messageExpr->getSelector();
for (unsigned i = 0, end = messageExpr->getNumSelectorLocs();
i < end; ++i)
{
SourceLocation selectorLoc = messageExpr->getSelectorLoc(i);
_rewriter.ReplaceText(selectorLoc,
selector.getNameForSlot(i).size(),
"newSelector");
}
}
return Base::VisitObjCMessageExpr(messageExpr);
}
private:
typedef clang::RecursiveASTVisitor<RewritingVisitor> Base;
clang::SourceManager &_sourceManager;
clang::Rewriter _rewriter;
};
} // end namespace clang_tooling