mirror of
https://github.com/RetroShare/RetroShare.git
synced 2024-12-28 08:59:37 -05:00
Merge pull request #2527 from G10h4ck/cmake_build
Integrate python3 JSON API generator into libretroshare
This commit is contained in:
commit
bc76b10792
@ -1,447 +1,10 @@
|
||||
// SPDX-FileCopyrightText: (C) 2004-2019 Retroshare Team <contact@retroshare.cc>
|
||||
// SPDX-FileCopyrightText: (C) 2021 Retroshare Team <contact@retroshare.cc>
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
RetroShare JSON API
|
||||
===================
|
||||
= Moved
|
||||
|
||||
:Cxx: C++
|
||||
JSON API generator is now part of `libretroshare` look under `src/jsonapi/`
|
||||
directory in `libretroshare` repository.
|
||||
|
||||
== How to use RetroShare JSON API
|
||||
|
||||
Look for methods marked with +@jsonapi+ doxygen custom command into
|
||||
+libretroshare/src/retroshare+. The method path is composed by service instance
|
||||
pointer name like +rsGxsChannels+ for +RsGxsChannels+, and the method name like
|
||||
+createGroup+ and pass the input paramethers as a JSON object.
|
||||
|
||||
.Service instance pointer in rsgxschannels.h
|
||||
[source,cpp]
|
||||
--------------------------------------------------------------------------------
|
||||
/**
|
||||
* Pointer to global instance of RsGxsChannels service implementation
|
||||
* @jsonapi{development}
|
||||
*/
|
||||
extern RsGxsChannels* rsGxsChannels;
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.Method declaration in rsgxschannels.h
|
||||
[source,cpp]
|
||||
--------------------------------------------------------------------------------
|
||||
/**
|
||||
* @brief Request channel creation.
|
||||
* The action is performed asyncronously, so it could fail in a subsequent
|
||||
* phase even after returning true.
|
||||
* @jsonapi{development}
|
||||
* @param[out] token Storage for RsTokenService token to track request
|
||||
* status.
|
||||
* @param[in] group Channel data (name, description...)
|
||||
* @return false on error, true otherwise
|
||||
*/
|
||||
virtual bool createGroup(uint32_t& token, RsGxsChannelGroup& group) = 0;
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.paramethers.json
|
||||
[source,json]
|
||||
--------------------------------------------------------------------------------
|
||||
{
|
||||
"group":{
|
||||
"mMeta":{
|
||||
"mGroupName":"JSON test group",
|
||||
"mGroupFlags":4,
|
||||
"mSignFlags":520
|
||||
},
|
||||
"mDescription":"JSON test group description"
|
||||
},
|
||||
"caller_data":"Here can go any kind of JSON data (even objects) that the caller want to get back together with the response"
|
||||
}
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.Calling the JSON API with curl on the terminal
|
||||
[source,bash]
|
||||
--------------------------------------------------------------------------------
|
||||
curl -u $API_USER --data @paramethers.json http://127.0.0.1:9092/rsGxsChannels/createGroup
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.JSON API call result
|
||||
[source,json]
|
||||
--------------------------------------------------------------------------------
|
||||
{
|
||||
"caller_data": "Here can go any kind of JSON data (even objects) that the caller want to get back together with the response",
|
||||
"retval": true,
|
||||
"token": 3
|
||||
}
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Even if it is less efficient because of URL encoding HTTP +GET+ method is
|
||||
supported too, so in cases where the client cannot use +POST+ she can still use
|
||||
+GET+ taking care of encoding the JSON data. With +curl+ this can be done at
|
||||
least in two different ways.
|
||||
|
||||
.Calling the JSON API with GET method with curl on the terminal
|
||||
[source,bash]
|
||||
--------------------------------------------------------------------------------
|
||||
curl -u $API_USER --get --data-urlencode jsonData@paramethers.json \
|
||||
http://127.0.0.1:9092/rsGxsChannels/createGroup
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Letting +curl+ do the encoding is much more elegant but it is semantically
|
||||
equivalent to the following.
|
||||
|
||||
.Calling the JSON API with GET method and pre-encoded data with curl on the terminal
|
||||
--------------------------------------------------------------------------------
|
||||
curl -u $API_USER http://127.0.0.1:9092/rsGxsChannels/createGroup?jsonData=%7B%0A%20%20%20%20%22group%22%3A%7B%0A%20%20%20%20%20%20%20%20%22mMeta%22%3A%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mGroupName%22%3A%22JSON%20test%20group%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mGroupFlags%22%3A4%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mSignFlags%22%3A520%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%22mDescription%22%3A%22JSON%20test%20group%20description%22%0A%20%20%20%20%7D%2C%0A%20%20%20%20%22caller_data%22%3A%22Here%20can%20go%20any%20kind%20of%20JSON%20data%20%28even%20objects%29%20that%20the%20caller%20want%20to%20get%20back%20together%20with%20the%20response%22%0A%7D
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Note that using +GET+ method +?jsonData=+ and then the JSON data URL encoded are
|
||||
added after the path in the HTTP request.
|
||||
|
||||
|
||||
== JSON API authentication
|
||||
|
||||
Most of JSON API methods require authentication as they give access to
|
||||
RetroShare user data, and we don't want any application running on the system
|
||||
eventually by other users be able to access private data indiscriminately.
|
||||
JSON API support HTTP Basic as authentication scheme, this is enough as JSON API
|
||||
server is intented for usage on the same system (127.0.0.1) not over an
|
||||
untrusted network.
|
||||
If you need to use JSON API over an untrusted network consider using a reverse
|
||||
proxy with HTTPS such as NGINX in front of JSON API server.
|
||||
If RetroShare login has been effectuated through the JSON API you can use your
|
||||
location SSLID as username and your PGP password as credential for the JSON API,
|
||||
but we suggests you use specific meaningful and human readable credentials for
|
||||
each JSON API client so the human user can have better control over which client
|
||||
can access the JSON API.
|
||||
|
||||
.NewToken.json
|
||||
[source,json]
|
||||
--------------------------------------------------------------------------------
|
||||
{
|
||||
"token": "myNewUser:myNewPassword"
|
||||
}
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.An authenticated client can authorize new tokens like this
|
||||
--------------------------------------------------------------------------------
|
||||
curl -u $API_USER --data @NewToken.json http://127.0.0.1:9092/jsonApiServer/authorizeToken
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.An unauthenticated JSON API client can request access with
|
||||
--------------------------------------------------------------------------------
|
||||
curl --data @NewToken.json http://127.0.0.1:9092/jsonApiServer/requestNewTokenAutorization
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
When an unauthenticated client request his token to be authorized, JSON API
|
||||
server will try to ask confirmation to the human user if possible through
|
||||
+mNewAccessRequestCallback+, if it is not possible or the user didn't authorized
|
||||
the token +false+ is returned.
|
||||
|
||||
|
||||
== Offer new RetroShare services through JSON API
|
||||
|
||||
To offer a retroshare service through the JSON API, first of all one need find
|
||||
the global pointer to the service instance and document it in doxygen syntax,
|
||||
plus marking with the custom doxygen command +@jsonapi{RS_VERSION}+ where
|
||||
+RS_VERSION+ is the retroshare version in which this service became available
|
||||
with the current semantic (major changes to the service semantic, changes the
|
||||
meaning of the service itself, so the version should be updated in the
|
||||
documentation in that case).
|
||||
|
||||
.Service instance pointer in rsgxschannels.h
|
||||
[source,cpp]
|
||||
--------------------------------------------------------------------------------
|
||||
/**
|
||||
* Pointer to global instance of RsGxsChannels service implementation
|
||||
* @jsonapi{development}
|
||||
*/
|
||||
extern RsGxsChannels* rsGxsChannels;
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
||||
Once the service instance itself is known to the JSON API you need to document
|
||||
in doxygen syntax and mark with the custom doxygen command
|
||||
+@jsonapi{RS_VERSION}+ the methods of the service that you want to make
|
||||
available through JSON API.
|
||||
|
||||
.Offering RsGxsChannels::getChannelDownloadDirectory in rsgxschannels.h
|
||||
[source,cpp]
|
||||
--------------------------------------------------------------------------------
|
||||
/**
|
||||
* Get download directory for the given channel
|
||||
* @jsonapi{development}
|
||||
* @param[in] channelId id of the channel
|
||||
* @param[out] directory reference to string where to store the path
|
||||
* @return false on error, true otherwise
|
||||
*/
|
||||
virtual bool getChannelDownloadDirectory( const RsGxsGroupId& channelId,
|
||||
std::string& directory ) = 0;
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
For each paramether you must specify if it is used as input +@param[in]+ as
|
||||
output +@param[out]+ or both +@param[inout]+. Paramethers and return value
|
||||
types must be of a type supported by +RsTypeSerializer+ which already support
|
||||
most basic types (+bool+, +std::string+...), +RsSerializable+ and containers of
|
||||
them like +std::vector<std::string>+. Paramethers passed by value and by
|
||||
reference of those types are both supported, while passing by pointer is not
|
||||
supported. If your paramether or return +class+/+struct+ type is not supported
|
||||
yet by +RsTypeSerializer+ most convenient approach is to make it derive from
|
||||
+RsSerializable+ and implement +serial_process+ method like I did with
|
||||
+RsGxsChannelGroup+.
|
||||
|
||||
.Deriving RsGxsChannelGroup from RsSerializable in rsgxschannels.h
|
||||
[source,cpp]
|
||||
--------------------------------------------------------------------------------
|
||||
struct RsGxsChannelGroup : RsSerializable
|
||||
{
|
||||
RsGroupMetaData mMeta;
|
||||
std::string mDescription;
|
||||
RsGxsImage mImage;
|
||||
|
||||
bool mAutoDownload;
|
||||
|
||||
/// @see RsSerializable
|
||||
virtual void serial_process( RsGenericSerializer::SerializeJob j,
|
||||
RsGenericSerializer::SerializeContext& ctx )
|
||||
{
|
||||
RS_SERIAL_PROCESS(mMeta);
|
||||
RS_SERIAL_PROCESS(mDescription);
|
||||
RS_SERIAL_PROCESS(mImage);
|
||||
RS_SERIAL_PROCESS(mAutoDownload);
|
||||
}
|
||||
};
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
You can do the same recursively for any member of your +struct+ that is not yet
|
||||
supported by +RsTypeSerializer+.
|
||||
|
||||
Some Retroshare {Cxx} API functions are asyncronous, historically RetroShare
|
||||
didn't follow a policy on how to expose asyncronous API so differents services
|
||||
and some times even differents method of the same service follow differents
|
||||
asyncronous patterns, thus making automatic generation of JSON API wrappers for
|
||||
those methods impractical. Instead of dealing with all those differents patterns
|
||||
I have chosed to support only one new pattern taking advantage of modern {Cxx}11
|
||||
and restbed features. On the {Cxx}11 side lambdas and +std::function+s are used,
|
||||
on the restbed side Server Side Events are used to send asyncronous results.
|
||||
|
||||
Lets see an example so it will be much esier to understand.
|
||||
|
||||
.RsGxsChannels::turtleSearchRequest asyncronous API
|
||||
[source,cpp]
|
||||
--------------------------------------------------------------------------------
|
||||
/**
|
||||
* @brief Request remote channels search
|
||||
* @jsonapi{development}
|
||||
* @param[in] matchString string to look for in the search
|
||||
* @param multiCallback function that will be called each time a search
|
||||
* result is received
|
||||
* @param[in] maxWait maximum wait time in seconds for search results
|
||||
* @return false on error, true otherwise
|
||||
*/
|
||||
virtual bool turtleSearchRequest(
|
||||
const std::string& matchString,
|
||||
const std::function<void (const RsGxsGroupSummary& result)>& multiCallback,
|
||||
std::time_t maxWait = 300 ) = 0;
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
+RsGxsChannels::turtleSearchRequest(...)+ is an asyncronous method because it
|
||||
send a channel search request on turtle network and then everytime a result is
|
||||
received from the network +multiCallback+ is called and the result is passed as
|
||||
parameter. To be supported by the automatic JSON API wrappers generator an
|
||||
asyncronous method need a parameter of type +std::function<void (...)>+ called
|
||||
+callback+ if the callback will be called only once or +multiCallback+ if the
|
||||
callback is expected to be called more then once like in this case.
|
||||
A second mandatory parameter is +maxWait+ of type +std::time_t+ it indicates the
|
||||
maximum amount of time in seconds for which the caller is willing to wait for
|
||||
results, in case the timeout is reached the callback will not be called anymore.
|
||||
|
||||
[IMPORTANT]
|
||||
================================================================================
|
||||
+callback+ and +multiCallback+ parameters documentation must *not* specify
|
||||
+[in]+, +[out]+, +[inout]+, in Doxygen documentation as this would fool the
|
||||
automatic wrapper generator, and ultimately break the compilation.
|
||||
================================================================================
|
||||
|
||||
.RsFiles::turtleSearchRequest asyncronous JSON API usage example
|
||||
[source,bash]
|
||||
--------------------------------------------------------------------------------
|
||||
$ cat turtle_search.json
|
||||
{
|
||||
"matchString":"linux"
|
||||
}
|
||||
$ curl --data @turtle_search.json http://127.0.0.1:9092/rsFiles/turtleSearchRequest
|
||||
data: {"retval":true}
|
||||
|
||||
data: {"results":[{"size":157631,"hash":"69709b4d01025584a8def5cd78ebbd1a3cf3fd05","name":"kill_bill_linux_1024x768.jpg"},{"size":192560,"hash":"000000000000000000009a93e5be8486c496f46c","name":"coffee_box_linux2.jpg"},{"size":455087,"hash":"9a93e5be8486c496f46c00000000000000000000","name":"Linux.png"},{"size":182004,"hash":"e8845280912ebf3779e400000000000000000000","name":"Linux_2_6.png"}]}
|
||||
|
||||
data: {"results":[{"size":668,"hash":"e8845280912ebf3779e400000000000000000000","name":"linux.png"},{"size":70,"hash":"e8845280912ebf3779e400000000000000000000","name":"kali-linux-2016.2-amd64.txt.sha1sum"},{"size":3076767744,"hash":"e8845280912ebf3779e400000000000000000000","name":"kali-linux-2016.2-amd64.iso"},{"size":2780872,"hash":"e8845280912ebf3779e400000000000000000000","name":"openwrt-ar71xx-generic-vmlinux.bin"},{"size":917504,"hash":"e8845280912ebf3779e400000000000000000000","name":"openwrt-ar71xx-generic-vmlinux.lzma"},{"size":2278404096,"hash":"e8845280912ebf3779e400000000000000000000","name":"gentoo-linux-livedvd-amd64-multilib-20160704.iso"},{"size":151770333,"hash":"e8845280912ebf3779e400000000000000000000","name":"flashtool-0.9.23.0-linux.tar.7z"},{"size":2847372,"hash":"e8845280912ebf3779e400000000000000000000","name":"openwrt-ar71xx-generic-vmlinux.elf"},{"size":1310720,"hash":"e8845280912ebf3779e400000000000000000000","name":"openwrt-ar71xx-generic-vmlinux.gz"},{"size":987809,"hash":"e8845280912ebf3779e400000000000000000000","name":"openwrt-ar71xx-generic-vmlinux-lzma.elf"}]}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
By default JSON API methods requires client authentication and their wrappers
|
||||
are automatically generated by +json-api-generator+.
|
||||
In some cases methods need do be accessible without authentication such as
|
||||
+rsLoginHelper/getLocations+ so in the doxygen documentaion they have the custom
|
||||
command +@jsonapi{RS_VERSION,unauthenticated}+.
|
||||
Other methods such as +/rsControl/rsGlobalShutDown+ need special care so they
|
||||
are marked with the custom doxygen command +@jsonapi{RS_VERSION,manualwrapper}+
|
||||
and their wrappers are not automatically generated but written manually into
|
||||
+JsonApiServer::JsonApiServer(...)+.
|
||||
|
||||
== Quirks
|
||||
|
||||
=== 64 bits integers handling
|
||||
|
||||
While JSON doesn't have problems representing 64 bits integers JavaScript, Dart
|
||||
and other languages are not capable to handle those numbers natively.
|
||||
To overcome this limitation JSON API output 64 bit integers as an object with
|
||||
two keys, one as proper integer and one as string representation.
|
||||
|
||||
.JSON API 64 bit integer output example
|
||||
[source,json]
|
||||
--------------------------------------------------------------------------------
|
||||
"lobby_id": { "xint64": 6215642878098695544, "xstr64": "6215642878098695544" }
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
So from languages that have proper 64bit integers support like Python or C++ one
|
||||
better read from `xint64` which is represented as a JSON integer, from languages
|
||||
where there is no proper 64bit integers support like JavaScript one can read from
|
||||
`xstr64` which is represented as JSON string (note that the first is not wrapped
|
||||
in "" while the latter is).
|
||||
|
||||
When one input a 64bit integer into the JSON API it first try to parse it as if
|
||||
it was sent the old way for retrocompatibility.
|
||||
|
||||
.JSON API 64 bit integer deprecated format input example
|
||||
[source,json]
|
||||
--------------------------------------------------------------------------------
|
||||
"lobby_id":6215642878098695544
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
This way is *DEPRECATED* and may disappear in the future, it is TEMPORALLY kept
|
||||
only for retrocompatibiliy with old clients.
|
||||
|
||||
If retrocompatible parsing attempt fail then it try to parse with the new way
|
||||
with proper JSON integer format.
|
||||
|
||||
.JSON API 64 bit integer new proper integer format input example
|
||||
[source,json]
|
||||
--------------------------------------------------------------------------------
|
||||
lobby_id": { "xint64": 6215642878098695544 }
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
If this fails then it try to parse with the new way with JSON string format.
|
||||
|
||||
.JSON API 64 bit integer new string format input example
|
||||
[source,json]
|
||||
--------------------------------------------------------------------------------
|
||||
"lobby_id": { "xstr64": "6215642878098695544" }
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
[WARNING]
|
||||
================================================================================
|
||||
Clients written in languages without proper 64bit integers support must
|
||||
use *ONLY* the string format otherwise they will send approximated values and
|
||||
get unexpected results from the JSON API, because parsing will success but the
|
||||
value will not be exactly the one you believe you sent.
|
||||
================================================================================
|
||||
|
||||
|
||||
== A bit of history
|
||||
|
||||
=== First writings about this
|
||||
|
||||
The previous attempt of exposing a RetroShare JSON API is called +libresapi+ and
|
||||
unfortunatley it requires a bunch of boilerplate code when we want to expose
|
||||
something present in the {Cxx} API in the JSON API.
|
||||
|
||||
As an example here you can see the libresapi that exposes part of the retroshare
|
||||
chat {Cxx} API and lot of boilerplate code just to convert {Cxx} objects to JSON
|
||||
|
||||
https://github.com/RetroShare/RetroShare/blob/v0.6.4/libresapi/src/api/ChatHandler.cpp#L44
|
||||
|
||||
To avoid the {Cxx} to JSON and back conversion boilerplate code I have worked out
|
||||
an extension to our {Cxx} serialization code so it is capable to serialize and
|
||||
deserialize to JSON you can see it in this pull request
|
||||
|
||||
https://github.com/RetroShare/RetroShare/pull/1155
|
||||
|
||||
So first step toward having a good API is to take advantage of the fact that RS
|
||||
is now capable of converting C++ objects from and to JSON.
|
||||
|
||||
The current API is accessible via HTTP and unix socket, there is no
|
||||
authentication in both of them, so anyone having access to the HTTP server or to
|
||||
the unix socket can access the API without extra restrictions.
|
||||
Expecially for the HTTP API this is a big risk because also if the http server
|
||||
listen on 127.0.0.1 every application on the machine (even rogue javascript
|
||||
running on your web browser) can access that and for example on android it is
|
||||
not safe at all (because of that I implemented the unix socket access so at
|
||||
least in android API was reasonably safe) because of this.
|
||||
|
||||
A second step to improve the API would be to implement some kind of API
|
||||
authentication mechanism (it would be nice that the mechanism is handled at API
|
||||
level and not at transport level so we can use it for any API trasport not just
|
||||
HTTP for example)
|
||||
|
||||
The HTTP server used by libresapi is libmicrohttpd server that is very minimal,
|
||||
it doesn't provide HTTPS nor modern HTTP goodies, like server notifications,
|
||||
websockets etc. because the lack of support we have a token polling mechanism in
|
||||
libresapi to avoid polling for every thing but it is still ugly, so if we can
|
||||
completely get rid of polling in the API that would be really nice.
|
||||
I have done a crawl to look for a replacement and briefly looked at
|
||||
|
||||
- https://www.gnu.org/software/libmicrohttpd/
|
||||
- http://wolkykim.github.io/libasyncd/
|
||||
- https://github.com/corvusoft/restbed
|
||||
- https://code.facebook.com/posts/1503205539947302/introducing-proxygen-facebook-s-c-http-framework/
|
||||
- https://github.com/cmouse/yahttp
|
||||
|
||||
taking in account a few metrics like modern HTTP goodies support, license,
|
||||
platform support, external dependencies and documentation it seemed to me that
|
||||
restbed is the more appropriate.
|
||||
|
||||
Another source of boilerplate code into libresapi is the mapping between JSON
|
||||
API requests and C++ API methods as an example you can look at this
|
||||
|
||||
https://github.com/RetroShare/RetroShare/blob/v0.6.4/libresapi/src/api/ChatHandler.cpp#L158
|
||||
|
||||
and this
|
||||
|
||||
https://github.com/RetroShare/RetroShare/blob/v0.6.4/libresapi/src/api/ApiServer.cpp#L253
|
||||
|
||||
The abstract logic of this thing is, when libreasapi get a request like
|
||||
+/chat/initiate_distant_chat+ then call
|
||||
+ChatHandler::handleInitiateDistantChatConnexion+ which in turn is just a
|
||||
wrapper of +RsMsgs::initiateDistantChatConnexion+ all this process is basically
|
||||
implemented as boilerplate code and would be unnecessary in a smarter design of
|
||||
the API because almost all the information needed is already present in the
|
||||
C++ API +libretroshare/src/retroshare+.
|
||||
|
||||
So a third step to improve the JSON API would be to remove this source of
|
||||
boilerplate code by automatizing the mapping between C++ and JSON API call.
|
||||
|
||||
This may result a little tricky as language parsing or other adevanced things
|
||||
may be required.
|
||||
|
||||
Hope this dive is useful for you +
|
||||
Cheers +
|
||||
G10h4ck
|
||||
|
||||
=== Second writings about this
|
||||
|
||||
I have been investigating a bit more about:
|
||||
[verse, G10h4ck]
|
||||
________________________________________________________________________________
|
||||
So a third step to improve the JSON API would be to remove this source of
|
||||
boilerplate code by automatizing the mapping between C++ and JSON API call
|
||||
________________________________________________________________________________
|
||||
|
||||
After spending some hours investigating this topic the most reasonable approach
|
||||
seems to:
|
||||
|
||||
1. Properly document headers in +libretroshare/src/retroshare/+ in doxygen syntax
|
||||
specifying wihich params are input and/or output (doxygen sysntax for this is
|
||||
+@param[in/out/inout]+) this will be the API documentation too.
|
||||
|
||||
2. At compile time use doxygen to generate XML description of the headers and use
|
||||
the XML to generate the JSON api server stub.
|
||||
http://www.stack.nl/~dimitri/doxygen/manual/customize.html#xmlgenerator
|
||||
|
||||
3. Enjoy
|
||||
This directory and all it's content is kept as is only for retro-compatibility
|
||||
with the old, deprecated `qmake` build system.
|
||||
|
@ -1,80 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* RetroShare JSON API *
|
||||
* *
|
||||
* Copyright (C) 2018-2019 Gioacchino Mazzurco <gio@eigenlab.org> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License version 3 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
registerHandler( "$%apiPath%$",
|
||||
[this](const std::shared_ptr<rb::Session> session)
|
||||
{
|
||||
const std::multimap<std::string, std::string> headers
|
||||
{
|
||||
{ "Connection", "keep-alive" },
|
||||
{ "Content-Type", "text/event-stream" }
|
||||
};
|
||||
session->yield(rb::OK, headers);
|
||||
|
||||
size_t reqSize = session->get_request()->get_header("Content-Length", 0);
|
||||
session->fetch( reqSize, [this](
|
||||
const std::shared_ptr<rb::Session> session,
|
||||
const rb::Bytes& body )
|
||||
{
|
||||
INITIALIZE_API_CALL_JSON_CONTEXT;
|
||||
|
||||
if( !checkRsServicePtrReady(
|
||||
$%instanceName%$, "$%instanceName%$", cAns, session ) )
|
||||
return;
|
||||
|
||||
$%paramsDeclaration%$
|
||||
|
||||
$%inputParamsDeserialization%$
|
||||
|
||||
const std::weak_ptr<rb::Service> weakService(mService);
|
||||
const std::weak_ptr<rb::Session> weakSession(session);
|
||||
$%callbackName%$ = [weakService, weakSession]($%callbackParams%$)
|
||||
{
|
||||
auto session = weakSession.lock();
|
||||
if(!session || session->is_closed()) return;
|
||||
|
||||
auto lService = weakService.lock();
|
||||
if(!lService || lService->is_down()) return;
|
||||
|
||||
$%callbackParamsSerialization%$
|
||||
|
||||
std::stringstream sStream;
|
||||
sStream << "data: " << compactJSON << ctx.mJson << "\n\n";
|
||||
const std::string message = sStream.str();
|
||||
|
||||
lService->schedule( [weakSession, message]()
|
||||
{
|
||||
auto session = weakSession.lock();
|
||||
if(!session || session->is_closed()) return;
|
||||
session->yield(message);
|
||||
$%sessionEarlyClose%$
|
||||
} );
|
||||
};
|
||||
|
||||
$%functionCall%$
|
||||
|
||||
$%outputParamsSerialization%$
|
||||
|
||||
// return them to the API caller
|
||||
std::stringstream message;
|
||||
message << "data: " << compactJSON << cAns.mJson << "\n\n";
|
||||
session->yield(message.str());
|
||||
$%sessionDelayedClose%$
|
||||
} );
|
||||
}, $%requiresAuth%$ );
|
1
jsonapi-generator/src/async-method-wrapper-template.cpp.tmpl
Symbolic link
1
jsonapi-generator/src/async-method-wrapper-template.cpp.tmpl
Symbolic link
@ -0,0 +1 @@
|
||||
../../libretroshare/src/jsonapi/async-method-wrapper-template.cpp.tmpl
|
@ -1,230 +0,0 @@
|
||||
DOXYFILE_ENCODING = UTF-8
|
||||
PROJECT_NAME = "libretroshare"
|
||||
|
||||
ALIASES += jsonapi{1}="\xmlonly<jsonapi minversion=\"\1\"/>\endxmlonly"
|
||||
ALIASES += jsonapi{2}="\xmlonly<jsonapi minversion=\"\1\" access=\"\2\"/>\endxmlonly"
|
||||
|
||||
# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
|
||||
# documentation from any documented member that it re-implements.
|
||||
# The default value is: YES.
|
||||
|
||||
INHERIT_DOCS = YES
|
||||
|
||||
# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
|
||||
# according to the Markdown format, which allows for more readable
|
||||
# documentation. See http://daringfireball.net/projects/markdown/ for details.
|
||||
# The output of markdown processing is further processed by doxygen, so you can
|
||||
# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
|
||||
# case of backward compatibilities issues.
|
||||
# The default value is: YES.
|
||||
|
||||
MARKDOWN_SUPPORT = YES
|
||||
|
||||
# When enabled doxygen tries to link words that correspond to documented
|
||||
# classes, or namespaces to their corresponding documentation. Such a link can
|
||||
# be prevented in individual cases by putting a % sign in front of the word or
|
||||
# globally by setting AUTOLINK_SUPPORT to NO.
|
||||
# The default value is: YES.
|
||||
|
||||
AUTOLINK_SUPPORT = YES
|
||||
|
||||
|
||||
# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
|
||||
# documentation are documented, even if no documentation was available. Private
|
||||
# class members and static file members will be hidden unless the
|
||||
# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
|
||||
# Note: This will also disable the warnings about undocumented members that are
|
||||
# normally produced when WARNINGS is set to YES.
|
||||
# The default value is: NO.
|
||||
|
||||
EXTRACT_ALL = YES
|
||||
|
||||
|
||||
# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
|
||||
# undocumented members inside documented classes or files. If set to NO these
|
||||
# members will be included in the various overviews, but no documentation
|
||||
# section is generated. This option has no effect if EXTRACT_ALL is enabled.
|
||||
# The default value is: NO.
|
||||
|
||||
HIDE_UNDOC_MEMBERS = NO
|
||||
|
||||
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
|
||||
# undocumented classes that are normally visible in the class hierarchy. If set
|
||||
# to NO, these classes will be included in the various overviews. This option
|
||||
# has no effect if EXTRACT_ALL is enabled.
|
||||
# The default value is: NO.
|
||||
|
||||
HIDE_UNDOC_CLASSES = NO
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the input files
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# The INPUT tag is used to specify the files and/or directories that contain
|
||||
# documented source files. You may enter file names like myfile.cpp or
|
||||
# directories like /usr/src/myproject. Separate the files or directories with
|
||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||
# Note: If this tag is empty the current directory is searched.
|
||||
|
||||
#INPUT =
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
||||
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
|
||||
# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
|
||||
# possible encodings.
|
||||
# The default value is: UTF-8.
|
||||
|
||||
INPUT_ENCODING = UTF-8
|
||||
|
||||
# If the value of the INPUT tag contains directories, you can use the
|
||||
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
|
||||
# *.h) to filter out the source-files in the directories.
|
||||
#
|
||||
# Note that for custom extensions or not directly supported extensions you also
|
||||
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
|
||||
# read by doxygen.
|
||||
#
|
||||
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
|
||||
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
|
||||
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
|
||||
# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
|
||||
# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
|
||||
|
||||
FILE_PATTERNS = *.c \
|
||||
*.cc \
|
||||
*.cxx \
|
||||
*.cpp \
|
||||
*.c++ \
|
||||
*.java \
|
||||
*.ii \
|
||||
*.ixx \
|
||||
*.ipp \
|
||||
*.i++ \
|
||||
*.inl \
|
||||
*.idl \
|
||||
*.ddl \
|
||||
*.odl \
|
||||
*.h \
|
||||
*.hh \
|
||||
*.hxx \
|
||||
*.hpp \
|
||||
*.h++ \
|
||||
*.cs \
|
||||
*.d \
|
||||
*.php \
|
||||
*.php4 \
|
||||
*.php5 \
|
||||
*.phtml \
|
||||
*.inc \
|
||||
*.m \
|
||||
*.markdown \
|
||||
*.md \
|
||||
*.mm \
|
||||
*.dox \
|
||||
*.py \
|
||||
*.pyw \
|
||||
*.f90 \
|
||||
*.f95 \
|
||||
*.f03 \
|
||||
*.f08 \
|
||||
*.f \
|
||||
*.for \
|
||||
*.tcl \
|
||||
*.vhd \
|
||||
*.vhdl \
|
||||
*.ucf \
|
||||
*.qsf
|
||||
|
||||
# The RECURSIVE tag can be used to specify whether or not subdirectories should
|
||||
# be searched for input files as well.
|
||||
# The default value is: NO.
|
||||
|
||||
RECURSIVE = YES
|
||||
|
||||
# The EXCLUDE tag can be used to specify files and/or directories that should be
|
||||
# excluded from the INPUT source files. This way you can easily exclude a
|
||||
# subdirectory from a directory tree whose root is specified with the INPUT tag.
|
||||
#
|
||||
# Note that relative paths are relative to the directory from which doxygen is
|
||||
# run.
|
||||
|
||||
EXCLUDE =
|
||||
|
||||
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
|
||||
# directories that are symbolic links (a Unix file system feature) are excluded
|
||||
# from the input.
|
||||
# The default value is: NO.
|
||||
|
||||
EXCLUDE_SYMLINKS = NO
|
||||
|
||||
# If the value of the INPUT tag contains directories, you can use the
|
||||
# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
|
||||
# certain files from those directories.
|
||||
#
|
||||
# Note that the wildcards are matched against the file with absolute path, so to
|
||||
# exclude all test directories for example use the pattern */test/*
|
||||
|
||||
EXCLUDE_PATTERNS =
|
||||
|
||||
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
|
||||
# (namespaces, classes, functions, etc.) that should be excluded from the
|
||||
# output. The symbol name can be a fully qualified name, a word, or if the
|
||||
# wildcard * is used, a substring. Examples: ANamespace, AClass,
|
||||
# AClass::ANamespace, ANamespace::*Test
|
||||
#
|
||||
# Note that the wildcards are matched against the file with absolute path, so to
|
||||
# exclude all test directories use the pattern */test/*
|
||||
|
||||
EXCLUDE_SYMBOLS =
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the HTML output
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
|
||||
# The default value is: YES.
|
||||
|
||||
GENERATE_HTML = NO
|
||||
|
||||
# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
|
||||
# The default value is: YES.
|
||||
|
||||
GENERATE_LATEX = NO
|
||||
|
||||
# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
|
||||
# captures the structure of the code including all documentation.
|
||||
# The default value is: NO.
|
||||
|
||||
GENERATE_XML = YES
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the preprocessor
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
|
||||
# C-preprocessor directives found in the sources and include files.
|
||||
# The default value is: YES.
|
||||
|
||||
ENABLE_PREPROCESSING = YES
|
||||
|
||||
# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
|
||||
# in the source code. If set to NO, only conditional compilation will be
|
||||
# performed. Macro expansion can be done in a controlled way by setting
|
||||
# EXPAND_ONLY_PREDEF to YES.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
MACRO_EXPANSION = NO
|
||||
|
||||
# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
|
||||
# the macro expansion is limited to the macros specified with the PREDEFINED and
|
||||
# EXPAND_AS_DEFINED tags.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
EXPAND_ONLY_PREDEF = NO
|
||||
|
1
jsonapi-generator/src/jsonapi-generator-doxygen.conf
Symbolic link
1
jsonapi-generator/src/jsonapi-generator-doxygen.conf
Symbolic link
@ -0,0 +1 @@
|
||||
../../libretroshare/src/jsonapi/jsonapi-generator-doxygen.conf
|
@ -1,50 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* RetroShare JSON API *
|
||||
* *
|
||||
* Copyright (C) 2018-2019 Gioacchino Mazzurco <gio@eigenlab.org> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as *
|
||||
* published by the Free Software Foundation, either version 3 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
registerHandler( "$%apiPath%$",
|
||||
[](const std::shared_ptr<rb::Session> session)
|
||||
{
|
||||
size_t reqSize = session->get_request()->get_header("Content-Length", 0);
|
||||
session->fetch( reqSize, [](
|
||||
const std::shared_ptr<rb::Session> session,
|
||||
const rb::Bytes& body )
|
||||
{
|
||||
INITIALIZE_API_CALL_JSON_CONTEXT;
|
||||
|
||||
if( !checkRsServicePtrReady(
|
||||
$%instanceName%$, "$%instanceName%$", cAns, session ) )
|
||||
return;
|
||||
|
||||
$%paramsDeclaration%$
|
||||
|
||||
// deserialize input parameters from JSON
|
||||
$%inputParamsDeserialization%$
|
||||
|
||||
// call retroshare C++ API
|
||||
$%functionCall%$
|
||||
|
||||
// serialize out parameters and return value to JSON
|
||||
$%outputParamsSerialization%$
|
||||
|
||||
// return them to the API caller
|
||||
DEFAULT_API_CALL_JSON_RETURN(rb::OK);
|
||||
} );
|
||||
}, $%requiresAuth%$ );
|
||||
|
1
jsonapi-generator/src/method-wrapper-template.cpp.tmpl
Symbolic link
1
jsonapi-generator/src/method-wrapper-template.cpp.tmpl
Symbolic link
@ -0,0 +1 @@
|
||||
../../libretroshare/src/jsonapi/method-wrapper-template.cpp.tmpl
|
@ -6,7 +6,7 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
cmake_minimum_required (VERSION 2.8.12)
|
||||
project(libbitdht)
|
||||
project(bitdht)
|
||||
|
||||
file(
|
||||
GLOB BITDHT_SOURCES
|
||||
|
@ -8,6 +8,11 @@
|
||||
cmake_minimum_required (VERSION 3.18.0)
|
||||
project(retroshare)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
|
||||
set(FETCHCONTENT_QUIET OFF)
|
||||
include(FetchContent)
|
||||
|
||||
# sqlcipher
|
||||
option(
|
||||
RS_SQLCIPHER
|
||||
@ -28,18 +33,22 @@ option(
|
||||
ON )
|
||||
|
||||
# use_dht_stunner
|
||||
option(
|
||||
cmake_dependent_option(
|
||||
RS_BITDHT_STUNNER
|
||||
"Use bitdht (BitTorrent DHT own implementation) for NAT type discovery and \
|
||||
attempt the STUN (Session Traversal Utilities for NAT)"
|
||||
ON )
|
||||
ON
|
||||
"RS_BITDHT"
|
||||
OFF )
|
||||
|
||||
# use_dht_stunner_ext_ip
|
||||
option(
|
||||
cmake_dependent_option(
|
||||
RS_BITDHT_STUNNER_EXT_IP
|
||||
"Use bitdht (BitTorrent DHT own implementation) stunner to figure out our \
|
||||
external IP. As this purely relying on random DHT peers that answer our \
|
||||
request, it can easily be abused. Therefore, it is turned off by default."
|
||||
OFF
|
||||
"RS_BITDHT_STUNNER"
|
||||
OFF )
|
||||
|
||||
# rs_jsonapi
|
||||
@ -71,53 +80,45 @@ option(
|
||||
option(
|
||||
RS_MINIUPNPC
|
||||
"Forward ports in NAT router via miniupnpc"
|
||||
ON
|
||||
)
|
||||
ON )
|
||||
|
||||
include(CMakeDependentOption)
|
||||
cmake_dependent_option(
|
||||
RS_LIBUPNP
|
||||
"Forward ports in NAT router via libupnp (unstable)"
|
||||
OFF
|
||||
"NOT RS_MINIUPNPC"
|
||||
OFF
|
||||
)
|
||||
OFF )
|
||||
|
||||
option(
|
||||
RS_LIBRETROSHARE_STATIC
|
||||
"Build RetroShare static library"
|
||||
ON
|
||||
)
|
||||
ON )
|
||||
|
||||
cmake_dependent_option(
|
||||
RS_LIBRETROSHARE_SHARED
|
||||
"Build RetroShare shared library"
|
||||
OFF
|
||||
"NOT RS_LIBRETROSHARE_STATIC"
|
||||
OFF
|
||||
)
|
||||
OFF )
|
||||
|
||||
# rs_deprecatedwarning
|
||||
option(
|
||||
RS_WARN_DEPRECATED
|
||||
"Print warning about RetroShare deprecated components usage during build"
|
||||
ON
|
||||
)
|
||||
ON )
|
||||
|
||||
# rs_cppwarning
|
||||
option(
|
||||
RS_WARN_LESS
|
||||
"Silence a few at the moment very common warnings about RetroShare \
|
||||
components during build"
|
||||
OFF
|
||||
)
|
||||
OFF )
|
||||
|
||||
# rs_v07_changes
|
||||
option(
|
||||
RS_V07_BREAKING_CHANGES
|
||||
"Enable retro-compatibility breaking changes planned for RetroShare 0.7.0"
|
||||
OFF
|
||||
)
|
||||
OFF )
|
||||
|
||||
set(
|
||||
RS_DATA_DIR
|
||||
@ -127,8 +128,24 @@ set(
|
||||
|
||||
################################################################################
|
||||
|
||||
find_package(Git REQUIRED)
|
||||
|
||||
#function(check_submodule sPath)
|
||||
# if(NOT EXISTS "${sPath}/.git" )
|
||||
# message("Initializing submodule ${sPath}")
|
||||
# execute_process(
|
||||
# COMMAND "${GIT_EXECUTABLE}" submodule update --init
|
||||
# WORKING_DIRECTORY "${sPath}"
|
||||
# COMMAND_ECHO STDERR
|
||||
# COMMAND_ERROR_IS_FATAL ANY)
|
||||
# endif()
|
||||
#endfunction()
|
||||
|
||||
################################################################################
|
||||
|
||||
include(src/CMakeLists.txt)
|
||||
list(TRANSFORM RS_SOURCES PREPEND src/)
|
||||
list(TRANSFORM RS_PUBLIC_HEADERS PREPEND src/)
|
||||
|
||||
if(RS_LIBRETROSHARE_STATIC)
|
||||
add_library(${PROJECT_NAME} STATIC ${RS_SOURCES})
|
||||
@ -153,15 +170,150 @@ target_link_libraries(${PROJECT_NAME} PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
||||
|
||||
################################################################################
|
||||
|
||||
add_subdirectory(../openpgpsdk ${CMAKE_BINARY_DIR}/openpgpsdk)
|
||||
set(OPENPGPSDK_DEVEL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../openpgpsdk/")
|
||||
if(EXISTS "${OPENPGPSDK_DEVEL_DIR}/.git" )
|
||||
message(
|
||||
CHECK_PASS
|
||||
"openpgpsdk submodule found at ${OPENPGPSDK_DEVEL_DIR} using it" )
|
||||
add_subdirectory(${OPENPGPSDK_DEVEL_DIR} ${CMAKE_BINARY_DIR}/openpgpsdk)
|
||||
else()
|
||||
FetchContent_Declare(
|
||||
openpgpsdk
|
||||
GIT_REPOSITORY "https://gitlab.com/RetroShare/openpgpsdk.git"
|
||||
GIT_TAG "origin/master"
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
TIMEOUT 10
|
||||
)
|
||||
FetchContent_MakeAvailable(openpgpsdk)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE openpgpsdk)
|
||||
|
||||
################################################################################
|
||||
|
||||
if(RS_BITDHT)
|
||||
set(BITDHT_DEVEL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../libbitdht/")
|
||||
if(EXISTS "${BITDHT_DEVEL_DIR}/.git" )
|
||||
message(
|
||||
CHECK_PASS
|
||||
"BitDHT submodule found at ${BITDHT_DEVEL_DIR} using it" )
|
||||
add_subdirectory(${BITDHT_DEVEL_DIR} ${CMAKE_BINARY_DIR}/bitdht)
|
||||
else()
|
||||
FetchContent_Declare(
|
||||
bitdht
|
||||
GIT_REPOSITORY "https://gitlab.com/RetroShare/bitdht.git"
|
||||
GIT_TAG "origin/master"
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
TIMEOUT 10
|
||||
)
|
||||
FetchContent_MakeAvailable(bitdht)
|
||||
endif()
|
||||
|
||||
add_compile_definitions(RS_USE_BITDHT)
|
||||
add_subdirectory(../libbitdht ${CMAKE_BINARY_DIR}/libbitdht)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE libbitdht)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE bitdht)
|
||||
|
||||
if(RS_BITDHT_STUNNER)
|
||||
add_compile_definitions(RS_USE_DHT_STUNNER)
|
||||
|
||||
if(RS_BITDHT_STUNNER_EXT_IP)
|
||||
# TODO: Refactor this define to use proper naming
|
||||
add_compile_definitions(ALLOW_DHT_STUNNER)
|
||||
endif(RS_BITDHT_STUNNER_EXT_IP)
|
||||
endif(RS_BITDHT_STUNNER)
|
||||
endif(RS_BITDHT)
|
||||
|
||||
################################################################################
|
||||
|
||||
if(RS_JSON_API)
|
||||
find_package(Doxygen REQUIRED)
|
||||
find_package(Python3 REQUIRED)
|
||||
|
||||
## TODO: execute at build time instead that at cofiguration time see
|
||||
## add_custom_command or add_custom_target
|
||||
|
||||
set(
|
||||
JSON_API_GENERATOR_WORK_DIR
|
||||
"${CMAKE_BINARY_DIR}/jsonapi-generator.workdir/" )
|
||||
|
||||
set(
|
||||
JSON_API_GENERATOR_DOXYFILE
|
||||
"${JSON_API_GENERATOR_WORK_DIR}/jsonapi-generator-doxygen.conf" )
|
||||
|
||||
set(
|
||||
JSONAPI_GENERATOR_OUTPUT_DIR
|
||||
"${JSON_API_GENERATOR_WORK_DIR}/src/" )
|
||||
|
||||
set(
|
||||
JSONAPI_GENERATOR_SOURCE_DIR
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/jsonapi/" )
|
||||
|
||||
set(
|
||||
JSONAPI_GENERATOR_EXECUTABLE
|
||||
"${JSONAPI_GENERATOR_SOURCE_DIR}/jsonapi-generator.py" )
|
||||
|
||||
file(
|
||||
COPY "src/jsonapi/jsonapi-generator-doxygen.conf"
|
||||
DESTINATION "${JSON_API_GENERATOR_WORK_DIR}" )
|
||||
|
||||
file(
|
||||
APPEND
|
||||
"${JSON_API_GENERATOR_DOXYFILE}"
|
||||
"OUTPUT_DIRECTORY=${JSONAPI_GENERATOR_OUTPUT_DIR}\n"
|
||||
"INPUT=${CMAKE_CURRENT_SOURCE_DIR}" )
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
"${JSONAPI_GENERATOR_OUTPUT_DIR}/jsonapi-includes.inl"
|
||||
"${JSONAPI_GENERATOR_OUTPUT_DIR}/jsonapi-wrappers.inl"
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${JSON_API_GENERATOR_DOXYFILE}
|
||||
COMMAND
|
||||
${Python3_EXECUTABLE} ${JSONAPI_GENERATOR_EXECUTABLE}
|
||||
${JSONAPI_GENERATOR_SOURCE_DIR} ${JSONAPI_GENERATOR_OUTPUT_DIR}
|
||||
MAIN_DEPENDENCY "${JSONAPI_GENERATOR_EXECUTABLE}"
|
||||
DEPENDS ${JSON_API_GENERATOR_DOXYFILE} ${RS_PUBLIC_HEADERS} )
|
||||
|
||||
target_sources(
|
||||
${PROJECT_NAME} PRIVATE
|
||||
"${JSONAPI_GENERATOR_OUTPUT_DIR}/jsonapi-includes.inl"
|
||||
"${JSONAPI_GENERATOR_OUTPUT_DIR}/jsonapi-wrappers.inl" )
|
||||
|
||||
include_directories(${JSONAPI_GENERATOR_OUTPUT_DIR})
|
||||
|
||||
set(BUILD_TESTS OFF CACHE BOOL "Do not build restbed tests")
|
||||
set(BUILD_SSL OFF CACHE BOOL "Do not build restbed SSL support")
|
||||
|
||||
FetchContent_Declare(
|
||||
restbed
|
||||
GIT_REPOSITORY "https://github.com/Corvusoft/restbed.git"
|
||||
GIT_TAG "4.8"
|
||||
GIT_SUBMODULES dependency/asio dependency/catch
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
TIMEOUT 10
|
||||
)
|
||||
FetchContent_MakeAvailable(restbed)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE restbed)
|
||||
|
||||
## TODO: work around target_include_directories should be added upstream
|
||||
include_directories(${restbed_SOURCE_DIR}/source/)
|
||||
|
||||
add_compile_definitions(RS_JSONAPI)
|
||||
endif(RS_JSON_API)
|
||||
|
||||
################################################################################
|
||||
|
||||
if(RS_FORUM_DEEP_INDEX)
|
||||
find_package(Xapian REQUIRED)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${XAPIAN_LIBRARIES})
|
||||
|
||||
add_compile_definitions(RS_DEEP_FORUMS_INDEX)
|
||||
endif(RS_FORUM_DEEP_INDEX)
|
||||
|
||||
################################################################################
|
||||
|
||||
## TODO: Check if https://github.com/rbock/sqlpp11 or
|
||||
## https://github.com/rbock/sqlpp17 may improve GXS code
|
||||
if(RS_SQLCIPHER)
|
||||
@ -196,12 +348,17 @@ if(RS_V07_BREAKING_CHANGES)
|
||||
V07_NON_BACKWARD_COMPATIBLE_CHANGE_UNNAMED )
|
||||
endif()
|
||||
|
||||
if(RS_DH_PRIME_INIT_CHECK)
|
||||
add_compile_definitions(RS_DISABLE_DIFFIE_HELLMAN_INIT_CHECK)
|
||||
endif(RS_DH_PRIME_INIT_CHECK)
|
||||
|
||||
if(RS_MINIUPNPC)
|
||||
add_compile_definitions(RS_USE_LIBMINIUPNPC)
|
||||
endif(RS_MINIUPNPC)
|
||||
|
||||
if(RS_LIBUPNP)
|
||||
add_compile_definitions(RS_USE_LIBUPNP)
|
||||
message(FATAL_ERROR "UPnP support via libupnp is currently not supported")
|
||||
#add_compile_definitions(RS_USE_LIBUPNP)
|
||||
endif(RS_LIBUPNP)
|
||||
|
||||
if(RS_GXS_SEND_ALL)
|
||||
@ -209,13 +366,24 @@ if(RS_GXS_SEND_ALL)
|
||||
endif(RS_GXS_SEND_ALL)
|
||||
|
||||
if(RS_BRODCAST_DISCOVERY)
|
||||
add_subdirectory(
|
||||
../supportlibs/udp-discovery-cpp/
|
||||
${CMAKE_BINARY_DIR}/supportlibs/udp-discovery-cpp/ )
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE udp-discovery)
|
||||
## TODO: upstream option to disable tests building
|
||||
set(BUILD_EXAMPLE OFF CACHE BOOL "Do not build udp-discovery-cpp examples")
|
||||
set(BUILD_TOOL OFF CACHE BOOL "Do not build udp-discovery-tool application")
|
||||
FetchContent_Declare(
|
||||
udp-discovery-cpp
|
||||
GIT_REPOSITORY "https://github.com/truvorskameikin/udp-discovery-cpp.git"
|
||||
GIT_TAG "origin/master"
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
TIMEOUT 10
|
||||
)
|
||||
FetchContent_MakeAvailable(udp-discovery-cpp)
|
||||
|
||||
## Temporary work around target_include_directories should be added upstream
|
||||
include_directories(../supportlibs/udp-discovery-cpp/)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE udp-discovery-cpp)
|
||||
|
||||
## TODO: Temporary work around target_include_directories should be added
|
||||
## upstream
|
||||
include_directories(${udp-discovery-cpp_SOURCE_DIR})
|
||||
endif(RS_BRODCAST_DISCOVERY)
|
||||
|
||||
if(NOT RS_WARN_DEPRECATED)
|
||||
@ -223,7 +391,7 @@ if(NOT RS_WARN_DEPRECATED)
|
||||
target_compile_options(
|
||||
${PROJECT_NAME} PRIVATE
|
||||
-Wno-deprecated -Wno-deprecated-declarations )
|
||||
endif(RS_WARN_DEPRECATED)
|
||||
endif(NOT RS_WARN_DEPRECATED)
|
||||
|
||||
if(RS_WARN_LESS)
|
||||
add_compile_definitions(RS_NO_WARN_CPP)
|
||||
|
@ -5,6 +5,49 @@
|
||||
#
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
list(
|
||||
APPEND RS_PUBLIC_HEADERS
|
||||
retroshare/rsexpr.h
|
||||
retroshare/rsgxsdistsync.h
|
||||
retroshare/rsiface.h
|
||||
retroshare/rsrtt.h
|
||||
retroshare/rsbanlist.h
|
||||
retroshare/rsconfig.h
|
||||
retroshare/rsdisc.h
|
||||
retroshare/rsflags.h
|
||||
retroshare/rsgrouter.h
|
||||
retroshare/rsgxsflags.h
|
||||
retroshare/rsgxsservice.h
|
||||
retroshare/rsgxstrans.h
|
||||
retroshare/rsgxstunnel.h
|
||||
retroshare/rsids.h
|
||||
retroshare/rsnotify.h
|
||||
retroshare/rsphoto.h
|
||||
retroshare/rsplugin.h
|
||||
retroshare/rsreputations.h
|
||||
retroshare/rsservicecontrol.h
|
||||
retroshare/rstokenservice.h
|
||||
retroshare/rsturtle.h
|
||||
retroshare/rsgossipdiscovery.h
|
||||
retroshare/rsgxscommon.h
|
||||
retroshare/rsposted.h
|
||||
retroshare/rsstatus.h
|
||||
retroshare/rsversion.h
|
||||
retroshare/rsgxsifacehelper.h
|
||||
retroshare/rshistory.h
|
||||
retroshare/rsidentity.h
|
||||
retroshare/rsmsgs.h
|
||||
retroshare/rsgxschannels.h
|
||||
retroshare/rsgxscircles.h
|
||||
retroshare/rsgxsiface.h
|
||||
retroshare/rsgxsifacetypes.h
|
||||
retroshare/rstypes.h
|
||||
retroshare/rsgxsforums.h
|
||||
retroshare/rsevents.h
|
||||
retroshare/rsfiles.h
|
||||
retroshare/rsinit.h
|
||||
retroshare/rspeers.h )
|
||||
|
||||
list(
|
||||
APPEND RS_SOURCES
|
||||
chat/distantchat.cc
|
||||
@ -17,6 +60,10 @@ list(
|
||||
crypto/rscrypto.cpp )
|
||||
|
||||
if(RS_BITDHT)
|
||||
list(
|
||||
APPEND RS_PUBLIC_HEADERS
|
||||
retroshare/rsdht.h )
|
||||
|
||||
list(
|
||||
APPEND RS_SOURCES
|
||||
dht/connectstatebox.cc
|
||||
@ -80,6 +127,10 @@ list(
|
||||
gxstunnel/p3gxstunnel.cc )
|
||||
|
||||
if(RS_JSON_API)
|
||||
list(
|
||||
APPEND RS_PUBLIC_HEADERS
|
||||
retroshare/rsjsonapi.h )
|
||||
|
||||
list(
|
||||
APPEND RS_SOURCES
|
||||
jsonapi/jsonapi.cpp )
|
||||
@ -151,6 +202,7 @@ list(
|
||||
rsitems/rsrttitems.cc
|
||||
rsitems/rsserviceinfoitems.cc )
|
||||
|
||||
#retroshare/rswiki.h
|
||||
#./rsitems/rswikiitems.cc
|
||||
#./rsitems/rswikiitems.h
|
||||
#./rsitems/rswireitems.h
|
||||
@ -167,6 +219,7 @@ list(
|
||||
#./rsitems/rsposteditems.cc
|
||||
#./rsitems/rsposteditems.h
|
||||
#./rsitems/rswireitems.cc
|
||||
#retroshare/rswire.h
|
||||
|
||||
list(
|
||||
APPEND RS_SOURCES
|
||||
@ -243,6 +296,10 @@ list(
|
||||
#./services/p3posted.h
|
||||
|
||||
if(RS_BRODCAST_DISCOVERY)
|
||||
list(
|
||||
APPEND RS_PUBLIC_HEADERS
|
||||
retroshare/rsbroadcastdiscovery.h )
|
||||
|
||||
list(
|
||||
APPEND RS_SOURCES
|
||||
services/broadcastdiscoveryservice.cc )
|
||||
@ -255,9 +312,14 @@ list(
|
||||
tcponudp/tou.cc
|
||||
tcponudp/udppeer.cc
|
||||
tcponudp/bss_tou.cc
|
||||
tcponudp/udprelay.cc
|
||||
tcponudp/udpstunner.cc )
|
||||
|
||||
tcponudp/udprelay.cc )
|
||||
|
||||
if(RS_BITDHT_STUNNER)
|
||||
list(
|
||||
APPEND RS_SOURCES
|
||||
tcponudp/udpstunner.cc )
|
||||
endif(RS_BITDHT_STUNNER)
|
||||
|
||||
list(
|
||||
APPEND RS_SOURCES
|
||||
turtle/rsturtleitem.cc
|
||||
|
447
libretroshare/src/jsonapi/README.adoc
Normal file
447
libretroshare/src/jsonapi/README.adoc
Normal file
@ -0,0 +1,447 @@
|
||||
// SPDX-FileCopyrightText: (C) 2004-2019 Retroshare Team <contact@retroshare.cc>
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
RetroShare JSON API
|
||||
===================
|
||||
|
||||
:Cxx: C++
|
||||
|
||||
== How to use RetroShare JSON API
|
||||
|
||||
Look for methods marked with +@jsonapi+ doxygen custom command into
|
||||
+libretroshare/src/retroshare+. The method path is composed by service instance
|
||||
pointer name like +rsGxsChannels+ for +RsGxsChannels+, and the method name like
|
||||
+createGroup+ and pass the input paramethers as a JSON object.
|
||||
|
||||
.Service instance pointer in rsgxschannels.h
|
||||
[source,cpp]
|
||||
--------------------------------------------------------------------------------
|
||||
/**
|
||||
* Pointer to global instance of RsGxsChannels service implementation
|
||||
* @jsonapi{development}
|
||||
*/
|
||||
extern RsGxsChannels* rsGxsChannels;
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.Method declaration in rsgxschannels.h
|
||||
[source,cpp]
|
||||
--------------------------------------------------------------------------------
|
||||
/**
|
||||
* @brief Request channel creation.
|
||||
* The action is performed asyncronously, so it could fail in a subsequent
|
||||
* phase even after returning true.
|
||||
* @jsonapi{development}
|
||||
* @param[out] token Storage for RsTokenService token to track request
|
||||
* status.
|
||||
* @param[in] group Channel data (name, description...)
|
||||
* @return false on error, true otherwise
|
||||
*/
|
||||
virtual bool createGroup(uint32_t& token, RsGxsChannelGroup& group) = 0;
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.paramethers.json
|
||||
[source,json]
|
||||
--------------------------------------------------------------------------------
|
||||
{
|
||||
"group":{
|
||||
"mMeta":{
|
||||
"mGroupName":"JSON test group",
|
||||
"mGroupFlags":4,
|
||||
"mSignFlags":520
|
||||
},
|
||||
"mDescription":"JSON test group description"
|
||||
},
|
||||
"caller_data":"Here can go any kind of JSON data (even objects) that the caller want to get back together with the response"
|
||||
}
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.Calling the JSON API with curl on the terminal
|
||||
[source,bash]
|
||||
--------------------------------------------------------------------------------
|
||||
curl -u $API_USER --data @paramethers.json http://127.0.0.1:9092/rsGxsChannels/createGroup
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.JSON API call result
|
||||
[source,json]
|
||||
--------------------------------------------------------------------------------
|
||||
{
|
||||
"caller_data": "Here can go any kind of JSON data (even objects) that the caller want to get back together with the response",
|
||||
"retval": true,
|
||||
"token": 3
|
||||
}
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Even if it is less efficient because of URL encoding HTTP +GET+ method is
|
||||
supported too, so in cases where the client cannot use +POST+ she can still use
|
||||
+GET+ taking care of encoding the JSON data. With +curl+ this can be done at
|
||||
least in two different ways.
|
||||
|
||||
.Calling the JSON API with GET method with curl on the terminal
|
||||
[source,bash]
|
||||
--------------------------------------------------------------------------------
|
||||
curl -u $API_USER --get --data-urlencode jsonData@paramethers.json \
|
||||
http://127.0.0.1:9092/rsGxsChannels/createGroup
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Letting +curl+ do the encoding is much more elegant but it is semantically
|
||||
equivalent to the following.
|
||||
|
||||
.Calling the JSON API with GET method and pre-encoded data with curl on the terminal
|
||||
--------------------------------------------------------------------------------
|
||||
curl -u $API_USER http://127.0.0.1:9092/rsGxsChannels/createGroup?jsonData=%7B%0A%20%20%20%20%22group%22%3A%7B%0A%20%20%20%20%20%20%20%20%22mMeta%22%3A%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mGroupName%22%3A%22JSON%20test%20group%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mGroupFlags%22%3A4%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mSignFlags%22%3A520%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%22mDescription%22%3A%22JSON%20test%20group%20description%22%0A%20%20%20%20%7D%2C%0A%20%20%20%20%22caller_data%22%3A%22Here%20can%20go%20any%20kind%20of%20JSON%20data%20%28even%20objects%29%20that%20the%20caller%20want%20to%20get%20back%20together%20with%20the%20response%22%0A%7D
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Note that using +GET+ method +?jsonData=+ and then the JSON data URL encoded are
|
||||
added after the path in the HTTP request.
|
||||
|
||||
|
||||
== JSON API authentication
|
||||
|
||||
Most of JSON API methods require authentication as they give access to
|
||||
RetroShare user data, and we don't want any application running on the system
|
||||
eventually by other users be able to access private data indiscriminately.
|
||||
JSON API support HTTP Basic as authentication scheme, this is enough as JSON API
|
||||
server is intented for usage on the same system (127.0.0.1) not over an
|
||||
untrusted network.
|
||||
If you need to use JSON API over an untrusted network consider using a reverse
|
||||
proxy with HTTPS such as NGINX in front of JSON API server.
|
||||
If RetroShare login has been effectuated through the JSON API you can use your
|
||||
location SSLID as username and your PGP password as credential for the JSON API,
|
||||
but we suggests you use specific meaningful and human readable credentials for
|
||||
each JSON API client so the human user can have better control over which client
|
||||
can access the JSON API.
|
||||
|
||||
.NewToken.json
|
||||
[source,json]
|
||||
--------------------------------------------------------------------------------
|
||||
{
|
||||
"token": "myNewUser:myNewPassword"
|
||||
}
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.An authenticated client can authorize new tokens like this
|
||||
--------------------------------------------------------------------------------
|
||||
curl -u $API_USER --data @NewToken.json http://127.0.0.1:9092/jsonApiServer/authorizeToken
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.An unauthenticated JSON API client can request access with
|
||||
--------------------------------------------------------------------------------
|
||||
curl --data @NewToken.json http://127.0.0.1:9092/jsonApiServer/requestNewTokenAutorization
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
When an unauthenticated client request his token to be authorized, JSON API
|
||||
server will try to ask confirmation to the human user if possible through
|
||||
+mNewAccessRequestCallback+, if it is not possible or the user didn't authorized
|
||||
the token +false+ is returned.
|
||||
|
||||
|
||||
== Offer new RetroShare services through JSON API
|
||||
|
||||
To offer a retroshare service through the JSON API, first of all one need find
|
||||
the global pointer to the service instance and document it in doxygen syntax,
|
||||
plus marking with the custom doxygen command +@jsonapi{RS_VERSION}+ where
|
||||
+RS_VERSION+ is the retroshare version in which this service became available
|
||||
with the current semantic (major changes to the service semantic, changes the
|
||||
meaning of the service itself, so the version should be updated in the
|
||||
documentation in that case).
|
||||
|
||||
.Service instance pointer in rsgxschannels.h
|
||||
[source,cpp]
|
||||
--------------------------------------------------------------------------------
|
||||
/**
|
||||
* Pointer to global instance of RsGxsChannels service implementation
|
||||
* @jsonapi{development}
|
||||
*/
|
||||
extern RsGxsChannels* rsGxsChannels;
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
||||
Once the service instance itself is known to the JSON API you need to document
|
||||
in doxygen syntax and mark with the custom doxygen command
|
||||
+@jsonapi{RS_VERSION}+ the methods of the service that you want to make
|
||||
available through JSON API.
|
||||
|
||||
.Offering RsGxsChannels::getChannelDownloadDirectory in rsgxschannels.h
|
||||
[source,cpp]
|
||||
--------------------------------------------------------------------------------
|
||||
/**
|
||||
* Get download directory for the given channel
|
||||
* @jsonapi{development}
|
||||
* @param[in] channelId id of the channel
|
||||
* @param[out] directory reference to string where to store the path
|
||||
* @return false on error, true otherwise
|
||||
*/
|
||||
virtual bool getChannelDownloadDirectory( const RsGxsGroupId& channelId,
|
||||
std::string& directory ) = 0;
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
For each paramether you must specify if it is used as input +@param[in]+ as
|
||||
output +@param[out]+ or both +@param[inout]+. Paramethers and return value
|
||||
types must be of a type supported by +RsTypeSerializer+ which already support
|
||||
most basic types (+bool+, +std::string+...), +RsSerializable+ and containers of
|
||||
them like +std::vector<std::string>+. Paramethers passed by value and by
|
||||
reference of those types are both supported, while passing by pointer is not
|
||||
supported. If your paramether or return +class+/+struct+ type is not supported
|
||||
yet by +RsTypeSerializer+ most convenient approach is to make it derive from
|
||||
+RsSerializable+ and implement +serial_process+ method like I did with
|
||||
+RsGxsChannelGroup+.
|
||||
|
||||
.Deriving RsGxsChannelGroup from RsSerializable in rsgxschannels.h
|
||||
[source,cpp]
|
||||
--------------------------------------------------------------------------------
|
||||
struct RsGxsChannelGroup : RsSerializable
|
||||
{
|
||||
RsGroupMetaData mMeta;
|
||||
std::string mDescription;
|
||||
RsGxsImage mImage;
|
||||
|
||||
bool mAutoDownload;
|
||||
|
||||
/// @see RsSerializable
|
||||
virtual void serial_process( RsGenericSerializer::SerializeJob j,
|
||||
RsGenericSerializer::SerializeContext& ctx )
|
||||
{
|
||||
RS_SERIAL_PROCESS(mMeta);
|
||||
RS_SERIAL_PROCESS(mDescription);
|
||||
RS_SERIAL_PROCESS(mImage);
|
||||
RS_SERIAL_PROCESS(mAutoDownload);
|
||||
}
|
||||
};
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
You can do the same recursively for any member of your +struct+ that is not yet
|
||||
supported by +RsTypeSerializer+.
|
||||
|
||||
Some Retroshare {Cxx} API functions are asyncronous, historically RetroShare
|
||||
didn't follow a policy on how to expose asyncronous API so differents services
|
||||
and some times even differents method of the same service follow differents
|
||||
asyncronous patterns, thus making automatic generation of JSON API wrappers for
|
||||
those methods impractical. Instead of dealing with all those differents patterns
|
||||
I have chosed to support only one new pattern taking advantage of modern {Cxx}11
|
||||
and restbed features. On the {Cxx}11 side lambdas and +std::function+s are used,
|
||||
on the restbed side Server Side Events are used to send asyncronous results.
|
||||
|
||||
Lets see an example so it will be much esier to understand.
|
||||
|
||||
.RsGxsChannels::turtleSearchRequest asyncronous API
|
||||
[source,cpp]
|
||||
--------------------------------------------------------------------------------
|
||||
/**
|
||||
* @brief Request remote channels search
|
||||
* @jsonapi{development}
|
||||
* @param[in] matchString string to look for in the search
|
||||
* @param multiCallback function that will be called each time a search
|
||||
* result is received
|
||||
* @param[in] maxWait maximum wait time in seconds for search results
|
||||
* @return false on error, true otherwise
|
||||
*/
|
||||
virtual bool turtleSearchRequest(
|
||||
const std::string& matchString,
|
||||
const std::function<void (const RsGxsGroupSummary& result)>& multiCallback,
|
||||
std::time_t maxWait = 300 ) = 0;
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
+RsGxsChannels::turtleSearchRequest(...)+ is an asyncronous method because it
|
||||
send a channel search request on turtle network and then everytime a result is
|
||||
received from the network +multiCallback+ is called and the result is passed as
|
||||
parameter. To be supported by the automatic JSON API wrappers generator an
|
||||
asyncronous method need a parameter of type +std::function<void (...)>+ called
|
||||
+callback+ if the callback will be called only once or +multiCallback+ if the
|
||||
callback is expected to be called more then once like in this case.
|
||||
A second mandatory parameter is +maxWait+ of type +std::time_t+ it indicates the
|
||||
maximum amount of time in seconds for which the caller is willing to wait for
|
||||
results, in case the timeout is reached the callback will not be called anymore.
|
||||
|
||||
[IMPORTANT]
|
||||
================================================================================
|
||||
+callback+ and +multiCallback+ parameters documentation must *not* specify
|
||||
+[in]+, +[out]+, +[inout]+, in Doxygen documentation as this would fool the
|
||||
automatic wrapper generator, and ultimately break the compilation.
|
||||
================================================================================
|
||||
|
||||
.RsFiles::turtleSearchRequest asyncronous JSON API usage example
|
||||
[source,bash]
|
||||
--------------------------------------------------------------------------------
|
||||
$ cat turtle_search.json
|
||||
{
|
||||
"matchString":"linux"
|
||||
}
|
||||
$ curl --data @turtle_search.json http://127.0.0.1:9092/rsFiles/turtleSearchRequest
|
||||
data: {"retval":true}
|
||||
|
||||
data: {"results":[{"size":157631,"hash":"69709b4d01025584a8def5cd78ebbd1a3cf3fd05","name":"kill_bill_linux_1024x768.jpg"},{"size":192560,"hash":"000000000000000000009a93e5be8486c496f46c","name":"coffee_box_linux2.jpg"},{"size":455087,"hash":"9a93e5be8486c496f46c00000000000000000000","name":"Linux.png"},{"size":182004,"hash":"e8845280912ebf3779e400000000000000000000","name":"Linux_2_6.png"}]}
|
||||
|
||||
data: {"results":[{"size":668,"hash":"e8845280912ebf3779e400000000000000000000","name":"linux.png"},{"size":70,"hash":"e8845280912ebf3779e400000000000000000000","name":"kali-linux-2016.2-amd64.txt.sha1sum"},{"size":3076767744,"hash":"e8845280912ebf3779e400000000000000000000","name":"kali-linux-2016.2-amd64.iso"},{"size":2780872,"hash":"e8845280912ebf3779e400000000000000000000","name":"openwrt-ar71xx-generic-vmlinux.bin"},{"size":917504,"hash":"e8845280912ebf3779e400000000000000000000","name":"openwrt-ar71xx-generic-vmlinux.lzma"},{"size":2278404096,"hash":"e8845280912ebf3779e400000000000000000000","name":"gentoo-linux-livedvd-amd64-multilib-20160704.iso"},{"size":151770333,"hash":"e8845280912ebf3779e400000000000000000000","name":"flashtool-0.9.23.0-linux.tar.7z"},{"size":2847372,"hash":"e8845280912ebf3779e400000000000000000000","name":"openwrt-ar71xx-generic-vmlinux.elf"},{"size":1310720,"hash":"e8845280912ebf3779e400000000000000000000","name":"openwrt-ar71xx-generic-vmlinux.gz"},{"size":987809,"hash":"e8845280912ebf3779e400000000000000000000","name":"openwrt-ar71xx-generic-vmlinux-lzma.elf"}]}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
By default JSON API methods requires client authentication and their wrappers
|
||||
are automatically generated by +json-api-generator+.
|
||||
In some cases methods need do be accessible without authentication such as
|
||||
+rsLoginHelper/getLocations+ so in the doxygen documentaion they have the custom
|
||||
command +@jsonapi{RS_VERSION,unauthenticated}+.
|
||||
Other methods such as +/rsControl/rsGlobalShutDown+ need special care so they
|
||||
are marked with the custom doxygen command +@jsonapi{RS_VERSION,manualwrapper}+
|
||||
and their wrappers are not automatically generated but written manually into
|
||||
+JsonApiServer::JsonApiServer(...)+.
|
||||
|
||||
== Quirks
|
||||
|
||||
=== 64 bits integers handling
|
||||
|
||||
While JSON doesn't have problems representing 64 bits integers JavaScript, Dart
|
||||
and other languages are not capable to handle those numbers natively.
|
||||
To overcome this limitation JSON API output 64 bit integers as an object with
|
||||
two keys, one as proper integer and one as string representation.
|
||||
|
||||
.JSON API 64 bit integer output example
|
||||
[source,json]
|
||||
--------------------------------------------------------------------------------
|
||||
"lobby_id": { "xint64": 6215642878098695544, "xstr64": "6215642878098695544" }
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
So from languages that have proper 64bit integers support like Python or C++ one
|
||||
better read from `xint64` which is represented as a JSON integer, from languages
|
||||
where there is no proper 64bit integers support like JavaScript one can read from
|
||||
`xstr64` which is represented as JSON string (note that the first is not wrapped
|
||||
in "" while the latter is).
|
||||
|
||||
When one input a 64bit integer into the JSON API it first try to parse it as if
|
||||
it was sent the old way for retrocompatibility.
|
||||
|
||||
.JSON API 64 bit integer deprecated format input example
|
||||
[source,json]
|
||||
--------------------------------------------------------------------------------
|
||||
"lobby_id":6215642878098695544
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
This way is *DEPRECATED* and may disappear in the future, it is TEMPORALLY kept
|
||||
only for retrocompatibiliy with old clients.
|
||||
|
||||
If retrocompatible parsing attempt fail then it try to parse with the new way
|
||||
with proper JSON integer format.
|
||||
|
||||
.JSON API 64 bit integer new proper integer format input example
|
||||
[source,json]
|
||||
--------------------------------------------------------------------------------
|
||||
lobby_id": { "xint64": 6215642878098695544 }
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
If this fails then it try to parse with the new way with JSON string format.
|
||||
|
||||
.JSON API 64 bit integer new string format input example
|
||||
[source,json]
|
||||
--------------------------------------------------------------------------------
|
||||
"lobby_id": { "xstr64": "6215642878098695544" }
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
[WARNING]
|
||||
================================================================================
|
||||
Clients written in languages without proper 64bit integers support must
|
||||
use *ONLY* the string format otherwise they will send approximated values and
|
||||
get unexpected results from the JSON API, because parsing will success but the
|
||||
value will not be exactly the one you believe you sent.
|
||||
================================================================================
|
||||
|
||||
|
||||
== A bit of history
|
||||
|
||||
=== First writings about this
|
||||
|
||||
The previous attempt of exposing a RetroShare JSON API is called +libresapi+ and
|
||||
unfortunatley it requires a bunch of boilerplate code when we want to expose
|
||||
something present in the {Cxx} API in the JSON API.
|
||||
|
||||
As an example here you can see the libresapi that exposes part of the retroshare
|
||||
chat {Cxx} API and lot of boilerplate code just to convert {Cxx} objects to JSON
|
||||
|
||||
https://github.com/RetroShare/RetroShare/blob/v0.6.4/libresapi/src/api/ChatHandler.cpp#L44
|
||||
|
||||
To avoid the {Cxx} to JSON and back conversion boilerplate code I have worked out
|
||||
an extension to our {Cxx} serialization code so it is capable to serialize and
|
||||
deserialize to JSON you can see it in this pull request
|
||||
|
||||
https://github.com/RetroShare/RetroShare/pull/1155
|
||||
|
||||
So first step toward having a good API is to take advantage of the fact that RS
|
||||
is now capable of converting C++ objects from and to JSON.
|
||||
|
||||
The current API is accessible via HTTP and unix socket, there is no
|
||||
authentication in both of them, so anyone having access to the HTTP server or to
|
||||
the unix socket can access the API without extra restrictions.
|
||||
Expecially for the HTTP API this is a big risk because also if the http server
|
||||
listen on 127.0.0.1 every application on the machine (even rogue javascript
|
||||
running on your web browser) can access that and for example on android it is
|
||||
not safe at all (because of that I implemented the unix socket access so at
|
||||
least in android API was reasonably safe) because of this.
|
||||
|
||||
A second step to improve the API would be to implement some kind of API
|
||||
authentication mechanism (it would be nice that the mechanism is handled at API
|
||||
level and not at transport level so we can use it for any API trasport not just
|
||||
HTTP for example)
|
||||
|
||||
The HTTP server used by libresapi is libmicrohttpd server that is very minimal,
|
||||
it doesn't provide HTTPS nor modern HTTP goodies, like server notifications,
|
||||
websockets etc. because the lack of support we have a token polling mechanism in
|
||||
libresapi to avoid polling for every thing but it is still ugly, so if we can
|
||||
completely get rid of polling in the API that would be really nice.
|
||||
I have done a crawl to look for a replacement and briefly looked at
|
||||
|
||||
- https://www.gnu.org/software/libmicrohttpd/
|
||||
- http://wolkykim.github.io/libasyncd/
|
||||
- https://github.com/corvusoft/restbed
|
||||
- https://code.facebook.com/posts/1503205539947302/introducing-proxygen-facebook-s-c-http-framework/
|
||||
- https://github.com/cmouse/yahttp
|
||||
|
||||
taking in account a few metrics like modern HTTP goodies support, license,
|
||||
platform support, external dependencies and documentation it seemed to me that
|
||||
restbed is the more appropriate.
|
||||
|
||||
Another source of boilerplate code into libresapi is the mapping between JSON
|
||||
API requests and C++ API methods as an example you can look at this
|
||||
|
||||
https://github.com/RetroShare/RetroShare/blob/v0.6.4/libresapi/src/api/ChatHandler.cpp#L158
|
||||
|
||||
and this
|
||||
|
||||
https://github.com/RetroShare/RetroShare/blob/v0.6.4/libresapi/src/api/ApiServer.cpp#L253
|
||||
|
||||
The abstract logic of this thing is, when libreasapi get a request like
|
||||
+/chat/initiate_distant_chat+ then call
|
||||
+ChatHandler::handleInitiateDistantChatConnexion+ which in turn is just a
|
||||
wrapper of +RsMsgs::initiateDistantChatConnexion+ all this process is basically
|
||||
implemented as boilerplate code and would be unnecessary in a smarter design of
|
||||
the API because almost all the information needed is already present in the
|
||||
C++ API +libretroshare/src/retroshare+.
|
||||
|
||||
So a third step to improve the JSON API would be to remove this source of
|
||||
boilerplate code by automatizing the mapping between C++ and JSON API call.
|
||||
|
||||
This may result a little tricky as language parsing or other adevanced things
|
||||
may be required.
|
||||
|
||||
Hope this dive is useful for you +
|
||||
Cheers +
|
||||
G10h4ck
|
||||
|
||||
=== Second writings about this
|
||||
|
||||
I have been investigating a bit more about:
|
||||
[verse, G10h4ck]
|
||||
________________________________________________________________________________
|
||||
So a third step to improve the JSON API would be to remove this source of
|
||||
boilerplate code by automatizing the mapping between C++ and JSON API call
|
||||
________________________________________________________________________________
|
||||
|
||||
After spending some hours investigating this topic the most reasonable approach
|
||||
seems to:
|
||||
|
||||
1. Properly document headers in +libretroshare/src/retroshare/+ in doxygen syntax
|
||||
specifying wihich params are input and/or output (doxygen sysntax for this is
|
||||
+@param[in/out/inout]+) this will be the API documentation too.
|
||||
|
||||
2. At compile time use doxygen to generate XML description of the headers and use
|
||||
the XML to generate the JSON api server stub.
|
||||
http://www.stack.nl/~dimitri/doxygen/manual/customize.html#xmlgenerator
|
||||
|
||||
3. Enjoy
|
@ -0,0 +1,80 @@
|
||||
/*******************************************************************************
|
||||
* RetroShare JSON API *
|
||||
* *
|
||||
* Copyright (C) 2018-2019 Gioacchino Mazzurco <gio@eigenlab.org> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License version 3 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
registerHandler( "$%apiPath%$",
|
||||
[this](const std::shared_ptr<rb::Session> session)
|
||||
{
|
||||
const std::multimap<std::string, std::string> headers
|
||||
{
|
||||
{ "Connection", "keep-alive" },
|
||||
{ "Content-Type", "text/event-stream" }
|
||||
};
|
||||
session->yield(rb::OK, headers);
|
||||
|
||||
size_t reqSize = session->get_request()->get_header("Content-Length", 0);
|
||||
session->fetch( reqSize, [this](
|
||||
const std::shared_ptr<rb::Session> session,
|
||||
const rb::Bytes& body )
|
||||
{
|
||||
INITIALIZE_API_CALL_JSON_CONTEXT;
|
||||
|
||||
if( !checkRsServicePtrReady(
|
||||
$%instanceName%$, "$%instanceName%$", cAns, session ) )
|
||||
return;
|
||||
|
||||
$%paramsDeclaration%$
|
||||
|
||||
$%inputParamsDeserialization%$
|
||||
|
||||
const std::weak_ptr<rb::Service> weakService(mService);
|
||||
const std::weak_ptr<rb::Session> weakSession(session);
|
||||
$%callbackName%$ = [weakService, weakSession]($%callbackParams%$)
|
||||
{
|
||||
auto session = weakSession.lock();
|
||||
if(!session || session->is_closed()) return;
|
||||
|
||||
auto lService = weakService.lock();
|
||||
if(!lService || lService->is_down()) return;
|
||||
|
||||
$%callbackParamsSerialization%$
|
||||
|
||||
std::stringstream sStream;
|
||||
sStream << "data: " << compactJSON << ctx.mJson << "\n\n";
|
||||
const std::string message = sStream.str();
|
||||
|
||||
lService->schedule( [weakSession, message]()
|
||||
{
|
||||
auto session = weakSession.lock();
|
||||
if(!session || session->is_closed()) return;
|
||||
session->yield(message);
|
||||
$%sessionEarlyClose%$
|
||||
} );
|
||||
};
|
||||
|
||||
$%functionCall%$
|
||||
|
||||
$%outputParamsSerialization%$
|
||||
|
||||
// return them to the API caller
|
||||
std::stringstream message;
|
||||
message << "data: " << compactJSON << cAns.mJson << "\n\n";
|
||||
session->yield(message.str());
|
||||
$%sessionDelayedClose%$
|
||||
} );
|
||||
}, $%requiresAuth%$ );
|
230
libretroshare/src/jsonapi/jsonapi-generator-doxygen.conf
Normal file
230
libretroshare/src/jsonapi/jsonapi-generator-doxygen.conf
Normal file
@ -0,0 +1,230 @@
|
||||
DOXYFILE_ENCODING = UTF-8
|
||||
PROJECT_NAME = "libretroshare"
|
||||
|
||||
ALIASES += jsonapi{1}="\xmlonly<jsonapi minversion=\"\1\"/>\endxmlonly"
|
||||
ALIASES += jsonapi{2}="\xmlonly<jsonapi minversion=\"\1\" access=\"\2\"/>\endxmlonly"
|
||||
|
||||
# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
|
||||
# documentation from any documented member that it re-implements.
|
||||
# The default value is: YES.
|
||||
|
||||
INHERIT_DOCS = YES
|
||||
|
||||
# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
|
||||
# according to the Markdown format, which allows for more readable
|
||||
# documentation. See http://daringfireball.net/projects/markdown/ for details.
|
||||
# The output of markdown processing is further processed by doxygen, so you can
|
||||
# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
|
||||
# case of backward compatibilities issues.
|
||||
# The default value is: YES.
|
||||
|
||||
MARKDOWN_SUPPORT = YES
|
||||
|
||||
# When enabled doxygen tries to link words that correspond to documented
|
||||
# classes, or namespaces to their corresponding documentation. Such a link can
|
||||
# be prevented in individual cases by putting a % sign in front of the word or
|
||||
# globally by setting AUTOLINK_SUPPORT to NO.
|
||||
# The default value is: YES.
|
||||
|
||||
AUTOLINK_SUPPORT = YES
|
||||
|
||||
|
||||
# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
|
||||
# documentation are documented, even if no documentation was available. Private
|
||||
# class members and static file members will be hidden unless the
|
||||
# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
|
||||
# Note: This will also disable the warnings about undocumented members that are
|
||||
# normally produced when WARNINGS is set to YES.
|
||||
# The default value is: NO.
|
||||
|
||||
EXTRACT_ALL = YES
|
||||
|
||||
|
||||
# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
|
||||
# undocumented members inside documented classes or files. If set to NO these
|
||||
# members will be included in the various overviews, but no documentation
|
||||
# section is generated. This option has no effect if EXTRACT_ALL is enabled.
|
||||
# The default value is: NO.
|
||||
|
||||
HIDE_UNDOC_MEMBERS = NO
|
||||
|
||||
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
|
||||
# undocumented classes that are normally visible in the class hierarchy. If set
|
||||
# to NO, these classes will be included in the various overviews. This option
|
||||
# has no effect if EXTRACT_ALL is enabled.
|
||||
# The default value is: NO.
|
||||
|
||||
HIDE_UNDOC_CLASSES = NO
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the input files
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# The INPUT tag is used to specify the files and/or directories that contain
|
||||
# documented source files. You may enter file names like myfile.cpp or
|
||||
# directories like /usr/src/myproject. Separate the files or directories with
|
||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||
# Note: If this tag is empty the current directory is searched.
|
||||
|
||||
#INPUT =
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
||||
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
|
||||
# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
|
||||
# possible encodings.
|
||||
# The default value is: UTF-8.
|
||||
|
||||
INPUT_ENCODING = UTF-8
|
||||
|
||||
# If the value of the INPUT tag contains directories, you can use the
|
||||
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
|
||||
# *.h) to filter out the source-files in the directories.
|
||||
#
|
||||
# Note that for custom extensions or not directly supported extensions you also
|
||||
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
|
||||
# read by doxygen.
|
||||
#
|
||||
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
|
||||
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
|
||||
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
|
||||
# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
|
||||
# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
|
||||
|
||||
FILE_PATTERNS = *.c \
|
||||
*.cc \
|
||||
*.cxx \
|
||||
*.cpp \
|
||||
*.c++ \
|
||||
*.java \
|
||||
*.ii \
|
||||
*.ixx \
|
||||
*.ipp \
|
||||
*.i++ \
|
||||
*.inl \
|
||||
*.idl \
|
||||
*.ddl \
|
||||
*.odl \
|
||||
*.h \
|
||||
*.hh \
|
||||
*.hxx \
|
||||
*.hpp \
|
||||
*.h++ \
|
||||
*.cs \
|
||||
*.d \
|
||||
*.php \
|
||||
*.php4 \
|
||||
*.php5 \
|
||||
*.phtml \
|
||||
*.inc \
|
||||
*.m \
|
||||
*.markdown \
|
||||
*.md \
|
||||
*.mm \
|
||||
*.dox \
|
||||
*.py \
|
||||
*.pyw \
|
||||
*.f90 \
|
||||
*.f95 \
|
||||
*.f03 \
|
||||
*.f08 \
|
||||
*.f \
|
||||
*.for \
|
||||
*.tcl \
|
||||
*.vhd \
|
||||
*.vhdl \
|
||||
*.ucf \
|
||||
*.qsf
|
||||
|
||||
# The RECURSIVE tag can be used to specify whether or not subdirectories should
|
||||
# be searched for input files as well.
|
||||
# The default value is: NO.
|
||||
|
||||
RECURSIVE = YES
|
||||
|
||||
# The EXCLUDE tag can be used to specify files and/or directories that should be
|
||||
# excluded from the INPUT source files. This way you can easily exclude a
|
||||
# subdirectory from a directory tree whose root is specified with the INPUT tag.
|
||||
#
|
||||
# Note that relative paths are relative to the directory from which doxygen is
|
||||
# run.
|
||||
|
||||
EXCLUDE =
|
||||
|
||||
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
|
||||
# directories that are symbolic links (a Unix file system feature) are excluded
|
||||
# from the input.
|
||||
# The default value is: NO.
|
||||
|
||||
EXCLUDE_SYMLINKS = NO
|
||||
|
||||
# If the value of the INPUT tag contains directories, you can use the
|
||||
# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
|
||||
# certain files from those directories.
|
||||
#
|
||||
# Note that the wildcards are matched against the file with absolute path, so to
|
||||
# exclude all test directories for example use the pattern */test/*
|
||||
|
||||
EXCLUDE_PATTERNS =
|
||||
|
||||
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
|
||||
# (namespaces, classes, functions, etc.) that should be excluded from the
|
||||
# output. The symbol name can be a fully qualified name, a word, or if the
|
||||
# wildcard * is used, a substring. Examples: ANamespace, AClass,
|
||||
# AClass::ANamespace, ANamespace::*Test
|
||||
#
|
||||
# Note that the wildcards are matched against the file with absolute path, so to
|
||||
# exclude all test directories use the pattern */test/*
|
||||
|
||||
EXCLUDE_SYMBOLS =
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the HTML output
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
|
||||
# The default value is: YES.
|
||||
|
||||
GENERATE_HTML = NO
|
||||
|
||||
# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
|
||||
# The default value is: YES.
|
||||
|
||||
GENERATE_LATEX = NO
|
||||
|
||||
# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
|
||||
# captures the structure of the code including all documentation.
|
||||
# The default value is: NO.
|
||||
|
||||
GENERATE_XML = YES
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the preprocessor
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
|
||||
# C-preprocessor directives found in the sources and include files.
|
||||
# The default value is: YES.
|
||||
|
||||
ENABLE_PREPROCESSING = YES
|
||||
|
||||
# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
|
||||
# in the source code. If set to NO, only conditional compilation will be
|
||||
# performed. Macro expansion can be done in a controlled way by setting
|
||||
# EXPAND_ONLY_PREDEF to YES.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
MACRO_EXPANSION = NO
|
||||
|
||||
# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
|
||||
# the macro expansion is limited to the macros specified with the PREDEFINED and
|
||||
# EXPAND_AS_DEFINED tags.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
EXPAND_ONLY_PREDEF = NO
|
||||
|
365
libretroshare/src/jsonapi/jsonapi-generator.py
Executable file
365
libretroshare/src/jsonapi/jsonapi-generator.py
Executable file
@ -0,0 +1,365 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# RetroShare JSON API generator
|
||||
#
|
||||
# Copyright (C) 2019 selankon <selankon@selankon.xyz>
|
||||
# Copyright (C) 2021 Gioacchino Mazzurco <gio@eigenlab.org>
|
||||
# Copyright (C) 2021 Asociación Civil Altermundi <info@altermundi.net>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the
|
||||
# Free Software Foundation, version 3.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#
|
||||
# SPDX-FileCopyrightText: Retroshare Team <contact@retroshare.cc>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
|
||||
# Original idea and implementation by G10h4ck (jsonapi-generator.cpp)
|
||||
# Initial python reimplementation by Sehraf
|
||||
#
|
||||
# This python 3 script has superseded the original C++/Qt implementation this
|
||||
# and is now used at build time in without depending on Qt.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
from string import Template
|
||||
|
||||
|
||||
class MethodParam:
|
||||
_type = ''
|
||||
_name = ''
|
||||
_defval = ''
|
||||
_in = False
|
||||
_out = False
|
||||
_isMultiCallback = False
|
||||
_isSingleCallback = False
|
||||
|
||||
|
||||
class TemplateOwn(Template):
|
||||
delimiter = '$%'
|
||||
pattern = '''
|
||||
\$%(?:
|
||||
(?P<escaped>\$\%) | # Escape sequence of two delimiters
|
||||
(?P<named>[_a-z][_a-z0-9]*)%\$ | # delimiter and a Python identifier
|
||||
{(?P<braced>[_a-z][_a-z0-9]*)} | # delimiter and a braced identifier
|
||||
(?P<invalid>) # Other ill-formed delimiter exprs
|
||||
)
|
||||
'''
|
||||
|
||||
|
||||
def getText(e):
|
||||
return "".join(e.itertext())
|
||||
|
||||
|
||||
def processFile(file):
|
||||
try:
|
||||
dom1 = ET.parse(file).getroot()
|
||||
except FileNotFoundError:
|
||||
print('Can\'t open:', file)
|
||||
|
||||
headerFileInfo = dom1[0].findall('location')[0].attrib['file']
|
||||
headerRelPath = os.path.dirname(headerFileInfo).split('/')[-1] + '/' + os.path.basename(headerFileInfo)
|
||||
|
||||
for sectDef in dom1.findall('.//memberdef'):
|
||||
if sectDef.attrib['kind'] != 'variable' or sectDef.find('.//jsonapi') == None:
|
||||
continue
|
||||
|
||||
instanceName = sectDef.find('name').text
|
||||
typeName = sectDef.find('type/ref').text
|
||||
|
||||
typeFilePath = sectDef.find('type/ref').attrib['refid']
|
||||
|
||||
try:
|
||||
dom2 = ET.parse(doxPrefix + typeFilePath + '.xml').getroot()
|
||||
except FileNotFoundError:
|
||||
print('Can\'t open:', doxPrefix + typeFilePath + '.xml')
|
||||
|
||||
for member in dom2.findall('.//member'):
|
||||
refid = member.attrib['refid']
|
||||
methodName = member.find('name').text
|
||||
|
||||
requiresAuth = True
|
||||
|
||||
defFilePath = refid.split('_')[0] + '.xml'
|
||||
defFile = defFilePath
|
||||
|
||||
print('Looking for', typeName, methodName, 'into', typeFilePath)
|
||||
|
||||
try:
|
||||
defDoc = ET.parse(doxPrefix + defFilePath).getroot()
|
||||
except FileNotFoundError:
|
||||
print('Can\'t open:', doxPrefix + defFilePath)
|
||||
|
||||
memberdef = None
|
||||
for tmpMBD in defDoc.findall('.//memberdef'):
|
||||
tmpId = tmpMBD.attrib['id']
|
||||
tmpKind = tmpMBD.attrib['kind']
|
||||
tmpJsonApiTagList = tmpMBD.findall('.//jsonapi')
|
||||
|
||||
if len(tmpJsonApiTagList) != 0 and tmpId == refid and tmpKind == 'function':
|
||||
tmpJsonApiTag = tmpJsonApiTagList[0]
|
||||
|
||||
tmpAccessValue = None
|
||||
if 'access' in tmpJsonApiTag.attrib:
|
||||
tmpAccessValue = tmpJsonApiTag.attrib['access']
|
||||
|
||||
requiresAuth = 'unauthenticated' != tmpAccessValue;
|
||||
|
||||
if 'manualwrapper' != tmpAccessValue:
|
||||
memberdef = tmpMBD
|
||||
|
||||
break
|
||||
|
||||
if memberdef == None:
|
||||
continue
|
||||
|
||||
apiPath = '/' + instanceName + '/' + methodName
|
||||
|
||||
retvalType = getText(memberdef.find('type'))
|
||||
# Apparently some xml declarations include new lines ('\n') and/or multiple spaces
|
||||
# Strip them using python magic
|
||||
retvalType = ' '.join(retvalType.split())
|
||||
|
||||
paramsMap = {}
|
||||
orderedParamNames = []
|
||||
|
||||
hasInput = False
|
||||
hasOutput = False
|
||||
hasSingleCallback = False
|
||||
hasMultiCallback = False
|
||||
callbackName = ''
|
||||
callbackParams = ''
|
||||
|
||||
for tmpPE in memberdef.findall('param'):
|
||||
mp = MethodParam()
|
||||
|
||||
pName = getText(tmpPE.find('declname'))
|
||||
tmpDefval = tmpPE.find('defval')
|
||||
mp._defval = getText(tmpDefval) if tmpDefval != None else ''
|
||||
pType = getText(tmpPE.find('type'))
|
||||
|
||||
if pType.startswith('const '): pType = pType[6:]
|
||||
if pType.startswith('std::function'):
|
||||
if pType.endswith('&'): pType = pType[:-1]
|
||||
if pName.startswith('multiCallback'):
|
||||
mp._isMultiCallback = True
|
||||
hasMultiCallback = True
|
||||
elif pName.startswith('callback'):
|
||||
mp._isSingleCallback = True
|
||||
hasSingleCallback = True
|
||||
callbackName = pName
|
||||
callbackParams = pType
|
||||
else:
|
||||
pType = pType.replace('&', '').replace(' ', '')
|
||||
|
||||
# Apparently some xml declarations include new lines ('\n') and/or multiple spaces
|
||||
# Strip them using python magic
|
||||
pType = ' '.join(pType.split())
|
||||
mp._defval = ' '.join(mp._defval.split())
|
||||
|
||||
mp._type = pType
|
||||
mp._name = pName
|
||||
|
||||
paramsMap[pName] = mp
|
||||
orderedParamNames.append(pName)
|
||||
|
||||
for tmpPN in memberdef.findall('.//parametername'):
|
||||
tmpParam = paramsMap[tmpPN.text]
|
||||
tmpD = tmpPN.attrib['direction'] if 'direction' in tmpPN.attrib else ''
|
||||
|
||||
if 'in' in tmpD:
|
||||
tmpParam._in = True
|
||||
hasInput = True
|
||||
if 'out' in tmpD:
|
||||
tmpParam._out = True
|
||||
hasOutput = True
|
||||
|
||||
# Params sanity check
|
||||
for pmKey in paramsMap:
|
||||
pm = paramsMap[pmKey]
|
||||
if not (pm._isMultiCallback or pm._isSingleCallback or pm._in or pm._out):
|
||||
print('ERROR', 'Parameter:', pm._name, 'of:', apiPath,
|
||||
'declared in:', headerRelPath,
|
||||
'miss doxygen parameter direction attribute!',
|
||||
defFile)
|
||||
sys.exit()
|
||||
|
||||
functionCall = '\t\t'
|
||||
if retvalType != 'void':
|
||||
functionCall += retvalType + ' retval = '
|
||||
hasOutput = True
|
||||
functionCall += instanceName + '->' + methodName + '('
|
||||
functionCall += ', '.join(orderedParamNames) + ');\n'
|
||||
|
||||
print(instanceName, apiPath, retvalType, typeName, methodName)
|
||||
for pn in orderedParamNames:
|
||||
mp = paramsMap[pn]
|
||||
print('\t', mp._type, mp._name, mp._in, mp._out)
|
||||
|
||||
inputParamsDeserialization = ''
|
||||
if hasInput:
|
||||
inputParamsDeserialization += '\t\t{\n'
|
||||
inputParamsDeserialization += '\t\t\tRsGenericSerializer::SerializeContext& ctx(cReq);\n'
|
||||
inputParamsDeserialization += '\t\t\tRsGenericSerializer::SerializeJob j(RsGenericSerializer::FROM_JSON);\n';
|
||||
|
||||
outputParamsSerialization = ''
|
||||
if hasOutput:
|
||||
outputParamsSerialization += '\t\t{\n'
|
||||
outputParamsSerialization += '\t\t\tRsGenericSerializer::SerializeContext& ctx(cAns);\n'
|
||||
outputParamsSerialization += '\t\t\tRsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON);\n';
|
||||
|
||||
paramsDeclaration = ''
|
||||
for pn in orderedParamNames:
|
||||
mp = paramsMap[pn]
|
||||
paramsDeclaration += '\t\t' + mp._type + ' ' + mp._name
|
||||
if mp._defval != '':
|
||||
paramsDeclaration += ' = ' + mp._defval
|
||||
paramsDeclaration += ';\n'
|
||||
if mp._in:
|
||||
inputParamsDeserialization += '\t\t\tRS_SERIAL_PROCESS('
|
||||
inputParamsDeserialization += mp._name + ');\n'
|
||||
if mp._out:
|
||||
outputParamsSerialization += '\t\t\tRS_SERIAL_PROCESS('
|
||||
outputParamsSerialization += mp._name + ');\n'
|
||||
|
||||
if hasInput:
|
||||
inputParamsDeserialization += '\t\t}\n'
|
||||
if retvalType != 'void':
|
||||
outputParamsSerialization += '\t\t\tRS_SERIAL_PROCESS(retval);\n'
|
||||
if hasOutput:
|
||||
outputParamsSerialization += '\t\t}\n'
|
||||
|
||||
captureVars = ''
|
||||
|
||||
sessionEarlyClose = ''
|
||||
if hasSingleCallback:
|
||||
sessionEarlyClose = 'session->close();'
|
||||
|
||||
sessionDelayedClose = ''
|
||||
if hasMultiCallback:
|
||||
sessionDelayedClose = """
|
||||
RsThread::async( [=]()
|
||||
{
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::seconds(maxWait+120) );
|
||||
auto lService = weakService.lock();
|
||||
if(!lService || lService->is_down()) return;
|
||||
lService->schedule( [=]()
|
||||
{
|
||||
auto session = weakSession.lock();
|
||||
if(session && session->is_open())
|
||||
session->close();
|
||||
} );
|
||||
} );
|
||||
"""
|
||||
captureVars = 'this'
|
||||
|
||||
callbackParamsSerialization = ''
|
||||
|
||||
if hasSingleCallback or hasMultiCallback or (callbackParams.find('(') + 2 < callbackParams.find(')')):
|
||||
cbs = ''
|
||||
|
||||
callbackParams = callbackParams.split('(')[1]
|
||||
callbackParams = callbackParams.split(')')[0]
|
||||
|
||||
cbs += '\t\t\tRsGenericSerializer::SerializeContext ctx;\n'
|
||||
|
||||
for cbPar in callbackParams.split(','):
|
||||
isConst = cbPar.startswith('const ')
|
||||
pSep = ' '
|
||||
isRef = '&' in cbPar
|
||||
if isRef: pSep = '&'
|
||||
sepIndex = cbPar.rfind(pSep) + 1
|
||||
cpt = cbPar[0:sepIndex][6:]
|
||||
cpn = cbPar[sepIndex:]
|
||||
|
||||
cbs += '\t\t\tRsTypeSerializer::serial_process('
|
||||
cbs += 'RsGenericSerializer::TO_JSON, ctx, '
|
||||
if isConst:
|
||||
cbs += 'const_cast<'
|
||||
cbs += cpt
|
||||
cbs += '>('
|
||||
cbs += cpn
|
||||
if isConst: cbs += ')'
|
||||
cbs += ', "'
|
||||
cbs += cpn
|
||||
cbs += '" );\n'
|
||||
|
||||
callbackParamsSerialization += cbs
|
||||
|
||||
substitutionsMap = dict()
|
||||
substitutionsMap['paramsDeclaration'] = paramsDeclaration
|
||||
substitutionsMap['inputParamsDeserialization'] = inputParamsDeserialization
|
||||
substitutionsMap['outputParamsSerialization'] = outputParamsSerialization
|
||||
substitutionsMap['instanceName'] = instanceName
|
||||
substitutionsMap['functionCall'] = functionCall
|
||||
substitutionsMap['apiPath'] = apiPath
|
||||
substitutionsMap['sessionEarlyClose'] = sessionEarlyClose
|
||||
substitutionsMap['sessionDelayedClose'] = sessionDelayedClose
|
||||
substitutionsMap['captureVars'] = captureVars
|
||||
substitutionsMap['callbackName'] = callbackName
|
||||
substitutionsMap['callbackParams'] = callbackParams
|
||||
substitutionsMap['callbackParamsSerialization'] = callbackParamsSerialization
|
||||
substitutionsMap['requiresAuth'] = 'true' if requiresAuth else 'false'
|
||||
|
||||
# print(substitutionsMap)
|
||||
|
||||
templFilePath = sourcePath
|
||||
if hasMultiCallback or hasSingleCallback:
|
||||
templFilePath += '/async-method-wrapper-template.cpp.tmpl'
|
||||
else:
|
||||
templFilePath += '/method-wrapper-template.cpp.tmpl'
|
||||
|
||||
templFile = open(templFilePath, 'r')
|
||||
wrapperDef = TemplateOwn(templFile.read())
|
||||
|
||||
tmp = wrapperDef.substitute(substitutionsMap)
|
||||
wrappersDefFile.write(tmp)
|
||||
|
||||
cppApiIncludesSet.add('#include "' + headerRelPath + '"\n')
|
||||
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
print('Usage:', sys.argv[0], 'SOURCE_PATH OUTPUT_PATH Got:', sys.argv[:])
|
||||
sys.exit(-1)
|
||||
|
||||
sourcePath = str(sys.argv[1])
|
||||
outputPath = str(sys.argv[2])
|
||||
doxPrefix = outputPath + '/xml/'
|
||||
|
||||
try:
|
||||
wrappersDefFile = open(outputPath + '/jsonapi-wrappers.inl', 'w')
|
||||
except FileNotFoundError:
|
||||
print('Can\'t open:', outputPath + '/jsonapi-wrappers.inl')
|
||||
|
||||
try:
|
||||
cppApiIncludesFile = open(outputPath + '/jsonapi-includes.inl', 'w');
|
||||
except FileNotFoundError:
|
||||
print('Can\'t open:', outputPath + '/jsonapi-includes.inl')
|
||||
|
||||
cppApiIncludesSet = set()
|
||||
|
||||
filesIterator = None
|
||||
try:
|
||||
filesIterator = os.listdir(doxPrefix)
|
||||
except FileNotFoundError:
|
||||
print("Doxygen xml output dir not found: ", doxPrefix)
|
||||
os.exit(-1)
|
||||
|
||||
for file in filesIterator:
|
||||
if file.endswith("8h.xml"):
|
||||
processFile(os.path.join(doxPrefix, file))
|
||||
|
||||
|
||||
for incl in cppApiIncludesSet:
|
||||
cppApiIncludesFile.write(incl)
|
50
libretroshare/src/jsonapi/method-wrapper-template.cpp.tmpl
Normal file
50
libretroshare/src/jsonapi/method-wrapper-template.cpp.tmpl
Normal file
@ -0,0 +1,50 @@
|
||||
/*******************************************************************************
|
||||
* RetroShare JSON API *
|
||||
* *
|
||||
* Copyright (C) 2018-2019 Gioacchino Mazzurco <gio@eigenlab.org> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as *
|
||||
* published by the Free Software Foundation, either version 3 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
registerHandler( "$%apiPath%$",
|
||||
[](const std::shared_ptr<rb::Session> session)
|
||||
{
|
||||
size_t reqSize = session->get_request()->get_header("Content-Length", 0);
|
||||
session->fetch( reqSize, [](
|
||||
const std::shared_ptr<rb::Session> session,
|
||||
const rb::Bytes& body )
|
||||
{
|
||||
INITIALIZE_API_CALL_JSON_CONTEXT;
|
||||
|
||||
if( !checkRsServicePtrReady(
|
||||
$%instanceName%$, "$%instanceName%$", cAns, session ) )
|
||||
return;
|
||||
|
||||
$%paramsDeclaration%$
|
||||
|
||||
// deserialize input parameters from JSON
|
||||
$%inputParamsDeserialization%$
|
||||
|
||||
// call retroshare C++ API
|
||||
$%functionCall%$
|
||||
|
||||
// serialize out parameters and return value to JSON
|
||||
$%outputParamsSerialization%$
|
||||
|
||||
// return them to the API caller
|
||||
DEFAULT_API_CALL_JSON_RETURN(rb::OK);
|
||||
} );
|
||||
}, $%requiresAuth%$ );
|
||||
|
Loading…
Reference in New Issue
Block a user