diff --git a/README.md b/README.md
index 1951ca5f1..555029320 100755
--- a/README.md
+++ b/README.md
@@ -147,6 +147,12 @@ For previous versions, please read:
## V3 changes
+* v3.0, 2019-12-17, Fix HTTP CORS bug when sending response for OPTIONS. 3.0.72
+* v3.0, 2019-12-17, Enhance HTTP response write for final_request.
+* v3.0, 2019-12-17, Refactor HTTP stream to disconnect client when unpublish.
+* v3.0, 2019-12-17, Fix HTTP-FLV and VOD-FLV conflicting bug.
+* v3.0, 2019-12-17, Refactor HttpResponseWriter.write, default to single text mode.
+* 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/configure b/trunk/configure
index d5e6b3082..000f60975 100755
--- a/trunk/configure
+++ b/trunk/configure
@@ -315,7 +315,7 @@ fi
# utest, the unit-test cases of srs, base on gtest1.6
if [ $SRS_UTEST = YES ]; then
MODULE_FILES=("srs_utest" "srs_utest_amf0" "srs_utest_protocol" "srs_utest_kernel" "srs_utest_core"
- "srs_utest_config" "srs_utest_protostack" "srs_utest_reload" "srs_utest_service" "srs_utest_app")
+ "srs_utest_config" "srs_utest_rtmp" "srs_utest_http" "srs_utest_reload" "srs_utest_service" "srs_utest_app")
ModuleLibIncs=(${SRS_OBJS_DIR} ${LibSTRoot} ${LibSSLRoot})
ModuleLibFiles=(${LibSTfile} ${LibSSLfile})
MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "SERVICE" "APP")
diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp
index 9f8ed7726..c5fae9974 100644
--- a/trunk/src/app/srs_app_config.cpp
+++ b/trunk/src/app/srs_app_config.cpp
@@ -907,7 +907,7 @@ SrsJsonAny* SrsConfDirective::dumps_arg0_to_str()
SrsJsonAny* SrsConfDirective::dumps_arg0_to_integer()
{
- return SrsJsonAny::integer(::atol(arg0().c_str()));
+ return SrsJsonAny::integer(::atoll(arg0().c_str()));
}
SrsJsonAny* SrsConfDirective::dumps_arg0_to_number()
diff --git a/trunk/src/app/srs_app_conn.cpp b/trunk/src/app/srs_app_conn.cpp
index 79188ea95..b54970ab9 100644
--- a/trunk/src/app/srs_app_conn.cpp
+++ b/trunk/src/app/srs_app_conn.cpp
@@ -181,10 +181,12 @@ srs_error_t SrsConnection::cycle()
// client close peer.
// TODO: FIXME: Only reset the error when client closed it.
- if (srs_is_client_gracefully_close(srs_error_code(err))) {
+ if (srs_is_client_gracefully_close(err)) {
srs_warn("client disconnect peer. ret=%d", srs_error_code(err));
+ } else if (srs_is_server_gracefully_close(err)) {
+ srs_warn("server disconnect. ret=%d", srs_error_code(err));
} else {
- srs_error("connect error %s", srs_error_desc(err).c_str());
+ srs_error("serve error %s", srs_error_desc(err).c_str());
}
srs_freep(err);
diff --git a/trunk/src/app/srs_app_http_api.cpp b/trunk/src/app/srs_app_http_api.cpp
index bd6c01ced..2d84a029c 100644
--- a/trunk/src/app/srs_app_http_api.cpp
+++ b/trunk/src/app/srs_app_http_api.cpp
@@ -664,12 +664,7 @@ srs_error_t SrsGoApiRequests::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess
// request headers
SrsJsonObject* headers = SrsJsonAny::object();
data->set("headers", headers);
-
- for (int i = 0; i < r->request_header_count(); i++) {
- std::string key = r->request_header_key_at(i);
- std::string value = r->request_header_value_at(i);
- headers->set(key, SrsJsonAny::str(value.c_str()));
- }
+ r->header()->dumps(headers);
// server informations
SrsJsonObject* server = SrsJsonAny::object();
diff --git a/trunk/src/app/srs_app_http_conn.cpp b/trunk/src/app/srs_app_http_conn.cpp
index f1289dfbd..68663cdb8 100644
--- a/trunk/src/app/srs_app_http_conn.cpp
+++ b/trunk/src/app/srs_app_http_conn.cpp
@@ -81,11 +81,9 @@ srs_error_t SrsHttpConn::do_cycle()
{
srs_error_t err = srs_success;
- srs_trace("HTTP client ip=%s", ip.c_str());
-
// initialize parser
if ((err = parser->initialize(HTTP_REQUEST, false)) != srs_success) {
- return srs_error_wrap(err, "init parser");
+ return srs_error_wrap(err, "init parser for %s", ip.c_str());
}
// set the recv timeout, for some clients never disconnect the connection.
@@ -102,10 +100,12 @@ srs_error_t SrsHttpConn::do_cycle()
}
// process http messages.
- while ((err = trd->pull()) == srs_success) {
- ISrsHttpMessage* req = NULL;
-
+ for (int req_id = 0; (err = trd->pull()) == srs_success; req_id++) {
+ // Try to receive a message from http.
+ srs_trace("HTTP client ip=%s, request=%d, to=%dms", ip.c_str(), req_id, srsu2ms(SRS_HTTP_RECV_TIMEOUT));
+
// get a http message
+ ISrsHttpMessage* req = NULL;
if ((err = parser->parse_message(skt, &req)) != srs_success) {
break;
}
diff --git a/trunk/src/app/srs_app_http_static.cpp b/trunk/src/app/srs_app_http_static.cpp
index e486e773a..5be561259 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,16 +107,17 @@ 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));
w->header()->set_content_type("video/x-flv");
+ w->write_header(SRS_CONSTS_HTTP_OK);
// write flv header and sequence header.
if ((err = w->write(flv_header, sizeof(flv_header))) != srs_success) {
@@ -132,7 +133,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 +147,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.
@@ -169,20 +171,19 @@ srs_error_t SrsVodStream::serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsHttpMe
// write http header for ts.
w->header()->set_content_length(left);
w->header()->set_content_type("video/mp4");
-
- // status code 206 to make dash.as happy.
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/app/srs_app_http_stream.cpp b/trunk/src/app/srs_app_http_stream.cpp
index 3e5551131..1f4064528 100755
--- a/trunk/src/app/srs_app_http_stream.cpp
+++ b/trunk/src/app/srs_app_http_stream.cpp
@@ -540,6 +540,9 @@ srs_error_t SrsLiveStream::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess
return srs_error_new(ERROR_HTTP_LIVE_STREAM_EXT, "invalid pattern=%s", entry->pattern.c_str());
}
SrsAutoFree(ISrsBufferEncoder, enc);
+
+ // Enter chunked mode, because we didn't set the content-length.
+ w->write_header(SRS_CONSTS_HTTP_OK);
// create consumer of souce, ignore gop cache, use the audio gop cache.
SrsConsumer* consumer = NULL;
@@ -594,7 +597,7 @@ srs_error_t SrsLiveStream::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess
if ((err = hc->set_socket_buffer(mw_sleep)) != srs_success) {
return srs_error_wrap(err, "set mw_sleep %" PRId64, mw_sleep);
}
-
+
SrsHttpRecvThread* trd = new SrsHttpRecvThread(hc);
SrsAutoFree(SrsHttpRecvThread, trd);
@@ -607,6 +610,7 @@ srs_error_t SrsLiveStream::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess
enc->has_cache(), msgs.max);
// TODO: free and erase the disabled entry after all related connections is closed.
+ // TODO: FXIME: Support timeout for player, quit infinite-loop.
while (entry->enabled) {
pprint->elapse();
@@ -656,8 +660,10 @@ srs_error_t SrsLiveStream::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess
return srs_error_wrap(err, "send messages");
}
}
-
- return err;
+
+ // Here, the entry is disabled by encoder un-publishing or reloading,
+ // so we must return a io.EOF error to disconnect the client, or the client will never quit.
+ return srs_error_new(ERROR_HTTP_STREAM_EOF, "Stream EOF");
}
srs_error_t SrsLiveStream::http_hooks_on_play(ISrsHttpMessage* r)
@@ -1046,6 +1052,18 @@ srs_error_t SrsHttpStreamServer::hijack(ISrsHttpMessage* request, ISrsHttpHandle
return err;
}
}
+
+ // For HTTP-FLV stream, the template must have the same schema with upath.
+ // The template is defined in config, the mout of http stream. The upath is specified by http request path.
+ // If template is "[vhost]/[app]/[stream].flv", the upath should be:
+ // matched for "/live/livestream.flv"
+ // matched for "ossrs.net/live/livestream.flv"
+ // not-matched for "/livestream.flv", which is actually "/__defaultApp__/livestream.flv", HTTP not support default app.
+ // not-matched for "/live/show/livestream.flv"
+ string upath = request->path();
+ if (srs_string_count(upath, "/") != srs_string_count(entry->mount, "/")) {
+ return err;
+ }
// convert to concreate class.
SrsHttpMessage* hreq = dynamic_cast(request);
diff --git a/trunk/src/app/srs_app_rtmp_conn.cpp b/trunk/src/app/srs_app_rtmp_conn.cpp
index 867c4aea1..dc480dbeb 100644
--- a/trunk/src/app/srs_app_rtmp_conn.cpp
+++ b/trunk/src/app/srs_app_rtmp_conn.cpp
@@ -390,8 +390,8 @@ srs_error_t SrsRtmpConn::service_cycle()
// stream service must terminated with error, never success.
// when terminated with success, it's user required to stop.
- if (srs_error_code(err) == ERROR_SUCCESS) {
- srs_freep(err);
+ // TODO: FIXME: Support RTMP client timeout, https://github.com/ossrs/srs/issues/1134
+ if (err == srs_success) {
continue;
}
diff --git a/trunk/src/app/srs_app_rtsp.cpp b/trunk/src/app/srs_app_rtsp.cpp
index b96b2190e..4f5fb7b6a 100644
--- a/trunk/src/app/srs_app_rtsp.cpp
+++ b/trunk/src/app/srs_app_rtsp.cpp
@@ -387,10 +387,9 @@ srs_error_t SrsRtspConn::cycle()
if (err == srs_success) {
srs_trace("client finished.");
- } else if (srs_is_client_gracefully_close(srs_error_code(err))) {
+ } else if (srs_is_client_gracefully_close(err)) {
srs_warn("client disconnect peer. code=%d", srs_error_code(err));
srs_freep(err);
- err = srs_success;
}
if (video_rtp) {
diff --git a/trunk/src/core/srs_core.hpp b/trunk/src/core/srs_core.hpp
index f7cee590d..8a5ce5e16 100644
--- a/trunk/src/core/srs_core.hpp
+++ b/trunk/src/core/srs_core.hpp
@@ -27,7 +27,7 @@
// The version config.
#define VERSION_MAJOR 3
#define VERSION_MINOR 0
-#define VERSION_REVISION 71
+#define VERSION_REVISION 72
// The macros generated by configure script.
#include
diff --git a/trunk/src/kernel/srs_kernel_consts.hpp b/trunk/src/kernel/srs_kernel_consts.hpp
index 8df891fd2..9a75955c4 100644
--- a/trunk/src/kernel/srs_kernel_consts.hpp
+++ b/trunk/src/kernel/srs_kernel_consts.hpp
@@ -213,7 +213,7 @@
#define SRS_CONSTS_HTTP_QUERY_SEP '?'
// The default recv timeout.
-#define SRS_HTTP_RECV_TIMEOUT (60 * SRS_UTIME_SECONDS)
+#define SRS_HTTP_RECV_TIMEOUT (15 * SRS_UTIME_SECONDS)
// 6.1.1 Status Code and Reason Phrase
#define SRS_CONSTS_HTTP_Continue 100
diff --git a/trunk/src/kernel/srs_kernel_error.cpp b/trunk/src/kernel/srs_kernel_error.cpp
index de13304db..de52f710b 100644
--- a/trunk/src/kernel/srs_kernel_error.cpp
+++ b/trunk/src/kernel/srs_kernel_error.cpp
@@ -30,30 +30,26 @@
#include
using namespace std;
-bool srs_is_system_control_error(int error_code)
+bool srs_is_system_control_error(srs_error_t err)
{
+ int error_code = srs_error_code(err);
return error_code == ERROR_CONTROL_RTMP_CLOSE
|| error_code == ERROR_CONTROL_REPUBLISH
|| error_code == ERROR_CONTROL_REDIRECT;
}
-bool srs_is_system_control_error(srs_error_t err)
+bool srs_is_client_gracefully_close(srs_error_t err)
{
int error_code = srs_error_code(err);
- return srs_is_system_control_error(error_code);
-}
-
-bool srs_is_client_gracefully_close(int error_code)
-{
return error_code == ERROR_SOCKET_READ
|| error_code == ERROR_SOCKET_READ_FULLY
|| error_code == ERROR_SOCKET_WRITE;
}
-bool srs_is_client_gracefully_close(srs_error_t err)
+bool srs_is_server_gracefully_close(srs_error_t err)
{
- int error_code = srs_error_code(err);
- return srs_is_client_gracefully_close(error_code);
+ int code = srs_error_code(err);
+ return code == ERROR_HTTP_STREAM_EOF;
}
SrsCplxError::SrsCplxError()
diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp
index ae543662f..a5c19da61 100644
--- a/trunk/src/kernel/srs_kernel_error.hpp
+++ b/trunk/src/kernel/srs_kernel_error.hpp
@@ -320,6 +320,7 @@
#define ERROR_HTTP_REQUEST_EOF 4029
#define ERROR_HTTP_302_INVALID 4038
#define ERROR_BASE64_DECODE 4039
+#define ERROR_HTTP_STREAM_EOF 4040
///////////////////////////////////////////////////////
// HTTP API error.
@@ -336,10 +337,11 @@
// Whether the error code is an system control error.
// TODO: FIXME: Remove it from underlayer for confused with error and logger.
-extern bool srs_is_system_control_error(int error_code);
extern bool srs_is_system_control_error(srs_error_t err);
-extern bool srs_is_client_gracefully_close(int error_code);
+// It's closed by client.
extern bool srs_is_client_gracefully_close(srs_error_t err);
+// It's closed by server, such as streaming EOF.
+extern bool srs_is_server_gracefully_close(srs_error_t err);
// The complex error carries code, message, callstack and instant variables,
// which is more strong and easy to locate problem by log,
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/kernel/srs_kernel_utility.cpp b/trunk/src/kernel/srs_kernel_utility.cpp
index 9a2b7c46c..b1f8a0cdd 100644
--- a/trunk/src/kernel/srs_kernel_utility.cpp
+++ b/trunk/src/kernel/srs_kernel_utility.cpp
@@ -37,6 +37,7 @@
#include
#include
+#include
using namespace std;
#include
@@ -451,6 +452,16 @@ bool srs_string_contains(string str, string flag0, string flag1, string flag2)
return str.find(flag0) != string::npos || str.find(flag1) != string::npos || str.find(flag2) != string::npos;
}
+int srs_string_count(string str, string flag)
+{
+ int nn = 0;
+ for (int i = 0; i < (int)flag.length(); i++) {
+ char ch = flag.at(i);
+ nn += std::count(str.begin(), str.end(), ch);
+ }
+ return nn;
+}
+
vector srs_string_split(string str, string flag)
{
vector arr;
diff --git a/trunk/src/kernel/srs_kernel_utility.hpp b/trunk/src/kernel/srs_kernel_utility.hpp
index 3b55a520f..30a8b5568 100644
--- a/trunk/src/kernel/srs_kernel_utility.hpp
+++ b/trunk/src/kernel/srs_kernel_utility.hpp
@@ -98,6 +98,8 @@ extern bool srs_string_starts_with(std::string str, std::string flag0, std::stri
extern bool srs_string_contains(std::string str, std::string flag);
extern bool srs_string_contains(std::string str, std::string flag0, std::string flag1);
extern bool srs_string_contains(std::string str, std::string flag0, std::string flag1, std::string flag2);
+// Count each char of flag in string
+extern int srs_string_count(std::string str, std::string flag);
// Find the min match in str for flags.
extern std::string srs_string_min_match(std::string str, std::vector flags);
// Split the string by flag to array.
diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp
index 3f4db8559..e2d1d1e57 100644
--- a/trunk/src/protocol/srs_http_stack.cpp
+++ b/trunk/src/protocol/srs_http_stack.cpp
@@ -35,9 +35,13 @@ using namespace std;
#include
#include
#include
+#include
#define SRS_HTTP_DEFAULT_PAGE "index.html"
+// @see ISrsHttpMessage._http_ts_send_buffer
+#define SRS_HTTP_TS_SEND_BUFFER_SIZE 4096
+
// get the status text of code.
string srs_generate_http_status_text(int status)
{
@@ -99,9 +103,9 @@ string srs_generate_http_status_text(int status)
// permits a body. See RFC2616, section 4.4.
bool srs_go_http_body_allowd(int status)
{
- if (status >= 100 && status <= 199) {
+ if (status >= SRS_CONSTS_HTTP_Continue && status < SRS_CONSTS_HTTP_OK) {
return false;
- } else if (status == 204 || status == 304) {
+ } else if (status == SRS_CONSTS_HTTP_NoContent || status == SRS_CONSTS_HTTP_NotModified) {
return false;
}
@@ -116,9 +120,7 @@ bool srs_go_http_body_allowd(int status)
// returns "application/octet-stream".
string srs_go_http_detect(char* data, int size)
{
- // detect only when data specified.
- if (data) {
- }
+ // TODO: Implement the request content-type detecting.
return "application/octet-stream"; // fallback
}
@@ -158,14 +160,37 @@ void SrsHttpHeader::set(string key, string value)
string SrsHttpHeader::get(string key)
{
std::string v;
-
- if (headers.find(key) != headers.end()) {
- v = headers[key];
+
+ map::iterator it = headers.find(key);
+ if (it != headers.end()) {
+ v = it->second;
}
return v;
}
+void SrsHttpHeader::del(string key)
+{
+ map::iterator it = headers.find(key);
+ if (it != headers.end()) {
+ headers.erase(it);
+ }
+}
+
+int SrsHttpHeader::count()
+{
+ return (int)headers.size();
+}
+
+void SrsHttpHeader::dumps(SrsJsonObject* o)
+{
+ map::iterator it;
+ for (it = headers.begin(); it != headers.end(); ++it) {
+ string v = it->second;
+ o->set(it->first, SrsJsonAny::str(v.c_str()));
+ }
+}
+
int64_t SrsHttpHeader::content_length()
{
std::string cl = get("Content-Length");
@@ -194,7 +219,7 @@ void SrsHttpHeader::set_content_type(string ct)
void SrsHttpHeader::write(stringstream& ss)
{
- std::map::iterator it;
+ map::iterator it;
for (it = headers.begin(); it != headers.end(); ++it) {
ss << it->first << ": " << it->second << SRS_HTTP_CRLF;
}
@@ -247,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());
@@ -279,37 +304,67 @@ srs_error_t SrsHttpNotFoundHandler::serve_http(ISrsHttpResponseWriter* w, ISrsHt
return srs_go_http_error(w, SRS_CONSTS_HTTP_NotFound);
}
+string srs_http_fs_fullpath(string dir, string pattern, string upath)
+{
+ // add default pages.
+ if (srs_string_ends_with(upath, "/")) {
+ upath += SRS_HTTP_DEFAULT_PAGE;
+ }
+
+ // Remove the virtual directory.
+ // For example:
+ // pattern=/api, the virtual directory is api, upath=/api/index.html, fullpath={dir}/index.html
+ // pattern=/api, the virtual directory is api, upath=/api/views/index.html, fullpath={dir}/views/index.html
+ // The vhost prefix is ignored, for example:
+ // pattern=ossrs.net/api, the vhost is ossrs.net, the pattern equals to /api under this vhost,
+ // so the virtual directory is also api
+ size_t pos = pattern.find("/");
+ string filename = upath;
+ if (upath.length() > pattern.length() && pos != string::npos) {
+ filename = upath.substr(pattern.length() - pos);
+ }
+
+ string fullpath = srs_string_trim_end(dir, "/");
+ if (!srs_string_starts_with(filename, "/")) {
+ fullpath += "/";
+ }
+ fullpath += filename;
+
+ 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);
+}
+
+void SrsHttpFileServer::set_fs_factory(ISrsFileReaderFactory* f)
+{
+ srs_freep(fs_factory);
+ fs_factory = f;
+}
+
+void SrsHttpFileServer::set_path_check(_pfn_srs_path_exists pfn)
+{
+ _srs_path_exists = pfn;
}
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);
- } else {
- fullpath += upath;
- }
+
+ string upath = r->path();
+ string fullpath = srs_http_fs_fullpath(dir, entry->pattern, upath);
// 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);
@@ -332,15 +387,16 @@ 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();
+
+ // 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);
@@ -390,10 +446,13 @@ srs_error_t SrsHttpFileServer::serve_file(ISrsHttpResponseWriter* w, ISrsHttpMes
w->header()->set_content_type(_mime[ext]);
}
}
+
+ // Enter chunked mode, because we didn't set the content-length.
+ w->write_header(SRS_CONSTS_HTTP_OK);
// 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);
}
@@ -422,10 +481,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");
}
@@ -458,11 +515,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);
}
@@ -471,25 +532,22 @@ srs_error_t SrsHttpFileServer::copy(ISrsHttpResponseWriter* w, SrsFileReader* fs
srs_error_t err = srs_success;
int left = size;
- char* buf = r->http_ts_send_buffer();
+ char* buf = new char[SRS_HTTP_TS_SEND_BUFFER_SIZE];
+ SrsAutoFreeA(char, buf);
while (left > 0) {
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;
}
@@ -609,16 +667,13 @@ srs_error_t SrsHttpServeMux::handle(std::string pattern, ISrsHttpHandler* handle
std::string rpattern = pattern.substr(0, pattern.length() - 1);
SrsHttpMuxEntry* entry = NULL;
- // free the exists not explicit entry
+ // free the exists implicit entry
if (entries.find(rpattern) != entries.end()) {
- SrsHttpMuxEntry* exists = entries[rpattern];
- if (!exists->explicit_match) {
- entry = exists;
- }
+ entry = entries[rpattern];
}
// create implicit redirect.
- if (!entry || entry->explicit_match) {
+ if (!entry || !entry->explicit_match) {
srs_freep(entry);
entry = new SrsHttpMuxEntry();
@@ -666,7 +721,7 @@ srs_error_t SrsHttpServeMux::find_handler(ISrsHttpMessage* r, ISrsHttpHandler**
// always hijack.
if (!hijackers.empty()) {
- // notice all hijacker the match failed.
+ // notify all hijackers unless matching failed.
std::vector::iterator it;
for (it = hijackers.begin(); it != hijackers.end(); ++it) {
ISrsHttpMatchHijacker* hijacker = *it;
@@ -765,17 +820,10 @@ srs_error_t SrsHttpCorsMux::initialize(ISrsHttpServeMux* worker, bool cros_enabl
srs_error_t SrsHttpCorsMux::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r)
{
- srs_error_t err = srs_success;
-
// If CORS enabled, and there is a "Origin" header, it's CORS.
if (enabled) {
- for (int i = 0; i < r->request_header_count(); i++) {
- string k = r->request_header_key_at(i);
- if (k == "Origin" || k == "origin") {
- required = true;
- break;
- }
- }
+ SrsHttpHeader* h = r->header();
+ required = !h->get("Origin").empty();
}
// When CORS required, set the CORS headers.
@@ -795,9 +843,7 @@ srs_error_t SrsHttpCorsMux::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessag
} else {
w->write_header(SRS_CONSTS_HTTP_MethodNotAllowed);
}
- if ((err = w->final_request()) != srs_success) {
- return srs_error_wrap(err, "final request");
- }
+ return w->final_request();
}
srs_assert(next);
@@ -806,17 +852,10 @@ srs_error_t SrsHttpCorsMux::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessag
ISrsHttpMessage::ISrsHttpMessage()
{
- _http_ts_send_buffer = new char[SRS_HTTP_TS_SEND_BUFFER_SIZE];
}
ISrsHttpMessage::~ISrsHttpMessage()
{
- srs_freepa(_http_ts_send_buffer);
-}
-
-char* ISrsHttpMessage::http_ts_send_buffer()
-{
- return _http_ts_send_buffer;
}
SrsHttpUri::SrsHttpUri()
@@ -912,6 +951,8 @@ string SrsHttpUri::get_uri_field(string uri, void* php_u, int ifield)
// For #if !defined(SRS_EXPORT_LIBRTMP)
#endif
+// LCOV_EXCL_START
+
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
@@ -3436,3 +3477,13 @@ http_parser_set_max_header_size(uint32_t size) {
max_header_size = size;
}
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+
+// LCOV_EXCL_STOP
diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp
index 062178d57..9044f5549 100644
--- a/trunk/src/protocol/srs_http_stack.hpp
+++ b/trunk/src/protocol/srs_http_stack.hpp
@@ -45,6 +45,8 @@ class SrsHttpHeader;
class ISrsHttpMessage;
class SrsHttpMuxEntry;
class ISrsHttpResponseWriter;
+class SrsJsonObject;
+class ISrsFileReaderFactory;
// From http specification
// CR =
@@ -62,9 +64,6 @@ class ISrsHttpResponseWriter;
#define SRS_HTTP_CRLF "\r\n" // 0x0D0A
#define SRS_HTTP_CRLFCRLF "\r\n\r\n" // 0x0D0A0D0A
-// @see ISrsHttpMessage._http_ts_send_buffer
-#define SRS_HTTP_TS_SEND_BUFFER_SIZE 4096
-
// For ead all of http body, read each time.
#define SRS_HTTP_READ_CACHE_BYTES 4096
@@ -108,6 +107,11 @@ enum SrsHttpParseState {
class SrsHttpHeader
{
private:
+ // The order in which header fields with differing field names are
+ // received is not significant. However, it is "good practice" to send
+ // general-header fields first, followed by request-header or response-
+ // header fields, and ending with the entity-header fields.
+ // @doc https://tools.ietf.org/html/rfc2616#section-4.2
std::map headers;
public:
SrsHttpHeader();
@@ -121,6 +125,14 @@ public:
// To access multiple values of a key, access the map directly
// with CanonicalHeaderKey.
virtual std::string get(std::string key);
+ // Delete the http header indicated by key.
+ // Return the removed header field.
+ virtual void del(std::string);
+ // Get the count of headers.
+ virtual int count();
+public:
+ // Dumps to a JSON object.
+ virtual void dumps(SrsJsonObject* o);
public:
// Get the content length. -1 if not set.
virtual int64_t content_length();
@@ -138,7 +150,11 @@ public:
// A ResponseWriter interface is used by an HTTP handler to
// construct an HTTP response.
-// Usage 1, response with specified length content:
+// Usage 0, response with a message once:
+// ISrsHttpResponseWriter* w; // create or get response.
+// std::string msg = "Hello, HTTP!";
+// w->write((char*)msg.data(), (int)msg.length());
+// Usage 1, response with specified length content, same to #0:
// ISrsHttpResponseWriter* w; // create or get response.
// std::string msg = "Hello, HTTP!";
// w->header()->set_content_type("text/plain; charset=utf-8");
@@ -265,6 +281,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 pattern, std::string upath);
+
// FileServer returns a handler that serves HTTP requests
// with the contents of the file system rooted at root.
//
@@ -277,9 +299,17 @@ class SrsHttpFileServer : public ISrsHttpHandler
{
protected:
std::string dir;
+protected:
+ 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 void set_fs_factory(ISrsFileReaderFactory* v);
+ // For utest to mock the path check function.
+ virtual void set_path_check(_pfn_srs_path_exists pfn);
public:
virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r);
private:
@@ -420,9 +450,6 @@ public:
virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r);
};
-// For http header.
-typedef std::pair SrsHttpHeaderField;
-
// A Request represents an HTTP request received by a server
// or to be sent by a client.
//
@@ -444,16 +471,9 @@ typedef std::pair SrsHttpHeaderField;
// @rmark for mode 2, the infinite chunked, all left data is body.
class ISrsHttpMessage
{
-private:
- // Use a buffer to read and send ts file.
- // TODO: FIXME: remove it.
- char* _http_ts_send_buffer;
public:
ISrsHttpMessage();
virtual ~ISrsHttpMessage();
-public:
- // The http request level cache.
- virtual char* http_ts_send_buffer();
public:
virtual uint8_t method() = 0;
virtual uint16_t status_code() = 0;
@@ -500,9 +520,7 @@ public:
// then query_get("start") is "100", and query_get("end") is "200"
virtual std::string query_get(std::string key) = 0;
// Get the headers.
- virtual int request_header_count() = 0;
- virtual std::string request_header_key_at(int index) = 0;
- virtual std::string request_header_value_at(int index) = 0;
+ virtual SrsHttpHeader* header() = 0;
public:
// Whether the current request is JSONP,
// which has a "callback=xxx" in QueryString.
diff --git a/trunk/src/protocol/srs_protocol_json.cpp b/trunk/src/protocol/srs_protocol_json.cpp
index f7bd1c5ed..aae9412e1 100644
--- a/trunk/src/protocol/srs_protocol_json.cpp
+++ b/trunk/src/protocol/srs_protocol_json.cpp
@@ -23,6 +23,7 @@
#include
+// LCOV_EXCL_START
/* vim: set et ts=3 sw=3 sts=3 ft=c:
*
* Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved.
@@ -1314,6 +1315,7 @@ void json_value_free (json_value * value)
}
#endif
+// LCOV_EXCL_STOP
/**
* The MIT License (MIT)
diff --git a/trunk/src/protocol/srs_rtmp_stack.cpp b/trunk/src/protocol/srs_rtmp_stack.cpp
index bbb6408f2..2ff3a94f2 100644
--- a/trunk/src/protocol/srs_rtmp_stack.cpp
+++ b/trunk/src/protocol/srs_rtmp_stack.cpp
@@ -1884,20 +1884,9 @@ srs_error_t SrsRtmpClient::handshake()
SrsAutoFree(SrsComplexHandshake, complex_hs);
if ((err = complex_hs->handshake_with_server(hs_bytes, io)) != srs_success) {
- if (srs_error_code(err) == ERROR_RTMP_TRY_SIMPLE_HS) {
- srs_freep(err);
-
- // always alloc object at heap.
- // @see https://github.com/ossrs/srs/issues/509
- SrsSimpleHandshake* simple_hs = new SrsSimpleHandshake();
- SrsAutoFree(SrsSimpleHandshake, simple_hs);
-
- if ((err = simple_hs->handshake_with_server(hs_bytes, io)) != srs_success) {
- return srs_error_wrap(err, "simple handshake");
- }
- } else {
- return srs_error_wrap(err, "complex handshake");
- }
+ // As client, we never verify s0s1s2, because some server doesn't follow the RTMP spec.
+ // So we never have chance to use simple handshake.
+ return srs_error_wrap(err, "complex handshake");
}
hs_bytes->dispose();
diff --git a/trunk/src/protocol/srs_rtsp_stack.cpp b/trunk/src/protocol/srs_rtsp_stack.cpp
index 656737039..d3f5d7366 100644
--- a/trunk/src/protocol/srs_rtsp_stack.cpp
+++ b/trunk/src/protocol/srs_rtsp_stack.cpp
@@ -887,7 +887,7 @@ srs_error_t SrsRtspStack::do_recv_message(SrsRtspRequest* req)
if ((err = recv_token_eof(seq)) != srs_success) {
return srs_error_wrap(err, "seq");
}
- req->seq = ::atol(seq.c_str());
+ req->seq = ::atoll(seq.c_str());
} else if (token == SRS_RTSP_TOKEN_CONTENT_TYPE) {
std::string ct;
if ((err = recv_token_eof(ct)) != srs_success) {
@@ -899,7 +899,7 @@ srs_error_t SrsRtspStack::do_recv_message(SrsRtspRequest* req)
if ((err = recv_token_eof(cl)) != srs_success) {
return srs_error_wrap(err, "cl");
}
- req->content_length = ::atol(cl.c_str());
+ req->content_length = ::atoll(cl.c_str());
} else if (token == SRS_RTSP_TOKEN_TRANSPORT) {
std::string transport;
if ((err = recv_token_eof(transport)) != srs_success) {
diff --git a/trunk/src/service/srs_service_http_conn.cpp b/trunk/src/service/srs_service_http_conn.cpp
index c7cd873e0..3d8d1df06 100644
--- a/trunk/src/service/srs_service_http_conn.cpp
+++ b/trunk/src/service/srs_service_http_conn.cpp
@@ -39,11 +39,13 @@ using namespace std;
SrsHttpParser::SrsHttpParser()
{
buffer = new SrsFastStream();
+ header = NULL;
}
SrsHttpParser::~SrsHttpParser()
{
srs_freep(buffer);
+ srs_freep(header);
}
srs_error_t SrsHttpParser::initialize(enum http_parser_type type, bool allow_jsonp)
@@ -70,19 +72,20 @@ srs_error_t SrsHttpParser::initialize(enum http_parser_type type, bool allow_jso
srs_error_t SrsHttpParser::parse_message(ISrsReader* reader, ISrsHttpMessage** ppmsg)
{
- *ppmsg = NULL;
-
srs_error_t err = srs_success;
+
+ *ppmsg = NULL;
- // reset request data.
- field_name = "";
- field_value = "";
- expect_field_name = true;
+ // Reset request data.
state = SrsHttpParseStateInit;
- header = http_parser();
- url = "";
- headers.clear();
+ hp_header = http_parser();
+ // The body that we have read from cache.
pbody = NULL;
+ // We must reset the field name and value, because we may get a partial value in on_header_value.
+ field_name = field_value = "";
+ // The header of the request.
+ srs_freep(header);
+ header = new SrsHttpHeader();
// do parse
if ((err = parse_message_imp(reader)) != srs_success) {
@@ -90,10 +93,12 @@ srs_error_t SrsHttpParser::parse_message(ISrsReader* reader, ISrsHttpMessage** p
}
// create msg
- SrsHttpMessage* msg = new SrsHttpMessage(reader);
-
- // initalize http msg, parse url.
- if ((err = msg->update(url, jsonp, &header, buffer, headers)) != srs_success) {
+ SrsHttpMessage* msg = new SrsHttpMessage(reader, buffer);
+
+ // Initialize the basic information.
+ msg->set_basic(hp_header.method, hp_header.status_code, hp_header.content_length);
+ msg->set_header(header, http_should_keep_alive(&hp_header));
+ if ((err = msg->set_url(url, jsonp)) != srs_success) {
srs_freep(msg);
return srs_error_wrap(err, "update message");
}
@@ -137,10 +142,10 @@ srs_error_t SrsHttpParser::parse_message_imp(ISrsReader* reader)
return srs_error_wrap(err, "grow buffer");
}
}
-
- // parse last header.
- if (!field_name.empty() && !field_value.empty()) {
- headers.push_back(std::make_pair(field_name, field_value));
+
+ SrsHttpParser* obj = this;
+ if (!obj->field_value.empty()) {
+ obj->header->set(obj->field_name, obj->field_value);
}
return err;
@@ -163,7 +168,7 @@ int SrsHttpParser::on_headers_complete(http_parser* parser)
SrsHttpParser* obj = (SrsHttpParser*)parser->data;
srs_assert(obj);
- obj->header = *parser;
+ obj->hp_header = *parser;
// save the parser when header parse completed.
obj->state = SrsHttpParseStateHeaderComplete;
@@ -192,7 +197,7 @@ int SrsHttpParser::on_url(http_parser* parser, const char* at, size_t length)
srs_assert(obj);
if (length > 0) {
- obj->url.append(at, (int)length);
+ obj->url = string(at, (int)length);
}
srs_info("Method: %d, Url: %.*s", parser->method, (int)length, at);
@@ -204,16 +209,11 @@ int SrsHttpParser::on_header_field(http_parser* parser, const char* at, size_t l
{
SrsHttpParser* obj = (SrsHttpParser*)parser->data;
srs_assert(obj);
-
- // field value=>name, reap the field.
- if (!obj->expect_field_name) {
- obj->headers.push_back(std::make_pair(obj->field_name, obj->field_value));
-
- // reset the field name when parsed.
- obj->field_name = "";
- obj->field_value = "";
+
+ if (!obj->field_value.empty()) {
+ obj->header->set(obj->field_name, obj->field_value);
+ obj->field_name = obj->field_value = "";
}
- obj->expect_field_name = true;
if (length > 0) {
obj->field_name.append(at, (int)length);
@@ -231,7 +231,6 @@ int SrsHttpParser::on_header_value(http_parser* parser, const char* at, size_t l
if (length > 0) {
obj->field_value.append(at, (int)length);
}
- obj->expect_field_name = false;
srs_info("Header value(%d bytes): %.*s", (int)length, (int)length, at);
return 0;
@@ -253,58 +252,72 @@ int SrsHttpParser::on_body(http_parser* parser, const char* at, size_t length)
return 0;
}
-SrsHttpMessage::SrsHttpMessage(ISrsReader* reader) : ISrsHttpMessage()
+SrsHttpMessage::SrsHttpMessage(ISrsReader* reader, SrsFastStream* buffer) : ISrsHttpMessage()
{
owner_conn = NULL;
chunked = false;
infinite_chunked = false;
- keep_alive = true;
_uri = new SrsHttpUri();
- _body = new SrsHttpResponseReader(this, reader);
- _http_ts_send_buffer = new char[SRS_HTTP_TS_SEND_BUFFER_SIZE];
+ _body = new SrsHttpResponseReader(this, reader, buffer);
+
jsonp = false;
+
+ _method = 0;
+ _status = 0;
+ _content_length = -1;
+ _keep_alive = true;
}
SrsHttpMessage::~SrsHttpMessage()
{
srs_freep(_body);
srs_freep(_uri);
- srs_freepa(_http_ts_send_buffer);
}
-srs_error_t SrsHttpMessage::update(string url, bool allow_jsonp, http_parser* header, SrsFastStream* body, vector& headers)
+void SrsHttpMessage::set_basic(uint8_t method, uint16_t status, int64_t content_length)
+{
+ _method = method;
+ _status = status;
+ if (_content_length == -1) {
+ _content_length = content_length;
+ }
+}
+
+void SrsHttpMessage::set_header(SrsHttpHeader* header, bool keep_alive)
{
- srs_error_t err = srs_success;
-
- _url = url;
_header = *header;
- _headers = headers;
-
+ _keep_alive = keep_alive;
+
// whether chunked.
- std::string transfer_encoding = get_request_header("Transfer-Encoding");
- chunked = (transfer_encoding == "chunked");
-
- // whether keep alive.
- keep_alive = http_should_keep_alive(header);
-
- // set the buffer.
- if ((err = _body->initialize(body)) != srs_success) {
- return srs_error_wrap(err, "init body");
+ chunked = (header->get("Transfer-Encoding") == "chunked");
+
+ // Update the content-length in header.
+ string clv = header->get("Content-Length");
+ if (!clv.empty()) {
+ _content_length = ::atoll(clv.c_str());
}
+}
+
+srs_error_t SrsHttpMessage::set_url(string url, bool allow_jsonp)
+{
+ srs_error_t err = srs_success;
- // parse uri from url.
- std::string host = get_request_header("Host");
-
- // use server public ip when no host specified.
+ _url = url;
+
+ // use server public ip when host not specified.
// to make telnet happy.
+ std::string host = _header.get("Host");
if (host.empty()) {
host= srs_get_public_internet_address();
}
-
- // parse uri to schema/server:port/path?query
- std::string uri = "http://" + host + _url;
+
+ // parse uri from schema/server:port/path?query
+ std::string uri = _url;
+ if (!host.empty()) {
+ uri = "http://" + host + _url;
+ }
if ((err = _uri->initialize(uri)) != srs_success) {
- return srs_error_wrap(err, "init uri");
+ return srs_error_wrap(err, "init uri %s", uri.c_str());
}
// parse ext.
@@ -349,13 +362,13 @@ uint8_t SrsHttpMessage::method()
return SRS_CONSTS_HTTP_DELETE;
}
}
-
- return (uint8_t)_header.method;
+
+ return _method;
}
uint16_t SrsHttpMessage::status_code()
{
- return (uint16_t)_header.status_code;
+ return _status;
}
string SrsHttpMessage::method_str()
@@ -405,7 +418,7 @@ bool SrsHttpMessage::is_http_delete()
bool SrsHttpMessage::is_http_options()
{
- return _header.method == SRS_CONSTS_HTTP_OPTIONS;
+ return _method == SRS_CONSTS_HTTP_OPTIONS;
}
bool SrsHttpMessage::is_chunked()
@@ -415,7 +428,7 @@ bool SrsHttpMessage::is_chunked()
bool SrsHttpMessage::is_keep_alive()
{
- return keep_alive;
+ return _keep_alive;
}
bool SrsHttpMessage::is_infinite_chunked()
@@ -447,6 +460,11 @@ string SrsHttpMessage::host()
return _uri->get_host();
}
+int SrsHttpMessage::port()
+{
+ return _uri->get_port();
+}
+
string SrsHttpMessage::path()
{
return _uri->get_path();
@@ -524,7 +542,7 @@ ISrsHttpResponseReader* SrsHttpMessage::body_reader()
int64_t SrsHttpMessage::content_length()
{
- return _header.content_length;
+ return _content_length;
}
string SrsHttpMessage::query_get(string key)
@@ -538,39 +556,9 @@ string SrsHttpMessage::query_get(string key)
return v;
}
-int SrsHttpMessage::request_header_count()
+SrsHttpHeader* SrsHttpMessage::header()
{
- return (int)_headers.size();
-}
-
-string SrsHttpMessage::request_header_key_at(int index)
-{
- srs_assert(index < request_header_count());
- SrsHttpHeaderField item = _headers[index];
- return item.first;
-}
-
-string SrsHttpMessage::request_header_value_at(int index)
-{
- srs_assert(index < request_header_count());
- SrsHttpHeaderField item = _headers[index];
- return item.second;
-}
-
-string SrsHttpMessage::get_request_header(string name)
-{
- std::vector::iterator it;
-
- for (it = _headers.begin(); it != _headers.end(); ++it) {
- SrsHttpHeaderField& elem = *it;
- std::string key = elem.first;
- std::string value = elem.second;
- if (key == name) {
- return value;
- }
- }
-
- return "";
+ return &_header;
}
SrsRequest* SrsHttpMessage::to_request(string vhost)
@@ -590,7 +578,7 @@ SrsRequest* SrsHttpMessage::to_request(string vhost)
// generate others.
req->tcUrl = "rtmp://" + vhost + "/" + req->app;
- req->pageUrl = get_request_header("Referer");
+ req->pageUrl = _header.get("Referer");
req->objectEncoding = 0;
std::string query = _uri->get_query();
@@ -614,7 +602,15 @@ bool SrsHttpMessage::is_jsonp()
return jsonp;
}
-SrsHttpResponseWriter::SrsHttpResponseWriter(SrsStSocket* io)
+ISrsHttpHeaderFilter::ISrsHttpHeaderFilter()
+{
+}
+
+ISrsHttpHeaderFilter::~ISrsHttpHeaderFilter()
+{
+}
+
+SrsHttpResponseWriter::SrsHttpResponseWriter(ISrsProtocolReadWriter* io)
{
skt = io;
hdr = new SrsHttpHeader();
@@ -625,6 +621,7 @@ SrsHttpResponseWriter::SrsHttpResponseWriter(SrsStSocket* io)
header_sent = false;
nb_iovss_cache = 0;
iovss_cache = NULL;
+ hf = NULL;
}
SrsHttpResponseWriter::~SrsHttpResponseWriter()
@@ -635,10 +632,17 @@ SrsHttpResponseWriter::~SrsHttpResponseWriter()
srs_error_t SrsHttpResponseWriter::final_request()
{
+ srs_error_t err = srs_success;
+
// write the header data in memory.
if (!header_wrote) {
write_header(SRS_CONSTS_HTTP_OK);
}
+
+ // whatever header is wrote, we should try to send header.
+ if ((err = send_header(NULL, 0)) != srs_success) {
+ return srs_error_wrap(err, "send header");
+ }
// complete the chunked encoding.
if (content_length == -1) {
@@ -663,6 +667,10 @@ srs_error_t SrsHttpResponseWriter::write(char* data, int size)
// write the header data in memory.
if (!header_wrote) {
+ if (hdr->content_type().empty()) {
+ hdr->set_content_type("text/plain; charset=utf-8");
+ }
+ hdr->set_content_length(size);
write_header(SRS_CONSTS_HTTP_OK);
}
@@ -678,7 +686,7 @@ srs_error_t SrsHttpResponseWriter::write(char* data, int size)
}
// ignore NULL content.
- if (!data) {
+ if (!data || size <= 0) {
return err;
}
@@ -823,7 +831,7 @@ srs_error_t SrsHttpResponseWriter::send_header(char* data, int size)
// detect content type
if (srs_go_http_body_allowd(status)) {
- if (hdr->content_type().empty()) {
+ if (data && hdr->content_type().empty()) {
hdr->set_content_type(srs_go_http_detect(data, size));
}
}
@@ -840,8 +848,13 @@ srs_error_t SrsHttpResponseWriter::send_header(char* data, int size)
// keep alive to make vlc happy.
hdr->set("Connection", "Keep-Alive");
+
+ // Filter the header before writing it.
+ if (hf && ((err = hf->filter(hdr)) != srs_success)) {
+ return srs_error_wrap(err, "filter header");
+ }
- // write headers
+ // write header
hdr->write(ss);
// header_eof
@@ -851,32 +864,20 @@ srs_error_t SrsHttpResponseWriter::send_header(char* data, int size)
return skt->write((void*)buf.c_str(), buf.length(), NULL);
}
-SrsHttpResponseReader::SrsHttpResponseReader(SrsHttpMessage* msg, ISrsReader* reader)
+SrsHttpResponseReader::SrsHttpResponseReader(SrsHttpMessage* msg, ISrsReader* reader, SrsFastStream* body)
{
skt = reader;
owner = msg;
is_eof = false;
nb_total_read = 0;
nb_left_chunk = 0;
- buffer = NULL;
+ buffer = body;
}
SrsHttpResponseReader::~SrsHttpResponseReader()
{
}
-srs_error_t SrsHttpResponseReader::initialize(SrsFastStream* body)
-{
- srs_error_t err = srs_success;
-
- nb_chunk = 0;
- nb_left_chunk = 0;
- nb_total_read = 0;
- buffer = body;
-
- return err;
-}
-
bool SrsHttpResponseReader::eof()
{
return is_eof;
diff --git a/trunk/src/service/srs_service_http_conn.hpp b/trunk/src/service/srs_service_http_conn.hpp
index 3bfdfb4ee..997e26b66 100644
--- a/trunk/src/service/srs_service_http_conn.hpp
+++ b/trunk/src/service/srs_service_http_conn.hpp
@@ -35,7 +35,7 @@ class SrsFastStream;
class SrsRequest;
class ISrsReader;
class SrsHttpResponseReader;
-class SrsStSocket;
+class ISrsProtocolReadWriter;
// A wrapper for http-parser,
// provides HTTP message originted service.
@@ -49,14 +49,12 @@ private:
// Whether allow jsonp parse.
bool jsonp;
private:
- // http parse data, reset before parse message.
- bool expect_field_name;
std::string field_name;
std::string field_value;
SrsHttpParseState state;
- http_parser header;
+ http_parser hp_header;
std::string url;
- std::vector headers;
+ SrsHttpHeader* header;
const char* pbody;
public:
SrsHttpParser();
@@ -85,9 +83,6 @@ private:
static int on_body(http_parser* parser, const char* at, size_t length);
};
-// for http header.
-typedef std::pair SrsHttpHeaderField;
-
// A Request represents an HTTP request received by a server
// or to be sent by a client.
//
@@ -97,42 +92,51 @@ typedef std::pair SrsHttpHeaderField;
class SrsHttpMessage : public ISrsHttpMessage
{
private:
- // The parsed url.
- std::string _url;
- // The extension of file, for example, .flv
- std::string _ext;
- // parsed http header.
- http_parser _header;
// The body object, reader object.
// @remark, user can get body in string by get_body().
SrsHttpResponseReader* _body;
- // Whether the body is chunked.
- bool chunked;
// Whether the body is infinite chunked.
bool infinite_chunked;
+ // Use a buffer to read and send ts file.
+ // The transport connection, can be NULL.
+ SrsConnection* owner_conn;
+private:
+ uint8_t _method;
+ uint16_t _status;
+ int64_t _content_length;
+private:
+ // The http headers
+ SrsHttpHeader _header;
// Whether the request indicates should keep alive for the http connection.
- bool keep_alive;
+ bool _keep_alive;
+ // Whether the body is chunked.
+ bool chunked;
+private:
+ // The parsed url.
+ std::string _url;
+ // The extension of file, for example, .flv
+ std::string _ext;
// The uri parser
SrsHttpUri* _uri;
- // Use a buffer to read and send ts file.
- // TODO: FIXME: remove it.
- char* _http_ts_send_buffer;
- // The http headers
- std::vector _headers;
// The query map
std::map _query;
- // The transport connection, can be NULL.
- SrsConnection* owner_conn;
+private:
// Whether request is jsonp.
bool jsonp;
// The method in QueryString will override the HTTP method.
std::string jsonp_method;
public:
- SrsHttpMessage(ISrsReader* io);
+ SrsHttpMessage(ISrsReader* reader = NULL, SrsFastStream* buffer = NULL);
virtual ~SrsHttpMessage();
public:
+ // Set the basic information for HTTP request.
+ // @remark User must call set_basic before set_header, because the content_length will be overwrite by header.
+ virtual void set_basic(uint8_t method, uint16_t status, int64_t content_length);
+ // Set HTTP header and whether the request require keep alive.
+ // @remark User must call set_header before set_url, because the Host in header is used for url.
+ virtual void set_header(SrsHttpHeader* header, bool keep_alive);
// set the original messages, then update the message.
- virtual srs_error_t update(std::string url, bool allow_jsonp, http_parser* header, SrsFastStream* body, std::vector& headers);
+ virtual srs_error_t set_url(std::string url, bool allow_jsonp);
public:
// Get the owner connection, maybe NULL.
virtual SrsConnection* connection();
@@ -159,6 +163,7 @@ public:
// The url maybe the path.
virtual std::string url();
virtual std::string host();
+ virtual int port();
virtual std::string path();
virtual std::string query();
virtual std::string ext();
@@ -179,10 +184,7 @@ public:
// then query_get("start") is "100", and query_get("end") is "200"
virtual std::string query_get(std::string key);
// Get the headers.
- virtual int request_header_count();
- virtual std::string request_header_key_at(int index);
- virtual std::string request_header_value_at(int index);
- virtual std::string get_request_header(std::string name);
+ virtual SrsHttpHeader* header();
public:
// Convert the http message to a request.
// @remark user must free the return request.
@@ -195,12 +197,25 @@ public:
// for writev, there always one chunk to send it.
#define SRS_HTTP_HEADER_CACHE_SIZE 64
+class ISrsHttpHeaderFilter
+{
+public:
+ ISrsHttpHeaderFilter();
+ virtual ~ISrsHttpHeaderFilter();
+public:
+ // Filter the HTTP header h.
+ virtual srs_error_t filter(SrsHttpHeader* h) = 0;
+};
+
// Response writer use st socket
class SrsHttpResponseWriter : public ISrsHttpResponseWriter
{
private:
- SrsStSocket* skt;
+ ISrsProtocolReadWriter* skt;
SrsHttpHeader* hdr;
+ // Before writing header, there is a chance to filter it,
+ // such as remove some headers or inject new.
+ ISrsHttpHeaderFilter* hf;
private:
char header_cache[SRS_HTTP_HEADER_CACHE_SIZE];
iovec* iovss_cache;
@@ -222,7 +237,7 @@ private:
// logically written.
bool header_sent;
public:
- SrsHttpResponseWriter(SrsStSocket* io);
+ SrsHttpResponseWriter(ISrsProtocolReadWriter* io);
virtual ~SrsHttpResponseWriter();
public:
virtual srs_error_t final_request();
@@ -248,11 +263,10 @@ private:
// Already read total bytes.
int64_t nb_total_read;
public:
- SrsHttpResponseReader(SrsHttpMessage* msg, ISrsReader* reader);
+ // Generally the reader is the under-layer io such as socket,
+ // while buffer is a fast cache which may have cached some data from reader.
+ SrsHttpResponseReader(SrsHttpMessage* msg, ISrsReader* reader, SrsFastStream* buffer);
virtual ~SrsHttpResponseReader();
-public:
- // Initialize the response reader with buffer.
- virtual srs_error_t initialize(SrsFastStream* buffer);
// Interface ISrsHttpResponseReader
public:
virtual bool eof();
diff --git a/trunk/src/utest/srs_utest.hpp b/trunk/src/utest/srs_utest.hpp
index 34ee90a0c..e128d350e 100644
--- a/trunk/src/utest/srs_utest.hpp
+++ b/trunk/src/utest/srs_utest.hpp
@@ -35,8 +35,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "gtest/gtest.h"
#include
+using namespace std;
#include
+#include
// we add an empty macro for upp to show the smart tips.
#define VOID
@@ -61,6 +63,10 @@ extern srs_utime_t _srs_tmp_timeout;
#define HELPER_ARRAY_INIT(buf, sz, val) \
for (int i = 0; i < (int)sz; i++) (buf)[i]=val
+// Dump simple stream to string.
+#define HELPER_BUFFER2STR(io) \
+ string((const char*)(io)->bytes(), (size_t)(io)->length())
+
// the asserts of gtest:
// * {ASSERT|EXPECT}_EQ(expected, actual): Tests that expected == actual
// * {ASSERT|EXPECT}_NE(v1, v2): Tests that v1 != v2
diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp
new file mode 100644
index 000000000..c1d49fcc4
--- /dev/null
+++ b/trunk/src/utest/srs_utest_http.cpp
@@ -0,0 +1,1433 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2013-2019 Winlin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+#include
+
+#include
+using namespace std;
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class MockResponseWriter : virtual public ISrsHttpResponseWriter, virtual public ISrsHttpHeaderFilter
+{
+public:
+ SrsHttpResponseWriter* w;
+ MockBufferIO io;
+public:
+ MockResponseWriter();
+ virtual ~MockResponseWriter();
+public:
+ virtual srs_error_t final_request();
+ virtual SrsHttpHeader* header();
+ virtual srs_error_t write(char* data, int size);
+ virtual srs_error_t writev(const iovec* iov, int iovcnt, ssize_t* pnwrite);
+ virtual void write_header(int code);
+public:
+ virtual srs_error_t filter(SrsHttpHeader* h);
+};
+
+MockResponseWriter::MockResponseWriter()
+{
+ w = new SrsHttpResponseWriter(&io);
+ w->hf = this;
+}
+
+MockResponseWriter::~MockResponseWriter()
+{
+ srs_freep(w);
+}
+
+srs_error_t MockResponseWriter::final_request()
+{
+ return w->final_request();
+}
+
+SrsHttpHeader* MockResponseWriter::header()
+{
+ return w->header();
+}
+
+srs_error_t MockResponseWriter::write(char* data, int size)
+{
+ return w->write(data, size);
+}
+
+srs_error_t MockResponseWriter::writev(const iovec* iov, int iovcnt, ssize_t* pnwrite)
+{
+ return w->writev(iov, iovcnt, pnwrite);
+}
+
+void MockResponseWriter::write_header(int code)
+{
+ w->write_header(code);
+}
+
+srs_error_t MockResponseWriter::filter(SrsHttpHeader* h)
+{
+ h->del("Content-Type");
+ h->del("Server");
+ h->del("Connection");
+ h->del("Location");
+ h->del("Content-Range");
+ h->del("Access-Control-Allow-Origin");
+ h->del("Access-Control-Allow-Methods");
+ h->del("Access-Control-Expose-Headers");
+ h->del("Access-Control-Allow-Headers");
+ return srs_success;
+}
+
+string mock_http_response(int status, string content)
+{
+ stringstream ss;
+ ss << "HTTP/1.1 " << status << " " << srs_generate_http_status_text(status) << "\r\n"
+ << "Content-Length: " << content.length() << "\r\n"
+ << "\r\n"
+ << content;
+ return ss.str();
+}
+
+string mock_http_response2(int status, string content)
+{
+ stringstream ss;
+ ss << "HTTP/1.1 " << status << " " << srs_generate_http_status_text(status) << "\r\n"
+ << "Transfer-Encoding: chunked" << "\r\n"
+ << "\r\n"
+ << content;
+ return ss.str();
+}
+
+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());
+ }
+};
+
+class MockHttpHandler : virtual public ISrsHttpHandler, virtual public ISrsHttpMatchHijacker
+{
+public:
+ string bytes;
+ MockHttpHandler(string data) {
+ bytes = data;
+ }
+ virtual ~MockHttpHandler() {
+ }
+ virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* /*r*/) {
+ return w->write((char*)bytes.data(), (int)bytes.length());
+ }
+ virtual srs_error_t hijack(ISrsHttpMessage* /*r*/, ISrsHttpHandler** ph) {
+ if (ph) {
+ *ph = this;
+ }
+ return srs_success;
+ }
+};
+
+bool _mock_srs_path_always_exists(std::string /*path*/)
+{
+ return true;
+}
+
+bool _mock_srs_path_not_exists(std::string /*path*/)
+{
+ return false;
+}
+
+#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())
+
+#define __MOCK_HTTP_EXPECT_STREQ2(status, text, w) \
+ EXPECT_STREQ(mock_http_response2(status, text).c_str(), HELPER_BUFFER2STR(&w.io.out_buffer).c_str())
+
+VOID TEST(ProtocolHTTPTest, StatusCode2Text)
+{
+ EXPECT_STREQ(SRS_CONSTS_HTTP_OK_str, srs_generate_http_status_text(SRS_CONSTS_HTTP_OK).c_str());
+ EXPECT_STREQ("Status Unknown", srs_generate_http_status_text(999).c_str());
+
+ EXPECT_FALSE(srs_go_http_body_allowd(SRS_CONSTS_HTTP_Continue));
+ EXPECT_FALSE(srs_go_http_body_allowd(SRS_CONSTS_HTTP_OK-1));
+ EXPECT_FALSE(srs_go_http_body_allowd(SRS_CONSTS_HTTP_NoContent));
+ EXPECT_FALSE(srs_go_http_body_allowd(SRS_CONSTS_HTTP_NotModified));
+ EXPECT_TRUE(srs_go_http_body_allowd(SRS_CONSTS_HTTP_OK));
+}
+
+VOID TEST(ProtocolHTTPTest, ResponseDetect)
+{
+ EXPECT_STREQ("application/octet-stream", srs_go_http_detect(NULL, 0).c_str());
+ EXPECT_STREQ("application/octet-stream", srs_go_http_detect((char*)"Hello, world!", 0).c_str());
+}
+
+VOID TEST(ProtocolHTTPTest, ResponseWriter)
+{
+ // If directly write string, response with content-length.
+ if (true) {
+ MockResponseWriter w;
+
+ char msg[] = "Hello, world!";
+ w.write((char*)msg, sizeof(msg) - 1);
+
+ __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w);
+ }
+
+ // Response with specified length string, response with content-length.
+ if (true) {
+ MockResponseWriter w;
+
+ char msg[] = "Hello, world!";
+
+ w.header()->set_content_type("text/plain; charset=utf-8");
+ w.header()->set_content_length(sizeof(msg) - 1);
+ w.write_header(SRS_CONSTS_HTTP_OK);
+ w.write((char*)msg, sizeof(msg) - 1);
+ w.final_request();
+
+ __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w);
+ }
+
+ // If set content-length to 0 then final_request, send an empty resonse with content-length 0.
+ if (true) {
+ MockResponseWriter w;
+
+ w.header()->set_content_length(0);
+ w.write_header(SRS_CONSTS_HTTP_OK);
+ w.final_request();
+
+ __MOCK_HTTP_EXPECT_STREQ(200, "", w);
+ }
+
+ // If set content-length to 0 then write, send an empty resonse with content-length 0.
+ if (true) {
+ MockResponseWriter w;
+
+ w.header()->set_content_length(0);
+ w.write_header(SRS_CONSTS_HTTP_OK);
+ w.write(NULL, 0);
+
+ __MOCK_HTTP_EXPECT_STREQ(200, "", w);
+ }
+
+ // If write_header without content-length, enter chunked encoding mode.
+ if (true) {
+ MockResponseWriter w;
+
+ w.header()->set_content_type("application/octet-stream");
+ w.write_header(SRS_CONSTS_HTTP_OK);
+ w.write((char*)"Hello", 5);
+ w.write((char*)", world!", 8);
+ w.final_request();
+
+ __MOCK_HTTP_EXPECT_STREQ2(200, "5\r\nHello\r\n8\r\n, world!\r\n0\r\n\r\n", w);
+ }
+
+ // If directly write empty string, sent an empty response with content-length 0
+ if (true) {
+ MockResponseWriter w;
+ w.write(NULL, 0);
+ __MOCK_HTTP_EXPECT_STREQ(200, "", w);
+ }
+
+ // If directly final request, response with EOF of chunked.
+ if (true) {
+ MockResponseWriter w;
+ w.final_request();
+ __MOCK_HTTP_EXPECT_STREQ2(200, "0\r\n\r\n", w);
+ }
+}
+
+VOID TEST(ProtocolHTTPTest, ResponseHTTPError)
+{
+ srs_error_t err;
+
+ if (true) {
+ MockResponseWriter w;
+ HELPER_EXPECT_SUCCESS(srs_go_http_error(&w, SRS_CONSTS_HTTP_Found));
+ __MOCK_HTTP_EXPECT_STREQ(302, "Found", w);
+ }
+
+ if (true) {
+ MockResponseWriter w;
+ HELPER_EXPECT_SUCCESS(srs_go_http_error(&w, SRS_CONSTS_HTTP_InternalServerError));
+ __MOCK_HTTP_EXPECT_STREQ(500, "Internal Server Error", w);
+ }
+
+ if (true) {
+ MockResponseWriter w;
+ HELPER_EXPECT_SUCCESS(srs_go_http_error(&w, SRS_CONSTS_HTTP_ServiceUnavailable));
+ __MOCK_HTTP_EXPECT_STREQ(503, "Service Unavailable", w);
+ }
+}
+
+VOID TEST(ProtocolHTTPTest, HTTPHeader)
+{
+ SrsHttpHeader h;
+ h.set("Server", "SRS");
+ EXPECT_STREQ("SRS", h.get("Server").c_str());
+ EXPECT_EQ(1, h.count());
+
+ stringstream ss;
+ h.write(ss);
+ EXPECT_STREQ("Server: SRS\r\n", ss.str().c_str());
+
+ h.del("Server");
+ EXPECT_TRUE(h.get("Server").empty());
+ EXPECT_EQ(0, h.count());
+
+ EXPECT_EQ(-1, h.content_length());
+
+ h.set_content_length(0);
+ EXPECT_EQ(0, h.content_length());
+ EXPECT_EQ(1, h.count());
+
+ h.set_content_length(1024);
+ EXPECT_EQ(1024, h.content_length());
+
+ h.set_content_type("text/plain");
+ EXPECT_STREQ("text/plain", h.content_type().c_str());
+ EXPECT_EQ(2, h.count());
+
+ SrsJsonObject* o = SrsJsonAny::object();
+ h.dumps(o);
+ EXPECT_EQ(2, o->count());
+ srs_freep(o);
+}
+
+VOID TEST(ProtocolHTTPTest, HTTPServerMuxerVhost)
+{
+ srs_error_t err;
+
+ // For vhost.
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* h0 = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/", h0));
+
+ MockHttpHandler* h1 = new MockHttpHandler("Done");
+ HELPER_ASSERT_SUCCESS(s.handle("ossrs.net/api/", h1));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+
+ SrsHttpHeader h;
+ h.set("Host", "ossrs.net");
+ r.set_header(&h, false);
+
+ HELPER_ASSERT_SUCCESS(r.set_url("/api/", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Done", w);
+ }
+
+ // For vhost.
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* h0 = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/api/", h0));
+
+ MockHttpHandler* h1 = new MockHttpHandler("Done");
+ HELPER_ASSERT_SUCCESS(s.handle("ossrs.net/api/", h1));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+
+ SrsHttpHeader h;
+ h.set("Host", "ossrs.net");
+ r.set_header(&h, false);
+
+ HELPER_ASSERT_SUCCESS(r.set_url("/api/", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Done", w);
+ }
+}
+
+VOID TEST(ProtocolHTTPTest, HTTPServerMuxerImplicitHandler)
+{
+ srs_error_t err;
+
+ // Implicit handler.
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* h1 = new MockHttpHandler("Done");
+ HELPER_ASSERT_SUCCESS(s.handle("/api", h1));
+
+ MockHttpHandler* h0 = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/api/", h0));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/api", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Done", w);
+ }
+
+ // Fail if explicit handler exists.
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* h0 = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/api/", h0));
+
+ MockHttpHandler* h1 = new MockHttpHandler("Done");
+ HELPER_EXPECT_FAILED(s.handle("/api/", h1));
+ }
+
+ // Explicit handler will overwrite the implicit handler.
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* h0 = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/api/", h0));
+
+ MockHttpHandler* h1 = new MockHttpHandler("Done");
+ HELPER_ASSERT_SUCCESS(s.handle("/api", h1));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/api", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Done", w);
+ }
+
+ // Implicit handler.
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* h0 = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/api/", h0));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/api", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(302, "Redirect to /api/", w);
+ }
+}
+
+VOID TEST(ProtocolHTTPTest, HTTPServerMuxerHijack)
+{
+ srs_error_t err;
+
+ // For hijacker, notify all.
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler h1("Done");
+ s.hijack(&h1);
+
+ MockHttpHandler h0("Hello, world!");
+ s.hijack(&h0);
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/home/index.html", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w);
+ }
+
+ // For hijacker, notify all.
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler h0("Hello, world!");
+ s.hijack(&h0);
+
+ MockHttpHandler h1("Done");
+ s.hijack(&h1);
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/home/index.html", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Done", w);
+ }
+
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler hroot("Hello, world!");
+ s.hijack(&hroot);
+ s.unhijack(&hroot);
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(404, "Not Found", w);
+ }
+
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler hroot("Hello, world!");
+ s.hijack(&hroot);
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w);
+ }
+}
+
+VOID TEST(ProtocolHTTPTest, HTTPServerMuxerBasic)
+{
+ srs_error_t err;
+
+ // Ignore if not enabled.
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* h0 = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/", h0));
+
+ MockHttpHandler* h1 = new MockHttpHandler("Done");
+ HELPER_ASSERT_SUCCESS(s.handle("/api/", h1));
+
+ h1->entry->enabled = false;
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/api/index.html", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w);
+ }
+
+ // Empty pattern, failed.
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* h0 = new MockHttpHandler("Hello, world!");
+ HELPER_EXPECT_FAILED(s.handle("", h0));
+ }
+
+ // If not endswith '/', exactly match.
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* h0 = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/", h0));
+
+ MockHttpHandler* h1 = new MockHttpHandler("Done");
+ HELPER_ASSERT_SUCCESS(s.handle("/home", h1));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/home/index.html", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w);
+ }
+
+ // If endswith '/', match any.
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* h0 = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/", h0));
+
+ MockHttpHandler* h1 = new MockHttpHandler("Done");
+ HELPER_ASSERT_SUCCESS(s.handle("/home/", h1));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/home/index.html", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Done", w);
+ }
+
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* h0 = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/", h0));
+
+ MockHttpHandler* h1 = new MockHttpHandler("Done");
+ HELPER_ASSERT_SUCCESS(s.handle("/api/v1", h1));
+
+ MockHttpHandler* h2 = new MockHttpHandler("Another one");
+ HELPER_ASSERT_SUCCESS(s.handle("/apis/", h2));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/apis/v1/echo", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Another one", w);
+ }
+
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* h0 = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/", h0));
+
+ MockHttpHandler* h1 = new MockHttpHandler("Done");
+ HELPER_ASSERT_SUCCESS(s.handle("/api/v1/", h1));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/apis/v1/echo", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w);
+ }
+
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* h0 = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/api/", h0));
+
+ MockHttpHandler* h1 = new MockHttpHandler("Done");
+ HELPER_ASSERT_SUCCESS(s.handle("/api/v1/", h1));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/api/v1/echo", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Done", w);
+ }
+
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* h0 = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/api/", h0));
+
+ MockHttpHandler* h1 = new MockHttpHandler("Done");
+ HELPER_ASSERT_SUCCESS(s.handle("/apis/", h1));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/apis/v1/echo", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Done", w);
+ }
+
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* h0 = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/", h0));
+
+ MockHttpHandler* h1 = new MockHttpHandler("Done");
+ HELPER_ASSERT_SUCCESS(s.handle("/index.html", h1));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Done", w);
+ }
+
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* hroot = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/", hroot));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false));
+
+ HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w);
+ }
+}
+
+VOID TEST(ProtocolHTTPTest, HTTPServerMuxerCORS)
+{
+ srs_error_t err;
+
+ // If CORS enabled, response others with ok except OPTIONS
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* hroot = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/", hroot));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ r.set_basic(HTTP_POST, 200, -1);
+ HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false));
+
+ SrsHttpCorsMux cs;
+ HELPER_ASSERT_SUCCESS(cs.initialize(&s, true));
+
+ HELPER_ASSERT_SUCCESS(cs.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w);
+ }
+
+ // If CORS enabled, response OPTIONS with ok
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* hroot = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/", hroot));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ r.set_basic(HTTP_OPTIONS, 200, -1);
+ HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false));
+
+ SrsHttpCorsMux cs;
+ HELPER_ASSERT_SUCCESS(cs.initialize(&s, true));
+
+ HELPER_ASSERT_SUCCESS(cs.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "", w);
+ }
+
+ // If CORS not enabled, response content except OPTIONS.
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* hroot = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/", hroot));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ r.set_basic(HTTP_POST, 200, -1);
+ HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false));
+
+ SrsHttpCorsMux cs;
+ HELPER_ASSERT_SUCCESS(cs.initialize(&s, false));
+
+ HELPER_ASSERT_SUCCESS(cs.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w);
+ }
+
+ // If CORS not enabled, response error for options.
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* hroot = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/", hroot));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ r.set_basic(HTTP_OPTIONS, 200, -1);
+ HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false));
+
+ SrsHttpCorsMux cs;
+ HELPER_ASSERT_SUCCESS(cs.initialize(&s, false));
+
+ HELPER_ASSERT_SUCCESS(cs.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(405, "", w);
+ }
+
+ if (true) {
+ SrsHttpServeMux s;
+ HELPER_ASSERT_SUCCESS(s.initialize());
+
+ MockHttpHandler* hroot = new MockHttpHandler("Hello, world!");
+ HELPER_ASSERT_SUCCESS(s.handle("/", hroot));
+
+ MockResponseWriter w;
+ SrsHttpMessage r(NULL, NULL);
+ HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false));
+
+ SrsHttpCorsMux cs;
+ HELPER_ASSERT_SUCCESS(cs.initialize(&s, true));
+
+ HELPER_ASSERT_SUCCESS(cs.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w);
+ }
+}
+
+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);
+ }
+
+ // Invalid format of range, use static file handler.
+ 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", false));
+
+ HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w);
+ }
+
+ // No range, use static file handler.
+ 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", false));
+
+ HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r));
+ __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w);
+ }
+}
+
+VOID TEST(ProtocolHTTPTest, BasicHandlers)
+{
+ srs_error_t err;
+
+ 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.mp4?range=0", 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.mp4", 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.mp4?range=12-3", 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.mp4?range=2-3", 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?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;
+ 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) {
+ 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);
+ EXPECT_FALSE(h.is_not_found());
+
+ 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);
+ EXPECT_TRUE(h.is_not_found());
+ }
+}
+
+class MockMSegmentsReader : public ISrsReader
+{
+public:
+ vector in_bytes;
+public:
+ MockMSegmentsReader();
+ virtual ~MockMSegmentsReader();
+public:
+ virtual srs_error_t read(void* buf, size_t size, ssize_t* nread);
+};
+
+MockMSegmentsReader::MockMSegmentsReader()
+{
+}
+
+MockMSegmentsReader::~MockMSegmentsReader()
+{
+}
+
+srs_error_t MockMSegmentsReader::read(void* buf, size_t size, ssize_t* nread)
+{
+ srs_error_t err = srs_success;
+
+ for (;;) {
+ if (in_bytes.empty() || size <= 0) {
+ return srs_error_new(-1, "EOF");
+ }
+
+ string v = in_bytes[0];
+ if (v.empty()) {
+ in_bytes.erase(in_bytes.begin());
+ continue;
+ }
+
+ int nn = srs_min(size, v.length());
+ memcpy(buf, v.data(), nn);
+ if (nread) {
+ *nread = nn;
+ }
+
+ if (nn < (int)v.length()) {
+ in_bytes[0] = string(v.data() + nn, v.length() - nn);
+ } else {
+ in_bytes.erase(in_bytes.begin());
+ }
+ break;
+ }
+
+ return err;
+}
+
+VOID TEST(ProtocolHTTPTest, MSegmentsReader)
+{
+ srs_error_t err;
+
+ MockMSegmentsReader r;
+ r.in_bytes.push_back("GET /api/v1/versions HTTP/1.1\r\n");
+ r.in_bytes.push_back("Host: ossrs.net\r\n");
+
+ if (true) {
+ char buf[1024];
+ HELPER_ARRAY_INIT(buf, 1024, 0);
+
+ ssize_t nn = 0;
+ HELPER_EXPECT_SUCCESS(r.read(buf, 1024, &nn));
+ ASSERT_EQ(31, nn);
+ EXPECT_STREQ("GET /api/v1/versions HTTP/1.1\r\n", buf);
+ }
+
+ if (true) {
+ char buf[1024];
+ HELPER_ARRAY_INIT(buf, 1024, 0);
+
+ ssize_t nn = 0;
+ HELPER_EXPECT_SUCCESS(r.read(buf, 1024, &nn));
+ ASSERT_EQ(17, nn);
+ EXPECT_STREQ("Host: ossrs.net\r\n", buf);
+ }
+}
+
+VOID TEST(ProtocolHTTPTest, HTTPMessageParser)
+{
+ srs_error_t err;
+
+ if (true) {
+ MockMSegmentsReader r;
+ r.in_bytes.push_back("GET /api/v1/versions HTTP/1.1\r\n");
+ r.in_bytes.push_back("Host: ossrs");
+ r.in_bytes.push_back(".net\r");
+ r.in_bytes.push_back("\n");
+ r.in_bytes.push_back("\r\n");
+
+ SrsHttpParser p;
+ HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST, false));
+
+ ISrsHttpMessage* msg = NULL;
+ HELPER_ASSERT_SUCCESS(p.parse_message(&r, &msg));
+ EXPECT_TRUE(msg->is_http_get());
+ EXPECT_STREQ("/api/v1/versions", msg->path().c_str());
+ EXPECT_STREQ("ossrs.net", msg->host().c_str());
+ srs_freep(msg);
+ }
+
+ if (true) {
+ MockMSegmentsReader r;
+ r.in_bytes.push_back("GET /api/v1/versions HTTP/1.1\r\n");
+ r.in_bytes.push_back("Host: ossrs");
+ r.in_bytes.push_back(".net\r\n");
+ r.in_bytes.push_back("\r\n");
+
+ SrsHttpParser p;
+ HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST, false));
+
+ ISrsHttpMessage* msg = NULL;
+ HELPER_ASSERT_SUCCESS(p.parse_message(&r, &msg));
+ EXPECT_TRUE(msg->is_http_get());
+ EXPECT_STREQ("/api/v1/versions", msg->path().c_str());
+ EXPECT_STREQ("ossrs.net", msg->host().c_str());
+ srs_freep(msg);
+ }
+
+ if (true) {
+ MockMSegmentsReader r;
+ r.in_bytes.push_back("GET /api/v1/versions HTTP/1.1\r\n");
+ r.in_bytes.push_back("User-Agent: curl/7.54.0\r\n");
+ r.in_bytes.push_back("Host: ossrs");
+ r.in_bytes.push_back(".net\r\n");
+ r.in_bytes.push_back("\r\n");
+
+ SrsHttpParser p;
+ HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST, false));
+
+ ISrsHttpMessage* msg = NULL;
+ HELPER_ASSERT_SUCCESS(p.parse_message(&r, &msg));
+ EXPECT_TRUE(msg->is_http_get());
+ EXPECT_STREQ("/api/v1/versions", msg->path().c_str());
+ EXPECT_STREQ("ossrs.net", msg->host().c_str());
+ EXPECT_STREQ("curl/7.54.0", msg->header()->get("User-Agent").c_str());
+ srs_freep(msg);
+ }
+
+ if (true) {
+ MockMSegmentsReader r;
+ r.in_bytes.push_back("GET /api/v1/versions HTTP/1.1\r\n");
+ r.in_bytes.push_back("User-");
+ r.in_bytes.push_back("Agent: curl/7.54.0\r\n");
+ r.in_bytes.push_back("Host: ossrs");
+ r.in_bytes.push_back(".net\r\n");
+ r.in_bytes.push_back("\r\n");
+
+ SrsHttpParser p;
+ HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST, false));
+
+ ISrsHttpMessage* msg = NULL;
+ HELPER_ASSERT_SUCCESS(p.parse_message(&r, &msg));
+ EXPECT_TRUE(msg->is_http_get());
+ EXPECT_STREQ("/api/v1/versions", msg->path().c_str());
+ EXPECT_STREQ("ossrs.net", msg->host().c_str());
+ EXPECT_STREQ("curl/7.54.0", msg->header()->get("User-Agent").c_str());
+ srs_freep(msg);
+ }
+
+ if (true) {
+ MockMSegmentsReader r;
+ r.in_bytes.push_back("GET /api/v1/versions HTTP/1.1\r\n");
+ r.in_bytes.push_back("User-");
+ r.in_bytes.push_back("Agent: curl");
+ r.in_bytes.push_back("/7.54.0\r\n");
+ r.in_bytes.push_back("Host: ossrs");
+ r.in_bytes.push_back(".net\r\n");
+ r.in_bytes.push_back("\r\n");
+
+ SrsHttpParser p;
+ HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST, false));
+
+ ISrsHttpMessage* msg = NULL;
+ HELPER_ASSERT_SUCCESS(p.parse_message(&r, &msg));
+ EXPECT_TRUE(msg->is_http_get());
+ EXPECT_STREQ("/api/v1/versions", msg->path().c_str());
+ EXPECT_STREQ("ossrs.net", msg->host().c_str());
+ EXPECT_STREQ("curl/7.54.0", msg->header()->get("User-Agent").c_str());
+ srs_freep(msg);
+ }
+
+ if (true) {
+ MockMSegmentsReader r;
+ r.in_bytes.push_back("GET /api/v1/versions HTTP/1.1\r\n");
+ r.in_bytes.push_back("Host: ossrs.net\r\n");
+ r.in_bytes.push_back("\r\n");
+
+ SrsHttpParser p;
+ HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST, false));
+
+ ISrsHttpMessage* msg = NULL;
+ HELPER_ASSERT_SUCCESS(p.parse_message(&r, &msg));
+ EXPECT_TRUE(msg->is_http_get());
+ EXPECT_STREQ("/api/v1/versions", msg->path().c_str());
+ EXPECT_STREQ("ossrs.net", msg->host().c_str());
+ srs_freep(msg);
+ }
+}
+
+VOID TEST(ProtocolHTTPTest, HTTPMessageUpdate)
+{
+ srs_error_t err;
+
+ if (true) {
+ SrsHttpHeader h;
+ h.set("Host", "ossrs.net:-1");
+
+ SrsHttpMessage m;
+ m.set_header(&h, false);
+ HELPER_EXPECT_FAILED(m.set_url("/api/v1", false));
+ }
+
+ // Port use 80 if error.
+ if (true) {
+ SrsHttpHeader h;
+ h.set("Host", "ossrs.net:0");
+
+ SrsHttpMessage m;
+ m.set_header(&h, false);
+ HELPER_ASSERT_SUCCESS(m.set_url("/api/v1", false));
+ EXPECT_EQ(80, m.port());
+ }
+
+ // Port default to 80.
+ if (true) {
+ SrsHttpHeader h;
+ h.set("Host", "ossrs.net");
+
+ SrsHttpMessage m;
+ m.set_header(&h, false);
+ HELPER_ASSERT_SUCCESS(m.set_url("/api/v1", false));
+ EXPECT_EQ(80, m.port());
+ }
+
+ // For port not 80.
+ if (true) {
+ SrsHttpHeader h;
+ h.set("Host", "ossrs.net:8080");
+
+ SrsHttpMessage m;
+ m.set_header(&h, false);
+ HELPER_ASSERT_SUCCESS(m.set_url("/api/v1", false));
+ EXPECT_EQ(8080, m.port());
+ }
+
+ // The host is overwrite by header.
+ if (true) {
+ SrsHttpHeader h;
+ h.set("Host", "ossrs.net");
+
+ SrsHttpMessage m;
+ HELPER_ASSERT_SUCCESS(m.set_url("/api/v1", false));
+ m.set_header(&h, false);
+ EXPECT_STRNE("ossrs.net", m.host().c_str());
+ }
+
+ // The host is overwrite by header.
+ if (true) {
+ SrsHttpHeader h;
+ h.set("Host", "ossrs.net");
+
+ SrsHttpMessage m;
+ m.set_header(&h, false);
+ HELPER_ASSERT_SUCCESS(m.set_url("/api/v1", false));
+ EXPECT_STREQ("ossrs.net", m.host().c_str());
+ }
+
+ // The content-length is overwrite by header.
+ if (true) {
+ SrsHttpHeader h;
+ h.set("Content-Length", "10");
+
+ SrsHttpMessage m;
+ m.set_header(&h, false);
+ m.set_basic(HTTP_POST, 200, 2);
+ EXPECT_EQ(10, m.content_length());
+ }
+
+ // The content-length is overwrite by header.
+ if (true) {
+ SrsHttpHeader h;
+ h.set("Content-Length", "10");
+
+ SrsHttpMessage m;
+ m.set_basic(HTTP_POST, 200, 2);
+ m.set_header(&h, false);
+ EXPECT_EQ(10, m.content_length());
+ }
+
+ if (true) {
+ SrsHttpHeader h;
+ h.set("Content-Length", "abc");
+
+ SrsHttpMessage m;
+ m.set_header(&h, false);
+ EXPECT_EQ(0, m.content_length());
+ }
+
+ if (true) {
+ SrsHttpHeader h;
+ h.set("Content-Length", "10");
+
+ SrsHttpMessage m;
+ m.set_header(&h, false);
+ EXPECT_EQ(10, m.content_length());
+ }
+
+ if (true) {
+ SrsHttpHeader h;
+ h.set("Content-Length", "0");
+
+ SrsHttpMessage m;
+ m.set_header(&h, false);
+ EXPECT_EQ(0, m.content_length());
+ }
+
+ if (true) {
+ SrsHttpMessage m;
+ m.set_basic(HTTP_POST, 200, 0);
+ EXPECT_EQ(0, m.content_length());
+ }
+
+ if (true) {
+ SrsHttpMessage m;
+ EXPECT_EQ(-1, m.content_length());
+ }
+}
diff --git a/trunk/src/utest/srs_utest_protostack.hpp b/trunk/src/utest/srs_utest_http.hpp
similarity index 80%
rename from trunk/src/utest/srs_utest_protostack.hpp
rename to trunk/src/utest/srs_utest_http.hpp
index 9f222d46a..ac93867d5 100644
--- a/trunk/src/utest/srs_utest_protostack.hpp
+++ b/trunk/src/utest/srs_utest_http.hpp
@@ -25,21 +25,11 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#define SRS_UTEST_PROTO_STACK_HPP
/*
-#include
+#include
*/
#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-
-using namespace _srs_internal;
-
-#include
+#include
#endif
diff --git a/trunk/src/utest/srs_utest_kernel.cpp b/trunk/src/utest/srs_utest_kernel.cpp
index 7f0479ddb..fcd39dd6f 100644
--- a/trunk/src/utest/srs_utest_kernel.cpp
+++ b/trunk/src/utest/srs_utest_kernel.cpp
@@ -2318,6 +2318,14 @@ VOID TEST(KernelUtility, StringUtils)
EXPECT_TRUE(srs_string_contains("srs", "s", "sr"));
EXPECT_TRUE(srs_string_contains("srs", "s", "sr", "srs"));
}
+
+ if (true) {
+ EXPECT_EQ(0, srs_string_count("srs", "y"));
+ EXPECT_EQ(0, srs_string_count("srs", ""));
+ EXPECT_EQ(1, srs_string_count("srs", "r"));
+ EXPECT_EQ(2, srs_string_count("srs", "s"));
+ EXPECT_EQ(3, srs_string_count("srs", "sr"));
+ }
if (true) {
vector flags;
@@ -2633,9 +2641,9 @@ VOID TEST(KernelUtility, RTMPUtils2)
VOID TEST(KernelErrorTest, CoverAll)
{
if (true) {
- EXPECT_TRUE(srs_is_system_control_error(ERROR_CONTROL_RTMP_CLOSE));
- EXPECT_TRUE(srs_is_system_control_error(ERROR_CONTROL_REPUBLISH));
- EXPECT_TRUE(srs_is_system_control_error(ERROR_CONTROL_REDIRECT));
+ EXPECT_TRUE(srs_is_system_control_error(srs_error_new(ERROR_CONTROL_RTMP_CLOSE, "err")));
+ EXPECT_TRUE(srs_is_system_control_error(srs_error_new(ERROR_CONTROL_REPUBLISH, "err")));
+ EXPECT_TRUE(srs_is_system_control_error(srs_error_new(ERROR_CONTROL_REDIRECT, "err")));
}
if (true) {
@@ -2645,9 +2653,9 @@ VOID TEST(KernelErrorTest, CoverAll)
}
if (true) {
- EXPECT_TRUE(srs_is_client_gracefully_close(ERROR_SOCKET_READ));
- EXPECT_TRUE(srs_is_client_gracefully_close(ERROR_SOCKET_READ_FULLY));
- EXPECT_TRUE(srs_is_client_gracefully_close(ERROR_SOCKET_WRITE));
+ EXPECT_TRUE(srs_is_client_gracefully_close(srs_error_new(ERROR_SOCKET_READ, "err")));
+ EXPECT_TRUE(srs_is_client_gracefully_close(srs_error_new(ERROR_SOCKET_READ_FULLY, "err")));
+ EXPECT_TRUE(srs_is_client_gracefully_close(srs_error_new(ERROR_SOCKET_WRITE, "err")));
}
if (true) {
diff --git a/trunk/src/utest/srs_utest_protostack.cpp b/trunk/src/utest/srs_utest_rtmp.cpp
similarity index 98%
rename from trunk/src/utest/srs_utest_protostack.cpp
rename to trunk/src/utest/srs_utest_rtmp.cpp
index 8ea1b0443..d06a040f7 100644
--- a/trunk/src/utest/srs_utest_protostack.cpp
+++ b/trunk/src/utest/srs_utest_rtmp.cpp
@@ -20,9 +20,7 @@ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-#include
-
-#include
+#include
#include
#include
@@ -75,7 +73,7 @@ public:
}
};
-VOID TEST(ProtoStackTest, PacketEncode)
+VOID TEST(ProtocolRTMPTest, PacketEncode)
{
srs_error_t err;
@@ -112,7 +110,7 @@ VOID TEST(ProtoStackTest, PacketEncode)
}
}
-VOID TEST(ProtoStackTest, ManualFlush)
+VOID TEST(ProtocolRTMPTest, ManualFlush)
{
srs_error_t err;
@@ -222,7 +220,7 @@ VOID TEST(ProtoStackTest, ManualFlush)
}
}
-VOID TEST(ProtoStackTest, SendPacketsError)
+VOID TEST(ProtocolRTMPTest, SendPacketsError)
{
srs_error_t err;
@@ -303,7 +301,7 @@ VOID TEST(ProtoStackTest, SendPacketsError)
}
}
-VOID TEST(ProtoStackTest, SendHugePacket)
+VOID TEST(ProtocolRTMPTest, SendHugePacket)
{
srs_error_t err;
@@ -318,7 +316,7 @@ VOID TEST(ProtoStackTest, SendHugePacket)
}
}
-VOID TEST(ProtoStackTest, SendZeroMessages)
+VOID TEST(ProtocolRTMPTest, SendZeroMessages)
{
srs_error_t err;
if (true) {
@@ -345,7 +343,7 @@ VOID TEST(ProtoStackTest, SendZeroMessages)
}
}
-VOID TEST(ProtoStackTest, HugeMessages)
+VOID TEST(ProtocolRTMPTest, HugeMessages)
{
srs_error_t err;
if (true) {
@@ -409,7 +407,7 @@ VOID TEST(ProtoStackTest, HugeMessages)
}
}
-VOID TEST(ProtoStackTest, DecodeMessages)
+VOID TEST(ProtocolRTMPTest, DecodeMessages)
{
srs_error_t err;
@@ -428,7 +426,7 @@ VOID TEST(ProtoStackTest, DecodeMessages)
}
}
-VOID TEST(ProtoStackTest, OnDecodeMessages)
+VOID TEST(ProtocolRTMPTest, OnDecodeMessages)
{
srs_error_t err;
@@ -469,7 +467,7 @@ SrsCommonMessage* _create_amf0(char* bytes, int size, int stream_id)
return msg;
}
-VOID TEST(ProtoStackTest, OnDecodeMessages2)
+VOID TEST(ProtocolRTMPTest, OnDecodeMessages2)
{
srs_error_t err;
@@ -536,7 +534,7 @@ VOID TEST(ProtoStackTest, OnDecodeMessages2)
}
}
-VOID TEST(ProtoStackTest, OnDecodeMessages3)
+VOID TEST(ProtocolRTMPTest, OnDecodeMessages3)
{
srs_error_t err;
@@ -705,7 +703,7 @@ VOID TEST(ProtoStackTest, OnDecodeMessages3)
}
}
-VOID TEST(ProtoStackTest, OnDecodeMessages4)
+VOID TEST(ProtocolRTMPTest, OnDecodeMessages4)
{
srs_error_t err;
@@ -1070,7 +1068,7 @@ VOID TEST(ProtoStackTest, OnDecodeMessages4)
}
}
-VOID TEST(ProtoStackTest, RecvMessage)
+VOID TEST(ProtocolRTMPTest, RecvMessage)
{
srs_error_t err;
@@ -1120,7 +1118,7 @@ VOID TEST(ProtoStackTest, RecvMessage)
}
}
-VOID TEST(ProtoStackTest, RecvMessage2)
+VOID TEST(ProtocolRTMPTest, RecvMessage2)
{
srs_error_t err;
@@ -1179,7 +1177,7 @@ VOID TEST(ProtoStackTest, RecvMessage2)
}
}
-VOID TEST(ProtoStackTest, RecvMessage3)
+VOID TEST(ProtocolRTMPTest, RecvMessage3)
{
if (true) {
SrsRequest req;
@@ -1239,7 +1237,7 @@ VOID TEST(ProtoStackTest, RecvMessage3)
}
}
-VOID TEST(ProtoStackTest, RecvMessage4)
+VOID TEST(ProtocolRTMPTest, RecvMessage4)
{
srs_error_t err;
@@ -1279,7 +1277,7 @@ VOID TEST(ProtoStackTest, RecvMessage4)
}
}
-VOID TEST(ProtoStackTest, HandshakeC0C1)
+VOID TEST(ProtocolRTMPTest, HandshakeC0C1)
{
srs_error_t err;
@@ -1379,7 +1377,7 @@ VOID TEST(ProtoStackTest, HandshakeC0C1)
}
}
-VOID TEST(ProtoStackTest, HandshakeS0S1S2)
+VOID TEST(ProtocolRTMPTest, HandshakeS0S1S2)
{
srs_error_t err;
@@ -1424,7 +1422,7 @@ VOID TEST(ProtoStackTest, HandshakeS0S1S2)
}
}
-VOID TEST(ProtoStackTest, HandshakeC2)
+VOID TEST(ProtocolRTMPTest, HandshakeC2)
{
srs_error_t err;
@@ -1469,7 +1467,7 @@ VOID TEST(ProtoStackTest, HandshakeC2)
}
}
-VOID TEST(ProtoStackTest, ServerInfo)
+VOID TEST(ProtocolRTMPTest, ServerInfo)
{
SrsServerInfo si;
EXPECT_EQ(0, si.pid);
@@ -1480,7 +1478,7 @@ VOID TEST(ProtoStackTest, ServerInfo)
EXPECT_EQ(0, si.build);
}
-VOID TEST(ProtoStackTest, ClientCommandMessage)
+VOID TEST(ProtocolRTMPTest, ClientCommandMessage)
{
srs_error_t err;
@@ -1581,7 +1579,7 @@ VOID TEST(ProtoStackTest, ClientCommandMessage)
}
}
-VOID TEST(ProtoStackTest, ServerCommandMessage)
+VOID TEST(ProtocolRTMPTest, ServerCommandMessage)
{
srs_error_t err;
@@ -1736,7 +1734,7 @@ VOID TEST(ProtoStackTest, ServerCommandMessage)
}
}
-VOID TEST(ProtoStackTest, ServerRedirect)
+VOID TEST(ProtocolRTMPTest, ServerRedirect)
{
srs_error_t err;
@@ -1843,7 +1841,7 @@ VOID TEST(ProtoStackTest, ServerRedirect)
}
}
-VOID TEST(ProtoStackTest, ServerIdentify)
+VOID TEST(ProtocolRTMPTest, ServerIdentify)
{
srs_error_t err;
@@ -2023,7 +2021,7 @@ VOID TEST(ProtoStackTest, ServerIdentify)
}
}
-VOID TEST(ProtoStackTest, ServerFMLEStart)
+VOID TEST(ProtocolRTMPTest, ServerFMLEStart)
{
srs_error_t err;
@@ -2088,7 +2086,7 @@ VOID TEST(ProtoStackTest, ServerFMLEStart)
}
}
-VOID TEST(ProtoStackTest, ServerHaivisionPublish)
+VOID TEST(ProtocolRTMPTest, ServerHaivisionPublish)
{
srs_error_t err;
@@ -2127,7 +2125,7 @@ VOID TEST(ProtoStackTest, ServerHaivisionPublish)
}
}
-VOID TEST(ProtoStackTest, ServerFMLEUnpublish)
+VOID TEST(ProtocolRTMPTest, ServerFMLEUnpublish)
{
srs_error_t err;
@@ -2180,7 +2178,7 @@ VOID TEST(ProtoStackTest, ServerFMLEUnpublish)
}
}
-VOID TEST(ProtoStackTest, ServerFlashPublish)
+VOID TEST(ProtocolRTMPTest, ServerFlashPublish)
{
srs_error_t err;
@@ -2205,7 +2203,7 @@ VOID TEST(ProtoStackTest, ServerFlashPublish)
}
}
-VOID TEST(ProtoStackTest, ServerRecursiveDepth)
+VOID TEST(ProtocolRTMPTest, ServerRecursiveDepth)
{
srs_error_t err;
@@ -2260,7 +2258,7 @@ VOID TEST(ProtoStackTest, ServerRecursiveDepth)
}
}
-VOID TEST(ProtoStackTest, ServerResponseCommands)
+VOID TEST(ProtocolRTMPTest, ServerResponseCommands)
{
srs_error_t err;
@@ -2402,7 +2400,7 @@ VOID TEST(ProtoStackTest, ServerResponseCommands)
}
}
-VOID TEST(ProtoStackTest, CoverAll)
+VOID TEST(ProtocolRTMPTest, CoverAll)
{
srs_error_t err;
@@ -2483,7 +2481,7 @@ VOID TEST(ProtoStackTest, CoverAll)
}
}
-VOID TEST(ProtoStackTest, CoverBandwidth)
+VOID TEST(ProtocolRTMPTest, CoverBandwidth)
{
if (true) {
SrsBandwidthPacket p;
@@ -2555,7 +2553,7 @@ VOID TEST(ProtoStackTest, CoverBandwidth)
}
}
-VOID TEST(ProtoStackTest, CoverAllUnmarshal)
+VOID TEST(ProtocolRTMPTest, CoverAllUnmarshal)
{
srs_error_t err;
@@ -2762,7 +2760,7 @@ VOID TEST(ProtoStackTest, CoverAllUnmarshal)
}
}
-VOID TEST(ProtoStackTest, ComplexToSimpleHandshake)
+VOID TEST(ProtocolRTMPTest, ComplexToSimpleHandshake)
{
srs_error_t err;
@@ -2785,7 +2783,7 @@ VOID TEST(ProtoStackTest, ComplexToSimpleHandshake)
}
}
-VOID TEST(ProtoStackTest, ConnectAppWithArgs)
+VOID TEST(ProtocolRTMPTest, ConnectAppWithArgs)
{
srs_error_t err;
@@ -2848,7 +2846,7 @@ VOID TEST(ProtoStackTest, ConnectAppWithArgs)
}
}
-VOID TEST(ProtoStackTest, AgentMessageCodec)
+VOID TEST(ProtocolRTMPTest, AgentMessageCodec)
{
srs_error_t err;
@@ -2950,7 +2948,7 @@ srs_error_t _mock_packet_to_shared_msg(SrsPacket* packet, int stream_id, SrsShar
return err;
}
-VOID TEST(ProtoStackTest, CheckStreamID)
+VOID TEST(ProtocolRTMPTest, CheckStreamID)
{
srs_error_t err;
@@ -2995,7 +2993,7 @@ VOID TEST(ProtoStackTest, CheckStreamID)
}
}
-VOID TEST(ProtoStackTest, AgentMessageTransform)
+VOID TEST(ProtocolRTMPTest, AgentMessageTransform)
{
srs_error_t err;
@@ -3095,7 +3093,7 @@ public:
}
};
-VOID TEST(ProtoStackTest, MergeReadHandler)
+VOID TEST(ProtocolRTMPTest, MergeReadHandler)
{
srs_error_t err;
diff --git a/trunk/src/utest/srs_utest_rtmp.hpp b/trunk/src/utest/srs_utest_rtmp.hpp
new file mode 100644
index 000000000..f18488104
--- /dev/null
+++ b/trunk/src/utest/srs_utest_rtmp.hpp
@@ -0,0 +1,35 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2013-2019 Winlin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef SRS_UTEST_PROTO_STACK_HPP
+#define SRS_UTEST_PROTO_STACK_HPP
+
+/*
+#include
+*/
+#include
+
+#include
+
+#endif
+