diff --git a/README.md b/README.md index ba359d84d..cddc782b8 100755 --- a/README.md +++ b/README.md @@ -145,6 +145,7 @@ For previous versions, please read: ## V3 changes +* v3.0, 2019-12-16, For [#1042][bug #1042], add test for HTTP protocol. * v3.0, 2019-12-13, [3.0 alpha4(3.0.71)][r3.0a4] released. 112928 lines. * v3.0, 2019-12-12, For [#547][bug #547], [#1506][bug #1506], default hls_dts_directly to on. 3.0.71 * v3.0, 2019-12-12, SrsPacket supports converting to message, so can be sent by one API. diff --git a/trunk/src/kernel/srs_kernel_file.cpp b/trunk/src/kernel/srs_kernel_file.cpp index 88bee7e68..832b7727b 100644 --- a/trunk/src/kernel/srs_kernel_file.cpp +++ b/trunk/src/kernel/srs_kernel_file.cpp @@ -175,6 +175,19 @@ srs_error_t SrsFileWriter::lseek(off_t offset, int whence, off_t* seeked) return srs_success; } +ISrsFileReaderFactory::ISrsFileReaderFactory() +{ +} + +ISrsFileReaderFactory::~ISrsFileReaderFactory() +{ +} + +SrsFileReader* ISrsFileReaderFactory::create_file_reader() +{ + return new SrsFileReader(); +} + SrsFileReader::SrsFileReader() { fd = -1; diff --git a/trunk/src/kernel/srs_kernel_file.hpp b/trunk/src/kernel/srs_kernel_file.hpp index c38b72672..0bfc02945 100644 --- a/trunk/src/kernel/srs_kernel_file.hpp +++ b/trunk/src/kernel/srs_kernel_file.hpp @@ -35,6 +35,8 @@ #include #endif +class SrsFileReader; + /** * file writer, to write to file. */ @@ -73,6 +75,16 @@ public: virtual srs_error_t lseek(off_t offset, int whence, off_t* seeked); }; +// The file reader factory. +class ISrsFileReaderFactory +{ +public: + ISrsFileReaderFactory(); + virtual ~ISrsFileReaderFactory(); +public: + virtual SrsFileReader* create_file_reader(); +}; + /** * file reader, to read from file. */ diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index 6e6fdf248..1285ecf93 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -272,7 +272,7 @@ srs_error_t SrsHttpRedirectHandler::serve_http(ISrsHttpResponseWriter* w, ISrsHt location += "?" + r->query(); } - string msg = "Redirect to" + location; + string msg = "Redirect to " + location; w->header()->set_content_type("text/plain; charset=utf-8"); w->header()->set_content_length(msg.length()); @@ -304,37 +304,60 @@ srs_error_t SrsHttpNotFoundHandler::serve_http(ISrsHttpResponseWriter* w, ISrsHt return srs_go_http_error(w, SRS_CONSTS_HTTP_NotFound); } -SrsHttpFileServer::SrsHttpFileServer(string root_dir) +string srs_http_fs_fullpath(string dir, string upath, string pattern) { - dir = root_dir; -} - -SrsHttpFileServer::~SrsHttpFileServer() -{ -} - -srs_error_t SrsHttpFileServer::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) -{ - string upath = r->path(); - // add default pages. if (srs_string_ends_with(upath, "/")) { upath += SRS_HTTP_DEFAULT_PAGE; } - + string fullpath = dir + "/"; - + // remove the virtual directory. - srs_assert(entry); - size_t pos = entry->pattern.find("/"); - if (upath.length() > entry->pattern.length() && pos != string::npos) { - fullpath += upath.substr(entry->pattern.length() - pos); + size_t pos = pattern.find("/"); + if (upath.length() > pattern.length() && pos != string::npos) { + fullpath += upath.substr(pattern.length() - pos); } else { fullpath += upath; } + + return fullpath; +} + +SrsHttpFileServer::SrsHttpFileServer(string root_dir) +{ + dir = root_dir; + fs_factory = new ISrsFileReaderFactory(); + _srs_path_exists = srs_path_exists; +} + +SrsHttpFileServer::~SrsHttpFileServer() +{ + srs_freep(fs_factory); +} + +SrsHttpFileServer* 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) +{ + _srs_path_exists = pfn; + return this; +} + +srs_error_t SrsHttpFileServer::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) +{ + srs_assert(entry); + + string upath = r->path(); + string fullpath = srs_http_fs_fullpath(dir, upath, entry->pattern); // stat current dir, if exists, return error. - if (!srs_path_exists(fullpath)) { + if (!_srs_path_exists(fullpath)) { srs_warn("http miss file=%s, pattern=%s, upath=%s", fullpath.c_str(), entry->pattern.c_str(), upath.c_str()); return SrsHttpNotFoundHandler().serve_http(w, r); @@ -357,15 +380,15 @@ srs_error_t SrsHttpFileServer::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMes srs_error_t SrsHttpFileServer::serve_file(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, string fullpath) { srs_error_t err = srs_success; - - // open the target file. - SrsFileReader fs; - - if ((err = fs.open(fullpath)) != srs_success) { + + SrsFileReader* fs = fs_factory->create_file_reader(); + SrsAutoFree(SrsFileReader, fs); + + if ((err = fs->open(fullpath)) != srs_success) { return srs_error_wrap(err, "open file %s", fullpath.c_str()); } - int64_t length = fs.filesize(); + int64_t length = fs->filesize(); // unset the content length to encode in chunked encoding. w->header()->set_content_length(length); @@ -418,7 +441,7 @@ srs_error_t SrsHttpFileServer::serve_file(ISrsHttpResponseWriter* w, ISrsHttpMes // write body. int64_t left = length; - 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, "copy file=%s size=%d", fullpath.c_str(), left); } diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp index e38ae9db1..a0f0dd88a 100644 --- a/trunk/src/protocol/srs_http_stack.hpp +++ b/trunk/src/protocol/srs_http_stack.hpp @@ -46,6 +46,7 @@ class ISrsHttpMessage; class SrsHttpMuxEntry; class ISrsHttpResponseWriter; class SrsJsonObject; +class ISrsFileReaderFactory; // From http specification // CR = @@ -276,6 +277,12 @@ public: virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); }; +// For utest to mock it. +typedef bool (*_pfn_srs_path_exists)(std::string path); + +// Build the file path from request r. +extern std::string srs_http_fs_fullpath(std::string dir, std::string upath, std::string pattern); + // FileServer returns a handler that serves HTTP requests // with the contents of the file system rooted at root. // @@ -288,9 +295,17 @@ class SrsHttpFileServer : public ISrsHttpHandler { protected: std::string dir; +private: + ISrsFileReaderFactory* fs_factory; + _pfn_srs_path_exists _srs_path_exists; public: SrsHttpFileServer(std::string root_dir); virtual ~SrsHttpFileServer(); +private: + // For utest to mock the fs. + virtual SrsHttpFileServer* set_fs_factory(ISrsFileReaderFactory* v); + // For utest to mock the path check function. + virtual SrsHttpFileServer* 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 3ad6504f7..5aaa863bd 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -30,6 +30,8 @@ using namespace std; #include #include #include +#include +#include class MockResponseWriter : virtual public ISrsHttpResponseWriter, virtual public ISrsHttpHeaderFilter { @@ -90,6 +92,7 @@ srs_error_t MockResponseWriter::filter(SrsHttpHeader* h) h->del("Content-Type"); h->del("Server"); h->del("Connection"); + h->del("Location"); return srs_success; } @@ -120,6 +123,9 @@ VOID TEST(ProtocolHTTPTest, ResponseDetect) EXPECT_STREQ("application/octet-stream", srs_go_http_detect(NULL, 0).c_str()); } +#define __MOCK_HTTP_EXPECT_STREQ(status, text, w) \ + EXPECT_STREQ(mock_http_response(status, text).c_str(), HELPER_BUFFER2STR(&w.io.out_buffer).c_str()) + VOID TEST(ProtocolHTTPTest, ResponseHTTPError) { srs_error_t err; @@ -127,7 +133,7 @@ VOID TEST(ProtocolHTTPTest, ResponseHTTPError) if (true) { MockResponseWriter w; HELPER_EXPECT_SUCCESS(srs_go_http_error(&w, SRS_CONSTS_HTTP_Found)); - EXPECT_STREQ(mock_http_response(302,"Found").c_str(), HELPER_BUFFER2STR(&w.io.out_buffer).c_str()); + __MOCK_HTTP_EXPECT_STREQ(302, "Found", w); } } @@ -165,6 +171,69 @@ VOID TEST(ProtocolHTTPTest, HTTPHeader) srs_freep(o); } +class MockFileReaderFactory : public ISrsFileReaderFactory +{ +public: + string bytes; + MockFileReaderFactory(string data) { + bytes = data; + } + virtual ~MockFileReaderFactory() { + } + virtual SrsFileReader* create_file_reader() { + return new MockSrsFileReader((const char*)bytes.data(), (int)bytes.length()); + } +}; + +bool _mock_srs_path_exists(std::string path) +{ + return true; +} + +VOID TEST(ProtocolHTTPTest, BasicHandlers) +{ + srs_error_t err; + + if (true) { + EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp", "/tmp/index.html", "/").c_str()); + } + + if (true) { + SrsHttpMuxEntry e; + e.pattern = "/"; + + SrsHttpFileServer h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!"))->set_path_check(_mock_srs_path_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(200, "Hello, world!", w); + } + + if (true) { + SrsHttpRedirectHandler h("/api", 500); + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/api?v=2.0", false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + __MOCK_HTTP_EXPECT_STREQ(500, "Redirect to /api?v=2.0", w); + } + + if (true) { + SrsHttpNotFoundHandler h; + + MockResponseWriter w; + HELPER_ASSERT_SUCCESS(h.serve_http(&w, NULL)); + __MOCK_HTTP_EXPECT_STREQ(404, "Not Found", w); + } +} + class MockMSegmentsReader : public ISrsReader { public: