我在虚拟机中安装了 Ubuntu 16.04,其中有一个带 mod_mono 的 Apache2 服务器。我注意到服务器无法Range
正确响应带有标头的请求。最初甚至无法在视频文件上查找特定时间,但使用mod_headers
并添加Header set Accept-Ranges bytes
到 VirtualHost 文件可以纠正这个问题。
206 Partial Content
然而,即使请求了,它也不会用 来回答Range
。这导致的问题是,当用户选择继续下载时,文件下载又从头开始。在移动设备上,这意味着无法观看内容,因为答案200 OK
会发送整个视频,而设备根本没有足够的内存来保存它。
下图显示了问题所在。我正在观看视频并暂停了它。当我选择再次播放视频时,整个视频会再次下载,这会占用更多带宽和设备内存。搜索也会导致这种情况。
我应该做什么配置才能使部分内容响应正常工作?
实际配置:
httpd.conf:
ServerName exemplo
User web_server
Group web_server
ServerRoot /home/web_server/server
IncludeOptional mods-enabled/*.load
IncludeOptional mods-enabled/*.conf
IncludeOptional conf-enabled/*.conf
IncludeOptional sites-enabled/*.conf
Listen 80
<IfModule ssl_module>
Listen 443
</IfModule>
<IfModule mod_gnutls.c>
Listen 443
</IfModule>
PidFile apache2log/httpd.pid
ErrorLog apache2log/error.log
HostnameLookups Off
LogLevel warn
<Directory "/">
Require all denied
Options -Indexes
AllowOverride None
</Directory>
<FilesMatch "^\.ht">
Require all denied
</FilesMatch>
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
CheckSpelling Off
CheckCaseOnly On
mono_conf.conf (在启用站点上):
<VirtualHost *:80>
ServerName local-server-1
ServerAdmin web-admin@local-server-1
DocumentRoot /home/web_server/server/mono
MonoServerPath local-server-1 "/usr/bin/mod-mono-server"
MonoSetEnv local-server-1 MONO_IOMAP=all;MONO_OLD_RX=1
MonoApplications local-server-1 "/:/home/web_server/server/mono"
<Location "/">
MonoSetServerAlias local-server-1
</Location>
<Location /mono>
SetHandler mono-ctrl
Order deny,allow
Deny from all
Allow from 127.0.0.1
</Location>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript
</IfModule>
<Directory "/home/web_server/server/mono">
SetHandler mono
Header set Accept-Ranges bytes
Allow from all
Require all granted
</Directory>
</VirtualHost>
<VirtualHost *:443>
SSLEngine On
SSLCertificateFile "/tmp/server.crt"
SSLCertificateKeyFile "/tmp/server.key"
ServerName local-server-1
ServerAdmin web-admin@local-server-1
DocumentRoot /home/web_server/server/mono
MonoServerPath local-server-1 "/usr/bin/mod-mono-server"
MonoSetEnv local-server-1 MONO_IOMAP=all;MONO_OLD_RX=1
MonoApplications local-server-1 "/:/home/web_server/server/mono"
<Location "/">
MonoSetServerAlias local-server-1
</Location>
<Location /mono>
SetHandler mono-ctrl
Order deny,allow
Deny from all
Allow from 127.0.0.1
</Location>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript
</IfModule>
<Directory "/home/web_server/server/mono">
SetHandler mono
Header set Accept-Ranges bytes
Allow from all
Require all granted
</Directory>
</VirtualHost>
浏览器请求:
GET http://localhost/arquivos/video.webm
Host: localhost
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0
Accept: video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5
Accept-Language: pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3
Referer: http://localhost/Default.aspx
Range: bytes=18120704-
Cookie: ASP.NET_SessionId=46ED58B6CD7745987060CDF5
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
服务器响应:
Date: Thu, 18 Jan 2018 04:45:55 GMT
Server: Apache/2.4.18 (Ubuntu)
Last-Modified: Thu, 18 Jan 2018 02:16:55 GMT
X-AspNet-Version: 4.0.30319
Content-Length: 26728741
Cache-Control: private
Accept-Ranges: bytes
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: video/webm
答案1
我能够通过创建一个处理程序来解决这个问题,专门检查请求是否有范围,然后给出带有范围和所需标题的响应。
在 HTML 上,需要在我想要允许范围请求的每个文件地址上添加以下内容:
<source src="PartialHandler.ashx?arquivos/video.webm" />
PartialHandler.ashx包含以下ProcessRequest:
public void ProcessRequest(HttpContext context)
{
string a = Uri.UnescapeDataString(context.Request.Url.Query);
if (a.StartsWith("?", StringComparison.InvariantCultureIgnoreCase))
{
a = a.Substring(1);
}
Console.WriteLine(a);
if (a == null || a == "")
{
context.Response.StatusCode = 403;
context.Response.End();
return;
}
Console.WriteLine(a);
FileInfo aberto;
long tam_tot;
long tam_tot_ran;
try
{
aberto = new FileInfo(HttpRuntime.AppDomainAppPath + a);
tam_tot = aberto.Length;
tam_tot_ran = tam_tot - 1;
context.Response.AppendHeader("Accept-Ranges", "0-" + tam_tot_ran);
context.Response.AppendHeader("Content-Type", MimeMapping.GetMimeMapping(a));
}
catch (FileNotFoundException)
{
context.Response.StatusCode = 404;
context.Response.End();
return;
}
string allhead = context.Request.Headers.ToString();
if (allhead.Contains("Range=bytes"))
{
var pedido = context.Request.Headers.Get("Range");
if (pedido.Contains(","))
{
context.Response.StatusCode = 416;
context.Response.End();
return;
}
Console.WriteLine(pedido); //bytes=5-15
long end_igual = pedido.IndexOf("=", StringComparison.InvariantCultureIgnoreCase);
long end_traco = pedido.IndexOf("-", StringComparison.InvariantCultureIgnoreCase);
string tam_ini_str = pedido.Substring((int)end_igual + 1, (int)end_traco - 1 - (int)end_igual);
string tam_fin_str = pedido.Substring((int)end_traco + 1);
long.TryParse(tam_ini_str, out long tam_ini);
long.TryParse(tam_fin_str, out long tam_fin);
if (tam_fin > tam_tot_ran)
{
context.Response.StatusCode = 416;
context.Response.End();
return;
}
context.Response.StatusCode = 206;
if (tam_fin == 0)
{
context.Response.AppendHeader("Content-Length", (tam_tot - tam_ini).ToString());
context.Response.AppendHeader("Content-Range", "bytes " + tam_ini + "-" + tam_tot_ran + "/" + tam_tot.ToString());
context.Response.TransmitFile(aberto.FullName, tam_ini, tam_tot_ran);
context.Response.End();
}
else
{
context.Response.AppendHeader("Content-Length", (tam_fin - tam_ini + 1).ToString());
context.Response.AppendHeader("Content-Range", "bytes " + tam_ini + "-" + tam_fin + "/" + tam_tot.ToString());
context.Response.TransmitFile(aberto.FullName, tam_ini, tam_fin);
context.Response.End();
}
}
else
{
context.Response.AppendHeader("Content-Length", aberto.Length.ToString());
context.Response.TransmitFile(aberto.FullName);
context.Response.End();
}
}
有了它,所有视频都可以在我测试的所有设备上播放,每次暂停或搜索时不再需要重新下载整个视频文件,并且移动设备可以播放它们。