# 구현 | 요청 읽기

## 제어구조

파싱 및 유효성 검사 과정에 대해서는 따로 설명하지 않습니다. 한 가지만 꼽아 말씀을 드린다면, 요청 메시지의 각 줄 끝을 "\r\n"이 아니라 "\n"으로 다룬다면 파싱할 때 문제가 생길 수 있습니다. 그 외에는 리퀘스트를 읽는 과정에서 발생할 수 있는 에러의 수가 굉장히 많기 때문에, 다양한 상태 코드에 대해 학습하면서 어떤 에러가 발생할 수 있는지 고민해보시기를 바랍니다.&#x20;

리퀘스트를 읽는 과정에서 문제가 있다면 에러 상태 코드(int)를 throw하는데, 이 경우 해당 상태 코드로 바로 응답을 생성합니다. 문제가 없는 경우에만 URL과 method를 참고하여 요청을 처리하고 거기에 맞는 응답을 생성하는 것이죠.

{% hint style="danger" %}
리퀘스트를 읽는 과정에서 recv든 read든 read operation을 하는 함수가 0 또는 -1을 리턴한다면 에러로 간주하고 Client를 제거해야 합니다. 에러 응답도 보내지 않습니다.
{% endhint %}

```cpp
bool
Server::runRecvAndSolve(Connection& connection)
{
	try {
		recvRequest(connection, connection.get_m_request());
	} catch (int status_code) {
		createResponse(connection, status_code);
		return (true);
	} catch (Server::IOError& e) {
		throw (e);
	} catch (std::exception& e) {
		ft::log(ServerManager::log_fd, std::string("[Failed][Request] Failed to create request because ") + e.what());
		createResponse(connection, 50001);
		return (true);
	}
	const Request& request = connection.get_m_request();
	if (request.get_m_phase() == Request::COMPLETE)
	{
		writeCreateNewRequestLog(request);
		connection.set_m_status(Connection::ON_EXECUTE);
		solveRequest(connection, connection.get_m_request());
		return (true);
	}
	return (false);
}
```

## Custom 상태 코드

에러 발생시 상태코드를 throw할 때, 아래와 같이 상태코드 뒤에 2자리 수를 더 붙여 해당 에러가 발생한 원인이나 위치를 로그로 남기면 유용합니다. 실제로 응답을 보낼 때는 앞의 3자리만 잘라서 보내면 되고, status\_map에 3자리 상태코드에 대한 사유구절도 모두 저장되어 있기 때문에 문제가 없습니다.

```cpp
status_map[40022] = "Bad Request: Credential Form unvalid";
status_map[40023] = "Bad Request: Not CGI-prgoram, POST method, Content-Length is not 0";
status_map[40101] = "Unauthorized";
status_map[40301] = "Forbidden: Credential Content unvalid";    
status_map[40401] = "Not Found: No suitable location";
```

## Enum

Enum을 활용하면 효율적인 프로그래밍이 가능합니다.

```cpp
	enum Method { DEFAULT, GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE };
	enum URIType { DIRECTORY, FILE, FILE_TO_CREATE, CGI_PROGRAM };
	enum TransferType { GENERAL, CHUNKED };
	enum Phase { READY, ON_HEADER, ON_BODY, COMPLETE };
```

## Chunked Transfer-Encoding

바디가 있는 리퀘스트의 경우 클라이언트가 바디를 전송해오는 방식에는 2가지가 있습니다. 하나는 Content-Length가 설정된 경우로, 그 값만큼만 body를 읽으면 됩니다. 하지만 대용량 데이터의 경우 한 번에 그렇게 많은 양을 읽을 수 없으므로 나누어서 보내게 되는데, 이 때에는 Transfer-Encdoing 헤더의 value가 chuked로 오게 됩니다.

![](/files/-MKR3_zdLqegQDCbsucw)

위와 같이 얼마만큼의 길이를 보낼 것인지 토큰을 한 번 보내고, 그 숫자만큼의 메시지를 보내고, 또 토큰을 보내고, 토큰의 숫자만큼 메시지를 보내는 과정을 반복합니다. 0 토큰이 종료 신호로 읽히며, raw data로 보면 "0\r\n\r\n"으로 종료됩니다.

{% hint style="warning" %}
토큰은 16진수로 옵니다. 변환을 위한 stoi 함수는 c++11 문법인데,  98을 지키라는 내용이 서브젝트에 없긴 하지만 저희는 98로 작성하기 위해 stoi, to\_string과 같은 함수들도 모두 만들어서 사용했습니다.
{% endhint %}

{% hint style="warning" %}
"0\r\n\r\n"이 body에 존재한다고 무조건 끝인 것은 아닙니다. 앞 토큰의 숫자 범위에 이 데이터가 포함되는 경우, 이 데이터는 종료 토큰이 아니라 바디의 일부로 읽히기 때문입니다. 이미지 데이터의 경우 이런 패턴이 많이 나오기 때문에 조심해서 읽어야 합니다.
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://42seoul.gitbook.io/webserv/or-read-request.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
