2019-02-17 13:35:12 +08:00
/*
2018-09-23 21:10:17 +08:00
* MIT License
*
2019-05-08 15:40:07 +08:00
* Copyright ( c ) 2016 - 2019 xiongziliang < 771730766 @ qq . com >
2018-09-23 21:10:17 +08:00
*
* This file is part of ZLMediaKit ( https : //github.com/xiongziliang/ZLMediaKit).
*
* 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 .
*/
2017-05-05 18:02:54 +08:00
2019-01-16 14:26:06 +08:00
# include <cstdlib>
2017-05-05 18:02:54 +08:00
# include "HttpClient.h"
2019-06-28 16:48:02 +08:00
# include "Common/config.h"
2017-05-05 18:02:54 +08:00
2018-10-24 17:17:55 +08:00
namespace mediakit {
2017-05-05 18:02:54 +08:00
2018-09-23 21:10:17 +08:00
HttpClient : : HttpClient ( ) {
2017-05-05 18:02:54 +08:00
}
2018-09-23 21:10:17 +08:00
HttpClient : : ~ HttpClient ( ) {
2017-05-05 18:02:54 +08:00
}
2018-09-23 21:10:17 +08:00
void HttpClient : : sendRequest ( const string & strUrl , float fTimeOutSec ) {
2018-06-21 14:03:43 +08:00
_aliveTicker . resetTime ( ) ;
2018-09-23 21:10:17 +08:00
auto protocol = FindField ( strUrl . data ( ) , NULL , " :// " ) ;
2017-05-05 18:02:54 +08:00
uint16_t defaultPort ;
bool isHttps ;
2018-11-27 11:05:44 +08:00
if ( strcasecmp ( protocol . data ( ) , " http " ) = = 0 ) {
2017-05-05 18:02:54 +08:00
defaultPort = 80 ;
isHttps = false ;
2018-11-27 11:05:44 +08:00
} else if ( strcasecmp ( protocol . data ( ) , " https " ) = = 0 ) {
2017-05-05 18:02:54 +08:00
defaultPort = 443 ;
isHttps = true ;
2018-09-23 21:10:17 +08:00
} else {
2017-08-09 18:39:30 +08:00
auto strErr = StrPrinter < < " 非法的协议: " < < protocol < < endl ;
2017-05-05 18:02:54 +08:00
throw std : : invalid_argument ( strErr ) ;
}
2018-09-23 21:10:17 +08:00
2017-05-05 18:02:54 +08:00
auto host = FindField ( strUrl . data ( ) , " :// " , " / " ) ;
if ( host . empty ( ) ) {
host = FindField ( strUrl . data ( ) , " :// " , NULL ) ;
}
_path = FindField ( strUrl . data ( ) , host . data ( ) , NULL ) ;
if ( _path . empty ( ) ) {
_path = " / " ;
}
2019-01-03 15:05:52 +08:00
uint16_t port = atoi ( FindField ( host . data ( ) , " : " , NULL ) . data ( ) ) ;
2017-05-05 18:02:54 +08:00
if ( port < = 0 ) {
//默认端口
port = defaultPort ;
} else {
//服务器域名
host = FindField ( host . data ( ) , NULL , " : " ) ;
}
2019-06-13 18:39:57 +08:00
_header . emplace ( " Host " , host ) ;
_header . emplace ( " Tools " , " ZLMediaKit " ) ;
_header . emplace ( " Connection " , " keep-alive " ) ;
_header . emplace ( " Accept " , " */* " ) ;
_header . emplace ( " Accept-Language " , " zh-CN,zh;q=0.8 " ) ;
_header . emplace ( " User-Agent " , " Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 " ) ;
2018-09-23 21:10:17 +08:00
if ( _body & & _body - > remainSize ( ) ) {
2019-06-13 18:39:57 +08:00
_header . emplace ( " Content-Length " , to_string ( _body - > remainSize ( ) ) ) ;
_header . emplace ( " Content-Type " , " application/x-www-form-urlencoded; charset=UTF-8 " ) ;
2017-05-05 18:02:54 +08:00
}
bool bChanged = ( _lastHost ! = host + " : " + to_string ( port ) ) | | ( _isHttps ! = isHttps ) ;
_lastHost = host + " : " + to_string ( port ) ;
_isHttps = isHttps ;
2018-06-21 14:03:43 +08:00
_fTimeOutSec = fTimeOutSec ;
2018-09-24 00:50:02 +08:00
auto cookies = HttpCookieStorage : : Instance ( ) . get ( _lastHost , _path ) ;
_StrPrinter printer ;
for ( auto & cookie : cookies ) {
printer < < cookie - > getKey ( ) < < " = " < < cookie - > getVal ( ) < < " ; " ;
}
if ( ! printer . empty ( ) ) {
printer . pop_back ( ) ;
2019-06-13 18:39:57 +08:00
_header . emplace ( " Cookie " , printer ) ;
2018-09-24 00:50:02 +08:00
}
2018-09-23 21:10:17 +08:00
if ( ! alive ( ) | | bChanged ) {
2018-02-06 15:28:27 +08:00
//InfoL << "reconnet:" << _lastHost;
2018-09-23 21:10:17 +08:00
startConnect ( host , port , fTimeOutSec ) ;
} else {
2017-05-05 18:02:54 +08:00
SockException ex ;
onConnect ( ex ) ;
}
}
2018-09-23 21:10:17 +08:00
void HttpClient : : onConnect ( const SockException & ex ) {
2018-06-21 14:03:43 +08:00
_aliveTicker . resetTime ( ) ;
2018-09-23 21:10:17 +08:00
if ( ex ) {
onDisconnect ( ex ) ;
return ;
}
2019-04-23 12:16:14 +08:00
//先假设http客户端只会接收一点点数据( 只接受http头, 节省内存)
_sock - > setReadBuffer ( std : : make_shared < BufferRaw > ( 1 * 1024 ) ) ;
2018-09-23 21:10:17 +08:00
_totalBodySize = 0 ;
_recvedBodySize = 0 ;
HttpRequestSplitter : : reset ( ) ;
2018-11-13 23:59:06 +08:00
_chunkedSplitter . reset ( ) ;
2018-09-23 21:10:17 +08:00
2018-06-21 14:03:43 +08:00
_StrPrinter printer ;
printer < < _method + " " < < _path + " HTTP/1.1 \r \n " ;
2017-05-05 18:02:54 +08:00
for ( auto & pr : _header ) {
2018-09-23 21:10:17 +08:00
printer < < pr . first + " : " ;
2018-06-21 14:03:43 +08:00
printer < < pr . second + " \r \n " ;
2017-05-05 18:02:54 +08:00
}
2018-06-21 14:03:43 +08:00
send ( printer < < " \r \n " ) ;
2019-05-29 18:08:50 +08:00
onFlush ( ) ;
2017-05-05 18:02:54 +08:00
}
2018-09-23 21:10:17 +08:00
void HttpClient : : onRecv ( const Buffer : : Ptr & pBuf ) {
_aliveTicker . resetTime ( ) ;
2019-03-27 10:01:18 +08:00
HttpRequestSplitter : : input ( pBuf - > data ( ) , pBuf - > size ( ) ) ;
2017-05-05 18:02:54 +08:00
}
2018-09-23 21:10:17 +08:00
void HttpClient : : onErr ( const SockException & ex ) {
2018-11-13 22:50:43 +08:00
if ( ex . getErrCode ( ) = = Err_eof & & _totalBodySize < 0 ) {
2018-06-21 14:03:43 +08:00
//如果Content-Length未指定 但服务器断开链接
//则认为本次http请求完成
2018-09-23 21:10:17 +08:00
onResponseCompleted_l ( ) ;
2018-06-21 14:03:43 +08:00
}
2018-09-23 21:10:17 +08:00
onDisconnect ( ex ) ;
2017-05-05 18:02:54 +08:00
}
2018-09-23 21:10:17 +08:00
int64_t HttpClient : : onRecvHeader ( const char * data , uint64_t len ) {
_parser . Parse ( data ) ;
2019-07-01 09:53:58 +08:00
if ( _parser . Url ( ) = = " 302 " | | _parser . Url ( ) = = " 301 " ) {
auto newUrl = _parser [ " Location " ] ;
if ( newUrl . empty ( ) ) {
shutdown ( SockException ( Err_shutdown , " 未找到Location字段(跳转url) " ) ) ;
return 0 ;
}
2019-07-01 17:48:09 +08:00
HttpClient : : clear ( ) ;
2019-07-01 09:53:58 +08:00
HttpClient : : sendRequest ( newUrl , _fTimeOutSec ) ;
return 0 ;
}
2018-09-24 00:50:02 +08:00
checkCookie ( _parser . getValues ( ) ) ;
2018-11-13 22:50:43 +08:00
_totalBodySize = onResponseHeader ( _parser . Url ( ) , _parser . getValues ( ) ) ;
2018-09-23 21:10:17 +08:00
2018-11-13 22:50:43 +08:00
if ( ! _parser [ " Content-Length " ] . empty ( ) ) {
//有Content-Length字段时忽略onResponseHeader的返回值
_totalBodySize = atoll ( _parser [ " Content-Length " ] . data ( ) ) ;
2018-09-23 21:10:17 +08:00
}
2018-09-23 21:19:24 +08:00
2018-11-13 23:59:06 +08:00
if ( _parser [ " Transfer-Encoding " ] = = " chunked " ) {
2019-04-23 12:16:14 +08:00
//我们认为这种情况下后面应该有大量的数据过来,加大接收缓存提高性能
_sock - > setReadBuffer ( std : : make_shared < BufferRaw > ( 256 * 1024 ) ) ;
2018-11-13 23:59:06 +08:00
//如果Transfer-Encoding字段等于chunked, 则认为后续的content是不限制长度的
_totalBodySize = - 1 ;
_chunkedSplitter = std : : make_shared < HttpChunkedSplitter > ( [ this ] ( const char * data , uint64_t len ) {
if ( len > 0 ) {
auto recvedBodySize = _recvedBodySize + len ;
onResponseBody ( data , len , recvedBodySize , INT64_MAX ) ;
_recvedBodySize = recvedBodySize ;
} else {
onResponseCompleted_l ( ) ;
}
} ) ;
}
2018-09-23 21:19:24 +08:00
if ( _totalBodySize = = 0 ) {
2018-11-13 22:50:43 +08:00
//后续没content, 本次http请求结束
2018-09-23 21:19:24 +08:00
onResponseCompleted_l ( ) ;
return 0 ;
}
2018-09-23 21:10:17 +08:00
2018-11-13 22:50:43 +08:00
//当_totalBodySize != 0时到达这里, 代表后续有content
//虽然我们在_totalBodySize >0 时知道content的确切大小,
2018-09-23 21:10:17 +08:00
//但是由于我们没必要等content接收完毕才回调onRecvContent(因为这样浪费内存并且要多次拷贝数据)
//所以返回-1代表我们接下来分段接收content
2018-11-13 22:50:43 +08:00
_recvedBodySize = 0 ;
2019-04-23 12:25:10 +08:00
if ( _totalBodySize > 0 ) {
//根据_totalBodySize设置接收缓存大小
_sock - > setReadBuffer ( std : : make_shared < BufferRaw > ( MIN ( _totalBodySize + 1 , 256 * 1024 ) ) ) ;
} else {
_sock - > setReadBuffer ( std : : make_shared < BufferRaw > ( 256 * 1024 ) ) ;
}
2018-09-23 21:10:17 +08:00
return - 1 ;
}
void HttpClient : : onRecvContent ( const char * data , uint64_t len ) {
2018-11-13 23:59:06 +08:00
if ( _chunkedSplitter ) {
_chunkedSplitter - > input ( data , len ) ;
return ;
}
2018-09-23 21:10:17 +08:00
auto recvedBodySize = _recvedBodySize + len ;
2018-11-13 22:50:43 +08:00
if ( _totalBodySize < 0 ) {
//不限长度的content,最大支持INT64_MAX个字节
onResponseBody ( data , len , recvedBodySize , INT64_MAX ) ;
_recvedBodySize = recvedBodySize ;
return ;
}
//固定长度的content
if ( recvedBodySize < _totalBodySize ) {
//content还未接收完毕
2018-09-23 21:10:17 +08:00
onResponseBody ( data , len , recvedBodySize , _totalBodySize ) ;
_recvedBodySize = recvedBodySize ;
2018-11-13 22:50:43 +08:00
return ;
}
//content接收完毕
onResponseBody ( data , _totalBodySize - _recvedBodySize , _totalBodySize , _totalBodySize ) ;
bool biggerThanExpected = recvedBodySize > _totalBodySize ;
onResponseCompleted_l ( ) ;
if ( biggerThanExpected ) {
//声明的content数据比真实的小, 那么我们只截取前面部分的并断开链接
2019-05-29 18:08:50 +08:00
shutdown ( SockException ( Err_shutdown , " http response content size bigger than expected " ) ) ;
2018-09-23 21:10:17 +08:00
}
2017-05-05 18:02:54 +08:00
}
2018-06-21 14:03:43 +08:00
2019-05-29 18:08:50 +08:00
void HttpClient : : onFlush ( ) {
2018-06-21 14:03:43 +08:00
_aliveTicker . resetTime ( ) ;
2018-09-23 21:10:17 +08:00
while ( _body & & _body - > remainSize ( ) & & ! isSocketBusy ( ) ) {
2018-06-21 14:03:43 +08:00
auto buffer = _body - > readData ( ) ;
2018-09-23 21:10:17 +08:00
if ( ! buffer ) {
2018-06-21 14:03:43 +08:00
//数据发送结束或读取数据异常
break ;
}
2018-09-23 21:10:17 +08:00
if ( send ( buffer ) < = 0 ) {
2018-06-21 14:03:43 +08:00
//发送数据失败, 不需要回滚数据, 因为发送前已经通过isSocketBusy()判断socket可写
//所以发送缓存区肯定未满,该buffer肯定已经写入socket
break ;
}
}
}
void HttpClient : : onManager ( ) {
2019-02-17 13:35:12 +08:00
if ( _aliveTicker . elapsedTime ( ) > 3 * 1000 & & _totalBodySize < 0 & & ! _chunkedSplitter ) {
2018-06-21 14:03:43 +08:00
//如果Content-Length未指定 但接收数据超时
//则认为本次http请求完成
2018-09-23 21:10:17 +08:00
onResponseCompleted_l ( ) ;
2018-06-21 14:03:43 +08:00
}
2018-09-23 21:10:17 +08:00
if ( _fTimeOutSec > 0 & & _aliveTicker . elapsedTime ( ) > _fTimeOutSec * 1000 ) {
2018-06-21 14:03:43 +08:00
//超时
2019-05-29 18:08:50 +08:00
shutdown ( SockException ( Err_timeout , " http request timeout " ) ) ;
2018-06-21 14:03:43 +08:00
}
}
2018-11-13 22:50:43 +08:00
void HttpClient : : onResponseCompleted_l ( ) {
2018-09-23 21:10:17 +08:00
_totalBodySize = 0 ;
_recvedBodySize = 0 ;
2018-11-13 22:50:43 +08:00
onResponseCompleted ( ) ;
2018-09-23 21:10:17 +08:00
}
2019-03-14 09:59:07 +08:00
void HttpClient : : checkCookie ( HttpClient : : HttpHeader & headers ) {
2018-09-24 00:50:02 +08:00
//Set-Cookie: IPTV_SERVER=8E03927B-CC8C-4389-BC00-31DBA7EC7B49;expires=Sun, Sep 23 2018 15:07:31 GMT;path=/index/api/
2019-06-13 11:45:13 +08:00
for ( auto it_set_cookie = headers . find ( " Set-Cookie " ) ; it_set_cookie ! = headers . end ( ) ; + + it_set_cookie ) {
auto key_val = Parser : : parseArgs ( it_set_cookie - > second , " ; " , " = " ) ;
HttpCookie : : Ptr cookie = std : : make_shared < HttpCookie > ( ) ;
cookie - > setHost ( _lastHost ) ;
int index = 0 ;
auto arg_vec = split ( it_set_cookie - > second , " ; " ) ;
for ( string & key_val : arg_vec ) {
auto key = FindField ( key_val . data ( ) , NULL , " = " ) ;
auto val = FindField ( key_val . data ( ) , " = " , NULL ) ;
if ( index + + = = 0 ) {
cookie - > setKeyVal ( key , val ) ;
continue ;
}
2018-09-25 09:26:23 +08:00
2019-06-13 11:45:13 +08:00
if ( key = = " path " ) {
cookie - > setPath ( val ) ;
continue ;
}
2019-03-14 09:59:07 +08:00
2019-06-13 11:45:13 +08:00
if ( key = = " expires " ) {
cookie - > setExpires ( val , headers [ " Date " ] ) ;
continue ;
}
2019-03-14 09:59:07 +08:00
}
2019-06-13 11:45:13 +08:00
if ( ! ( * cookie ) ) {
//无效的cookie
2019-03-14 09:59:07 +08:00
continue ;
2018-09-24 00:50:02 +08:00
}
2019-06-13 11:45:13 +08:00
HttpCookieStorage : : Instance ( ) . set ( cookie ) ;
2018-09-24 00:50:02 +08:00
}
}
2018-06-21 14:03:43 +08:00
2018-10-24 17:17:55 +08:00
} /* namespace mediakit */
2017-05-05 18:02:54 +08:00