复现已有算法
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
This folder contains a [jsonstore](https://github.com/bluzi/jsonstore)-like storage service. But is multi-threaded and stores everything in memory. Serving as a showcase on how to build a minimally useful RESTful APIs in Drogon.
|
||||
|
||||
## API
|
||||
|
||||
#### /get-token
|
||||
Generate a token that the user can use to create, read, modify and delete records.
|
||||
|
||||
* **method**: GET
|
||||
* **URL params**: None
|
||||
* **Body**: None
|
||||
* **Success response**
|
||||
* **Code**: 200
|
||||
* **Content**: `{"token":"3a322920d42ef0763152a6efff2ed51985530aedd45370f92fd0f0b8dcc30220"}`
|
||||
* **Sample call**
|
||||
|
||||
```bash
|
||||
❯ curl -XGET http://localhost:8848/get-token
|
||||
{"token":"3a322920d42ef0763152a6efff2ed51985530aedd45370f92fd0f0b8dcc30220"}
|
||||
```
|
||||
|
||||
#### /{token}
|
||||
|
||||
Create a new JSON object associated with the token
|
||||
|
||||
* **method**: POST
|
||||
* **URL params**: None
|
||||
* **Body**: The initial JSON object to store
|
||||
* **Success response**
|
||||
* **Code**: 200
|
||||
* **Content**: `{"ok":true}`
|
||||
* **Failed response**:
|
||||
* **Code**: 500
|
||||
* **Why**: Either the token already is associated with the data or request body/content-type is not JSON.
|
||||
* **Content**: `{"ok":false}`
|
||||
* **Sample call**
|
||||
|
||||
```bash
|
||||
❯ curl -XPOST http://localhost:8848/3a322920d42ef0763152a6efff2ed51985530aedd45370f92fd0f0b8dcc30220 \
|
||||
-H 'content-type: application/json' -d '{"foo":{"bar":42}}'
|
||||
{"ok":true}
|
||||
```
|
||||
|
||||
Delete the JSON object associated with the token
|
||||
|
||||
* **method**: DELETE
|
||||
* **URL params**: None
|
||||
* **Body**: None
|
||||
* **Success response**
|
||||
* **Code**: 200
|
||||
* **Content**: `{"ok":true}`
|
||||
* **Sample call**
|
||||
|
||||
```bash
|
||||
❯ curl -XDELETE http://localhost:8848/3a322920d42ef0763152a6efff2ed51985530aedd45370f92fd0f0b8dcc30220
|
||||
{"ok":true}
|
||||
```
|
||||
|
||||
#### /{token}/{some/path/to/data}
|
||||
|
||||
Retrieve data at and below the specified path
|
||||
* **method**: GET
|
||||
* **URL params**: None
|
||||
* **Body**: None
|
||||
* **Success response**
|
||||
* **Code**: 200
|
||||
* **Content**: `{"foo":{"bar":42}}`
|
||||
* **Failed response**:
|
||||
* **Code**: 500
|
||||
* **Why**: No data associated with the provided token, or it does not exist in the JSON object.
|
||||
* **Content**: `{"ok":false}`
|
||||
* **Sample call**
|
||||
|
||||
```bash
|
||||
❯ curl -XGET http://localhost:8848/3a322920d42ef0763152a6efff2ed51985530aedd45370f92fd0f0b8dcc30220/
|
||||
{"foo":{"bar":42}}
|
||||
```
|
||||
|
||||
Update data at the specified path
|
||||
* **method**: PUT
|
||||
* **URL params**: None
|
||||
* **Body**: The JSON object you wish to replace to
|
||||
* **Success response**
|
||||
* **Code**: 200
|
||||
* **Content**: `{"ok":true}`
|
||||
* **Failed response**:
|
||||
* **Code**: 500
|
||||
* **Why**: No data associated with the provided token or it does not exist in the JSON object.
|
||||
* **Content**: `{"ok":false}`
|
||||
* **Sample call**
|
||||
|
||||
```bash
|
||||
❯ curl -XPUT http://localhost:8848/3a322920d42ef0763152a6efff2ed51985530aedd45370f92fd0f0b8dcc30220/foo \
|
||||
-H 'content-type: application/json' -d '{"fruit":"apple"}'
|
||||
{"ok":true}
|
||||
```
|
||||
|
||||
## Example use
|
||||
|
||||
```bash
|
||||
export URL="http://localhost:8848"
|
||||
export TOKEN=`curl $URL/get-token -s | sed 's/.*"\([0-9a-f]*\)".*/\1/'`
|
||||
printf "Token is: $TOKEN\n"
|
||||
|
||||
printf 'Creating new data \n> '
|
||||
curl -XPOST $URL/$TOKEN -H 'content-type: application/json' -d '{"foo":{"bar":42}}'
|
||||
printf '\nRetrieving value of data["foo"]["bar"] \n> '
|
||||
curl $URL/$TOKEN/foo/bar
|
||||
printf '\nModifing data \n> '
|
||||
curl -XPUT $URL/$TOKEN/foo -H 'content-type: application/json' -d '{"zoo":"zebra"}'
|
||||
printf '\nNow data["foo"]["bar"] no longer exists \n> '
|
||||
curl $URL/$TOKEN/foo/bar
|
||||
printf '\nDelete data \n> '
|
||||
curl -XDELETE $URL/$TOKEN
|
||||
echo
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Token is: 5e73ba044b45e68b4856925faea268391091f39fc62ab8c58955cf20957018fa
|
||||
Creating new data
|
||||
> {"ok":true}
|
||||
Retrieving value of data["foo"]["bar"]
|
||||
> 42
|
||||
Modifying data
|
||||
> {"ok":true}
|
||||
Now data["foo"]["bar"] no longer exists
|
||||
> {"ok":false}
|
||||
Delete data
|
||||
> {"ok":true}
|
||||
```
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
#include <drogon/drogon.h>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
HttpResponsePtr makeFailedResponse()
|
||||
{
|
||||
Json::Value json;
|
||||
json["ok"] = false;
|
||||
auto resp = HttpResponse::newHttpJsonResponse(json);
|
||||
resp->setStatusCode(k500InternalServerError);
|
||||
return resp;
|
||||
}
|
||||
|
||||
HttpResponsePtr makeSuccessResponse()
|
||||
{
|
||||
Json::Value json;
|
||||
json["ok"] = true;
|
||||
auto resp = HttpResponse::newHttpJsonResponse(json);
|
||||
return resp;
|
||||
}
|
||||
|
||||
std::string getRandomString(size_t n)
|
||||
{
|
||||
std::vector<unsigned char> random(n);
|
||||
utils::secureRandomBytes(random.data(), random.size());
|
||||
|
||||
// This is cryptographically safe as 256 mod 16 == 0
|
||||
static const std::string alphabets = "0123456789abcdef";
|
||||
assert(256 % alphabets.size() == 0);
|
||||
std::string randomString(n, '\0');
|
||||
for (size_t i = 0; i < n; i++)
|
||||
randomString[i] = alphabets[random[i] % alphabets.size()];
|
||||
return randomString;
|
||||
}
|
||||
|
||||
struct DataItem
|
||||
{
|
||||
Json::Value item;
|
||||
std::mutex mtx;
|
||||
};
|
||||
|
||||
class JsonStore : public HttpController<JsonStore>
|
||||
{
|
||||
public:
|
||||
METHOD_LIST_BEGIN
|
||||
ADD_METHOD_TO(JsonStore::getToken, "/get-token", Get);
|
||||
ADD_METHOD_VIA_REGEX(JsonStore::getItem, "/([a-f0-9]{64})/(.*)", Get);
|
||||
ADD_METHOD_VIA_REGEX(JsonStore::createItem, "/([a-f0-9]{64})", Post);
|
||||
ADD_METHOD_VIA_REGEX(JsonStore::deleteItem, "/([a-f0-9]{64})", Delete);
|
||||
ADD_METHOD_VIA_REGEX(JsonStore::updateItem, "/([a-f0-9]{64})/(.*)", Put);
|
||||
METHOD_LIST_END
|
||||
|
||||
void getToken(const HttpRequestPtr &,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
std::string randomString = getRandomString(64);
|
||||
Json::Value res;
|
||||
res["token"] = randomString;
|
||||
|
||||
callback(HttpResponse::newHttpJsonResponse(std::move(res)));
|
||||
}
|
||||
|
||||
void getItem(const HttpRequestPtr &,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||
const std::string &token,
|
||||
const std::string &path)
|
||||
{
|
||||
auto itemPtr = [this, &token]() -> std::shared_ptr<DataItem> {
|
||||
// It is possible that the item is being removed while another
|
||||
// thread tries to look it up. The mutex here prevents that from
|
||||
// happening.
|
||||
std::lock_guard<std::mutex> lock(storageMtx_);
|
||||
auto it = dataStore_.find(token);
|
||||
if (it == dataStore_.end())
|
||||
return nullptr;
|
||||
return it->second;
|
||||
}();
|
||||
if (itemPtr == nullptr)
|
||||
{
|
||||
callback(makeFailedResponse());
|
||||
return;
|
||||
}
|
||||
|
||||
auto &item = *itemPtr;
|
||||
// Prevents another thread from writing to the same item while this
|
||||
// thread reads. Could cause blockage if multiple clients are asking to
|
||||
// read the same object. But that should be rare.
|
||||
std::lock_guard<std::mutex> lock(item.mtx);
|
||||
Json::Value *valuePtr = walkJson(item.item, path);
|
||||
|
||||
if (valuePtr == nullptr)
|
||||
{
|
||||
callback(makeFailedResponse());
|
||||
return;
|
||||
}
|
||||
|
||||
auto resp = HttpResponse::newHttpJsonResponse(*valuePtr);
|
||||
callback(resp);
|
||||
}
|
||||
|
||||
void updateItem(const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||
const std::string &token,
|
||||
const std::string &path)
|
||||
{
|
||||
auto jsonPtr = req->jsonObject();
|
||||
auto itemPtr = [this, &token]() -> std::shared_ptr<DataItem> {
|
||||
// It is possible that the item is being removed while another
|
||||
// thread tries to look it up. The mutex here prevents that from
|
||||
// happening.
|
||||
std::lock_guard<std::mutex> lock(storageMtx_);
|
||||
auto it = dataStore_.find(token);
|
||||
if (it == dataStore_.end())
|
||||
return nullptr;
|
||||
return it->second;
|
||||
}();
|
||||
|
||||
if (itemPtr == nullptr || jsonPtr == nullptr)
|
||||
{
|
||||
callback(makeFailedResponse());
|
||||
return;
|
||||
}
|
||||
|
||||
auto &item = *itemPtr;
|
||||
std::lock_guard<std::mutex> lock(item.mtx);
|
||||
Json::Value *valuePtr = walkJson(item.item, path, 1);
|
||||
|
||||
if (valuePtr == nullptr)
|
||||
{
|
||||
callback(makeFailedResponse());
|
||||
return;
|
||||
}
|
||||
|
||||
std::string key = utils::splitString(path, "/").back();
|
||||
(*valuePtr)[key] = *jsonPtr;
|
||||
|
||||
callback(makeSuccessResponse());
|
||||
}
|
||||
|
||||
void createItem(const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||
const std::string &token)
|
||||
{
|
||||
auto jsonPtr = req->jsonObject();
|
||||
if (jsonPtr == nullptr)
|
||||
{
|
||||
callback(makeFailedResponse());
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(storageMtx_);
|
||||
if (dataStore_.find(token) == dataStore_.end())
|
||||
{
|
||||
auto item = std::make_shared<DataItem>();
|
||||
item->item = std::move(*jsonPtr);
|
||||
dataStore_.insert({token, std::move(item)});
|
||||
|
||||
callback(makeSuccessResponse());
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(makeFailedResponse());
|
||||
}
|
||||
}
|
||||
|
||||
void deleteItem(const HttpRequestPtr &,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||
const std::string &token)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(storageMtx_);
|
||||
dataStore_.erase(token);
|
||||
|
||||
callback(makeSuccessResponse());
|
||||
}
|
||||
|
||||
protected:
|
||||
static Json::Value *walkJson(Json::Value &json,
|
||||
const std::string &path,
|
||||
size_t ignore_back = 0)
|
||||
{
|
||||
auto pathElem = utils::splitString(path, "/", false);
|
||||
if (pathElem.size() >= ignore_back)
|
||||
pathElem.resize(pathElem.size() - ignore_back);
|
||||
Json::Value *valuePtr = &json;
|
||||
for (const auto &elem : pathElem)
|
||||
{
|
||||
if (valuePtr->isArray())
|
||||
{
|
||||
Json::Value &value = (*valuePtr)[std::stoi(elem)];
|
||||
if (value.isNull())
|
||||
return nullptr;
|
||||
|
||||
valuePtr = &value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Json::Value &value = (*valuePtr)[elem];
|
||||
if (value.isNull())
|
||||
return nullptr;
|
||||
|
||||
valuePtr = &value;
|
||||
}
|
||||
}
|
||||
return valuePtr;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<DataItem>> dataStore_;
|
||||
std::mutex storageMtx_;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
app().addListener("127.0.0.1", 8848).run();
|
||||
}
|
||||
Reference in New Issue
Block a user