C# HttpClient PUT

15

出于某种原因,我之前可以正常工作的下面代码现在会导致异常:

public static async Task<string> HttpPut(string inUrl, string inFilePath)
    {
        using (var handler = new HttpClientHandler
        {
            AllowAutoRedirect = false
        })
        {
            using (var client = new HttpClient(handler))
            {
                //var content = new StreamContent(new FileStream(inFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true));
                using (var content = new StreamContent(new FileStream(inFilePath, FileMode.Open)))
                {
                    content.Headers.Remove("Content-Type");
                    content.Headers.Add("Content-Type", "application/octet-stream");

                    using (var req = new HttpRequestMessage(HttpMethod.Put, inUrl))
                    {
                        string authInfo = String.Format("{0}:{1}", Program.Config.MediaStorageList.Find(o => o.Name == "Viz Media Engine Test").UserName, Program.Config.MediaStorageList.Find(o => o.Name == "Viz Media Engine Test").Password);
                        authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
                        req.Headers.Add("Authorization", "Basic " + authInfo);
                        req.Headers.Remove("Expect");
                        req.Headers.Add("Expect", "");
                        //req.Headers.TransferEncodingChunked = true;

                        req.Content = content;

                        // Ignore Certificate validation failures (aka untrusted certificate + certificate chains)
                        ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);

                        using (HttpResponseMessage resp = await client.SendAsync(req))
                        {
                            //This part is specific to the setup on an Expo we're at...
                            if (resp.StatusCode == HttpStatusCode.Redirect || resp.StatusCode == HttpStatusCode.TemporaryRedirect)
                            {
                                string redirectUrl = resp.Headers.Location.ToString();

                                if (redirectUrl.Contains("vme-store"))
                                {
                                    redirectUrl = redirectUrl.Replace("vme-store", "10.230.0.11");
                                }
                                return await HttpPut(redirectUrl, inFilePath);
                            }

                            resp.EnsureSuccessStatusCode();

                            return await resp.Content.ReadAsStringAsync();
                        }
                    }
                }
            }
        }
    }

我得到的异常信息是:

System.NotSupportedException was unhandled
HResult=-2146233067
Message=The stream does not support concurrent IO read or write operations.
Source=System
StackTrace:
     at System.Net.ConnectStream.InternalWrite(Boolean async, Byte[] buffer, Int32 offset, Int32 size, AsyncCallback callback, Object state)
   at System.Net.ConnectStream.BeginWrite(Byte[] buffer, Int32 offset, Int32 size, AsyncCallback callback, Object state)
   at System.Net.Http.StreamToStreamCopy.BufferReadCallback(IAsyncResult ar)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at VizWolfInnerServer.Tools.HttpConnector.<HttpPut>d__39.MoveNext() in c:\Users\christer\Documents\Visual Studio 2012\Projects\VizWolfNew\VizWolfInnerServer\Tools\HttpConnector.cs:line 202
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at VizWolfInnerServer.Tools.VizAPIConnector.<VmeUploadMedia>d__0.MoveNext() in c:\Users\christer\Documents\Visual Studio 2012\Projects\VizWolfNew\VizWolfInnerServer\Tools\VizAPIConnector.cs:line 187
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<ThrowAsync>b__1(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
InnerException: 

我很难找到有关HttpClient的适当文档和示例,我正在努力弄清楚为什么这突然不起作用(使用StringContent而不是StreamContent的完全类似方法可以完美地工作)...

最初它从自己的线程调用,然后像这样:

public static async void VmeUploadMedia(string inUploadLink, string inFilePath)
{
    string result = await HttpConnector.HttpPut(inUploadLink, inFilePath);
}

有人发现了什么明显的问题吗?

谢谢

更新

结果发现让expo的人将他们的存储名称与其IP进行映射,以便我可以回到原来的代码是最好的解决方法。我遇到的问题与AllowAutoRedirect = false有关。即使没有实际重定向,HttpResponseMessage resp = await client.SendAsync(req)也会引发异常。我有点迷失为什么会发生这种情况,但使用此代码现在一切都正常:

public static async Task<string> HttpPut(string inUrl, string inFilePath)
    {
        using (var client = new HttpClient())
        {
            using (var content = new StreamContent(File.OpenRead(inFilePath)))
            {
                content.Headers.Remove("Content-Type");
                content.Headers.Add("Content-Type", "application/octet-stream");

                using (var req = new HttpRequestMessage(HttpMethod.Put, inUrl))
                {
                    string authInfo = String.Format("{0}:{1}", Program.Config.MediaStorageList.Find(o => o.Name == "Viz Media Engine Test").UserName, Program.Config.MediaStorageList.Find(o => o.Name == "Viz Media Engine Test").Password);
                    authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
                    req.Headers.Add("Authorization", "Basic " + authInfo);
                    req.Headers.Remove("Expect");
                    req.Headers.Add("Expect", "");

                    req.Content = content;

                    // Ignore Certificate validation failures (aka untrusted certificate + certificate chains)
                    ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);

                    using (HttpResponseMessage resp = await client.SendAsync(req))
                    {
                        resp.EnsureSuccessStatusCode();

                        return await resp.Content.ReadAsStringAsync();
                    }
                }
            }
        }
    }

感谢那些一直在努力帮忙的人


您可以将您的答案作为实际答案添加到网站上,并选择其作为正确答案。目前看来,这似乎是一个未回答的问题。 - dmportella
好的,现在就去做。 - CeeRo
你现在不能只使用 PutAsJsonAsync<> 吗? - niico
3个回答

9

事实证明,让expo-guys将其存储名称与其IP进行映射,以便我可以回到原始代码是最好的解决方案。我遇到的问题与AllowAutoRedirect = false有关。即使没有真正发生重定向,也会在HttpResponseMessage resp = await client.SendAsync(req)上发生异常。我对为什么会发生这种情况感到有些困惑,但使用此代码现在一切都正常了:

public static async Task<string> HttpPut(string inUrl, string inFilePath)
    {
        using (var client = new HttpClient())
        {
            using (var content = new StreamContent(File.OpenRead(inFilePath)))
            {
                content.Headers.Remove("Content-Type");
                content.Headers.Add("Content-Type", "application/octet-stream");

                using (var req = new HttpRequestMessage(HttpMethod.Put, inUrl))
                {
                    string authInfo = String.Format("{0}:{1}", Program.Config.MediaStorageList.Find(o => o.Name == "Viz Media Engine Test").UserName, Program.Config.MediaStorageList.Find(o => o.Name == "Viz Media Engine Test").Password);
                    authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
                    req.Headers.Add("Authorization", "Basic " + authInfo);
                    req.Headers.Remove("Expect");
                    req.Headers.Add("Expect", "");

                    req.Content = content;

                    // Ignore Certificate validation failures (aka untrusted certificate + certificate chains)
                    ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);

                    using (HttpResponseMessage resp = await client.SendAsync(req))
                    {
                        resp.EnsureSuccessStatusCode();

                        return await resp.Content.ReadAsStringAsync();
                    }
                }
            }
        }
    }

感谢那些试图提供帮助的人


1
using (HttpResponseMessage resp = await client.SendAsync(req))

看起来这一行将为该执行创建一个新的上下文,因此是一个新的线程上下文,也许你不能这样做,因为你将共享FileStream的锁。


有没有绕过获取响应的方法?如果你在考虑使用部分,那么我实际上是在开始收到异常后才添加的,以确保我处理了所有内容。 - CeeRo
如果你仔细想一下,你现在使用的这段代码基本上是没有用的,因为你使用了一个异步函数,但是你仍然阻塞了执行。如果你使用的是回调函数,那我就能理解了,但事实并非如此。 - MeTitus
使用(HttpResponseMessage resp = await client.SendAsync(req))。看起来这行代码将为该执行创建一个新的上下文,因此是一个新的线程上下文,也许您不能这样做。我可以使用类似于http://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext.aspx吗?或者当涉及到线程和异步时,我的想法完全错误了吗? - CeeRo
你试过用我在“update”中提供的代码替换这个代码,以确保问题不是从这里引起的吗? - MeTitus
实际上,我提供的代码来自另一个类,所以忘记它吧。我创建了一个模拟网站来测试您的代码,并能够得到相同的异常。创建FileStream时,默认的FileAccess是ReadWrite,因此您需要使用这个构造函数:FileStream(inFilePath,FileMode.Open,FileAccess.ReadWrite),这解决了问题。 - MeTitus
显示剩余2条评论

0

看起来你正在再次调用 HttpPut(),但是你仍然打开了 FileStream。在从内部递归调用 HttpPut() 之前,请尝试处理掉 FileStream

//This part is specific to the setup on an Expo we're at...
                            if (resp.StatusCode == HttpStatusCode.Redirect || resp.StatusCode == HttpStatusCode.TemporaryRedirect)
                            {
                                string redirectUrl = resp.Headers.Location.ToString();

                                if (redirectUrl.Contains("vme-store"))
                                {

                                    redirectUrl = redirectUrl.Replace("vme-store", "10.230.0.11");
                                }
                                content.Dispose();
                                return await HttpPut(redirectUrl, inFilePath);
                        }

此外,您可能还想处理任何其他IDisposable对象,例如http响应,以确保在进一步深入堆栈跟踪之前释放所有资源。这是递归的一个问题,因为当您使用“Using”语句时,您没有离开其范围,因此它们无法完成其工作。

当然在提问之前我应该尝试过,但现在我尝试了在递归调用之前添加content.Dispose();,但是没有帮助 :/ 另外,在我们的本地测试环境中运行代码时,我从未触发递归调用。 - CeeRo
流应该是打开的以获取请求,同时它不是一个FileStream。 - MeTitus
你能验证一下是否在没有递归的情况下出现了问题吗?移除对 HttpPut() 的内部调用,看看是否可以重现该问题。如果不能,那至少可以提供一个很大的线索,指出问题所在。@Marco 看看这行代码 using (var content = new StreamContent(new FileStream(inFilePath, FileMode.Open))) - Despertar
1
太好了!不要将解决方案添加到您的问题中,而是将其作为回答放在自己的问题下,然后将其标记为接受的答案。这将有助于未来遇到类似问题的人更快地找到它。 - Despertar
这并不是我所问的问题的答案,我仍然不知道为什么那样做不起作用 :p - CeeRo
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接