문제의 발단: Rack::Attack 스로틀과 StringIO
-
Rack::Attack스로틀은 요청을 분류하고 제한하는 데 사용되며,req.body.read를 통해 요청 본문을 읽어JSON.parse를 시도합니다. -
초기 코드에서는
JSON.parse(req.body.read)이후에req.body.rewind가 있었으나,JSON.parse이전에 커서 위치를 확인한 결과, 커서가 0이 아닌 3281과 같은 위치에 있음을 확인했습니다. -
이는
req.body.read가 빈 문자열을 반환하여JSON::ParserError를 유발했습니다.
StringIO의 특성과 커서 관리
-
req.body는StringIO객체로, 내부적으로 커서를 사용하여 버퍼를 읽습니다. 이 커서는 수동으로rewind메서드를 호출하여 초기 위치(0)로 되돌려야 합니다. -
StringIO의 가변 커서(movable cursor)는 스트리밍 및 청크 전송, 메모리 효율성, 점진적 파싱 등 특정 시나리오에서 유용하지만, 본 애플리케이션에서는IO상태 캡슐화 부족으로 인해 문제를 야기했습니다.
커서 이동의 원인
-
jortt-app코드베이스를 분석한 결과, 여러 컨트롤러에서req.body.read를 사용하고 있었으며, 일부는rewind를 사용했지만 다른 곳에서는 전혀 사용하지 않아StringIO커서가 중간에 멈춰 있는 경우가 발생했습니다. -
이러한 일관성 없는
IO접근 방식이Rack::Attack스로틀과 같은 다운스트림 소비자에게 영향을 미쳐JSON::ParserError를 유발했습니다.
해결책: BufferReader 헬퍼 모듈 도입
-
StringIO객체의 안전한 읽기를 보장하기 위해Jortt::BufferReader모듈에safe_read헬퍼 메서드를 추가했습니다. -
safe_read는readable객체가rewind메서드를 가지고 있는지 확인하고, 읽기 전과 후에rewind를 호출하여 커서 위치를 항상 0으로 재설정합니다. -
이 헬퍼를
req.body를 직접 읽는 모든 곳에 적용하여 일관된IO상태 관리를 확립했습니다.