๐ฌ HTTP Message Parsing
๊ฐ์โ
์ด ๋ฌธ์๋ Sprout Framework์ HTTP ๋ฉ์์ง ํ์ฑ ์์คํ
์ ๋ํ ๊ธฐ์ ์ ๋ถ์์ ์ ๊ณตํฉ๋๋ค. ์์ HTTP ์์ฒญ ํ
์คํธ๋ฅผ ๊ตฌ์กฐํ๋ HttpRequest
๊ฐ์ฒด๋ก ๋ณํํ๋ ํ์ฑ ํ์ดํ๋ผ์ธ์ ๋ด๋ถ ๊ตฌ์กฐ, ์๊ณ ๋ฆฌ์ฆ, ์ค๊ณ ๊ฒฐ์ ์ ๊ฒํ ํฉ๋๋ค.
ํ์ฑ ํ์ดํ๋ผ์ธ ์ํคํ ์ฒโ
์ ์ฒด ํ์ฑ ํ๋ฆโ
์์ HTTP ํ
์คํธ โ HttpRequestParser โ ๊ตฌ์กฐํ๋ HttpRequest ๊ฐ์ฒด
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ RequestLineParser โ HttpHeaderParser โ QueryStringParser โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๊ตฌ์ฑ ์์๋ณ ์ฑ ์ ๋ถ๋ฆฌโ
๊ฐ ํ์๋ HTTP ๋ฉ์์ง์ ํน์ ๋ถ๋ถ์ ๋ด๋นํ๋ ๋จ์ผ ์ฑ ์ ์์น์ ๋ฐ๋ฅด๋๋ก ์ค๊ณ ๋์์ต๋๋ค.
- HttpRequestParser: ์ ์ฒด ์กฐ์ ๋ฐ ๋ฉ์์ง ๋ถํ
- RequestLineParser: ์์ฒญ ๋ผ์ธ (๋ฉ์๋, ๊ฒฝ๋ก, HTTP ๋ฒ์ )
- HttpHeaderParser: HTTP ํค๋ ํ์ฑ
- QueryStringParser: URL ์ฟผ๋ฆฌ ์คํธ๋ง ํ๋ผ๋ฏธํฐ
ํต์ฌ ๊ตฌ์ฑ ์์ ๋ถ์โ
1. HttpRequestParser: ๋ง์คํฐ ์กฐ์ ์โ
๋ฉ์์ง ๋ถํ ์๊ณ ๋ฆฌ์ฆ
private String[] split(String raw) {
// 1. CRLF ๊ตฌ๋ถ์ ์ฐ์ ํ์ (\r\n\r\n)
int delimiterIdx = raw.indexOf("\r\n\r\n");
int delimiterLen = 4;
// 2. CRLF ์์ผ๋ฉด LF ๊ตฌ๋ถ์ ํ์ (\n\n)
if (delimiterIdx == -1) {
delimiterIdx = raw.indexOf("\n\n");
delimiterLen = 2;
}
// 3. ๊ตฌ๋ถ์ ๊ธฐ์ค์ผ๋ก ํค๋/๋ฐ๋ ๋ถํ
if (delimiterIdx != -1) {
return new String[]{
raw.substring(0, delimiterIdx), // ํค๋ + ์์ฒญ๋ผ์ธ
raw.substring(delimiterIdx + delimiterLen) // ๋ฐ๋
};
}
// 4. ๊ตฌ๋ถ์ ์์ผ๋ฉด ๋ฐ๋๋ ๋น ๋ฌธ์์ด
return new String[]{ raw, "" };
}
์ค๊ณ ๊ฒฐ์ ๋ถ์
- ๊ด๋ํ ๊ตฌ๋ถ์ ์ฒ๋ฆฌ: CRLF์ LF ๋ชจ๋ ์ง์ํ์ฌ ๋ค์ํ ํด๋ผ์ด์ธํธ์์ ํธํ ์ฑ ํ๋ณด
- ์กฐ๊ธฐ ์คํจ ๋ฐฉ์ง: ๊ตฌ๋ถ์๊ฐ ์์ด๋ ์ ์ฒด ์์ฒญ์ ํค๋๋ก ์ฒ๋ฆฌ
- ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ:
substring()
์ฌ์ฉ์ผ๋ก ๋ถํ์ํ ๋ณต์ฌ ์ต์ํ
ํ์ฑ ์กฐ์ ๋ก์ง
public HttpRequest<?> parse(String raw) {
String[] parts = split(raw);
String headerAndRequestLinePart = parts[0];
String bodyPart = parts[1];
// ์ฒซ ๋ฒ์งธ ๋ผ์ธ ์ถ์ถ (์์ฒญ ๋ผ์ธ)
String firstLine = headerAndRequestLinePart.split("\r?\n", 2)[0];
// ๊ฐ ํ์์ ์์
var rl = lineParser.parse(firstLine);
var query = qsParser.parse(rl.rawPath());
// ํค๋ ๋ถ๋ถ ์ถ์ถ ๋ฐ ํ์ฑ
String rawHeadersOnly = extractHeaders(headerAndRequestLinePart);
Map<String, String> headers = headerParser.parse(rawHeadersOnly);
return new HttpRequest<>(rl.method(), rl.cleanPath(), bodyPart, query, headers);
}
2. RequestLineParser: HTTP ์์ฒญ ๋ผ์ธ ํ์ฑโ
ํ์ฑ ์๊ณ ๋ฆฌ์ฆ
public RequestLine parse(String line) {
String[] parts = line.trim().split(" ", 3); // ์ต๋ 3๊ฐ ๋ถ๋ถ์ผ๋ก ๋ถํ
if (parts.length < 2) {
throw new BadRequestException(ExceptionMessage.BAD_REQUEST, ResponseCode.BAD_REQUEST);
}
HttpMethod method = HttpMethod.valueOf(parts[0].toUpperCase());
String rawPath = parts[1];
String cleanPath = rawPath.split("\\?")[0]; // ์ฟผ๋ฆฌ ์คํธ๋ง ์ ๊ฑฐ
return new RequestLine(method, rawPath, cleanPath);
}
ํต์ฌ ์ค๊ณ ํน์ง
- ์ ํ๋ ๋ถํ :
split(" ", 3)
๋ก HTTP ๋ฒ์ ์ ๊ณต๋ฐฑ์ด ์์ด๋ ์์ ํ๊ฒ ์ฒ๋ฆฌ - ๋์๋ฌธ์ ์ ๊ทํ: HTTP ๋ฉ์๋๋ฅผ ๋๋ฌธ์๋ก ํต์ผ
- ๊ฒฝ๋ก ์ ์ฒ๋ฆฌ: ์ฟผ๋ฆฌ ์คํธ๋ง์ ๋ถ๋ฆฌํ์ฌ clean path ์์ฑ
- ์ ํจ์ฑ ๊ฒ์ฆ: ์ต์ ์๊ตฌ์ฌํญ(๋ฉ์๋, ๊ฒฝ๋ก) ํ์ธ
์ฑ๋ฅ ์ต์ ํ
- ํ ๋ฒ์
split
ํธ์ถ๋ก ๋ชจ๋ ๋ถ๋ถ ์ถ์ถ trim()
์ผ๋ก ์ ํ/ํํ ๊ณต๋ฐฑ ์ ๊ฑฐ- ์์ธ ์ฒ๋ฆฌ๋ฅผ ํตํ ์กฐ๊ธฐ ์คํจ
3. HttpHeaderParser: HTTP ํค๋ ํ์ฑโ
์ ๊ท์ ๊ธฐ๋ฐ ํ์ฑ
private static final Pattern HEADER_PATTERN = Pattern.compile("^([^:]+):\\s*(.*)$");
public Map<String, String> parse(String rawHeaders) {
Map<String, String> headers = new HashMap<>();
String[] lines = rawHeaders.split("\r?\n"); // ๊ด๋ํ ๋ผ์ธ ๋ถ๋ฆฌ
for (String line : lines) {
if (line.isBlank()) continue; // ๋น ๋ผ์ธ ๊ฑด๋๋ฐ๊ธฐ
Matcher matcher = HEADER_PATTERN.matcher(line);
if (matcher.matches()) {
String key = matcher.group(1).trim();
String value = matcher.group(2).trim();
headers.put(key, value);
} else {
// ์ ํจํ์ง ์์ ํค๋ ํ์ ๊ฒฝ๊ณ
System.err.println("Warning: Invalid header format: " + line);
}
}
return headers;
}
์ ๊ท์ ํจํด ๋ถ์
^([^:]+)
: ์ฝ๋ก ์ด์ ์ ๋ชจ๋ ๋ฌธ์ (ํค๋ ์ด๋ฆ):\\s*
: ์ฝ๋ก ๊ณผ ์ ํ์ ๊ณต๋ฐฑ(.*)$
: ๋๋จธ์ง ๋ชจ๋ ๋ฌธ์ (ํค๋ ๊ฐ)
์ค๋ฅ ์ฒ๋ฆฌ ์ ๋ต
- ์ ์ฐํ ๋ผ์ธ ์ข
๋ฃ:
\r?\n
๋ก CRLF/LF ๋ชจ๋ ์ง์ - ๋น ๋ผ์ธ ๋ฌด์: HTTP ์คํ์ ๋ฐ๋ฅธ ๋น ๋ผ์ธ ๊ฑด๋๋ฐ๊ธฐ
- ๋ถ๋ถ์ ์คํจ ํ์ฉ: ์๋ชป๋ ํค๋๊ฐ ์์ด๋ ํ์ฑ ๊ณ์
- ๋๋ฒ๊น ์ง์: ์๋ชป๋ ํ์์ ๋ํ ๊ฒฝ๊ณ ๋ฉ์์ง
4. QueryStringParser: URL ํ๋ผ๋ฏธํฐ ํ์ฑโ
URL ๋์ฝ๋ฉ๊ณผ ํ๋ผ๋ฏธํฐ ์ถ์ถ
public Map<String,String> parse(String rawPath) {
Map<String,String> out = new HashMap<>();
String[] parts = rawPath.split("\\?", 2); // ๊ฒฝ๋ก์ ์ฟผ๋ฆฌ์คํธ๋ง ๋ถ๋ฆฌ
if (parts.length == 2) {
for (String token : parts[1].split("&")) { // ํ๋ผ๋ฏธํฐ๋ณ ๋ถํ
String[] kv = token.split("=", 2); // ํค=๊ฐ ๋ถํ
if (kv.length == 2) {
// ์ ์์ ์ธ ํค=๊ฐ ์
out.put(
URLDecoder.decode(kv[0], StandardCharsets.UTF_8),
URLDecoder.decode(kv[1], StandardCharsets.UTF_8)
);
} else if (kv.length == 1 && !kv[0].isEmpty()) {
// ๊ฐ ์๋ ํ๋ผ๋ฏธํฐ (์: ?flag&other=value)
out.put(URLDecoder.decode(kv[0], StandardCharsets.UTF_8), "");
}
}
}
return out;
}
URL ๋์ฝ๋ฉ ์ฒ๋ฆฌ
- ๋ฌธ์ ์ธ์ฝ๋ฉ: UTF-8 ๊ฐ์ ์ฌ์ฉ์ผ๋ก ์ผ๊ด์ฑ ๋ณด์ฅ
- ์์ ํ ๋์ฝ๋ฉ:
URLDecoder.decode()
์ฌ์ฉ - ๋น ๊ฐ ์ฒ๋ฆฌ: ๊ฐ์ด ์๋ ํ๋ผ๋ฏธํฐ๋ ์ง์
์ฃ์ง ์ผ์ด์ค ์ฒ๋ฆฌ
- ์ฟผ๋ฆฌ์คํธ๋ง ์์:
split
๊ฒฐ๊ณผ๊ฐ 1๊ฐ์ผ ๋ ๋น ๋งต ๋ฐํ - ๋น ํ๋ผ๋ฏธํฐ: ๋น ํค๋ ๋ฌด์, ๋น ๊ฐ์ ํ์ฉ
- ํน์ ๋ฌธ์: URL ์ธ์ฝ๋ฉ๋ ๋ฌธ์๋ค ์ฌ๋ฐ๋ฅด๊ฒ ๋์ฝ๋ฉ
๋ฐ์ดํฐ ๊ตฌ์กฐ์ ํ์ ์์ ์ฑโ
RequestLine Record ํ์ฉโ
public record RequestLine(HttpMethod method, String rawPath, String cleanPath) {}
Record ์ฌ์ฉ์ ์ฅ์
- ๋ถ๋ณ์ฑ: ์์ฑ ํ ์์ ๋ถ๊ฐ๋ฅํ ๋ฐ์ดํฐ ๊ตฌ์กฐ
- ์๋ ๊ตฌํ:
equals()
,hashCode()
,toString()
์๋ ์์ฑ - ํ์ ์์ ์ฑ: ์ปดํ์ผ ์๊ฐ์ ํ์ ๊ฒ์ฆ
- ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ: ์ต์ํ์ ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋
HttpRequest ๊ตฌ์กฐํโ
new HttpRequest<>(rl.method(), rl.cleanPath(), bodyPart, query, headers)
๊ตฌ์กฐํ๋ ๋ฐ์ดํฐ์ ์ด์
- ํ์ ์์ ์ฑ: ๊ฐ ํ๋๊ฐ ์ ์ ํ ํ์ ์ผ๋ก ๋งคํ
- ์ ๊ทผ ํธ์์ฑ: ๊ตฌ์กฐํ๋ ์ ๊ทผ์๋ฅผ ํตํ ๋ฐ์ดํฐ ์ ๊ทผ
- ํ์ฅ ๊ฐ๋ฅ์ฑ: ์ ๋ค๋ฆญ ํ์ ์ผ๋ก ๋ฐ๋ ํ์ ์ ์ฐ์ฑ
์ฑ๋ฅ ๋ถ์โ
์๊ฐ ๋ณต์ก๋โ
HttpRequestParser
- ์ ์ฒด ํ์ฑ: O(n) (n = ์์ ๋ฉ์์ง ๊ธธ์ด)
- ๋ฉ์์ง ๋ถํ : O(n)
- ๊ฐ ์๋ธํ์ ํธ์ถ: O(k) (k = ๊ฐ ์น์ ๊ธธ์ด)
๊ฐ๋ณ ํ์๋ค
- RequestLineParser: O(1) (๊ณ ์ ๋ ๋ถํ ํ์)
- HttpHeaderParser: O(h) (h = ํค๋ ๋ผ์ธ ์)
- QueryStringParser: O(p) (p = ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ ์)
๊ณต๊ฐ ๋ณต์ก๋โ
๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ ํจํด
- ์์ ๋ฌธ์์ด: ์ ๋ ฅ ํฌ๊ธฐ๋งํผ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ
- ์ค๊ฐ ๋ฐฐ์ด๋ค:
split()
์ฐ์ฐ์ผ๋ก ์์ ๋ฐฐ์ด ์์ฑ - ์ต์ข ๊ฐ์ฒด: ํ์ฑ๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ์ฒด๋ค
์ต์ ํ ๊ธฐ๋ฒ
substring()
์ฌ์ฉ์ผ๋ก ๋ฌธ์์ด ๋ณต์ฌ ์ต์ํ- ์ ๊ท์ ์ปดํ์ผ ์บ์ฑ (static Pattern)
- ์กฐ๊ธฐ ์ข ๋ฃ๋ก ๋ถํ์ํ ์ฒ๋ฆฌ ๋ฐฉ์ง
๋ฉ๋ชจ๋ฆฌ ํ ๋น ํจํดโ
// ํจ์จ์ ์ธ ๋ฌธ์์ด ์ฒ๋ฆฌ
String[] parts = rawPath.split("\\?", 2); // ์ต๋ 2๊ฐ๋ก ์ ํ
String cleanPath = rawPath.split("\\?")[0]; // ์ฒซ ๋ฒ์งธ ๋ถ๋ถ๋ง ์ฌ์ฉ
// ์ ๊ท์ ์ฌ์ฌ์ฉ
private static final Pattern HEADER_PATTERN = Pattern.compile("...");
์ค๋ฅ ์ฒ๋ฆฌ ๋ฐ ๋ณต์๋ ฅโ
๊ณ์ธตํ๋ ์ค๋ฅ ์ฒ๋ฆฌโ
๋ ๋ฒจ 1: ๊ตฌ๋ฌธ ์ค๋ฅ (Syntax Errors)
if (parts.length < 2) {
throw new BadRequestException(ExceptionMessage.BAD_REQUEST, ResponseCode.BAD_REQUEST);
}
๋ ๋ฒจ 2: ํ์ ๊ฒฝ๊ณ (Format Warnings)
System.err.println("Warning: Invalid header format detected: " + line);
๋ ๋ฒจ 3: ๊ด๋ํ ์ฒ๋ฆฌ (Lenient Processing)
if (line.isBlank()) continue; // ๋น ๋ผ์ธ ๋ฌด์
๋ณต์๋ ฅ ์๋ ํ์ฑโ
- ๋ถ๋ถ์ ์คํจ ํ์ฉ: ์ผ๋ถ ํค๋๊ฐ ์๋ชป๋์ด๋ ๋๋จธ์ง ํ์ฑ ๊ณ์
- ๊ธฐ๋ณธ๊ฐ ์ ๊ณต: ๋๋ฝ๋ ์์์ ๋ํ ํฉ๋ฆฌ์ ๊ธฐ๋ณธ๊ฐ
- ๋ค์ค ํ์ ์ง์: CRLF/LF ๋ผ์ธ ์ข ๋ฃ ๋ชจ๋ ์ง์
- ์ธ์ฝ๋ฉ ์์ ์ฑ: UTF-8 ๊ฐ์ ์ฌ์ฉ์ผ๋ก ๋ฌธ์ ์ธ์ฝ๋ฉ ๋ฌธ์ ๋ฐฉ์ง
HTTP ์คํ ์ค์๋โ
RFC 7230/7231 ์ค์ ์ฌํญโ
์์ฒญ ๋ผ์ธ ์ฒ๋ฆฌ:
- HTTP ๋ฉ์๋ ๋์๋ฌธ์ ์ฒ๋ฆฌ โ
- URI ๊ฒฝ๋ก ์ถ์ถ โ
- HTTP ๋ฒ์ ๋ฌด์ (๋จ์ํ) โ ๏ธ
ํค๋ ์ฒ๋ฆฌ:
- ํ๋๋ช :๊ฐ ํ์ โ
- ์ ํ/ํํ ๊ณต๋ฐฑ ์ ๊ฑฐ โ
- ๋น ๋ผ์ธ์ผ๋ก ํค๋ ์ข ๋ฃ โ
- ํค๋ ์ค๋ณต ์ฒ๋ฆฌ (๋ง์ง ๋ง ๊ฐ ์ฌ์ฉ) โ ๏ธ
๋ฉ์์ง ๋ฐ๋:
- Content-Length ๊ฒ์ฆ ์์ โ ๏ธ
- Transfer-Encoding ๋ฏธ์ง์ โ ๏ธ
๋จ์ํ๋ ๊ตฌํโ
Sprout์ ๊ต์ก/ํ์ต ๋ชฉ์ ์ ํ๋ ์์ํฌ๋ก์ HTTP ์คํ์ ํต์ฌ ๋ถ๋ถ๋ง ๊ตฌํ๋์ด ์์ต๋๋ค.
์ง์ ๊ธฐ๋ฅ
- ๊ธฐ๋ณธ์ ์ธ ์์ฒญ ๋ผ์ธ ํ์ฑ
- ํ์ค ํค๋ ํ์
- URL ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ
- UTF-8 ์ธ์ฝ๋ฉ
์๋์ ์ ์ธ
- HTTP/2, HTTP/3 ์ง์
- ์ฒญํฌ ์ ์ก ์ธ์ฝ๋ฉ
- ๋ณต์กํ ์ธ์ฆ ํค๋
- ๋ค์ค๊ฐ ํค๋ ์ฒ๋ฆฌ
ํ์ฅ์ฑ๊ณผ ์ ์ง๋ณด์์ฑโ
ํ์ ๊ต์ฒด ๊ฐ๋ฅ์ฑโ
์์กด์ฑ ์ฃผ์ ์ ํตํด ๊ฐ ํ์๋ฅผ ๋ ๋ฆฝ์ ์ผ๋ก ๊ต์ฒด ๊ฐ๋ฅํฉ๋๋ค.
public HttpRequestParser(RequestLineParser lineParser,
QueryStringParser qsParser,
HttpHeaderParser headerParser) {
// ๊ฐ ํ์๋ฅผ ๋
๋ฆฝ์ ์ผ๋ก ์ฃผ์
}
์๋ก์ด ํ์ฑ ๊ธฐ๋ฅ ์ถ๊ฐโ
ํ์ฅ ํฌ์ธํธ
- ์๋ก์ด ํ์ ์ถ๊ฐ: ๋ฐ๋ ํ์, ์ธ์ฝ๋ฉ ํ์ ๋ฑ
- ํ์ฑ ์ ๋ต ๋ณ๊ฒฝ: ์ ๊ท์ ๋์ ์ํ ๋จธ์ ํ์
- ๊ฒ์ฆ ๊ท์น ์ถ๊ฐ: ๋ ์๊ฒฉํ HTTP ์คํ ์ค์
- ์ฑ๋ฅ ์ต์ ํ: ์คํธ๋ฆฌ๋ฐ ํ์, ์ ๋ก์นดํผ ํ์ฑ
ํ ์คํธ ๊ฐ๋ฅ์ฑโ
๊ฐ ํ์๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก ํ ์คํธ ๊ฐ๋ฅํ ์์ ํจ์๋ก ์ค๊ณ๋์ด ์์ต๋๋ค.
// ๋จ์ ํ
์คํธ ์ฉ์ด์ฑ
@Test
void testValidRequestLine() {
RequestLine result = parser.parse("GET /path HTTP/1.1");
assertEquals(HttpMethod.GET, result.method());
assertEquals("/path", result.cleanPath());
}
๋ณด์ ๊ณ ๋ ค์ฌํญโ
์ ๋ ฅ ๊ฒ์ฆโ
ํ์ฌ ๋ณด์ ๋ฉ์ปค๋์ฆ
- ์ ๋ ฅ ํฌ๊ธฐ ์ ํ ์์ โ ๏ธ: DoS ๊ณต๊ฒฉ ๊ฐ๋ฅ์ฑ
- URL ๋์ฝ๋ฉ ์์ : ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ
- ์ ๊ท์ ๋ณด์: ๊ฐ๋จํ ํจํด์ผ๋ก ReDoS ์ํ ๋ฎ์
๋ณด์ ๊ฐ์ ์ฌ์
// ๊ถ์ฅ: ์
๋ ฅ ํฌ๊ธฐ ์ ํ
public HttpRequest<?> parse(String raw) {
if (raw.length() > MAX_REQUEST_SIZE) {
throw new RequestTooLargeException();
}
// ... ๊ธฐ์กด ๋ก์ง
}
์ธ์ ์ ๊ณต๊ฒฉ ๋ฐฉ์งโ
ํค๋ ์ธ์ ์
- ํ์ฌ: ๊ธฐ๋ณธ์ ์ธ ํ์ ๊ฒ์ฆ๋ง ์ํ
- ๊ฐ์ : CRLF ์ธ์ ์ ๊ฒ์ฆ ํ์
๊ฒฝ๋ก ์กฐ์
- ํ์ฌ: ๊ธฐ๋ณธ์ ์ธ URL ๋์ฝ๋ฉ
- ๊ฐ์ : ๊ฒฝ๋ก ์ํ ๊ณต๊ฒฉ ๋ฐฉ์ง ํ์
Sprout์ HTTP ํ์ฑ ์์คํ ์ ๊ต์ก์ ๋ชฉ์ ์ ์ ํฉํ ๋ช ํํ๊ณ ์ดํดํ๊ธฐ ์ฌ์ด ๊ตฌ์กฐ๋ฅผ ์ ๊ณตํฉ๋๋ค. ๊ฐ ํ์์ ๋จ์ผ ์ฑ ์ ์์น ์ค์, ์์กด์ฑ ์ฃผ์ ์ ํตํ ํ ์คํธ ๊ฐ๋ฅ์ฑ, ๊ทธ๋ฆฌ๊ณ ํฉ๋ฆฌ์ ์ธ ์ฑ๋ฅ ํน์ฑ์ ๋ณด์ฌ์ค๋๋ค.
๋ง์ฝ ๋ณด์ ๋ฐ ์คํ ์ค์์ ๋ํด ๊ธฐ์ฌ๋ฅผ ํ๊ณ ์ถ๋ค๋ฉด, ์ด์ ๋ฑ๋ก ํ ์์ ๊ฐ๋ฅํฉ๋๋ค.