Skip to content

Commit f901831

Browse files
author
Haibo Chen
authored
Enhance HLS: support http callback on_play/stop, support statistic (#2578)
* Enhance HLS: support http callback on_play/stop, support statistic * make code readable * make code readable * rename secret
1 parent 40f8460 commit f901831

File tree

7 files changed

+277
-5
lines changed

7 files changed

+277
-5
lines changed

trunk/src/app/srs_app_http_api.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -840,8 +840,13 @@ srs_error_t SrsGoApiClients::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessa
840840
return srs_api_response_code(w, r, ERROR_RTMP_CLIENT_NOT_FOUND);
841841
}
842842

843-
client->conn->expire();
844-
srs_warn("kickoff client id=%s ok", client_id.c_str());
843+
if (client->conn) {
844+
client->conn->expire();
845+
srs_warn("kickoff client id=%s ok", client_id.c_str());
846+
} else {
847+
srs_error("kickoff client id=%s error", client_id.c_str());
848+
return srs_api_response_code(w, r, SRS_CONSTS_HTTP_BadRequest);
849+
}
845850
} else {
846851
return srs_go_http_error(w, SRS_CONSTS_HTTP_MethodNotAllowed);
847852
}

trunk/src/app/srs_app_http_static.cpp

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,27 @@ using namespace std;
3333
#include <srs_app_pithy_print.hpp>
3434
#include <srs_app_source.hpp>
3535
#include <srs_app_server.hpp>
36+
#include <srs_service_utility.hpp>
37+
#include <srs_app_http_hooks.hpp>
38+
#include <srs_app_statistic.hpp>
39+
40+
#define SRS_CONTEXT_IN_HLS "hls_ctx"
3641

3742
SrsVodStream::SrsVodStream(string root_dir) : SrsHttpFileServer(root_dir)
3843
{
44+
_srs_hybrid->timer5s()->subscribe(this);
3945
}
4046

4147
SrsVodStream::~SrsVodStream()
4248
{
49+
_srs_hybrid->timer5s()->unsubscribe(this);
50+
std::map<std::string, SrsM3u8CtxInfo>::iterator it;
51+
for (it = map_ctx_info_.begin(); it != map_ctx_info_.end(); ++it) {
52+
srs_freep(it->second.req);
53+
}
54+
map_ctx_info_.clear();
4355
}
44-
56+
4557
srs_error_t SrsVodStream::serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, string fullpath, int offset)
4658
{
4759
srs_error_t err = srs_success;
@@ -171,6 +183,173 @@ srs_error_t SrsVodStream::serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsHttpMe
171183
}
172184

173185
return err;
186+
}
187+
188+
srs_error_t SrsVodStream::serve_m3u8_ctx(ISrsHttpResponseWriter * w, ISrsHttpMessage * r, std::string fullpath)
189+
{
190+
srs_error_t err = srs_success;
191+
192+
SrsHttpMessage* hr = dynamic_cast<SrsHttpMessage*>(r);
193+
srs_assert(hr);
194+
195+
SrsRequest* req = hr->to_request(hr->host())->as_http();
196+
SrsAutoFree(SrsRequest, req);
197+
198+
string ctx = hr->query_get(SRS_CONTEXT_IN_HLS);
199+
if (!ctx.empty() && ctx_is_exist(ctx)) {
200+
alive(ctx, NULL);
201+
return SrsHttpFileServer::serve_m3u8_ctx(w, r, fullpath);
202+
}
203+
204+
if ((err = http_hooks_on_play(req)) != srs_success) {
205+
return srs_error_wrap(err, "HLS: http_hooks_on_play");
206+
}
207+
208+
if (ctx.empty()) {
209+
// make sure unique
210+
do {
211+
ctx = srs_random_str(8); // the same as cid
212+
} while (ctx_is_exist(ctx));
213+
}
214+
215+
std::stringstream ss;
216+
ss << "#EXTM3U" << SRS_CONSTS_LF;
217+
ss << "#EXT-X-STREAM-INF:BANDWIDTH=1,AVERAGE-BANDWIDTH=1" << SRS_CONSTS_LF;
218+
ss << hr->path() << "?" << SRS_CONTEXT_IN_HLS << "=" << ctx;
219+
if (!hr->query().empty() && hr->query_get(SRS_CONTEXT_IN_HLS).empty())
220+
{
221+
ss << "&" << hr->query();
222+
}
223+
224+
std::string res = ss.str();
225+
int length = res.length();
226+
227+
w->header()->set_content_length(length);
228+
w->header()->set_content_type("application/vnd.apple.mpegurl");
229+
w->write_header(SRS_CONSTS_HTTP_OK);
230+
231+
if ((err = w->write((char*)res.c_str(), length)) != srs_success) {
232+
return srs_error_wrap(err, "write bytes=%d", length);
233+
}
234+
235+
if ((err = w->final_request()) != srs_success) {
236+
return srs_error_wrap(err, "final request");
237+
}
238+
239+
alive(ctx, req->copy());
240+
241+
// update the statistic when source disconveried.
242+
SrsStatistic* stat = SrsStatistic::instance();
243+
if ((err = stat->on_client(ctx, req, NULL, SrsRtmpConnPlay)) != srs_success) {
244+
return srs_error_wrap(err, "stat on client");
245+
}
246+
247+
return err;
248+
}
249+
250+
bool SrsVodStream::ctx_is_exist(std::string ctx)
251+
{
252+
return (map_ctx_info_.find(ctx) != map_ctx_info_.end());
253+
}
254+
255+
void SrsVodStream::alive(std::string ctx, SrsRequest* req)
256+
{
257+
std::map<std::string, SrsM3u8CtxInfo>::iterator it;
258+
if ((it = map_ctx_info_.find(ctx)) != map_ctx_info_.end()) {
259+
it->second.request_time = srs_get_system_time();
260+
} else {
261+
SrsM3u8CtxInfo info;
262+
info.req = req;
263+
info.request_time = srs_get_system_time();
264+
map_ctx_info_.insert(make_pair(ctx, info));
265+
}
266+
}
267+
268+
srs_error_t SrsVodStream::http_hooks_on_play(SrsRequest* req)
269+
{
270+
srs_error_t err = srs_success;
271+
272+
if (!_srs_config->get_vhost_http_hooks_enabled(req->vhost)) {
273+
return err;
274+
}
275+
276+
// the http hooks will cause context switch,
277+
// so we must copy all hooks for the on_connect may freed.
278+
// @see https://github.com/ossrs/srs/issues/475
279+
vector<string> hooks;
280+
281+
if (true) {
282+
SrsConfDirective* conf = _srs_config->get_vhost_on_play(req->vhost);
283+
284+
if (!conf) {
285+
return err;
286+
}
287+
288+
hooks = conf->args;
289+
}
290+
291+
for (int i = 0; i < (int)hooks.size(); i++) {
292+
std::string url = hooks.at(i);
293+
if ((err = SrsHttpHooks::on_play(url, req)) != srs_success) {
294+
return srs_error_wrap(err, "http on_play %s", url.c_str());
295+
}
296+
}
297+
298+
return err;
299+
}
300+
301+
void SrsVodStream::http_hooks_on_stop(SrsRequest* req)
302+
{
303+
if (!_srs_config->get_vhost_http_hooks_enabled(req->vhost)) {
304+
return;
305+
}
306+
307+
// the http hooks will cause context switch,
308+
// so we must copy all hooks for the on_connect may freed.
309+
// @see https://github.com/ossrs/srs/issues/475
310+
vector<string> hooks;
311+
312+
if (true) {
313+
SrsConfDirective* conf = _srs_config->get_vhost_on_stop(req->vhost);
314+
315+
if (!conf) {
316+
srs_info("ignore the empty http callback: on_stop");
317+
return;
318+
}
319+
320+
hooks = conf->args;
321+
}
322+
323+
for (int i = 0; i < (int)hooks.size(); i++) {
324+
std::string url = hooks.at(i);
325+
SrsHttpHooks::on_stop(url, req);
326+
}
327+
328+
return;
329+
}
330+
331+
srs_error_t SrsVodStream::on_timer(srs_utime_t interval)
332+
{
333+
srs_error_t err = srs_success;
334+
335+
std::map<std::string, SrsM3u8CtxInfo>::iterator it;
336+
for (it = map_ctx_info_.begin(); it != map_ctx_info_.end(); ++it) {
337+
string ctx = it->first;
338+
SrsRequest* req = it->second.req;
339+
srs_utime_t hls_window = _srs_config->get_hls_window(req->vhost);
340+
if (it->second.request_time + (2 * hls_window) < srs_get_system_time()) {
341+
http_hooks_on_stop(req);
342+
srs_freep(req);
343+
344+
SrsStatistic* stat = SrsStatistic::instance();
345+
stat->on_disconnect(ctx);
346+
map_ctx_info_.erase(it);
347+
348+
break;
349+
}
350+
}
351+
352+
return err;
174353
}
175354

176355
SrsHttpStaticServer::SrsHttpStaticServer(SrsServer* svr)

trunk/src/app/srs_app_http_static.hpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,36 @@
1111

1212
#include <srs_app_http_conn.hpp>
1313

14+
struct SrsM3u8CtxInfo
15+
{
16+
srs_utime_t request_time;
17+
SrsRequest* req;
18+
};
19+
1420
// The flv vod stream supports flv?start=offset-bytes.
1521
// For example, http://server/file.flv?start=10240
1622
// server will write flv header and sequence header,
1723
// then seek(10240) and response flv tag data.
18-
class SrsVodStream : public SrsHttpFileServer
24+
class SrsVodStream : public SrsHttpFileServer, public ISrsFastTimer
1925
{
26+
private:
27+
// The period of validity of the ctx
28+
std::map<std::string, SrsM3u8CtxInfo> map_ctx_info_;
2029
public:
2130
SrsVodStream(std::string root_dir);
2231
virtual ~SrsVodStream();
2332
protected:
2433
virtual srs_error_t serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath, int offset);
2534
virtual srs_error_t serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath, int start, int end);
35+
virtual srs_error_t serve_m3u8_ctx(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath);
36+
private:
37+
virtual bool ctx_is_exist(std::string ctx);
38+
virtual void alive(std::string ctx, SrsRequest* req);
39+
virtual srs_error_t http_hooks_on_play(SrsRequest* req);
40+
virtual void http_hooks_on_stop(SrsRequest* req);
41+
// interface ISrsFastTimer
42+
private:
43+
srs_error_t on_timer(srs_utime_t interval);
2644
};
2745

2846
// The http static server instance,

trunk/src/protocol/srs_http_stack.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ srs_error_t SrsHttpFileServer::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMes
364364

365365
string upath = r->path();
366366
string fullpath = srs_http_fs_fullpath(dir, entry->pattern, upath);
367-
367+
368368
// stat current dir, if exists, return error.
369369
if (!_srs_path_exists(fullpath)) {
370370
srs_warn("http miss file=%s, pattern=%s, upath=%s",
@@ -380,6 +380,8 @@ srs_error_t SrsHttpFileServer::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMes
380380
return serve_flv_file(w, r, fullpath);
381381
} else if (srs_string_ends_with(fullpath, ".mp4")) {
382382
return serve_mp4_file(w, r, fullpath);
383+
} else if (srs_string_ends_with(upath, ".m3u8")) {
384+
return serve_m3u8_file(w, r, fullpath);
383385
}
384386

385387
// serve common static file.
@@ -522,6 +524,11 @@ srs_error_t SrsHttpFileServer::serve_mp4_file(ISrsHttpResponseWriter* w, ISrsHtt
522524
}
523525

524526
return serve_mp4_stream(w, r, fullpath, start, end);
527+
}
528+
529+
srs_error_t SrsHttpFileServer::serve_m3u8_file(ISrsHttpResponseWriter * w, ISrsHttpMessage * r, std::string fullpath)
530+
{
531+
return serve_m3u8_ctx(w, r, fullpath);
525532
}
526533

527534
srs_error_t SrsHttpFileServer::serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, string fullpath, int offset)
@@ -536,6 +543,13 @@ srs_error_t SrsHttpFileServer::serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsH
536543
// @remark For common http file server, we don't support stream request, please use SrsVodStream instead.
537544
// TODO: FIXME: Support range in header https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Range_requests
538545
return serve_file(w, r, fullpath);
546+
}
547+
548+
srs_error_t SrsHttpFileServer::serve_m3u8_ctx(ISrsHttpResponseWriter * w, ISrsHttpMessage * r, std::string fullpath)
549+
{
550+
// @remark For common http file server, we don't support stream request, please use SrsVodStream instead.
551+
// TODO: FIXME: Support range in header https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Range_requests
552+
return serve_file(w, r, fullpath);
539553
}
540554

541555
srs_error_t SrsHttpFileServer::copy(ISrsHttpResponseWriter* w, SrsFileReader* fs, ISrsHttpMessage* r, int size)

trunk/src/protocol/srs_http_stack.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ class SrsHttpFileServer : public ISrsHttpHandler
285285
virtual srs_error_t serve_file(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath);
286286
virtual srs_error_t serve_flv_file(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath);
287287
virtual srs_error_t serve_mp4_file(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath);
288+
virtual srs_error_t serve_m3u8_file(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath);
288289
protected:
289290
// When access flv file with x.flv?start=xxx
290291
virtual srs_error_t serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath, int offset);
@@ -293,6 +294,16 @@ class SrsHttpFileServer : public ISrsHttpHandler
293294
// @param end the end offset in bytes. -1 to end of file.
294295
// @remark response data in [start, end].
295296
virtual srs_error_t serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath, int start, int end);
297+
// For HLS protocol.
298+
// When the request url, like as "http://127.0.0.1:8080/live/livestream.m3u8",
299+
// returns the response like as "http://127.0.0.1:8080/live/livestream.m3u8?hls_ctx=12345678" .
300+
// SRS use "hls_ctx" to keep track of subsequent requests that is short-connection.
301+
// Remark 1:
302+
// Fill the parameter "hls_ctx" by yourself in the first request is allowed, SRS will use it.
303+
// And MUST make sure it is unique.
304+
// Remark 2:
305+
// If use two same "hls_ctx" in different requests, SRS cannot detect so that they will be treated as one.
306+
virtual srs_error_t serve_m3u8_ctx(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath);
296307
protected:
297308
// Copy the fs to response writer in size bytes.
298309
virtual srs_error_t copy(ISrsHttpResponseWriter* w, SrsFileReader* fs, ISrsHttpMessage* r, int size);

trunk/src/utest/srs_utest_http.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ string mock_http_response2(int status, string content)
143143
return ss.str();
144144
}
145145

146+
bool is_string_contain(string substr, string str)
147+
{
148+
return (string::npos != str.find(substr));
149+
}
150+
146151
class MockFileReaderFactory : public ISrsFileReaderFactory
147152
{
148153
public:
@@ -1183,6 +1188,42 @@ VOID TEST(ProtocolHTTPTest, VodStreamHandlers)
11831188
HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r));
11841189
__MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w);
11851190
}
1191+
1192+
// should return "hls_ctx"
1193+
if (true) {
1194+
SrsHttpMuxEntry e;
1195+
e.pattern = "/";
1196+
1197+
SrsVodStream h("/tmp");
1198+
h.set_fs_factory(new MockFileReaderFactory("Hello, world!"));
1199+
h.set_path_check(_mock_srs_path_always_exists);
1200+
h.entry = &e;
1201+
1202+
MockResponseWriter w;
1203+
SrsHttpMessage r(NULL, NULL);
1204+
HELPER_ASSERT_SUCCESS(r.set_url("/index.m3u8", false));
1205+
1206+
HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r));
1207+
__MOCK_HTTP_EXPECT_STRCT(200, "index.m3u8?hls_ctx=", w);
1208+
}
1209+
1210+
// should return "hls_ctx"
1211+
if (true) {
1212+
SrsHttpMuxEntry e;
1213+
e.pattern = "/";
1214+
1215+
SrsVodStream h("/tmp");
1216+
h.set_fs_factory(new MockFileReaderFactory("Hello, world!"));
1217+
h.set_path_check(_mock_srs_path_always_exists);
1218+
h.entry = &e;
1219+
1220+
MockResponseWriter w;
1221+
SrsHttpMessage r(NULL, NULL);
1222+
HELPER_ASSERT_SUCCESS(r.set_url("/index.m3u8?hls_ctx=123456", false));
1223+
1224+
HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r));
1225+
__MOCK_HTTP_EXPECT_STRCT(200, "index.m3u8?hls_ctx=123456", w);
1226+
}
11861227
}
11871228

11881229
VOID TEST(ProtocolHTTPTest, BasicHandlers)

trunk/src/utest/srs_utest_http.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,16 @@ class MockResponseWriter : public ISrsHttpResponseWriter, public ISrsHttpHeaderF
3939

4040
string mock_http_response(int status, string content);
4141
string mock_http_response2(int status, string content);
42+
bool is_string_contain(string substr, string str);
4243

4344
#define __MOCK_HTTP_EXPECT_STREQ(status, text, w) \
4445
EXPECT_STREQ(mock_http_response(status, text).c_str(), HELPER_BUFFER2STR(&w.io.out_buffer).c_str())
4546

4647
#define __MOCK_HTTP_EXPECT_STREQ2(status, text, w) \
4748
EXPECT_STREQ(mock_http_response2(status, text).c_str(), HELPER_BUFFER2STR(&w.io.out_buffer).c_str())
4849

50+
#define __MOCK_HTTP_EXPECT_STRCT(status, text, w) \
51+
EXPECT_PRED2(is_string_contain, text, HELPER_BUFFER2STR(&w.io.out_buffer).c_str())
52+
4953
#endif
5054

0 commit comments

Comments
 (0)