Browse Source

Test of session started

master
Jørgen Lien Sellæg 9 years ago
parent
commit
65eeb22503
  1. 22
      toREST/include/http.hpp
  2. 240
      toREST/include/session.hpp
  3. 58
      toREST/src/main.cxx
  4. 68
      toREST/src/session.cpp
  5. 76
      toREST/tests/session_test.cpp
  6. 1
      toREST/tests/test.cpp

22
toREST/include/http.hpp

@ -37,26 +37,16 @@ namespace http {
std::string second;
};
class basic_response {
protected:
virtual std::ostream& do_response(std::ostream& os)const=0;
public:
virtual ~basic_response(){}
virtual void add_header(const http::header &header)=0;
virtual void set_status(http::status code)=0;
virtual void set_body(const std::string &content)=0;
friend std::ostream& operator<<(std::ostream& os,const http::basic_response &rh){ return rh.do_response(os); }
};
class response : public basic_response {
class response {
public:
friend std::ostream& operator<<(std::ostream& os,const response &rh){ return rh.do_response(os); }
response():status_code(http::ok){}
void set_body(const std::string &content) override { body=content; }
void set_body(const std::string &content) { body=content; }
void set_body(const nlohmann::json &json);
void add_header(const http::header &header) override { response_headers.emplace(header); }
void set_status(http::status code) override { status_code=code; }
void add_header(const http::header &header) { response_headers.emplace(header); }
void set_status(http::status code) { status_code=code; }
std::ostream& do_response(std::ostream &os) const;
protected:
std::ostream& do_response(std::ostream &os) const override ;
std::string body;
headers response_headers;
code status_code;

240
toREST/include/session.hpp

@ -1,182 +1,68 @@
#ifndef _TOREST_SESSION_HPP_
#define _TOREST_SESSION_HPP_
#include <resource.hpp>
#include <util.hpp>
#include <libtorrent/session.hpp>
#include <http.hpp>
#include <util.hpp>
namespace session {
class basic_manager {
public:
virtual ~basic_manager()=0;
virtual nlohmann::json get_json() const = 0;
virtual bool patch(const nlohmann::json &data) = 0;
virtual bool is_valid() const = 0;
virtual int listen_port() const = 0;
virtual void set_session(std::shared_ptr<libtorrent::session>) = 0;
virtual void set_listen_port() = 0;
};
class manager : public basic_manager {
public:
void set_session(std::shared_ptr<libtorrent::session> session) override { handle=session; }
nlohmann::json get_json() const override ;
bool is_valid() const override { return handle && handle->is_valid(); }
bool patch(const nlohmann::json &data) override ;
private:
std::shared_ptr<libtorrent::session> handle;
};
class basic_resource {
public:
virtual ~basic_resource() {}
virtual void set_session(std::shared_ptr<basic_manager>)=0;
};
class resource : public basic_resource {
public:
void set_session(std::shared_ptr<basic_manager> session_manager) override { mgr=session_manager; }
virtual void get(nlohmann::json arg=nullptr) {
if(mgr && mgr->is_valid()){
response.set_status(http::ok);
response.set_body(mgr->get_json());
}else{
response.set_status(http::service_unavailable);
namespace tr{
namespace session {
template<class torrent_session, class request, class response>
void get(torrent_session&session,response resp,request req){
auto http_response = http::response();
if (session.is_valid()) {
const auto settings = session.get_settings();
http_response.set_body({
{ "paused", session.is_paused() },
{ "port", session.listen_port() },
{ "dht_enabled", session.is_dht_running() },
{ "down_limit", settings.get_int(settings.download_rate_limit) },
{ "up_limit", settings.get_int(settings.upload_rate_limit) },
{ "torrents", session.get_torrents().size() }, //TODO Optimize
// { "default_download_dir", default_download_dir.filename().string() },
// { "root_dir", root_dir.string() },
});
} else {
auto response_code = http::code(http::service_unavailable);
http_response.set_body({{"code", response_code.first}, { "status", response_code.second}});
http_response.set_status(response_code.first);
}
*resp << http_response;
}
/**
* PATCH /session
* --------------
* \brief Change values on the session.
* @param[in] data takes an JSON object with at least one `action` set.
* available `action`s are `is_paused<bool>` `listen_port<bool>`
*/
void patch(const nlohmann::json&data=nlohmann::json::object());
// @todo should we return service unavailable or just method not allowed?
void del(nlohmann::json arg=nullptr) { response.set_status(http::method_not_allowed); }
void put(nlohmann::json arg=nullptr) { response.set_status(http::method_not_allowed); }
virtual void post(nlohmann::json arg=nullptr) { response.set_status(http::method_not_allowed); }
protected:
std::shared_ptr<basic_manager> mgr;
http::response response;
};
}
#endif // _TOREST_RESOURCE_HPP_
/*! Resource: torrents
Other:
- If multiple headers with the same key is posted, the server uses only the last.
- Server can return 503 Service Unavailable if for some reason a service is down */
/*
class torrents : public Resource<std::shared_ptr<HttpServer::Response>,std::shared_ptr<HttpServer::Request>> {
public:
torrents(libtorrent::session &session);
};
*/
// std::function<void(StreamType,StreamType)>
/*! API-End-point: [POST] /v1/session/torrents
Requirements:
- Content-Type: application/json
* This header must be set, otherwise toREST will return 403 Forbidden.
- Data:
* A valid JSON-object is required.
* A valid format:
```json
{
"torrent": "<torrent_url>",
"save_path": "<path>"
}
```
* Fields `torrent` and `save_path` can not be empty. toREST will return 400 Bad Request
* The `torrent` field can be a `local torrent file`, a `magnet-uri` or a HTTP-resource.
toREST will Accept every file with a valid url, uri or path, but will not download
or create torrents if the path is a invalid torrent file.
* The `save_path` field can be any location on the server machine, in which the invoking
user has write permissions. If the save_path is a directory, the torrent will download
into the directory. If the save_path is a file, toREST will return 400 Bad Request.
Side-Effects:
- 202 Accepted, if the request is valid.
* The `Location` header is set if the torrent info_hash is available
* If a WebSocket is open a TORRENT_ADDED action is posted when the torrent resource is available.
*
POST=[&session](std::shared_ptr<HttpServer::Response> resp,std::shared_ptr<HttpServer::Request> req){
http::request_handler handler(req,resp);
// If session is invalid there is no point hanging around.
if(!session.is_valid())
return handler.respond(i18N::session_unavailable,http::service_unavailable);
// At least one header named with Content-Type, then check if the last one set is application/json
if(handler.header_equals("Content-Type","application/json"))
return handler.respond(i18N::content_type_not_set,http::forbidden);
// If the supplied JSON is invalid, bad request
auto request_json=util::json::parse(req->content);
if(!request_json.is_object() || request_json.is_null())
return handler.respond(nlohmann::json(i18N::unable_to_parse_json),http::bad_request);
// handle required fields
std::vector<std::string> required_fields={
request_json.value("torrent",""),
request_json.value("save_path","")
};
std::vector<std::string> empty_fields;
std::copy_if(required_fields.begin(),required_fields.end(),empty_fields.begin(),[](const std::string &field){return field.empty();});
if(!empty_fields.empty()){
std::string message;
for(auto &field:empty_fields)
message+=" The required field `"+field+"` is empty";
return handler.respond(nlohmann::json(i18N::wrong_format + message),http::bad_request);
}
auto torrent_field=required_fields.front();
libtorrent::add_torrent_params torrent_options;
boost::system::error_code ec;
if(torrent_field.substr(0,6)=="magnet"){
libtorrent::parse_magnet_uri(torrent_field,torrent_options,ec);
ECERROR(ec);
}
else if(boost::filesystem::exists(torrent_field,ec) && !boost::filesystem::is_directory(torrent_field,ec))
torrent_options.ti=boost::make_shared<libtorrent::torrent_info>(libtorrent::torrent_info(torrent_field,ec));
else if(boost::regex_match(torrent_field,util::regex))
torrent_options.url=torrent_field;
else {
ECERROR(ec);
return handler.respond(nlohmann::json(i18N::unable_to_parse_torrent_uri),http::bad_request);
}
boost::filesystem::path save_path(required_fields.back());
auto status=boost::filesystem::status(save_path,ec);
if(!ec){
if(boost::filesystem::exists(status) && !boost::filesystem::is_directory(status))
return handler.respond(nlohmann::json(i18N::wrong_format + " The save path is invalid"),http::bad_request);
else {
// TODO chroot
try {
boost::filesystem::create_directories(save_path);
} catch(const std::exception &ex){
return handler.respond(nlohmann::json(i18N::write_error),http::forbidden);
}
}
} else {
// invalid file status
ECERROR(ec);
}
torrent_options.save_path=save_path.generic_string();
session.async_add_torrent(torrent_options);
// TODO add websocket event
std::string info_hash(torrent_options.info_hash.data());
if(info_hash!="undefined"){
std::stringstream ss;
ss << torrent_options.info_hash;
info_hash=ss.str();
handler.headers["Location:"]= "/torrents/" + info_hash;
template<class torrent_session, class request, class response>
void patch(torrent_session&session,response resp,request req){
auto http_response = http::response();
if (!session.is_valid()) {
auto response_code = http::code(http::service_unavailable);
http_response.set_body({{response_code.first, response_code.second}});
http_response.set_status(response_code.first);
} else {
const auto json=util::json::parse(req->content);
if(json.is_object() && !json.is_null()) {
if(util::json::get("paused",json,session.is_paused()))
session.pause();
else
session.resume();
auto settings = session.get_settings();
const auto new_listen_port=util::json::get("port",json,session.listen_port());
if(new_listen_port!=session.listen_port())
settings.set_str(settings.listen_interfaces,"0.0.0.0:"+std::to_string(new_listen_port));
const auto new_dht_enabled=util::json::get("dht_enabled",json,session.is_dht_running());
if(new_dht_enabled!=session.is_dht_running())
settings.set_bool(settings.enable_dht,new_dht_enabled);
auto old_limit=settings.get_int(settings.download_rate_limit);
auto new_limit=util::json::get("down_limit",json,old_limit);
if(new_limit!=old_limit)
settings.set_int(settings.download_rate_limit,new_limit);
old_limit=settings.get_int(settings.upload_rate_limit);
new_limit=util::json::get("up_limit",json,old_limit);
if(new_limit!=old_limit)
settings.set_int(settings.upload_rate_limit,new_limit);
session.apply_settings(settings);
http_response.set_body({{200,"OK"}});
} else {
auto response_code = http::code(http::bad_request);
http_response.set_body({{response_code.first,response_code.second}});
http_response.set_status(response_code.first);
}
}
*resp << http_response;
}
return handler.respond(nlohmann::json(http::http_code(http::accepted).second), http::accepted);
};
}*/
}
}

58
toREST/src/main.cxx

@ -1,6 +1,7 @@
#include <server_http.hpp>
#include <http.hpp>
#include <session.hpp>
#include <libtorrent/session.hpp>
#include <libtorrent/session_stats.hpp>
#include <libtorrent/session_status.hpp>
#include <boost/filesystem.hpp>
@ -31,64 +32,11 @@ int main(int argc, char *argv[]) {
};
http_server.resource["^/session(\\?.*)?\\/?$"]["GET"]=[&](std::shared_ptr<HttpServer::Response> resp, std::shared_ptr<HttpServer::Request> req) {
auto response = http::response();
if (session.is_valid()) {
const auto settings = session.get_settings();
response.set_body({
{ "paused", session.is_paused() },
{ "port", session.listen_port() },
{ "dht_enabled", session.is_dht_running() },
{ "down_limit", settings.get_int(settings.download_rate_limit) },
{ "up_limit", settings.get_int(settings.upload_rate_limit) },
{ "torrents", session.get_torrents().size() }, //TODO Optimize
{ "default_download_dir", default_download_dir.filename().string() },
{ "root_dir", root_dir.string() },
});
} else {
auto response_code = http::code(http::service_unavailable);
response.set_body({{response_code.first, response_code.second}});
response.set_status(response_code.first);
}
*resp << response;
tr::session::get(session,resp,req);
};
http_server.resource["^/session(\\?.*)?\\/?$"]["PATCH"]=[&](std::shared_ptr<HttpServer::Response> resp, std::shared_ptr<HttpServer::Request> req) {
auto response = http::response();
if (!session.is_valid()) {
auto response_code = http::code(http::service_unavailable);
response.set_body({{response_code.first, response_code.second}});
response.set_status(response_code.first);
} else {
const auto json=util::json::parse(req->content);
if(json.is_object() && !json.is_null()) {
if(util::json::get("paused",json,session.is_paused()))
session.pause();
else
session.resume();
auto settings = session.get_settings();
const auto new_listen_port=util::json::get("port",json,session.listen_port());
if(new_listen_port!=session.listen_port())
settings.set_str(settings.listen_interfaces,"0.0.0.0:"+std::to_string(new_listen_port));
const auto new_dht_enabled=util::json::get("dht_enabled",json,session.is_dht_running());
if(new_dht_enabled!=session.is_dht_running())
settings.set_bool(settings.enable_dht,new_dht_enabled);
auto old_limit=settings.get_int(settings.download_rate_limit);
auto new_limit=util::json::get("down_limit",json,old_limit);
if(new_limit!=old_limit)
settings.set_int(settings.download_rate_limit,new_limit);
old_limit=settings.get_int(settings.upload_rate_limit);
new_limit=util::json::get("up_limit",json,old_limit);
if(new_limit!=old_limit)
settings.set_int(settings.upload_rate_limit,new_limit);
session.apply_settings(settings);
response.set_body({{200,"OK"}});
} else {
auto response_code = http::code(http::bad_request);
response.set_body({{response_code.first,response_code.second}});
response.set_status(response_code.first);
}
}
*resp << response;
tr::session::patch(session,resp,req);
};
http_server.resource["^/session/torrents(\\?.*)?\\/?$"]["GET"]=[&session](std::shared_ptr<HttpServer::Response> resp, std::shared_ptr<HttpServer::Request> req) {

68
toREST/src/session.cpp

@ -1,68 +0,0 @@
#include <session.hpp>
namespace session {
nlohmann::json manager::get_json() const {
nlohmann::json session_json;
session_json["id"] = handle->id().to_string();
session_json["paused"] = handle->is_paused();
session_json["listening"] = handle->is_listening();
session_json["port"] = handle->listen_port();
session_json["ssl_port"] = handle->ssl_listen_port();
return session_json;
}
bool manager::patch(const nlohmann::json &data) {
int paused=data["paused"];
int port=data["port"];
const auto pause = [this](int is_paused){
if(is_paused!=-1){
if(is_paused==handle->is_paused()){
return true;
}else if(is_paused==true){
handle->resume();
return true;
}else if(is_paused==false){
handle->pause();
return true;
}
}
return false;
};
const auto listen = [this](int listen_port){
if(listen_port!=-1&&listen_port>6880){
libtorrent::settings_pack sp;
sp.set_str(libtorrent::settings_pack::listen_interfaces,"0.0.0.0:"+std::to_string(listen_port));
handle->apply_settings(sp);
return true;
}
return false;
};
return pause(paused) && listen(port);
};
void resource::patch(const nlohmann::json &data){
if(mgr && mgr->is_valid()){
if(data.is_object() && !data.is_null()){
int is_paused=util::json::get<int>("is_paused",data,-1),
listen_port=util::json::get<int>("listen_port",data,-1);
bool at_at_least_one_option_is_set=is_paused!=-1||listen_port!=-1;
if(at_at_least_one_option_is_set){
nlohmann::json patch={{"is_paused",is_paused},{"listen_port",listen_port}};
if(mgr->patch(patch)){
return response.set_status(http::ok);
}else{
return response.set_status(http::internal_server_error);
}
}else{
return response.set_status(http::bad_request);
}
}
return response.set_status(http::bad_request);
// auto data=data.array();
}else{
return response.set_status(http::service_unavailable);
}
}
}

76
toREST/tests/session_test.cpp

@ -0,0 +1,76 @@
#include <Catch/fakeit.hpp>
#include <fakeserver.hpp>
#include <session.hpp>
using namespace std;
class TestTorrent {};
class TestRequest : public std::stringstream {};
class TestResponse : public std::stringstream {};
class TestSessionSettings {
public:
const int download_rate_limit = 1;
const int upload_rate_limit = 2;
int get_int(int type) const {
return 0;
}
};
class TestTorrentSession {
public:
bool valid = false;
bool paused = false;
bool dht_running = true;
bool is_valid() {
return valid;
}
TestSessionSettings get_settings() {
return TestSessionSettings();
}
bool is_paused() {
return paused;
}
int listen_port() {
return 0;
}
int is_dht_running() {
return dht_running;
}
std::vector<TestTorrent> get_torrents() const {
return std::vector<TestTorrent>();
}
};
const std::string service_unavailable_json = "HTTP/1.1 503 Service Unavailable\r\nContent-Type: application/json\r\nContent-Length: 43\r\n\r\n{\"code\":503,\"status\":\"Service Unavailable\"}";
const std::string ok_json = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 82\r\n\r\n{\"dht_enabled\":1,\"down_limit\":0,\"paused\":false,\"port\":0,\"torrents\":0,\"up_limit\":0}";
const std::string ok_json_paused = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 81\r\n\r\n{\"dht_enabled\":1,\"down_limit\":0,\"paused\":true,\"port\":0,\"torrents\":0,\"up_limit\":0}";
SCENARIO("We recive a GET request to the /session resource") {
auto torrent_session = TestTorrentSession();
auto response = std::make_shared<TestResponse>();
GIVEN("the session is invalid") {
auto request = std::make_shared<TestResponse>();
tr::session::get(torrent_session, response, request);
THEN("the server should reply with service unavailable") {
REQUIRE(response->str() == service_unavailable_json);
}
}
GIVEN("the session is valid") {
auto request = std::make_shared<TestResponse>();
torrent_session.valid = true;
tr::session::get(torrent_session, response, request);
THEN("the server should reply with service unavailable") {
REQUIRE(response->str() == ok_json);
}
}
GIVEN("the session is valid") {
auto request = std::make_shared<TestResponse>();
torrent_session.valid = true;
torrent_session.paused = true;
tr::session::get(torrent_session, response, request);
THEN("the server should reply with service unavailable") {
REQUIRE(response->str() == ok_json_paused);
}
}
}

1
toREST/tests/test.cpp

@ -1,3 +1,2 @@
#define CATCH_CONFIG_MAIN
#include <Catch/fakeit.hpp>
Loading…
Cancel
Save