当请求范围时,带有 mod_mono 的 Apache2 不会以部分内容进行应答

当请求范围时,带有 mod_mono 的 Apache2 不会以部分内容进行应答

我在虚拟机中安装了 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();
            }
        }

有了它,所有视频都可以在我测试的所有设备上播放,每次暂停或搜索时不再需要重新下载整个视频文件,并且移动设备可以播放它们。

相关内容