在 IIS 上发布包含 jpg 和 json 的多部分请求会导致内部服务器错误和 win32 状态 64

Posted

技术标签:

【中文标题】在 IIS 上发布包含 jpg 和 json 的多部分请求会导致内部服务器错误和 win32 状态 64【英文标题】:Posting multipart request containing jpg and json causes interal server error and win32 status 64 on IIS 【发布时间】:2020-12-26 19:38:07 【问题描述】:

我的 winforms 应用程序向 asp.net Web api 服务发送 PUT/POST 请求。大多数情况下,它将 json 对象作为请求的内容发送,并且这些请求运行良好。有时,当它需要将 jpg 与 json 对象一起发送时,它会创建 multiPart 请求,其中 jpg 是内容,json 在 url 中传递,如下所示:

example.com/EditPart?id=193&PartJson=<serialized json object>

这里是发送请求的方法的完整定义:

public async void Edit(string attachmentPath)
        

            using (var client = new HttpClient())
            
                var serializedProduct = JsonConvert.SerializeObject(this, new JsonSerializerSettings  DateFormatString = "yyyy-MM-ddTHH:mm:ss.fff" );
                string url = Secrets.ApiAddress + $"Edittypeof(T).Name?token=" + Secrets.TenantToken + $"&id=this.Id&UserId=RuntimeSettings.UserId" + $"&typeof(T).NameJson=serializedProduct";
                MultipartFormDataContent content = new MultipartFormDataContent();
                try
                
                    using (var fileStream = System.IO.File.OpenRead(attachmentPath))
                    
                        var fileInfo = new FileInfo(attachmentPath);
                        StreamContent fcontent = new StreamContent(fileStream);
                        fcontent.Headers.Add("Content-Type", "application/octet-stream");
                        fcontent.Headers.Add("Content-Disposition", "form-data; name=\"file\"; filename=\"" + fileInfo.Name + "\"");
                        content.Add(fcontent, "file", fileInfo.Name);
                        System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
                        var result = await client.PutAsync(url, content);//<--stops here
                        if (result.IsSuccessStatusCode)
                        
                            MessageBox.Show("Edycja zakończona powodzeniem!");
                        
                        else
                        
                            MessageBox.Show("Serwer zwrócił błąd przy próbie edycji. Wiadomość: " + result.ReasonPhrase);
                        
                    
                
                catch (Exception ex)
                
                    MessageBox.Show("Problem z wysyłką żądania do serwera. Wiadomość: " + ex.Message + ". " + ex.InnerException.Message, "Błąd żądania", MessageBoxButtons.OK, MessageBoxIcon.Error);
                
            
        

它转到await client.PutAsync(url, content);,然后直接进入异常状态:发送请求时发生错误。底层连接已关闭。接收时出现意外错误。

当我检查 IIS 日志时,我看到请求正确到达服务器,但以状态 500 和 win32 状态 64 结束。我什至将使用 NLog 的日志记录到 EditPart 方法,但它从不触发。看起来像该方法根本没有被调用,但当然从 IIS 日志中我知道它是。

这是关于 asp.net web api 的完整 EditPart 定义:

[HttpPut]
        [Route("EditPart")]
        [ResponseType(typeof(void))]

        public HttpResponseMessage EditPart(string token, int id, int UserId, string PartJson)
        
            try
            
                javascriptSerializer jss = new JavaScriptSerializer();
                JDE_Parts item = jss.Deserialize<JDE_Parts>(PartJson);

                try
                
                    var items = db.JDE_Parts.Where(u => u.PartId == id);
                    if (items.Any())
                    
                        Logger.Info("EditPart: Znalazłem odpowiednią część. Przystępuję do edycji Id=id, UserId=UserId", id, UserId);
                        JDE_Parts orgItem = items.FirstOrDefault();

                        //handle image

                        var httpRequest = HttpContext.Current.Request;
                        if (httpRequest.ContentLength > 0)
                        
                            //there's a new content
                            if (httpRequest.ContentLength > Static.RuntimeSettings.MaxFileContentLength)
                            
                                return Request.CreateResponse(HttpStatusCode.BadRequest, $"item.Name przekracza dopuszczalną wielość pliku (Static.RuntimeSettings.MaxFileContentLength MB) i został odrzucony");
                            

                            var postedFile = httpRequest.Files[0];
                            string filePath = "";
                            if (postedFile != null && postedFile.ContentLength > 0)
                            
                                Logger.Info("EditPart: Znaleziono nowe pliki. Przystępuję do zapisu na dysku. Id=id, UserId=UserId", id, UserId);
                                var ext = postedFile.FileName.Substring(postedFile.FileName.LastIndexOf('.'));

                                filePath = $"Static.RuntimeSettings.Path2Filesitem.Token + ext.ToLower()";

                                string oFileName = db.JDE_Parts.Where(p => p.PartId == id).FirstOrDefault().Image;
                                if (!string.IsNullOrEmpty(oFileName))
                                
                                    // There was a file, must delete it first
                                    Logger.Info("EditPart: Istnieją poprzednie pliki pod tą nazwą. Przystępuję do usuwania. Id=id, UserId=UserId", id, UserId);
                                    System.IO.File.Delete(Path.Combine(RuntimeSettings.Path2Files, oFileName));
                                    System.IO.File.Delete(Path.Combine(RuntimeSettings.Path2Thumbs, oFileName));
                                
                                postedFile.SaveAs(filePath);
                                Logger.Info("EditPart: Zapisano pliki. Przystępuję do utworzenia miniatury.. Id=id, UserId=UserId", id, UserId);
                                Static.Utilities.ProduceThumbnail(filePath);
                                item.Image = item.Token + ext.ToLower();
                            

                        

                        try
                        
                            Logger.Info("EditPart: Przystępuję do zapisu zmian w bazie danych. Id=id, UserId=UserId", id, UserId);
                            db.Entry(orgItem).CurrentValues.SetValues(item);
                            db.Entry(orgItem).State = EntityState.Modified;
                            db.SaveChanges();
                            Logger.Info("EditPart: Zapisano zmiany w bazie. Id=id, UserId=UserId", id, UserId);
                        
                        catch (Exception ex)
                        
                            Logger.Error("Błąd w EditPart. Id=id, UserId=UserId. Szczegóły: Message, nowa wartość: item", id, UserId, ex.ToString(), item);
                            return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
                        
                    
                
                catch (Exception ex)
                

                    Logger.Error("Błąd w EditPart. Id=id, UserId=UserId. Szczegóły: Message, nowa wartość: item", id, UserId, ex.ToString(), item);
                    return Request.CreateResponse(HttpStatusCode.NoContent);
                
            
            catch (Exception ex)
            
                Logger.Error("Błąd w EditPart. Id=id, UserId=UserId. Szczegóły: Message", id, UserId, ex.ToString());
                return Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message);
            

            return Request.CreateResponse(HttpStatusCode.NoContent);
        

奇怪的是,这一切都运行了好几个月,直到前一段时间才停止。另外,当我在我的机器上调试 asp.net 应用程序时,请求运行没有任何问题。我还能做什么追踪这个问题?

【问题讨论】:

HTTP 500 错误是一般错误,表明服务器出现问题。 windows 错误 64 表示网络名称不再可用。我怀疑该错误是由于在请求开始时使用 TCP 发生的 TLS 身份验证造成的。五年前,由于安全漏洞,业界决定消除 TLS 1.0/1.1。今年 6 月,微软推送了一项安全更新,禁用 TLS 1.0/1.1 服务器并强制客户端使用 TLS 1.2 或更高版本。您的请求可能仍在使用默认的 TLS 版本。 尝试:SecurityProtocolType.Tls12。请参阅:inthetechpit.com/2018/12/15/… 感谢 jdweng,但事实并非如此。我已经放置了 System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;在发出请求之前,但它并没有改变任何事情。这是有道理的,因为这个方法的另一个版本向同一个 API 发送请求但没有文件,并且它一直在正常工作 可能是证书不支持 TLS 1.2 的加密模式/密钥。您可以使用诸如wireshark 或fiddler 之类的嗅探器来查看TSL 是否完成。 我使用了 Fiddler 但它只说 504 ReadResponse() failed: 服务器没有为此请求返回完整的响应。服务器返回 0 个字节。我想我会在表单字段中更改包含文件和 json 的多部分的服务器方法并测试它是如何进行的 【参考方案1】:

当您调试asp.net api应用程序时,它可以毫无问题地运行。这说明api应用没有问题。

但是IIS日志中的status是500,大于500的error一般是服务器造成的,客户端正常。这和上面那个api没问题的结论相矛盾。

由于我不知道您使用的 .net 版本,我不确定是使用 TLS 1.0、1.1 还是 1.2。不同版本的 .net 针对不同的 TLS,最新的 4.6 目前支持 1.2。所以最安全有效的方法就是设置所有的TLS和SSL。

System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12| SecurityProtocolType.Ssl3;

【讨论】:

它是带有 IIS 10 和 .Net 框架 4.7.03062 的 Windows Server 2016。我认为它与 TLS/SSL 无关,同一个 Asp.net web api 服务上的所有其他方法都可以正常工作,问题只涉及附加文件的请求。我尝试在提出请求之前输入System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12| SecurityProtocolType.Ssl3;,但它并没有解决问题 你检查失败请求跟踪了吗?可能会显示更多关于应用程序的过程。【参考方案2】:

原来问题出在客户端应用程序中我的 Edit 方法中的单行。将fcontent.Headers.Add("Content-Type", "application/octet-stream") 更改为fcontent.Headers.ContentType = new MediaTypeHeaderValue(MimeMapping.GetMimeMapping(fileInfo.Name)) 后,它可以完美运行。换句话说,我的请求甚至没有发送到服务器。然而,令人费解的是,同样的代码已经运行了几个月然后停止了......

public async void Edit(string attachmentPath)
    

        using (var client = new HttpClient())
        
            var serializedProduct = JsonConvert.SerializeObject(this, new JsonSerializerSettings  DateFormatString = "yyyy-MM-ddTHH:mm:ss.fff" );
            string url = Secrets.ApiAddress + $"Edittypeof(T).Name?token=" + Secrets.TenantToken + $"&id=this.Id&UserId=RuntimeSettings.UserId" + $"&typeof(T).NameJson=serializedProduct";
            MultipartFormDataContent content = new MultipartFormDataContent();
            try
            
                using (var fileStream = System.IO.File.OpenRead(attachmentPath))
                
                    var fileInfo = new FileInfo(attachmentPath);
                    StreamContent fcontent = new StreamContent(fileStream);
                    fcontent.Headers.ContentType = new MediaTypeHeaderValue(MimeMapping.GetMimeMapping(fileInfo.Name)); //fcontent.Headers.Add("Content-Type", "application/octet-stream");
                    fcontent.Headers.Add("Content-Disposition", "form-data; name=\"file\"; filename=\"" + fileInfo.Name + "\"");
                    content.Add(fcontent, "file", fileInfo.Name);
                    System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
                    var result = await client.PutAsync(url, content);//<--stops here
                    if (result.IsSuccessStatusCode)
                    
                        MessageBox.Show("Edycja zakończona powodzeniem!");
                    
                    else
                    
                        MessageBox.Show("Serwer zwrócił błąd przy próbie edycji. Wiadomość: " + result.ReasonPhrase);
                    
                
            
            catch (Exception ex)
            
                MessageBox.Show("Problem z wysyłką żądania do serwera. Wiadomość: " + ex.Message + ". " + ex.InnerException.Message, "Błąd żądania", MessageBoxButtons.OK, MessageBoxIcon.Error);
            
        
    

【讨论】:

以上是关于在 IIS 上发布包含 jpg 和 json 的多部分请求会导致内部服务器错误和 win32 状态 64的主要内容,如果未能解决你的问题,请参考以下文章

使用 .NET 和 IIS 的多租户 SaaS

Gatsby:在页面上组合两个 graphql 源(.json 和 .jpg 源)

在 IIS8 中使用 Gzip 进行 Json HTTP 压缩

IIS7和IIS7.5解析漏洞

IIS加载JSON文件 错误 404

使用 Retrofit 上传 JSON 格式的多部分图像数据?