ERR_HTTP_HEADERS_SENT - HTTP 응답 후 수정 시도 문제 해결
문제
이 에러는 Swagger를 이용해 하나의 API 메소드를 여러번 호출했을 때 발생하는 에러로, response 인터셉터를 설정한 이후 자주 발생했다.
서버 내부적으로 에러는 발생하지만 통신이 다운되거나 메소드가 작동하지 않는 경우는 없었기에 바로 처리하지 않고 처리를 여태 미뤄두었다. 계속 신경쓰였던 에러였고 추후 배포 시 문제가 될 것이라고 여겨 수리를 시작했다.
Cannot remove headers after they are sent to the client {
code: 'ERR_HTTP_HEADERS_SENT'
}
Cannot set headers after they are sent to the client이 에러는 클라이언트에게 응답을 보낸 후에 다시 응답 헤더를 수정하거나 제거하려고 할 때 발생한다고 한다. 무슨 말인지 완벽히 이해가 되진 않지만 통신 응답을 반환하는 부분이 제대로 마무리가 되지 않는 문제가 있을 것이라고 여겼다.
원인
문제의 원인은 인터셉터로 설정해둔 미들웨어의 로직에 있었다.
중복된 응답 반환
(미들웨어를 거치지 않고 응답을 반환할 경우엔 에러가 발생하지 않았다.)
아래는 백엔드 서버 전역으로 설정해둔 인터셉터 미들웨어다.
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class ApiResponseInterceptor implements NestInterceptor {
constructor(private configService: ConfigService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
tap((response) => {
const httpResponse = context.switchToHttp().getResponse();
const { statusCode, message, data, redirect } = response;
const frontendBaseUrl = this.configService.get<string>('REDIRECT_ORIGIN');
if (redirect) {
httpResponse.redirect(`${frontendBaseUrl}${redirect}`);
} else {
httpResponse.status(statusCode).send({
success: true,
statusCode,
message: message || '요청이 성공적으로 처리되었습니다.',
data,
});
}
}),
);
}
}위 인터셉터 미들웨어의 로직은,
intercept()메소드로 비동기 작업을 수행할 수 있도록 한 rxjs의 Observable 를 반환하도록 설정해두었고, HTTP 응답을 반환할 수 있도록 내부에 reponse.send()를 사용했다.
하지만 예상했던 흐름과는 다르게 intercept()가 Observable를 반환하기도 전에 .send()가 HTTP 응답을 즉시 반환해버리고,
이후 Observable를 반환하게 되어 응답이 중복 처리되어 위와 같은 에러가 발생한 것으로 여겨진다.
해결
원인이 된 로직을 파악했으므로 아래와 같이 코드를 수정해 문제를 해결했다.
중복된 응답 반환 제거
에러의 핵심 문제였던 응답이 중복으로 반환되는 위치를 확인했으므로 아래와 같이 코드를 수정했다.
httpResponse.status(statusCode);
return {
success: true,
statusCode,
message: message || '요청이 성공적으로 처리되었습니다.',
data,
};reponse.status(statusCode).send()를 사용했던 이유는 상태코드를 직접 지정하고 지정된 상태코드로 응답되기를 원했기 때문이다.
하지만 .send()까지 처리를 하는 것이 중복 반환의 문제였기 때문에, .status(statusCode)로 상태코드만 response에 지정해두고 데이터는 인터셉터의 흐름대로 반환되게끔 해두었다.
이후 에러는 발생하지 않았다.