Home > front end >  string stream vs direct string
string stream vs direct string

Time:09-28

I'm attempting to send a STOMP frame to a server. I first tried to create the message directly using std::string, but the server kept complaining I am doing it wrong. But, when I created the message using stringstream, it works. Anyone can spot my mistake? The code is as shown. It complains it can't find the terminating \0 at the end of the message (parsingmissingnullinbody).

bool CheckResponse(const std::string& response)
{
// We do not parse the whole message. We only check that it contains some
// expected items.
bool ok {true};
ok &= response.find("ERROR") != std::string::npos;
ok &= response.find("ValidationInvalidAuth") != std::string::npos;
std::cout << response << "\n";
return ok;
}

BOOST_AUTO_TEST_CASE(connect_to_network_events){
    // Always start with an I/O context object.
    boost::asio::io_context ioc {};

    // Create also a tls context
    boost::asio::ssl::context ctx{boost::asio::ssl::context::tlsv12_client};
    // Connection targets
    const std::string url {"ltnm.learncppthroughprojects.com"};
    const std::string port {"443"};
    const std::string endpoint {"/network-events"};

    // The class under test
    WebSocketClient client {url, endpoint, port, ioc, ctx};

    // MY ATTEMPT AT CREATING MESSAGE DIRECTLY, THIS FAILED
    // const std::string message {"STOMP\naccept-version:1.2\nhost:transportforlondon.com\nlogin:fake_username\npasscode:fake_password\n\n\0"};

    // THE FOLLOWING SUCCEEDED INSTEAD
    const std::string username {"fake_username"};
    const std::string password {"fake_password"};
    std::stringstream ss {};
    ss << "STOMP" << std::endl
       << "accept-version:1.2" << std::endl
       << "host:transportforlondon.com" << std::endl
       << "login:" << username << std::endl
       << "passcode:" << password << std::endl
       << std::endl // Headers need to be followed by a blank line.
       << '\0'; // The body (even if absent) must be followed by a NULL octet.
    const std::string message {ss.str()};

    std::string response;
    // We use these flags to check that the connection, send, receive functions
    // work as expected.
    bool connected {false};
    bool messageSent {false};
    bool messageReceived {false};
    bool messageMatches {false};
    bool disconnected {false};

    // Our own callbacks
    auto onSend {[&messageSent](auto ec) {
        messageSent = !ec;
    }};
    auto onConnect {[&client, &connected, &onSend, &message](auto ec) {
        connected = !ec;
        if (!ec) {
            client.Send(message, onSend);
        }
    }};
    auto onClose {[&disconnected](auto ec) {
        disconnected = !ec;
    }};
    auto onReceive {[&client,
                      &onClose,
                      &messageReceived,
                      &messageMatches,
                      &message,
                      &response](auto ec, auto received) {
        messageReceived = !ec;
        response = received;
        client.Close(onClose);
    }};

    // We must call io_context::run for asynchronous callbacks to run.
    client.Connect(onConnect, onReceive);
    ioc.run();

    BOOST_CHECK(CheckResponse(response));
}

CodePudding user response:

Creating an std::string from const char * will ignore the terminating NULL character. You could use the char[] constructor but I think it won't fit your use case.

Example:

// Example program
#include <iostream>
#include <string>

int main()
{
    std::string message1 {"STOMP\naccept-version:1.2\nhost:transportforlondon.com\nlogin:fake_username\npasscode:fake_password\n\n\0"};
    std::cout << message1.length() << std::endl;
    
    std::string message2 {"STOMP\naccept-version:1.2\nhost:transportforlondon.com\nlogin:fake_username\npasscode:fake_password\n\n"};
    std::cout << message2.length() << std::endl;
    
    message2.push_back('\0');
    std::cout << message2.length() << std::endl;
}

Output:

97
97
98

So you just need to create the message without the terminating NULL character and then append \0.

Update: If you are in C 14 you may want to use string literals:

using namespace std::string_literals;

// ... 

std::string message = "STOMP\naccept-version:1.2\nhost:transportforlondon.com\nlogin:fake_username\npasscode:fake_password\n\n\0"s;

Note the s at the end of the string.

CodePudding user response:

You could also use string constructor with iterators to copy all the characters including trailing \0

#include <cassert>
#include <string>

// helper function to copy ALL the chars including terminating 0 into a string
template<std::size_t N>
auto create_message(const char(&chars)[N])
{
    std::string message(std::begin(chars), std::end(chars));
    return message;
}

int main()
{
    auto message = create_message("STOMP\naccept-version:1.2\nhost:transportforlondon.com\nlogin:fake_username\npasscode:fake_password\n\n");

    // check last character in string is actually 0
    assert(message[message.length() - 1] == 0);
    return 0;
}
  • Related