Refine the REST examples; refine the http message parsing.
This commit is contained in:
@@ -6,5 +6,5 @@ file(GLOB SRCS *.cc *.h)
|
||||
|
||||
add_executable(rest_book_client ${SRCS})
|
||||
|
||||
target_link_libraries(rest_book_client webcc ${Boost_LIBRARIES})
|
||||
target_link_libraries(rest_book_client webcc jsoncpp ${Boost_LIBRARIES})
|
||||
target_link_libraries(rest_book_client "${CMAKE_THREAD_LIBS_INIT}")
|
||||
|
||||
@@ -1,87 +1,209 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "boost/algorithm/string.hpp"
|
||||
#include "json/json.h" // jsoncpp
|
||||
|
||||
#include "webcc/http_client.h"
|
||||
#include "webcc/http_request.h"
|
||||
#include "webcc/http_response.h"
|
||||
|
||||
class BookListClient {
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class BookClientBase {
|
||||
public:
|
||||
BookListClient() {
|
||||
host_ = "localhost";
|
||||
port_ = "8080";
|
||||
BookClientBase(const std::string& host, const std::string& port)
|
||||
: host_(host), port_(port) {
|
||||
}
|
||||
|
||||
bool Request(const std::string& method,
|
||||
const std::string& url,
|
||||
const std::string& content,
|
||||
webcc::HttpResponse* http_response) {
|
||||
webcc::HttpRequest http_request;
|
||||
|
||||
http_request.set_method(method);
|
||||
http_request.set_url(url);
|
||||
http_request.SetHost(host_, port_);
|
||||
if (!content.empty()) { // TODO
|
||||
http_request.SetContent(content);
|
||||
}
|
||||
http_request.Build();
|
||||
|
||||
webcc::HttpClient http_client;
|
||||
webcc::Error error = http_client.SendRequest(http_request, http_response);
|
||||
|
||||
return error == webcc::kNoError;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string host_;
|
||||
std::string port_;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class BookListClient : public BookClientBase {
|
||||
public:
|
||||
BookListClient(const std::string& host, const std::string& port)
|
||||
: BookClientBase(host, port) {
|
||||
}
|
||||
|
||||
bool ListBooks() {
|
||||
webcc::HttpRequest http_request;
|
||||
|
||||
http_request.set_method(webcc::kHttpGet);
|
||||
http_request.set_url("/books");
|
||||
http_request.SetHost(host_, port_);
|
||||
|
||||
http_request.Build();
|
||||
|
||||
webcc::HttpResponse http_response;
|
||||
|
||||
webcc::HttpClient http_client;
|
||||
webcc::Error error = http_client.SendRequest(http_request, &http_response);
|
||||
|
||||
if (error != webcc::kNoError) {
|
||||
if (!Request(webcc::kHttpGet, "/books", "", &http_response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cout << "Book list: " << std::endl
|
||||
<< http_response.content() << std::endl;
|
||||
std::cout << http_response.content() << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string host_;
|
||||
std::string port_;
|
||||
bool CreateBook(const std::string& id,
|
||||
const std::string& title,
|
||||
double price) {
|
||||
Json::Value root(Json::objectValue);
|
||||
root["id"] = id;
|
||||
root["title"] = title;
|
||||
root["price"] = price;
|
||||
|
||||
Json::StreamWriterBuilder builder;
|
||||
std::string book_json = Json::writeString(builder, root);
|
||||
|
||||
webcc::HttpResponse http_response;
|
||||
if (!Request(webcc::kHttpPost, "/books", book_json, &http_response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cout << http_response.status() << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class BookDetailClient {
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class BookDetailClient : public BookClientBase {
|
||||
public:
|
||||
BookDetailClient() {
|
||||
host_ = "localhost";
|
||||
port_ = "8080";
|
||||
BookDetailClient(const std::string& host, const std::string& port)
|
||||
: BookClientBase(host, port) {
|
||||
}
|
||||
|
||||
bool GetBook(const std::string& id) {
|
||||
webcc::HttpRequest http_request;
|
||||
|
||||
http_request.set_method(webcc::kHttpGet);
|
||||
http_request.set_url("/books/" + id);
|
||||
http_request.SetHost(host_, port_);
|
||||
|
||||
http_request.Build();
|
||||
|
||||
webcc::HttpResponse http_response;
|
||||
|
||||
webcc::HttpClient http_client;
|
||||
webcc::Error error = http_client.SendRequest(http_request, &http_response);
|
||||
|
||||
if (error != webcc::kNoError) {
|
||||
if (!Request(webcc::kHttpGet, "/books/" + id, "", &http_response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cout << "Book: " << id << std::endl
|
||||
<< http_response.content() << std::endl;
|
||||
std::cout << http_response.content() << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string host_;
|
||||
std::string port_;
|
||||
};
|
||||
|
||||
int main() {
|
||||
BookListClient book_list_client;
|
||||
book_list_client.ListBooks();
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
BookDetailClient book_detail_client;
|
||||
book_detail_client.GetBook("1");
|
||||
std::string g_host;
|
||||
std::string g_port;
|
||||
|
||||
void ListBooks() {
|
||||
std::cout << "List books" << std::endl;
|
||||
|
||||
BookListClient client(g_host, g_port);
|
||||
|
||||
client.ListBooks();
|
||||
}
|
||||
|
||||
void CreateBook(const std::string& id,
|
||||
const std::string& title,
|
||||
double price) {
|
||||
std::cout << "Create book: " << id << " " << title << " " << price << std::endl;
|
||||
|
||||
BookListClient client(g_host, g_port);
|
||||
|
||||
client.CreateBook(id, title, price);
|
||||
}
|
||||
|
||||
void Help(const char* argv0) {
|
||||
std::cout << "Usage: " << argv0 << " <host> <port>" << std::endl;
|
||||
std::cout << " E.g.," << std::endl;
|
||||
std::cout << " " << argv0 << " localhost 8080" << std::endl;
|
||||
}
|
||||
|
||||
std::string GetUserInput() {
|
||||
char input[256];
|
||||
std::size_t length = 0;
|
||||
do {
|
||||
std::cout << ">> ";
|
||||
std::cin.getline(input, 256);
|
||||
length = strlen(input);
|
||||
} while (length == 0);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc != 3) {
|
||||
Help(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
g_host = argv[1];
|
||||
g_port = argv[2];
|
||||
|
||||
// Type commands to execute actions.
|
||||
// Commands: list, create, update, remove
|
||||
// Examples:
|
||||
// >> list
|
||||
// >> create 1 { "title": "1984", "price": 12.3 }
|
||||
// >> detail 1
|
||||
// >> update 1 { "price": 32 }
|
||||
// >> delete 1
|
||||
|
||||
while (true) {
|
||||
std::string input = GetUserInput();
|
||||
boost::trim(input);
|
||||
|
||||
std::string command;
|
||||
std::size_t i = input.find(' ');
|
||||
if (i == std::string::npos) {
|
||||
command = input;
|
||||
} else {
|
||||
command = input.substr(0, i);
|
||||
}
|
||||
|
||||
if (command == "exit") {
|
||||
break;
|
||||
}
|
||||
|
||||
if (command == "list") {
|
||||
ListBooks();
|
||||
continue;
|
||||
}
|
||||
|
||||
++i;
|
||||
|
||||
std::size_t j = input.find(' ', i);
|
||||
std::string id = input.substr(i, j - i);
|
||||
i = j + 1;
|
||||
|
||||
if (command == "create") {
|
||||
std::string json = input.substr(i);
|
||||
std::cout << "json:" << json << std::endl;
|
||||
|
||||
Json::Value root;
|
||||
Json::CharReaderBuilder builder;
|
||||
std::string errs;
|
||||
if (Json::parseFromStream(builder, std::stringstream(json), &root, &errs)) {
|
||||
CreateBook(id, root["title"].asString(), root["price"].asDouble());
|
||||
} else {
|
||||
std::cerr << errs << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//BookDetailClient book_detail_client(host, port);
|
||||
//book_detail_client.GetBook("1");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "book_services.h"
|
||||
|
||||
#include <list>
|
||||
#include <iostream>
|
||||
#include "boost/lexical_cast.hpp"
|
||||
#include "json/json.h" // jsoncpp
|
||||
|
||||
@@ -28,16 +29,16 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Book& book) {
|
||||
os << "{ " << book.id << ", " << book.title << ", " << book.price << " }";
|
||||
return os;
|
||||
}
|
||||
|
||||
static const Book kNullBook{};
|
||||
|
||||
class BookStore {
|
||||
public:
|
||||
BookStore() {
|
||||
// Prepare test data.
|
||||
books_.push_back({ "1", "Title1", 11.1 });
|
||||
books_.push_back({ "2", "Title2", 22.2 });
|
||||
books_.push_back({ "3", "Title3", 33.3 });
|
||||
}
|
||||
BookStore() = default;
|
||||
|
||||
const std::list<Book>& books() const {
|
||||
return books_;
|
||||
@@ -87,16 +88,39 @@ bool BookListService::Handle(const std::string& http_method,
|
||||
const std::string& request_content,
|
||||
std::string* response_content) {
|
||||
if (http_method == webcc::kHttpGet) {
|
||||
// Return all books as a JSON array.
|
||||
|
||||
Json::Value root(Json::arrayValue);
|
||||
for (const Book& book : g_book_store.books()) {
|
||||
root.append(book.ToJson());
|
||||
}
|
||||
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
*response_content = Json::writeString(wbuilder, root);
|
||||
Json::StreamWriterBuilder builder;
|
||||
*response_content = Json::writeString(builder, root);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (http_method == webcc::kHttpPost) {
|
||||
// Add a new book.
|
||||
|
||||
Json::Value root;
|
||||
Json::CharReaderBuilder builder;
|
||||
std::stringstream stream(request_content);
|
||||
std::string errs;
|
||||
if (!Json::parseFromStream(builder, stream, &root, &errs)) {
|
||||
std::cerr << errs << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
Book book;
|
||||
book.id = root["id"].asString();
|
||||
book.title = root["title"].asString();
|
||||
book.price = root["price"].asDouble();
|
||||
|
||||
return g_book_store.AddBook(book);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -119,8 +143,8 @@ bool BookDetailService::Handle(const std::string& http_method,
|
||||
return false;
|
||||
}
|
||||
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
*response_content = Json::writeString(wbuilder, book.ToJson());
|
||||
Json::StreamWriterBuilder builder;
|
||||
*response_content = Json::writeString(builder, book.ToJson());
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
+11
-21
@@ -43,32 +43,22 @@ public:
|
||||
SetHeader(kContentType, content_type);
|
||||
}
|
||||
|
||||
void SetContent(std::string&& content) {
|
||||
content_ = std::move(content);
|
||||
SetContentLength(content_.size());
|
||||
}
|
||||
|
||||
void SetContent(const std::string& content) {
|
||||
content_ = content;
|
||||
SetContentLength(content_.size());
|
||||
}
|
||||
|
||||
private:
|
||||
void SetContentLength(std::size_t content_length) {
|
||||
content_length_ = content_length;
|
||||
SetHeader(kContentLength, std::to_string(content_length));
|
||||
}
|
||||
|
||||
// Use move semantics to avoid copy.
|
||||
void set_content(std::string&& content) {
|
||||
content_ = std::move(content);
|
||||
}
|
||||
|
||||
void AppendContent(const char* data, std::size_t count) {
|
||||
content_.append(data, count);
|
||||
}
|
||||
|
||||
void AppendContent(const std::string& data) {
|
||||
content_.append(data);
|
||||
}
|
||||
|
||||
bool IsContentFull() const {
|
||||
return IsContentLengthValid() && content_.length() >= content_length_;
|
||||
}
|
||||
|
||||
bool IsContentLengthValid() const {
|
||||
return content_length_ != kInvalidLength;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Start line with trailing "\r\n".
|
||||
std::string start_line_;
|
||||
|
||||
+29
-12
@@ -9,6 +9,7 @@ namespace webcc {
|
||||
|
||||
HttpParser::HttpParser(HttpMessage* message)
|
||||
: message_(message)
|
||||
, content_length_(kInvalidLength)
|
||||
, start_line_parsed_(false)
|
||||
, content_length_parsed_(false)
|
||||
, header_parsed_(false)
|
||||
@@ -18,11 +19,11 @@ HttpParser::HttpParser(HttpMessage* message)
|
||||
Error HttpParser::Parse(const char* data, std::size_t len) {
|
||||
if (header_parsed_) {
|
||||
// Add the data to the content.
|
||||
message_->AppendContent(data, len);
|
||||
AppendContent(data, len);
|
||||
|
||||
if (message_->IsContentFull()) {
|
||||
if (IsContentFull()) {
|
||||
// All content has been read.
|
||||
finished_ = true;
|
||||
Finish();
|
||||
}
|
||||
|
||||
return kNoError;
|
||||
@@ -66,22 +67,21 @@ Error HttpParser::Parse(const char* data, std::size_t len) {
|
||||
// Headers just ended.
|
||||
|
||||
if (!content_length_parsed_) {
|
||||
// No Content-Length, no content.
|
||||
message_->SetContentLength(0);
|
||||
finished_ = true;
|
||||
// No Content-Length, no content. (TODO: Support chucked data)
|
||||
Finish();
|
||||
return kNoError;
|
||||
} else {
|
||||
// Invalid Content-Length in the request.
|
||||
if (!message_->IsContentLengthValid()) {
|
||||
if (content_length_ == kInvalidLength) {
|
||||
return kHttpContentLengthError;
|
||||
}
|
||||
}
|
||||
|
||||
message_->AppendContent(pending_data_.substr(off));
|
||||
AppendContent(pending_data_.substr(off));
|
||||
|
||||
if (message_->IsContentFull()) {
|
||||
if (IsContentFull()) {
|
||||
// All content has been read.
|
||||
finished_ = true;
|
||||
Finish();
|
||||
}
|
||||
} else {
|
||||
// Save the unparsed piece for next parsing.
|
||||
@@ -110,11 +110,28 @@ void HttpParser::ParseContentLength(const std::string& line) {
|
||||
std::string value = line.substr(pos);
|
||||
|
||||
try {
|
||||
std::size_t length = boost::lexical_cast<std::size_t>(value);
|
||||
message_->SetContentLength(length);
|
||||
content_length_ = boost::lexical_cast<std::size_t>(value);
|
||||
} catch (boost::bad_lexical_cast&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpParser::Finish() {
|
||||
message_->SetContent(content_);
|
||||
finished_ = true;
|
||||
}
|
||||
|
||||
void HttpParser::AppendContent(const char* data, std::size_t count) {
|
||||
content_.append(data, count);
|
||||
}
|
||||
|
||||
void HttpParser::AppendContent(const std::string& data) {
|
||||
content_.append(data);
|
||||
}
|
||||
|
||||
bool HttpParser::IsContentFull() const {
|
||||
return content_length_ != kInvalidLength &&
|
||||
content_length_ <= content_.length();
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
+14
-1
@@ -13,6 +13,10 @@ class HttpParser {
|
||||
public:
|
||||
explicit HttpParser(HttpMessage* message);
|
||||
|
||||
~HttpParser() = default;
|
||||
HttpParser(const HttpParser&) = delete;
|
||||
HttpParser& operator=(const HttpParser&) = delete;
|
||||
|
||||
bool finished() const {
|
||||
return finished_;
|
||||
}
|
||||
@@ -25,6 +29,13 @@ protected:
|
||||
|
||||
void ParseContentLength(const std::string& line);
|
||||
|
||||
void Finish();
|
||||
|
||||
void AppendContent(const char* data, std::size_t count);
|
||||
void AppendContent(const std::string& data);
|
||||
|
||||
bool IsContentFull() const;
|
||||
|
||||
protected:
|
||||
// The result HTTP message.
|
||||
HttpMessage* message_;
|
||||
@@ -34,7 +45,9 @@ protected:
|
||||
// Data waiting to be parsed.
|
||||
std::string pending_data_;
|
||||
|
||||
// Parsing helper flags.
|
||||
// Temporary data and helper flags for parsing.
|
||||
std::size_t content_length_;
|
||||
std::string content_;
|
||||
bool start_line_parsed_;
|
||||
bool content_length_parsed_;
|
||||
bool header_parsed_;
|
||||
|
||||
@@ -96,7 +96,7 @@ std::vector<boost::asio::const_buffer> HttpResponse::ToBuffers() const {
|
||||
buffers.push_back(boost::asio::buffer(misc_strings::CRLF));
|
||||
|
||||
// Content (optional)
|
||||
if (IsContentLengthValid()) {
|
||||
if (!content_.empty()) {
|
||||
buffers.push_back(boost::asio::buffer(content_));
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,7 @@ void HttpSession::SetResponseContent(const std::string& content_type,
|
||||
std::size_t content_length,
|
||||
std::string&& content) {
|
||||
response_.SetContentType(content_type);
|
||||
response_.SetContentLength(content.length());
|
||||
response_.set_content(std::move(content));
|
||||
response_.SetContent(std::move(content));
|
||||
}
|
||||
|
||||
void HttpSession::SendResponse() {
|
||||
|
||||
@@ -40,11 +40,9 @@ Error SoapClient::Call(const std::string& operation,
|
||||
http_request.set_method(kHttpPost);
|
||||
http_request.set_url(url_);
|
||||
http_request.SetContentType(kTextXmlUtf8);
|
||||
http_request.SetContentLength(http_content.size());
|
||||
http_request.SetContent(std::move(http_content));
|
||||
http_request.SetHost(host_, port_);
|
||||
http_request.SetHeader(kSoapAction, operation);
|
||||
http_request.set_content(std::move(http_content));
|
||||
|
||||
http_request.Build();
|
||||
|
||||
HttpResponse http_response;
|
||||
|
||||
Reference in New Issue
Block a user