1 前言
本文我们学习关于HTTP 是如何在FFMPEG 中实现的,关于HTTP 协议部分请参考《ffmpeg 之 http 分析 一》, 只有熟悉了http 协议部分,我们在阅读http 源码时才能有的放矢,不至于迷失方向。 和其他demuxer 一样, 我们从分析其给外界开出的API 入手。
const URLProtocol ff_http_protocol = {
.name = "http",
.url_open2 = http_open,
.url_accept = http_accept,
.url_handshake = http_handshake,
.url_read = http_read,
.url_write = http_write,
.url_seek = http_seek,
.url_close = http_close,
.url_get_file_handle = http_get_file_handle,
.url_get_short_seek = http_get_short_seek,
.url_shutdown = http_shutdown,
.priv_data_size = sizeof(HTTPContext),
.priv_data_class = &http_context_class,
.flags = URL_PROTOCOL_FLAG_NETWORK,
.default_whitelist = "http,https,tls,rtp,tcp,udp,crypto,httpproxy"
};
2 http_open()
关于http_open() 函数, 关于http 协议往下就是tcp 协议, 因此在http_open() 时就会往下call ffurl_open_whitelist() tcp open,噢,对了不是tcp 协议还有三次握手吗? 就是这个函数到tcp 协议层时会由三次握手操作。 保持tcp open 建立连接之后,客户端就需要发送请求报文, 也是就是在http_connect() 函数中实现的类容。
为了更直观的了解http_open() 这个函数, 抓了关于http 的网络包进行分析。 如下图所示,第9,10,11三个网络包乃是三次握手的数据包。接下来就发送GET http 请求报文。
if (s->method)
method = s->method;
else
method = post ? "POST" : "GET";
....
ret = snprintf(s->buffer, sizeof(s->buffer), // 请求报文,通过snprintf() 打包到buff 中
"%s %s HTTP/1.1\r\n"
"%s"
"%s"
"%s"
"%s%s"
"\r\n",
method,
path,
post && s->chunked_post ? "Transfer-Encoding: chunked\r\n" : "",
headers,
authstr ? authstr : "",
proxyauthstr ? "Proxy-" : "", proxyauthstr ? proxyauthstr : "");
发送完请求报文之后,就等待服务端发送响应报文过来,在process_line() 中处理服务端的响应报文。
static int process_line(URLContext *h, char *line, int line_count,
int *new_location)
{
HTTPContext *s = h->priv_data;
const char *auto_method = h->flags & AVIO_FLAG_READ ? "POST" : "GET";
char *tag, *p, *end, *method, *resource, *version;
int ret;
p = line;
if (line_count == 0) {
if (s->is_connected_server) { //错误处理
// HTTP method
method = p;
while (*p && !av_isspace(*p))
p++;
*(p++) = '\0';
av_log(h, AV_LOG_TRACE, "Received method: %s\n", method);
if (s->method) {
if (av_strcasecmp(s->method, method)) {
av_log(h, AV_LOG_ERROR, "Received and expected HTTP method do not match. (%s expected, %s received)\n",
s->method, method);
return ff_http_averror(400, AVERROR(EIO)); //上报网络错误
}
} else {
// use autodetected HTTP method to expect
av_log(h, AV_LOG_TRACE, "Autodetected %s HTTP method\n", auto_method);
if (av_strcasecmp(auto_method, method)) {
av_log(h, AV_LOG_ERROR, "Received and autodetected HTTP method did not match "
"(%s autodetected %s received)\n", auto_method, method);
return ff_http_averror(400, AVERROR(EIO));
}
if (!(s->method = av_strdup(method)))
return AVERROR(ENOMEM);
}
// HTTP resource
while (av_isspace(*p))
p++;
resource = p;
while (!av_isspace(*p))
p++;
*(p++) = '\0';
av_log(h, AV_LOG_TRACE, "Requested resource: %s\n", resource);
if (!(s->resource = av_strdup(resource)))
return AVERROR(ENOMEM);
// HTTP version
while (av_isspace(*p))
p++;
version = p;
while (*p && !av_isspace(*p))
p++;
*p = '\0';
if (av_strncasecmp(version, "HTTP/", 5)) {
av_log(h, AV_LOG_ERROR, "Malformed HTTP version string.\n");
return ff_http_averror(400, AVERROR(EIO));
}
av_log(h, AV_LOG_TRACE, "HTTP version string: %s\n", version);
} else {
if (av_strncasecmp(p, "HTTP/1.0", 8) == 0)
s->willclose = 1;
while (*p != '/' && *p != '\0')
p++;
while (*p == '/')
p++;
av_freep(&s->http_version);
s->http_version = av_strndup(p, 3);
while (!av_isspace(*p) && *p != '\0')
p++;
while (av_isspace(*p))
p++;
s->http_code = strtol(p, &end, 10);
av_log(h, AV_LOG_TRACE, "http_code=%d\n", s->http_code);
if ((ret = check_http_code(h, s->http_code, end)) < 0)
return ret;
}
} else {
while (*p != '\0' && *p != ':')
p++;
if (*p != ':')
return 1;
*p = '\0';
tag = line;
p++;
while (av_isspace(*p))
p++;
if (!av_strcasecmp(tag, "Location")) {
if ((ret = parse_location(s, p)) < 0)
return ret;
*new_location = 1;
} else if (!av_strcasecmp(tag, "Content-Length") && //本次响应返回数据的大小
s->filesize == UINT64_MAX) {
s->filesize = strtoull(p, NULL, 10);
} else if (!av_strcasecmp(tag, "Content-Range")) { //请求资源的range
parse_content_range(h, p);
} else if (!av_strcasecmp(tag, "Accept-Ranges") && //可访问资源的range
!strncmp(p, "bytes", 5) &&
s->seekable == -1) {
h->is_streamed = 0;
} else if (!av_strcasecmp(tag, "Transfer-Encoding") &&
!av_strncasecmp(p, "chunked", 7)) {
s->filesize = UINT64_MAX;
s->chunksize = 0;
} else if (!av_strcasecmp(tag, "WWW-Authenticate")) {
ff_http_auth_handle_header(&s->auth_state, tag, p);
} else if (!av_strcasecmp(tag, "Authentication-Info")) {
ff_http_auth_handle_header(&s->auth_state, tag, p);
} else if (!av_strcasecmp(tag, "Proxy-Authenticate")) {
ff_http_auth_handle_header(&s->proxy_auth_state, tag, p);
} else if (!av_strcasecmp(tag, "Connection")) {
if (!strcmp(p, "close"))
s->willclose = 1; //响应本次请求后,服务端将关闭
} else if (!av_strcasecmp(tag, "Server")) {
if (!av_strcasecmp(p, "AkamaiGHost")) {
s->is_akamai = 1;
} else if (!av_strncasecmp(p, "MediaGateway", 12)) {
s->is_mediagateway = 1;
}
} else if (!av_strcasecmp(tag, "Content-Type")) {
av_free(s->mime_type);
s->mime_type = av_strdup(p);
} else if (!av_strcasecmp(tag, "Set-Cookie")) {
if (parse_cookie(s, p, &s->cookie_dict))
av_log(h, AV_LOG_WARNING, "Unable to parse '%s'\n", p);
} else if (!av_strcasecmp(tag, "Icy-MetaInt")) {
s->icy_metaint = strtoull(p, NULL, 10);
} else if (!av_strncasecmp(tag, "Icy-", 4)) {
if ((ret = parse_icy(s, tag, p)) < 0)
return ret;
} else if (!av_strcasecmp(tag, "Content-Encoding")) {
if ((ret = parse_content_encoding(h, p)) < 0)
return ret;
}
}
return 1;
}
2 http_read()
关于http_read() 函数,往下实际是call tcp read() 去拿数据,也就是对应的ffurl_read() , 当前buff 中如果有数据,优先返回buff 中已经读取到的数据。