요격기 작동 데이터 흐름의 이상 문제 분석

【배경】

당시에는 특정 요청에 대해 서명 인증을 수행해야했고이를 처리하기 위해 인터셉터를 사용하기로 결정했습니다. 처음에는 Body에 서명 된 정보를 넣었으므로 HttpServletRequest 및 Body를 통해 인터셉터를 가져와야합니다. 데이터는 스트림에 저장됩니다. 이때 스트림 정보는 request.getReader ()를 통해 읽었습니다. 또한 컨트롤러에서 @RequestBody를 사용하여 JSON 본문을 수신했습니다. 그 결과 프로그램을 실행할 때 다음과 같은 문제가 발생했습니다. 실행 중 :

오류 1 :이 요청에 대해 getInputStream ()이 이미 호출되었습니다
. 오류 2 : 중첩 된 예외는 java.lang.IllegalStateException : getWriter ()가 이미이 응답에 대해 호출되었습니다
. 참고 : 프로그램에서 오류 1이 발생했지만 위의 두 오류 문제의 원리는 동일합니다. 여기에 요약되어 있습니다.

[오류 코드 사례]

질문 1 : 요청
여기에 사진 설명 삽입
질문 2 : HttpServletResponse
여기에 사진 설명 삽입

【분석】

스트림은 두 개의 예외를 읽을 수 없습니다.이 예외는 일반적으로 프레임 또는 인터셉터가 요청에서 스트림의 데이터를 읽을 때 발생합니다. 스트림의 데이터가 더 이상 존재하지 않기 때문에 비즈니스 코드 (예 : @requestBody)에서 다시 읽습니다. , 두 번째로 읽을 때 예외가 발생합니다.

【해결책】

솔루션 1 :이 솔루션은 인터넷에서 찾은 가장 일반적인 솔루션이기도합니다.
먼저 요청 본문을 저장 한 다음 Servlet과 함께 제공되는 HttpServletRequestWrapper 클래스를 통해 getReader () 및
getInputStream () 메서드 를 덮어 쓰면 스트림이 구원받은 몸에서 읽어야합니다. 그런 다음 필터에서 ServletRequest를 AuthenticationRequestWrapper로 바꿉니다.

public class MyRequestWrapper extends HttpServletRequestWrapper {
    
    
    private byte[] body;
 
    public MyRequestWrapper(HttpServletRequest request) throws IOException {
    
    
        super(request);
 
        StringBuilder sb = new StringBuilder();
        String line;
        BufferedReader reader = request.getReader();
        while ((line = reader.readLine()) != null) {
    
    
            sb.append(line);
        }
        String body = sb.toString();
        this.body = body.getBytes(StandardCharsets.UTF_8);
    }
 
 
    public String getBody() {
    
    
        return new String(body, StandardCharsets.UTF_8);
    }
}
//使用
MyRequestWrapper myRequestWrapper = new MyRequestWrapper(request);
      myRequestWrapper.getBody();

해결 방법 2 :

 public static ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal();

ThreadLocal을 사용하고, 스레드 개인 특성을 활용하고, 각 요청 처리 스레드에 대해 ThreadLocal을 할당 한 다음 인터셉터가 스트림을 사용할 때 ThreadLocal 컨테이너에 스트림 데이터를 저장 한 다음 나중에 여기서 직접 읽습니다.
예를 들어 인터셉터는 본문 내용을 읽고이를 Map으로 변환 한 다음 정적 ThreadLocal 뒤의 컨트롤러에 저장하여 컨테이너 데이터를 가져옵니다.

해결 방법 3 :
위의 두 가지 방법은 모두 방해가됩니다. 실제로 유사한 솔루션이 많이 있습니다. 저장을 위해 선택한 컨테이너에 따라 위의 방법 외에도 request.setAttribute 매체 등에 데이터를 저장할 수 있습니다.
그러나 이렇게 작성하는 경우 MyRequestWrapper를 통해 본문을 가져 오거나 ThreadLocal.get ()을 통해 데이터를 가져 오는 것과 같이 향후 새 코드를 추가하기 위해 다른 사람이나 자신을위한 특수 프로세스를 추가해야합니다. 한편으로는 추가 메모리 스토리지가 필요하고 다른 한편으로는 코드가 추가됩니다. 하지만 어쩔 수없는 경우에는이 방법 만 선택할 수 있습니다. 제 실무에서는 요청에 서명하고 인증 만하면되기 때문에 위에서 언급 한 문제를 피하기 위해 서명 정보를 비즈니스 데이터에서 분리합니다. 서명 정보는 헤더에 배치되고 비즈니스 데이터는 그대로 유지됩니다. 본문에 배치 한 다음 인터셉터에 배치합니다.이 단계에서는 request.getHeader 만 필요합니다. 더 이상 스트리밍 데이터를 조작 할 필요가 없습니다. 물론이 방법을 사용하려면 호출자가 요청에 포함 된 매개 변수를 수정해야하며 특정 구현 계획은 여전히 ​​실제 요구 사항과 자체 생각을 기반으로해야합니다.

추천

출처blog.csdn.net/Octopus21/article/details/110732774