programing

문자열을 반환하는 Spring MVC @ResponseBody 메서드에서 HTTP 400 오류가 발생하여 응답하는 방법

magicmemo 2023. 3. 27. 21:10
반응형

문자열을 반환하는 Spring MVC @ResponseBody 메서드에서 HTTP 400 오류가 발생하여 응답하는 방법

MVC를 Spring MVC는 Spring MVC로 되어 있습니다.@ResponseBody직접 생산하는 레이어가 이미 .(JSON은 JSON을 직접 생산하는 서비스 입니다.)

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        // TODO: how to respond with e.g. 400 "bad request"?
    }
    return json;
}

이 시나리오에서 HTTP 400 오류에 대응하는 가장 간단하고 깨끗한 방법은 무엇입니까?

나는 다음과 같은 접근방식을 발견했다.

return new ResponseEntity(HttpStatus.BAD_REQUEST);

그러나 메서드의 반환 유형이 ResponseEntity가 아닌 String이기 때문에 여기서는 사용할 수 없습니다.

을 " " "로 변경합니다.ResponseEntity<> 하다, 400:다, 하다, 하다, 이렇게 쓸 수 요.

return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

올바른 요청의 경우:

return new ResponseEntity<>(json,HttpStatus.OK);

Spring 4.1 이후 ResponseEntity에는 다음과 같은 도우미 메서드가 있습니다.

return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);

그리고.

return ResponseEntity.ok(json);

이와 같은 방법이 효과가 있을 것입니다만, 보다 간단한 방법이 있을지는 잘 모르겠습니다.

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId, @RequestBody String body,
            HttpServletRequest request, HttpServletResponse response) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        response.setStatus( HttpServletResponse.SC_BAD_REQUEST  );
    }
    return json;
}

이것은 반드시 가장 간단한 방법은 아니지만, 제 생각에는 꽤 깨끗합니다.

if(json == null) {
    throw new BadThingException();
}
...

@ExceptionHandler(BadThingException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public @ResponseBody MyError handleException(BadThingException e) {
    return new MyError("That doesn’t work");
}

3. 를 는 예외 으로 @를 할 수 않으면 Spring 3.1+ 를 사용합니다.ModelAndView아, 아, 아, 아, 아, 아, 아, 아, 아.

@ResponseBody는 @ExceptionHandler [SPR-6902]#11567에서 동작하지 않습니다.

구현을 약간 변경합니다.

'I'를 .UnknownMatchException:

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UnknownMatchException extends RuntimeException {
    public UnknownMatchException(String matchId) {
        super("Unknown match: " + matchId);
    }
}

@ResponseStatus의 사용에 주의해 주십시오.이것은 스프링의ResponseStatusExceptionResolver, 코드를 (도 실례했습니다404 - Not Found이 사용 사례에 더 적합하다고 생각합니다만, 계속 사용하세요.HttpStatus.BAD_REQUEST괜찮으시다면).


다음, 음음음 the the는 the the the the the the the next next next next?MatchService하다

interface MatchService {
    public Match findMatch(String matchId);
}

하고 Spring Spring's Spring'에 위임합니다.MappingJackson2HttpMessageConverter하고 JSON을 됩니다).@EnableWebMvc ★★★★★★★★★★★★★★★★★」<mvc:annotation-driven />설정을 변경합니다.레퍼런스 메뉴얼을 참조해 주세요).

@RequestMapping(value = "/matches/{matchId}", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Match match(@PathVariable String matchId) {
    // Throws an UnknownMatchException if the matchId is not known
    return matchService.findMatch(matchId);
}

도메인 오브젝트를 뷰 오브젝트 또는 DTO 오브젝트에서 분리하는 것은 매우 일반적입니다.이것은, 시리얼 가능한 JSON 오브젝트를 반환하는 소규모의 DTO 팩토리를 추가하는 것으로 간단하게 실현할 수 있습니다.

@RequestMapping(value = "/matches/{matchId}", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public MatchDTO match(@PathVariable String matchId) {
    Match match = matchService.findMatch(matchId);
    return MatchDtoFactory.createDTO(match);
}

여기 다른 방법이 있습니다. 「」을 합니다.Exception'어느 정도'라는 주석이 붙어 .@ResponseStatus예를 들면 다음과 같습니다.

@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Not Found")
public class NotFoundException extends Exception {

    public NotFoundException() {
    }
}

그리고 필요할 때 던져라.

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        throw new NotFoundException();
    }
    return json;
}

입니다.ResponseStatusException:

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId, @RequestBody String body) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        throw new ResponseStatusException(HttpStatus.NOT_FOUND);
    }
    return json;
}

일부 답변에서 설명한 바와 같이 반환하는 HTTP 상태별로 예외 클래스를 만들 수 있습니다.각 프로젝트에 대해 상태별로 클래스를 만들어야 한다는 생각은 마음에 들지 않습니다.내가 대신 생각해낸 건 이거야.

  • HTTP 상태를 받아들이는 일반 예외 만들기
  • 컨트롤러 어드바이스 예외 핸들러를 만듭니다.

코드로 들어가자

package com.javaninja.cam.exception;

import org.springframework.http.HttpStatus;


/**
 * The exception used to return a status and a message to the calling system.
 * @author norrisshelton
 */
@SuppressWarnings("ClassWithoutNoArgConstructor")
public class ResourceException extends RuntimeException {

    private HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

    /**
     * Gets the HTTP status code to be returned to the calling system.
     * @return http status code.  Defaults to HttpStatus.INTERNAL_SERVER_ERROR (500).
     * @see HttpStatus
     */
    public HttpStatus getHttpStatus() {
        return httpStatus;
    }

    /**
     * Constructs a new runtime exception with the specified HttpStatus code and detail message.
     * The cause is not initialized, and may subsequently be initialized by a call to {@link #initCause}.
     * @param httpStatus the http status.  The detail message is saved for later retrieval by the {@link
     *                   #getHttpStatus()} method.
     * @param message    the detail message. The detail message is saved for later retrieval by the {@link
     *                   #getMessage()} method.
     * @see HttpStatus
     */
    public ResourceException(HttpStatus httpStatus, String message) {
        super(message);
        this.httpStatus = httpStatus;
    }
}

그런 다음 컨트롤러 어드바이스 클래스를 만듭니다.

package com.javaninja.cam.spring;


import com.javaninja.cam.exception.ResourceException;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;


/**
 * Exception handler advice class for all SpringMVC controllers.
 * @author norrisshelton
 * @see org.springframework.web.bind.annotation.ControllerAdvice
 */
@org.springframework.web.bind.annotation.ControllerAdvice
public class ControllerAdvice {

    /**
     * Handles ResourceExceptions for the SpringMVC controllers.
     * @param e SpringMVC controller exception.
     * @return http response entity
     * @see ExceptionHandler
     */
    @ExceptionHandler(ResourceException.class)
    public ResponseEntity handleException(ResourceException e) {
        return ResponseEntity.status(e.getHttpStatus()).body(e.getMessage());
    }
}

사용하기 위해서

throw new ResourceException(HttpStatus.BAD_REQUEST, "My message");

http://javaninja.net/2016/06/throwing-exceptions-messages-spring-mvc-controller/

Spring Boot 어플리케이션에서 사용하고 있습니다.

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public ResponseEntity<?> match(@PathVariable String matchId, @RequestBody String body,
            HttpServletRequest request, HttpServletResponse response) {

    Product p;
    try {
      p = service.getProduct(request.getProductId());
    } catch(Exception ex) {
       return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
    }

    return new ResponseEntity(p, HttpStatus.OK);
}

Spring Boot에서는 이것이 왜 필요한지는 잘 모르겠습니다(Spring Boot에서는/error에도 폴백@ResponseBody에 정의되어 있다.@ExceptionHandler)는, 그 자체는 기능하지 않았습니다.

@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorMessage handleIllegalArguments(HttpServletRequest httpServletRequest, IllegalArgumentException e) {
    log.error("Illegal arguments received.", e);
    ErrorMessage errorMessage = new ErrorMessage();
    errorMessage.code = 400;
    errorMessage.message = e.getMessage();
    return errorMessage;
}

요청 속성으로 정의된 프로덕션 미디어 유형이 없기 때문에 여전히 예외가 발생했습니다.

// AbstractMessageConverterMethodProcessor
@SuppressWarnings("unchecked")
protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
        ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
        throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

    Class<?> valueType = getReturnValueType(value, returnType);
    Type declaredType = getGenericType(returnType);
    HttpServletRequest request = inputMessage.getServletRequest();
    List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
    List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
if (value != null && producibleMediaTypes.isEmpty()) {
        throw new IllegalArgumentException("No converter found for return value of type: " + valueType);   // <-- throws
    }

// ....

@SuppressWarnings("unchecked")
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, Type declaredType) {
    Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    if (!CollectionUtils.isEmpty(mediaTypes)) {
        return new ArrayList<MediaType>(mediaTypes);

그래서 넣었어요.

@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorMessage handleIllegalArguments(HttpServletRequest httpServletRequest, IllegalArgumentException e) {
    Set<MediaType> mediaTypes = new HashSet<>();
    mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
    httpServletRequest.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
    log.error("Illegal arguments received.", e);
    ErrorMessage errorMessage = new ErrorMessage();
    errorMessage.code = 400;
    errorMessage.message = e.getMessage();
    return errorMessage;
}

이를 통해 "지원되는 호환 미디어 유형"을 사용할 수 있었습니다. 하지만 여전히 작동하지 않았습니다.ErrorMessage에 장애가 있었습니다.

public class ErrorMessage {
    int code;

    String message;
}

Jackson Mapper는 그것을 "컨버터블"로 취급하지 않았기 때문에 나는 getters/setters를 추가해야 했고 나는 또한 추가했다.@JsonProperty주석

public class ErrorMessage {
    @JsonProperty("code")
    private int code;

    @JsonProperty("message")
    private String message;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

그리고 나는 의도한 대로 내 메시지를 받았다.

{"code":400,"message":"An \"url\" parameter must be defined."}

또 다른 접근법은@ExceptionHandler와 함께@ControllerAdvice모든 핸들러를 같은 클래스에 집중시킵니다.그렇지 않은 경우 예외를 관리하는 모든 컨트롤러에 핸들러 메서드를 삽입해야 합니다.

핸들러 클래스:

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {

  @ExceptionHandler(MyBadRequestException.class)
  public ResponseEntity<MyError> handleException(MyBadRequestException e) {
    return ResponseEntity
        .badRequest()
        .body(new MyError(HttpStatus.BAD_REQUEST, e.getDescription()));
  }
}

커스텀 예외:

public class MyBadRequestException extends RuntimeException {

  private String description;

  public MyBadRequestException(String description) {
    this.description = description;
  }

  public String getDescription() {
    return this.description;
  }
}

이것으로, 임의의 컨트롤러로부터 예외를 슬로우 할 수 있게 되어 어드바이스 클래스내에 다른 핸들러를 정의할 수 있게 되었습니다.

명시적으로 반환하지 않고 컨트롤러에서 예외를 처리하는 가장 심플하고 깨끗한 방법ResponseEntity그냥 더하는 것이다.@ExceptionHandler방법들.

Spring Boot 2.0.3을 사용한 스니펫의 예릴리즈:

// Prefer static import of HttpStatus constants as it's cleaner IMHO

// Handle with no content returned
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(BAD_REQUEST)
void onIllegalArgumentException() {}

// Return 404 when JdbcTemplate does not return a single row
@ExceptionHandler(IncorrectResultSizeDataAccessException.class)
@ResponseStatus(NOT_FOUND)
void onIncorrectResultSizeDataAccessException() {}

// Catch all handler with the exception as content
@ExceptionHandler(Exception.class)
@ResponseStatus(I_AM_A_TEAPOT)
@ResponseBody Exception onException(Exception e) {
  return e;
}

여백으로서:

  • 모든 컨텍스트/사용방법에서matchService.getMatchJson(matchId) == null무효입니다. 그러면 제 제안은getMatchJson예를 들어, 예를 들어,IllegalArgumentException돌아오는 대신null컨트롤러에 거품이 일게 합니다.@ExceptionHandler.

  • 한다면null다른 조건을 테스트하기 위해 사용됩니다.그러면 다음과 같은 구체적인 방법을 사용할 수 있습니다.matchService.hasMatchJson(matchId)저는 null한 한 예기치 않은 「예상치 않은」을 NullPointerException.

또한 Spring의 기본 오류 처리 기능을 활용할 수도 있습니다.

그러나 이러한 기본 오류와 마찬가지로 응답 본문은 설정되지 않습니다.

이러한 요구는 보다 심층적인 커스텀 검증과 그 기준에 따라 요구가 거부되었다는 사실을 모호하게 하기 때문에 수작업으로 이루어졌을 뿐이며 잠재적으로 악의적인 의도를 나타낼 수 있는 요구를 거부할 때 유용합니다.

상태 코드와 함께 사용자 지정 응답을 사용합니다.

다음과 같이 합니다.

class Response<T>(
    val timestamp: String = DateTimeFormatter
            .ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS")
            .withZone(ZoneOffset.UTC)
            .format(Instant.now()),
    val code: Int = ResultCode.SUCCESS.code,
    val message: String? = ResultCode.SUCCESS.message,
    val status: HttpStatus = HttpStatus.OK,
    val error: String? = "",
    val token: String? = null,
    val data: T? = null
) : : ResponseEntity<Response.CustomResponseBody>(status) {

data class CustomResponseBody(
    val timestamp: String = DateTimeFormatter
            .ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS")
            .withZone(ZoneOffset.UTC)
            .format(Instant.now()),
    val code: Int = ResultCode.SUCCESS.code,
    val message: String? = ResultCode.SUCCESS.message,
    val error: String? = "",
    val token: String? = null,
    val data: Any? = null
)

override fun getBody(): CustomResponseBody? = CustomResponseBody(timestamp, code, message, error, token, data)

언급URL : https://stackoverflow.com/questions/16232833/how-to-respond-with-an-http-400-error-in-a-spring-mvc-responsebody-method-retur

반응형