Skip to content

fix(proxy): naver shopping/news upstream 4xx 를 502 upstream_error 로 정규화 (Refs #717) - #766

Open
vkehfdl1 wants to merge 1 commit into
devfrom
fix/naver-shopping-upstream-status
Open

vkehfdl1 wants to merge 1 commit into
devfrom
fix/naver-shopping-upstream-status

Conversation

@vkehfdl1

@vkehfdl1 vkehfdl1 commented Nov 2, 2026

Copy link
Copy Markdown
Member

배경 (Refs #717 item 2)

/v1/naver-shopping/search 는 upstream 이 4xx 를 주면 그 상태 코드를 그대로 클라이언트에 전달했습니다. 그래서 프로덕션 앱에 Shopping search API 권한이 없어 네이버가 404 SE05 를 주면, 클라이언트/스킬은 "프록시 라우트가 없다"로 해석하게 됩니다.

배포 중인 프록시(현재 main 기준)에서 재현:

$ curl -sS -m 30 -w '\nHTTP_STATUS:%{http_code}\n' \
    'https://k-skill-proxy.nomadamas.org/v1/naver-shopping/search?q=airpods'
{"error":"upstream_error","message":"Naver Search API responded with 404.","proxy":{...},
 "upstream":{"status_code":404,"body_snippet":"{\n \"errorMessage\": \"Invalid search api (존재하지 않는 검색 api 입니다.)\",\n \"errorCode\": \"SE05\"\n}\n"}}
HTTP_STATUS:404
  • 관측: 프록시가 404 를 반환 (라우트 미존재로 보임).
  • 원인: packages/k-skill-proxy/src/naver-shopping.js 의 fetchNaverShoppingOpenApiSearch 가 error.statusCode = response.status >= 400 && response.status < 500 ? response.status : 502 로 upstream 4xx 를 그대로 통과시켰고(수정 전 753-756행), src/server.js 의 라우트 catch 가 그 값을 reply.code(...) 로 사용했습니다(수정 전 5600행 부근). naver-news.js(수정 전 226-229행) / 그 라우트도 동일했습니다.
  • 기대: upstream 4xx/5xx 는 프록시 502 upstream_error 로 정규화하고, 진단 정보(upstream 상태/본문 스니펫)와 네이버 오류 코드는 upstream 블록에 보존한다. 429 는 429 를 유지한다.

변경

  • src/naver-upstream-error.js (신규): createUpstreamHttpError({ apiLabel, status, body, headers })
    • 429 → code: "upstream_rate_limited", 429 + Retry-After 파싱(숫자 초만, HTTP-date 는 무시)
    • 그 외 4xx/5xx → code: "upstream_error", 502
    • 항상 upstreamStatusCode, upstreamBodySnippet(200자), upstreamErrorCode(JSON 본문의 errorCode/error_code, 예: SE05/024/010) 보존
  • src/naver-shopping.js: 공식 Search API 경로 + no-key BFF fallback 경로 모두 이 헬퍼 사용 (BFF 는 이미 502 였지만 코드 중복을 제거하고 429/오류코드 처리를 동일하게 맞춤). fallback 추가 없음(BFF 는 418 을 주므로 도움되지 않음).
  • src/naver-news.js: 동일 헬퍼 사용.
  • src/server.js: 두 라우트의 catch 에서 buildUpstreamErrorDetails(error) 로 upstream.status_code/body_snippet(+ 있으면 error_code, retry_after) 를 만들고, retry_after 가 있으면 retry-after 응답 헤더도 내려줍니다. 프록시 자체 검증 실패는 계속 400.
  • 문서: naver-news-search/instruction.md 실패 모드 목록이 기존 401/429 passthrough 를 문서화하고 있어 새 규약으로 갱신(generate:skill-stubs, sync:cli-skills 실행, patch changeset 추가). docs/features/naver-news-search.md 와 CLI 스냅샷도 함께 갱신. naver-shopping-search/instruction.md 는 passthrough 를 문서화하지 않아 변경하지 않았습니다.

테스트 (mocked fetch, 실패→통과 확인)

수정 전 node --test test/naver-shopping.test.js test/naver-news.test.js 에서 6개 실패:

✖ naver shopping openapi upstream 404 hides route behind a proxy 502 upstream_error   (actual 404, expected 502)
✖ naver shopping openapi upstream 401 auth failure surfaces as a proxy 502 upstream_error (actual 401, expected 502)
✖ naver shopping openapi upstream 429 stays 429 upstream_rate_limited ... (actual 'upstream_error')
✖ naver news search endpoint surfaces upstream 401 auth failure as a proxy 502 ... (actual 401, expected 502)
✖ naver news search endpoint hides upstream 404 behind a proxy 502 upstream_error (actual 404, expected 502)
✖ naver news search endpoint surfaces upstream 429 as upstream_rate_limited ... (actual 'upstream_error')

수정 후 전부 통과. 추가로:

  • upstream 404 + errorCode: SE05 → 502 upstream_error, upstream.status_code: 404, upstream.error_code: "SE05"
  • upstream 401 + errorCode: 024 → 502 (naver-news 는 실패 응답 미캐시 검증 유지)
  • upstream 429 + Retry-After: 60 → 429 upstream_rate_limited, upstream.retry_after: 60, retry-after 헤더
  • BFF fallback 418 → 502 upstream_error, upstream.status_code: 418
  • 성공 경로(200)는 그대로

로컬 검증: cd packages/k-skill-proxy && node --test → 493 tests / 491 pass / 0 fail / 2 skipped, npm run lint 통과.

남은 것 (이 PR 범위 밖, ops)

프로덕션 네이버 앱에 Shopping search API 권한이 없어 SE05 자체는 계속 발생합니다. 이 PR 은 그 사실이 404 대신 진단 가능한 502 + upstream.error_code: SE05 로 보이게만 합니다. key 없는 fallback(ns-portal.shopping.naver.com)은 418 access temporarily restricted 를 반환해 대체 경로가 될 수 없습니다.

The Naver shopping/news routes forwarded the upstream HTTP status verbatim,
so an upstream 404 (`errorCode: SE05`, missing Shopping search permission on
the production app) reached clients as if `/v1/naver-shopping/search` did not
exist, and an upstream 401 looked like a proxy-level auth rejection.

`createUpstreamHttpError` (new `src/naver-upstream-error.js`) now classifies
every non-OK Naver response: 429 stays 429 with `upstream_rate_limited` and a
passthrough `Retry-After`, everything else becomes 502 `upstream_error`.
The real status and a body snippet stay in `upstream.status_code` /
`upstream.body_snippet`, plus `upstream.error_code` parsed from the Naver JSON
body (SE05/024/010) and `upstream.retry_after`. Proxy-side validation still
answers 400. The no-key BFF fallback uses the same helper for consistency.

Refs #717
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant