diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2017-05-24 15:39:59 -0400 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2017-05-24 15:39:59 -0400 |
| commit | 71f7fc4e116f32a20c63ddbb17844a81283a6ba4 (patch) | |
| tree | d12fde3dd5178579e71e2f268602ad642ffaebe7 /SocketHttpListener/Net/HttpResponseStream.Managed.cs | |
| parent | f07af448fa11330db93dd7ddcabac37ef9e014c7 (diff) | |
3.2.17.12
Diffstat (limited to 'SocketHttpListener/Net/HttpResponseStream.Managed.cs')
| -rw-r--r-- | SocketHttpListener/Net/HttpResponseStream.Managed.cs | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/SocketHttpListener/Net/HttpResponseStream.Managed.cs b/SocketHttpListener/Net/HttpResponseStream.Managed.cs new file mode 100644 index 000000000..0a9efccb2 --- /dev/null +++ b/SocketHttpListener/Net/HttpResponseStream.Managed.cs @@ -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; + } + } + } + } +} |
