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: