diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index a8d5d78c6..c7ac344c9 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog for SRS. ## SRS 5.0 Changelog +* v5.0, 2022-09-30, GB28181: Refine HTTP parser to support SIP. v5.0.70 * v5.0, 2022-09-30, Kernel: Support lazy sweeping simple GC. v5.0.69 * v5.0, 2022-09-30, HTTP: Support HTTP header in creating order. v5.0.68 * v5.0, 2022-09-27, For [#2899](https://github.com/ossrs/srs/issues/2899): API: Support exporter for Prometheus. v5.0.67 diff --git a/trunk/src/core/srs_core_version5.hpp b/trunk/src/core/srs_core_version5.hpp index ca787cc1d..eb2d9c323 100644 --- a/trunk/src/core/srs_core_version5.hpp +++ b/trunk/src/core/srs_core_version5.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 5 #define VERSION_MINOR 0 -#define VERSION_REVISION 69 +#define VERSION_REVISION 70 #endif diff --git a/trunk/src/protocol/srs_protocol_http_client.cpp b/trunk/src/protocol/srs_protocol_http_client.cpp index 4e696c4dc..8e75c4857 100644 --- a/trunk/src/protocol/srs_protocol_http_client.cpp +++ b/trunk/src/protocol/srs_protocol_http_client.cpp @@ -324,6 +324,7 @@ srs_error_t SrsHttpClient::post(string path, string req, ISrsHttpMessage** ppmsg path = "/"; } + // TODO: FIXME: Use SrsHttpMessageWriter, never use stringstream and headers. // send POST request to uri // POST %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %d\r\n\r\n%s std::stringstream ss; diff --git a/trunk/src/protocol/srs_protocol_http_conn.cpp b/trunk/src/protocol/srs_protocol_http_conn.cpp index c679d6aea..33af49806 100644 --- a/trunk/src/protocol/srs_protocol_http_conn.cpp +++ b/trunk/src/protocol/srs_protocol_http_conn.cpp @@ -28,6 +28,7 @@ SrsHttpParser::SrsHttpParser() p_body_start = p_header_tail = NULL; type_ = HTTP_REQUEST; + parsed_type_ = HTTP_BOTH; } SrsHttpParser::~SrsHttpParser() @@ -70,7 +71,7 @@ srs_error_t SrsHttpParser::parse_message(ISrsReader* reader, ISrsHttpMessage** p *ppmsg = NULL; - // Reset request data. + // Reset parser data and state. state = SrsHttpParseStateInit; memset(&hp_header, 0, sizeof(http_parser)); // The body that we have read from cache. @@ -93,6 +94,8 @@ srs_error_t SrsHttpParser::parse_message(ISrsReader* reader, ISrsHttpMessage** p // and the message fail. // @note You can comment the bellow line, the utest will fail. http_parser_init(&parser, type_); + // Reset the parsed type. + parsed_type_ = HTTP_BOTH; // callback object ptr. parser.data = (void*)this; @@ -105,10 +108,10 @@ srs_error_t SrsHttpParser::parse_message(ISrsReader* reader, ISrsHttpMessage** p SrsHttpMessage* msg = new SrsHttpMessage(reader, buffer); // Initialize the basic information. - msg->set_basic(hp_header.type, hp_header.method, hp_header.status_code, hp_header.content_length); + msg->set_basic(hp_header.type, (http_method)hp_header.method, (http_status)hp_header.status_code, hp_header.content_length); msg->set_header(header, http_should_keep_alive(&hp_header)); // For HTTP response, no url. - if (type_ != HTTP_RESPONSE && (err = msg->set_url(url, jsonp)) != srs_success) { + if (parsed_type_ != HTTP_RESPONSE && (err = msg->set_url(url, jsonp)) != srs_success) { srs_freep(msg); return srs_error_wrap(err, "set url=%s, jsonp=%d", url.c_str(), jsonp); } @@ -177,8 +180,12 @@ int SrsHttpParser::on_message_begin(http_parser* parser) { SrsHttpParser* obj = (SrsHttpParser*)parser->data; srs_assert(obj); - + + // Now, we start to parse HTTP message. obj->state = SrsHttpParseStateStart; + + // If we set to HTTP_BOTH, the type is detected and speicifed by parser. + obj->parsed_type_ = (http_parser_type)parser->type; srs_info("***MESSAGE BEGIN***"); @@ -305,9 +312,9 @@ SrsHttpMessage::SrsHttpMessage(ISrsReader* reader, SrsFastStream* buffer) : ISrs jsonp = false; // As 0 is DELETE, so we use GET as default. - _method = SRS_CONSTS_HTTP_GET; + _method = (http_method)SRS_CONSTS_HTTP_GET; // 200 is ok. - _status = SRS_CONSTS_HTTP_OK; + _status = (http_status)SRS_CONSTS_HTTP_OK; // -1 means infinity chunked mode. _content_length = -1; // From HTTP/1.1, default to keep alive. @@ -323,7 +330,7 @@ SrsHttpMessage::~SrsHttpMessage() srs_freep(_uri); } -void SrsHttpMessage::set_basic(uint8_t type, uint8_t method, uint16_t status, int64_t content_length) +void SrsHttpMessage::set_basic(uint8_t type, http_method method, http_status status, int64_t content_length) { type_ = type; _method = method; @@ -376,6 +383,11 @@ srs_error_t SrsHttpMessage::set_url(string url, bool allow_jsonp) host = srs_get_public_internet_address(true); } + // The url must starts with slash if no schema. For example, SIP request line starts with "sip". + if (!host.empty() && !srs_string_starts_with(_url, "/")) { + host += "/"; + } + if (!host.empty()) { uri = "http://" + host + _url; } @@ -425,6 +437,11 @@ string SrsHttpMessage::schema() return schema_; } +uint8_t SrsHttpMessage::message_type() +{ + return type_; +} + uint8_t SrsHttpMessage::method() { if (jsonp && !jsonp_method.empty()) { @@ -654,33 +671,41 @@ ISrsHttpHeaderFilter::~ISrsHttpHeaderFilter() { } -SrsHttpResponseWriter::SrsHttpResponseWriter(ISrsProtocolReadWriter* io) +ISrsHttpFirstLineWriter::ISrsHttpFirstLineWriter() +{ +} + +ISrsHttpFirstLineWriter::~ISrsHttpFirstLineWriter() +{ +} + +SrsHttpMessageWriter::SrsHttpMessageWriter(ISrsProtocolReadWriter* io, ISrsHttpFirstLineWriter* flw) { skt = io; hdr = new SrsHttpHeader(); - header_wrote = false; - status = SRS_CONSTS_HTTP_OK; + header_wrote_ = false; content_length = -1; written = 0; header_sent = false; nb_iovss_cache = 0; iovss_cache = NULL; - hf = NULL; + hf_ = NULL; + flw_ = flw; } -SrsHttpResponseWriter::~SrsHttpResponseWriter() +SrsHttpMessageWriter::~SrsHttpMessageWriter() { srs_freep(hdr); srs_freepa(iovss_cache); } -srs_error_t SrsHttpResponseWriter::final_request() +srs_error_t SrsHttpMessageWriter::final_request() { srs_error_t err = srs_success; // write the header data in memory. - if (!header_wrote) { - write_header(SRS_CONSTS_HTTP_OK); + if (!header_wrote_) { + flw_->write_default_header(); } // whatever header is wrote, we should try to send header. @@ -700,24 +725,24 @@ srs_error_t SrsHttpResponseWriter::final_request() return write(NULL, 0); } -SrsHttpHeader* SrsHttpResponseWriter::header() +SrsHttpHeader* SrsHttpMessageWriter::header() { return hdr; } -srs_error_t SrsHttpResponseWriter::write(char* data, int size) +srs_error_t SrsHttpMessageWriter::write(char* data, int size) { srs_error_t err = srs_success; // write the header data in memory. - if (!header_wrote) { + if (!header_wrote_) { if (hdr->content_type().empty()) { hdr->set_content_type("text/plain; charset=utf-8"); } if (hdr->content_length() == -1) { hdr->set_content_length(size); } - write_header(SRS_CONSTS_HTTP_OK); + flw_->write_default_header(); } // whatever header is wrote, we should try to send header. @@ -765,12 +790,12 @@ srs_error_t SrsHttpResponseWriter::write(char* data, int size) return err; } -srs_error_t SrsHttpResponseWriter::writev(const iovec* iov, int iovcnt, ssize_t* pnwrite) +srs_error_t SrsHttpMessageWriter::writev(const iovec* iov, int iovcnt, ssize_t* pnwrite) { srs_error_t err = srs_success; // when header not ready, or not chunked, send one by one. - if (!header_wrote || content_length != -1) { + if (!header_wrote_ || content_length != -1) { ssize_t nwrite = 0; for (int i = 0; i < iovcnt; i++) { nwrite += iov[i].iov_len; @@ -848,21 +873,16 @@ srs_error_t SrsHttpResponseWriter::writev(const iovec* iov, int iovcnt, ssize_t* return err; } -void SrsHttpResponseWriter::write_header(int code) +void SrsHttpMessageWriter::write_header() { - if (header_wrote) { - srs_warn("http: multiple write_header calls, code=%d", code); - return; - } - - header_wrote = true; - status = code; + if (header_wrote_) return; + header_wrote_ = true; // parse the content length from header. content_length = hdr->content_length(); } -srs_error_t SrsHttpResponseWriter::send_header(char* data, int size) +srs_error_t SrsHttpMessageWriter::send_header(char* data, int size) { srs_error_t err = srs_success; @@ -872,16 +892,10 @@ srs_error_t SrsHttpResponseWriter::send_header(char* data, int size) header_sent = true; std::stringstream ss; - - // status_line - ss << "HTTP/1.1 " << status << " " - << srs_generate_http_status_text(status) << SRS_HTTP_CRLF; - - // detect content type - if (srs_go_http_body_allowd(status)) { - if (data && hdr->content_type().empty()) { - hdr->set_content_type(srs_go_http_detect(data, size)); - } + + // First line, the request line or status line. + if ((err = flw_->build_first_line(ss, data, size)) != srs_success) { + return srs_error_wrap(err, "first line"); } // set server if not set. @@ -900,7 +914,7 @@ srs_error_t SrsHttpResponseWriter::send_header(char* data, int size) } // Filter the header before writing it. - if (hf && ((err = hf->filter(hdr)) != srs_success)) { + if (hf_ && ((err = hf_->filter(hdr)) != srs_success)) { return srs_error_wrap(err, "filter header"); } @@ -914,6 +928,151 @@ srs_error_t SrsHttpResponseWriter::send_header(char* data, int size) return skt->write((void*)buf.c_str(), buf.length(), NULL); } +bool SrsHttpMessageWriter::header_wrote() +{ + return header_wrote_; +} + +void SrsHttpMessageWriter::set_header_filter(ISrsHttpHeaderFilter* hf) +{ + hf_ = hf; +} + +SrsHttpResponseWriter::SrsHttpResponseWriter(ISrsProtocolReadWriter* io) +{ + writer_ = new SrsHttpMessageWriter(io, this); + status = SRS_CONSTS_HTTP_OK; +} + +SrsHttpResponseWriter::~SrsHttpResponseWriter() +{ + srs_freep(writer_); +} + +void SrsHttpResponseWriter::set_header_filter(ISrsHttpHeaderFilter* hf) +{ + writer_->set_header_filter(hf); +} + +srs_error_t SrsHttpResponseWriter::final_request() +{ + return writer_->final_request(); +} + +SrsHttpHeader* SrsHttpResponseWriter::header() +{ + return writer_->header(); +} + +srs_error_t SrsHttpResponseWriter::write(char* data, int size) +{ + return writer_->write(data, size); +} + +srs_error_t SrsHttpResponseWriter::writev(const iovec* iov, int iovcnt, ssize_t* pnwrite) +{ + return writer_->writev(iov, iovcnt, pnwrite); +} + +void SrsHttpResponseWriter::write_header(int code) +{ + if (writer_->header_wrote()) { + srs_warn("http: multiple write_header calls, status=%d, code=%d", status, code); + return; + } + + status = code; + return writer_->write_header(); +} + +srs_error_t SrsHttpResponseWriter::build_first_line(std::stringstream& ss, char* data, int size) +{ + srs_error_t err = srs_success; + + // Write status line for response. + ss << "HTTP/1.1 " << status << " " << srs_generate_http_status_text(status) << SRS_HTTP_CRLF; + + // Try to detect content type from response body data. + SrsHttpHeader* hdr = writer_->header(); + if (srs_go_http_body_allowd(status)) { + if (data && hdr->content_type().empty()) { + hdr->set_content_type(srs_go_http_detect(data, size)); + } + } + + return err; +} + +void SrsHttpResponseWriter::write_default_header() +{ + write_header(SRS_CONSTS_HTTP_OK); +} + +SrsHttpRequestWriter::SrsHttpRequestWriter(ISrsProtocolReadWriter* io) +{ + writer_ = new SrsHttpMessageWriter(io, this); + method_ = NULL; + path_ = NULL; +} + +SrsHttpRequestWriter::~SrsHttpRequestWriter() +{ + srs_freep(writer_); +} + +srs_error_t SrsHttpRequestWriter::final_request() +{ + return writer_->final_request(); +} + +SrsHttpHeader* SrsHttpRequestWriter::header() +{ + return writer_->header(); +} + +srs_error_t SrsHttpRequestWriter::write(char* data, int size) +{ + return writer_->write(data, size); +} + +srs_error_t SrsHttpRequestWriter::writev(const iovec* iov, int iovcnt, ssize_t* pnwrite) +{ + return writer_->writev(iov, iovcnt, pnwrite); +} + +void SrsHttpRequestWriter::write_header(const char* method, const char* path) +{ + if (writer_->header_wrote()) { + srs_warn("http: multiple write_header calls, current=%s(%s), now=%s(%s)", method_, path_, method, path); + return; + } + + method_ = method; + path_ = path; + return writer_->write_header(); +} + +srs_error_t SrsHttpRequestWriter::build_first_line(std::stringstream& ss, char* data, int size) +{ + srs_error_t err = srs_success; + + // Write status line for response. + ss << method_ << " " << path_ << " HTTP/1.1" << SRS_HTTP_CRLF; + + // Try to detect content type from request body data. + SrsHttpHeader* hdr = writer_->header(); + if (data && hdr->content_type().empty()) { + hdr->set_content_type(srs_go_http_detect(data, size)); + } + + return err; +} + +void SrsHttpRequestWriter::write_default_header() +{ + write_header("GET", "/"); +} + SrsHttpResponseReader::SrsHttpResponseReader(SrsHttpMessage* msg, ISrsReader* reader, SrsFastStream* body) { skt = reader; diff --git a/trunk/src/protocol/srs_protocol_http_conn.hpp b/trunk/src/protocol/srs_protocol_http_conn.hpp index 700be1045..4897f113a 100644 --- a/trunk/src/protocol/srs_protocol_http_conn.hpp +++ b/trunk/src/protocol/srs_protocol_http_conn.hpp @@ -10,6 +10,7 @@ #include #include +#include #include @@ -39,6 +40,7 @@ private: std::string url; SrsHttpHeader* header; enum http_parser_type type_; + enum http_parser_type parsed_type_; private: // Point to the start of body. const char* p_body_start; @@ -92,8 +94,8 @@ private: // enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; uint8_t type_; // The HTTP method defined by HTTP_METHOD_MAP - uint8_t _method; - uint16_t _status; + http_method _method; + http_status _status; int64_t _content_length; private: // The http headers @@ -123,7 +125,7 @@ public: 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 type, uint8_t method, uint16_t status, int64_t content_length); + virtual void set_basic(uint8_t type, http_method method, http_status 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); @@ -138,6 +140,7 @@ public: public: // The schema, http or https. virtual std::string schema(); + virtual uint8_t message_type(); virtual uint8_t method(); virtual uint16_t status_code(); // The method helpers. @@ -198,24 +201,38 @@ public: virtual srs_error_t filter(SrsHttpHeader* h) = 0; }; -// Response writer use st socket -class SrsHttpResponseWriter : public ISrsHttpResponseWriter +class ISrsHttpFirstLineWriter +{ +public: + ISrsHttpFirstLineWriter(); + virtual ~ISrsHttpFirstLineWriter(); +public: + // Build first line of HTTP message to ss. Note that data with size of bytes is the body to write, which enables us + // to setup the header by detecting the body, and it might be NULL. + virtual srs_error_t build_first_line(std::stringstream& ss, char* data, int size) = 0; + // Write a default header line if user does not specify one. + virtual void write_default_header() = 0; +}; + +// Message writer use st socket, for writing HTTP request or response, which is only different at the first line. For +// HTTP request, the first line is RequestLine. While for HTTP response, it's StatusLine. +class SrsHttpMessageWriter { private: ISrsProtocolReadWriter* skt; SrsHttpHeader* hdr; // Before writing header, there is a chance to filter it, // such as remove some headers or inject new. - ISrsHttpHeaderFilter* hf; + ISrsHttpHeaderFilter* hf_; + // The first line writer. + ISrsHttpFirstLineWriter* flw_; private: char header_cache[SRS_HTTP_HEADER_CACHE_SIZE]; iovec* iovss_cache; int nb_iovss_cache; private: // Reply header has been (logically) written - bool header_wrote; - // The status code passed to WriteHeader - int status; + bool header_wrote_; private: // The explicitly-declared Content-Length; or -1 int64_t content_length; @@ -227,16 +244,68 @@ private: // (*response).wroteHeader, which tells only whether it was // logically written. bool header_sent; +public: + SrsHttpMessageWriter(ISrsProtocolReadWriter* io, ISrsHttpFirstLineWriter* flw); + virtual ~SrsHttpMessageWriter(); +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(); + virtual srs_error_t send_header(char* data, int size); +public: + bool header_wrote(); + void set_header_filter(ISrsHttpHeaderFilter* hf); +}; + +// Response writer use st socket +class SrsHttpResponseWriter : public ISrsHttpResponseWriter, public ISrsHttpFirstLineWriter +{ +protected: + SrsHttpMessageWriter* writer_; + // The status code passed to WriteHeader, for response only. + int status; public: SrsHttpResponseWriter(ISrsProtocolReadWriter* io); virtual ~SrsHttpResponseWriter(); +public: + void set_header_filter(ISrsHttpHeaderFilter* hf); +// Interface ISrsHttpResponseWriter 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); - virtual srs_error_t send_header(char* data, int size); +// Interface ISrsHttpFirstLineWriter +public: + virtual srs_error_t build_first_line(std::stringstream& ss, char* data, int size); + virtual void write_default_header(); +}; + +// Request writer use st socket +class SrsHttpRequestWriter : public ISrsHttpRequestWriter, public ISrsHttpFirstLineWriter +{ +protected: + SrsHttpMessageWriter* writer_; + // The method and path passed to WriteHeader, for request only. + const char* method_; + const char* path_; +public: + SrsHttpRequestWriter(ISrsProtocolReadWriter* io); + virtual ~SrsHttpRequestWriter(); +// Interface ISrsHttpResponseWriter +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(const char* method, const char* path); +// Interface ISrsHttpFirstLineWriter +public: + virtual srs_error_t build_first_line(std::stringstream& ss, char* data, int size); + virtual void write_default_header(); }; // Response reader use st socket. diff --git a/trunk/src/protocol/srs_protocol_http_stack.cpp b/trunk/src/protocol/srs_protocol_http_stack.cpp index b9b88d6f8..ac579d4da 100644 --- a/trunk/src/protocol/srs_protocol_http_stack.cpp +++ b/trunk/src/protocol/srs_protocol_http_stack.cpp @@ -255,6 +255,14 @@ ISrsHttpResponseReader::~ISrsHttpResponseReader() { } +ISrsHttpRequestWriter::ISrsHttpRequestWriter() +{ +} + +ISrsHttpRequestWriter::~ISrsHttpRequestWriter() +{ +} + ISrsHttpHandler::ISrsHttpHandler() { entry = NULL; @@ -1696,6 +1704,9 @@ enum state , s_res_HT , s_res_HTT , s_res_HTTP + , s_res_S /* SIP https://www.ietf.org/rfc/rfc3261.html */ + , s_res_SI /* SIP https://www.ietf.org/rfc/rfc3261.html */ + , s_res_SIP /* SIP https://www.ietf.org/rfc/rfc3261.html */ , s_res_http_major , s_res_http_dot , s_res_http_minor @@ -1728,6 +1739,9 @@ enum state , s_req_http_HTTP , s_req_http_I , s_req_http_IC + , s_req_http_S /* SIP https://www.ietf.org/rfc/rfc3261.html */ + , s_req_http_SI /* SIP https://www.ietf.org/rfc/rfc3261.html */ + , s_req_http_SIP /* SIP https://www.ietf.org/rfc/rfc3261.html */ , s_req_http_major , s_req_http_dot , s_req_http_minor @@ -2143,6 +2157,11 @@ reexecute: if (ch == 'H') { UPDATE_STATE(s_res_or_resp_H); + CALLBACK_NOTIFY(message_begin); + } else if (ch == 'S') { /* SIP https://www.ietf.org/rfc/rfc3261.html */ + parser->type = HTTP_RESPONSE; + UPDATE_STATE(s_res_S); + CALLBACK_NOTIFY(message_begin); } else { parser->type = HTTP_REQUEST; @@ -2179,6 +2198,8 @@ reexecute: if (ch == 'H') { UPDATE_STATE(s_res_H); + } else if (ch == 'S') { /* SIP https://www.ietf.org/rfc/rfc3261.html */ + UPDATE_STATE(s_res_S); } else { SET_ERRNO(HPE_INVALID_CONSTANT); goto error; @@ -2208,6 +2229,24 @@ reexecute: UPDATE_STATE(s_res_http_major); break; + /* SIP https://www.ietf.org/rfc/rfc3261.html */ + case s_res_S: + STRICT_CHECK(ch != 'I'); + UPDATE_STATE(s_res_SI); + break; + + /* SIP https://www.ietf.org/rfc/rfc3261.html */ + case s_res_SI: + STRICT_CHECK(ch != 'P'); + UPDATE_STATE(s_res_SIP); + break; + + /* SIP https://www.ietf.org/rfc/rfc3261.html */ + case s_res_SIP: + STRICT_CHECK(ch != '/'); + UPDATE_STATE(s_res_http_major); + break; + case s_res_http_major: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); @@ -2342,20 +2381,21 @@ reexecute: parser->method = (enum http_method) 0; parser->index = 1; switch (ch) { - case 'A': parser->method = HTTP_ACL; break; - case 'B': parser->method = HTTP_BIND; break; + case 'A': parser->method = HTTP_ACL; /* or ACK */ break; + case 'B': parser->method = HTTP_BIND; /* or BYE */ break; case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; case 'D': parser->method = HTTP_DELETE; break; case 'G': parser->method = HTTP_GET; break; case 'H': parser->method = HTTP_HEAD; break; + case 'I': parser->method = HTTP_INVITE; break; /* SIP https://www.ietf.org/rfc/rfc3261.html */ case 'L': parser->method = HTTP_LOCK; /* or LINK */ break; - case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break; + case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR, MESSAGE */ break; case 'N': parser->method = HTTP_NOTIFY; break; case 'O': parser->method = HTTP_OPTIONS; break; case 'P': parser->method = HTTP_POST; /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ break; - case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break; + case 'R': parser->method = HTTP_REPORT; /* or REBIND, REGISTER */ break; case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH, SOURCE */ break; case 'T': parser->method = HTTP_TRACE; break; case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break; @@ -2394,6 +2434,8 @@ reexecute: XX(POST, 1, 'A', PATCH) XX(POST, 1, 'R', PROPFIND) XX(PUT, 2, 'R', PURGE) + XX(ACL, 2, 'K', ACK) /* SIP https://www.ietf.org/rfc/rfc3261.html */ + XX(BIND, 1, 'Y', BYE) /* SIP https://www.ietf.org/rfc/rfc3261.html */ XX(CONNECT, 1, 'H', CHECKOUT) XX(CONNECT, 2, 'P', COPY) XX(MKCOL, 1, 'O', MOVE) @@ -2401,9 +2443,11 @@ reexecute: XX(MKCOL, 1, '-', MSEARCH) XX(MKCOL, 2, 'A', MKACTIVITY) XX(MKCOL, 3, 'A', MKCALENDAR) + XX(MERGE, 2, 'S', MESSAGE) /* SIP https://www.ietf.org/rfc/rfc3261.html */ XX(SUBSCRIBE, 1, 'E', SEARCH) XX(SUBSCRIBE, 1, 'O', SOURCE) XX(REPORT, 2, 'B', REBIND) + XX(REPORT, 2, 'G', REGISTER) /* SIP https://www.ietf.org/rfc/rfc3261.html */ XX(PROPFIND, 4, 'P', PROPPATCH) XX(LOCK, 1, 'I', LINK) XX(UNLOCK, 2, 'S', UNSUBSCRIBE) @@ -2432,6 +2476,11 @@ reexecute: UPDATE_STATE(s_req_server_start); } + /* SIP https://www.ietf.org/rfc/rfc3261.html */ + if (parser->method >= HTTP_REGISTER && parser->method <= HTTP_BYE) { + UPDATE_STATE(s_req_path); + } + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); @@ -2503,6 +2552,9 @@ reexecute: case 'H': UPDATE_STATE(s_req_http_H); break; + case 'S': /* SIP https://www.ietf.org/rfc/rfc3261.html */ + UPDATE_STATE(s_req_http_S); /* SIP https://www.ietf.org/rfc/rfc3261.html */ + break; /* SIP https://www.ietf.org/rfc/rfc3261.html */ case 'I': if (parser->method == HTTP_SOURCE) { UPDATE_STATE(s_req_http_I); @@ -2545,6 +2597,24 @@ reexecute: UPDATE_STATE(s_req_http_major); break; + /* SIP https://www.ietf.org/rfc/rfc3261.html */ + case s_req_http_S: + STRICT_CHECK(ch != 'I'); + UPDATE_STATE(s_req_http_SI); + break; + + /* SIP https://www.ietf.org/rfc/rfc3261.html */ + case s_req_http_SI: + STRICT_CHECK(ch != 'P'); + UPDATE_STATE(s_req_http_SIP); + break; + + /* SIP https://www.ietf.org/rfc/rfc3261.html */ + case s_req_http_SIP: + STRICT_CHECK(ch != '/'); + UPDATE_STATE(s_req_http_major); + break; + case s_req_http_major: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); diff --git a/trunk/src/protocol/srs_protocol_http_stack.hpp b/trunk/src/protocol/srs_protocol_http_stack.hpp index 493235242..7ec56440e 100644 --- a/trunk/src/protocol/srs_protocol_http_stack.hpp +++ b/trunk/src/protocol/srs_protocol_http_stack.hpp @@ -207,6 +207,72 @@ public: virtual bool eof() = 0; }; +// A RequestWriter interface is used by an HTTP handler to +// construct an HTTP request. +// Usage 0, request with a message once: +// ISrsHttpRequestWriter* w; // create or get request. +// std::string msg = "Hello, HTTP!"; +// w->write((char*)msg.data(), (int)msg.length()); +// Usage 1, request with specified length content, same to #0: +// ISrsHttpRequestWriter* w; // create or get request. +// std::string msg = "Hello, HTTP!"; +// w->header()->set_content_type("text/plain; charset=utf-8"); +// w->header()->set_content_length(msg.length()); +// w->write_header("POST", "/"); +// w->write((char*)msg.data(), (int)msg.length()); // write N times, N>0 +// w->final_request(); // optional flush. +// Usage 2, request with HTTP code only, zero content length. +// ISrsHttpRequestWriter* w; // create or get request. +// w->header()->set_content_length(0); +// w->write_header("GET", "/"); +// w->final_request(); +// Usage 3, request in chunked encoding. +// ISrsHttpRequestWriter* w; // create or get request. +// std::string msg = "Hello, HTTP!"; +// w->header()->set_content_type("application/octet-stream"); +// w->write_header("POST", "/"); +// w->write((char*)msg.data(), (int)msg.length()); +// w->write((char*)msg.data(), (int)msg.length()); +// w->write((char*)msg.data(), (int)msg.length()); +// w->write((char*)msg.data(), (int)msg.length()); +// w->final_request(); // required to end the chunked and flush. +class ISrsHttpRequestWriter +{ +public: + ISrsHttpRequestWriter(); + virtual ~ISrsHttpRequestWriter(); +public: + // When chunked mode, + // final the request to complete the chunked encoding. + // For no-chunked mode, + // final to send request, for example, content-length is 0. + virtual srs_error_t final_request() = 0; + + // Header returns the header map that will be sent by WriteHeader. + // Changing the header after a call to WriteHeader (or Write) has + // no effect. + virtual SrsHttpHeader* header() = 0; + + // Write writes the data to the connection as part of an HTTP reply. + // If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK) + // before writing the data. If the Header does not contain a + // Content-Type line, Write adds a Content-Type set to the result of passing + // The initial 512 bytes of written data to DetectContentType. + // @param data, the data to send. NULL to flush header only. + virtual srs_error_t write(char* data, int size) = 0; + // for the HTTP FLV, to writev to improve performance. + // @see https://github.com/ossrs/srs/issues/405 + virtual srs_error_t writev(const iovec* iov, int iovcnt, ssize_t* pnwrite) = 0; + + // WriteHeader sends an HTTP request header with status code. + // If WriteHeader is not called explicitly, the first call to Write + // will trigger an implicit WriteHeader(http.StatusOK). + // Thus explicit calls to WriteHeader are mainly used to + // send error codes. + // @remark, user must set header then write or write_header. + virtual void write_header(const char* method, const char* path) = 0; +}; + // Objects implementing the Handler interface can be // registered to serve a particular path or subtree // in the HTTP server. @@ -455,6 +521,7 @@ public: ISrsHttpMessage(); virtual ~ISrsHttpMessage(); public: + virtual uint8_t message_type() = 0; virtual uint8_t method() = 0; virtual uint16_t status_code() = 0; // Method helpers. @@ -783,6 +850,12 @@ enum http_status XX(32, UNLINK, UNLINK) \ /* icecast */ \ XX(33, SOURCE, SOURCE) \ + /* SIP https://www.ietf.org/rfc/rfc3261.html */ \ + XX(34, REGISTER, REGISTER) \ + XX(35, INVITE, INVITE) \ + XX(36, ACK, ACK) \ + XX(37, MESSAGE, MESSAGE) \ + XX(38, BYE, BYE) \ enum http_method { diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index 05313ae1c..52efafd59 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -19,20 +19,6 @@ using namespace std; #include #include -class MockMSegmentsReader : public ISrsReader -{ -public: - vector in_bytes; -public: - MockMSegmentsReader(); - virtual ~MockMSegmentsReader(); -public: - virtual void append(string b) { - in_bytes.push_back(b); - } - virtual srs_error_t read(void* buf, size_t size, ssize_t* nread); -}; - MockMSegmentsReader::MockMSegmentsReader() { } @@ -41,6 +27,11 @@ MockMSegmentsReader::~MockMSegmentsReader() { } +void MockMSegmentsReader::append(string b) +{ + in_bytes.push_back(b); +} + srs_error_t MockMSegmentsReader::read(void* buf, size_t size, ssize_t* nread) { srs_error_t err = srs_success; @@ -76,7 +67,7 @@ srs_error_t MockMSegmentsReader::read(void* buf, size_t size, ssize_t* nread) MockResponseWriter::MockResponseWriter() { w = new SrsHttpResponseWriter(&io); - w->hf = this; + w->set_header_filter(this); } MockResponseWriter::~MockResponseWriter() @@ -1053,7 +1044,7 @@ VOID TEST(ProtocolHTTPTest, HTTPServerMuxerCORS) MockResponseWriter w; SrsHttpMessage r(NULL, NULL); - r.set_basic(HTTP_REQUEST, HTTP_POST, 200, -1); + r.set_basic(HTTP_REQUEST, HTTP_POST, (http_status)200, -1); HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false)); SrsHttpCorsMux cs; @@ -1073,7 +1064,7 @@ VOID TEST(ProtocolHTTPTest, HTTPServerMuxerCORS) MockResponseWriter w; SrsHttpMessage r(NULL, NULL); - r.set_basic(HTTP_REQUEST, HTTP_OPTIONS, 200, -1); + r.set_basic(HTTP_REQUEST, HTTP_OPTIONS, (http_status)200, -1); HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false)); SrsHttpCorsMux cs; @@ -1093,7 +1084,7 @@ VOID TEST(ProtocolHTTPTest, HTTPServerMuxerCORS) MockResponseWriter w; SrsHttpMessage r(NULL, NULL); - r.set_basic(HTTP_REQUEST, HTTP_POST, 200, -1); + r.set_basic(HTTP_REQUEST, HTTP_POST, (http_status)200, -1); HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false)); SrsHttpCorsMux cs; @@ -1113,7 +1104,7 @@ VOID TEST(ProtocolHTTPTest, HTTPServerMuxerCORS) MockResponseWriter w; SrsHttpMessage r(NULL, NULL); - r.set_basic(HTTP_REQUEST, HTTP_OPTIONS, 200, -1); + r.set_basic(HTTP_REQUEST, HTTP_OPTIONS, (http_status)200, -1); HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false)); SrsHttpCorsMux cs; @@ -1778,7 +1769,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageUpdate) SrsHttpMessage m; m.set_header(&h, false); - m.set_basic(HTTP_REQUEST, HTTP_POST, 200, 2); + m.set_basic(HTTP_REQUEST, HTTP_POST, (http_status)200, 2); EXPECT_EQ(10, m.content_length()); } @@ -1788,7 +1779,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageUpdate) h.set("Content-Length", "10"); SrsHttpMessage m; - m.set_basic(HTTP_REQUEST, HTTP_POST, 200, 2); + m.set_basic(HTTP_REQUEST, HTTP_POST, (http_status)200, 2); m.set_header(&h, false); EXPECT_EQ(10, m.content_length()); } @@ -1822,7 +1813,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageUpdate) if (true) { SrsHttpMessage m; - m.set_basic(HTTP_REQUEST, HTTP_POST, 200, 0); + m.set_basic(HTTP_REQUEST, HTTP_POST, (http_status)200, 0); EXPECT_EQ(0, m.content_length()); } @@ -2117,6 +2108,7 @@ VOID TEST(ProtocolHTTPTest, QueryEscape) VOID TEST(ProtocolHTTPTest, PathEscape) { srs_error_t err = srs_success; + struct EscapeTest path[] = { {"", "", srs_success}, {"abc", "abc", srs_success}, @@ -2138,5 +2130,5 @@ VOID TEST(ProtocolHTTPTest, PathEscape) EXPECT_STREQ(d.in.c_str(), value.c_str()); EXPECT_EQ(0, memcmp(d.in.c_str(), value.c_str(), d.in.length())); } - } + diff --git a/trunk/src/utest/srs_utest_http.hpp b/trunk/src/utest/srs_utest_http.hpp index a1619c6d6..0ded27178 100644 --- a/trunk/src/utest/srs_utest_http.hpp +++ b/trunk/src/utest/srs_utest_http.hpp @@ -37,6 +37,18 @@ public: virtual srs_error_t filter(SrsHttpHeader* h); }; +class MockMSegmentsReader : public ISrsReader +{ +public: + std::vector in_bytes; +public: + MockMSegmentsReader(); + virtual ~MockMSegmentsReader(); +public: + virtual void append(string b); + virtual srs_error_t read(void* buf, size_t size, ssize_t* nread); +}; + string mock_http_response(int status, string content); string mock_http_response2(int status, string content); bool is_string_contain(string substr, string str);