You've already forked linux-packaging-mono
Imported Upstream version 5.12.0.220
Former-commit-id: c477e03582759447177c6d4bf412cd2355aad476
This commit is contained in:
parent
8bd104cef2
commit
8fc30896db
File diff suppressed because it is too large
Load Diff
@ -39,6 +39,8 @@ using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Net
|
||||
@ -60,16 +62,28 @@ namespace System.Net
|
||||
Stream stream;
|
||||
|
||||
// Constructors
|
||||
|
||||
internal HttpWebResponse (Uri uri, string method, WebConnectionData data, CookieContainer container)
|
||||
|
||||
internal HttpWebResponse (Uri uri, string method, HttpStatusCode status, WebHeaderCollection headers)
|
||||
{
|
||||
this.uri = uri;
|
||||
this.method = method;
|
||||
webHeaders = data.Headers;
|
||||
version = data.Version;
|
||||
statusCode = (HttpStatusCode) data.StatusCode;
|
||||
statusDescription = data.StatusDescription;
|
||||
stream = data.stream;
|
||||
this.statusCode = status;
|
||||
this.statusDescription = HttpStatusDescription.Get (status);
|
||||
this.webHeaders = headers;
|
||||
version = HttpVersion.Version10;
|
||||
contentLength = -1;
|
||||
}
|
||||
|
||||
internal HttpWebResponse (Uri uri, string method, WebResponseStream stream, CookieContainer container)
|
||||
{
|
||||
this.uri = uri;
|
||||
this.method = method;
|
||||
this.stream = stream;
|
||||
|
||||
webHeaders = stream.Headers ?? new WebHeaderCollection ();
|
||||
version = stream.Version;
|
||||
statusCode = stream.StatusCode;
|
||||
statusDescription = stream.StatusDescription ?? HttpStatusDescription.Get (statusCode);
|
||||
contentLength = -1;
|
||||
|
||||
try {
|
||||
@ -86,12 +100,12 @@ namespace System.Net
|
||||
}
|
||||
|
||||
string content_encoding = webHeaders ["Content-Encoding"];
|
||||
if (content_encoding == "gzip" && (data.request.AutomaticDecompression & DecompressionMethods.GZip) != 0) {
|
||||
stream = new GZipStream (stream, CompressionMode.Decompress);
|
||||
if (content_encoding == "gzip" && (stream.Request.AutomaticDecompression & DecompressionMethods.GZip) != 0) {
|
||||
this.stream = new GZipStream (stream, CompressionMode.Decompress);
|
||||
webHeaders.Remove (HttpRequestHeader.ContentEncoding);
|
||||
}
|
||||
else if (content_encoding == "deflate" && (data.request.AutomaticDecompression & DecompressionMethods.Deflate) != 0) {
|
||||
stream = new DeflateStream (stream, CompressionMode.Decompress);
|
||||
else if (content_encoding == "deflate" && (stream.Request.AutomaticDecompression & DecompressionMethods.Deflate) != 0) {
|
||||
this.stream = new DeflateStream (stream, CompressionMode.Decompress);
|
||||
webHeaders.Remove (HttpRequestHeader.ContentEncoding);
|
||||
}
|
||||
}
|
||||
@ -154,6 +168,8 @@ namespace System.Net
|
||||
|
||||
if (contentType == null)
|
||||
contentType = webHeaders ["Content-Type"];
|
||||
if (contentType == null)
|
||||
contentType = string.Empty;
|
||||
|
||||
return contentType;
|
||||
}
|
||||
@ -263,17 +279,6 @@ namespace System.Net
|
||||
return (value != null) ? value : "";
|
||||
}
|
||||
|
||||
internal void ReadAll ()
|
||||
{
|
||||
WebConnectionStream wce = stream as WebConnectionStream;
|
||||
if (wce == null)
|
||||
return;
|
||||
|
||||
try {
|
||||
wce.ReadAll ();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
public override Stream GetResponseStream ()
|
||||
{
|
||||
CheckDisposed ();
|
||||
@ -310,12 +315,9 @@ namespace System.Net
|
||||
|
||||
public override void Close ()
|
||||
{
|
||||
if (stream != null) {
|
||||
Stream st = stream;
|
||||
stream = null;
|
||||
if (st != null)
|
||||
st.Close ();
|
||||
}
|
||||
var st = Interlocked.Exchange (ref stream, null);
|
||||
if (st != null)
|
||||
st.Close ();
|
||||
}
|
||||
|
||||
void IDisposable.Dispose ()
|
||||
|
@ -1,52 +0,0 @@
|
||||
//
|
||||
// IWebConnectionState.cs
|
||||
//
|
||||
// Author:
|
||||
// Martin Baulig <martin.baulig@xamarin.com>
|
||||
//
|
||||
// Copyright (c) 2014 Xamarin Inc. (http://www.xamarin.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.
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
interface IWebConnectionState {
|
||||
WebConnectionGroup Group {
|
||||
get;
|
||||
}
|
||||
|
||||
ServicePoint ServicePoint {
|
||||
get;
|
||||
}
|
||||
|
||||
bool Busy {
|
||||
get;
|
||||
}
|
||||
|
||||
DateTime IdleSince {
|
||||
get;
|
||||
}
|
||||
|
||||
bool TrySetBusy ();
|
||||
|
||||
void SetIdle ();
|
||||
}
|
||||
}
|
@ -38,20 +38,15 @@ using System.Net.Sockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
|
||||
namespace System.Net
|
||||
namespace System.Net
|
||||
{
|
||||
public class ServicePoint
|
||||
{
|
||||
readonly Uri uri;
|
||||
int connectionLimit;
|
||||
int maxIdleTime;
|
||||
int currentConnections;
|
||||
DateTime idleSince;
|
||||
DateTime lastDnsResolve;
|
||||
Version protocolVersion;
|
||||
IPHostEntry host;
|
||||
bool usesProxy;
|
||||
Dictionary<string,WebConnectionGroup> groups;
|
||||
bool sendContinue = true;
|
||||
bool useConnect;
|
||||
object hostE = new object ();
|
||||
@ -60,21 +55,22 @@ namespace System.Net
|
||||
bool tcp_keepalive;
|
||||
int tcp_keepalive_time;
|
||||
int tcp_keepalive_interval;
|
||||
Timer idleTimer;
|
||||
|
||||
// Constructors
|
||||
|
||||
internal ServicePoint (Uri uri, int connectionLimit, int maxIdleTime)
|
||||
{
|
||||
this.uri = uri;
|
||||
this.connectionLimit = connectionLimit;
|
||||
this.maxIdleTime = maxIdleTime;
|
||||
this.currentConnections = 0;
|
||||
this.idleSince = DateTime.UtcNow;
|
||||
this.uri = uri;
|
||||
|
||||
Scheduler = new ServicePointScheduler (this, connectionLimit, maxIdleTime);
|
||||
}
|
||||
|
||||
|
||||
internal ServicePointScheduler Scheduler {
|
||||
get;
|
||||
}
|
||||
|
||||
// Properties
|
||||
|
||||
|
||||
public Uri Address {
|
||||
get { return uri; }
|
||||
}
|
||||
@ -84,15 +80,13 @@ namespace System.Net
|
||||
return new NotImplementedException ();
|
||||
}
|
||||
|
||||
public BindIPEndPoint BindIPEndPointDelegate
|
||||
{
|
||||
public BindIPEndPoint BindIPEndPointDelegate {
|
||||
get { return endPointCallback; }
|
||||
set { endPointCallback = value; }
|
||||
}
|
||||
|
||||
|
||||
[MonoTODO]
|
||||
public int ConnectionLeaseTimeout
|
||||
{
|
||||
public int ConnectionLeaseTimeout {
|
||||
get {
|
||||
throw GetMustImplement ();
|
||||
}
|
||||
@ -100,54 +94,39 @@ namespace System.Net
|
||||
throw GetMustImplement ();
|
||||
}
|
||||
}
|
||||
|
||||
public int ConnectionLimit {
|
||||
get { return connectionLimit; }
|
||||
set {
|
||||
if (value <= 0)
|
||||
throw new ArgumentOutOfRangeException ();
|
||||
|
||||
connectionLimit = value;
|
||||
}
|
||||
public int ConnectionLimit {
|
||||
get { return Scheduler.ConnectionLimit; }
|
||||
set { Scheduler.ConnectionLimit = value; }
|
||||
}
|
||||
|
||||
|
||||
public string ConnectionName {
|
||||
get { return uri.Scheme; }
|
||||
}
|
||||
|
||||
public int CurrentConnections {
|
||||
get {
|
||||
return currentConnections;
|
||||
return Scheduler.CurrentConnections;
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime IdleSince {
|
||||
get {
|
||||
return idleSince.ToLocalTime ();
|
||||
return Scheduler.IdleSince.ToLocalTime ();
|
||||
}
|
||||
}
|
||||
|
||||
public int MaxIdleTime {
|
||||
get { return maxIdleTime; }
|
||||
set {
|
||||
if (value < Timeout.Infinite || value > Int32.MaxValue)
|
||||
throw new ArgumentOutOfRangeException ();
|
||||
|
||||
lock (this) {
|
||||
maxIdleTime = value;
|
||||
if (idleTimer != null)
|
||||
idleTimer.Change (maxIdleTime, maxIdleTime);
|
||||
}
|
||||
}
|
||||
get { return Scheduler.MaxIdleTime; }
|
||||
set { Scheduler.MaxIdleTime = value; }
|
||||
}
|
||||
|
||||
|
||||
public virtual Version ProtocolVersion {
|
||||
get { return protocolVersion; }
|
||||
}
|
||||
|
||||
[MonoTODO]
|
||||
public int ReceiveBufferSize
|
||||
{
|
||||
public int ReceiveBufferSize {
|
||||
get {
|
||||
throw GetMustImplement ();
|
||||
}
|
||||
@ -155,7 +134,7 @@ namespace System.Net
|
||||
throw GetMustImplement ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool SupportsPipelining {
|
||||
get { return HttpVersion.Version11.Equals (protocolVersion); }
|
||||
}
|
||||
@ -172,8 +151,10 @@ namespace System.Net
|
||||
}
|
||||
|
||||
internal bool SendContinue {
|
||||
get { return sendContinue &&
|
||||
(protocolVersion == null || protocolVersion == HttpVersion.Version11); }
|
||||
get {
|
||||
return sendContinue &&
|
||||
(protocolVersion == null || protocolVersion == HttpVersion.Version11);
|
||||
}
|
||||
set { sendContinue = value; }
|
||||
}
|
||||
// Methods
|
||||
@ -197,25 +178,25 @@ namespace System.Net
|
||||
if (!tcp_keepalive)
|
||||
return;
|
||||
|
||||
byte [] bytes = new byte [12];
|
||||
PutBytes (bytes, (uint) (tcp_keepalive ? 1 : 0), 0);
|
||||
PutBytes (bytes, (uint) tcp_keepalive_time, 4);
|
||||
PutBytes (bytes, (uint) tcp_keepalive_interval, 8);
|
||||
byte[] bytes = new byte[12];
|
||||
PutBytes (bytes, (uint)(tcp_keepalive ? 1 : 0), 0);
|
||||
PutBytes (bytes, (uint)tcp_keepalive_time, 4);
|
||||
PutBytes (bytes, (uint)tcp_keepalive_interval, 8);
|
||||
socket.IOControl (IOControlCode.KeepAliveValues, bytes, null);
|
||||
}
|
||||
|
||||
static void PutBytes (byte [] bytes, uint v, int offset)
|
||||
static void PutBytes (byte[] bytes, uint v, int offset)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian) {
|
||||
bytes [offset] = (byte) (v & 0x000000ff);
|
||||
bytes [offset + 1] = (byte) ((v & 0x0000ff00) >> 8);
|
||||
bytes [offset + 2] = (byte) ((v & 0x00ff0000) >> 16);
|
||||
bytes [offset + 3] = (byte) ((v & 0xff000000) >> 24);
|
||||
bytes[offset] = (byte)(v & 0x000000ff);
|
||||
bytes[offset + 1] = (byte)((v & 0x0000ff00) >> 8);
|
||||
bytes[offset + 2] = (byte)((v & 0x00ff0000) >> 16);
|
||||
bytes[offset + 3] = (byte)((v & 0xff000000) >> 24);
|
||||
} else {
|
||||
bytes [offset + 3] = (byte) (v & 0x000000ff);
|
||||
bytes [offset + 2] = (byte) ((v & 0x0000ff00) >> 8);
|
||||
bytes [offset + 1] = (byte) ((v & 0x00ff0000) >> 16);
|
||||
bytes [offset] = (byte) ((v & 0xff000000) >> 24);
|
||||
bytes[offset + 3] = (byte)(v & 0x000000ff);
|
||||
bytes[offset + 2] = (byte)((v & 0x0000ff00) >> 8);
|
||||
bytes[offset + 1] = (byte)((v & 0x00ff0000) >> 16);
|
||||
bytes[offset] = (byte)((v & 0xff000000) >> 24);
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,107 +212,7 @@ namespace System.Net
|
||||
set { useConnect = value; }
|
||||
}
|
||||
|
||||
WebConnectionGroup GetConnectionGroup (string name)
|
||||
{
|
||||
if (name == null)
|
||||
name = "";
|
||||
|
||||
/*
|
||||
* Optimization:
|
||||
*
|
||||
* In the vast majority of cases, we only have one single WebConnectionGroup per ServicePoint, so we
|
||||
* don't need to allocate a dictionary.
|
||||
*
|
||||
*/
|
||||
|
||||
WebConnectionGroup group;
|
||||
if (groups != null && groups.TryGetValue (name, out group))
|
||||
return group;
|
||||
|
||||
group = new WebConnectionGroup (this, name);
|
||||
group.ConnectionClosed += (s, e) => currentConnections--;
|
||||
|
||||
if (groups == null)
|
||||
groups = new Dictionary<string, WebConnectionGroup> ();
|
||||
groups.Add (name, group);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
void RemoveConnectionGroup (WebConnectionGroup group)
|
||||
{
|
||||
if (groups == null || groups.Count == 0)
|
||||
throw new InvalidOperationException ();
|
||||
|
||||
groups.Remove (group.Name);
|
||||
}
|
||||
|
||||
bool CheckAvailableForRecycling (out DateTime outIdleSince)
|
||||
{
|
||||
outIdleSince = DateTime.MinValue;
|
||||
|
||||
TimeSpan idleTimeSpan;
|
||||
List<WebConnectionGroup> groupList = null, removeList = null;
|
||||
lock (this) {
|
||||
if (groups == null || groups.Count == 0) {
|
||||
idleSince = DateTime.MinValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
idleTimeSpan = TimeSpan.FromMilliseconds (maxIdleTime);
|
||||
|
||||
/*
|
||||
* WebConnectionGroup.TryRecycle() must run outside the lock, so we need to
|
||||
* copy the group dictionary if it exists.
|
||||
*
|
||||
* In most cases, we only have a single connection group, so we can simply store
|
||||
* that in a local variable instead of copying a collection.
|
||||
*
|
||||
*/
|
||||
|
||||
groupList = new List<WebConnectionGroup> (groups.Values);
|
||||
}
|
||||
|
||||
foreach (var group in groupList) {
|
||||
if (!group.TryRecycle (idleTimeSpan, ref outIdleSince))
|
||||
continue;
|
||||
if (removeList == null)
|
||||
removeList = new List<WebConnectionGroup> ();
|
||||
removeList.Add (group);
|
||||
}
|
||||
|
||||
lock (this) {
|
||||
idleSince = outIdleSince;
|
||||
|
||||
if (removeList != null && groups != null) {
|
||||
foreach (var group in removeList)
|
||||
if (groups.ContainsKey (group.Name))
|
||||
RemoveConnectionGroup (group);
|
||||
}
|
||||
|
||||
if (groups != null && groups.Count == 0)
|
||||
groups = null;
|
||||
|
||||
if (groups == null) {
|
||||
if (idleTimer != null) {
|
||||
idleTimer.Dispose ();
|
||||
idleTimer = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void IdleTimerCallback (object obj)
|
||||
{
|
||||
DateTime dummy;
|
||||
CheckAvailableForRecycling (out dummy);
|
||||
}
|
||||
|
||||
private bool HasTimedOut
|
||||
{
|
||||
private bool HasTimedOut {
|
||||
get {
|
||||
int timeout = ServicePointManager.DnsRefreshTimeout;
|
||||
return timeout != Timeout.Infinite &&
|
||||
@ -339,8 +220,7 @@ namespace System.Net
|
||||
}
|
||||
}
|
||||
|
||||
internal IPHostEntry HostEntry
|
||||
{
|
||||
internal IPHostEntry HostEntry {
|
||||
get {
|
||||
lock (hostE) {
|
||||
string uriHost = uri.Host;
|
||||
@ -356,7 +236,7 @@ namespace System.Net
|
||||
}
|
||||
|
||||
// Creates IPHostEntry
|
||||
host = new IPHostEntry();
|
||||
host = new IPHostEntry ();
|
||||
host.AddressList = new IPAddress[] { IPAddress.Parse (uriHost) };
|
||||
return host;
|
||||
}
|
||||
@ -382,41 +262,18 @@ namespace System.Net
|
||||
protocolVersion = version;
|
||||
}
|
||||
|
||||
internal EventHandler SendRequest (HttpWebRequest request, string groupName)
|
||||
internal void SendRequest (WebOperation operation, string groupName)
|
||||
{
|
||||
WebConnection cnc;
|
||||
|
||||
lock (this) {
|
||||
bool created;
|
||||
WebConnectionGroup cncGroup = GetConnectionGroup (groupName);
|
||||
cnc = cncGroup.GetConnection (request, out created);
|
||||
if (created) {
|
||||
++currentConnections;
|
||||
if (idleTimer == null)
|
||||
idleTimer = new Timer (IdleTimerCallback, null, maxIdleTime, maxIdleTime);
|
||||
}
|
||||
Scheduler.SendRequest (operation, groupName);
|
||||
}
|
||||
|
||||
return cnc.SendRequest (request);
|
||||
}
|
||||
|
||||
public bool CloseConnectionGroup (string connectionGroupName)
|
||||
{
|
||||
WebConnectionGroup cncGroup = null;
|
||||
|
||||
lock (this) {
|
||||
cncGroup = GetConnectionGroup (connectionGroupName);
|
||||
if (cncGroup != null) {
|
||||
RemoveConnectionGroup (cncGroup);
|
||||
}
|
||||
return Scheduler.CloseConnectionGroup (connectionGroupName);
|
||||
}
|
||||
|
||||
// WebConnectionGroup.Close() must *not* be called inside the lock
|
||||
if (cncGroup != null) {
|
||||
cncGroup.Close ();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
|
646
mcs/class/System/System.Net/ServicePointScheduler.cs
Normal file
646
mcs/class/System/System.Net/ServicePointScheduler.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,232 +0,0 @@
|
||||
//
|
||||
// SimpleAsyncResult.cs
|
||||
//
|
||||
// Authors:
|
||||
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
|
||||
// Martin Baulig (martin.baulig@xamarin.com)
|
||||
//
|
||||
// (C) 2003 Ximian, Inc (http://www.ximian.com)
|
||||
// Copyright (c) 2014 Xamarin Inc. (http://www.xamarin.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.
|
||||
//
|
||||
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
delegate void SimpleAsyncCallback (SimpleAsyncResult result);
|
||||
|
||||
class SimpleAsyncResult : IAsyncResult
|
||||
{
|
||||
ManualResetEvent handle;
|
||||
bool synch;
|
||||
bool isCompleted;
|
||||
readonly SimpleAsyncCallback cb;
|
||||
object state;
|
||||
bool callbackDone;
|
||||
Exception exc;
|
||||
object locker = new object ();
|
||||
|
||||
SimpleAsyncResult (SimpleAsyncCallback cb)
|
||||
{
|
||||
this.cb = cb;
|
||||
}
|
||||
|
||||
protected SimpleAsyncResult (AsyncCallback cb, object state)
|
||||
{
|
||||
this.state = state;
|
||||
this.cb = result => {
|
||||
if (cb != null)
|
||||
cb (this);
|
||||
};
|
||||
}
|
||||
|
||||
public static void Run (Func<SimpleAsyncResult, bool> func, SimpleAsyncCallback callback)
|
||||
{
|
||||
var result = new SimpleAsyncResult (callback);
|
||||
try {
|
||||
if (!func (result))
|
||||
result.SetCompleted (true);
|
||||
} catch (Exception ex) {
|
||||
result.SetCompleted (true, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RunWithLock (object locker, Func<SimpleAsyncResult, bool> func, SimpleAsyncCallback callback)
|
||||
{
|
||||
Run (inner => {
|
||||
bool running = func (inner);
|
||||
if (running)
|
||||
Monitor.Exit (locker);
|
||||
return running;
|
||||
}, inner => {
|
||||
if (inner.GotException) {
|
||||
if (inner.synch)
|
||||
Monitor.Exit (locker);
|
||||
callback (inner);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!inner.synch)
|
||||
Monitor.Enter (locker);
|
||||
|
||||
callback (inner);
|
||||
} finally {
|
||||
Monitor.Exit (locker);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void Reset_internal ()
|
||||
{
|
||||
callbackDone = false;
|
||||
exc = null;
|
||||
lock (locker) {
|
||||
isCompleted = false;
|
||||
if (handle != null)
|
||||
handle.Reset ();
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetCompleted (bool synch, Exception e)
|
||||
{
|
||||
SetCompleted_internal (synch, e);
|
||||
DoCallback_private ();
|
||||
}
|
||||
|
||||
internal void SetCompleted (bool synch)
|
||||
{
|
||||
SetCompleted_internal (synch);
|
||||
DoCallback_private ();
|
||||
}
|
||||
|
||||
void SetCompleted_internal (bool synch, Exception e)
|
||||
{
|
||||
this.synch = synch;
|
||||
exc = e;
|
||||
lock (locker) {
|
||||
isCompleted = true;
|
||||
if (handle != null)
|
||||
handle.Set ();
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetCompleted_internal (bool synch)
|
||||
{
|
||||
SetCompleted_internal (synch, null);
|
||||
}
|
||||
|
||||
void DoCallback_private ()
|
||||
{
|
||||
if (callbackDone)
|
||||
return;
|
||||
callbackDone = true;
|
||||
if (cb == null)
|
||||
return;
|
||||
cb (this);
|
||||
}
|
||||
|
||||
protected void DoCallback_internal ()
|
||||
{
|
||||
if (!callbackDone && cb != null) {
|
||||
callbackDone = true;
|
||||
cb (this);
|
||||
}
|
||||
}
|
||||
|
||||
internal void WaitUntilComplete ()
|
||||
{
|
||||
if (IsCompleted)
|
||||
return;
|
||||
|
||||
AsyncWaitHandle.WaitOne ();
|
||||
}
|
||||
|
||||
internal bool WaitUntilComplete (int timeout, bool exitContext)
|
||||
{
|
||||
if (IsCompleted)
|
||||
return true;
|
||||
|
||||
return AsyncWaitHandle.WaitOne (timeout, exitContext);
|
||||
}
|
||||
|
||||
public object AsyncState {
|
||||
get { return state; }
|
||||
}
|
||||
|
||||
public WaitHandle AsyncWaitHandle {
|
||||
get {
|
||||
lock (locker) {
|
||||
if (handle == null)
|
||||
handle = new ManualResetEvent (isCompleted);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
bool? user_read_synch;
|
||||
|
||||
public bool CompletedSynchronously {
|
||||
get {
|
||||
//
|
||||
// CompletedSynchronously (for System.Net networking stack) means "was the operation completed before the first time
|
||||
// that somebody asked if it was completed synchronously"? They do this because some of their asynchronous operations
|
||||
// (particularly those in the Socket class) will avoid the cost of capturing and transferring the ExecutionContext
|
||||
// to the callback thread by checking CompletedSynchronously, and calling the callback from within BeginXxx instead of
|
||||
// on the completion port thread if the native winsock call completes quickly.
|
||||
//
|
||||
// TODO: racy
|
||||
if (user_read_synch != null)
|
||||
return user_read_synch.Value;
|
||||
|
||||
user_read_synch = synch;
|
||||
return user_read_synch.Value;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool CompletedSynchronouslyPeek {
|
||||
get {
|
||||
return synch;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCompleted {
|
||||
get {
|
||||
lock (locker) {
|
||||
return isCompleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal bool GotException {
|
||||
get { return (exc != null); }
|
||||
}
|
||||
|
||||
internal Exception Exception {
|
||||
get { return exc; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,132 +0,0 @@
|
||||
//
|
||||
// System.Net.WebAsyncResult
|
||||
//
|
||||
// Authors:
|
||||
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
|
||||
//
|
||||
// (C) 2003 Ximian, Inc (http://www.ximian.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.
|
||||
//
|
||||
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
class WebAsyncResult : SimpleAsyncResult
|
||||
{
|
||||
int nbytes;
|
||||
IAsyncResult innerAsyncResult;
|
||||
HttpWebResponse response;
|
||||
Stream writeStream;
|
||||
byte [] buffer;
|
||||
int offset;
|
||||
int size;
|
||||
public bool EndCalled;
|
||||
public bool AsyncWriteAll;
|
||||
public HttpWebRequest AsyncObject;
|
||||
|
||||
public WebAsyncResult (AsyncCallback cb, object state)
|
||||
: base (cb, state)
|
||||
{
|
||||
}
|
||||
|
||||
public WebAsyncResult (HttpWebRequest request, AsyncCallback cb, object state)
|
||||
: base (cb, state)
|
||||
{
|
||||
this.AsyncObject = request;
|
||||
}
|
||||
|
||||
public WebAsyncResult (AsyncCallback cb, object state, byte [] buffer, int offset, int size)
|
||||
: base (cb, state)
|
||||
{
|
||||
this.buffer = buffer;
|
||||
this.offset = offset;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
internal void Reset ()
|
||||
{
|
||||
this.nbytes = 0;
|
||||
this.response = null;
|
||||
this.buffer = null;
|
||||
this.offset = 0;
|
||||
this.size = 0;
|
||||
Reset_internal ();
|
||||
}
|
||||
|
||||
internal void SetCompleted (bool synch, int nbytes)
|
||||
{
|
||||
this.nbytes = nbytes;
|
||||
SetCompleted_internal (synch);
|
||||
}
|
||||
|
||||
internal void SetCompleted (bool synch, Stream writeStream)
|
||||
{
|
||||
this.writeStream = writeStream;
|
||||
SetCompleted_internal (synch);
|
||||
}
|
||||
|
||||
internal void SetCompleted (bool synch, HttpWebResponse response)
|
||||
{
|
||||
this.response = response;
|
||||
SetCompleted_internal (synch);
|
||||
}
|
||||
|
||||
internal void DoCallback ()
|
||||
{
|
||||
DoCallback_internal ();
|
||||
}
|
||||
|
||||
internal int NBytes {
|
||||
get { return nbytes; }
|
||||
set { nbytes = value; }
|
||||
}
|
||||
|
||||
internal IAsyncResult InnerAsyncResult {
|
||||
get { return innerAsyncResult; }
|
||||
set { innerAsyncResult = value; }
|
||||
}
|
||||
|
||||
internal Stream WriteStream {
|
||||
get { return writeStream; }
|
||||
}
|
||||
|
||||
internal HttpWebResponse Response {
|
||||
get { return response; }
|
||||
}
|
||||
|
||||
internal byte [] Buffer {
|
||||
get { return buffer; }
|
||||
}
|
||||
|
||||
internal int Offset {
|
||||
get { return offset; }
|
||||
}
|
||||
|
||||
internal int Size {
|
||||
get { return size; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
155
mcs/class/System/System.Net/WebCompletionSource.cs
Normal file
155
mcs/class/System/System.Net/WebCompletionSource.cs
Normal file
@ -0,0 +1,155 @@
|
||||
//
|
||||
// WebCompletionSource.cs
|
||||
//
|
||||
// Author:
|
||||
// Martin Baulig <mabaul@microsoft.com>
|
||||
//
|
||||
// Copyright (c) 2018 Xamarin Inc. (http://www.xamarin.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.
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.ExceptionServices;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
class WebCompletionSource<T>
|
||||
{
|
||||
TaskCompletionSource<Result> completion;
|
||||
Result currentResult;
|
||||
|
||||
public WebCompletionSource (bool runAsync = true)
|
||||
{
|
||||
completion = new TaskCompletionSource<Result> (
|
||||
runAsync ?
|
||||
TaskCreationOptions.RunContinuationsAsynchronously :
|
||||
TaskCreationOptions.None);
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide a non-blocking way of getting the current status.
|
||||
*
|
||||
* We are using `TaskCreationOptions.RunContinuationsAsynchronously`
|
||||
* to prevent any user continuations from being run during the
|
||||
* `TrySet*()` methods - to make these safe to be called while holding
|
||||
* internal locks.
|
||||
*
|
||||
*/
|
||||
internal Result CurrentResult => currentResult;
|
||||
|
||||
internal Status CurrentStatus => currentResult?.Status ?? Status.Running;
|
||||
|
||||
internal Task Task => completion.Task;
|
||||
|
||||
public bool TrySetCompleted (T argument)
|
||||
{
|
||||
var result = new Result (argument);
|
||||
if (Interlocked.CompareExchange (ref currentResult, result, null) != null)
|
||||
return false;
|
||||
return completion.TrySetResult (result);
|
||||
}
|
||||
|
||||
public bool TrySetCompleted ()
|
||||
{
|
||||
var result = new Result (Status.Completed, null);
|
||||
if (Interlocked.CompareExchange (ref currentResult, result, null) != null)
|
||||
return false;
|
||||
return completion.TrySetResult (result);
|
||||
}
|
||||
|
||||
public bool TrySetCanceled ()
|
||||
{
|
||||
return TrySetCanceled (new OperationCanceledException ());
|
||||
}
|
||||
|
||||
public bool TrySetCanceled (OperationCanceledException error)
|
||||
{
|
||||
var result = new Result (Status.Canceled, ExceptionDispatchInfo.Capture (error));
|
||||
if (Interlocked.CompareExchange (ref currentResult, result, null) != null)
|
||||
return false;
|
||||
return completion.TrySetResult (result);
|
||||
}
|
||||
|
||||
public bool TrySetException (Exception error)
|
||||
{
|
||||
var result = new Result (Status.Faulted, ExceptionDispatchInfo.Capture (error));
|
||||
if (Interlocked.CompareExchange (ref currentResult, result, null) != null)
|
||||
return false;
|
||||
return completion.TrySetResult (result);
|
||||
}
|
||||
|
||||
public void ThrowOnError ()
|
||||
{
|
||||
if (!completion.Task.IsCompleted)
|
||||
return;
|
||||
completion.Task.Result.Error?.Throw ();
|
||||
}
|
||||
|
||||
public async Task<T> WaitForCompletion ()
|
||||
{
|
||||
var result = await completion.Task.ConfigureAwait (false);
|
||||
if (result.Status == Status.Completed)
|
||||
return (T)result.Argument;
|
||||
// This will always throw once we get here.
|
||||
result.Error.Throw ();
|
||||
throw new InvalidOperationException ("Should never happen.");
|
||||
}
|
||||
|
||||
internal enum Status : int {
|
||||
Running,
|
||||
Completed,
|
||||
Canceled,
|
||||
Faulted
|
||||
}
|
||||
|
||||
internal class Result
|
||||
{
|
||||
public Status Status {
|
||||
get;
|
||||
}
|
||||
|
||||
public bool Success => Status == Status.Completed;
|
||||
|
||||
public ExceptionDispatchInfo Error {
|
||||
get;
|
||||
}
|
||||
|
||||
public T Argument {
|
||||
get;
|
||||
}
|
||||
|
||||
public Result (T argument)
|
||||
{
|
||||
Status = Status.Completed;
|
||||
Argument = argument;
|
||||
}
|
||||
|
||||
public Result (Status state, ExceptionDispatchInfo error)
|
||||
{
|
||||
Status = state;
|
||||
Error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WebCompletionSource : WebCompletionSource<object>
|
||||
{
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,80 +0,0 @@
|
||||
//
|
||||
// System.Net.WebConnectionData
|
||||
//
|
||||
// Authors:
|
||||
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
|
||||
//
|
||||
// (C) 2003 Ximian, Inc (http://www.ximian.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.
|
||||
//
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
class WebConnectionData
|
||||
{
|
||||
HttpWebRequest _request;
|
||||
public int StatusCode;
|
||||
public string StatusDescription;
|
||||
public WebHeaderCollection Headers;
|
||||
public Version Version;
|
||||
public Version ProxyVersion;
|
||||
public Stream stream;
|
||||
public string[] Challenge;
|
||||
ReadState _readState;
|
||||
|
||||
public WebConnectionData ()
|
||||
{
|
||||
_readState = ReadState.None;
|
||||
}
|
||||
|
||||
public WebConnectionData (HttpWebRequest request)
|
||||
{
|
||||
this._request = request;
|
||||
}
|
||||
|
||||
public HttpWebRequest request {
|
||||
get {
|
||||
return _request;
|
||||
}
|
||||
set {
|
||||
_request = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ReadState ReadState {
|
||||
get {
|
||||
return _readState;
|
||||
}
|
||||
set {
|
||||
lock (this) {
|
||||
if ((_readState == ReadState.Aborted) && (value != ReadState.Aborted))
|
||||
throw new WebException ("Aborted", WebExceptionStatus.RequestCanceled);
|
||||
_readState = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,292 +0,0 @@
|
||||
//
|
||||
// System.Net.WebConnectionGroup
|
||||
//
|
||||
// Authors:
|
||||
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
|
||||
// Martin Baulig (martin.baulig@xamarin.com)
|
||||
//
|
||||
// (C) 2003 Ximian, Inc (http://www.ximian.com)
|
||||
// Copyright 2011-2014 Xamarin, Inc (http://www.xamarin.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.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Configuration;
|
||||
using System.Net.Sockets;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
class WebConnectionGroup
|
||||
{
|
||||
ServicePoint sPoint;
|
||||
string name;
|
||||
LinkedList<ConnectionState> connections;
|
||||
Queue queue;
|
||||
bool closing;
|
||||
|
||||
public WebConnectionGroup (ServicePoint sPoint, string name)
|
||||
{
|
||||
this.sPoint = sPoint;
|
||||
this.name = name;
|
||||
connections = new LinkedList<ConnectionState> ();
|
||||
queue = new Queue ();
|
||||
}
|
||||
|
||||
public event EventHandler ConnectionClosed;
|
||||
|
||||
void OnConnectionClosed ()
|
||||
{
|
||||
if (ConnectionClosed != null)
|
||||
ConnectionClosed (this, null);
|
||||
}
|
||||
|
||||
public void Close ()
|
||||
{
|
||||
List<WebConnection> connectionsToClose = null;
|
||||
|
||||
//TODO: what do we do with the queue? Empty it out and abort the requests?
|
||||
//TODO: abort requests or wait for them to finish
|
||||
lock (sPoint) {
|
||||
closing = true;
|
||||
var iter = connections.First;
|
||||
while (iter != null) {
|
||||
var cnc = iter.Value.Connection;
|
||||
var node = iter;
|
||||
iter = iter.Next;
|
||||
|
||||
// Closing connections inside the lock leads to a deadlock.
|
||||
if (connectionsToClose == null)
|
||||
connectionsToClose = new List<WebConnection>();
|
||||
|
||||
connectionsToClose.Add (cnc);
|
||||
connections.Remove (node);
|
||||
}
|
||||
}
|
||||
|
||||
if (connectionsToClose != null) {
|
||||
foreach (var cnc in connectionsToClose) {
|
||||
cnc.Close (false);
|
||||
OnConnectionClosed ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public WebConnection GetConnection (HttpWebRequest request, out bool created)
|
||||
{
|
||||
lock (sPoint) {
|
||||
return CreateOrReuseConnection (request, out created);
|
||||
}
|
||||
}
|
||||
|
||||
static void PrepareSharingNtlm (WebConnection cnc, HttpWebRequest request)
|
||||
{
|
||||
if (!cnc.NtlmAuthenticated)
|
||||
return;
|
||||
|
||||
bool needs_reset = false;
|
||||
NetworkCredential cnc_cred = cnc.NtlmCredential;
|
||||
|
||||
bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.RequestUri));
|
||||
ICredentials req_icreds = (!isProxy) ? request.Credentials : request.Proxy.Credentials;
|
||||
NetworkCredential req_cred = (req_icreds != null) ? req_icreds.GetCredential (request.RequestUri, "NTLM") : null;
|
||||
|
||||
if (cnc_cred == null || req_cred == null ||
|
||||
cnc_cred.Domain != req_cred.Domain || cnc_cred.UserName != req_cred.UserName ||
|
||||
cnc_cred.Password != req_cred.Password) {
|
||||
needs_reset = true;
|
||||
}
|
||||
|
||||
if (!needs_reset) {
|
||||
bool req_sharing = request.UnsafeAuthenticatedConnectionSharing;
|
||||
bool cnc_sharing = cnc.UnsafeAuthenticatedConnectionSharing;
|
||||
needs_reset = (req_sharing == false || req_sharing != cnc_sharing);
|
||||
}
|
||||
if (needs_reset) {
|
||||
cnc.Close (false); // closes the authenticated connection
|
||||
cnc.ResetNtlm ();
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionState FindIdleConnection ()
|
||||
{
|
||||
foreach (var cnc in connections) {
|
||||
if (cnc.Busy)
|
||||
continue;
|
||||
|
||||
connections.Remove (cnc);
|
||||
connections.AddFirst (cnc);
|
||||
return cnc;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
WebConnection CreateOrReuseConnection (HttpWebRequest request, out bool created)
|
||||
{
|
||||
var cnc = FindIdleConnection ();
|
||||
if (cnc != null) {
|
||||
created = false;
|
||||
PrepareSharingNtlm (cnc.Connection, request);
|
||||
return cnc.Connection;
|
||||
}
|
||||
|
||||
if (sPoint.ConnectionLimit > connections.Count || connections.Count == 0) {
|
||||
created = true;
|
||||
cnc = new ConnectionState (this);
|
||||
connections.AddFirst (cnc);
|
||||
return cnc.Connection;
|
||||
}
|
||||
|
||||
created = false;
|
||||
cnc = connections.Last.Value;
|
||||
connections.Remove (cnc);
|
||||
connections.AddFirst (cnc);
|
||||
return cnc.Connection;
|
||||
}
|
||||
|
||||
public string Name {
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
internal Queue Queue {
|
||||
get { return queue; }
|
||||
}
|
||||
|
||||
internal bool TryRecycle (TimeSpan maxIdleTime, ref DateTime idleSince)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
again:
|
||||
bool recycled;
|
||||
List<WebConnection> connectionsToClose = null;
|
||||
|
||||
lock (sPoint) {
|
||||
if (closing) {
|
||||
idleSince = DateTime.MinValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
var iter = connections.First;
|
||||
while (iter != null) {
|
||||
var cnc = iter.Value;
|
||||
var node = iter;
|
||||
iter = iter.Next;
|
||||
|
||||
++count;
|
||||
if (cnc.Busy)
|
||||
continue;
|
||||
|
||||
if (count <= sPoint.ConnectionLimit && now - cnc.IdleSince < maxIdleTime) {
|
||||
if (cnc.IdleSince > idleSince)
|
||||
idleSince = cnc.IdleSince;
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Do not call WebConnection.Close() while holding the ServicePoint lock
|
||||
* as this could deadlock when attempting to take the WebConnection lock.
|
||||
*
|
||||
*/
|
||||
|
||||
if (connectionsToClose == null)
|
||||
connectionsToClose = new List<WebConnection> ();
|
||||
connectionsToClose.Add (cnc.Connection);
|
||||
connections.Remove (node);
|
||||
}
|
||||
|
||||
recycled = connections.Count == 0;
|
||||
}
|
||||
|
||||
// Did we find anything that can be closed?
|
||||
if (connectionsToClose == null)
|
||||
return recycled;
|
||||
|
||||
// Ok, let's get rid of these!
|
||||
foreach (var cnc in connectionsToClose)
|
||||
cnc.Close (false);
|
||||
|
||||
// Re-take the lock, then remove them from the connection list.
|
||||
goto again;
|
||||
}
|
||||
|
||||
class ConnectionState : IWebConnectionState {
|
||||
public WebConnection Connection {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public WebConnectionGroup Group {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public ServicePoint ServicePoint {
|
||||
get { return Group.sPoint; }
|
||||
}
|
||||
|
||||
bool busy;
|
||||
DateTime idleSince;
|
||||
|
||||
public bool Busy {
|
||||
get { return busy; }
|
||||
}
|
||||
|
||||
public DateTime IdleSince {
|
||||
get { return idleSince; }
|
||||
}
|
||||
|
||||
public bool TrySetBusy ()
|
||||
{
|
||||
lock (ServicePoint) {
|
||||
if (busy)
|
||||
return false;
|
||||
busy = true;
|
||||
idleSince = DateTime.UtcNow + TimeSpan.FromDays (3650);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetIdle ()
|
||||
{
|
||||
lock (ServicePoint) {
|
||||
busy = false;
|
||||
idleSince = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectionState (WebConnectionGroup group)
|
||||
{
|
||||
Group = group;
|
||||
idleSince = DateTime.UtcNow;
|
||||
Connection = new WebConnection (this, group.sPoint);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
271
mcs/class/System/System.Net/WebConnectionTunnel.cs
Normal file
271
mcs/class/System/System.Net/WebConnectionTunnel.cs
Normal file
@ -0,0 +1,271 @@
|
||||
//
|
||||
// System.Net.WebConnectionTunnel
|
||||
//
|
||||
// Authors:
|
||||
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
|
||||
// Martin Baulig <mabaul@microsoft.com>
|
||||
//
|
||||
// (C) 2003 Ximian, Inc (http://www.ximian.com)
|
||||
// Copyright (c) 2017 Xamarin Inc. (http://www.xamarin.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.
|
||||
//
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Diagnostics;
|
||||
using Mono.Net.Security;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
class WebConnectionTunnel
|
||||
{
|
||||
public HttpWebRequest Request {
|
||||
get;
|
||||
}
|
||||
|
||||
public Uri ConnectUri {
|
||||
get;
|
||||
}
|
||||
|
||||
public WebConnectionTunnel (HttpWebRequest request, Uri connectUri)
|
||||
{
|
||||
Request = request;
|
||||
ConnectUri = connectUri;
|
||||
}
|
||||
|
||||
enum NtlmAuthState
|
||||
{
|
||||
None,
|
||||
Challenge,
|
||||
Response
|
||||
}
|
||||
|
||||
HttpWebRequest connectRequest;
|
||||
NtlmAuthState ntlmAuthState;
|
||||
|
||||
public bool Success {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool CloseConnection {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public int StatusCode {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string StatusDescription {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string[] Challenge {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public WebHeaderCollection Headers {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Version ProxyVersion {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public byte[] Data {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
internal async Task Initialize (Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder ();
|
||||
sb.Append ("CONNECT ");
|
||||
sb.Append (Request.Address.Host);
|
||||
sb.Append (':');
|
||||
sb.Append (Request.Address.Port);
|
||||
sb.Append (" HTTP/");
|
||||
if (Request.ProtocolVersion == HttpVersion.Version11)
|
||||
sb.Append ("1.1");
|
||||
else
|
||||
sb.Append ("1.0");
|
||||
|
||||
sb.Append ("\r\nHost: ");
|
||||
sb.Append (Request.Address.Authority);
|
||||
|
||||
bool ntlm = false;
|
||||
var challenge = Challenge;
|
||||
Challenge = null;
|
||||
var auth_header = Request.Headers["Proxy-Authorization"];
|
||||
bool have_auth = auth_header != null;
|
||||
if (have_auth) {
|
||||
sb.Append ("\r\nProxy-Authorization: ");
|
||||
sb.Append (auth_header);
|
||||
ntlm = auth_header.ToUpper ().Contains ("NTLM");
|
||||
} else if (challenge != null && StatusCode == 407) {
|
||||
ICredentials creds = Request.Proxy.Credentials;
|
||||
have_auth = true;
|
||||
|
||||
if (connectRequest == null) {
|
||||
// create a CONNECT request to use with Authenticate
|
||||
connectRequest = (HttpWebRequest)WebRequest.Create (
|
||||
ConnectUri.Scheme + "://" + ConnectUri.Host + ":" + ConnectUri.Port + "/");
|
||||
connectRequest.Method = "CONNECT";
|
||||
connectRequest.Credentials = creds;
|
||||
}
|
||||
|
||||
if (creds != null) {
|
||||
for (int i = 0; i < challenge.Length; i++) {
|
||||
var auth = AuthenticationManager.Authenticate (challenge[i], connectRequest, creds);
|
||||
if (auth == null)
|
||||
continue;
|
||||
ntlm = (auth.ModuleAuthenticationType == "NTLM");
|
||||
sb.Append ("\r\nProxy-Authorization: ");
|
||||
sb.Append (auth.Message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ntlm) {
|
||||
sb.Append ("\r\nProxy-Connection: keep-alive");
|
||||
ntlmAuthState++;
|
||||
}
|
||||
|
||||
sb.Append ("\r\n\r\n");
|
||||
|
||||
StatusCode = 0;
|
||||
byte[] connectBytes = Encoding.Default.GetBytes (sb.ToString ());
|
||||
await stream.WriteAsync (connectBytes, 0, connectBytes.Length, cancellationToken).ConfigureAwait (false);
|
||||
|
||||
(Headers, Data, StatusCode) = await ReadHeaders (stream, cancellationToken).ConfigureAwait (false);
|
||||
|
||||
if ((!have_auth || ntlmAuthState == NtlmAuthState.Challenge) && Headers != null && StatusCode == 407) { // Needs proxy auth
|
||||
var connectionHeader = Headers["Connection"];
|
||||
if (!string.IsNullOrEmpty (connectionHeader) && connectionHeader.ToLower () == "close") {
|
||||
// The server is requesting that this connection be closed
|
||||
CloseConnection = true;
|
||||
}
|
||||
|
||||
Challenge = Headers.GetValues ("Proxy-Authenticate");
|
||||
Success = false;
|
||||
} else {
|
||||
Success = StatusCode == 200 && Headers != null;
|
||||
}
|
||||
|
||||
if (Challenge == null && (StatusCode == 401 || StatusCode == 407)) {
|
||||
var response = new HttpWebResponse (ConnectUri, "CONNECT", (HttpStatusCode)StatusCode, Headers);
|
||||
throw new WebException (
|
||||
StatusCode == 407 ? "(407) Proxy Authentication Required" : "(401) Unauthorized",
|
||||
null, WebExceptionStatus.ProtocolError, response);
|
||||
}
|
||||
}
|
||||
|
||||
async Task<(WebHeaderCollection, byte[], int)> ReadHeaders (Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
byte[] retBuffer = null;
|
||||
int status = 200;
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
MemoryStream ms = new MemoryStream ();
|
||||
|
||||
while (true) {
|
||||
cancellationToken.ThrowIfCancellationRequested ();
|
||||
int n = await stream.ReadAsync (buffer, 0, 1024, cancellationToken).ConfigureAwait (false);
|
||||
if (n == 0)
|
||||
throw WebConnection.GetException (WebExceptionStatus.ServerProtocolViolation, null);
|
||||
|
||||
ms.Write (buffer, 0, n);
|
||||
int start = 0;
|
||||
string str = null;
|
||||
bool gotStatus = false;
|
||||
WebHeaderCollection headers = new WebHeaderCollection ();
|
||||
while (WebConnection.ReadLine (ms.GetBuffer (), ref start, (int)ms.Length, ref str)) {
|
||||
if (str == null) {
|
||||
int contentLen;
|
||||
var clengthHeader = headers["Content-Length"];
|
||||
if (string.IsNullOrEmpty (clengthHeader) || !int.TryParse (clengthHeader, out contentLen))
|
||||
contentLen = 0;
|
||||
|
||||
if (ms.Length - start - contentLen > 0) {
|
||||
// we've read more data than the response header and conents,
|
||||
// give back extra data to the caller
|
||||
retBuffer = new byte[ms.Length - start - contentLen];
|
||||
Buffer.BlockCopy (ms.GetBuffer (), start + contentLen, retBuffer, 0, retBuffer.Length);
|
||||
} else {
|
||||
// haven't read in some or all of the contents for the response, do so now
|
||||
FlushContents (stream, contentLen - (int)(ms.Length - start));
|
||||
}
|
||||
|
||||
return (headers, retBuffer, status);
|
||||
}
|
||||
|
||||
if (gotStatus) {
|
||||
headers.Add (str);
|
||||
continue;
|
||||
}
|
||||
|
||||
string[] parts = str.Split (' ');
|
||||
if (parts.Length < 2)
|
||||
throw WebConnection.GetException (WebExceptionStatus.ServerProtocolViolation, null);
|
||||
|
||||
if (String.Compare (parts[0], "HTTP/1.1", true) == 0)
|
||||
ProxyVersion = HttpVersion.Version11;
|
||||
else if (String.Compare (parts[0], "HTTP/1.0", true) == 0)
|
||||
ProxyVersion = HttpVersion.Version10;
|
||||
else
|
||||
throw WebConnection.GetException (WebExceptionStatus.ServerProtocolViolation, null);
|
||||
|
||||
status = (int)UInt32.Parse (parts[1]);
|
||||
if (parts.Length >= 3)
|
||||
StatusDescription = String.Join (" ", parts, 2, parts.Length - 2);
|
||||
|
||||
gotStatus = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FlushContents (Stream stream, int contentLength)
|
||||
{
|
||||
while (contentLength > 0) {
|
||||
byte[] contentBuffer = new byte[contentLength];
|
||||
int bytesRead = stream.Read (contentBuffer, 0, contentLength);
|
||||
if (bytesRead > 0) {
|
||||
contentLength -= bytesRead;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
349
mcs/class/System/System.Net/WebOperation.cs
Normal file
349
mcs/class/System/System.Net/WebOperation.cs
Normal file
@ -0,0 +1,349 @@
|
||||
//
|
||||
// WebOperation.cs
|
||||
//
|
||||
// Author:
|
||||
// Martin Baulig <mabaul@microsoft.com>
|
||||
//
|
||||
// Copyright (c) 2017 Xamarin Inc. (http://www.xamarin.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.
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
class WebOperation
|
||||
{
|
||||
public HttpWebRequest Request {
|
||||
get;
|
||||
}
|
||||
|
||||
public WebConnection Connection {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public ServicePoint ServicePoint {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public BufferOffsetSize WriteBuffer {
|
||||
get;
|
||||
}
|
||||
|
||||
public bool IsNtlmChallenge {
|
||||
get;
|
||||
}
|
||||
|
||||
#if MONO_WEB_DEBUG
|
||||
static int nextID;
|
||||
internal readonly int ID = ++nextID;
|
||||
string ME => $"WO({ID},{Connection?.ID ?? -1})";
|
||||
#else
|
||||
internal readonly int ID;
|
||||
string ME;
|
||||
#endif
|
||||
|
||||
public WebOperation (HttpWebRequest request, BufferOffsetSize writeBuffer, bool isNtlmChallenge, CancellationToken cancellationToken)
|
||||
{
|
||||
Request = request;
|
||||
WriteBuffer = writeBuffer;
|
||||
IsNtlmChallenge = isNtlmChallenge;
|
||||
cts = CancellationTokenSource.CreateLinkedTokenSource (cancellationToken);
|
||||
requestTask = new WebCompletionSource<WebRequestStream> ();
|
||||
requestWrittenTask = new WebCompletionSource<WebRequestStream> ();
|
||||
responseTask = new WebCompletionSource<WebResponseStream> ();
|
||||
finishedTask = new WebCompletionSource<(bool, WebOperation)> ();
|
||||
}
|
||||
|
||||
CancellationTokenSource cts;
|
||||
WebCompletionSource<WebRequestStream> requestTask;
|
||||
WebCompletionSource<WebRequestStream> requestWrittenTask;
|
||||
WebCompletionSource<WebResponseStream> responseTask;
|
||||
WebCompletionSource<(bool, WebOperation)> finishedTask;
|
||||
WebRequestStream writeStream;
|
||||
WebResponseStream responseStream;
|
||||
ExceptionDispatchInfo disposedInfo;
|
||||
ExceptionDispatchInfo closedInfo;
|
||||
WebOperation priorityRequest;
|
||||
int requestSent;
|
||||
int finished;
|
||||
|
||||
public bool Aborted {
|
||||
get {
|
||||
if (disposedInfo != null || Request.Aborted)
|
||||
return true;
|
||||
if (cts != null && cts.IsCancellationRequested)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Closed {
|
||||
get {
|
||||
return Aborted || closedInfo != null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Abort ()
|
||||
{
|
||||
var (exception, disposed) = SetDisposed (ref disposedInfo);
|
||||
if (!disposed)
|
||||
return;
|
||||
cts?.Cancel ();
|
||||
SetCanceled ();
|
||||
Close ();
|
||||
}
|
||||
|
||||
public void Close ()
|
||||
{
|
||||
var (exception, closed) = SetDisposed (ref closedInfo);
|
||||
if (!closed)
|
||||
return;
|
||||
|
||||
var stream = Interlocked.Exchange (ref writeStream, null);
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.Close ();
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
|
||||
void SetCanceled ()
|
||||
{
|
||||
WebConnection.Debug ($"{ME} SET CANCELED");
|
||||
|
||||
var error = new OperationCanceledException ();
|
||||
requestTask.TrySetCanceled (error);
|
||||
requestWrittenTask.TrySetCanceled (error);
|
||||
responseTask.TrySetCanceled (error);
|
||||
Finish (false, error);
|
||||
}
|
||||
|
||||
void SetError (Exception error)
|
||||
{
|
||||
WebConnection.Debug ($"{ME} SET ERROR: error={error.GetType ().Name}");
|
||||
|
||||
requestTask.TrySetException (error);
|
||||
requestWrittenTask.TrySetException (error);
|
||||
responseTask.TrySetException (error);
|
||||
Finish (false, error);
|
||||
}
|
||||
|
||||
(ExceptionDispatchInfo, bool) SetDisposed (ref ExceptionDispatchInfo field)
|
||||
{
|
||||
var wexc = new WebException (SR.GetString (SR.net_webstatus_RequestCanceled), WebExceptionStatus.RequestCanceled);
|
||||
var exception = ExceptionDispatchInfo.Capture (wexc);
|
||||
var old = Interlocked.CompareExchange (ref field, exception, null);
|
||||
return (old ?? exception, old == null);
|
||||
}
|
||||
|
||||
internal ExceptionDispatchInfo CheckDisposed (CancellationToken cancellationToken)
|
||||
{
|
||||
if (Aborted || cancellationToken.IsCancellationRequested)
|
||||
return CheckThrowDisposed (false, ref disposedInfo);
|
||||
return null;
|
||||
}
|
||||
|
||||
internal void ThrowIfDisposed ()
|
||||
{
|
||||
ThrowIfDisposed (CancellationToken.None);
|
||||
}
|
||||
|
||||
internal void ThrowIfDisposed (CancellationToken cancellationToken)
|
||||
{
|
||||
if (Aborted || cancellationToken.IsCancellationRequested)
|
||||
CheckThrowDisposed (true, ref disposedInfo);
|
||||
}
|
||||
|
||||
internal void ThrowIfClosedOrDisposed ()
|
||||
{
|
||||
ThrowIfClosedOrDisposed (CancellationToken.None);
|
||||
}
|
||||
|
||||
internal void ThrowIfClosedOrDisposed (CancellationToken cancellationToken)
|
||||
{
|
||||
if (Closed || cancellationToken.IsCancellationRequested)
|
||||
CheckThrowDisposed (true, ref closedInfo);
|
||||
}
|
||||
|
||||
ExceptionDispatchInfo CheckThrowDisposed (bool throwIt, ref ExceptionDispatchInfo field)
|
||||
{
|
||||
var (exception, disposed) = SetDisposed (ref field);
|
||||
if (disposed)
|
||||
cts?.Cancel ();
|
||||
if (throwIt)
|
||||
exception.Throw ();
|
||||
return exception;
|
||||
}
|
||||
|
||||
internal void RegisterRequest (ServicePoint servicePoint, WebConnection connection)
|
||||
{
|
||||
if (servicePoint == null)
|
||||
throw new ArgumentNullException (nameof (servicePoint));
|
||||
if (connection == null)
|
||||
throw new ArgumentNullException (nameof (connection));
|
||||
|
||||
lock (this) {
|
||||
if (Interlocked.CompareExchange (ref requestSent, 1, 0) != 0)
|
||||
throw new InvalidOperationException ("Invalid nested call.");
|
||||
ServicePoint = servicePoint;
|
||||
Connection = connection;
|
||||
}
|
||||
|
||||
cts.Token.Register (() => {
|
||||
Request.FinishedReading = true;
|
||||
SetDisposed (ref disposedInfo);
|
||||
});
|
||||
}
|
||||
|
||||
public void SetPriorityRequest (WebOperation operation)
|
||||
{
|
||||
lock (this) {
|
||||
if (requestSent != 1 || ServicePoint == null || finished != 0)
|
||||
throw new InvalidOperationException ("Should never happen.");
|
||||
if (Interlocked.CompareExchange (ref priorityRequest, operation, null) != null)
|
||||
throw new InvalidOperationException ("Invalid nested request.");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Stream> GetRequestStream ()
|
||||
{
|
||||
return await requestTask.WaitForCompletion ().ConfigureAwait (false);
|
||||
}
|
||||
|
||||
internal Task<WebRequestStream> GetRequestStreamInternal ()
|
||||
{
|
||||
return requestTask.WaitForCompletion ();
|
||||
}
|
||||
|
||||
public Task WaitUntilRequestWritten ()
|
||||
{
|
||||
return requestWrittenTask.WaitForCompletion ();
|
||||
}
|
||||
|
||||
public WebRequestStream WriteStream {
|
||||
get {
|
||||
ThrowIfDisposed ();
|
||||
return writeStream;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<WebResponseStream> GetResponseStream ()
|
||||
{
|
||||
return responseTask.WaitForCompletion ();
|
||||
}
|
||||
|
||||
internal WebCompletionSource<(bool, WebOperation)> Finished => finishedTask;
|
||||
|
||||
internal async void Run ()
|
||||
{
|
||||
try {
|
||||
WebConnection.Debug ($"{ME} RUN");
|
||||
|
||||
ThrowIfClosedOrDisposed ();
|
||||
|
||||
var requestStream = await Connection.InitConnection (this, cts.Token).ConfigureAwait (false);
|
||||
|
||||
ThrowIfClosedOrDisposed ();
|
||||
|
||||
writeStream = requestStream;
|
||||
|
||||
await requestStream.Initialize (cts.Token).ConfigureAwait (false);
|
||||
|
||||
ThrowIfClosedOrDisposed ();
|
||||
|
||||
requestTask.TrySetCompleted (requestStream);
|
||||
|
||||
var stream = new WebResponseStream (requestStream);
|
||||
responseStream = stream;
|
||||
|
||||
await stream.InitReadAsync (cts.Token).ConfigureAwait (false);
|
||||
|
||||
WebConnection.Debug ($"{ME} RUN COMPLETE");
|
||||
|
||||
responseTask.TrySetCompleted (stream);
|
||||
} catch (OperationCanceledException) {
|
||||
WebConnection.Debug ($"{ME} RUN CANCELED");
|
||||
SetCanceled ();
|
||||
} catch (Exception e) {
|
||||
WebConnection.Debug ($"{ME} RUN ERROR: {e.GetType ().Name}");
|
||||
SetError (e);
|
||||
}
|
||||
}
|
||||
|
||||
internal void CompleteRequestWritten (WebRequestStream stream, Exception error = null)
|
||||
{
|
||||
WebConnection.Debug ($"{ME} COMPLETE REQUEST WRITTEN: {error != null}");
|
||||
|
||||
if (error != null)
|
||||
SetError (error);
|
||||
else
|
||||
requestWrittenTask.TrySetCompleted (stream);
|
||||
}
|
||||
|
||||
internal void Finish (bool ok, Exception error = null)
|
||||
{
|
||||
if (Interlocked.CompareExchange (ref finished, 1, 0) != 0)
|
||||
return;
|
||||
|
||||
#if MONO_WEB_DEBUG
|
||||
var me = $"{ME} FINISH";
|
||||
#else
|
||||
string me = null;
|
||||
#endif
|
||||
WebConnection.Debug ($"{me}: ok={ok} error={error?.GetType ()}");
|
||||
|
||||
WebResponseStream stream;
|
||||
WebOperation next;
|
||||
|
||||
lock (this) {
|
||||
stream = Interlocked.Exchange (ref responseStream, null);
|
||||
next = Interlocked.Exchange (ref priorityRequest, null);
|
||||
Request.FinishedReading = true;
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
if (next != null)
|
||||
next.SetError (error);
|
||||
WebConnection.Debug ($"{me} SET ERROR: {error.GetType ().Name}");
|
||||
finishedTask.TrySetException (error);
|
||||
return;
|
||||
}
|
||||
|
||||
WebConnection.Debug ($"{me}: ok={ok} stream={stream != null} next={next != null}");
|
||||
|
||||
var keepAlive = !Aborted && ok && (stream?.KeepAlive ?? false);
|
||||
if (next != null && next.Aborted) {
|
||||
next = null;
|
||||
keepAlive = false;
|
||||
}
|
||||
|
||||
finishedTask.TrySetCompleted ((keepAlive, next));
|
||||
|
||||
WebConnection.Debug ($"{me} DONE: {keepAlive} next={next?.ID}");
|
||||
}
|
||||
}
|
||||
}
|
449
mcs/class/System/System.Net/WebRequestStream.cs
Normal file
449
mcs/class/System/System.Net/WebRequestStream.cs
Normal file
@ -0,0 +1,449 @@
|
||||
//
|
||||
// WebRequestStream.cs
|
||||
//
|
||||
// Author:
|
||||
// Martin Baulig <mabaul@microsoft.com>
|
||||
//
|
||||
// Copyright (c) 2017 Xamarin Inc. (http://www.xamarin.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.
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
class WebRequestStream : WebConnectionStream
|
||||
{
|
||||
static byte[] crlf = new byte[] { 13, 10 };
|
||||
MemoryStream writeBuffer;
|
||||
bool requestWritten;
|
||||
bool allowBuffering;
|
||||
bool sendChunked;
|
||||
WebCompletionSource pendingWrite;
|
||||
long totalWritten;
|
||||
byte[] headers;
|
||||
bool headersSent;
|
||||
int completeRequestWritten;
|
||||
int chunkTrailerWritten;
|
||||
|
||||
internal readonly string ME;
|
||||
|
||||
public WebRequestStream (WebConnection connection, WebOperation operation,
|
||||
Stream stream, WebConnectionTunnel tunnel)
|
||||
: base (connection, operation, stream)
|
||||
{
|
||||
allowBuffering = operation.Request.InternalAllowBuffering;
|
||||
sendChunked = operation.Request.SendChunked && operation.WriteBuffer == null;
|
||||
if (!sendChunked && allowBuffering && operation.WriteBuffer == null)
|
||||
writeBuffer = new MemoryStream ();
|
||||
|
||||
KeepAlive = Request.KeepAlive;
|
||||
if (tunnel?.ProxyVersion != null && tunnel?.ProxyVersion != HttpVersion.Version11)
|
||||
KeepAlive = false;
|
||||
|
||||
#if MONO_WEB_DEBUG
|
||||
ME = $"WRQ(Cnc={Connection.ID}, Op={Operation.ID})";
|
||||
#endif
|
||||
}
|
||||
|
||||
public bool KeepAlive {
|
||||
get;
|
||||
}
|
||||
|
||||
public override long Length {
|
||||
get {
|
||||
throw new NotSupportedException ();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanRead => false;
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
internal bool SendChunked {
|
||||
get { return sendChunked; }
|
||||
set { sendChunked = value; }
|
||||
}
|
||||
|
||||
internal bool HasWriteBuffer {
|
||||
get {
|
||||
return Operation.WriteBuffer != null || writeBuffer != null;
|
||||
}
|
||||
}
|
||||
|
||||
internal int WriteBufferLength {
|
||||
get {
|
||||
if (Operation.WriteBuffer != null)
|
||||
return Operation.WriteBuffer.Size;
|
||||
if (writeBuffer != null)
|
||||
return (int)writeBuffer.Length;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
internal BufferOffsetSize GetWriteBuffer ()
|
||||
{
|
||||
if (Operation.WriteBuffer != null)
|
||||
return Operation.WriteBuffer;
|
||||
if (writeBuffer == null || writeBuffer.Length == 0)
|
||||
return null;
|
||||
var buffer = writeBuffer.GetBuffer ();
|
||||
return new BufferOffsetSize (buffer, 0, (int)writeBuffer.Length, false);
|
||||
}
|
||||
|
||||
async Task FinishWriting (CancellationToken cancellationToken)
|
||||
{
|
||||
if (Interlocked.CompareExchange (ref completeRequestWritten, 1, 0) != 0)
|
||||
return;
|
||||
|
||||
WebConnection.Debug ($"{ME} FINISH WRITING: {sendChunked}");
|
||||
try {
|
||||
Operation.ThrowIfClosedOrDisposed (cancellationToken);
|
||||
if (sendChunked)
|
||||
await WriteChunkTrailer_inner (cancellationToken).ConfigureAwait (false);
|
||||
} catch (Exception ex) {
|
||||
Operation.CompleteRequestWritten (this, ex);
|
||||
throw;
|
||||
} finally {
|
||||
WebConnection.Debug ($"{ME} FINISH WRITING DONE");
|
||||
}
|
||||
|
||||
Operation.CompleteRequestWritten (this);
|
||||
}
|
||||
|
||||
public override Task WriteAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException (nameof (buffer));
|
||||
|
||||
int length = buffer.Length;
|
||||
if (offset < 0 || length < offset)
|
||||
throw new ArgumentOutOfRangeException (nameof (offset));
|
||||
if (count < 0 || (length - offset) < count)
|
||||
throw new ArgumentOutOfRangeException (nameof (count));
|
||||
|
||||
WebConnection.Debug ($"{ME} WRITE ASYNC: {buffer.Length}/{offset}/{count}");
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return Task.FromCanceled (cancellationToken);
|
||||
|
||||
Operation.ThrowIfClosedOrDisposed (cancellationToken);
|
||||
|
||||
if (Operation.WriteBuffer != null)
|
||||
throw new InvalidOperationException ();
|
||||
|
||||
var completion = new WebCompletionSource ();
|
||||
if (Interlocked.CompareExchange (ref pendingWrite, completion, null) != null)
|
||||
throw new InvalidOperationException (SR.GetString (SR.net_repcall));
|
||||
|
||||
return WriteAsyncInner (buffer, offset, count, completion, cancellationToken);
|
||||
}
|
||||
|
||||
async Task WriteAsyncInner (byte[] buffer, int offset, int size,
|
||||
WebCompletionSource completion,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try {
|
||||
await ProcessWrite (buffer, offset, size, cancellationToken).ConfigureAwait (false);
|
||||
|
||||
WebConnection.Debug ($"{ME} WRITE ASYNC #1: {allowBuffering} {sendChunked} {Request.ContentLength} {totalWritten}");
|
||||
|
||||
if (Request.ContentLength > 0 && totalWritten == Request.ContentLength)
|
||||
await FinishWriting (cancellationToken);
|
||||
|
||||
pendingWrite = null;
|
||||
completion.TrySetCompleted ();
|
||||
} catch (Exception ex) {
|
||||
KillBuffer ();
|
||||
closed = true;
|
||||
|
||||
WebConnection.Debug ($"{ME} WRITE ASYNC EX: {ex.Message}");
|
||||
|
||||
var oldError = Operation.CheckDisposed (cancellationToken);
|
||||
if (oldError != null)
|
||||
ex = oldError.SourceException;
|
||||
else if (ex is SocketException)
|
||||
ex = new IOException ("Error writing request", ex);
|
||||
|
||||
Operation.CompleteRequestWritten (this, ex);
|
||||
|
||||
pendingWrite = null;
|
||||
completion.TrySetException (ex);
|
||||
|
||||
if (oldError != null)
|
||||
oldError.Throw ();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
async Task ProcessWrite (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
|
||||
{
|
||||
Operation.ThrowIfClosedOrDisposed (cancellationToken);
|
||||
|
||||
if (sendChunked) {
|
||||
requestWritten = true;
|
||||
|
||||
string cSize = String.Format ("{0:X}\r\n", size);
|
||||
byte[] head = Encoding.ASCII.GetBytes (cSize);
|
||||
int chunkSize = 2 + size + head.Length;
|
||||
byte[] newBuffer = new byte[chunkSize];
|
||||
Buffer.BlockCopy (head, 0, newBuffer, 0, head.Length);
|
||||
Buffer.BlockCopy (buffer, offset, newBuffer, head.Length, size);
|
||||
Buffer.BlockCopy (crlf, 0, newBuffer, head.Length + size, crlf.Length);
|
||||
|
||||
if (allowBuffering) {
|
||||
if (writeBuffer == null)
|
||||
writeBuffer = new MemoryStream ();
|
||||
writeBuffer.Write (buffer, offset, size);
|
||||
}
|
||||
|
||||
totalWritten += size;
|
||||
|
||||
buffer = newBuffer;
|
||||
offset = 0;
|
||||
size = chunkSize;
|
||||
} else {
|
||||
CheckWriteOverflow (Request.ContentLength, totalWritten, size);
|
||||
|
||||
if (allowBuffering) {
|
||||
if (writeBuffer == null)
|
||||
writeBuffer = new MemoryStream ();
|
||||
writeBuffer.Write (buffer, offset, size);
|
||||
totalWritten += size;
|
||||
|
||||
if (Request.ContentLength <= 0 || totalWritten < Request.ContentLength)
|
||||
return;
|
||||
|
||||
requestWritten = true;
|
||||
buffer = writeBuffer.GetBuffer ();
|
||||
offset = 0;
|
||||
size = (int)totalWritten;
|
||||
} else {
|
||||
totalWritten += size;
|
||||
}
|
||||
}
|
||||
|
||||
await InnerStream.WriteAsync (buffer, offset, size, cancellationToken).ConfigureAwait (false);
|
||||
}
|
||||
|
||||
void CheckWriteOverflow (long contentLength, long totalWritten, long size)
|
||||
{
|
||||
if (contentLength == -1)
|
||||
return;
|
||||
|
||||
long avail = contentLength - totalWritten;
|
||||
if (size > avail) {
|
||||
KillBuffer ();
|
||||
closed = true;
|
||||
var throwMe = new ProtocolViolationException (
|
||||
"The number of bytes to be written is greater than " +
|
||||
"the specified ContentLength.");
|
||||
Operation.CompleteRequestWritten (this, throwMe);
|
||||
throw throwMe;
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task Initialize (CancellationToken cancellationToken)
|
||||
{
|
||||
Operation.ThrowIfClosedOrDisposed (cancellationToken);
|
||||
|
||||
WebConnection.Debug ($"{ME} INIT: {Operation.WriteBuffer != null}");
|
||||
|
||||
if (Operation.WriteBuffer != null) {
|
||||
if (Operation.IsNtlmChallenge)
|
||||
Request.InternalContentLength = 0;
|
||||
else
|
||||
Request.InternalContentLength = Operation.WriteBuffer.Size;
|
||||
}
|
||||
|
||||
await SetHeadersAsync (false, cancellationToken).ConfigureAwait (false);
|
||||
|
||||
Operation.ThrowIfClosedOrDisposed (cancellationToken);
|
||||
|
||||
if (Operation.WriteBuffer != null && !Operation.IsNtlmChallenge) {
|
||||
await WriteRequestAsync (cancellationToken);
|
||||
Close ();
|
||||
}
|
||||
}
|
||||
|
||||
async Task SetHeadersAsync (bool setInternalLength, CancellationToken cancellationToken)
|
||||
{
|
||||
Operation.ThrowIfClosedOrDisposed (cancellationToken);
|
||||
|
||||
if (headersSent)
|
||||
return;
|
||||
|
||||
string method = Request.Method;
|
||||
bool no_writestream = (method == "GET" || method == "CONNECT" || method == "HEAD" ||
|
||||
method == "TRACE");
|
||||
bool webdav = (method == "PROPFIND" || method == "PROPPATCH" || method == "MKCOL" ||
|
||||
method == "COPY" || method == "MOVE" || method == "LOCK" ||
|
||||
method == "UNLOCK");
|
||||
|
||||
if (Operation.IsNtlmChallenge)
|
||||
no_writestream = true;
|
||||
|
||||
if (setInternalLength && !no_writestream && HasWriteBuffer)
|
||||
Request.InternalContentLength = WriteBufferLength;
|
||||
|
||||
bool has_content = !no_writestream && (!HasWriteBuffer || Request.ContentLength > -1);
|
||||
if (!(sendChunked || has_content || no_writestream || webdav))
|
||||
return;
|
||||
|
||||
headersSent = true;
|
||||
headers = Request.GetRequestHeaders ();
|
||||
|
||||
WebConnection.Debug ($"{ME} SET HEADERS: {Request.ContentLength}");
|
||||
|
||||
try {
|
||||
await InnerStream.WriteAsync (headers, 0, headers.Length, cancellationToken).ConfigureAwait (false);
|
||||
var cl = Request.ContentLength;
|
||||
if (!sendChunked && cl == 0)
|
||||
requestWritten = true;
|
||||
} catch (Exception e) {
|
||||
if (e is WebException || e is OperationCanceledException)
|
||||
throw;
|
||||
throw new WebException ("Error writing headers", WebExceptionStatus.SendFailure, WebExceptionInternalStatus.RequestFatal, e);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task WriteRequestAsync (CancellationToken cancellationToken)
|
||||
{
|
||||
Operation.ThrowIfClosedOrDisposed (cancellationToken);
|
||||
|
||||
WebConnection.Debug ($"{ME} WRITE REQUEST: {requestWritten} {sendChunked} {allowBuffering} {HasWriteBuffer}");
|
||||
|
||||
if (requestWritten)
|
||||
return;
|
||||
|
||||
requestWritten = true;
|
||||
if (sendChunked || !HasWriteBuffer)
|
||||
return;
|
||||
|
||||
BufferOffsetSize buffer = GetWriteBuffer ();
|
||||
if (buffer != null && !Operation.IsNtlmChallenge && Request.ContentLength != -1 && Request.ContentLength < buffer.Size) {
|
||||
closed = true;
|
||||
var throwMe = new WebException ("Specified Content-Length is less than the number of bytes to write", null,
|
||||
WebExceptionStatus.ServerProtocolViolation, null);
|
||||
Operation.CompleteRequestWritten (this, throwMe);
|
||||
throw throwMe;
|
||||
}
|
||||
|
||||
await SetHeadersAsync (true, cancellationToken).ConfigureAwait (false);
|
||||
|
||||
WebConnection.Debug ($"{ME} WRITE REQUEST #1: {buffer != null}");
|
||||
|
||||
Operation.ThrowIfClosedOrDisposed (cancellationToken);
|
||||
if (buffer != null && buffer.Size > 0)
|
||||
await InnerStream.WriteAsync (buffer.Buffer, 0, buffer.Size, cancellationToken);
|
||||
|
||||
await FinishWriting (cancellationToken);
|
||||
}
|
||||
|
||||
async Task WriteChunkTrailer_inner (CancellationToken cancellationToken)
|
||||
{
|
||||
if (Interlocked.CompareExchange (ref chunkTrailerWritten, 1, 0) != 0)
|
||||
return;
|
||||
Operation.ThrowIfClosedOrDisposed (cancellationToken);
|
||||
byte[] chunk = Encoding.ASCII.GetBytes ("0\r\n\r\n");
|
||||
await InnerStream.WriteAsync (chunk, 0, chunk.Length, cancellationToken).ConfigureAwait (false);
|
||||
}
|
||||
|
||||
async Task WriteChunkTrailer ()
|
||||
{
|
||||
var cts = new CancellationTokenSource ();
|
||||
try {
|
||||
cts.CancelAfter (WriteTimeout);
|
||||
var timeoutTask = Task.Delay (WriteTimeout, cts.Token);
|
||||
while (true) {
|
||||
var completion = new WebCompletionSource ();
|
||||
var oldCompletion = Interlocked.CompareExchange (ref pendingWrite, completion, null);
|
||||
if (oldCompletion == null)
|
||||
break;
|
||||
var oldWriteTask = oldCompletion.WaitForCompletion ();
|
||||
var ret = await Task.WhenAny (timeoutTask, oldWriteTask).ConfigureAwait (false);
|
||||
if (ret == timeoutTask)
|
||||
throw new WebException ("The operation has timed out.", WebExceptionStatus.Timeout);
|
||||
}
|
||||
|
||||
await WriteChunkTrailer_inner (cts.Token).ConfigureAwait (false);
|
||||
} catch {
|
||||
// Intentionally eating exceptions.
|
||||
} finally {
|
||||
pendingWrite = null;
|
||||
cts.Cancel ();
|
||||
cts.Dispose ();
|
||||
}
|
||||
}
|
||||
|
||||
internal void KillBuffer ()
|
||||
{
|
||||
writeBuffer = null;
|
||||
}
|
||||
|
||||
public override Task<int> ReadAsync (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromException<int> (new NotSupportedException (SR.net_writeonlystream));
|
||||
}
|
||||
|
||||
protected override void Close_internal (ref bool disposed)
|
||||
{
|
||||
WebConnection.Debug ($"{ME} CLOSE: {disposed} {requestWritten} {allowBuffering}");
|
||||
|
||||
if (disposed)
|
||||
return;
|
||||
disposed = true;
|
||||
|
||||
if (sendChunked) {
|
||||
// Don't use FinishWriting() here, we need to block on 'pendingWrite' to ensure that
|
||||
// any pending WriteAsync() has been completed.
|
||||
//
|
||||
// FIXME: I belive .NET simply aborts if you call Close() or Dispose() while writing,
|
||||
// need to check this. 2017/07/17 Martin.
|
||||
WriteChunkTrailer ().Wait ();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!allowBuffering || requestWritten) {
|
||||
Operation.CompleteRequestWritten (this);
|
||||
return;
|
||||
}
|
||||
|
||||
long length = Request.ContentLength;
|
||||
|
||||
if (!sendChunked && !Operation.IsNtlmChallenge && length != -1 && totalWritten != length) {
|
||||
IOException io = new IOException ("Cannot close the stream until all bytes are written");
|
||||
closed = true;
|
||||
disposed = true;
|
||||
var throwMe = new WebException ("Request was cancelled.", WebExceptionStatus.RequestCanceled, WebExceptionInternalStatus.RequestFatal, io);
|
||||
Operation.CompleteRequestWritten (this, throwMe);
|
||||
throw throwMe;
|
||||
}
|
||||
|
||||
// Commented out the next line to fix xamarin bug #1512
|
||||
//WriteRequest ();
|
||||
disposed = true;
|
||||
Operation.CompleteRequestWritten (this);
|
||||
}
|
||||
}
|
||||
}
|
739
mcs/class/System/System.Net/WebResponseStream.cs
Normal file
739
mcs/class/System/System.Net/WebResponseStream.cs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user