Spring Boot接口报错时,Xamarin HttpClient的ReasonPhrase始终为空的问题
我的REST API基于Spring Boot开发,客户端采用Xamarin Forms,使用HttpClient与服务端进行数据交互。目前遇到问题:即便服务端返回错误,HttpClient中的ReasonPhrase始终为空。想请教REST API返回错误时需遵循何种特定格式,才能填充该字段?我当前返回的错误为标准JSON结构(示例如下),期望ReasonPhrase能获取到其中的message内容。
示例错误返回格式:
{ "timestamp": "2022-09-11T17:58:02.811+00:00", "status": 404, "error": "Not Found", "message": "No message available" }
附上相关代码:
Spring Boot 相关代码
全局异常处理器
@ControllerAdvice public class AppExceptionsHandler extends ResponseEntityExceptionHandler { // 处理自定义异常 @ExceptionHandler(value = { ApplicationException.class }) public ResponseEntity<Object> handleUserServiceException(ApplicationException ex, WebRequest request) { ErrorMessage errorMessage = new ErrorMessage(new Date(), HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage()); return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR); } // 处理未知异常 @ExceptionHandler(value = { Exception.class }) public ResponseEntity<Object> handleOtherExceptions(Exception ex, WebRequest request) { ErrorMessage errorMessage = new ErrorMessage(new Date(), ex.getMessage()); return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR); } }
自定义异常类
public class ApplicationException extends RuntimeException{ private static final long serialVersionUID = 7289264700624277238L; public ApplicationException(String message) { super(message); } }
错误消息实体类
public class ErrorMessage { private Date timestamp; private HttpStatus status; private String message; public ErrorMessage() {} public ErrorMessage(Date timestamp, HttpStatus status, String message) { this(); this.timestamp = timestamp; this.message = message; this.status = status; } // 省略getter和setter }
抛出异常代码
// 业务代码中抛出异常 throw new ApplicationException("Wrong user");
Xamarin HttpClient 请求代码
internal class HttpMethods { private HttpClient _httpClient; public HttpMethods() { _httpClient = new HttpClient(); _httpClient.Timeout = TimeSpan.FromSeconds(15); _httpClient.MaxResponseContentBufferSize = 256000; _httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); } public async Task<(TA, ErrorDto)> HttpPost<TA, TB>(string url, string token, TB data) { var result = default(TA); var error = new ErrorDto(); HttpResponseMessage response = new(); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); HttpContent content; content = FileContent(data as string); response = await _httpClient.PostAsync(new Uri(url), content); if (response.IsSuccessStatusCode) { var jsonDataResponse = await response.Content.ReadAsStringAsync(); result = JsonConvert.DeserializeObject<TA>(jsonDataResponse); } else { error.ReasonPhrase = (int)response.StatusCode; error.StatusCode = (string)response.ReasonPhrase; } return (result, error); } }
核心认知纠正
首先要明确:HTTP的ReasonPhrase是响应状态行的一部分,对应标准状态码的默认描述(比如500对应"Internal Server Error"),并非用来承载业务级错误信息。你期望用ReasonPhrase获取响应体JSON中的message是对该字段的误用,REST API的错误信息规范做法是放在响应体中,而非状态行。
另外,你的Xamarin代码存在字段赋值错误:将状态码(int)赋值给ReasonPhrase,将ReasonPhrase(string)赋值给StatusCode,这会导致类型错误,也是你觉得ReasonPhrase为空的原因之一,需先纠正这个错误。
方案一:正确读取响应体中的错误信息(推荐)
这是REST API的标准做法,业务错误信息放在响应体JSON中,客户端解析该JSON获取错误详情。
1. 调整Xamarin代码
修改HttpPost方法的错误分支,读取响应体并解析成对应错误模型:
else { // 读取响应体的错误JSON var errorJson = await response.Content.ReadAsStringAsync(); // 解析为ErrorDto(需确保ErrorDto结构与后端ErrorMessage匹配) var errorDetails = JsonConvert.DeserializeObject<ErrorDto>(errorJson); // 正确赋值:状态码从响应获取,错误消息从解析后的JSON获取 error.StatusCode = (int)response.StatusCode; error.ReasonPhrase = errorDetails.Message; }
2. 确保ErrorDto结构匹配
定义与后端ErrorMessage对应的客户端模型:
public class ErrorDto { public DateTime Timestamp { get; set; } public int Status { get; set; } public string Message { get; set; } // 以下是你自定义的错误字段,用于返回给上层 public int StatusCode { get; set; } public string ReasonPhrase { get; set; } }
方案二:强制设置ReasonPhrase(不推荐)
如果一定要让ReasonPhrase承载自定义错误消息,需要在Spring Boot后端显式设置响应状态行的描述。注意:这种做法不符合REST规范,且部分HTTP客户端对ReasonPhrase的长度有限制,仅作为特殊场景的备选方案。
修改Spring Boot全局异常处理器
放弃返回ResponseEntity,直接操作HttpServletResponse设置状态码和ReasonPhrase,同时写入错误JSON响应体:
@ExceptionHandler(value = { ApplicationException.class }) public void handleUserServiceException(ApplicationException ex, WebRequest request, HttpServletResponse response) throws IOException { ErrorMessage errorMessage = new ErrorMessage(new Date(), HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage()); // 设置状态码和自定义ReasonPhrase response.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage()); // 设置响应类型为JSON并写入错误内容 response.setContentType("application/json;charset=UTF-8"); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.writeValue(response.getWriter(), errorMessage); }
此时客户端的response.ReasonPhrase会拿到ex.getMessage()的内容(比如"Wrong user"),同时响应体仍保留完整的错误JSON结构。但请谨慎使用这种方式,避免破坏REST API的规范约定。
内容的提问来源于stack exchange,提问作者JBD

