Java.Net.ProtocolException:“意外的状态行:<html>”通过 httpclient 进行连续 api 调用

Posted

技术标签:

【中文标题】Java.Net.ProtocolException:“意外的状态行:<html>”通过 httpclient 进行连续 api 调用【英文标题】:Java.Net.ProtocolException: 'Unexpected status line: <html>' on consecutive api call through httpclient 【发布时间】:2021-10-04 12:47:10 【问题描述】:

我有一个视图,其中列出了与当前登录用户相关的预订。一旦用户单击其中一个预订,他就会导航到详细信息视图,该视图通过 httpclient 连接到 api 并获取预订详细信息。代码如下。

                client.BaseAddress = new Uri(this.URL);
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.TokenStr);

                var result = await client.GetAsync($"/api/reservation/getforid?Id=id");
                client.Dispose();
                if (result.StatusCode == System.Net.HttpStatusCode.OK)
                
                    var str = result.Content.ReadAsStringAsync().Result.Replace("\\", " ");
                    var model = JsonConvert.DeserializeObject<ReservationModel>(str);

                    return model;
                


详细信息页面还允许修改所选预订的某些数据,并在成功编辑后关闭视图并重新加载预订数据。

发生的情况是在 client.getAsync() 上引发 Java.Net.ProtocolException。并且在向 api 发出请求时,似乎 httpclient 并没有等待数据,而是得到了一些无法转换为响应对象的错误响应。

总结一下:

    首次请求显示预订详情页面时,HttpClient 连接到 api 没有任何问题。 修改预订并尝试重新加载预订数据后,即使发出请求的代码/数据相同,也会引发异常

编辑:

我重构了我的项目,使其使用共享的 HttpClient 实例,并找到了导致问题的代码行。

   private IMvxCommand submit;
        public IMvxCommand Submit
        
            get
            
                submit = submit ?? new MvxAsyncCommand(async () =>
                
                    var validationResult =  validateFields();
                    if (validationResult)
                    
                        await _personApi.UpdatePerson(Model);
                        _nav.Close(this,true);
                 
                    
                );
                return submit;
            
        



_personApi.UpdatePerson() 方法似乎以某种方式搞砸了。 评论后视图关闭,列出人员的 ReservationDetailsView 成功获取数据。但是,当我取消注释该方法时,导航到 ReservationDetailViewModel 时会引发相同的异常...

您可以在下面看到 UpdatePerson 方法:

  public async Task UpdatePerson(PersonModel model)
        

            var token = GetCurrentToken();

            //client = new HttpClient();
            //   client.BaseAddress = new Uri(this.URL);
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.TokenStr);
            //  client.DefaultRequestHeaders.Add("content-type","application/json");
            var dynamic = new  model.Id, model.Name, model.NumberPhone, model.Pesel, model.Street, model.Email, model.WhoToInfrom, model.PostalCode, model.CityPerson ;
            var content = new StringContent(JsonConvert.SerializeObject(dynamic), Encoding.UTF8, "application/json");

            var result = await client.PostAsync($"/api/person/update", content);


        

编辑 2:

仍然卡在这个问题上,不管我使用 HttpClient 多少次,它都可以正常工作,直到我从 EditPersonViewModel 中的 PersonApi 调用 HttpClient 来更新人员。

任何后续调用实际上都会连接到我的 API(我设置了断点,没有抛出异常),但客户端收到如下响应。它来自哪里?

这是 IIS 记录的错误:

Event code: 3005 
Event message: An unhandled exception has occurred. 
Event time: 30.07.2021 11:14:01 
Event time (UTC): 30.07.2021 09:14:01 
Event ID: 17452691323540639122452734bf1713 
Event sequence: 19 
Event occurrence: 15 
Event detail code: 0 
 
Application information: 
    Application domain: /LM/W3SVC/62/ROOT-4-132720999030559530 
    Trust level: Full 
    Application Virtual Path: / 
    Application Path: D:\www\abc\AbcHoliday\AbcHoliday\ 
    Machine name: SERVER3 
 
Process information: 
    Process ID: 11392 
    Process name: w3wp.exe 
    Account name: IIS APPPOOL\abc.syntio.pl 
 
Exception information: 
    Exception type: TaskCanceledException 
    Exception message: A task was canceled.
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__1.MoveNext()
--- 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.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__5.MoveNext()
--- 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.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__15.MoveNext()
--- 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.Web.Http.HttpServer.<SendAsync>d__24.MoveNext()
--- 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.Web.Http.Owin.HttpMessageHandlerAdapter.<InvokeCore>d__20.MoveNext()
--- 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 Microsoft.Owin.Security.Infrastructure.AuthenticationMiddleware`1.<Invoke>d__5.MoveNext()
--- 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 Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.IntegratedPipelineContextStage.<RunApp>d__5.MoveNext()
--- 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 Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.IntegratedPipelineContext.<DoFinalWork>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.StageAsyncResult.End(IAsyncResult ar)
   at Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.IntegratedPipelineContext.EndFinalWork(IAsyncResult ar)
   at System.Web.HttpApplication.AsyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step)
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

 
 
Request information: 
    Request URL: http://abc.syntio.pl/api/reservation/getforid?Id=6384 
    Request path: /api/reservation/getforid 
    User host address: 10.10.0.1 
    User:  
    Is authenticated: True 
    Authentication Type: JWT 
    Thread account name: IIS APPPOOL\abc.syntio.pl 
 
Thread information: 
    Thread ID: 25 
    Thread account name: IIS APPPOOL\abc.syntio.pl 
    Is impersonating: False 
    Stack trace:    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__1.MoveNext()
--- 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.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__5.MoveNext()
--- 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.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__15.MoveNext()
--- 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.Web.Http.HttpServer.<SendAsync>d__24.MoveNext()
--- 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.Web.Http.Owin.HttpMessageHandlerAdapter.<InvokeCore>d__20.MoveNext()
--- 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 Microsoft.Owin.Security.Infrastructure.AuthenticationMiddleware`1.<Invoke>d__5.MoveNext()
--- 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 Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.IntegratedPipelineContextStage.<RunApp>d__5.MoveNext()
--- 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 Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.IntegratedPipelineContext.<DoFinalWork>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.StageAsyncResult.End(IAsyncResult ar)
   at Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.IntegratedPipelineContext.EndFinalWork(IAsyncResult ar)
   at System.Web.HttpApplication.AsyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step)
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
 

【问题讨论】:

你检查过服务器日志,或者用 Postman 或浏览器测试过吗?存在许多关于“意外状态行”的问题,它们似乎指向服务器上的问题 您是否考虑过将 Submit 声明为 IMvxAsyncCommand 而不是 IMvxCommand ?我不认为这是问题的一部分,但这是一种更好的做法,尤其是因为您已经在 lambda 中使用了 async/await。 【参考方案1】:

您将需要尝试/捕获虚拟机中的 Web API 调用。你不能指望这个电话每次都能成功!您可以在出错时使用缓存的响应。

根据错误描述,听起来您无法正确解析 JSON。您可能需要提供与您的 API 服务器正在使用的设置兼容的自定义 JsonSerializerSettings。如果问题是由于 JSON 序列化/反序列化错误引起的,Newtonsoft 应该发送JsonSerializationException 提供更多信息。

至于 Java 异常,目前 Xamarin.android 默认使用 AndroidClientHandler。似乎存在一些错误,因为您可以在 .NET Standard/PCL 项目中捕获 Java 异常(从 Java.Lang.* 命名空间抛出的异常)。这是一个糟糕的情况,因为 .NET Standard/PCL 项目不了解 Android。

我建议在使用 MvvmCross 的现代 Xamarin.Android 应用程序中使用以下方法:

    在整个应用程序中使用单个 HttpClient 实例。通常最好的方法是使用IHttpClientFactory,但 Xamarin 还没有很好的支持。实例化 HttpClient 的好地方是在您的 MvxAndroidSetup 内。
public class Setup : MvxAndroidSetup<App>

  protected override void InitializeFirstChance()
  
    base.InitializeFirstChance();
    var httpClient = new HttpClient(new BasicAuthenticationDelegatingHandler(new SafeAndroidClientHandler()));

    // TODO: Setup httpClient.DefaultRequestHeaders, etc.

    Mvx.IoCProvider.RegisterSingleton(httpClient);
  

    使用SafeAndroidClientHandler 捕获 HttpClient 抛出的 Java.Lang.* 异常,然后重新抛出,然后包装在一个 .NET 友好的异常中。这是我的实现。您可以为 GetAsync 方法覆盖做类似的事情。
public class SafeAndroidClientHandler : AndroidClientHandler

    public SafeAndroidClientHandler()
    
        AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate;
    

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    
        try
        
            return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
        
        catch (Java.Lang.Exception ex)
        
            throw new HttpRequestException(ex.Message, ex);
        
    

    现在您可以简单地将HttpClient 注入到 ViewModel、Service 等中,这要归功于 MvvmCross IoC。如果抛出 Java.Lang.* 异常,它现在会在 ViewModel 中被捕获为 HttpRequestException

【讨论】:

【参考方案2】:

HTTPClient Class 说:

// HttpClient 旨在为每个应用程序实例化一次,而不是每次使用。见备注。

static readonly HttpClient client = new HttpClient();

鉴于:

请勿拨打client.Dispose; 不要在每次调用时都创建 HTTPClient - 创建一次。

【讨论】:

感谢您的指出,我不知道这一点。知道我应该在哪里存储这样的 HttpClient 实例吗?还是任何普通的包装类都可以? @KamilBrucki - 没关系。在一个简单的应用程序中,我在我的自定义应用程序(或应用程序)子类或独立静态类中收集静态数据。在一个更大的项目中,我会创建一个文件夹,其中包含与 HTTP 相关或与服务器通信相关的所有类。同一个文件夹中的静态类会很好。 您正在使用 MvvmCross,因此将 HttpClient 作为 Android 项目中 MvxAndroidSetup.InitializeFirstChance() 方法中的单例存储在 IoC 容器中。

以上是关于Java.Net.ProtocolException:“意外的状态行:<html>”通过 httpclient 进行连续 api 调用的主要内容,如果未能解决你的问题,请参考以下文章