'error'에 해당하는 글 9건

Spring MVC 에서는 기본 error 페이지를 제공하지 않아 SimpleMappingExceptionResolver 를 사용해야 하지만, Spring Boot 는 기본적으로 BasicErrorController 를 설정하여 모든 error 를 처리하는 /error 매핑을 제공하며, 서블릿 컨테이너에 전역 error 페이지로 등록된다. 예로, Thymeleaf 를 사용할 경우 /error 와 매핑되는 페이지는 src/main/resources/templates/error.html 가 될 것이다.


시스템 클라이언트의 경우, HTTP 상태, error, 메시지를 담은 JSON 형식으로 응답한다. 브라우저 클라이언트의 경우, 위와 동일한 데이터를 HTML 형식으로 렌더링한 /error 페이지로 응답할 것이며, 존재하지 않을 경우 Spring boot 에서 제공하는 "whitelabel" error 페이지로 응답한다. whitelabel 에는 exception 정보가 명시되므로 다른 error 페이지로 대체하는 것이 좋다. (properties 파일에서 설정이 가능하다. server.error.whitelabel.enabled=false)


error 를 처리할 뷰를 추가하여 커스터마이징 할 수 있는데, @ExceptionHandler 와 @ControllerAdvice 같은 일반적인 Spring MVC 기능을 사용할 수 있다.

@ExceptionHandler 는 단일 컨트롤러 등에서, @ControllerAdvice 는 전역에서 exception 처리를 한다.



@ResponseStatus 과 HTTP 상태 코드 사용


일반적으로 웹 요청을 Spring boot 에서 처리할 때 정의되지 않은 exception 이 발생하면 서버는 HTTP 500 응답을 반환한다. @ResponseStatus 어노테이션을 메소드나 exception 클래스에 사용하면 지정한 상태 코드와 메시지를 반환할 수 있다. 아래는 404 exception 클래스를 만들고, 컨트롤러에 전달된 주문번호(id)가 존재하지 않으면 해당 exception 처리를 하는 예이다.


@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order")  // 404
public class OrderNotFoundException extends RuntimeException {
    // Process Exception
}
 
@RequestMapping(value="/orders/{id}", method=GET)
public String showOrder(@PathVariable("id"long id, Model model) {
    Order order = orderRepository.findOrderById(id);
 
    // Call Exception
    if (order == nullthrow new OrderNotFoundException(id);
 
    model.addAttribute(order);
    return "orderDetail";
}
cs


이렇게 반환되는 에러 정보는 콘솔이나 /error 에 매핑되는 페이지에서 확인할 수 있으며, 없다면 whitelabel 페이지에서 확인할 수 있다.

RESTful 요청의 경우 다음과 같이 error 정보를 json 형식의 응답으로 보여준다.


{
    "timestamp""2018-11-08T05:22:05.440+0000",
    "status"404,
    "error""Not Found",
    "message""No such page",
    "path""/orders"
}
cs



@ExceptionHandler 사용


@RequestMapping 어노테이션을 가진 메소드에서 exception 을 처리하기 위해 동일한 컨트롤러에 @ExceptionHandler 메소드를 추가하여 사용할 수 있다. @ExceptionHandler 을 사용하면, @ResponseStatus 어노테이션이 없는 exception 도 다룰 수 있고, 별로도 작성한 error 페이지로 사용자를 리디렉션 시킬 수 있다. @ExceptionHandler 는 HttpServletRequest, HttpSession, WebRequest, Locale, InputStream, OutputStream, Empty Model, ... 등의 여러가지 파라미터 type 을 가질 수 있다. 또한 ModelAndView, Model, Map, View, String, @ResponseBody, HttpEntiry<?>, ResponseEntity<?>, void, ... 등의 type 을 반환할 수 있다.


다음은 DataIntegrityViolationException 라는 미리 작성한 exception 클래스를 호출시켜 핸들러에서 처리하는 예제이다.


@Controller
public class ExceptionHandlingController {
    ...
    @RequestMapping("/dataIntegrityViolation")
    String throwDataIntegrityViolationException() throws SQLException {
        throw new DataIntegrityViolationException("Duplicate id");
    }
 
    @ResponseStatus(value = HttpStatus.CONFLICT, reason = "Data integrity violation"// 409
    @ExceptionHandler(DataIntegrityViolationException.class)
    public void conflict() {
        // Nothing to do
    }
}
cs



@ControllerAdvice 사용


@ControllerAdvice 를 사용하면 개별 컨트롤러뿐만 아니라 전체 어플리케이션에서 exception 처리를 할 수 있다.

모든 exception 의 기본 핸들러의 코드는 다음과 같다.


@ControllerAdvice
class GlobalDefaultExceptionHandler {
    public static final String DEFAULT_ERROR_VIEW = "error";
 
    @ExceptionHandler(value = Exception.class)
    public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        // exception 에 @ResponseStatus 를 가진 경우 프레임워크가 처리하도록 한다.
        if (AnnotationUtils.findAnnotation (e.getClass(), ResponseStatus.class!= null)
            throw e;
 
        ModelAndView mav = new ModelAndView();
        mav.addObject("exception", e);
        mav.addObject("url", req.getRequestURL());
        mav.setViewName(DEFAULT_ERROR_VIEW);
        return mav;
    }
}
cs




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,
INFO 8508 --- [nio-8080-exec-1] o.apache.coyote.http11.Http11Processor   : Error parsing HTTP request header
 Note: further occurrences of HTTP header parsing errors will be logged at DEBUG level.
 
java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986
    at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:479) ~[tomcat-embed-core-8.5.34.jar:8.5.34]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:684) ~[tomcat-embed-core-8.5.34.jar:8.5.34]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.34.jar:8.5.34]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806) [tomcat-embed-core-8.5.34.jar:8.5.34]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498) [tomcat-embed-core-8.5.34.jar:8.5.34]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.34.jar:8.5.34]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_191]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_191]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.34.jar:8.5.34]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_191]
cs


이번 프로젝트 로그에 유독 많이 보이는 400 에러. 헤더 파싱에서 오류가 났다는. 요청에 잘못된 문자가 있다는.

테스트 결과 IE 에서 GET 방식의 파라미터에 한글이나 특수문자 등을 인코딩 없이 강제로 타이핑 할 경우 발생했다.

Spring Framework 안으로 들어오지도 못하고 web.xml 도 무시하고 서버단에서 요청을 차단해 버리는 기이한 현상. 따지자면 IE 가 문제지...

Filter 와 Interceptor 까지도 무시해 버리는 이 무시무시한 차단을 우째스까잉.


검색 결과 Tomcat 의 특정 버전 이상에서 QueryString 문자를 엄격하게 차단한다는 것을 알았다.

ChangeLog 를 확인했지만, 마땅한 해결책은... 모르겠다.


8.0.39 / 8.5.7

Add additional checks for valid characters to the HTTP request line parsing so invalid request lines are rejected sooner.


- invalid 요청을 더 일찍 거부하도록 문자열 검사를 하라는데 이 때부터 시작한거 같지만, 테스트 결과 8.0.32 에서도 400 에러를 확인했다는...


8.0.42 / 8.5.12

Allow some invalid characters that were recently restricted to be processed in requests by using the system property tomcat.util.http.parser.HttpParser.requestTargetAllow.


- 요청시 invalid characters 를 처리하도록 relaxedQueryChars (requestTargetAllow deprecated) 속성을 사용하라고 하지만, IE 에서 한글을 입력한 것 처럼 깨진 문자는 어찌 해결해야 할지...


테스트에서 invalid characters 가 포함된 HTTP 요청이 Spring Framework 를 통과한 Tomcat 버전은...


8.0.28 (Spring Boot 1.3.0)

8.5.4 (Spring Boot 1.4.0)


이 이후의 것들은 어찌 처리해야 할지...

결국 꽤 오랜 시간을 투자했지만 이것에 대한 문제는 처리하지 못했다.

그냥 적당한 Tomcat 버전을 사용하는 걸로...




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,

404 Error page

Daily/Prog 2018. 9. 3. 20:25


Spring boot 1.5.9 사용 중이다. 이래저래 해서 2.x 를 사용하지 못하고 있는데, 4~50x 전역 에러 페이지 만드는게 이렇게 괴로울 줄이야 ㅋㅋ


@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
    return new MyCustomizer();
}
 
private static class MyCustomizer implements EmbeddedServletContainerCustomizer{
    @Override
    public void customize(ConfigurableEmbeddedServletContainer container) {
        String errorUrl = "/error";
        Logger log = LoggerFactory.getLogger("ErrorConconfiguration.class");
        container.addErrorPages(
            new ErrorPage(errorUrl)
        );
    }
}
cs


이래저래 요약하면 이 몇줄로 모든 HttpStatus 에 대한 에러는 /error 페이지를 찾아가야 한다.

나를 괴롭히는건, 로컬에서는 잘 돌아가는데 서버에만 올리면 ㅄ짓을 한다는...

안 해본게 없는거 같다. 괴롭다. 장트러블까지 생긴듯.

아무래도 서버에서는 기본적으로 어디론가 보내는 듯 하고 거기에 페이지가 없어서자꾸 404 가 뜨는듯 한데...

도저히 모르겠어서 web.xml 파일에다 때려넣었다.


...
<error-page>
   <error-code>404</error-code>
   <location>/error/404</location>
</error-page>
....
cs




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,

Parse push failed

Daily/Prog 2017. 2. 1. 16:51

Error generating response. ParseError {

  code: 135,

  message: 'at least one ID field (deviceToken, installationId) must be specified in this operation' }


Error generating response. ParseError {

  code: 137,

  message: 'A duplicate value for a field with unique values was provided' }


갑자기 생겨난 원인을 찾기 힘든 parse error 메시지.

마이그레이션 하고 나서 개발자들이 테스트 하다가 이 모양이 됐다.


135 error 는 deviceToken 이나 installationId 가 payload 에 있어야 하는데 없다는 얘기 같고. 그래도 뭔소린지 모르겠고.. 

(deviceToken 이랑 installationId 가 둘다 값이 없는 데이터는 한 개도 없음.)

137 error 는 unique 컬럼에 중복 값이 있다는 오류 같고.

(이것도 검색 결과 나오지 않았음.)


아 놔 에러 메시지가 친절히 나와도 이거 당최 알 수가 없네.

일단 deviceToken 을 살펴보니 테스트들 한답시고 test 와 123456... 이런 문자들이... ㅋㅋ 줸장!

mongoDB 에서 문자의 길이가 64자 미만의 deviceToken 값들을 찾아봤다.



db.getCollection('_Installation').find({$where"this.deviceToken.length < 64"}).limit(10);
cs


Error: error: {

"$err" : "TypeError: Cannot read property 'length' of undefined\n    at _funcs2 (_funcs2:1:46) near '64' ",

"code" : 16722

}


undefined 값들의 길이를 구할 수 없다는 에러가 ㅋㅋ;

토큰이 빈건 또 뭐여...


db.getCollection('_Installation').find({deviceToken: {$existstrue}, $where"this.deviceToken.length < 64"})
cs


조회하고 삭제. (find -> remove 대체.)


이렇게 했는데도 push 가 반절은 발송되지 않았다. unique 컬럼을 찾아봤다.


db.getCollection('_Installation').getIndexes()
cs


unique 키는 installationId_1 이랑 installationId_1_deviceToken_1.

중복값을 체크해 봤다.


db.getCollection('_Installation').aggregate([
       {$group : {_id : "$deviceToken", count : {$sum 1}}},
       {$match : {count : {$gt : 1}}} ])
 
db.getCollection('_Installation').aggregate([
       {$group : {_id : "$installationId", count : {$sum 1}}},
       {$match : {count : {$gt : 1}}} ])
cs


중복값은 없다;


deviceToken 과 installationId 가 마이그레이션 후에 undefined 로 들어간게 2천개 가량 있는데 이 부분이 약간 의심스러움. SDK 문제인지...

index 까지 완전 동일한 테스트 서버에서는 undefined 백개 있어도 잘 보내짐 ㅜㅜ

운영서버는 push 보내려면 또 한 달을 기다려야 하고... 아~ 짜증난다...




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,