From 8d9554df8e840bbdf144e3617d3cf2fac6120646 Mon Sep 17 00:00:00 2001 From: winlin Date: Fri, 13 Dec 2019 16:31:32 +0800 Subject: [PATCH 01/28] Remove dead code in SrsRtmpClient::handshake --- trunk/src/protocol/srs_rtmp_stack.cpp | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) 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(); From 0394d95e4b8276887ee7ac7b8d8421c1b7643f33 Mon Sep 17 00:00:00 2001 From: winlin Date: Fri, 13 Dec 2019 20:04:33 +0800 Subject: [PATCH 02/28] Refine file structure for RTMP/HTTP protocol stack utest. --- trunk/configure | 2 +- trunk/src/utest/srs_utest_http.cpp | 25 ++++++ ...test_protostack.hpp => srs_utest_http.hpp} | 14 +--- ...test_protostack.cpp => srs_utest_rtmp.cpp} | 80 +++++++++---------- trunk/src/utest/srs_utest_rtmp.hpp | 35 ++++++++ 5 files changed, 102 insertions(+), 54 deletions(-) create mode 100644 trunk/src/utest/srs_utest_http.cpp rename trunk/src/utest/{srs_utest_protostack.hpp => srs_utest_http.hpp} (80%) rename trunk/src/utest/{srs_utest_protostack.cpp => srs_utest_rtmp.cpp} (98%) create mode 100644 trunk/src/utest/srs_utest_rtmp.hpp 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/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp new file mode 100644 index 000000000..ef4c6b130 --- /dev/null +++ b/trunk/src/utest/srs_utest_http.cpp @@ -0,0 +1,25 @@ +/* +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 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_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 + From 6ce04051e41e550a11d61e58f987f250cc982378 Mon Sep 17 00:00:00 2001 From: winlin Date: Fri, 13 Dec 2019 20:05:18 +0800 Subject: [PATCH 03/28] Improve test coverage of status for HTTP. --- trunk/src/utest/srs_utest_http.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index ef4c6b130..18bd42360 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -23,3 +23,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include + +VOID TEST(ProtoStackTest, 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()); +} From 4758a284d7b6d2435a0dc264955b5c2c712b9c2b Mon Sep 17 00:00:00 2001 From: winlin Date: Sat, 14 Dec 2019 10:12:25 +0800 Subject: [PATCH 04/28] Add test for http status. --- trunk/src/protocol/srs_http_stack.cpp | 8 +++----- trunk/src/utest/srs_utest_http.cpp | 8 +++++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index 3f4db8559..2f4e6dcb8 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -99,9 +99,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 +116,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 content detecting. return "application/octet-stream"; // fallback } diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index 18bd42360..9da963ef4 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -24,8 +24,14 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -VOID TEST(ProtoStackTest, StatusCode2Text) +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)); } From 474266eae705861eee0f0a2363690c1aef7d6ff1 Mon Sep 17 00:00:00 2001 From: winlin Date: Sat, 14 Dec 2019 23:34:09 +0800 Subject: [PATCH 05/28] Refine the comments for http content-type detecting --- trunk/src/protocol/srs_http_stack.cpp | 2 +- trunk/src/utest/srs_utest_http.cpp | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index 2f4e6dcb8..b798c7de7 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -116,7 +116,7 @@ bool srs_go_http_body_allowd(int status) // returns "application/octet-stream". string srs_go_http_detect(char* data, int size) { - // TODO: Implement the content detecting. + // TODO: Implement the request content-type detecting. return "application/octet-stream"; // fallback } diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index 9da963ef4..6b33b04d6 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -35,3 +35,8 @@ VOID TEST(ProtocolHTTPTest, StatusCode2Text) 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()); +} From 8c10c26f0085f4a818589abaf632101d1b81c8bc Mon Sep 17 00:00:00 2001 From: winlin Date: Mon, 16 Dec 2019 11:36:39 +0800 Subject: [PATCH 06/28] Improve the coverage for HTTP error response. --- trunk/src/protocol/srs_http_stack.cpp | 17 +++- trunk/src/protocol/srs_http_stack.hpp | 3 + trunk/src/service/srs_service_http_conn.cpp | 16 +++- trunk/src/service/srs_service_http_conn.hpp | 19 ++++- trunk/src/utest/srs_utest.hpp | 6 ++ trunk/src/utest/srs_utest_http.cpp | 88 +++++++++++++++++++++ 6 files changed, 141 insertions(+), 8 deletions(-) diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index b798c7de7..a95dc7ea2 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -156,14 +156,23 @@ 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); + } +} + int64_t SrsHttpHeader::content_length() { std::string cl = get("Content-Length"); @@ -192,7 +201,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; } diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp index 062178d57..f62dcb2c7 100644 --- a/trunk/src/protocol/srs_http_stack.hpp +++ b/trunk/src/protocol/srs_http_stack.hpp @@ -121,6 +121,9 @@ 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); public: // Get the content length. -1 if not set. virtual int64_t content_length(); diff --git a/trunk/src/service/srs_service_http_conn.cpp b/trunk/src/service/srs_service_http_conn.cpp index c7cd873e0..698d4b4a1 100644 --- a/trunk/src/service/srs_service_http_conn.cpp +++ b/trunk/src/service/srs_service_http_conn.cpp @@ -614,7 +614,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 +633,7 @@ SrsHttpResponseWriter::SrsHttpResponseWriter(SrsStSocket* io) header_sent = false; nb_iovss_cache = 0; iovss_cache = NULL; + hf = NULL; } SrsHttpResponseWriter::~SrsHttpResponseWriter() @@ -840,6 +849,11 @@ 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 hdr->write(ss); diff --git a/trunk/src/service/srs_service_http_conn.hpp b/trunk/src/service/srs_service_http_conn.hpp index 3bfdfb4ee..4ac4d043f 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. @@ -195,12 +195,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 +235,7 @@ private: // logically written. bool header_sent; public: - SrsHttpResponseWriter(SrsStSocket* io); + SrsHttpResponseWriter(ISrsProtocolReadWriter* io); virtual ~SrsHttpResponseWriter(); public: virtual srs_error_t final_request(); 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 index 6b33b04d6..217071807 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -22,7 +22,84 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include +#include +using namespace std; + #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"); + 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(); +} VOID TEST(ProtocolHTTPTest, StatusCode2Text) { @@ -40,3 +117,14 @@ VOID TEST(ProtocolHTTPTest, ResponseDetect) { EXPECT_STREQ("application/octet-stream", srs_go_http_detect(NULL, 0).c_str()); } + +VOID TEST(ProtocolHTTPTest, ResponseHTTPError) +{ + srs_error_t err; + + if (true) { + MockResponseWriter w; + HELPER_EXPECT_SUCCESS(srs_go_http_error(&w, SRS_CONSTS_HTTP_Found)); + EXPECT_STREQ(mock_http_response(302,"Found").c_str(), HELPER_BUFFER2STR(&w.io.out_buffer).c_str()); + } +} From 0886acbdb7185f74ff5ccf90f9263d4ca5cde88c Mon Sep 17 00:00:00 2001 From: winlin Date: Mon, 16 Dec 2019 12:11:29 +0800 Subject: [PATCH 07/28] Improve test coverage for HTTP header. --- trunk/src/utest/srs_utest_http.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index 217071807..21a856de6 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -128,3 +128,28 @@ VOID TEST(ProtocolHTTPTest, ResponseHTTPError) EXPECT_STREQ(mock_http_response(302,"Found").c_str(), HELPER_BUFFER2STR(&w.io.out_buffer).c_str()); } } + +VOID TEST(ProtocolHTTPTest, HTTPHeader) +{ + SrsHttpHeader h; + h.set("Server", "SRS"); + EXPECT_STREQ("SRS", h.get("Server").c_str()); + + 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(-1, h.content_length()); + + h.set_content_length(0); + EXPECT_EQ(0, h.content_length()); + + 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()); +} From 547cd4f51879ca21c20997f1d6e68a659b686922 Mon Sep 17 00:00:00 2001 From: winlin Date: Mon, 16 Dec 2019 12:25:29 +0800 Subject: [PATCH 08/28] Word the comments about the order of HTTP header --- trunk/src/protocol/srs_http_stack.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp index f62dcb2c7..a59b26230 100644 --- a/trunk/src/protocol/srs_http_stack.hpp +++ b/trunk/src/protocol/srs_http_stack.hpp @@ -108,6 +108,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(); From dcb7b6aae07ea117df8247cdba09ee1510f86606 Mon Sep 17 00:00:00 2001 From: winlin Date: Mon, 16 Dec 2019 15:12:26 +0800 Subject: [PATCH 09/28] Refactor HTTP Message by decoupling with http_parser --- trunk/src/protocol/srs_http_stack.hpp | 3 - trunk/src/service/srs_service_http_conn.cpp | 66 ++++++++++----------- trunk/src/service/srs_service_http_conn.hpp | 27 +++++---- trunk/src/utest/srs_utest_http.cpp | 4 ++ 4 files changed, 52 insertions(+), 48 deletions(-) diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp index a59b26230..e27978df3 100644 --- a/trunk/src/protocol/srs_http_stack.hpp +++ b/trunk/src/protocol/srs_http_stack.hpp @@ -428,9 +428,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. // diff --git a/trunk/src/service/srs_service_http_conn.cpp b/trunk/src/service/srs_service_http_conn.cpp index 698d4b4a1..8525390b6 100644 --- a/trunk/src/service/srs_service_http_conn.cpp +++ b/trunk/src/service/srs_service_http_conn.cpp @@ -90,10 +90,14 @@ srs_error_t SrsHttpParser::parse_message(ISrsReader* reader, ISrsHttpMessage** p } // create msg - SrsHttpMessage* msg = new SrsHttpMessage(reader); + SrsHttpMessage* msg = new SrsHttpMessage(reader, buffer); + + // Initialize the basic information. + bool keep_alive = http_should_keep_alive(&header); + msg->set_basic(header.method, header.status_code, header.content_length, keep_alive); // initalize http msg, parse url. - if ((err = msg->update(url, jsonp, &header, buffer, headers)) != srs_success) { + if ((err = msg->update(url, jsonp, headers)) != srs_success) { srs_freep(msg); return srs_error_wrap(err, "update message"); } @@ -253,16 +257,21 @@ 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); + _body = new SrsHttpResponseReader(this, reader, buffer); _http_ts_send_buffer = new char[SRS_HTTP_TS_SEND_BUFFER_SIZE]; + jsonp = false; + + _method = 0; + _status = 0; + _content_length = 0; + _keep_alive = true; } SrsHttpMessage::~SrsHttpMessage() @@ -272,26 +281,25 @@ SrsHttpMessage::~SrsHttpMessage() 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, bool keep_alive) +{ + _method = method; + _status = status; + _content_length = content_length; + _keep_alive = keep_alive; +} + +srs_error_t SrsHttpMessage::update(string url, bool allow_jsonp, vector& headers) { srs_error_t err = srs_success; _url = url; - _header = *header; _headers = headers; // 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"); - } - // parse uri from url. std::string host = get_request_header("Host"); @@ -349,13 +357,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 +413,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 +423,7 @@ bool SrsHttpMessage::is_chunked() bool SrsHttpMessage::is_keep_alive() { - return keep_alive; + return _keep_alive; } bool SrsHttpMessage::is_infinite_chunked() @@ -524,7 +532,7 @@ ISrsHttpResponseReader* SrsHttpMessage::body_reader() int64_t SrsHttpMessage::content_length() { - return _header.content_length; + return _content_length; } string SrsHttpMessage::query_get(string key) @@ -865,32 +873,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 4ac4d043f..da40ccea5 100644 --- a/trunk/src/service/srs_service_http_conn.hpp +++ b/trunk/src/service/srs_service_http_conn.hpp @@ -37,6 +37,9 @@ class ISrsReader; class SrsHttpResponseReader; class ISrsProtocolReadWriter; +// For http header. +typedef std::pair SrsHttpHeaderField; + // A wrapper for http-parser, // provides HTTP message originted service. class SrsHttpParser @@ -101,8 +104,6 @@ private: 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; @@ -110,8 +111,6 @@ private: bool chunked; // Whether the body is infinite chunked. bool infinite_chunked; - // Whether the request indicates should keep alive for the http connection. - bool keep_alive; // The uri parser SrsHttpUri* _uri; // Use a buffer to read and send ts file. @@ -123,16 +122,25 @@ private: std::map _query; // The transport connection, can be NULL. SrsConnection* owner_conn; +private: + uint8_t _method; + uint16_t _status; + int64_t _content_length; + // Whether the request indicates should keep alive for the http connection. + bool _keep_alive; +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, SrsFastStream* buffer); virtual ~SrsHttpMessage(); public: + // Set the basic information for HTTP request. + virtual void set_basic(uint8_t method, uint16_t status, int64_t content_length, 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 update(std::string url, bool allow_jsonp, std::vector& headers); public: // Get the owner connection, maybe NULL. virtual SrsConnection* connection(); @@ -261,11 +269,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_http.cpp b/trunk/src/utest/srs_utest_http.cpp index 21a856de6..fe1a3e865 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -153,3 +153,7 @@ VOID TEST(ProtocolHTTPTest, HTTPHeader) h.set_content_type("text/plain"); EXPECT_STREQ("text/plain", h.content_type().c_str()); } + +VOID TEST(ProtocolHTTPTest, HTTPCommonHandler) +{ +} From ca2b68f42854ebbe12826a3b1229376e2c117c7e Mon Sep 17 00:00:00 2001 From: winlin Date: Mon, 16 Dec 2019 16:00:02 +0800 Subject: [PATCH 10/28] Refactor header of HTTP message by using SrsHttpHeader. --- trunk/src/app/srs_app_http_api.cpp | 7 +- trunk/src/protocol/srs_http_stack.cpp | 23 ++-- trunk/src/protocol/srs_http_stack.hpp | 10 +- trunk/src/service/srs_service_http_conn.cpp | 122 ++++++++------------ trunk/src/service/srs_service_http_conn.hpp | 47 ++++---- trunk/src/utest/srs_utest_http.cpp | 4 + 6 files changed, 94 insertions(+), 119 deletions(-) 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/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index a95dc7ea2..b21db2d0b 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -173,6 +173,20 @@ void SrsHttpHeader::del(string key) } } +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"); @@ -776,13 +790,8 @@ srs_error_t SrsHttpCorsMux::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessag // 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. diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp index e27978df3..a6691ce33 100644 --- a/trunk/src/protocol/srs_http_stack.hpp +++ b/trunk/src/protocol/srs_http_stack.hpp @@ -45,6 +45,7 @@ class SrsHttpHeader; class ISrsHttpMessage; class SrsHttpMuxEntry; class ISrsHttpResponseWriter; +class SrsJsonObject; // From http specification // CR = @@ -129,6 +130,11 @@ public: // 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(); @@ -505,9 +511,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/service/srs_service_http_conn.cpp b/trunk/src/service/srs_service_http_conn.cpp index 8525390b6..4048f2934 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,22 @@ 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(); + hp_header = http_parser(); + // Reset the message url. url = ""; - headers.clear(); + // The body that we have read from cache. pbody = NULL; + // Reset the temporarily parsed header field. + expect_field_name = true; + // The header of the request. + srs_freep(header); + header = new SrsHttpHeader(); // do parse if ((err = parse_message_imp(reader)) != srs_success) { @@ -93,11 +98,9 @@ srs_error_t SrsHttpParser::parse_message(ISrsReader* reader, ISrsHttpMessage** p SrsHttpMessage* msg = new SrsHttpMessage(reader, buffer); // Initialize the basic information. - bool keep_alive = http_should_keep_alive(&header); - msg->set_basic(header.method, header.status_code, header.content_length, keep_alive); - - // initalize http msg, parse url. - if ((err = msg->update(url, jsonp, headers)) != srs_success) { + 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"); } @@ -142,11 +145,6 @@ srs_error_t SrsHttpParser::parse_message_imp(ISrsReader* reader) } } - // parse last header. - if (!field_name.empty() && !field_value.empty()) { - headers.push_back(std::make_pair(field_name, field_value)); - } - return err; } @@ -167,7 +165,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; @@ -196,7 +194,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); @@ -211,16 +209,12 @@ int SrsHttpParser::on_header_field(http_parser* parser, const char* at, size_t l // 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 = ""; + obj->header->set(obj->field_name, obj->field_value); } obj->expect_field_name = true; if (length > 0) { - obj->field_name.append(at, (int)length); + obj->field_name = string(at, (int)length); } srs_info("Header field(%d bytes): %.*s", (int)length, (int)length, at); @@ -233,7 +227,7 @@ int SrsHttpParser::on_header_value(http_parser* parser, const char* at, size_t l srs_assert(obj); if (length > 0) { - obj->field_value.append(at, (int)length); + obj->field_value = string(at, (int)length); } obj->expect_field_name = false; @@ -281,38 +275,42 @@ SrsHttpMessage::~SrsHttpMessage() srs_freepa(_http_ts_send_buffer); } -void SrsHttpMessage::set_basic(uint8_t method, uint16_t status, int64_t content_length, bool keep_alive) +void SrsHttpMessage::set_basic(uint8_t method, uint16_t status, int64_t content_length) { _method = method; _status = status; _content_length = content_length; +} + +void SrsHttpMessage::set_header(SrsHttpHeader* header, bool keep_alive) +{ + _header = *header; _keep_alive = keep_alive; + + // whether chunked. + chunked = (header->get("Transfer-Encoding") == "chunked"); } -srs_error_t SrsHttpMessage::update(string url, bool allow_jsonp, vector& headers) +srs_error_t SrsHttpMessage::set_url(string url, bool allow_jsonp) { srs_error_t err = srs_success; _url = url; - _headers = headers; - - // whether chunked. - std::string transfer_encoding = get_request_header("Transfer-Encoding"); - chunked = (transfer_encoding == "chunked"); - - // parse uri from url. - std::string host = get_request_header("Host"); - - // use server public ip when no host specified. + + // 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. @@ -546,39 +544,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) @@ -598,7 +566,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(); @@ -863,7 +831,7 @@ srs_error_t SrsHttpResponseWriter::send_header(char* data, int size) return srs_error_wrap(err, "filter header"); } - // write headers + // write header hdr->write(ss); // header_eof diff --git a/trunk/src/service/srs_service_http_conn.hpp b/trunk/src/service/srs_service_http_conn.hpp index da40ccea5..f4716b885 100644 --- a/trunk/src/service/srs_service_http_conn.hpp +++ b/trunk/src/service/srs_service_http_conn.hpp @@ -37,9 +37,6 @@ class ISrsReader; class SrsHttpResponseReader; class ISrsProtocolReadWriter; -// For http header. -typedef std::pair SrsHttpHeaderField; - // A wrapper for http-parser, // provides HTTP message originted service. class SrsHttpParser @@ -57,9 +54,9 @@ private: 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(); @@ -88,9 +85,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. // @@ -100,34 +94,36 @@ 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; // 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; - // 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: 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; + // 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; + // The query map + std::map _query; private: // Whether request is jsonp. bool jsonp; @@ -138,9 +134,11 @@ public: virtual ~SrsHttpMessage(); public: // Set the basic information for HTTP request. - virtual void set_basic(uint8_t method, uint16_t status, int64_t content_length, bool keep_alive); + virtual void set_basic(uint8_t method, uint16_t status, int64_t content_length); + // Set HTTP header and whether the request require keep alive. + 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, 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(); @@ -187,10 +185,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. diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index fe1a3e865..f620a5671 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -134,6 +134,7 @@ 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); @@ -141,17 +142,20 @@ VOID TEST(ProtocolHTTPTest, HTTPHeader) 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()); } VOID TEST(ProtocolHTTPTest, HTTPCommonHandler) From 45ed458927a76265431653121ca243e8a7ae4001 Mon Sep 17 00:00:00 2001 From: winlin Date: Mon, 16 Dec 2019 16:07:17 +0800 Subject: [PATCH 11/28] Eliminate dead code of ts cache for HTTP message --- trunk/src/protocol/srs_http_stack.cpp | 14 ++++++-------- trunk/src/protocol/srs_http_stack.hpp | 10 ---------- trunk/src/service/srs_service_http_conn.cpp | 2 -- trunk/src/service/srs_service_http_conn.hpp | 2 -- 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index b21db2d0b..6e6fdf248 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) { @@ -492,7 +496,8 @@ 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; @@ -822,17 +827,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() diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp index a6691ce33..e38ae9db1 100644 --- a/trunk/src/protocol/srs_http_stack.hpp +++ b/trunk/src/protocol/srs_http_stack.hpp @@ -63,9 +63,6 @@ class SrsJsonObject; #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 @@ -455,16 +452,9 @@ public: // @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; diff --git a/trunk/src/service/srs_service_http_conn.cpp b/trunk/src/service/srs_service_http_conn.cpp index 4048f2934..3cb58f54a 100644 --- a/trunk/src/service/srs_service_http_conn.cpp +++ b/trunk/src/service/srs_service_http_conn.cpp @@ -258,7 +258,6 @@ SrsHttpMessage::SrsHttpMessage(ISrsReader* reader, SrsFastStream* buffer) : ISrs infinite_chunked = false; _uri = new SrsHttpUri(); _body = new SrsHttpResponseReader(this, reader, buffer); - _http_ts_send_buffer = new char[SRS_HTTP_TS_SEND_BUFFER_SIZE]; jsonp = false; @@ -272,7 +271,6 @@ SrsHttpMessage::~SrsHttpMessage() { srs_freep(_body); srs_freep(_uri); - srs_freepa(_http_ts_send_buffer); } void SrsHttpMessage::set_basic(uint8_t method, uint16_t status, int64_t content_length) diff --git a/trunk/src/service/srs_service_http_conn.hpp b/trunk/src/service/srs_service_http_conn.hpp index f4716b885..6d956f1de 100644 --- a/trunk/src/service/srs_service_http_conn.hpp +++ b/trunk/src/service/srs_service_http_conn.hpp @@ -100,8 +100,6 @@ private: // Whether the body is infinite chunked. bool infinite_chunked; // Use a buffer to read and send ts file. - // TODO: FIXME: remove it. - char* _http_ts_send_buffer; // The transport connection, can be NULL. SrsConnection* owner_conn; private: From 6bad973a7c6692e2269c5b1d9c8a8775fb3ca316 Mon Sep 17 00:00:00 2001 From: winlin Date: Mon, 16 Dec 2019 18:21:39 +0800 Subject: [PATCH 12/28] Fix HTTP parser bug for parsing header from multiple pieces of data. --- trunk/src/service/srs_service_http_conn.cpp | 23 +-- trunk/src/service/srs_service_http_conn.hpp | 2 - trunk/src/utest/srs_utest_http.cpp | 207 +++++++++++++++++++- 3 files changed, 218 insertions(+), 14 deletions(-) diff --git a/trunk/src/service/srs_service_http_conn.cpp b/trunk/src/service/srs_service_http_conn.cpp index 3cb58f54a..8919b6283 100644 --- a/trunk/src/service/srs_service_http_conn.cpp +++ b/trunk/src/service/srs_service_http_conn.cpp @@ -79,12 +79,10 @@ srs_error_t SrsHttpParser::parse_message(ISrsReader* reader, ISrsHttpMessage** p // Reset request data. state = SrsHttpParseStateInit; hp_header = http_parser(); - // Reset the message url. - url = ""; // The body that we have read from cache. pbody = NULL; - // Reset the temporarily parsed header field. - expect_field_name = true; + // 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(); @@ -144,6 +142,11 @@ srs_error_t SrsHttpParser::parse_message_imp(ISrsReader* reader) return srs_error_wrap(err, "grow buffer"); } } + + SrsHttpParser* obj = this; + if (!obj->field_value.empty()) { + obj->header->set(obj->field_name, obj->field_value); + } return err; } @@ -206,15 +209,14 @@ 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) { + + 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 = string(at, (int)length); + obj->field_name.append(at, (int)length); } srs_info("Header field(%d bytes): %.*s", (int)length, (int)length, at); @@ -227,9 +229,8 @@ int SrsHttpParser::on_header_value(http_parser* parser, const char* at, size_t l srs_assert(obj); if (length > 0) { - obj->field_value = string(at, (int)length); + 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; diff --git a/trunk/src/service/srs_service_http_conn.hpp b/trunk/src/service/srs_service_http_conn.hpp index 6d956f1de..2313b0207 100644 --- a/trunk/src/service/srs_service_http_conn.hpp +++ b/trunk/src/service/srs_service_http_conn.hpp @@ -49,8 +49,6 @@ 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; diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index f620a5671..3ad6504f7 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -28,6 +28,8 @@ using namespace std; #include #include #include +#include +#include class MockResponseWriter : virtual public ISrsHttpResponseWriter, virtual public ISrsHttpHeaderFilter { @@ -156,8 +158,211 @@ VOID TEST(ProtocolHTTPTest, HTTPHeader) 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); +} + +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, HTTPCommonHandler) +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); + } } From fa362607b2fd9cb6e4ea2848db5935f2671fb0bc Mon Sep 17 00:00:00 2001 From: winlin Date: Mon, 16 Dec 2019 19:32:41 +0800 Subject: [PATCH 13/28] Add test for http basic handler --- README.md | 1 + trunk/src/kernel/srs_kernel_file.cpp | 13 +++++ trunk/src/kernel/srs_kernel_file.hpp | 12 +++++ trunk/src/protocol/srs_http_stack.cpp | 73 ++++++++++++++++++--------- trunk/src/protocol/srs_http_stack.hpp | 15 ++++++ trunk/src/utest/srs_utest_http.cpp | 71 +++++++++++++++++++++++++- 6 files changed, 159 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index ba359d84d..cddc782b8 100755 --- a/README.md +++ b/README.md @@ -145,6 +145,7 @@ For previous versions, please read: ## V3 changes +* v3.0, 2019-12-16, For [#1042][bug #1042], add test for HTTP protocol. * v3.0, 2019-12-13, [3.0 alpha4(3.0.71)][r3.0a4] released. 112928 lines. * v3.0, 2019-12-12, For [#547][bug #547], [#1506][bug #1506], default hls_dts_directly to on. 3.0.71 * v3.0, 2019-12-12, SrsPacket supports converting to message, so can be sent by one API. diff --git a/trunk/src/kernel/srs_kernel_file.cpp b/trunk/src/kernel/srs_kernel_file.cpp index 88bee7e68..832b7727b 100644 --- a/trunk/src/kernel/srs_kernel_file.cpp +++ b/trunk/src/kernel/srs_kernel_file.cpp @@ -175,6 +175,19 @@ srs_error_t SrsFileWriter::lseek(off_t offset, int whence, off_t* seeked) return srs_success; } +ISrsFileReaderFactory::ISrsFileReaderFactory() +{ +} + +ISrsFileReaderFactory::~ISrsFileReaderFactory() +{ +} + +SrsFileReader* ISrsFileReaderFactory::create_file_reader() +{ + return new SrsFileReader(); +} + SrsFileReader::SrsFileReader() { fd = -1; diff --git a/trunk/src/kernel/srs_kernel_file.hpp b/trunk/src/kernel/srs_kernel_file.hpp index c38b72672..0bfc02945 100644 --- a/trunk/src/kernel/srs_kernel_file.hpp +++ b/trunk/src/kernel/srs_kernel_file.hpp @@ -35,6 +35,8 @@ #include #endif +class SrsFileReader; + /** * file writer, to write to file. */ @@ -73,6 +75,16 @@ public: virtual srs_error_t lseek(off_t offset, int whence, off_t* seeked); }; +// The file reader factory. +class ISrsFileReaderFactory +{ +public: + ISrsFileReaderFactory(); + virtual ~ISrsFileReaderFactory(); +public: + virtual SrsFileReader* create_file_reader(); +}; + /** * file reader, to read from file. */ diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index 6e6fdf248..1285ecf93 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -272,7 +272,7 @@ srs_error_t SrsHttpRedirectHandler::serve_http(ISrsHttpResponseWriter* w, ISrsHt location += "?" + r->query(); } - string msg = "Redirect to" + location; + string msg = "Redirect to " + location; w->header()->set_content_type("text/plain; charset=utf-8"); w->header()->set_content_length(msg.length()); @@ -304,37 +304,60 @@ srs_error_t SrsHttpNotFoundHandler::serve_http(ISrsHttpResponseWriter* w, ISrsHt return srs_go_http_error(w, SRS_CONSTS_HTTP_NotFound); } +string srs_http_fs_fullpath(string dir, string upath, string pattern) +{ + // add default pages. + if (srs_string_ends_with(upath, "/")) { + upath += SRS_HTTP_DEFAULT_PAGE; + } + + string fullpath = dir + "/"; + + // remove the virtual directory. + size_t pos = pattern.find("/"); + if (upath.length() > pattern.length() && pos != string::npos) { + fullpath += upath.substr(pattern.length() - pos); + } else { + fullpath += upath; + } + + return fullpath; +} + SrsHttpFileServer::SrsHttpFileServer(string root_dir) { dir = root_dir; + fs_factory = new ISrsFileReaderFactory(); + _srs_path_exists = srs_path_exists; } SrsHttpFileServer::~SrsHttpFileServer() { + srs_freep(fs_factory); +} + +SrsHttpFileServer* SrsHttpFileServer::set_fs_factory(ISrsFileReaderFactory* f) +{ + srs_freep(fs_factory); + fs_factory = f; + return this; +} + +SrsHttpFileServer* SrsHttpFileServer::set_path_check(_pfn_srs_path_exists pfn) +{ + _srs_path_exists = pfn; + return this; } srs_error_t SrsHttpFileServer::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) { - 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, upath, entry->pattern); // stat current dir, if exists, return error. - if (!srs_path_exists(fullpath)) { + if (!_srs_path_exists(fullpath)) { srs_warn("http miss file=%s, pattern=%s, upath=%s", fullpath.c_str(), entry->pattern.c_str(), upath.c_str()); return SrsHttpNotFoundHandler().serve_http(w, r); @@ -357,15 +380,15 @@ srs_error_t SrsHttpFileServer::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMes srs_error_t SrsHttpFileServer::serve_file(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, string fullpath) { srs_error_t err = srs_success; - - // open the target file. - SrsFileReader fs; - - if ((err = fs.open(fullpath)) != srs_success) { + + SrsFileReader* fs = fs_factory->create_file_reader(); + SrsAutoFree(SrsFileReader, fs); + + if ((err = fs->open(fullpath)) != srs_success) { return srs_error_wrap(err, "open file %s", fullpath.c_str()); } - int64_t length = fs.filesize(); + int64_t length = fs->filesize(); // unset the content length to encode in chunked encoding. w->header()->set_content_length(length); @@ -418,7 +441,7 @@ srs_error_t SrsHttpFileServer::serve_file(ISrsHttpResponseWriter* w, ISrsHttpMes // write body. int64_t left = length; - if ((err = copy(w, &fs, r, (int)left)) != srs_success) { + if ((err = copy(w, fs, r, (int)left)) != srs_success) { return srs_error_wrap(err, "copy file=%s size=%d", fullpath.c_str(), left); } diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp index e38ae9db1..a0f0dd88a 100644 --- a/trunk/src/protocol/srs_http_stack.hpp +++ b/trunk/src/protocol/srs_http_stack.hpp @@ -46,6 +46,7 @@ class ISrsHttpMessage; class SrsHttpMuxEntry; class ISrsHttpResponseWriter; class SrsJsonObject; +class ISrsFileReaderFactory; // From http specification // CR = @@ -276,6 +277,12 @@ public: virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); }; +// For utest to mock it. +typedef bool (*_pfn_srs_path_exists)(std::string path); + +// Build the file path from request r. +extern std::string srs_http_fs_fullpath(std::string dir, std::string upath, std::string pattern); + // FileServer returns a handler that serves HTTP requests // with the contents of the file system rooted at root. // @@ -288,9 +295,17 @@ class SrsHttpFileServer : public ISrsHttpHandler { protected: std::string dir; +private: + ISrsFileReaderFactory* fs_factory; + _pfn_srs_path_exists _srs_path_exists; public: SrsHttpFileServer(std::string root_dir); virtual ~SrsHttpFileServer(); +private: + // For utest to mock the fs. + virtual SrsHttpFileServer* set_fs_factory(ISrsFileReaderFactory* v); + // For utest to mock the path check function. + virtual SrsHttpFileServer* set_path_check(_pfn_srs_path_exists pfn); public: virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); private: diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index 3ad6504f7..5aaa863bd 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -30,6 +30,8 @@ using namespace std; #include #include #include +#include +#include class MockResponseWriter : virtual public ISrsHttpResponseWriter, virtual public ISrsHttpHeaderFilter { @@ -90,6 +92,7 @@ srs_error_t MockResponseWriter::filter(SrsHttpHeader* h) h->del("Content-Type"); h->del("Server"); h->del("Connection"); + h->del("Location"); return srs_success; } @@ -120,6 +123,9 @@ VOID TEST(ProtocolHTTPTest, ResponseDetect) EXPECT_STREQ("application/octet-stream", srs_go_http_detect(NULL, 0).c_str()); } +#define __MOCK_HTTP_EXPECT_STREQ(status, text, w) \ + EXPECT_STREQ(mock_http_response(status, text).c_str(), HELPER_BUFFER2STR(&w.io.out_buffer).c_str()) + VOID TEST(ProtocolHTTPTest, ResponseHTTPError) { srs_error_t err; @@ -127,7 +133,7 @@ VOID TEST(ProtocolHTTPTest, ResponseHTTPError) if (true) { MockResponseWriter w; HELPER_EXPECT_SUCCESS(srs_go_http_error(&w, SRS_CONSTS_HTTP_Found)); - EXPECT_STREQ(mock_http_response(302,"Found").c_str(), HELPER_BUFFER2STR(&w.io.out_buffer).c_str()); + __MOCK_HTTP_EXPECT_STREQ(302, "Found", w); } } @@ -165,6 +171,69 @@ VOID TEST(ProtocolHTTPTest, HTTPHeader) srs_freep(o); } +class MockFileReaderFactory : public ISrsFileReaderFactory +{ +public: + string bytes; + MockFileReaderFactory(string data) { + bytes = data; + } + virtual ~MockFileReaderFactory() { + } + virtual SrsFileReader* create_file_reader() { + return new MockSrsFileReader((const char*)bytes.data(), (int)bytes.length()); + } +}; + +bool _mock_srs_path_exists(std::string path) +{ + return true; +} + +VOID TEST(ProtocolHTTPTest, BasicHandlers) +{ + srs_error_t err; + + if (true) { + EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp", "/tmp/index.html", "/").c_str()); + } + + if (true) { + SrsHttpMuxEntry e; + e.pattern = "/"; + + SrsHttpFileServer h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!"))->set_path_check(_mock_srs_path_exists); + h.entry = &e; + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w); + } + + if (true) { + SrsHttpRedirectHandler h("/api", 500); + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/api?v=2.0", false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + __MOCK_HTTP_EXPECT_STREQ(500, "Redirect to /api?v=2.0", w); + } + + if (true) { + SrsHttpNotFoundHandler h; + + MockResponseWriter w; + HELPER_ASSERT_SUCCESS(h.serve_http(&w, NULL)); + __MOCK_HTTP_EXPECT_STREQ(404, "Not Found", w); + } +} + class MockMSegmentsReader : public ISrsReader { public: From 97f2c5bf0cd00862b275a32241e27d2568a78a98 Mon Sep 17 00:00:00 2001 From: winlin Date: Mon, 16 Dec 2019 20:07:06 +0800 Subject: [PATCH 14/28] Refactor http static file server path resolving. --- trunk/src/protocol/srs_http_stack.cpp | 25 +++++++++++++++++-------- trunk/src/protocol/srs_http_stack.hpp | 2 +- trunk/src/utest/srs_utest_http.cpp | 12 ++++++++++-- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index 1285ecf93..cd258380a 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -304,22 +304,31 @@ 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 upath, string pattern) +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; } - string fullpath = dir + "/"; - - // remove the virtual directory. + // 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) { - fullpath += upath.substr(pattern.length() - pos); - } else { - fullpath += upath; + filename = upath.substr(pattern.length() - pos); + } + + string fullpath = srs_string_trim_end(dir, "/"); + if (!srs_string_starts_with(filename, "/")) { + fullpath += "/"; } + fullpath += filename; return fullpath; } @@ -354,7 +363,7 @@ srs_error_t SrsHttpFileServer::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMes srs_assert(entry); string upath = r->path(); - string fullpath = srs_http_fs_fullpath(dir, upath, entry->pattern); + string fullpath = srs_http_fs_fullpath(dir, entry->pattern, upath); // stat current dir, if exists, return error. if (!_srs_path_exists(fullpath)) { diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp index a0f0dd88a..f09ee9668 100644 --- a/trunk/src/protocol/srs_http_stack.hpp +++ b/trunk/src/protocol/srs_http_stack.hpp @@ -281,7 +281,7 @@ public: typedef bool (*_pfn_srs_path_exists)(std::string path); // Build the file path from request r. -extern std::string srs_http_fs_fullpath(std::string dir, std::string upath, std::string pattern); +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. diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index 5aaa863bd..32de06d0c 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -185,7 +185,7 @@ public: } }; -bool _mock_srs_path_exists(std::string path) +bool _mock_srs_path_exists(std::string /*path*/) { return true; } @@ -195,7 +195,15 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) srs_error_t err; if (true) { - EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp", "/tmp/index.html", "/").c_str()); + 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) { From 8cdb7cc727ea39ec5f4b0a98b6cb537999f0afee Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 17 Dec 2019 14:33:47 +0800 Subject: [PATCH 15/28] Add test for vod stream handler --- trunk/src/app/srs_app_http_static.cpp | 38 +++--- trunk/src/protocol/srs_http_stack.cpp | 27 ++-- trunk/src/protocol/srs_http_stack.hpp | 6 +- trunk/src/utest/srs_utest_http.cpp | 184 ++++++++++++++++++++++++-- 4 files changed, 208 insertions(+), 47 deletions(-) diff --git a/trunk/src/app/srs_app_http_static.cpp b/trunk/src/app/srs_app_http_static.cpp index e486e773a..1bfd8bfe6 100644 --- a/trunk/src/app/srs_app_http_static.cpp +++ b/trunk/src/app/srs_app_http_static.cpp @@ -51,8 +51,7 @@ using namespace std; #include #include -SrsVodStream::SrsVodStream(string root_dir) -: SrsHttpFileServer(root_dir) +SrsVodStream::SrsVodStream(string root_dir) : SrsHttpFileServer(root_dir) { } @@ -64,22 +63,23 @@ srs_error_t SrsVodStream::serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMe { srs_error_t err = srs_success; - SrsFileReader fs; + SrsFileReader* fs = fs_factory->create_file_reader(); + SrsAutoFree(SrsFileReader, fs); // open flv file - if ((err = fs.open(fullpath)) != srs_success) { + if ((err = fs->open(fullpath)) != srs_success) { return srs_error_wrap(err, "open file"); } - if (offset > fs.filesize()) { + if (offset > fs->filesize()) { return srs_error_new(ERROR_HTTP_REMUX_OFFSET_OVERFLOW, "http flv streaming %s overflow. size=%" PRId64 ", offset=%d", - fullpath.c_str(), fs.filesize(), offset); + fullpath.c_str(), fs->filesize(), offset); } SrsFlvVodStreamDecoder ffd; // open fast decoder - if ((err = ffd.initialize(&fs)) != srs_success) { + if ((err = ffd.initialize(fs)) != srs_success) { return srs_error_wrap(err, "init ffd"); } @@ -107,12 +107,12 @@ srs_error_t SrsVodStream::serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMe } sh_data = new char[sh_size]; SrsAutoFreeA(char, sh_data); - if ((err = fs.read(sh_data, sh_size, NULL)) != srs_success) { + if ((err = fs->read(sh_data, sh_size, NULL)) != srs_success) { return srs_error_wrap(err, "fs read"); } // seek to data offset - int64_t left = fs.filesize() - offset; + int64_t left = fs->filesize() - offset; // write http header for ts. w->header()->set_content_length((int)(sizeof(flv_header) + sh_size + left)); @@ -132,7 +132,7 @@ srs_error_t SrsVodStream::serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMe } // send data - if ((err = copy(w, &fs, r, (int)left)) != srs_success) { + if ((err = copy(w, fs, r, (int)left)) != srs_success) { return srs_error_wrap(err, "read flv=%s size=%d", fullpath.c_str(), left); } @@ -146,21 +146,22 @@ srs_error_t SrsVodStream::serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsHttpMe srs_assert(start >= 0); srs_assert(end == -1 || end >= 0); - SrsFileReader fs; + SrsFileReader* fs = fs_factory->create_file_reader(); + SrsAutoFree(SrsFileReader, fs); // open flv file - if ((err = fs.open(fullpath)) != srs_success) { + if ((err = fs->open(fullpath)) != srs_success) { return srs_error_wrap(err, "fs open"); } // parse -1 to whole file. if (end == -1) { - end = (int)fs.filesize(); + end = (int)fs->filesize(); } - if (end > fs.filesize() || start > end) { + if (end > fs->filesize() || start > end) { return srs_error_new(ERROR_HTTP_REMUX_OFFSET_OVERFLOW, "http mp4 streaming %s overflow. size=%" PRId64 ", offset=%d", - fullpath.c_str(), fs.filesize(), start); + fullpath.c_str(), fs->filesize(), start); } // seek to data offset, [start, end] for range. @@ -174,15 +175,16 @@ srs_error_t SrsVodStream::serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsHttpMe w->write_header(SRS_CONSTS_HTTP_PartialContent); // response the content range header. + // https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Range_requests std::stringstream content_range; - content_range << "bytes " << start << "-" << end << "/" << fs.filesize(); + content_range << "bytes " << start << "-" << end << "/" << fs->filesize(); w->header()->set("Content-Range", content_range.str()); // write body. - fs.seek2(start); + fs->seek2(start); // send data - if ((err = copy(w, &fs, r, (int)left)) != srs_success) { + if ((err = copy(w, fs, r, (int)left)) != srs_success) { return srs_error_wrap(err, "read mp4=%s size=%d", fullpath.c_str(), left); } diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index cd258380a..4b478603b 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -345,17 +345,15 @@ SrsHttpFileServer::~SrsHttpFileServer() srs_freep(fs_factory); } -SrsHttpFileServer* SrsHttpFileServer::set_fs_factory(ISrsFileReaderFactory* f) +void SrsHttpFileServer::set_fs_factory(ISrsFileReaderFactory* f) { srs_freep(fs_factory); fs_factory = f; - return this; } -SrsHttpFileServer* SrsHttpFileServer::set_path_check(_pfn_srs_path_exists pfn) +void SrsHttpFileServer::set_path_check(_pfn_srs_path_exists pfn) { _srs_path_exists = pfn; - return this; } srs_error_t SrsHttpFileServer::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) @@ -396,8 +394,9 @@ srs_error_t SrsHttpFileServer::serve_file(ISrsHttpResponseWriter* w, ISrsHttpMes if ((err = fs->open(fullpath)) != srs_success) { return srs_error_wrap(err, "open file %s", fullpath.c_str()); } - - int64_t length = fs->filesize(); + + // The length of bytes we could response to. + int64_t length = fs->filesize() - fs->tellg(); // unset the content length to encode in chunked encoding. w->header()->set_content_length(length); @@ -479,10 +478,8 @@ srs_error_t SrsHttpFileServer::serve_flv_file(ISrsHttpResponseWriter* w, ISrsHtt srs_error_t SrsHttpFileServer::serve_mp4_file(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, string fullpath) { // for flash to request mp4 range in query string. - // for example, http://digitalprimates.net/dash/DashTest.html?url=http://dashdemo.edgesuite.net/digitalprimates/nexus/oops-20120802-manifest.mpd std::string range = r->query_get("range"); - // or, use bytes to request range, - // for example, http://dashas.castlabs.com/demo/try.html + // or, use bytes to request range. if (range.empty()) { range = r->query_get("bytes"); } @@ -515,11 +512,15 @@ srs_error_t SrsHttpFileServer::serve_mp4_file(ISrsHttpResponseWriter* w, ISrsHtt srs_error_t SrsHttpFileServer::serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, string fullpath, int offset) { + // @remark For common http file server, we don't support stream request, please use SrsVodStream instead. + // TODO: FIXME: Support range in header https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Range_requests return serve_file(w, r, fullpath); } srs_error_t SrsHttpFileServer::serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, string fullpath, int start, int end) { + // @remark For common http file server, we don't support stream request, please use SrsVodStream instead. + // TODO: FIXME: Support range in header https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Range_requests return serve_file(w, r, fullpath); } @@ -535,19 +536,15 @@ srs_error_t SrsHttpFileServer::copy(ISrsHttpResponseWriter* w, SrsFileReader* fs ssize_t nread = -1; int max_read = srs_min(left, SRS_HTTP_TS_SEND_BUFFER_SIZE); if ((err = fs->read(buf, max_read, &nread)) != srs_success) { - break; + return srs_error_wrap(err, "read limit=%d, left=%d", max_read, left); } left -= nread; if ((err = w->write(buf, (int)nread)) != srs_success) { - break; + return srs_error_wrap(err, "write limit=%d, bytes=%d, left=%d", max_read, nread, left); } } - if (err != srs_success) { - return srs_error_wrap(err, "copy"); - } - return err; } diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp index f09ee9668..f456c26d5 100644 --- a/trunk/src/protocol/srs_http_stack.hpp +++ b/trunk/src/protocol/srs_http_stack.hpp @@ -295,7 +295,7 @@ class SrsHttpFileServer : public ISrsHttpHandler { protected: std::string dir; -private: +protected: ISrsFileReaderFactory* fs_factory; _pfn_srs_path_exists _srs_path_exists; public: @@ -303,9 +303,9 @@ public: virtual ~SrsHttpFileServer(); private: // For utest to mock the fs. - virtual SrsHttpFileServer* set_fs_factory(ISrsFileReaderFactory* v); + virtual void set_fs_factory(ISrsFileReaderFactory* v); // For utest to mock the path check function. - virtual SrsHttpFileServer* set_path_check(_pfn_srs_path_exists pfn); + virtual void set_path_check(_pfn_srs_path_exists pfn); public: virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); private: diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index 32de06d0c..8dae41ca5 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -32,6 +32,7 @@ using namespace std; #include #include #include +#include class MockResponseWriter : virtual public ISrsHttpResponseWriter, virtual public ISrsHttpHeaderFilter { @@ -93,6 +94,7 @@ srs_error_t MockResponseWriter::filter(SrsHttpHeader* h) h->del("Server"); h->del("Connection"); h->del("Location"); + h->del("Content-Range"); return srs_success; } @@ -185,25 +187,120 @@ public: } }; -bool _mock_srs_path_exists(std::string /*path*/) +bool _mock_srs_path_always_exists(std::string /*path*/) { return true; } +bool _mock_srs_path_not_exists(std::string /*path*/) +{ + return false; +} + +VOID TEST(ProtocolHTTPTest, VodStreamHandlers) +{ + srs_error_t err; + + if (true) { + SrsHttpMuxEntry e; + e.pattern = "/"; + + string fs; + int nn_flv_prefix = 0; + if (true) { + char flv_header[13]; + nn_flv_prefix += sizeof(flv_header); + HELPER_ARRAY_INIT(flv_header, 13, 0); + fs.append(flv_header, 13); + } + if (true) { + uint8_t tag[15] = {9}; + nn_flv_prefix += sizeof(tag); + HELPER_ARRAY_INIT(tag+1, 14, 0); + fs.append((const char*)tag, sizeof(tag)); + } + if (true) { + uint8_t tag[15] = {8}; + nn_flv_prefix += sizeof(tag); + HELPER_ARRAY_INIT(tag+1, 14, 0); + fs.append((const char*)tag, sizeof(tag)); + } + string flv_content = "Hello, world!"; + fs.append(flv_content); + + SrsVodStream h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory(fs)); + h.set_path_check(_mock_srs_path_always_exists); + h.entry = &e; + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/index.flv?start=" + srs_int2str(nn_flv_prefix + 2), false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + + // We only compare the last content, ignore HTTP and FLV header. + string av2 = HELPER_BUFFER2STR(&w.io.out_buffer); + string av = av2.substr(av2.length() - flv_content.length() + 2); + string ev2 = mock_http_response(200, "llo, world!"); + string ev = ev2.substr(ev2.length() - flv_content.length() + 2); + EXPECT_STREQ(ev.c_str(), av.c_str()); + } + + if (true) { + SrsHttpMuxEntry e; + e.pattern = "/"; + + SrsVodStream h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); + h.set_path_check(_mock_srs_path_always_exists); + h.entry = &e; + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/index.mp4?range=2-3", false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + __MOCK_HTTP_EXPECT_STREQ(206, "ll", w); + } + + if (true) { + SrsHttpMuxEntry e; + e.pattern = "/"; + + SrsVodStream h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); + h.set_path_check(_mock_srs_path_always_exists); + h.entry = &e; + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/index.mp4?bytes=2-5", false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + __MOCK_HTTP_EXPECT_STREQ(206, "llo,", w); + } +} + VOID TEST(ProtocolHTTPTest, BasicHandlers) { srs_error_t err; if (true) { - EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp", "/", "/").c_str()); - EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp", "/", "/index.html").c_str()); - EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "/", "/index.html").c_str()); - EXPECT_STREQ("/tmp/ndex.html", srs_http_fs_fullpath("/tmp/", "//", "/index.html").c_str()); - EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "/api", "/api/index.html").c_str()); - EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "ossrs.net/api", "/api/index.html").c_str()); - EXPECT_STREQ("/tmp/views/index.html", srs_http_fs_fullpath("/tmp/", "/api", "/api/views/index.html").c_str()); - EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "/api/", "/api/index.html").c_str()); - EXPECT_STREQ("/tmp/ndex.html", srs_http_fs_fullpath("/tmp/", "/api//", "/api/index.html").c_str()); + SrsHttpMuxEntry e; + e.pattern = "/"; + + SrsHttpFileServer h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); + h.set_path_check(_mock_srs_path_always_exists); + h.entry = &e; + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/index.mp4?start=2", false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w); } if (true) { @@ -211,7 +308,42 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) e.pattern = "/"; SrsHttpFileServer h("/tmp"); - h.set_fs_factory(new MockFileReaderFactory("Hello, world!"))->set_path_check(_mock_srs_path_exists); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); + h.set_path_check(_mock_srs_path_always_exists); + h.entry = &e; + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/index.flv?start=2", false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w); + } + + if (true) { + SrsHttpMuxEntry e; + e.pattern = "/"; + + SrsHttpFileServer h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); + h.set_path_check(_mock_srs_path_always_exists); + h.entry = &e; + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/index.flv", false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w); + } + + if (true) { + SrsHttpMuxEntry e; + e.pattern = "/"; + + SrsHttpFileServer h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); + h.set_path_check(_mock_srs_path_always_exists); h.entry = &e; MockResponseWriter w; @@ -222,6 +354,35 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w); } + if (true) { + SrsHttpMuxEntry e; + e.pattern = "/"; + + SrsHttpFileServer h("/tmp"); + h.set_fs_factory(new MockFileReaderFactory("Hello, world!")); + h.set_path_check(_mock_srs_path_not_exists); + h.entry = &e; + + MockResponseWriter w; + SrsHttpMessage r(NULL, NULL); + HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false)); + + HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r)); + __MOCK_HTTP_EXPECT_STREQ(404, "Not Found", w); + } + + if (true) { + EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp", "/", "/").c_str()); + EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp", "/", "/index.html").c_str()); + EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "/", "/index.html").c_str()); + EXPECT_STREQ("/tmp/ndex.html", srs_http_fs_fullpath("/tmp/", "//", "/index.html").c_str()); + EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "/api", "/api/index.html").c_str()); + EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "ossrs.net/api", "/api/index.html").c_str()); + EXPECT_STREQ("/tmp/views/index.html", srs_http_fs_fullpath("/tmp/", "/api", "/api/views/index.html").c_str()); + EXPECT_STREQ("/tmp/index.html", srs_http_fs_fullpath("/tmp/", "/api/", "/api/index.html").c_str()); + EXPECT_STREQ("/tmp/ndex.html", srs_http_fs_fullpath("/tmp/", "/api//", "/api/index.html").c_str()); + } + if (true) { SrsHttpRedirectHandler h("/api", 500); @@ -239,6 +400,7 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) MockResponseWriter w; HELPER_ASSERT_SUCCESS(h.serve_http(&w, NULL)); __MOCK_HTTP_EXPECT_STREQ(404, "Not Found", w); + EXPECT_TRUE(h.is_not_found()); } } From d9842b037139d9158a932e22ca49e1139521bf9b Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 17 Dec 2019 15:14:59 +0800 Subject: [PATCH 16/28] Refactor HttpResponseWriter.write, default to single text mode. --- trunk/src/app/srs_app_http_static.cpp | 3 +- trunk/src/app/srs_app_http_stream.cpp | 3 + trunk/src/protocol/srs_http_stack.cpp | 3 + trunk/src/protocol/srs_http_stack.hpp | 6 +- trunk/src/service/srs_service_http_conn.cpp | 4 + trunk/src/utest/srs_utest_http.cpp | 101 ++++++++++++++++---- 6 files changed, 96 insertions(+), 24 deletions(-) diff --git a/trunk/src/app/srs_app_http_static.cpp b/trunk/src/app/srs_app_http_static.cpp index 1bfd8bfe6..5be561259 100644 --- a/trunk/src/app/srs_app_http_static.cpp +++ b/trunk/src/app/srs_app_http_static.cpp @@ -117,6 +117,7 @@ srs_error_t SrsVodStream::serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMe // 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) { @@ -170,8 +171,6 @@ 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. diff --git a/trunk/src/app/srs_app_http_stream.cpp b/trunk/src/app/srs_app_http_stream.cpp index 3e5551131..494a50193 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; diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index 4b478603b..abbfe8081 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -446,6 +446,9 @@ 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; diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp index f456c26d5..9044f5549 100644 --- a/trunk/src/protocol/srs_http_stack.hpp +++ b/trunk/src/protocol/srs_http_stack.hpp @@ -150,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"); diff --git a/trunk/src/service/srs_service_http_conn.cpp b/trunk/src/service/srs_service_http_conn.cpp index 8919b6283..748e3f795 100644 --- a/trunk/src/service/srs_service_http_conn.cpp +++ b/trunk/src/service/srs_service_http_conn.cpp @@ -647,6 +647,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); } diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index 8dae41ca5..a53cf2bb4 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -108,6 +108,47 @@ string mock_http_response(int status, string 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 : public ISrsHttpHandler +{ +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()); + } +}; + +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()) + VOID TEST(ProtocolHTTPTest, StatusCode2Text) { EXPECT_STREQ(SRS_CONSTS_HTTP_OK_str, srs_generate_http_status_text(SRS_CONSTS_HTTP_OK).c_str()); @@ -123,10 +164,20 @@ VOID TEST(ProtocolHTTPTest, StatusCode2Text) 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()); } -#define __MOCK_HTTP_EXPECT_STREQ(status, text, w) \ - EXPECT_STREQ(mock_http_response(status, text).c_str(), HELPER_BUFFER2STR(&w.io.out_buffer).c_str()) +VOID TEST(ProtocolHTTPTest, ResponseWriter) +{ + if (true) { + MockResponseWriter w; + + char msg[] = "Hello, world!"; + w.write((char*)msg, sizeof(msg) - 1); + + __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w); + } +} VOID TEST(ProtocolHTTPTest, ResponseHTTPError) { @@ -137,6 +188,18 @@ VOID TEST(ProtocolHTTPTest, ResponseHTTPError) 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) @@ -173,28 +236,24 @@ VOID TEST(ProtocolHTTPTest, HTTPHeader) srs_freep(o); } -class MockFileReaderFactory : public ISrsFileReaderFactory +VOID TEST(ProtocolHTTPTest, HTTPServerMuxer) { -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()); - } -}; + srs_error_t err; -bool _mock_srs_path_always_exists(std::string /*path*/) -{ - return true; -} + if (true) { + SrsHttpServeMux s; + HELPER_ASSERT_SUCCESS(s.initialize()); -bool _mock_srs_path_not_exists(std::string /*path*/) -{ - return false; + 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, VodStreamHandlers) From 2df1dcb05a7c05fc9197a2ccd31df8e9e4c7274a Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 17 Dec 2019 16:01:04 +0800 Subject: [PATCH 17/28] Fix HTTP-FLV and VOD-FLV conflicting bug. --- trunk/src/app/srs_app_http_stream.cpp | 12 ++++++++++++ trunk/src/kernel/srs_kernel_utility.cpp | 11 +++++++++++ trunk/src/kernel/srs_kernel_utility.hpp | 2 ++ trunk/src/utest/srs_utest_kernel.cpp | 8 ++++++++ 4 files changed, 33 insertions(+) diff --git a/trunk/src/app/srs_app_http_stream.cpp b/trunk/src/app/srs_app_http_stream.cpp index 494a50193..05cac25ef 100755 --- a/trunk/src/app/srs_app_http_stream.cpp +++ b/trunk/src/app/srs_app_http_stream.cpp @@ -1049,6 +1049,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/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/utest/srs_utest_kernel.cpp b/trunk/src/utest/srs_utest_kernel.cpp index 7f0479ddb..2843c304e 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; From 1e83da78125e14faa53fcc3f6c99344628d6f6ac Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 17 Dec 2019 16:37:00 +0800 Subject: [PATCH 18/28] Refactor HTTP recv request timeout to 15s. --- trunk/src/app/srs_app_http_conn.cpp | 12 ++++++------ trunk/src/app/srs_app_http_stream.cpp | 5 +++-- trunk/src/kernel/srs_kernel_consts.hpp | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) 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_stream.cpp b/trunk/src/app/srs_app_http_stream.cpp index 05cac25ef..64356c4df 100755 --- a/trunk/src/app/srs_app_http_stream.cpp +++ b/trunk/src/app/srs_app_http_stream.cpp @@ -597,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); @@ -610,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(); @@ -659,7 +660,7 @@ srs_error_t SrsLiveStream::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess return srs_error_wrap(err, "send messages"); } } - + return err; } 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 From 191b07668d91d4a380dadeb6eaa5ad54b0200478 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 17 Dec 2019 16:54:06 +0800 Subject: [PATCH 19/28] Refactor HTTP stream to disconnect client when unpublish --- trunk/src/app/srs_app_conn.cpp | 6 ++++-- trunk/src/app/srs_app_http_stream.cpp | 4 +++- trunk/src/app/srs_app_rtmp_conn.cpp | 4 ++-- trunk/src/app/srs_app_rtsp.cpp | 3 +-- trunk/src/kernel/srs_kernel_error.cpp | 16 ++++++---------- trunk/src/kernel/srs_kernel_error.hpp | 6 ++++-- trunk/src/utest/srs_utest_kernel.cpp | 12 ++++++------ 7 files changed, 26 insertions(+), 25 deletions(-) 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_stream.cpp b/trunk/src/app/srs_app_http_stream.cpp index 64356c4df..1f4064528 100755 --- a/trunk/src/app/srs_app_http_stream.cpp +++ b/trunk/src/app/srs_app_http_stream.cpp @@ -661,7 +661,9 @@ srs_error_t SrsLiveStream::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess } } - 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) 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/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/utest/srs_utest_kernel.cpp b/trunk/src/utest/srs_utest_kernel.cpp index 2843c304e..fcd39dd6f 100644 --- a/trunk/src/utest/srs_utest_kernel.cpp +++ b/trunk/src/utest/srs_utest_kernel.cpp @@ -2641,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) { @@ -2653,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) { From df359f747af0e5ffc2d8593f2d93cf6010c061e0 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 17 Dec 2019 17:30:49 +0800 Subject: [PATCH 20/28] Ignore coverage for json-parser --- README.md | 3 +++ trunk/src/protocol/srs_protocol_json.cpp | 2 ++ 2 files changed, 5 insertions(+) diff --git a/README.md b/README.md index cddc782b8..3d6d8e5c1 100755 --- a/README.md +++ b/README.md @@ -145,6 +145,9 @@ For previous versions, please read: ## V3 changes +* 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 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) From e27cc059e49679ba2ac5506788a3f45bcb1750e9 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 17 Dec 2019 17:37:29 +0800 Subject: [PATCH 21/28] Ignore coverage for http-parser --- trunk/src/protocol/srs_http_stack.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index abbfe8081..5d14de93a 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -958,6 +958,8 @@ string SrsHttpUri::get_uri_field(string uri, void* php_u, int ifield) // For #if !defined(SRS_EXPORT_LIBRTMP) #endif +// LCOV_EXCL_START + ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// @@ -3482,3 +3484,13 @@ http_parser_set_max_header_size(uint32_t size) { max_header_size = size; } +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// + +// LCOV_EXCL_STOP From 81947df819a2943283eefd30e188566297ed2dfd Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 17 Dec 2019 19:09:39 +0800 Subject: [PATCH 22/28] Enhance HTTP response write for final_request. --- README.md | 1 + trunk/src/service/srs_service_http_conn.cpp | 11 ++- trunk/src/utest/srs_utest_http.cpp | 98 ++++++++++++++++++++- 3 files changed, 107 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3d6d8e5c1..9a83d3bda 100755 --- a/README.md +++ b/README.md @@ -145,6 +145,7 @@ For previous versions, please read: ## V3 changes +* 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. diff --git a/trunk/src/service/srs_service_http_conn.cpp b/trunk/src/service/srs_service_http_conn.cpp index 748e3f795..a2fd307ec 100644 --- a/trunk/src/service/srs_service_http_conn.cpp +++ b/trunk/src/service/srs_service_http_conn.cpp @@ -619,10 +619,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) { @@ -666,7 +673,7 @@ srs_error_t SrsHttpResponseWriter::write(char* data, int size) } // ignore NULL content. - if (!data) { + if (!data || size <= 0) { return err; } @@ -811,7 +818,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)); } } diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index a53cf2bb4..9417ff4ad 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -108,6 +108,16 @@ string mock_http_response(int status, string 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: @@ -149,6 +159,9 @@ bool _mock_srs_path_not_exists(std::string /*path*/) #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()); @@ -169,6 +182,7 @@ VOID TEST(ProtocolHTTPTest, ResponseDetect) VOID TEST(ProtocolHTTPTest, ResponseWriter) { + // If directly write string, response with content-length. if (true) { MockResponseWriter w; @@ -177,6 +191,70 @@ VOID TEST(ProtocolHTTPTest, ResponseWriter) __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) @@ -356,7 +434,24 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) MockResponseWriter w; SrsHttpMessage r(NULL, NULL); - HELPER_ASSERT_SUCCESS(r.set_url("/index.mp4?start=2", false)); + 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); @@ -444,6 +539,7 @@ VOID TEST(ProtocolHTTPTest, BasicHandlers) if (true) { SrsHttpRedirectHandler h("/api", 500); + EXPECT_FALSE(h.is_not_found()); MockResponseWriter w; SrsHttpMessage r(NULL, NULL); From ec0fb82c0e511730d04a0b790f5da83c7256e553 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 17 Dec 2019 19:39:36 +0800 Subject: [PATCH 23/28] Improve test coverage for http server mux. --- trunk/src/protocol/srs_http_stack.cpp | 2 +- trunk/src/utest/srs_utest_http.cpp | 208 +++++++++++++++++++++++++- 2 files changed, 208 insertions(+), 2 deletions(-) diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index 5d14de93a..67d7e6e0a 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -724,7 +724,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; diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index 9417ff4ad..d70750ed2 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -132,7 +132,7 @@ public: } }; -class MockHttpHandler : public ISrsHttpHandler +class MockHttpHandler : virtual public ISrsHttpHandler, virtual public ISrsHttpMatchHijacker { public: string bytes; @@ -144,6 +144,12 @@ public: 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*/) @@ -318,6 +324,206 @@ VOID TEST(ProtocolHTTPTest, HTTPServerMuxer) { 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 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("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); + } + if (true) { SrsHttpServeMux s; HELPER_ASSERT_SUCCESS(s.initialize()); From dc1afc142f4a95402993053242bfd1a53929c513 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 17 Dec 2019 20:52:10 +0800 Subject: [PATCH 24/28] Fix HTTP CORS bug when sending response for OPTIONS. 3.0.72 --- README.md | 1 + trunk/src/app/srs_app_config.cpp | 2 +- trunk/src/core/srs_core.hpp | 2 +- trunk/src/protocol/srs_http_stack.cpp | 6 +- trunk/src/protocol/srs_rtsp_stack.cpp | 4 +- trunk/src/service/srs_service_http_conn.cpp | 12 +- trunk/src/service/srs_service_http_conn.hpp | 4 +- trunk/src/utest/srs_utest_http.cpp | 335 +++++++++++++++++++- 8 files changed, 345 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 9a83d3bda..7ceea8d6f 100755 --- a/README.md +++ b/README.md @@ -145,6 +145,7 @@ 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. 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/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/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index 67d7e6e0a..63a70b8d7 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -823,8 +823,6 @@ 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) { SrsHttpHeader* h = r->header(); @@ -848,9 +846,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); 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 a2fd307ec..2cc6196b4 100644 --- a/trunk/src/service/srs_service_http_conn.cpp +++ b/trunk/src/service/srs_service_http_conn.cpp @@ -264,7 +264,7 @@ SrsHttpMessage::SrsHttpMessage(ISrsReader* reader, SrsFastStream* buffer) : ISrs _method = 0; _status = 0; - _content_length = 0; + _content_length = -1; _keep_alive = true; } @@ -278,7 +278,9 @@ void SrsHttpMessage::set_basic(uint8_t method, uint16_t status, int64_t content_ { _method = method; _status = status; - _content_length = content_length; + if (_content_length == -1) { + _content_length = content_length; + } } void SrsHttpMessage::set_header(SrsHttpHeader* header, bool keep_alive) @@ -288,6 +290,12 @@ void SrsHttpMessage::set_header(SrsHttpHeader* header, bool keep_alive) // whether chunked. 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) diff --git a/trunk/src/service/srs_service_http_conn.hpp b/trunk/src/service/srs_service_http_conn.hpp index 2313b0207..cdd9524cf 100644 --- a/trunk/src/service/srs_service_http_conn.hpp +++ b/trunk/src/service/srs_service_http_conn.hpp @@ -126,12 +126,14 @@ private: // The method in QueryString will override the HTTP method. std::string jsonp_method; public: - SrsHttpMessage(ISrsReader* reader, SrsFastStream* buffer); + 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 set_url(std::string url, bool allow_jsonp); diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index d70750ed2..fa7b8abaa 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -320,7 +320,112 @@ VOID TEST(ProtocolHTTPTest, HTTPHeader) srs_freep(o); } -VOID TEST(ProtocolHTTPTest, HTTPServerMuxer) +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; + + // 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; @@ -362,6 +467,51 @@ VOID TEST(ProtocolHTTPTest, HTTPServerMuxer) __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; + + // 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; @@ -497,30 +647,39 @@ VOID TEST(ProtocolHTTPTest, HTTPServerMuxer) SrsHttpServeMux s; HELPER_ASSERT_SUCCESS(s.initialize()); - MockHttpHandler hroot("Hello, world!"); - s.hijack(&hroot); - s.unhijack(&hroot); + 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(404, "Not Found", w); + __MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w); } +} + +VOID TEST(ProtocolHTTPTest, HTTPServerMuxerCORS) +{ + srs_error_t err; + // If CORS not enabled, directly response any request. if (true) { SrsHttpServeMux s; HELPER_ASSERT_SUCCESS(s.initialize()); - MockHttpHandler hroot("Hello, world!"); - s.hijack(&hroot); + 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)); - HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r)); + 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); } @@ -535,7 +694,10 @@ VOID TEST(ProtocolHTTPTest, HTTPServerMuxer) SrsHttpMessage r(NULL, NULL); HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false)); - HELPER_ASSERT_SUCCESS(s.serve_http(&w, &r)); + 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); } } @@ -623,12 +785,82 @@ VOID TEST(ProtocolHTTPTest, VodStreamHandlers) 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 = "/"; @@ -966,3 +1198,88 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageParser) srs_freep(msg); } } + +VOID TEST(ProtocolHTTPTest, HTTPMessageUpdate) +{ + // The host is overwrite by header. + if (true) { + SrsHttpHeader h; + h.set("Host", "ossrs.net"); + + SrsHttpMessage m; + 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); + 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()); + } +} From 43a5cea1581e703d6740642eba78cac9ce98d283 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 17 Dec 2019 21:03:28 +0800 Subject: [PATCH 25/28] Improve test coverage for HTTP CORS --- trunk/src/utest/srs_utest_http.cpp | 66 +++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index fa7b8abaa..bdcbe58d1 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -95,6 +95,10 @@ srs_error_t MockResponseWriter::filter(SrsHttpHeader* h) 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; } @@ -663,7 +667,27 @@ VOID TEST(ProtocolHTTPTest, HTTPServerMuxerCORS) { srs_error_t err; - // If CORS not enabled, directly response any request. + // 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()); @@ -676,6 +700,26 @@ VOID TEST(ProtocolHTTPTest, HTTPServerMuxerCORS) 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)); @@ -683,6 +727,26 @@ VOID TEST(ProtocolHTTPTest, HTTPServerMuxerCORS) __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()); From 6c50d85671f21d009dae34919715443eb12f52fb Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 17 Dec 2019 21:08:18 +0800 Subject: [PATCH 26/28] Improve test coverage for http message --- trunk/src/service/srs_service_http_conn.cpp | 5 ++++ trunk/src/service/srs_service_http_conn.hpp | 1 + trunk/src/utest/srs_utest_http.cpp | 33 +++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/trunk/src/service/srs_service_http_conn.cpp b/trunk/src/service/srs_service_http_conn.cpp index 2cc6196b4..3d8d1df06 100644 --- a/trunk/src/service/srs_service_http_conn.cpp +++ b/trunk/src/service/srs_service_http_conn.cpp @@ -460,6 +460,11 @@ string SrsHttpMessage::host() return _uri->get_host(); } +int SrsHttpMessage::port() +{ + return _uri->get_port(); +} + string SrsHttpMessage::path() { return _uri->get_path(); diff --git a/trunk/src/service/srs_service_http_conn.hpp b/trunk/src/service/srs_service_http_conn.hpp index cdd9524cf..997e26b66 100644 --- a/trunk/src/service/srs_service_http_conn.hpp +++ b/trunk/src/service/srs_service_http_conn.hpp @@ -163,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(); diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index bdcbe58d1..c1c10ffea 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -1265,6 +1265,39 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageParser) VOID TEST(ProtocolHTTPTest, HTTPMessageUpdate) { + // Port use 80 if error. + if (true) { + SrsHttpHeader h; + h.set("Host", "ossrs.net:-1"); + + SrsHttpMessage m; + m.set_header(&h, false); + 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); + 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); + m.set_url("/api/v1", false); + EXPECT_EQ(8080, m.port()); + } + // The host is overwrite by header. if (true) { SrsHttpHeader h; From b247c9759a07f578b2cddbc9190286fdbdc4b137 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 17 Dec 2019 21:17:44 +0800 Subject: [PATCH 27/28] Improve test coverage for http handler --- trunk/src/utest/srs_utest_http.cpp | 44 ++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index c1c10ffea..b43bdaaac 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -507,6 +507,27 @@ 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; @@ -1265,14 +1286,25 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageParser) VOID TEST(ProtocolHTTPTest, HTTPMessageUpdate) { - // Port use 80 if error. + srs_error_t err; + if (true) { SrsHttpHeader h; h.set("Host", "ossrs.net:-1"); SrsHttpMessage m; m.set_header(&h, false); - m.set_url("/api/v1", 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()); } @@ -1283,7 +1315,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageUpdate) SrsHttpMessage m; m.set_header(&h, false); - m.set_url("/api/v1", false); + HELPER_ASSERT_SUCCESS(m.set_url("/api/v1", false)); EXPECT_EQ(80, m.port()); } @@ -1294,7 +1326,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageUpdate) SrsHttpMessage m; m.set_header(&h, false); - m.set_url("/api/v1", false); + HELPER_ASSERT_SUCCESS(m.set_url("/api/v1", false)); EXPECT_EQ(8080, m.port()); } @@ -1304,7 +1336,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageUpdate) h.set("Host", "ossrs.net"); SrsHttpMessage m; - m.set_url("/api/v1", false); + HELPER_ASSERT_SUCCESS(m.set_url("/api/v1", false)); m.set_header(&h, false); EXPECT_STRNE("ossrs.net", m.host().c_str()); } @@ -1316,7 +1348,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageUpdate) SrsHttpMessage m; m.set_header(&h, false); - m.set_url("/api/v1", false); + HELPER_ASSERT_SUCCESS(m.set_url("/api/v1", false)); EXPECT_STREQ("ossrs.net", m.host().c_str()); } From 216a23f70903cc624071d692ade6bbcfaf777869 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 17 Dec 2019 21:24:24 +0800 Subject: [PATCH 28/28] Fix the http implicit handler bug --- trunk/src/protocol/srs_http_stack.cpp | 9 +++------ trunk/src/utest/srs_utest_http.cpp | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index 63a70b8d7..e2d1d1e57 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -667,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(); diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index b43bdaaac..c1d49fcc4 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -381,6 +381,25 @@ 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;