Rework book example to support upload book photo.
This commit is contained in:
@@ -4,6 +4,8 @@
|
|||||||
#include <list>
|
#include <list>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "boost/filesystem/path.hpp"
|
||||||
|
|
||||||
// In-memory test data.
|
// In-memory test data.
|
||||||
// There should be some database in a real product.
|
// There should be some database in a real product.
|
||||||
|
|
||||||
@@ -11,6 +13,7 @@ struct Book {
|
|||||||
std::string id;
|
std::string id;
|
||||||
std::string title;
|
std::string title;
|
||||||
double price;
|
double price;
|
||||||
|
boost::filesystem::path photo;
|
||||||
|
|
||||||
bool IsNull() const { return id.empty(); }
|
bool IsNull() const { return id.empty(); }
|
||||||
};
|
};
|
||||||
@@ -21,7 +24,9 @@ extern const Book kNullBook;
|
|||||||
|
|
||||||
class BookStore {
|
class BookStore {
|
||||||
public:
|
public:
|
||||||
const std::list<Book>& books() const { return books_; }
|
const std::list<Book>& books() const {
|
||||||
|
return books_;
|
||||||
|
}
|
||||||
|
|
||||||
const Book& GetBook(const std::string& id) const;
|
const Book& GetBook(const std::string& id) const;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
|
#include "boost/algorithm/string/predicate.hpp"
|
||||||
|
#include "boost/filesystem/operations.hpp"
|
||||||
|
|
||||||
#include "json/json.h"
|
#include "json/json.h"
|
||||||
|
|
||||||
#include "webcc/client_session.h"
|
#include "webcc/client_session.h"
|
||||||
@@ -17,6 +20,8 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace bfs = boost::filesystem;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
class BookClient {
|
class BookClient {
|
||||||
@@ -27,7 +32,8 @@ public:
|
|||||||
|
|
||||||
bool ListBooks(std::list<Book>* books);
|
bool ListBooks(std::list<Book>* books);
|
||||||
|
|
||||||
bool CreateBook(const std::string& title, double price, std::string* id);
|
bool CreateBook(const std::string& title, double price,
|
||||||
|
const bfs::path& photo, std::string* id);
|
||||||
|
|
||||||
bool GetBook(const std::string& id, Book* book);
|
bool GetBook(const std::string& id, Book* book);
|
||||||
|
|
||||||
@@ -37,6 +43,8 @@ public:
|
|||||||
bool DeleteBook(const std::string& id);
|
bool DeleteBook(const std::string& id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool CheckPhoto(const bfs::path& photo);
|
||||||
|
|
||||||
// Check HTTP response status.
|
// Check HTTP response status.
|
||||||
bool CheckStatus(webcc::ResponsePtr response, int expected_status);
|
bool CheckStatus(webcc::ResponsePtr response, int expected_status);
|
||||||
|
|
||||||
@@ -83,15 +91,14 @@ bool BookClient::ListBooks(std::list<Book>* books) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool BookClient::CreateBook(const std::string& title, double price,
|
bool BookClient::CreateBook(const std::string& title, double price,
|
||||||
std::string* id) {
|
const bfs::path& photo, std::string* id) {
|
||||||
Json::Value req_json;
|
Json::Value req_json;
|
||||||
req_json["title"] = title;
|
req_json["title"] = title;
|
||||||
req_json["price"] = price;
|
req_json["price"] = price;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto r = session_.Send(WEBCC_POST(url_).Path("books").
|
auto r = session_.Send(WEBCC_POST(url_).Path("books").
|
||||||
Body(JsonToString(req_json))
|
Body(JsonToString(req_json))());
|
||||||
());
|
|
||||||
|
|
||||||
if (!CheckStatus(r, webcc::Status::kCreated)) {
|
if (!CheckStatus(r, webcc::Status::kCreated)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -100,7 +107,20 @@ bool BookClient::CreateBook(const std::string& title, double price,
|
|||||||
Json::Value rsp_json = StringToJson(r->data());
|
Json::Value rsp_json = StringToJson(r->data());
|
||||||
*id = rsp_json["id"].asString();
|
*id = rsp_json["id"].asString();
|
||||||
|
|
||||||
return !id->empty();
|
if (id->empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CheckPhoto(photo)) {
|
||||||
|
r = session_.Send(WEBCC_PUT(url_).Path("books").Path(*id).Path("photo").
|
||||||
|
File(photo)());
|
||||||
|
|
||||||
|
if (!CheckStatus(r, webcc::Status::kOK)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
} catch (const webcc::Error& error) {
|
} catch (const webcc::Error& error) {
|
||||||
std::cerr << error << std::endl;
|
std::cerr << error << std::endl;
|
||||||
@@ -132,8 +152,7 @@ bool BookClient::UpdateBook(const std::string& id, const std::string& title,
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
auto r = session_.Send(WEBCC_PUT(url_).Path("books").Path(id).
|
auto r = session_.Send(WEBCC_PUT(url_).Path("books").Path(id).
|
||||||
Body(JsonToString(json))
|
Body(JsonToString(json))());
|
||||||
());
|
|
||||||
|
|
||||||
if (!CheckStatus(r, webcc::Status::kOK)) {
|
if (!CheckStatus(r, webcc::Status::kOK)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -163,6 +182,23 @@ bool BookClient::DeleteBook(const std::string& id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BookClient::CheckPhoto(const bfs::path& photo) {
|
||||||
|
if (photo.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bfs::is_regular_file(photo) || !bfs::exists(photo)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ext = photo.extension().string();
|
||||||
|
if (!boost::iequals(ext, ".jpg") && !boost::iequals(ext, ".jpeg")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool BookClient::CheckStatus(webcc::ResponsePtr response, int expected_status) {
|
bool BookClient::CheckStatus(webcc::ResponsePtr response, int expected_status) {
|
||||||
if (response->status() != expected_status) {
|
if (response->status() != expected_status) {
|
||||||
LOG_ERRO("HTTP status error (actual: %d, expected: %d).",
|
LOG_ERRO("HTTP status error (actual: %d, expected: %d).",
|
||||||
@@ -194,40 +230,38 @@ void PrintBookList(const std::list<Book>& books) {
|
|||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
std::cout << "usage: rest_book_client <url> [timeout]" << std::endl;
|
std::cout << "usage: rest_book_client <url>" << std::endl;
|
||||||
std::cout << std::endl;
|
|
||||||
std::cout << "examples:" << std::endl;
|
std::cout << "examples:" << std::endl;
|
||||||
std::cout << " $ rest_book_client http://localhost:8080" << std::endl;
|
std::cout << " $ rest_book_client http://localhost:8080" << std::endl;
|
||||||
std::cout << " $ rest_book_client http://localhost:8080 2" << std::endl;
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string url = argv[1];
|
std::string url = argv[1];
|
||||||
|
|
||||||
int timeout = 0;
|
|
||||||
if (argc > 2) {
|
|
||||||
timeout = std::atoi(argv[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE_FILE_OVERWRITE);
|
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE_FILE_OVERWRITE);
|
||||||
|
|
||||||
BookClient client(url, timeout);
|
BookClient client(url);
|
||||||
|
|
||||||
PrintSeparator();
|
PrintSeparator();
|
||||||
|
|
||||||
|
// List all books.
|
||||||
|
|
||||||
std::list<Book> books;
|
std::list<Book> books;
|
||||||
if (client.ListBooks(&books)) {
|
if (client.ListBooks(&books)) {
|
||||||
PrintBookList(books);
|
PrintBookList(books);
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintSeparator();
|
PrintSeparator();
|
||||||
|
|
||||||
|
// Create a new book.
|
||||||
|
|
||||||
std::string id;
|
std::string id;
|
||||||
if (client.CreateBook("1984", 12.3, &id)) {
|
if (client.CreateBook("1984", 12.3, "", &id)) {
|
||||||
std::cout << "Book ID: " << id << std::endl;
|
std::cout << "Book ID: " << id << std::endl;
|
||||||
} else {
|
} else {
|
||||||
id = "1";
|
return 1;
|
||||||
std::cout << "Book ID: " << id << " (faked)"<< std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintSeparator();
|
PrintSeparator();
|
||||||
@@ -235,6 +269,8 @@ int main(int argc, char* argv[]) {
|
|||||||
books.clear();
|
books.clear();
|
||||||
if (client.ListBooks(&books)) {
|
if (client.ListBooks(&books)) {
|
||||||
PrintBookList(books);
|
PrintBookList(books);
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintSeparator();
|
PrintSeparator();
|
||||||
@@ -242,21 +278,29 @@ int main(int argc, char* argv[]) {
|
|||||||
Book book;
|
Book book;
|
||||||
if (client.GetBook(id, &book)) {
|
if (client.GetBook(id, &book)) {
|
||||||
PrintBook(book);
|
PrintBook(book);
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintSeparator();
|
PrintSeparator();
|
||||||
|
|
||||||
client.UpdateBook(id, "1Q84", 32.1);
|
if (!client.UpdateBook(id, "1Q84", 32.1)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
PrintSeparator();
|
PrintSeparator();
|
||||||
|
|
||||||
if (client.GetBook(id, &book)) {
|
if (client.GetBook(id, &book)) {
|
||||||
PrintBook(book);
|
PrintBook(book);
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintSeparator();
|
PrintSeparator();
|
||||||
|
|
||||||
client.DeleteBook(id);
|
if (!client.DeleteBook(id)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
PrintSeparator();
|
PrintSeparator();
|
||||||
|
|
||||||
|
|||||||
+183
-141
@@ -1,8 +1,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <list>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
#include "boost/filesystem/operations.hpp"
|
||||||
|
|
||||||
#include "json/json.h"
|
#include "json/json.h"
|
||||||
|
|
||||||
@@ -21,24 +20,18 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace bfs = boost::filesystem;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
static BookStore g_book_store;
|
static BookStore g_book_store;
|
||||||
|
|
||||||
static void Sleep(int seconds) {
|
|
||||||
if (seconds > 0) {
|
|
||||||
LOG_INFO("Sleep %d seconds...", seconds);
|
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(seconds));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
// BookListView
|
||||||
|
|
||||||
|
// URL: /books
|
||||||
class BookListView : public webcc::View {
|
class BookListView : public webcc::View {
|
||||||
public:
|
public:
|
||||||
explicit BookListView(int sleep_seconds) : sleep_seconds_(sleep_seconds) {
|
|
||||||
}
|
|
||||||
|
|
||||||
webcc::ResponsePtr Handle(webcc::RequestPtr request) override {
|
webcc::ResponsePtr Handle(webcc::RequestPtr request) override {
|
||||||
if (request->method() == "GET") {
|
if (request->method() == "GET") {
|
||||||
return Get(request);
|
return Get(request);
|
||||||
@@ -53,24 +46,116 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// Get a list of books based on query parameters.
|
// Get a list of books based on query parameters.
|
||||||
webcc::ResponsePtr Get(webcc::RequestPtr request);
|
webcc::ResponsePtr Get(webcc::RequestPtr request) {
|
||||||
|
Json::Value json(Json::arrayValue);
|
||||||
|
|
||||||
|
for (const Book& book : g_book_store.books()) {
|
||||||
|
json.append(BookToJson(book));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return all books as a JSON array.
|
||||||
|
|
||||||
|
return webcc::ResponseBuilder{}.OK().Body(JsonToString(json)).Json().Utf8()();
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new book.
|
// Create a new book.
|
||||||
webcc::ResponsePtr Post(webcc::RequestPtr request);
|
webcc::ResponsePtr Post(webcc::RequestPtr request) {
|
||||||
|
Book book;
|
||||||
|
if (JsonStringToBook(request->data(), &book)) {
|
||||||
|
std::string id = g_book_store.AddBook(book);
|
||||||
|
|
||||||
private:
|
Json::Value json;
|
||||||
// Sleep some seconds before send back the response.
|
json["id"] = id;
|
||||||
// For testing timeout control in client side.
|
|
||||||
int sleep_seconds_;
|
return webcc::ResponseBuilder{}.Created().Body(JsonToString(json)).Json().Utf8()();
|
||||||
|
} else {
|
||||||
|
// Invalid JSON
|
||||||
|
return webcc::ResponseBuilder{}.BadRequest()();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
// BookDetailView
|
||||||
|
|
||||||
// The URL is like '/books/{BookID}', and the 'args' parameter
|
// URL: /books/{id}
|
||||||
// contains the matched book ID.
|
|
||||||
class BookDetailView : public webcc::View {
|
class BookDetailView : public webcc::View {
|
||||||
public:
|
public:
|
||||||
explicit BookDetailView(int sleep_seconds) : sleep_seconds_(sleep_seconds) {
|
webcc::ResponsePtr Handle(webcc::RequestPtr request) override {
|
||||||
|
if (request->method() == "GET") {
|
||||||
|
return Get(request);
|
||||||
|
}
|
||||||
|
if (request->method() == "PUT") {
|
||||||
|
return Put(request);
|
||||||
|
}
|
||||||
|
if (request->method() == "DELETE") {
|
||||||
|
return Delete(request);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Get the detailed information of a book.
|
||||||
|
webcc::ResponsePtr Get(webcc::RequestPtr request) {
|
||||||
|
if (request->args().size() != 1) {
|
||||||
|
// NotFound means the resource specified by the URL cannot be found.
|
||||||
|
// BadRequest could be another choice.
|
||||||
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& id = request->args()[0];
|
||||||
|
|
||||||
|
const Book& book = g_book_store.GetBook(id);
|
||||||
|
if (book.IsNull()) {
|
||||||
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
|
}
|
||||||
|
|
||||||
|
return webcc::ResponseBuilder{}.OK().Body(BookToJsonString(book)).Json().Utf8()();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update a book.
|
||||||
|
webcc::ResponsePtr Put(webcc::RequestPtr request) {
|
||||||
|
if (request->args().size() != 1) {
|
||||||
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& id = request->args()[0];
|
||||||
|
|
||||||
|
Book book;
|
||||||
|
if (!JsonStringToBook(request->data(), &book)) {
|
||||||
|
return webcc::ResponseBuilder{}.BadRequest()();
|
||||||
|
}
|
||||||
|
|
||||||
|
book.id = id;
|
||||||
|
g_book_store.UpdateBook(book);
|
||||||
|
|
||||||
|
return webcc::ResponseBuilder{}.OK()();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a book.
|
||||||
|
webcc::ResponsePtr Delete(webcc::RequestPtr request) {
|
||||||
|
if (request->args().size() != 1) {
|
||||||
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& id = request->args()[0];
|
||||||
|
|
||||||
|
if (!g_book_store.DeleteBook(id)) {
|
||||||
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
|
}
|
||||||
|
|
||||||
|
return webcc::ResponseBuilder{}.OK()();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// BookPhotoView
|
||||||
|
|
||||||
|
// URL: /books/{id}/photo
|
||||||
|
class BookPhotoView : public webcc::View {
|
||||||
|
public:
|
||||||
|
explicit BookPhotoView(bfs::path upload_dir)
|
||||||
|
: upload_dir_(std::move(upload_dir)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
webcc::ResponsePtr Handle(webcc::RequestPtr request) override {
|
webcc::ResponsePtr Handle(webcc::RequestPtr request) override {
|
||||||
@@ -89,127 +174,79 @@ public:
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
// Stream the request data, an image, of PUT into a temp file.
|
||||||
// Get the detailed information of a book.
|
bool Stream(const std::string& method) override {
|
||||||
webcc::ResponsePtr Get(webcc::RequestPtr request);
|
return method == "PUT";
|
||||||
|
}
|
||||||
// Update a book.
|
|
||||||
webcc::ResponsePtr Put(webcc::RequestPtr request);
|
|
||||||
|
|
||||||
// Delete a book.
|
|
||||||
webcc::ResponsePtr Delete(webcc::RequestPtr request);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Sleep some seconds before send back the response.
|
// Get the photo of the book.
|
||||||
// For testing timeout control in client side.
|
// TODO: Check content type to see if it's JPEG.
|
||||||
int sleep_seconds_;
|
webcc::ResponsePtr Get(webcc::RequestPtr request) {
|
||||||
|
if (request->args().size() != 1) {
|
||||||
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& id = request->args()[0];
|
||||||
|
const Book& book = g_book_store.GetBook(id);
|
||||||
|
if (book.IsNull()) {
|
||||||
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
|
}
|
||||||
|
|
||||||
|
bfs::path photo_path = GetPhotoPath(id);
|
||||||
|
if (!bfs::exists(photo_path)) {
|
||||||
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
|
}
|
||||||
|
|
||||||
|
// File() might throw Error::kFileError.
|
||||||
|
// TODO: Avoid exception handling.
|
||||||
|
try {
|
||||||
|
return webcc::ResponseBuilder{}.OK().File(photo_path)();
|
||||||
|
} catch (const webcc::Error&) {
|
||||||
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the photo of the book.
|
||||||
|
// TODO: Check content type to see if it's JPEG.
|
||||||
|
webcc::ResponsePtr Put(webcc::RequestPtr request) {
|
||||||
|
if (request->args().size() != 1) {
|
||||||
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& id = request->args()[0];
|
||||||
|
|
||||||
|
const Book& book = g_book_store.GetBook(id);
|
||||||
|
if (book.IsNull()) {
|
||||||
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
|
}
|
||||||
|
|
||||||
|
request->file_body()->Move(GetPhotoPath(id));
|
||||||
|
|
||||||
|
return webcc::ResponseBuilder{}.OK()();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the photo of the book.
|
||||||
|
webcc::ResponsePtr Delete(webcc::RequestPtr request) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bfs::path GetPhotoPath(const std::string& book_id) const {
|
||||||
|
return upload_dir_ / "book_photo" / (book_id + ".jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bfs::path upload_dir_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Return all books as a JSON array.
|
|
||||||
webcc::ResponsePtr BookListView::Get(webcc::RequestPtr request) {
|
|
||||||
Sleep(sleep_seconds_);
|
|
||||||
|
|
||||||
Json::Value json(Json::arrayValue);
|
|
||||||
|
|
||||||
for (const Book& book : g_book_store.books()) {
|
|
||||||
json.append(BookToJson(book));
|
|
||||||
}
|
|
||||||
|
|
||||||
return webcc::ResponseBuilder{}.OK().Body(JsonToString(json)).Json().
|
|
||||||
Utf8()();
|
|
||||||
}
|
|
||||||
|
|
||||||
webcc::ResponsePtr BookListView::Post(webcc::RequestPtr request) {
|
|
||||||
Sleep(sleep_seconds_);
|
|
||||||
|
|
||||||
Book book;
|
|
||||||
if (JsonStringToBook(request->data(), &book)) {
|
|
||||||
std::string id = g_book_store.AddBook(book);
|
|
||||||
|
|
||||||
Json::Value json;
|
|
||||||
json["id"] = id;
|
|
||||||
|
|
||||||
return webcc::ResponseBuilder{}.Created().Body(JsonToString(json)).
|
|
||||||
Json().Utf8()();
|
|
||||||
} else {
|
|
||||||
// Invalid JSON
|
|
||||||
return webcc::ResponseBuilder{}.BadRequest()();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
webcc::ResponsePtr BookDetailView::Get(webcc::RequestPtr request) {
|
|
||||||
Sleep(sleep_seconds_);
|
|
||||||
|
|
||||||
if (request->args().size() != 1) {
|
|
||||||
// NotFound means the resource specified by the URL cannot be found.
|
|
||||||
// BadRequest could be another choice.
|
|
||||||
return webcc::ResponseBuilder{}.NotFound()();
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& book_id = request->args()[0];
|
|
||||||
|
|
||||||
const Book& book = g_book_store.GetBook(book_id);
|
|
||||||
if (book.IsNull()) {
|
|
||||||
return webcc::ResponseBuilder{}.NotFound()();
|
|
||||||
}
|
|
||||||
|
|
||||||
return webcc::ResponseBuilder{}.OK().Body(BookToJsonString(book)).
|
|
||||||
Json().Utf8()();
|
|
||||||
}
|
|
||||||
|
|
||||||
webcc::ResponsePtr BookDetailView::Put(webcc::RequestPtr request) {
|
|
||||||
Sleep(sleep_seconds_);
|
|
||||||
|
|
||||||
if (request->args().size() != 1) {
|
|
||||||
return webcc::ResponseBuilder{}.NotFound()();
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& book_id = request->args()[0];
|
|
||||||
|
|
||||||
Book book;
|
|
||||||
if (!JsonStringToBook(request->data(), &book)) {
|
|
||||||
return webcc::ResponseBuilder{}.BadRequest()();
|
|
||||||
}
|
|
||||||
|
|
||||||
book.id = book_id;
|
|
||||||
g_book_store.UpdateBook(book);
|
|
||||||
|
|
||||||
return webcc::ResponseBuilder{}.OK()();
|
|
||||||
}
|
|
||||||
|
|
||||||
webcc::ResponsePtr BookDetailView::Delete(webcc::RequestPtr request) {
|
|
||||||
Sleep(sleep_seconds_);
|
|
||||||
|
|
||||||
if (request->args().size() != 1) {
|
|
||||||
return webcc::ResponseBuilder{}.NotFound()();
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& book_id = request->args()[0];
|
|
||||||
|
|
||||||
if (!g_book_store.DeleteBook(book_id)) {
|
|
||||||
return webcc::ResponseBuilder{}.NotFound()();
|
|
||||||
}
|
|
||||||
|
|
||||||
return webcc::ResponseBuilder{}.OK()();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
if (argc < 2) {
|
if (argc < 3) {
|
||||||
std::cout << "usage: rest_book_server <port> [seconds]" << std::endl;
|
std::cout << "usage: rest_book_server <port> <upload_dir>" << std::endl;
|
||||||
std::cout << std::endl;
|
|
||||||
std::cout << "If |seconds| is provided, the server will sleep, for testing "
|
|
||||||
<< "timeout, before " << std::endl
|
|
||||||
<< "send back each response." << std::endl;
|
|
||||||
std::cout << std::endl;
|
|
||||||
std::cout << "examples:" << std::endl;
|
std::cout << "examples:" << std::endl;
|
||||||
std::cout << " $ rest_book_server 8080" << std::endl;
|
std::cout << " $ rest_book_server 8080 D:/upload" << std::endl;
|
||||||
std::cout << " $ rest_book_server 8080 3" << std::endl;
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,20 +254,25 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
std::uint16_t port = static_cast<std::uint16_t>(std::atoi(argv[1]));
|
std::uint16_t port = static_cast<std::uint16_t>(std::atoi(argv[1]));
|
||||||
|
|
||||||
int sleep_seconds = 0;
|
bfs::path upload_dir = argv[2];
|
||||||
if (argc >= 3) {
|
if (!bfs::is_directory(upload_dir) || !bfs::exists(upload_dir)) {
|
||||||
sleep_seconds = std::atoi(argv[2]);
|
std::cerr << "Invalid upload dir!" << std::endl;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
webcc::Server server(port);
|
webcc::Server server(port); // No doc root
|
||||||
|
|
||||||
server.Route("/books",
|
server.Route("/books",
|
||||||
std::make_shared<BookListView>(sleep_seconds),
|
std::make_shared<BookListView>(),
|
||||||
{ "GET", "POST" });
|
{ "GET", "POST" });
|
||||||
|
|
||||||
server.Route(webcc::R("/books/(\\d+)"),
|
server.Route(webcc::R("/books/(\\d+)"),
|
||||||
std::make_shared<BookDetailView>(sleep_seconds),
|
std::make_shared<BookDetailView>(),
|
||||||
|
{ "GET", "PUT", "DELETE" });
|
||||||
|
|
||||||
|
server.Route(webcc::R("/books/(\\d+)/photo"),
|
||||||
|
std::make_shared<BookPhotoView>(upload_dir),
|
||||||
{ "GET", "PUT", "DELETE" });
|
{ "GET", "PUT", "DELETE" });
|
||||||
|
|
||||||
server.Run(2);
|
server.Run(2);
|
||||||
|
|||||||
@@ -43,6 +43,18 @@ ResponsePtr ResponseBuilder::operator()() {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResponseBuilder& ResponseBuilder::File(const webcc::Path& path,
|
||||||
|
bool infer_media_type,
|
||||||
|
std::size_t chunk_size) {
|
||||||
|
body_.reset(new FileBody{ path, chunk_size });
|
||||||
|
|
||||||
|
if (infer_media_type) {
|
||||||
|
media_type_ = media_types::FromExtension(path.extension().string());
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
ResponseBuilder& ResponseBuilder::Date() {
|
ResponseBuilder& ResponseBuilder::Date() {
|
||||||
headers_.push_back(headers::kDate);
|
headers_.push_back(headers::kDate);
|
||||||
headers_.push_back(utility::GetTimestamp());
|
headers_.push_back(utility::GetTimestamp());
|
||||||
|
|||||||
@@ -92,6 +92,11 @@ public:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use the file content as body.
|
||||||
|
// NOTE: Error::kFileError might be thrown.
|
||||||
|
ResponseBuilder& File(const webcc::Path& path, bool infer_media_type = true,
|
||||||
|
std::size_t chunk_size = 1024);
|
||||||
|
|
||||||
ResponseBuilder& Header(const std::string& key, const std::string& value) {
|
ResponseBuilder& Header(const std::string& key, const std::string& value) {
|
||||||
headers_.push_back(key);
|
headers_.push_back(key);
|
||||||
headers_.push_back(value);
|
headers_.push_back(value);
|
||||||
|
|||||||
Reference in New Issue
Block a user