Refine the REST examples; refine the http message parsing.

master
Adam Gu 7 years ago
parent 803ae3eb5f
commit 4e35648b29

@ -6,5 +6,5 @@ file(GLOB SRCS *.cc *.h)
add_executable(rest_book_client ${SRCS}) 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}") target_link_libraries(rest_book_client "${CMAKE_THREAD_LIBS_INIT}")

@ -1,87 +1,209 @@
#include <iostream> #include <iostream>
#include "boost/algorithm/string.hpp"
#include "json/json.h" // jsoncpp
#include "webcc/http_client.h" #include "webcc/http_client.h"
#include "webcc/http_request.h" #include "webcc/http_request.h"
#include "webcc/http_response.h" #include "webcc/http_response.h"
class BookListClient { ////////////////////////////////////////////////////////////////////////////////
class BookClientBase {
public: public:
BookListClient() { BookClientBase(const std::string& host, const std::string& port)
host_ = "localhost"; : host_(host), port_(port) {
port_ = "8080";
} }
bool ListBooks() { bool Request(const std::string& method,
const std::string& url,
const std::string& content,
webcc::HttpResponse* http_response) {
webcc::HttpRequest http_request; webcc::HttpRequest http_request;
http_request.set_method(webcc::kHttpGet); http_request.set_method(method);
http_request.set_url("/books"); http_request.set_url(url);
http_request.SetHost(host_, port_); http_request.SetHost(host_, port_);
if (!content.empty()) { // TODO
http_request.SetContent(content);
}
http_request.Build(); http_request.Build();
webcc::HttpResponse http_response;
webcc::HttpClient http_client; webcc::HttpClient http_client;
webcc::Error error = http_client.SendRequest(http_request, &http_response); webcc::Error error = http_client.SendRequest(http_request, http_response);
if (error != webcc::kNoError) {
return false;
}
std::cout << "Book list: " << std::endl
<< http_response.content() << std::endl;
return true; return error == webcc::kNoError;
} }
private: protected:
std::string host_; std::string host_;
std::string port_; std::string port_;
}; };
class BookDetailClient { ////////////////////////////////////////////////////////////////////////////////
class BookListClient : public BookClientBase {
public: public:
BookDetailClient() { BookListClient(const std::string& host, const std::string& port)
host_ = "localhost"; : BookClientBase(host, port) {
port_ = "8080";
} }
bool GetBook(const std::string& id) { bool ListBooks() {
webcc::HttpRequest http_request; webcc::HttpResponse http_response;
if (!Request(webcc::kHttpGet, "/books", "", &http_response)) {
return false;
}
http_request.set_method(webcc::kHttpGet); std::cout << http_response.content() << std::endl;
http_request.set_url("/books/" + id);
http_request.SetHost(host_, port_);
http_request.Build(); return true;
}
webcc::HttpResponse http_response; 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;
webcc::HttpClient http_client; Json::StreamWriterBuilder builder;
webcc::Error error = http_client.SendRequest(http_request, &http_response); std::string book_json = Json::writeString(builder, root);
if (error != webcc::kNoError) { webcc::HttpResponse http_response;
if (!Request(webcc::kHttpPost, "/books", book_json, &http_response)) {
return false; return false;
} }
std::cout << "Book: " << id << std::endl std::cout << http_response.status() << std::endl;
<< http_response.content() << std::endl;
return true; return true;
} }
};
private: ////////////////////////////////////////////////////////////////////////////////
std::string host_;
std::string port_; class BookDetailClient : public BookClientBase {
public:
BookDetailClient(const std::string& host, const std::string& port)
: BookClientBase(host, port) {
}
bool GetBook(const std::string& id) {
webcc::HttpResponse http_response;
if (!Request(webcc::kHttpGet, "/books/" + id, "", &http_response)) {
return false;
}
std::cout << http_response.content() << std::endl;
return true;
}
}; };
int main() { ////////////////////////////////////////////////////////////////////////////////
BookListClient book_list_client;
book_list_client.ListBooks(); 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; //BookDetailClient book_detail_client(host, port);
book_detail_client.GetBook("1"); //book_detail_client.GetBook("1");
return 0; return 0;
} }

@ -1,6 +1,7 @@
#include "book_services.h" #include "book_services.h"
#include <list> #include <list>
#include <iostream>
#include "boost/lexical_cast.hpp" #include "boost/lexical_cast.hpp"
#include "json/json.h" // jsoncpp #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{}; static const Book kNullBook{};
class BookStore { class BookStore {
public: public:
BookStore() { BookStore() = default;
// Prepare test data.
books_.push_back({ "1", "Title1", 11.1 });
books_.push_back({ "2", "Title2", 22.2 });
books_.push_back({ "3", "Title3", 33.3 });
}
const std::list<Book>& books() const { const std::list<Book>& books() const {
return books_; return books_;
@ -87,16 +88,39 @@ bool BookListService::Handle(const std::string& http_method,
const std::string& request_content, const std::string& request_content,
std::string* response_content) { std::string* response_content) {
if (http_method == webcc::kHttpGet) { if (http_method == webcc::kHttpGet) {
// Return all books as a JSON array.
Json::Value root(Json::arrayValue); Json::Value root(Json::arrayValue);
for (const Book& book : g_book_store.books()) { for (const Book& book : g_book_store.books()) {
root.append(book.ToJson()); root.append(book.ToJson());
} }
Json::StreamWriterBuilder wbuilder; Json::StreamWriterBuilder builder;
*response_content = Json::writeString(wbuilder, root); *response_content = Json::writeString(builder, root);
return true; 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; return false;
} }
@ -119,8 +143,8 @@ bool BookDetailService::Handle(const std::string& http_method,
return false; return false;
} }
Json::StreamWriterBuilder wbuilder; Json::StreamWriterBuilder builder;
*response_content = Json::writeString(wbuilder, book.ToJson()); *response_content = Json::writeString(builder, book.ToJson());
return true; return true;

@ -43,30 +43,20 @@ public:
SetHeader(kContentType, content_type); SetHeader(kContentType, content_type);
} }
void SetContentLength(std::size_t content_length) { void SetContent(std::string&& content) {
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); content_ = std::move(content);
SetContentLength(content_.size());
} }
void AppendContent(const char* data, std::size_t count) { void SetContent(const std::string& content) {
content_.append(data, count); content_ = content;
} SetContentLength(content_.size());
void AppendContent(const std::string& data) {
content_.append(data);
}
bool IsContentFull() const {
return IsContentLengthValid() && content_.length() >= content_length_;
} }
bool IsContentLengthValid() const { private:
return content_length_ != kInvalidLength; void SetContentLength(std::size_t content_length) {
content_length_ = content_length;
SetHeader(kContentLength, std::to_string(content_length));
} }
protected: protected:

@ -9,6 +9,7 @@ namespace webcc {
HttpParser::HttpParser(HttpMessage* message) HttpParser::HttpParser(HttpMessage* message)
: message_(message) : message_(message)
, content_length_(kInvalidLength)
, start_line_parsed_(false) , start_line_parsed_(false)
, content_length_parsed_(false) , content_length_parsed_(false)
, header_parsed_(false) , header_parsed_(false)
@ -18,11 +19,11 @@ HttpParser::HttpParser(HttpMessage* message)
Error HttpParser::Parse(const char* data, std::size_t len) { Error HttpParser::Parse(const char* data, std::size_t len) {
if (header_parsed_) { if (header_parsed_) {
// Add the data to the content. // Add the data to the content.
message_->AppendContent(data, len); AppendContent(data, len);
if (message_->IsContentFull()) { if (IsContentFull()) {
// All content has been read. // All content has been read.
finished_ = true; Finish();
} }
return kNoError; return kNoError;
@ -66,22 +67,21 @@ Error HttpParser::Parse(const char* data, std::size_t len) {
// Headers just ended. // Headers just ended.
if (!content_length_parsed_) { if (!content_length_parsed_) {
// No Content-Length, no content. // No Content-Length, no content. (TODO: Support chucked data)
message_->SetContentLength(0); Finish();
finished_ = true;
return kNoError; return kNoError;
} else { } else {
// Invalid Content-Length in the request. // Invalid Content-Length in the request.
if (!message_->IsContentLengthValid()) { if (content_length_ == kInvalidLength) {
return kHttpContentLengthError; return kHttpContentLengthError;
} }
} }
message_->AppendContent(pending_data_.substr(off)); AppendContent(pending_data_.substr(off));
if (message_->IsContentFull()) { if (IsContentFull()) {
// All content has been read. // All content has been read.
finished_ = true; Finish();
} }
} else { } else {
// Save the unparsed piece for next parsing. // Save the unparsed piece for next parsing.
@ -110,11 +110,28 @@ void HttpParser::ParseContentLength(const std::string& line) {
std::string value = line.substr(pos); std::string value = line.substr(pos);
try { try {
std::size_t length = boost::lexical_cast<std::size_t>(value); content_length_ = boost::lexical_cast<std::size_t>(value);
message_->SetContentLength(length);
} catch (boost::bad_lexical_cast&) { } 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 } // namespace webcc

@ -13,6 +13,10 @@ class HttpParser {
public: public:
explicit HttpParser(HttpMessage* message); explicit HttpParser(HttpMessage* message);
~HttpParser() = default;
HttpParser(const HttpParser&) = delete;
HttpParser& operator=(const HttpParser&) = delete;
bool finished() const { bool finished() const {
return finished_; return finished_;
} }
@ -25,6 +29,13 @@ protected:
void ParseContentLength(const std::string& line); 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: protected:
// The result HTTP message. // The result HTTP message.
HttpMessage* message_; HttpMessage* message_;
@ -34,7 +45,9 @@ protected:
// Data waiting to be parsed. // Data waiting to be parsed.
std::string pending_data_; 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 start_line_parsed_;
bool content_length_parsed_; bool content_length_parsed_;
bool header_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)); buffers.push_back(boost::asio::buffer(misc_strings::CRLF));
// Content (optional) // Content (optional)
if (IsContentLengthValid()) { if (!content_.empty()) {
buffers.push_back(boost::asio::buffer(content_)); 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::size_t content_length,
std::string&& content) { std::string&& content) {
response_.SetContentType(content_type); response_.SetContentType(content_type);
response_.SetContentLength(content.length()); response_.SetContent(std::move(content));
response_.set_content(std::move(content));
} }
void HttpSession::SendResponse() { void HttpSession::SendResponse() {

@ -40,11 +40,9 @@ Error SoapClient::Call(const std::string& operation,
http_request.set_method(kHttpPost); http_request.set_method(kHttpPost);
http_request.set_url(url_); http_request.set_url(url_);
http_request.SetContentType(kTextXmlUtf8); http_request.SetContentType(kTextXmlUtf8);
http_request.SetContentLength(http_content.size()); http_request.SetContent(std::move(http_content));
http_request.SetHost(host_, port_); http_request.SetHost(host_, port_);
http_request.SetHeader(kSoapAction, operation); http_request.SetHeader(kSoapAction, operation);
http_request.set_content(std::move(http_content));
http_request.Build(); http_request.Build();
HttpResponse http_response; HttpResponse http_response;

Loading…
Cancel
Save