구현 | 응답 생성하기

다양한 종류의 응답을 효율적으로 처리하기

일반적으로, 요청을 읽는 제어 흐름은 하나입니다. body가 있을 경우 Content-Length를 이용한 전송이냐 Chunked transfer-encoding을 이용한 이용이냐에 따라 한 번 분기가 있을 뿐이죠. 메소드의 실행 역시 메소드에 따라 명확하게 나누어지고, Autoindex나 CGI만 GET과 POST에서 리다이렉션시키면 됩니다.

앞의 처리 과정들은 HTTP/1.1의 내용 자체가 함수 구조를 어느 정도 정해준다면, 응답을 생성하는 것은 상대적으로 다른 영역보다 개발자의 센스가 크게 필요한 영역입니다. 어떤 방식으로 응답을 생성해야 할까요?

1. 응답 종류마다 함수를 만들기

우리의 서브젝트에서 의미 있는 응답 상태 코드는 약 25개 정도인데, 단순하게 생각하면 생성할 응답마다 함수를 따로 만들어서 create200Response(), create400Response()와 같이 처리하면 됩니다. 하지만 응답마다 공통적인 작업들이 많기 때문에 그렇게까지 세분화하는 것이 좋은 방법인지는 모르겠습니다.

2. 응답 성격마다 함수를 만들기

상태 코드는 범위마다 의미를 가집니다. 그래서 200번대, 300번대, 400번대, 500번대 상태 코드들을 각각 묶은뒤 그룹별로 함수를 만들어 처리하는 방법도 있습니다.

3. 하나의 흐름으로 처리하기

모든 응답을 한 번에 처리하는 방법도 있습니다. 하나의 함수를 통해 관리할 수 있다는 편의성과 효율성이 있습니다. 다만 이 경우 가독성이 굉장히 떨어지고, 특정 응답을 위한 로직의 수정이 다른 응답에 잘못된 영향을 미칠 수 있다는 위험성이 있습니다. 저는 이 방법으로 구현하였고, 주요 로직은 아래와 같습니다.

...
headers.push_back(getDateHeader());
headers.push_back(getServerHeader(this));

if (status == CGI_SUCCESS_CODE)
	createCGIResponse(status, headers, body);
if (status >= 400 && status <= 599) {
	if (!location.get_m_error_page().empty())
		body = location.get_m_error_page();
	else
		body = m_default_error_page;
}
if (!ft::hasKey(ft::stringVectorToMap(headers), "Transfer-Encoding"))
	headers.push_back("Content-Length:" + ft::to_string(body.size()));
if (!body.empty())
		headers.push_back("Content-Language:ko-KR");
if (status / 100 != 2)
	headers.push_back("Connection:close");
if (status / 100 == 3 && !ft::hasKey(ft::stringVectorToMap(headers), "Location"))
	headers.push_back("Location:/");
if (status == 504 && !ft::hasKey(ft::stringVectorToMap(headers), "Retry-After")
	headers.push_back("Retry-After:3600");
if (connection.get_m_request().get_m_method() == Request::HEAD)
	body.clear();
...

이런 방식의 구현을 위해서는 '인자'로 무엇이 와야 할까요? 단순히 '응답 상태 코드'만 인자로 받아서는 절대 응답을 생성할 수 없습니다. body가 있는 경우 body가 인자로 와야 하고, 상태 코드에 1:1로 대응되지 않는 헤더가 있다면 헤더들도 들어와야 합니다. 가령 <405 Method Not Allowed> 응답의 경우에는 Allow 헤더에 대한 value가 있어야 응답을 만들 수 있습니다. 저와 같은 방식으로 객체를 설계했다면 connection, status_code, headers, body를 인자로 받아야 합니다.

처리 흐름은 이렇습니다. CGI 실행 결과의 경우 body에 child process로부터 받은 응답 시작줄과 헤더가 섞여있기 때문에 파싱하여 헤더와 바디를 재조정하는 작업을 먼저 거칩니다. 에러 페이지의 경우 커스텀/디폴트 에러 페이지를 바디로 설정합니다. chunked encoding이 아닌 경우 body의 크기를 Content-Length에 설정하고, 에러 응답이냐 아니냐에 따라 Connection 값을 설정 close/keep-alive로 설정합니다. 리다이렉션 응답에 대해 패스가 설정되어 있지 않다면 루트로 잡아주고, HEAD 메소드일 경우 바디를 비웁니다.

상태 코드와 사유 구절을 status_map에 저장하고, response 클래스의 static 변수로 보관해두었던 것처럼 각 상태 코드에 따른 default header key & value 쌍도 static 변수로 보관해두고 편하게 꺼내 쓸 수도 있습니다.

Last updated