调用springboot服务的error接口http响应码返回500的问题

背景:

一天早上睡觉醒来,翻了翻手机,发现7点时一个C端服务出现了短暂的报警,通过日志发现,大量的未知请求,有的返回了404,而/error请求http响应码返回了500,很疑惑,按道理服务里没有这个接口应该返回404啊,这个接口有什么特殊之处?于是就调研了一下。

通过排查:

原来springboot中有个BasicErrorController类,这个类有个默认的/error接口,
在这里插入图片描述
当我们调用没有定义的接口时,springboot会自动调用error接口将一个自定义的页面返回给用户,状态码是404
在这里插入图片描述
而我们直接调用这个error接口时,由于我们的服务是有默认的error接口的,所以不是404,而由于我们调用时没有传合适的参数,springboot执行逻辑时报错,返回了500状态码。注意:此时我们自定义的ExceptionHandlerAdvice全局异常捕获处理器是捕获不到异常的,因为springboot没有抛出异常,只是返回了500状态码,于是就出现了上边的问题。如何解决呢?如何在这种情况下不返回5xx呢?

解决方案一:

按照BasicErrorController类写一个类似的ServerErrorController,接口名改为/server/error,覆盖BasicErrorController的/error接口,如下:

import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.Map;
/**
 * 自定义的异常处理控制器,用于覆盖默认的BasicErrorController;
 **/
@Controller
@RequestMapping("/server/error")
public class ServerErrorController extends AbstractErrorController {

    private final ErrorProperties errorProperties;

    public ServerErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
        super(errorAttributes);
        this.errorProperties = serverProperties.getError();
    }

    @RequestMapping(
            produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
        //如果>=500返回400
        if (status.value() >= HttpStatus.INTERNAL_SERVER_ERROR.value()) {
            status = HttpStatus.BAD_REQUEST;
        }
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = this.getStatus(request);
        //如果>=500返回400
        if (status.value() >= HttpStatus.INTERNAL_SERVER_ERROR.value()) {
            status = HttpStatus.BAD_REQUEST;
        }
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity(status);
        } else {
            Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
            return new ResponseEntity(body, status);
        }
    }

    @ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
    public ResponseEntity<String> mediaTypeNotAcceptable(HttpServletRequest request) {
        HttpStatus status = this.getStatus(request);
        return ResponseEntity.status(status).build();
    }

    protected ErrorAttributeOptions getErrorAttributeOptions(HttpServletRequest request, MediaType mediaType) {
        ErrorAttributeOptions options = ErrorAttributeOptions.defaults();
        if (this.errorProperties.isIncludeException()) {
            options = options.including(new ErrorAttributeOptions.Include[]{ErrorAttributeOptions.Include.EXCEPTION});
        }

        if (this.isIncludeStackTrace(request, mediaType)) {
            options = options.including(new ErrorAttributeOptions.Include[]{ErrorAttributeOptions.Include.STACK_TRACE});
        }

        if (this.isIncludeMessage(request, mediaType)) {
            options = options.including(new ErrorAttributeOptions.Include[]{ErrorAttributeOptions.Include.MESSAGE});
        }

        if (this.isIncludeBindingErrors(request, mediaType)) {
            options = options.including(new ErrorAttributeOptions.Include[]{ErrorAttributeOptions.Include.BINDING_ERRORS});
        }

        return options;
    }

    protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
        switch(this.getErrorProperties().getIncludeStacktrace()) {
            case ALWAYS:
                return true;
            case ON_PARAM:
                return this.getTraceParameter(request);
            default:
                return false;
        }
    }

    protected boolean isIncludeMessage(HttpServletRequest request, MediaType produces) {
        switch(this.getErrorProperties().getIncludeMessage()) {
            case ALWAYS:
                return true;
            case ON_PARAM:
                return this.getMessageParameter(request);
            default:
                return false;
        }
    }

    protected boolean isIncludeBindingErrors(HttpServletRequest request, MediaType produces) {
        switch(this.getErrorProperties().getIncludeBindingErrors()) {
            case ALWAYS:
                return true;
            case ON_PARAM:
                return this.getErrorsParameter(request);
            default:
                return false;
        }
    }

    protected ErrorProperties getErrorProperties() {
        return this.errorProperties;
    }

然后修改application.yml配置:

server:
  error:
    path: /server/error

这样就覆盖了/error接口,当调用未定义的接口时,springboot会转到我们定义的/server/error接口。并且我们可以自定义接口的逻辑,例如,当状态码是5xx时,返回400.

/@RequestMapping(
            produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
        //如果>=500返回400
        if (status.value() >= HttpStatus.INTERNAL_SERVER_ERROR.value()) {
            status = HttpStatus.BAD_REQUEST;
        }
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

注意:BasicErrorController中其实是有两个接口,
一个是:(这个接口会返回一个html页面。当我们用浏览器调用时会调用这个,因为Content-Type是text/html)

    @RequestMapping(produces = {"text/html"})
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response)
    {}

另一个是:(这个接口会返回json数据。当我们用postman调用时会调用这个,因为Content-Type是application/json)

	@RequestMapping
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) 
	{}

因此两个接口都要改。
这样就解决了调用/error时返回500的问题

解决方案二:

直接在nginx侧改,当调用error时,返回4xx,简单粗暴。

哪个方案更好:

如果可以在不重启服务、不重启nginx的情况在nginx修改,肯定选择方案二。因为方案一虽然返回了4xx,但是请求仍然进了服务,消耗了服务资源。

举一反三:

因此我们的服务接口是不是应该搞个统一的前缀呢?这样就可以直接在nginx侧统一过滤掉错误请求了,使服务更健康。

  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,那么您可以使用Apache POI库来实现Excel的导出,同时结合Spring Boot响应体ResponseBody和HTTP状态来实现导出Excel成功时返回Blob,失败时返回指定报错信息的功能。 具体实现步骤如下: 1. 后端使用Apache POI库创建Excel文件并写入数据。 2. 在Controller中定义一个导出Excel的接口,使用ResponseBody注解将Excel的二进制流写入响应体中,并设置Content-Type为application/vnd.ms-excel,这样前端就可以直接下载Excel文件。 3. 如果导出Excel遇到异常,可以使用try-catch语句捕获异常,并返回指定的报错信息。 4. 可以通过HTTP状态来进一步标识导出Excel的结果,例如导出成功时返回200状态,失败时返回500状态。 下面是一个简单的示例代,仅供参考: ```java @RestController public class ExcelController { @GetMapping("/exportExcel") public ResponseEntity<byte[]> exportExcel() { try { // 使用Apache POI创建Excel文件并写入数据 Workbook wb = new HSSFWorkbook(); Sheet sheet = wb.createSheet("sheet1"); Row row = sheet.createRow(0); Cell cell = row.createCell(0); cell.setCellValue("Hello World!"); // 将Excel的二进制流写入响应体中 ByteArrayOutputStream out = new ByteArrayOutputStream(); wb.write(out); HttpHeaders headers = new HttpHeaders(); headers.setContentDispositionFormData("attachment", "example.xls"); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); return new ResponseEntity<>(out.toByteArray(), headers, HttpStatus.OK); } catch (Exception e) { // 如果导出Excel遇到异常,返回指定的报错信息 return new ResponseEntity<>("导出Excel失败", HttpStatus.INTERNAL_SERVER_ERROR); } } } ``` 在前端中,您可以使用axios或其他HTTP库来调用后端的导出Excel接口,例如: ```javascript axios.get('/exportExcel') .then(response => { // 导出Excel成功,使用Blob对象创建一个URL并下载Excel文件 let url = URL.createObjectURL(new Blob([response.data])); let link = document.createElement('a'); link.style.display = 'none'; link.href = url; link.setAttribute('download', 'example.xls'); document.body.appendChild(link); link.click(); }) .catch(error => { // 导出Excel失败,展示错误信息 console.error(error.response.data); alert('导出Excel失败'); }); ``` 这样就可以实现导出Excel成功时返回Blob,失败时返回指定报错信息的功能了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值