【问题标题】:Handle range requests with HttpListener使用 HttpListener 处理范围请求
【发布时间】:2014-11-11 23:35:10
【问题描述】:

我已经编写了一个自定义的 HTTP 服务器,它可以正常工作,直到浏览器发出一个字节范围的请求。尝试加载视频时(似乎是针对特定大小的文件,因为这种情况并非每次都发生),浏览器会使用此标头请求视频文件:

method: GET
/media/mp4/32.mp4
Connection - keep-alive
Accept - */*
Accept-Encoding - identity;q=1/*;q=0
Accept-Language - en-us/en;q=0.8
Host - localhost:20809
Referer - ...
Range - bytes=0-
User-Agent - Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.170 Safari/537.36

所以服务器发送请求的文件......然后,它立即发出这个请求:

method: GET
/media/mp4/32.mp4
Connection - keep-alive
Accept - */*
Accept-Encoding - identity;q=1/*;q=0
Accept-Language - en-us/en;q=0.8
Host - localhost:20809
Referer - ...
Range - bytes=40-3689973
User-Agent - Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.170 Safari/537.36

所以我将请求的字节写入输出流,但它总是在第二个请求时出错并出现错误。当浏览器发送另一个请求时,几乎就像服务器仍在尝试发送文件一样。

由于线程退出或应用程序请求,I/O 操作已中止

这是处理范围请求的代码:

public void StartServer()
{
    _server = new HttpListener();
    _server.Prefixes.Add("http://localhost:" + _port.ToString() + "/");

    LogWebserver("Listening...");

    _server.Start();
    th = new Thread(new ThreadStart(startlistener));
    th.Start();
}
private void startlistener()
{
    while (true)
    {
        ////blocks until a client has connected to the server
        ProcessRequest();
    }
}
private void ProcessRequest()
{
    var result = _server.BeginGetContext(ListenerCallback, _server);  
    result.AsyncWaitHandle.WaitOne();
}
private void ListenerCallback(IAsyncResult result)
{
    var context = _server.EndGetContext(result);
    HandleContext(context);
}
private void HandleContext(HttpListenerContext context)
{

    HttpListenerRequest req = context.Request;
...stuff...
   using (HttpListenerResponse resp = context.Response)
   {
        .... stuff....
        byte[] buffer = File.ReadAllBytes(localFile);
    if (mime.ToString().Contains("video") || mime.ToString().Contains("audio"))
    {

        resp.StatusCode = 206;
        resp.StatusDescription = "Partial Content";
        int startByte = -1;
        int endByte = -1;
        int byteRange = -1;
        if (req.Headers.GetValues("Range") != null)
        {
            string rangeHeader = req.Headers.GetValues("Range")[0].Replace("bytes=", "");
            string[] range = rangeHeader.Split('-');
            startByte = int.Parse(range[0]);
            if (range[1].Trim().Length > 0)   int.TryParse(range[1], out endByte);                                
            if (endByte == -1) endByte = buffer.Length;
        }
        else
        {
            startByte = 0;
            endByte = buffer.Length;
        }
        byteRange = endByte - startByte;
        resp.ContentLength64 = byteRange;
        resp.Headers.Add("Accept-Ranges", "bytes");
        resp.Headers.Add("Content-Range", string.Format("bytes {0}-{1}/{2}", startByte, byteRange - 1, byteRange));
        resp.Headers.Add("X-Content-Duration", "0.0");
        resp.Headers.Add("Content-Duration", "0.0");

       resp.OutputStream.Write(buffer, startByte, byteRange);/* this is where it gives the IO error */
            resp.OutputStream.Close();

            resp.Close();  
    }
    else
    {
        resp.ContentLength64 = buffer.Length;
        resp.OutputStream.Write(buffer, 0, buffer.Length);
        resp.OutputStream.Close();
        resp.Close();
    }

   }
}

我尝试过简单地忽略包含范围的请求,但是虽然没有抛出错误,但浏览器会抛出错误,因为没有下载视频。

如何处理这些范围请求并避免 IO 错误?

【问题讨论】:

  • 你设置 Accept-Ranges , Content-Range 标题了吗?
  • 是的,自从我发布了这个,我现在设置了 Accept-Ranges 和 Content-Range。接受范围:字节内容持续时间:0.0 内容长度:4459190 内容范围:字节 0-4459189/4459190 内容类型:视频/mp4 日期:2014 年 9 月 17 日星期三 21:38:45 GMT 服务器:Microsoft-HTTPAPI /2.0 X-Content-Duration:0.0
  • 如果我没有收到此 I/O 错误并且偶尔会收到指定的网络名称不再可用,它会起作用。我认为这是一个线程问题
  • I'm thinking it's a threading problem 所以你希望我们在不知道你真实代码的情况下回答:)
  • 好的,我已经编辑了尽可能多的代码。

标签: c# .net http http-headers


【解决方案1】:

我知道为时已晚,但我认为您的解决方案存在一些错误。您的代码不适用于 Safari。

  • 仅在请求时发送范围。
  • endbyte 不正确,应该是 +1
  • 2. Content-Range的totalcount错误,应该是
    的字节数 整个文件

Safari 总是请求 2 个字节进行检查,不幸的是如果失败则没有信息。 其他浏览器更宽容。

【讨论】:

    【解决方案2】:

    我解决了这个问题,但它涉及废弃HttpListener 并改用TcpListener

    我用这里的代码作为服务器的基础:http://www.codeproject.com/Articles/137979/Simple-HTTP-Server-in-C

    然后我用这个修改了handleGETRequest 函数:

    if (mime.ToString().Contains("video") || mime.ToString().Contains("audio"))
    {
    
    
        using (FileStream fs = new FileStream(localFile, FileMode.Open))
        {
            int startByte = -1;
            int endByte = -1;
            if (p.httpHeaders.Contains("Range"))
            {
                string rangeHeader = p.httpHeaders["Range"].ToString().Replace("bytes=", "");
                string[] range = rangeHeader.Split('-');
                startByte = int.Parse(range[0]);
                if (range[1].Trim().Length > 0) int.TryParse(range[1], out endByte);
                if (endByte == -1) endByte = (int)fs.Length;
            }
            else
            {
                startByte = 0;
                endByte = (int)fs.Length;
            }
            byte[] buffer = new byte[endByte - startByte];
            fs.Position = startByte;
            int read = fs.Read(buffer,0, endByte-startByte);
            fs.Flush();
            fs.Close();
            p.outputStream.AutoFlush = true;
            p.outputStream.WriteLine("HTTP/1.0 206 Partial Content");
            p.outputStream.WriteLine("Content-Type: " + mime);
            p.outputStream.WriteLine("Accept-Ranges: bytes");
            int totalCount = startByte + buffer.Length;
            p.outputStream.WriteLine(string.Format("Content-Range: bytes {0}-{1}/{2}", startByte, totalCount - 1, totalCount));
            p.outputStream.WriteLine("Content-Length: " + buffer.Length.ToString());
            p.outputStream.WriteLine("Connection: keep-alive");
            p.outputStream.WriteLine("");
            p.outputStream.AutoFlush = false;
    
            p.outputStream.BaseStream.Write(buffer, 0, buffer.Length);
            p.outputStream.BaseStream.Flush();
        }                   
    }
    else
    {
        byte[] buffer = File.ReadAllBytes(localFile);
        p.outputStream.AutoFlush = true;
        p.outputStream.WriteLine("HTTP/1.0 200 OK");
        p.outputStream.WriteLine("Content-Type: " + mime);
        p.outputStream.WriteLine("Connection: close");
        p.outputStream.WriteLine("Content-Length: " + buffer.Length.ToString());
        p.outputStream.WriteLine("");
    
        p.outputStream.AutoFlush = false;
        p.outputStream.BaseStream.Write(buffer, 0, buffer.Length);
        p.outputStream.BaseStream.Flush();
    
    }
    

    使用TcpListener 可以避免任何导致响应流上的 I/O 错误的愚蠢问题。

    【讨论】:

      猜你喜欢
      • 2012-02-20
      • 2012-10-08
      • 2014-12-17
      • 2021-04-09
      • 2013-10-18
      • 2012-01-07
      • 2011-02-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多