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 调用的主要内容,如果未能解决你的问题,请参考以下文章