diff --git a/trunk/src/app/srs_app_http_static.cpp b/trunk/src/app/srs_app_http_static.cpp index e486e773a..1bfd8bfe6 100644 --- a/trunk/src/app/srs_app_http_static.cpp +++ b/trunk/src/app/srs_app_http_static.cpp @@ -51,8 +51,7 @@ using namespace std; #include #include -SrsVodStream::SrsVodStream(string root_dir) -: SrsHttpFileServer(root_dir) +SrsVodStream::SrsVodStream(string root_dir) : SrsHttpFileServer(root_dir) { } @@ -64,22 +63,23 @@ srs_error_t SrsVodStream::serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMe { srs_error_t err = srs_success; - SrsFileReader fs; + SrsFileReader* fs = fs_factory->create_file_reader(); + SrsAutoFree(SrsFileReader, fs); // open flv file - if ((err = fs.open(fullpath)) != srs_success) { + if ((err = fs->open(fullpath)) != srs_success) { return srs_error_wrap(err, "open file"); } - if (offset > fs.filesize()) { + if (offset > fs->filesize()) { return srs_error_new(ERROR_HTTP_REMUX_OFFSET_OVERFLOW, "http flv streaming %s overflow. size=%" PRId64 ", offset=%d", - fullpath.c_str(), fs.filesize(), offset); + fullpath.c_str(), fs->filesize(), offset); } SrsFlvVodStreamDecoder ffd; // open fast decoder - if ((err = ffd.initialize(&fs)) != srs_success) { + if ((err = ffd.initialize(fs)) != srs_success) { return srs_error_wrap(err, "init ffd"); } @@ -107,12 +107,12 @@ srs_error_t SrsVodStream::serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMe } sh_data = new char[sh_size]; SrsAutoFreeA(char, sh_data); - if ((err = fs.read(sh_data, sh_size, NULL)) != srs_success) { + if ((err = fs->read(sh_data, sh_size, NULL)) != srs_success) { return srs_error_wrap(err, "fs read"); } // seek to data offset - int64_t left = fs.filesize() - offset; + int64_t left = fs->filesize() - offset; // write http header for ts. w->header()->set_content_length((int)(sizeof(flv_header) + sh_size + left)); @@ -132,7 +132,7 @@ srs_error_t SrsVodStream::serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMe } // send data - if ((err = copy(w, &fs, r, (int)left)) != srs_success) { + if ((err = copy(w, fs, r, (int)left)) != srs_success) { return srs_error_wrap(err, "read flv=%s size=%d", fullpath.c_str(), left); } @@ -146,21 +146,22 @@ srs_error_t SrsVodStream::serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsHttpMe srs_assert(start >= 0); srs_assert(end == -1 || end >= 0); - SrsFileReader fs; + SrsFileReader* fs = fs_factory->create_file_reader(); + SrsAutoFree(SrsFileReader, fs); // open flv file - if ((err = fs.open(fullpath)) != srs_success) { + if ((err = fs->open(fullpath)) != srs_success) { return srs_error_wrap(err, "fs open"); } // parse -1 to whole file. if (end == -1) { - end = (int)fs.filesize(); + end = (int)fs->filesize(); } - if (end > fs.filesize() || start > end) { + if (end > fs->filesize() || start > end) { return srs_error_new(ERROR_HTTP_REMUX_OFFSET_OVERFLOW, "http mp4 streaming %s overflow. size=%" PRId64 ", offset=%d", - fullpath.c_str(), fs.filesize(), start); + fullpath.c_str(), fs->filesize(), start); } // seek to data offset, [start, end] for range. @@ -174,15 +175,16 @@ srs_error_t SrsVodStream::serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsHttpMe w->write_header(SRS_CONSTS_HTTP_PartialContent); // response the content range header. + // https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Range_requests std::stringstream content_range; - content_range << "bytes " << start << "-" << end << "/" << fs.filesize(); + content_range << "bytes " << start << "-" << end << "/" << fs->filesize(); w->header()->set("Content-Range", content_range.str()); // write body. - fs.seek2(start); + fs->seek2(start); // send data - if ((err = copy(w, &fs, r, (int)left)) != srs_success) { + if ((err = copy(w, fs, r, (int)left)) != srs_success) { return srs_error_wrap(err, "read mp4=%s size=%d", fullpath.c_str(), left); } diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index cd258380a..4b478603b 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -345,17 +345,15 @@ SrsHttpFileServer::~SrsHttpFileServer() srs_freep(fs_factory); } -SrsHttpFileServer* SrsHttpFileServer::set_fs_factory(ISrsFileReaderFactory* f) +void SrsHttpFileServer::set_fs_factory(ISrsFileReaderFactory* f) { srs_freep(fs_factory); fs_factory = f; - return this; } -SrsHttpFileServer* SrsHttpFileServer::set_path_check(_pfn_srs_path_exists pfn) +void SrsHttpFileServer::set_path_check(_pfn_srs_path_exists pfn) { _srs_path_exists = pfn; - return this; } srs_error_t SrsHttpFileServer::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) @@ -396,8 +394,9 @@ srs_error_t SrsHttpFileServer::serve_file(ISrsHttpResponseWriter* w, ISrsHttpMes if ((err = fs->open(fullpath)) != srs_success) { return srs_error_wrap(err, "open file %s", fullpath.c_str()); } - - int64_t length = fs->filesize(); + + // The length of bytes we could response to. + int64_t length = fs->filesize() - fs->tellg(); // unset the content length to encode in chunked encoding. w->header()->set_content_length(length); @@ -479,10 +478,8 @@ srs_error_t SrsHttpFileServer::serve_flv_file(ISrsHttpResponseWriter* w, ISrsHtt srs_error_t SrsHttpFileServer::serve_mp4_file(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, string fullpath) { // for flash to request mp4 range in query string. - // for example, http://digitalprimates.net/dash/DashTest.html?url=http://dashdemo.edgesuite.net/digitalprimates/nexus/oops-20120802-manifest.mpd std::string range = r->query_get("range"); - // or, use bytes to request range, - // for example, http://dashas.castlabs.com/demo/try.html + // or, use bytes to request range. if (range.empty()) { range = r->query_get("bytes"); } @@ -515,11 +512,15 @@ srs_error_t SrsHttpFileServer::serve_mp4_file(ISrsHttpResponseWriter* w, ISrsHtt srs_error_t SrsHttpFileServer::serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, string fullpath, int offset) { + // @remark For common http file server, we don't support stream request, please use SrsVodStream instead. + // TODO: FIXME: Support range in header https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Range_requests return serve_file(w, r, fullpath); } srs_error_t SrsHttpFileServer::serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, string fullpath, int start, int end) { + // @remark For common http file server, we don't support stream request, please use SrsVodStream instead. + // TODO: FIXME: Support range in header https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Range_requests return serve_file(w, r, fullpath); } @@ -535,19 +536,15 @@ srs_error_t SrsHttpFileServer::copy(ISrsHttpResponseWriter* w, SrsFileReader* fs ssize_t nread = -1; int max_read = srs_min(left, SRS_HTTP_TS_SEND_BUFFER_SIZE); if ((err = fs->read(buf, max_read, &nread)) != srs_success) { - break; + return srs_error_wrap(err, "read limit=%d, left=%d", max_read, left); } left -= nread; if ((err = w->write(buf, (int)nread)) != srs_success) { - break; + return srs_error_wrap(err, "write limit=%d, bytes=%d, left=%d", max_read, nread, left); } } - if (err != srs_success) { - return srs_error_wrap(err, "copy"); - } - return err; } diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp index f09ee9668..f456c26d5 100644 --- a/trunk/src/protocol/srs_http_stack.hpp +++ b/trunk/src/protocol/srs_http_stack.hpp @@ -295,7 +295,7 @@ class SrsHttpFileServer : public ISrsHttpHandler { protected: std::string dir; -private: +protected: ISrsFileReaderFactory* fs_factory; _pfn_srs_path_exists _srs_path_exists; public: @@ -303,9 +303,9 @@ public: virtual ~SrsHttpFileServer(); private: // For utest to mock the fs. - virtual SrsHttpFileServer* set_fs_factory(ISrsFileReaderFactory* v); + virtual void set_fs_factory(ISrsFileReaderFactory* v); // For utest to mock the path check function. - virtual SrsHttpFileServer* set_path_check(_pfn_srs_path_exists pfn); + virtual void set_path_check(_pfn_srs_path_exists pfn); public: virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); private: diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index 32de06d0c..8dae41ca5 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -32,6 +32,7 @@ using namespace std; #include #include #include +#include class MockResponseWriter : virtual public ISrsHttpResponseWriter, virtual public ISrsHttpHeaderFilter { @@ -93,6 +94,7 @@ srs_error_t MockResponseWriter::filter(SrsHttpHeader* h) h->del("Server"); h->del("Connection"); h->del("Location"); + h->del("Content-Range"); return srs_success; } @@ -185,25 +187,120 @@ public: } }; -bool _mock_srs_path_exists(std::string /*path*/) +bool _mock_srs_path_always_exists(std::string /*path*/) { return true; } +bool _mock_srs_path_not_exists(std::string /*path*/) +{ + return false; +} + +VOID TEST(ProtocolHTTPTest, VodStreamHandlers) +{ + srs_error_t err; + + if (true) { + SrsHttpMuxEntry e; + e.pattern = "/"; + + string fs; + int nn_flv_prefix = 0; + if (true) { + char flv_header[13]; + nn_flv_prefix += sizeof(flv_header); + HELPER_ARRAY_INIT(flv_header, 13, 0); + fs.append(flv_header, 13); + } + if (true) { + uint8_t tag[15] = {9}; + nn_flv_prefix += sizeof(tag); + HELPER_ARRAY_INIT(tag+1, 14, 0); + fs.append((const char*)tag, sizeof(tag)); + } + if (true) { + uint8_t tag[15] = {8}; + nn_flv_prefix += sizeof(tag); + HELPER_ARRAY_INIT(tag+1, 14, 0); + fs.append((const char*)tag, sizeof(tag)); + } + string flv_content = "Hello, world!"; + fs.append(flv_content); + + SrsVodStream h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory(fs)); + h.set_path_check(_mock_srs_path_always_exists); + h.entry = &e; + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/index.flv?start=" + srs_int2str(nn_flv_prefix + 2), false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + + // We only compare the last content, ignore HTTP and FLV header. + string av2 = HELPER_BUFFER2STR(&w.io.out_buffer); + string av = av2.substr(av2.length() - flv_content.length() + 2); + string ev2 = mock_http_response(200, "llo, world!"); + string ev = ev2.substr(ev2.length() - flv_content.length() + 2); + EXPECT_STREQ(ev.c_str(), av.c_str()); + } + + if (true) { + SrsHttpMuxEntry e; + e.pattern = "/"; + + SrsVodStream h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); + h.set_path_check(_mock_srs_path_always_exists); + h.entry = &e; + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/index.mp4?range=2-3", false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + __MOCK_HTTP_EXPECT_STREQ(206, "ll", w); + } + + if (true) { + SrsHttpMuxEntry e; + e.pattern = "/"; + + SrsVodStream h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); + h.set_path_check(_mock_srs_path_always_exists); + h.entry = &e; + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/index.mp4?bytes=2-5", false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + __MOCK_HTTP_EXPECT_STREQ(206, "llo,", w); + } +} + VOID TEST(ProtocolHTTPTest, BasicHandlers) { srs_error_t err; if (true) { - EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp", "/", "/").c_str()); - EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp", "/", "/index.html").c_str()); - EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "/", "/index.html").c_str()); - EXPECT_STREQ("/tmp/ndex.html", srs_http_fs_fullpath("/tmp/", "//", "/index.html").c_str()); - EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "/api", "/api/index.html").c_str()); - EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "ossrs.net/api", "/api/index.html").c_str()); - EXPECT_STREQ("/tmp/views/index.html", srs_http_fs_fullpath("/tmp/", "/api", "/api/views/index.html").c_str()); - EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "/api/", "/api/index.html").c_str()); - EXPECT_STREQ("/tmp/ndex.html", srs_http_fs_fullpath("/tmp/", "/api//", "/api/index.html").c_str()); + SrsHttpMuxEntry e; + e.pattern = "/"; + + SrsHttpFileServer h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); + h.set_path_check(_mock_srs_path_always_exists); + h.entry = &e; + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/index.mp4?start=2", false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w); } if (true) { @@ -211,7 +308,42 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) e.pattern = "/"; SrsHttpFileServer h("/tmp"); - h.set_fs_factory(new MockFileReaderFactory("Hello, world!"))->set_path_check(_mock_srs_path_exists); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); + h.set_path_check(_mock_srs_path_always_exists); + h.entry = &e; + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/index.flv?start=2", false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w); + } + + if (true) { + SrsHttpMuxEntry e; + e.pattern = "/"; + + SrsHttpFileServer h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); + h.set_path_check(_mock_srs_path_always_exists); + h.entry = &e; + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/index.flv", false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w); + } + + if (true) { + SrsHttpMuxEntry e; + e.pattern = "/"; + + SrsHttpFileServer h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); + h.set_path_check(_mock_srs_path_always_exists); h.entry = &e; MockResponseWriter w; @@ -222,6 +354,35 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w); } + if (true) { + SrsHttpMuxEntry e; + e.pattern = "/"; + + SrsHttpFileServer h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); + h.set_path_check(_mock_srs_path_not_exists); + h.entry = &e; + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + __MOCK_HTTP_EXPECT_STREQ(404, "Not Found", w); + } + + if (true) { + EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp", "/", "/").c_str()); + EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp", "/", "/index.html").c_str()); + EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "/", "/index.html").c_str()); + EXPECT_STREQ("/tmp/ndex.html", srs_http_fs_fullpath("/tmp/", "//", "/index.html").c_str()); + EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "/api", "/api/index.html").c_str()); + EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "ossrs.net/api", "/api/index.html").c_str()); + EXPECT_STREQ("/tmp/views/index.html", srs_http_fs_fullpath("/tmp/", "/api", "/api/views/index.html").c_str()); + EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "/api/", "/api/index.html").c_str()); + EXPECT_STREQ("/tmp/ndex.html", srs_http_fs_fullpath("/tmp/", "/api//", "/api/index.html").c_str()); + } + if (true) { SrsHttpRedirectHandler h("/api", 500); @@ -239,6 +400,7 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) MockResponseWriter w; HELPER_ASSERT_SUCCESS(h.serve_http(&w, NULL)); __MOCK_HTTP_EXPECT_STREQ(404, "Not Found", w); + EXPECT_TRUE(h.is_not_found()); } }