|
a/src/mediaserver/cdplugins/plgwithslave.cxx |
|
b/src/mediaserver/cdplugins/plgwithslave.cxx |
|
... |
|
... |
19 |
#include "plgwithslave.hxx"
|
19 |
#include "plgwithslave.hxx"
|
20 |
|
20 |
|
21 |
#include <string>
|
21 |
#include <string>
|
22 |
#include <vector>
|
22 |
#include <vector>
|
23 |
#include <sstream>
|
23 |
#include <sstream>
|
|
|
24 |
#include <functional>
|
|
|
25 |
|
24 |
#include <string.h>
|
26 |
#include <string.h>
|
25 |
#include <fcntl.h>
|
27 |
#include <fcntl.h>
|
26 |
#include <upnp/upnp.h>
|
28 |
#include <upnp/upnp.h>
|
27 |
#include <microhttpd.h>
|
29 |
#include <microhttpd.h>
|
28 |
#include <json/json.h>
|
30 |
#include <json/json.h>
|
|
... |
|
... |
32 |
#include "pathut.h"
|
34 |
#include "pathut.h"
|
33 |
#include "smallut.h"
|
35 |
#include "smallut.h"
|
34 |
#include "conftree.h"
|
36 |
#include "conftree.h"
|
35 |
#include "sysvshm.h"
|
37 |
#include "sysvshm.h"
|
36 |
#include "main.hxx"
|
38 |
#include "main.hxx"
|
|
|
39 |
#include "streamproxy.h"
|
37 |
|
40 |
|
38 |
using namespace std;
|
41 |
using namespace std;
|
39 |
using namespace std::placeholders;
|
42 |
using namespace std::placeholders;
|
40 |
//using json = nlohmann::json;
|
43 |
//using json = nlohmann::json;
|
41 |
using namespace UPnPProvider;
|
44 |
using namespace UPnPProvider;
|
|
... |
|
... |
88 |
|
91 |
|
89 |
// Cached uri translation
|
92 |
// Cached uri translation
|
90 |
StreamHandle laststream;
|
93 |
StreamHandle laststream;
|
91 |
};
|
94 |
};
|
92 |
|
95 |
|
93 |
// microhttpd daemon handle. There is only one of these, and one port, we find
|
96 |
// HTTP Proxy/Redirect handler
|
94 |
// the right plugin by looking at the url path.
|
97 |
static StreamProxy *o_proxy;
|
95 |
static struct MHD_Daemon *o_mhd;
|
|
|
96 |
|
98 |
|
97 |
// Microhttpd connection handler. We re-build the complete url + query
|
99 |
StreamProxy::UrlTransReturn translateurl(
|
98 |
// string (&trackid=value), use this to retrieve a service URL
|
100 |
CDPluginServices *cdsrv,
|
99 |
// (tidal/qobuz...), and redirect to it (HTTP). A previous version
|
101 |
std::string& url,
|
100 |
// handled rtmp streams, and had to read them. Look up the history if
|
102 |
const std::unordered_map<std::string, std::string>& querymap)
|
101 |
// you need the code again (the apparition of RTMP streams was
|
|
|
102 |
// apparently linked to the use of a different API key).
|
|
|
103 |
static int answer_to_connection(void *cls, struct MHD_Connection *connection,
|
|
|
104 |
const char *url,
|
|
|
105 |
const char *method, const char *version,
|
|
|
106 |
const char *upload_data,
|
|
|
107 |
size_t *upload_data_size, void **con_cls)
|
|
|
108 |
{
|
103 |
{
|
109 |
static int aptr;
|
104 |
LOGDEB("PlgWithSlave::translateurl: url " << url << endl);
|
110 |
if (&aptr != *con_cls) {
|
|
|
111 |
/* do not respond on first call */
|
|
|
112 |
*con_cls = &aptr;
|
|
|
113 |
return MHD_YES;
|
|
|
114 |
}
|
|
|
115 |
|
105 |
|
116 |
LOGDEB("answer_to_connection: url " << url << " method " << method <<
|
|
|
117 |
" version " << version << endl);
|
|
|
118 |
|
|
|
119 |
// The 'plgi' here is just whatever plugin started up the httpd task
|
|
|
120 |
// We just use it to find the appropriate plugin for this path,
|
|
|
121 |
// and then dispatch the request.
|
|
|
122 |
CDPluginServices *cdpsrv = (CDPluginServices*)cls;
|
|
|
123 |
PlgWithSlave *realplg =
|
106 |
PlgWithSlave *realplg =
|
124 |
dynamic_cast<PlgWithSlave*>(cdpsrv->getpluginforpath(url));
|
107 |
dynamic_cast<PlgWithSlave*>(cdsrv->getpluginforpath(url));
|
125 |
if (nullptr == realplg) {
|
108 |
if (nullptr == realplg) {
|
126 |
LOGERR("answer_to_connection: no plugin for path [" << url << endl);
|
109 |
LOGERR("PlgWithSlave::translateurl: no plugin for path ["<<url<< endl);
|
127 |
return MHD_NO;
|
110 |
return StreamProxy::Error;
|
128 |
}
|
111 |
}
|
129 |
|
112 |
|
130 |
// We may need one day to subclass PlgWithSlave to implement a
|
113 |
// We may need one day to subclass PlgWithSlave to implement a
|
131 |
// plugin-specific method. For now, existing plugins have
|
114 |
// plugin-specific method. For now, existing plugins have
|
132 |
// compatible python code, and we can keep one c++ method.
|
115 |
// compatible python code, and we can keep one c++ method.
|
|
... |
|
... |
140 |
// The streaming services plugins set a trackId parameter in the
|
123 |
// The streaming services plugins set a trackId parameter in the
|
141 |
// URIs. This gets parsed out by mhttpd. We rebuild a full url
|
124 |
// URIs. This gets parsed out by mhttpd. We rebuild a full url
|
142 |
// which we pass to them for translation (they will extract the
|
125 |
// which we pass to them for translation (they will extract the
|
143 |
// trackid and use it, the rest of the path is bogus).
|
126 |
// trackid and use it, the rest of the path is bogus).
|
144 |
// The uprcl module has a real path and no trackid. Handle both cases
|
127 |
// The uprcl module has a real path and no trackid. Handle both cases
|
145 |
const char* stid =
|
128 |
const auto it = querymap.find("trackId");
|
146 |
MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND,
|
129 |
if (it != querymap.end() && !it->second.empty()) {
|
147 |
"trackId");
|
|
|
148 |
if (stid && *stid) {
|
|
|
149 |
path += string("?version=1&trackId=") + stid;
|
130 |
path += string("?version=1&trackId=") + it->second;
|
150 |
}
|
131 |
}
|
151 |
|
132 |
|
152 |
// Translate to Tidal/Qobuz etc real temporary URL
|
133 |
// Translate to Tidal/Qobuz etc real temporary URL
|
153 |
string media_url = realplg->get_media_url(path);
|
134 |
url = realplg->get_media_url(path);
|
154 |
if (media_url.empty()) {
|
135 |
if (url.empty()) {
|
155 |
LOGERR("answer_to_connection: no media_uri for: " << url << endl);
|
136 |
LOGERR("answer_to_connection: no media_uri for: " << url << endl);
|
156 |
return MHD_NO;
|
137 |
return StreamProxy::Error;
|
157 |
}
|
|
|
158 |
|
|
|
159 |
if (media_url.find("http") == 0) {
|
|
|
160 |
static char data[] = "<html><body></body></html>";
|
|
|
161 |
struct MHD_Response *response =
|
|
|
162 |
MHD_create_response_from_buffer(strlen(data), data,
|
|
|
163 |
MHD_RESPMEM_PERSISTENT);
|
|
|
164 |
if (response == NULL) {
|
|
|
165 |
LOGERR("answer_to_connection: could not create response" << endl);
|
|
|
166 |
return MHD_NO;
|
|
|
167 |
}
|
|
|
168 |
MHD_add_response_header (response, "Location", media_url.c_str());
|
|
|
169 |
int ret = MHD_queue_response(connection, 302, response);
|
|
|
170 |
MHD_destroy_response(response);
|
|
|
171 |
return ret;
|
|
|
172 |
} else {
|
|
|
173 |
LOGERR("PlgWithSlave: got non-http URL !: " << media_url << endl);
|
|
|
174 |
LOGERR("PlgWithSlave: the code for handling these is gone !\n");
|
|
|
175 |
LOGERR(" will have to fetch it from git history\n");
|
|
|
176 |
return MHD_NO;
|
|
|
177 |
}
|
138 |
}
|
178 |
}
|
139 |
return StreamProxy::Proxy;
|
179 |
|
|
|
180 |
static int accept_policy(void *, const struct sockaddr* sa, socklen_t addrlen)
|
|
|
181 |
{
|
|
|
182 |
return MHD_YES;
|
|
|
183 |
}
|
140 |
}
|
184 |
|
141 |
|
185 |
// Static
|
142 |
// Static
|
186 |
bool PlgWithSlave::startPluginCmd(CmdTalk& cmd, const string& appname,
|
143 |
bool PlgWithSlave::startPluginCmd(CmdTalk& cmd, const string& appname,
|
187 |
const string& host, unsigned int port,
|
144 |
const string& host, unsigned int port,
|
|
... |
|
... |
207 |
}
|
164 |
}
|
208 |
return true;
|
165 |
return true;
|
209 |
}
|
166 |
}
|
210 |
|
167 |
|
211 |
// Static
|
168 |
// Static
|
212 |
bool PlgWithSlave::maybeStartMHD(CDPluginServices *cdsrv)
|
169 |
bool PlgWithSlave::maybeStartProxy(CDPluginServices *cdsrv)
|
213 |
{
|
170 |
{
|
214 |
if (nullptr == o_mhd) {
|
171 |
if (nullptr == o_proxy) {
|
215 |
int port = CDPluginServices::microhttpport();
|
172 |
int port = CDPluginServices::microhttpport();
|
216 |
// Start the microhttpd daemon. There can be only one, and it
|
173 |
o_proxy = new StreamProxy(
|
217 |
// is started with a context handle which points to whatever
|
|
|
218 |
// plugin got there first. The callback will only use the
|
|
|
219 |
// handle to get to the plugin services, and retrieve the
|
|
|
220 |
// appropriate plugin based on the url path prefix.
|
|
|
221 |
LOGDEB("PlgWithSlave: starting httpd on port "<< port << endl);
|
|
|
222 |
o_mhd = MHD_start_daemon(
|
|
|
223 |
MHD_USE_THREAD_PER_CONNECTION,
|
|
|
224 |
//MHD_USE_SELECT_INTERNALLY,
|
|
|
225 |
port,
|
174 |
port,
|
226 |
/* Accept policy callback and arg */
|
175 |
std::bind(&translateurl, cdsrv, _1, _2));
|
227 |
accept_policy, NULL,
|
176 |
|
228 |
/* handler and arg */
|
|
|
229 |
&answer_to_connection, cdsrv,
|
|
|
230 |
MHD_OPTION_END);
|
|
|
231 |
if (nullptr == o_mhd) {
|
177 |
if (nullptr == o_proxy) {
|
232 |
LOGERR("PlgWithSlave: MHD_start_daemon failed\n");
|
178 |
LOGERR("PlgWithSlave: Proxy creation failed\n");
|
233 |
return false;
|
179 |
return false;
|
234 |
}
|
180 |
}
|
235 |
}
|
181 |
}
|
236 |
return true;
|
182 |
return true;
|
237 |
}
|
183 |
}
|
|
... |
|
... |
241 |
{
|
187 |
{
|
242 |
if (cmd.running()) {
|
188 |
if (cmd.running()) {
|
243 |
LOGDEB1("PlgWithSlave::maybeStartCmd: already running\n");
|
189 |
LOGDEB1("PlgWithSlave::maybeStartCmd: already running\n");
|
244 |
return true;
|
190 |
return true;
|
245 |
}
|
191 |
}
|
246 |
if (!maybeStartMHD(this->plg->m_services)) {
|
192 |
if (!maybeStartProxy(this->plg->m_services)) {
|
247 |
LOGDEB1("PlgWithSlave::maybeStartCmd: maybeStartMHD failed\n");
|
193 |
LOGDEB1("PlgWithSlave::maybeStartCmd: maybeStartMHD failed\n");
|
248 |
return false;
|
194 |
return false;
|
249 |
}
|
195 |
}
|
250 |
int port = CDPluginServices::microhttpport();
|
196 |
int port = CDPluginServices::microhttpport();
|
251 |
if (!startPluginCmd(cmd, plg->m_name, upnphost, port, pathprefix)) {
|
197 |
if (!startPluginCmd(cmd, plg->m_name, upnphost, port, pathprefix)) {
|