HttpClient.cpp 8.76 KB
Newer Older
xiongziliang committed
1
/*
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
* MIT License
*
* Copyright (c) 2016 xiongziliang <771730766@qq.com>
*
* 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.
*/
xiongziliang committed
26 27 28 29

#include "HttpClient.h"
#include "Rtsp/Rtsp.h"

xiongziliang committed
30
namespace mediakit {
xiongziliang committed
31 32


33
HttpClient::HttpClient() {
xiongziliang committed
34
}
35 36

HttpClient::~HttpClient() {
xiongziliang committed
37
}
38 39

void HttpClient::sendRequest(const string &strUrl, float fTimeOutSec) {
40
    _aliveTicker.resetTime();
41
    auto protocol = FindField(strUrl.data(), NULL, "://");
xiongziliang committed
42 43 44 45 46
    uint16_t defaultPort;
    bool isHttps;
    if (strcasecmp(protocol.data(), "http") == 0) {
        defaultPort = 80;
        isHttps = false;
47
    } else if (strcasecmp(protocol.data(), "https") == 0) {
xiongziliang committed
48 49
        defaultPort = 443;
        isHttps = true;
50
    } else {
51
        auto strErr = StrPrinter << "非法的协议:" << protocol << endl;
xiongziliang committed
52 53
        throw std::invalid_argument(strErr);
    }
54

xiongziliang committed
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
    auto host = FindField(strUrl.data(), "://", "/");
    if (host.empty()) {
        host = FindField(strUrl.data(), "://", NULL);
    }
    _path = FindField(strUrl.data(), host.data(), NULL);
    if (_path.empty()) {
        _path = "/";
    }
    auto port = atoi(FindField(host.data(), ":", NULL).data());
    if (port <= 0) {
        //默认端口
        port = defaultPort;
    } else {
        //服务器域名
        host = FindField(host.data(), NULL, ":");
    }
71 72 73 74 75 76 77 78 79 80 81
    _header.emplace(string("Host"), host);
    _header.emplace(string("Tools"), "ZLMediaKit");
    _header.emplace(string("Connection"), "keep-alive");
    _header.emplace(string("Accept"), "*/*");
    _header.emplace(string("Accept-Language"), "zh-CN,zh;q=0.8");
    _header.emplace(string("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");

    if (_body && _body->remainSize()) {
        _header.emplace(string("Content-Length"), to_string(_body->remainSize()));
        _header.emplace(string("Content-Type"), "application/x-www-form-urlencoded; charset=UTF-8");
xiongziliang committed
82 83 84 85 86
    }

    bool bChanged = (_lastHost != host + ":" + to_string(port)) || (_isHttps != isHttps);
    _lastHost = host + ":" + to_string(port);
    _isHttps = isHttps;
87
    _fTimeOutSec = fTimeOutSec;
88 89 90 91 92 93 94 95 96 97 98 99

    auto cookies = HttpCookieStorage::Instance().get(_lastHost,_path);
    _StrPrinter printer;
    for(auto &cookie : cookies){
        printer << cookie->getKey() << "=" << cookie->getVal() << ";";
    }
    if(!printer.empty()){
        printer.pop_back();
        _header.emplace(string("Cookie"), printer);
    }


100
    if (!alive() || bChanged) {
101
        //InfoL << "reconnet:" << _lastHost;
102 103
        startConnect(host, port, fTimeOutSec);
    } else {
xiongziliang committed
104 105 106 107 108 109
        SockException ex;
        onConnect(ex);
    }
}


110
void HttpClient::onConnect(const SockException &ex) {
111
    _aliveTicker.resetTime();
112 113 114 115 116 117 118 119 120
    if (ex) {
        onDisconnect(ex);
        return;
    }

    _totalBodySize = 0;
    _recvedBodySize = 0;
    HttpRequestSplitter::reset();

121 122
    _StrPrinter printer;
    printer << _method + " " << _path + " HTTP/1.1\r\n";
xiongziliang committed
123
    for (auto &pr : _header) {
124
        printer << pr.first + ": ";
125
        printer << pr.second + "\r\n";
xiongziliang committed
126
    }
127 128
    send(printer << "\r\n");
    onSend();
xiongziliang committed
129
}
130 131 132 133 134 135 136 137

void HttpClient::onRecv(const Buffer::Ptr &pBuf) {
    onRecvBytes(pBuf->data(), pBuf->size());
}

void HttpClient::onRecvBytes(const char *data, int size) {
    _aliveTicker.resetTime();
    HttpRequestSplitter::input(data, size);
xiongziliang committed
138 139
}

140 141
void HttpClient::onErr(const SockException &ex) {
    if (ex.getErrCode() == Err_eof && _totalBodySize == INT64_MAX) {
142 143
        //如果Content-Length未指定 但服务器断开链接
        //则认为本次http请求完成
144
        onResponseCompleted_l();
145
    }
146
    onDisconnect(ex);
xiongziliang committed
147 148
}

149 150 151
int64_t HttpClient::onRecvHeader(const char *data, uint64_t len) {
    _parser.Parse(data);
    onResponseHeader(_parser.Url(), _parser.getValues());
152
    checkCookie(_parser.getValues());
153

154 155
    if(_parser["Content-Length"].empty()){
        //没有Content-Length字段
xiongziliang committed
156 157 158 159
        if(_parser.Content().empty()){
            //content长度为0,本次http请求结束
            onResponseCompleted_l();
            return 0;
160
        }
xiongziliang committed
161 162 163 164 165
        //如果http回复未声明Content-Length字段,但是却有content内容,那说明可能是个不限长度的content
        _totalBodySize = INT64_MAX;
        _recvedBodySize = 0;
        //返回-1代表不限制content回复大小
        return -1;
166
    }
167

xiongziliang committed
168
    //有Content-Length字段
169
    _recvedBodySize = 0;
170 171 172 173 174 175 176
    _totalBodySize = atoll(_parser["Content-Length"].data());

    if(_totalBodySize == 0){
        //content长度为0,本次http请求结束
        onResponseCompleted_l();
        return 0;
    }
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198

    //虽然我们知道content的确切大小,
    //但是由于我们没必要等content接收完毕才回调onRecvContent(因为这样浪费内存并且要多次拷贝数据)
    //所以返回-1代表我们接下来分段接收content
    return -1;
}

void HttpClient::onRecvContent(const char *data, uint64_t len) {
    auto recvedBodySize = _recvedBodySize + len;
    if (recvedBodySize < _totalBodySize) {
        onResponseBody(data, len, recvedBodySize, _totalBodySize);
        _recvedBodySize = recvedBodySize;
    } else {
        onResponseBody(data, _totalBodySize - _recvedBodySize, _totalBodySize, _totalBodySize);
        bool biggerThanExpected = recvedBodySize > _totalBodySize;
        onResponseCompleted_l();
        if(biggerThanExpected) {
            //声明的content数据比真实的小,那么我们只截取前面部分的并断开链接
            shutdown();
            onDisconnect(SockException(Err_other, "http response content size bigger than expected"));
        }
    }
xiongziliang committed
199
}
200 201 202

void HttpClient::onSend() {
    _aliveTicker.resetTime();
203
    while (_body && _body->remainSize() && !isSocketBusy()) {
204
        auto buffer = _body->readData();
205
        if (!buffer) {
206 207 208
            //数据发送结束或读取数据异常
            break;
        }
209
        if (send(buffer) <= 0) {
210 211 212 213 214 215 216 217
            //发送数据失败,不需要回滚数据,因为发送前已经通过isSocketBusy()判断socket可写
            //所以发送缓存区肯定未满,该buffer肯定已经写入socket
            break;
        }
    }
}

void HttpClient::onManager() {
218
    if (_aliveTicker.elapsedTime() > 3 * 1000 && _totalBodySize == INT64_MAX) {
219 220
        //如果Content-Length未指定 但接收数据超时
        //则认为本次http请求完成
221
        onResponseCompleted_l();
222 223
    }

224
    if (_fTimeOutSec > 0 && _aliveTicker.elapsedTime() > _fTimeOutSec * 1000) {
225
        //超时
226
        onDisconnect(SockException(Err_timeout, "http request timeout"));
227 228 229 230
        shutdown();
    }
}

231 232 233 234 235 236 237
void HttpClient::onResponseCompleted_l() {
    _totalBodySize = 0;
    _recvedBodySize = 0;
    HttpRequestSplitter::reset();
    onResponseCompleted();
}

238 239 240 241 242 243 244 245 246 247 248
void HttpClient::checkCookie(HttpClient::HttpHeader &headers) {
    //Set-Cookie: IPTV_SERVER=8E03927B-CC8C-4389-BC00-31DBA7EC7B49;expires=Sun, Sep 23 2018 15:07:31 GMT;path=/index/api/
    auto set_cookie = headers["Set-Cookie"];
    if(set_cookie.empty()){
        return;
    }
    auto key_val = Parser::parseArgs(set_cookie,";","=");

    HttpCookie::Ptr cookie = std::make_shared<HttpCookie>();
    cookie->setHost(_lastHost);

249 250 251 252 253 254 255 256 257 258 259 260 261 262
    int index = 0;
    auto arg_vec = split(set_cookie, ";");
    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);
        } else{
            if(key == "path"){
                cookie->setPath(val);
            }else if(key == "expires"){
                cookie->setExpires(val);
            }
263 264
        }
    }
265

266 267 268 269 270 271 272
    if(!(*cookie)){
        //无效的cookie
        return;
    }
    HttpCookieStorage::Instance().set(cookie);
}

273

xiongziliang committed
274
} /* namespace mediakit */
xiongziliang committed
275