형식 하나가 어긋나 거부되는 요청, 바이트 단위로 맞추기

웹 · 앱 개발4분 읽기

레거시 시스템 연동에서 가장 김 빠지는 순간은 로그인도 되고 조회도 되는데 정작 실제 처리 요청만 거부될 때입니다. 한 통행료 정산 시스템을 연동할 때가 딱 그랬습니다. 미납 조회까지는 문제없이 되는데, 발송 처리를 요청하면 서버가 계속 처리 중 오류를 돌려줬습니다. 오류 메시지는 무성의했고 요청 내용은 눈으로 보기에 성공했던 요청과 다를 바 없어 보였습니다. 이 글은 그 보이지 않는 차이를 바이트 단위 대조로 찾아내 맞춘 과정을 정리했습니다.

눈에 안 보이는 차이가 요청을 거부한다

문제의 요청은 XML 형태였습니다. 프로그램은 일반적인 XML 생성 도구로 필요한 데이터를 담아 요청을 만들고 있었고 사람 눈으로 보면 컬럼과 값이 모두 제자리에 있었습니다. 그런데 서버는 이 요청을 받아들이지 않았습니다. 처음에는 값이 틀렸나 의심했지만 값은 정확했습니다. 문제는 값이 아니라 요청의 형식, 그것도 사람 눈에는 잘 안 보이는 미세한 형식의 차이에 있었습니다.

오래된 서버의 처리 로직은 관대하지 않은 경우가 많습니다. 요청이 자신이 기대하는 형태와 조금이라도 다르면, 친절한 오류 대신 그냥 실패를 돌려줍니다. 특히 특정 화면이 보내는 요청을 그대로 처리하도록 굳어진 서버라면, 그 화면이 만드는 요청의 형태를 픽셀 단위로 흉내 내지 않는 한 통과하기 어렵습니다. 그래서 접근을 바꿨습니다. 프로그램이 만든 요청을 다듬어서 서버를 이해시키려 하지 말고 애초에 성공했던 요청과 바이트 단위로 똑같은 요청을 만들기로 했습니다.

성공 캡처를 정답지로 삼는다

다행히 우리에게는 정답지가 있었습니다. 현장에서 실제 업무를 수행할 때 성공적으로 처리됐던 요청이 통신 캡처에 남아 있었습니다. 이 성공 요청과 프로그램이 만든 실패 요청을 나란히 놓고 한 글자씩 대조하기 시작했습니다. 이런 대조는 지루하지만 정직합니다. 두 요청이 달라지는 지점이 곧 문제의 지점이기 때문입니다. 그리고 대조를 시작하자 차이가 하나둘 드러났습니다.

가장 컸던 차이는 컬럼 스키마였습니다. 성공했던 요청은 그 화면이 다루는 전체 컬럼을 빠짐없이 선언했는데, 프로그램이 만든 요청은 실제로 값이 있는 컬럼만 선언했습니다. 사람이 보기에는 값이 없는 컬럼을 생략하는 것이 오히려 깔끔해 보이지만 서버의 처리 로직은 미리 약속된 전체 컬럼이 선언되어 있다는 전제로 동작하고 있었습니다. 선언되지 않은 컬럼을 읽으려다 서버 내부에서 오류가 났고 그 결과가 처리 중 오류라는 무뚝뚝한 메시지로 돌아왔습니다.

직렬화 규칙까지 캡처와 일치시키다

차이는 컬럼 개수에서 끝나지 않았습니다. 값을 XML로 풀어 쓰는 직렬화 규칙 자체가 미묘하게 달랐습니다. 컬럼이 선언되는 순서, 값이 없는 셀을 아예 생략할지 빈 셀로 남길지, 특정 문자를 어떻게 이스케이프할지, 심지어 태그 사이의 공백과 줄바꿈, 들여쓰기까지 성공 요청은 나름의 일관된 규칙을 따르고 있었습니다. 일반 XML 생성 도구는 이런 세부를 자기 방식대로 처리하기 때문에, 아무리 값이 맞아도 형태가 어긋났습니다.

그래서 이 화면의 요청만을 위한 전용 직렬화기를 따로 만들었습니다. 캡처가 보여 준 규칙을 그대로 옮겨 전체 컬럼 스키마를 선언하고 값이 있는 셀만 규칙대로 채우며 공백과 이스케이프까지 캡처와 일치시켰습니다. 그렇게 만든 요청을 보내자 서버는 마침내 정상 처리로 응답했습니다. 눈에 안 보이던 형식의 벽이 사라진 순간이었습니다. 이 프로젝트에서 가장 오래 붙들었던 문제가 이렇게 풀렸습니다.

위험을 최소화하는 방식으로 고친다

한 가지 덧붙이자면, 이렇게 전용 직렬화기를 만들 때도 이미 잘 동작하던 부분은 건드리지 않았습니다. 로그인이나 단순 조회처럼 기존 방식으로 문제없이 돌아가던 요청은 그대로 두고 실제로 거부되던 처리 요청 계열만 새 직렬화기로 옮겼습니다. 잘 되던 것까지 한꺼번에 갈아엎으면 새로운 문제가 어디서 생겼는지 알기 어려워집니다. 문제가 확인된 곳만 정확히 고치는 것이 회귀 위험을 줄이는 길입니다.

이 경험이 남긴 교훈은 분명합니다. 레거시 시스템 연동에서 값이 맞는데도 거부된다면, 의심할 곳은 값이 아니라 형식입니다. 형식 문제를 푸는 가장 확실한 방법은 성공했던 실제 요청을 정답지로 두고 바이트 단위로 맞추는 것입니다. 이 요청 재현이 로그인 암호화 해석과 함께 어떻게 전체 자동화로 이어졌는지는 레거시 통행료 시스템 역설계 연동 사례에서 볼 수 있고 이 요청이 오가는 프로토콜 자체의 해석은 Nexacro 레거시 프로토콜 역설계에서 다룹니다.

자주 묻는 질문

값이 다 맞는데 왜 요청이 거부되나요?

오래된 서버의 처리 로직은 요청의 형식에 민감한 경우가 많습니다. 컬럼을 얼마나 선언하는지, 값 없는 셀을 어떻게 처리하는지, 공백과 이스케이프를 어떻게 쓰는지 같은 형식이 서버가 기대하는 것과 다르면, 값이 맞아도 처리가 실패합니다. 문제는 데이터가 아니라 그 데이터를 감싸는 형식에 있는 경우가 많습니다.

바이트 단위 대조는 어떻게 하나요?

성공했던 실제 요청을 통신 캡처로 확보해 정답지로 삼고 프로그램이 만든 요청과 한 글자씩 비교합니다. 두 요청이 달라지는 지점이 곧 문제 지점입니다. 지루하지만 가장 확실한 방법이며 추측 대신 사실로 문제를 좁혀 줍니다.

잘 되던 요청까지 다 바꿔야 하나요?

아닙니다. 문제가 확인된 요청 계열만 새 방식으로 옮기고 기존 방식으로 잘 돌아가던 요청은 그대로 두는 것이 안전합니다. 잘 되던 것까지 한꺼번에 바꾸면 새 문제의 원인을 찾기 어려워지므로, 확인된 곳만 정확히 고쳐 회귀 위험을 줄입니다.

#XML#직렬화#레거시연동#디버깅#Nexacro

업무자동화 구축이 필요하신가요?

무료 상담 신청