复现已有算法

This commit is contained in:
cloud
2026-07-14 15:43:18 +08:00
parent abebd2a683
commit 50b8111fd9
860 changed files with 182250 additions and 18 deletions
+130
View File
@@ -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
View File
@@ -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();
}