3.2.17.12
This commit is contained in:
parent
f07af448fa
commit
71f7fc4e11
|
@ -114,15 +114,9 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
|
||||||
var outputStream = response.OutputStream;
|
var outputStream = response.OutputStream;
|
||||||
|
|
||||||
// This is needed with compression
|
// This is needed with compression
|
||||||
if (outputStream is ResponseStream)
|
|
||||||
{
|
|
||||||
//if (!string.IsNullOrWhiteSpace(GetHeader("Content-Encoding")))
|
|
||||||
{
|
|
||||||
outputStream.Flush();
|
outputStream.Flush();
|
||||||
}
|
|
||||||
|
|
||||||
outputStream.Dispose();
|
outputStream.Dispose();
|
||||||
}
|
|
||||||
response.Close();
|
response.Close();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
[assembly: AssemblyVersion("3.2.17.11")]
|
[assembly: AssemblyVersion("3.2.17.12")]
|
||||||
|
|
|
@ -218,7 +218,9 @@ namespace SocketHttpListener.Net
|
||||||
|
|
||||||
var supportsDirectSocketAccess = !context.Response.SendChunked && !isExpect100Continue && !secure;
|
var supportsDirectSocketAccess = !context.Response.SendChunked && !isExpect100Continue && !secure;
|
||||||
|
|
||||||
o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding, _fileSystem, sock, supportsDirectSocketAccess, _logger, _environment);
|
//o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding, _fileSystem, sock, supportsDirectSocketAccess, _logger, _environment);
|
||||||
|
|
||||||
|
o_stream = new HttpResponseStream(stream, context.Response, false, _memoryStreamFactory, sock, supportsDirectSocketAccess, _environment, _fileSystem);
|
||||||
}
|
}
|
||||||
return o_stream;
|
return o_stream;
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,7 @@ namespace SocketHttpListener.Net
|
||||||
|
|
||||||
if (String.Compare(Headers["Expect"], "100-continue", StringComparison.OrdinalIgnoreCase) == 0)
|
if (String.Compare(Headers["Expect"], "100-continue", StringComparison.OrdinalIgnoreCase) == 0)
|
||||||
{
|
{
|
||||||
var output = (ResponseStream)context.Connection.GetResponseStream(true);
|
var output = (HttpResponseStream)context.Connection.GetResponseStream(true);
|
||||||
|
|
||||||
var _100continue = _textEncoding.GetASCIIEncoding().GetBytes("HTTP/1.1 100 Continue\r\n\r\n");
|
var _100continue = _textEncoding.GetASCIIEncoding().GetBytes("HTTP/1.1 100 Continue\r\n\r\n");
|
||||||
|
|
||||||
|
|
|
@ -519,7 +519,7 @@ namespace SocketHttpListener.Net
|
||||||
|
|
||||||
public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return ((ResponseStream)OutputStream).TransmitFile(path, offset, count, fileShareMode, cancellationToken);
|
return ((HttpResponseStream)OutputStream).TransmitFile(path, offset, count, fileShareMode, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
459
SocketHttpListener/Net/HttpResponseStream.Managed.cs
Normal file
459
SocketHttpListener/Net/HttpResponseStream.Managed.cs
Normal file
|
@ -0,0 +1,459 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Runtime.ExceptionServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
using MediaBrowser.Model.System;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
//
|
||||||
|
// System.Net.ResponseStream
|
||||||
|
//
|
||||||
|
// Author:
|
||||||
|
// Gonzalo Paniagua Javier (gonzalo@novell.com)
|
||||||
|
//
|
||||||
|
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
|
||||||
|
internal partial class HttpResponseStream : Stream
|
||||||
|
{
|
||||||
|
private HttpListenerResponse _response;
|
||||||
|
private bool _ignore_errors;
|
||||||
|
private bool _trailer_sent;
|
||||||
|
private Stream _stream;
|
||||||
|
private readonly IMemoryStreamFactory _memoryStreamFactory;
|
||||||
|
private readonly IAcceptSocket _socket;
|
||||||
|
private readonly bool _supportsDirectSocketAccess;
|
||||||
|
private readonly IEnvironmentInfo _environment;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
internal HttpResponseStream(Stream stream, HttpListenerResponse response, bool ignore_errors, IMemoryStreamFactory memoryStreamFactory, IAcceptSocket socket, bool supportsDirectSocketAccess, IEnvironmentInfo environment, IFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
_response = response;
|
||||||
|
_ignore_errors = ignore_errors;
|
||||||
|
_memoryStreamFactory = memoryStreamFactory;
|
||||||
|
_socket = socket;
|
||||||
|
_supportsDirectSocketAccess = supportsDirectSocketAccess;
|
||||||
|
_environment = environment;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_stream = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposeCore()
|
||||||
|
{
|
||||||
|
byte[] bytes = null;
|
||||||
|
MemoryStream ms = GetHeaders(true);
|
||||||
|
bool chunked = _response.SendChunked;
|
||||||
|
if (_stream.CanWrite)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ms != null)
|
||||||
|
{
|
||||||
|
long start = ms.Position;
|
||||||
|
if (chunked && !_trailer_sent)
|
||||||
|
{
|
||||||
|
bytes = GetChunkSizeBytes(0, true);
|
||||||
|
ms.Position = ms.Length;
|
||||||
|
ms.Write(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
InternalWrite(ms.GetBuffer(), (int)start, (int)(ms.Length - start));
|
||||||
|
_trailer_sent = true;
|
||||||
|
}
|
||||||
|
else if (chunked && !_trailer_sent)
|
||||||
|
{
|
||||||
|
bytes = GetChunkSizeBytes(0, true);
|
||||||
|
InternalWrite(bytes, 0, bytes.Length);
|
||||||
|
_trailer_sent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HttpListenerException)
|
||||||
|
{
|
||||||
|
// Ignore error due to connection reset by peer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_response.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task WriteWebSocketHandshakeHeadersAsync()
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
if (_stream.CanWrite)
|
||||||
|
{
|
||||||
|
MemoryStream ms = GetHeaders(closing: false, isWebSocketHandshake: true);
|
||||||
|
bool chunked = _response.SendChunked;
|
||||||
|
|
||||||
|
long start = ms.Position;
|
||||||
|
if (chunked)
|
||||||
|
{
|
||||||
|
byte[] bytes = GetChunkSizeBytes(0, true);
|
||||||
|
ms.Position = ms.Length;
|
||||||
|
ms.Write(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
await InternalWriteAsync(ms.GetBuffer(), (int)start, (int)(ms.Length - start)).ConfigureAwait(false);
|
||||||
|
await _stream.FlushAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MemoryStream GetHeaders(bool closing, bool isWebSocketHandshake = false)
|
||||||
|
{
|
||||||
|
// SendHeaders works on shared headers
|
||||||
|
lock (_response.headers_lock)
|
||||||
|
{
|
||||||
|
if (_response.HeadersSent)
|
||||||
|
return null;
|
||||||
|
var ms = _memoryStreamFactory.CreateNew();
|
||||||
|
_response.SendHeaders(closing, ms);
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
//lock (_response._headersLock)
|
||||||
|
//{
|
||||||
|
// if (_response.SentHeaders)
|
||||||
|
// {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// MemoryStream ms = new MemoryStream();
|
||||||
|
// _response.SendHeaders(closing, ms, isWebSocketHandshake);
|
||||||
|
// return ms;
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] s_crlf = new byte[] { 13, 10 };
|
||||||
|
private static byte[] GetChunkSizeBytes(int size, bool final)
|
||||||
|
{
|
||||||
|
string str = String.Format("{0:x}\r\n{1}", size, final ? "\r\n" : "");
|
||||||
|
return Encoding.ASCII.GetBytes(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InternalWrite(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (_ignore_errors)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_stream.Write(buffer, offset, count);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_stream.Write(buffer, offset, count);
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
throw new HttpListenerException(ex.HResult, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Task InternalWriteAsync(byte[] buffer, int offset, int count) =>
|
||||||
|
_ignore_errors ? InternalWriteIgnoreErrorsAsync(buffer, offset, count) : _stream.WriteAsync(buffer, offset, count);
|
||||||
|
|
||||||
|
private async Task InternalWriteIgnoreErrorsAsync(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
try { await _stream.WriteAsync(buffer, offset, count).ConfigureAwait(false); }
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteCore(byte[] buffer, int offset, int size)
|
||||||
|
{
|
||||||
|
if (size == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
byte[] bytes = null;
|
||||||
|
MemoryStream ms = GetHeaders(false);
|
||||||
|
bool chunked = _response.SendChunked;
|
||||||
|
if (ms != null)
|
||||||
|
{
|
||||||
|
long start = ms.Position; // After the possible preamble for the encoding
|
||||||
|
ms.Position = ms.Length;
|
||||||
|
if (chunked)
|
||||||
|
{
|
||||||
|
bytes = GetChunkSizeBytes(size, false);
|
||||||
|
ms.Write(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
int new_count = Math.Min(size, 16384 - (int)ms.Position + (int)start);
|
||||||
|
ms.Write(buffer, offset, new_count);
|
||||||
|
size -= new_count;
|
||||||
|
offset += new_count;
|
||||||
|
InternalWrite(ms.GetBuffer(), (int)start, (int)(ms.Length - start));
|
||||||
|
ms.SetLength(0);
|
||||||
|
ms.Capacity = 0; // 'dispose' the buffer in ms.
|
||||||
|
}
|
||||||
|
else if (chunked)
|
||||||
|
{
|
||||||
|
bytes = GetChunkSizeBytes(size, false);
|
||||||
|
InternalWrite(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size > 0)
|
||||||
|
InternalWrite(buffer, offset, size);
|
||||||
|
if (chunked)
|
||||||
|
InternalWrite(s_crlf, 0, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IAsyncResult BeginWriteCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state)
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
{
|
||||||
|
HttpStreamAsyncResult ares = new HttpStreamAsyncResult(this);
|
||||||
|
ares._callback = cback;
|
||||||
|
ares._state = state;
|
||||||
|
ares.Complete();
|
||||||
|
return ares;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = null;
|
||||||
|
MemoryStream ms = GetHeaders(false);
|
||||||
|
bool chunked = _response.SendChunked;
|
||||||
|
if (ms != null)
|
||||||
|
{
|
||||||
|
long start = ms.Position;
|
||||||
|
ms.Position = ms.Length;
|
||||||
|
if (chunked)
|
||||||
|
{
|
||||||
|
bytes = GetChunkSizeBytes(size, false);
|
||||||
|
ms.Write(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
ms.Write(buffer, offset, size);
|
||||||
|
buffer = ms.GetBuffer();
|
||||||
|
offset = (int)start;
|
||||||
|
size = (int)(ms.Position - start);
|
||||||
|
}
|
||||||
|
else if (chunked)
|
||||||
|
{
|
||||||
|
bytes = GetChunkSizeBytes(size, false);
|
||||||
|
InternalWrite(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _stream.BeginWrite(buffer, offset, size, cback, state);
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
if (_ignore_errors)
|
||||||
|
{
|
||||||
|
HttpStreamAsyncResult ares = new HttpStreamAsyncResult(this);
|
||||||
|
ares._callback = cback;
|
||||||
|
ares._state = state;
|
||||||
|
ares.Complete();
|
||||||
|
return ares;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new HttpListenerException(ex.HResult, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EndWriteCore(IAsyncResult asyncResult)
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_ignore_errors)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_stream.EndWrite(asyncResult);
|
||||||
|
if (_response.SendChunked)
|
||||||
|
_stream.Write(s_crlf, 0, 2);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_stream.EndWrite(asyncResult);
|
||||||
|
if (_response.SendChunked)
|
||||||
|
_stream.Write(s_crlf, 0, 2);
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
// NetworkStream wraps exceptions in IOExceptions; if the underlying socket operation
|
||||||
|
// failed because of invalid arguments or usage, propagate that error. Otherwise
|
||||||
|
// wrap the whole thing in an HttpListenerException. This is all to match Windows behavior.
|
||||||
|
if (ex.InnerException is ArgumentException || ex.InnerException is InvalidOperationException)
|
||||||
|
{
|
||||||
|
throw ex.InnerException;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new HttpListenerException(ex.HResult, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool EnableSendFileWithSocket
|
||||||
|
{
|
||||||
|
get { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (_supportsDirectSocketAccess && offset == 0 && count == 0 && !_response.SendChunked && _response.ContentLength64 > 8192)
|
||||||
|
{
|
||||||
|
if (EnableSendFileWithSocket)
|
||||||
|
{
|
||||||
|
return TransmitFileOverSocket(path, offset, count, fileShareMode, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly byte[] _emptyBuffer = new byte[] { };
|
||||||
|
private Task TransmitFileOverSocket(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var ms = GetHeaders(false);
|
||||||
|
|
||||||
|
byte[] preBuffer;
|
||||||
|
if (ms != null)
|
||||||
|
{
|
||||||
|
using (var msCopy = new MemoryStream())
|
||||||
|
{
|
||||||
|
ms.CopyTo(msCopy);
|
||||||
|
preBuffer = msCopy.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
//_logger.Info("Socket sending file {0} {1}", path, response.ContentLength64);
|
||||||
|
return _socket.SendFile(path, preBuffer, _emptyBuffer, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int StreamCopyToBufferSize = 81920;
|
||||||
|
private async Task TransmitFileManaged(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var allowAsync = _environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows;
|
||||||
|
|
||||||
|
var fileOpenOptions = offset > 0
|
||||||
|
? FileOpenOptions.RandomAccess
|
||||||
|
: FileOpenOptions.SequentialScan;
|
||||||
|
|
||||||
|
if (allowAsync)
|
||||||
|
{
|
||||||
|
fileOpenOptions |= FileOpenOptions.Asynchronous;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
|
||||||
|
|
||||||
|
using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
|
||||||
|
{
|
||||||
|
if (offset > 0)
|
||||||
|
{
|
||||||
|
fs.Position = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetStream = this;
|
||||||
|
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
if (allowAsync)
|
||||||
|
{
|
||||||
|
await CopyToInternalAsync(fs, targetStream, count, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await CopyToInternalAsyncWithSyncRead(fs, targetStream, count, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (allowAsync)
|
||||||
|
{
|
||||||
|
await fs.CopyToAsync(targetStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fs.CopyTo(targetStream, StreamCopyToBufferSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task CopyToInternalAsyncWithSyncRead(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var array = new byte[StreamCopyToBufferSize];
|
||||||
|
int bytesRead;
|
||||||
|
|
||||||
|
while ((bytesRead = source.Read(array, 0, array.Length)) != 0)
|
||||||
|
{
|
||||||
|
var bytesToWrite = Math.Min(bytesRead, copyLength);
|
||||||
|
|
||||||
|
if (bytesToWrite > 0)
|
||||||
|
{
|
||||||
|
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
copyLength -= bytesToWrite;
|
||||||
|
|
||||||
|
if (copyLength <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var array = new byte[StreamCopyToBufferSize];
|
||||||
|
int bytesRead;
|
||||||
|
|
||||||
|
while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||||
|
{
|
||||||
|
var bytesToWrite = Math.Min(bytesRead, copyLength);
|
||||||
|
|
||||||
|
if (bytesToWrite > 0)
|
||||||
|
{
|
||||||
|
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
copyLength -= bytesToWrite;
|
||||||
|
|
||||||
|
if (copyLength <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
139
SocketHttpListener/Net/HttpResponseStream.cs
Normal file
139
SocketHttpListener/Net/HttpResponseStream.cs
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
internal sealed partial class HttpResponseStream : Stream
|
||||||
|
{
|
||||||
|
private bool _closed;
|
||||||
|
internal bool Closed => _closed;
|
||||||
|
|
||||||
|
public override bool CanRead => false;
|
||||||
|
public override bool CanSeek => false;
|
||||||
|
public override bool CanWrite => true;
|
||||||
|
|
||||||
|
public override void Flush() { }
|
||||||
|
public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
|
|
||||||
|
public override long Length
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||||
|
{
|
||||||
|
return base.BeginRead(buffer, offset, count, callback, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int EndRead(IAsyncResult asyncResult)
|
||||||
|
{
|
||||||
|
return base.EndRead(asyncResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int size)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
if (offset < 0 || offset > buffer.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||||
|
}
|
||||||
|
if (size < 0 || size > buffer.Length - offset)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(size));
|
||||||
|
}
|
||||||
|
if (_closed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteCore(buffer, offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
if (offset < 0 || offset > buffer.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||||
|
}
|
||||||
|
if (size < 0 || size > buffer.Length - offset)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
return BeginWriteCore(buffer, offset, size, callback, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void EndWrite(IAsyncResult asyncResult)
|
||||||
|
{
|
||||||
|
if (asyncResult == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(asyncResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
EndWriteCore(asyncResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_closed = true;
|
||||||
|
DisposeCore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,400 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using MediaBrowser.Model.Logging;
|
|
||||||
using MediaBrowser.Model.Net;
|
|
||||||
using MediaBrowser.Model.System;
|
|
||||||
using MediaBrowser.Model.Text;
|
|
||||||
using SocketHttpListener.Primitives;
|
|
||||||
|
|
||||||
namespace SocketHttpListener.Net
|
|
||||||
{
|
|
||||||
// FIXME: Does this buffer the response until Close?
|
|
||||||
// Update: we send a single packet for the first non-chunked Write
|
|
||||||
// What happens when we set content-length to X and write X-1 bytes then close?
|
|
||||||
// what if we don't set content-length at all?
|
|
||||||
public class ResponseStream : Stream
|
|
||||||
{
|
|
||||||
HttpListenerResponse response;
|
|
||||||
bool disposed;
|
|
||||||
bool trailer_sent;
|
|
||||||
Stream stream;
|
|
||||||
private readonly IMemoryStreamFactory _memoryStreamFactory;
|
|
||||||
private readonly ITextEncoding _textEncoding;
|
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
private readonly IAcceptSocket _socket;
|
|
||||||
private readonly bool _supportsDirectSocketAccess;
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly IEnvironmentInfo _environment;
|
|
||||||
|
|
||||||
internal ResponseStream(Stream stream, HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IAcceptSocket socket, bool supportsDirectSocketAccess, ILogger logger, IEnvironmentInfo environment)
|
|
||||||
{
|
|
||||||
this.response = response;
|
|
||||||
_memoryStreamFactory = memoryStreamFactory;
|
|
||||||
_textEncoding = textEncoding;
|
|
||||||
_fileSystem = fileSystem;
|
|
||||||
_socket = socket;
|
|
||||||
_supportsDirectSocketAccess = supportsDirectSocketAccess;
|
|
||||||
_logger = logger;
|
|
||||||
_environment = environment;
|
|
||||||
this.stream = stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanRead
|
|
||||||
{
|
|
||||||
get { return false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanSeek
|
|
||||||
{
|
|
||||||
get { return false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanWrite
|
|
||||||
{
|
|
||||||
get { return true; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public override long Length
|
|
||||||
{
|
|
||||||
get { throw new NotSupportedException(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public override long Position
|
|
||||||
{
|
|
||||||
get { throw new NotSupportedException(); }
|
|
||||||
set { throw new NotSupportedException(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposed == false)
|
|
||||||
{
|
|
||||||
disposed = true;
|
|
||||||
using (var ms = GetHeaders(response, _memoryStreamFactory, false))
|
|
||||||
{
|
|
||||||
if (stream.CanWrite)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
bool chunked = response.SendChunked;
|
|
||||||
|
|
||||||
if (ms != null)
|
|
||||||
{
|
|
||||||
var start = ms.Position;
|
|
||||||
if (chunked && !trailer_sent)
|
|
||||||
{
|
|
||||||
trailer_sent = true;
|
|
||||||
var bytes = GetChunkSizeBytes(0, true);
|
|
||||||
ms.Position = ms.Length;
|
|
||||||
ms.Write(bytes, 0, bytes.Length);
|
|
||||||
ms.Position = start;
|
|
||||||
}
|
|
||||||
|
|
||||||
ms.CopyTo(stream);
|
|
||||||
}
|
|
||||||
else if (chunked && !trailer_sent)
|
|
||||||
{
|
|
||||||
trailer_sent = true;
|
|
||||||
|
|
||||||
var bytes = GetChunkSizeBytes(0, true);
|
|
||||||
stream.Write(bytes, 0, bytes.Length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
// Ignore error due to connection reset by peer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
response.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static MemoryStream GetHeaders(HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, bool closing)
|
|
||||||
{
|
|
||||||
// SendHeaders works on shared headers
|
|
||||||
lock (response.headers_lock)
|
|
||||||
{
|
|
||||||
if (response.HeadersSent)
|
|
||||||
return null;
|
|
||||||
var ms = memoryStreamFactory.CreateNew();
|
|
||||||
response.SendHeaders(closing, ms);
|
|
||||||
return ms;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Flush()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
static byte[] crlf = new byte[] { 13, 10 };
|
|
||||||
byte[] GetChunkSizeBytes(int size, bool final)
|
|
||||||
{
|
|
||||||
string str = String.Format("{0:x}\r\n{1}", size, final ? "\r\n" : "");
|
|
||||||
return _textEncoding.GetASCIIEncoding().GetBytes(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void InternalWrite(byte[] buffer, int offset, int count)
|
|
||||||
{
|
|
||||||
stream.Write(buffer, offset, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
const int MsCopyBufferSize = 81920;
|
|
||||||
const int StreamCopyToBufferSize = 81920;
|
|
||||||
public override void Write(byte[] buffer, int offset, int count)
|
|
||||||
{
|
|
||||||
if (disposed)
|
|
||||||
throw new ObjectDisposedException(GetType().ToString());
|
|
||||||
|
|
||||||
if (count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var ms = GetHeaders(response, _memoryStreamFactory, false))
|
|
||||||
{
|
|
||||||
bool chunked = response.SendChunked;
|
|
||||||
if (ms != null)
|
|
||||||
{
|
|
||||||
long start = ms.Position; // After the possible preamble for the encoding
|
|
||||||
ms.Position = ms.Length;
|
|
||||||
if (chunked)
|
|
||||||
{
|
|
||||||
var bytes = GetChunkSizeBytes(count, false);
|
|
||||||
ms.Write(bytes, 0, bytes.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
ms.Write(buffer, offset, count);
|
|
||||||
|
|
||||||
if (chunked)
|
|
||||||
{
|
|
||||||
ms.Write(crlf, 0, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
ms.Position = start;
|
|
||||||
ms.CopyTo(stream, MsCopyBufferSize);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunked)
|
|
||||||
{
|
|
||||||
var bytes = GetChunkSizeBytes(count, false);
|
|
||||||
stream.Write(bytes, 0, bytes.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.Write(buffer, offset, count);
|
|
||||||
|
|
||||||
if (chunked)
|
|
||||||
stream.Write(crlf, 0, 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (disposed)
|
|
||||||
throw new ObjectDisposedException(GetType().ToString());
|
|
||||||
|
|
||||||
if (count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var ms = GetHeaders(response, _memoryStreamFactory, false))
|
|
||||||
{
|
|
||||||
bool chunked = response.SendChunked;
|
|
||||||
if (ms != null)
|
|
||||||
{
|
|
||||||
long start = ms.Position; // After the possible preamble for the encoding
|
|
||||||
ms.Position = ms.Length;
|
|
||||||
if (chunked)
|
|
||||||
{
|
|
||||||
var bytes = GetChunkSizeBytes(count, false);
|
|
||||||
ms.Write(bytes, 0, bytes.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
ms.Write(buffer, offset, count);
|
|
||||||
|
|
||||||
if (chunked)
|
|
||||||
{
|
|
||||||
ms.Write(crlf, 0, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
ms.Position = start;
|
|
||||||
await ms.CopyToAsync(stream, MsCopyBufferSize, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunked)
|
|
||||||
{
|
|
||||||
var bytes = GetChunkSizeBytes(count, false);
|
|
||||||
stream.Write(bytes, 0, bytes.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (chunked)
|
|
||||||
stream.Write(crlf, 0, 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int Read([In, Out] byte[] buffer, int offset, int count)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override long Seek(long offset, SeekOrigin origin)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetLength(long value)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool EnableSendFileWithSocket
|
|
||||||
{
|
|
||||||
get { return false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (_supportsDirectSocketAccess && offset == 0 && count == 0 && !response.SendChunked && response.ContentLength64 > 8192)
|
|
||||||
{
|
|
||||||
if (EnableSendFileWithSocket)
|
|
||||||
{
|
|
||||||
return TransmitFileOverSocket(path, offset, count, fileShareMode, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly byte[] _emptyBuffer = new byte[] { };
|
|
||||||
private Task TransmitFileOverSocket(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var ms = GetHeaders(response, _memoryStreamFactory, false);
|
|
||||||
|
|
||||||
byte[] preBuffer;
|
|
||||||
if (ms != null)
|
|
||||||
{
|
|
||||||
using (var msCopy = new MemoryStream())
|
|
||||||
{
|
|
||||||
ms.CopyTo(msCopy);
|
|
||||||
preBuffer = msCopy.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.Info("Socket sending file {0} {1}", path, response.ContentLength64);
|
|
||||||
return _socket.SendFile(path, preBuffer, _emptyBuffer, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task TransmitFileManaged(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var allowAsync = _environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows;
|
|
||||||
|
|
||||||
var fileOpenOptions = offset > 0
|
|
||||||
? FileOpenOptions.RandomAccess
|
|
||||||
: FileOpenOptions.SequentialScan;
|
|
||||||
|
|
||||||
if (allowAsync)
|
|
||||||
{
|
|
||||||
fileOpenOptions |= FileOpenOptions.Asynchronous;
|
|
||||||
}
|
|
||||||
|
|
||||||
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
|
|
||||||
|
|
||||||
using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
|
|
||||||
{
|
|
||||||
if (offset > 0)
|
|
||||||
{
|
|
||||||
fs.Position = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetStream = this;
|
|
||||||
|
|
||||||
if (count > 0)
|
|
||||||
{
|
|
||||||
if (allowAsync)
|
|
||||||
{
|
|
||||||
await CopyToInternalAsync(fs, targetStream, count, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await CopyToInternalAsyncWithSyncRead(fs, targetStream, count, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (allowAsync)
|
|
||||||
{
|
|
||||||
await fs.CopyToAsync(targetStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fs.CopyTo(targetStream, StreamCopyToBufferSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task CopyToInternalAsyncWithSyncRead(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var array = new byte[StreamCopyToBufferSize];
|
|
||||||
int bytesRead;
|
|
||||||
|
|
||||||
while ((bytesRead = source.Read(array, 0, array.Length)) != 0)
|
|
||||||
{
|
|
||||||
var bytesToWrite = Math.Min(bytesRead, copyLength);
|
|
||||||
|
|
||||||
if (bytesToWrite > 0)
|
|
||||||
{
|
|
||||||
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
copyLength -= bytesToWrite;
|
|
||||||
|
|
||||||
if (copyLength <= 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var array = new byte[StreamCopyToBufferSize];
|
|
||||||
int bytesRead;
|
|
||||||
|
|
||||||
while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
|
||||||
{
|
|
||||||
var bytesToWrite = Math.Min(bytesRead, copyLength);
|
|
||||||
|
|
||||||
if (bytesToWrite > 0)
|
|
||||||
{
|
|
||||||
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
copyLength -= bytesToWrite;
|
|
||||||
|
|
||||||
if (copyLength <= 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -70,11 +70,12 @@
|
||||||
<Compile Include="Net\HttpListenerResponse.cs" />
|
<Compile Include="Net\HttpListenerResponse.cs" />
|
||||||
<Compile Include="Net\HttpRequestStream.cs" />
|
<Compile Include="Net\HttpRequestStream.cs" />
|
||||||
<Compile Include="Net\HttpRequestStream.Managed.cs" />
|
<Compile Include="Net\HttpRequestStream.Managed.cs" />
|
||||||
|
<Compile Include="Net\HttpResponseStream.cs" />
|
||||||
|
<Compile Include="Net\HttpResponseStream.Managed.cs" />
|
||||||
<Compile Include="Net\HttpStatusCode.cs" />
|
<Compile Include="Net\HttpStatusCode.cs" />
|
||||||
<Compile Include="Net\HttpStreamAsyncResult.cs" />
|
<Compile Include="Net\HttpStreamAsyncResult.cs" />
|
||||||
<Compile Include="Net\HttpVersion.cs" />
|
<Compile Include="Net\HttpVersion.cs" />
|
||||||
<Compile Include="Net\ListenerPrefix.cs" />
|
<Compile Include="Net\ListenerPrefix.cs" />
|
||||||
<Compile Include="Net\ResponseStream.cs" />
|
|
||||||
<Compile Include="Net\WebHeaderCollection.cs" />
|
<Compile Include="Net\WebHeaderCollection.cs" />
|
||||||
<Compile Include="Net\WebSockets\HttpListenerWebSocketContext.cs" />
|
<Compile Include="Net\WebSockets\HttpListenerWebSocketContext.cs" />
|
||||||
<Compile Include="Net\WebSockets\WebSocketContext.cs" />
|
<Compile Include="Net\WebSockets\WebSocketContext.cs" />
|
||||||
|
|
Loading…
Reference in New Issue
Block a user