---
title: "OpenResty는 동적 요청과 응답을 지원하는 강화된 NGINX입니다. - API7.ai"
description: "OpenResty는 동적 요청과 응답을 지원하는 강화된 NGINX입니다. 이 글에서는 OpenResty에서 요청(Request)과 응답(Response)을 조작하기 위한 API들, 예를 들어 \"ngx.req.set_method\", \"ngx.req.set_uri_args\", \"ngx.req.set_header\" 등에 대해 설명합니다."
url: "https://api7.ai/ko/learning-center/openresty/handling-requests-responses-dynamically"
---

# OpenResty는 동적 요청과 응답을 지원하는 강화된 NGINX입니다.

API7.ai

October 23, 2022

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

이전의 소개를 통해 여러분은 OpenResty의 개념과 이를 학습하는 방법에 대해 이해하셨을 것입니다. 이 글에서는 OpenResty가 클라이언트 요청과 응답을 어떻게 처리하는지 안내하겠습니다.

OpenResty는 NGINX 기반의 웹 서버이지만, NGINX와는 근본적으로 다릅니다: NGINX는 정적 구성 파일에 의해 구동되는 반면, OpenResty는 Lua API에 의해 구동되어 더 많은 유연성과 프로그래밍 가능성을 제공합니다.

Lua API의 장점을 살펴보겠습니다.

## API 카테고리

먼저, OpenResty API는 다음과 같은 큰 범주로 나뉩니다.

- 요청 및 응답 처리.
- SSL 관련.
- 공유 딕셔너리.
- Cosocket.
- 4계층 트래픽 처리.
- 프로세스 및 워커.
- NGINX 변수 및 구성 접근.
- 문자열, 시간, 코덱 및 기타 일반 기능 등.

여기서 OpenResty의 Lua API 문서를 열어 [API 목록](https://github.com/openresty/lua-nginx-module/#nginx-api-for-lua)과 대조해 보시길 권장합니다. 이 카테고리와 관련이 있는지 확인해 보세요.

OpenResty API는 lua-nginx-module 프로젝트뿐만 아니라 lua-resty-core 프로젝트에도 존재합니다. 예를 들어 `ngx.ssl`, `ngx.base64`, `ngx.errlog`, `ngx.process`, `ngx.re.split`, `ngx.resp.add_header`, `ngx.balancer`, `ngx.semaphore`, `ngx.ocsp` 등의 API가 있습니다.

lua-nginx-module 프로젝트에 없는 API는 별도로 require 해야 사용할 수 있습니다. 예를 들어, split 함수를 사용하려면 다음과 같이 호출해야 합니다.

```
$ resty -e 'local ngx_re = require "ngx.re"
 local res, err = ngx_re.split("a,b,c,d", ",", nil, {pos = 5})
 print(res)
 '
```

물론, 혼란스러울 수 있습니다: `lua-nginx-module` 프로젝트에는 `ngx.re.sub`, `ngx.re.find` 등으로 시작하는 여러 API가 있는데, 왜 `ngx.re.split` API만 require를 먼저 해야 사용할 수 있을까요?

이전 `lua-resty-core` 챕터에서 언급했듯이, 새로운 OpenResty API는 `lua-rety-core` 저장소에서 FFI 방식으로 구현되어 있기 때문에, 어쩔 수 없이 조각난 느낌이 있습니다. 앞으로 `lua-nginx-module`과 `lua-resty-core` 프로젝트를 병합하여 이 문제를 해결할 수 있기를 기대합니다.

## 요청

다음으로, OpenResty가 클라이언트 요청과 응답을 어떻게 처리하는지 살펴보겠습니다. 먼저 요청을 처리하는 API를 보겠지만, `ngx.req`로 시작하는 API가 20개가 넘으니 어떻게 시작해야 할까요?

HTTP 요청 메시지는 요청 라인, 요청 헤더, 요청 본문으로 구성되므로, 이 세 부분에 따라 API를 소개하겠습니다.

### 요청 라인

먼저 요청 라인입니다. HTTP 요청 라인에는 요청 메서드, URI, HTTP 프로토콜 버전이 포함됩니다. NGINX에서는 내장 변수를 사용하여 이 값을 얻을 수 있지만, OpenResty에서는 `ngx.var.*` API에 해당합니다. 두 가지 예를 보겠습니다.

- 내장 변수 `$scheme`는 NGINX에서 프로토콜 이름을 나타내며, `http` 또는 `https`입니다; OpenResty에서는 `ngx.var.scheme`을 사용하여 동일한 값을 반환할 수 있습니다.
- `$request_method`는 `GET`, `POST` 등의 요청 메서드를 나타냅니다; OpenResty에서는 `ngx.var.request_method`를 통해 동일한 값을 반환할 수 있습니다.

NGINX 공식 문서를 방문하여 NGINX 내장 변수의 전체 목록을 확인할 수 있습니다: <http://nginx.org/en/docs/http/ngx_http_core_module.html#variables>.

그렇다면 질문이 생깁니다: `ngx.var.*`와 같은 변수 값을 반환하여 요청 라인의 데이터를 얻을 수 있는데, 왜 OpenResty는 요청 라인을 위한 별도의 API를 제공할까요?

이 결과에는 여러 요인이 있습니다:

- 먼저, `ngx.var`를 반복적으로 읽는 것은 성능상 비효율적이므로 권장되지 않습니다.
- 둘째, 프로그램 친화적인 측면에서 `ngx.var`는 문자열을 반환하며 Lua 객체가 아닙니다. `args`를 얻을 때 여러 값을 반환할 수 있으므로 처리하기 어렵습니다.
- 셋째, 유연성 측면에서 `ngx.var`는 대부분 읽기 전용이며, `$args`와 `limit_rate`와 같은 일부 변수만 쓰기가 가능합니다. 그러나 우리는 종종 메서드, URI, args를 수정해야 할 필요가 있습니다.

따라서 OpenResty는 요청 라인을 조작하기 위한 몇 가지 API를 제공하며, 이를 통해 요청 라인을 재작성하여 리디렉션과 같은 후속 작업을 수행할 수 있습니다.

HTTP 프로토콜 버전 번호를 API를 통해 얻는 방법을 살펴보겠습니다. OpenResty API `ngx.req.http_version`은 NGINX `$server_protocol` 변수와 동일한 작업을 수행합니다: HTTP 프로토콜의 버전 번호를 반환합니다. 그러나 이 API의 반환 값은 문자열이 아닌 숫자 형식이며, 가능한 값은 `2.0`, `1.0`, `1.1`, `0.9`입니다. 이 범위를 벗어나는 값은 `Nil`이 반환됩니다.

요청 라인에서 요청 메서드를 얻는 방법을 살펴보겠습니다. 앞서 언급했듯이, `ngx.req.get_method`와 NGINX의 `$request_method` 변수의 역할과 반환 값은 동일합니다: 문자열 형식입니다.

그러나 현재 HTTP 요청 메서드 `ngx.req.set_method`의 매개변수 형식은 문자열이 아닌 내장 숫자 상수입니다. 예를 들어, 다음 코드는 요청 메서드를 POST로 재작성합니다.

```
ngx.req.set_method(ngx.HTTP_POST)
```

내장 상수 `ngx.HTTP_POST`가 실제로 문자열이 아닌 숫자인지 확인하려면, 그 값을 출력하여 8이 나오는지 확인할 수 있습니다.

```
resty -e 'print(ngx.HTTP_POST)'
```

이렇게 하면 `get` 메서드의 반환 값은 문자열이고, `set` 메서드의 입력 값은 숫자입니다. `set` 메서드에 혼란스러운 값을 전달하면 API가 충돌하여 `500` 오류를 보고할 수 있습니다. 그러나 다음과 같은 판단 로직에서는:

```
if (ngx.req.get_method() == ngx.HTTP_POST) then
    -- do something
 end
```

이러한 코드는 오류를 보고하지 않으며, 코드 리뷰 중에도 발견하기 어렵습니다. 저도 비슷한 실수를 한 적이 있으며, 두 차례의 코드 리뷰와 불완전한 테스트 케이스를 통해 문제를 발견한 기억이 있습니다. 결국 온라인 환경에서의 이상 현상으로 문제를 추적할 수 있었습니다.

이러한 문제를 해결할 수 있는 실질적인 방법은 더 주의를 기울이거나 추가적인 캡슐화를 하는 것뿐입니다. 비즈니스 API를 설계할 때도 `get`과 `set` 메서드의 일관된 매개변수 형식을 고려하여 일부 성능을 희생하더라도 일관성을 유지하는 것이 좋습니다.

또한, 요청 라인을 재작성하는 방법 중에는 `ngx.req.set_uri`와 `ngx.req.set_uri_args`라는 두 가지 API가 있으며, 이를 통해 URI와 args를 재작성할 수 있습니다. 다음 NGINX 구성을 살펴보겠습니다.

```
rewrite ^ /foo?a=3? break;
```

그렇다면, 이와 동등한 Lua API로 어떻게 해결할 수 있을까요? 답은 다음 두 줄의 코드입니다.

```
ngx.req.set_uri_args("a=3")
ngx.req.set_uri("/foo")
```

공식 문서를 읽어보셨다면, `ngx.req.set_uri`에는 두 번째 매개변수인 `jump`가 있으며, 기본값은 "false"입니다. 이를 "true"로 설정하면 위 예제에서 `rewrite` 명령의 플래그를 `break` 대신 `last`로 설정하는 것과 같습니다.

그러나 저는 `rewrite` 명령의 플래그 구성이 가독성이 떨어지고 인식하기 어려우며, 코드보다 직관성과 유지보수성이 떨어진다고 생각합니다.

### 요청 헤더

HTTP 요청 헤더는 `key : value` 형식입니다. 예를 들어:

```
  Accept: text/css,*/*;q=0.1
  Accept-Encoding: gzip, deflate, br
```

OpenResty에서는 `ngx.req.get_headers`를 사용하여 요청 헤더를 파싱하고 얻을 수 있으며, 반환 값 유형은 테이블입니다.

```
local h, err = ngx.req.get_headers()
  if err == "truncated" then
      -- one can choose to ignore or reject the current request here
  end
  for k, v in pairs(h) do
      ...
  end
```

기본적으로 처음 100개의 헤더를 반환합니다. 100개를 초과하면 `truncated` 오류를 보고하며, 개발자가 이를 어떻게 처리할지 결정할 수 있습니다. 왜 이런 방식을 택했는지 궁금할 수 있는데, 이는 보안 취약점 섹션에서 언급하겠습니다.

그러나 주의할 점은 OpenResty가 특정 요청 헤더를 얻기 위한 특정 API를 제공하지 않는다는 것입니다. 즉, `ngx.req.header['host']`와 같은 형식이 없습니다. 이러한 필요가 있다면 NGINX 변수 `$http_xxx`에 의존해야 합니다. OpenResty에서는 `ngx.var.http_xxx`를 통해 이를 얻을 수 있습니다.

이제 요청 헤더를 재작성하고 삭제하는 방법을 살펴보겠습니다. 두 작업 모두 매우 직관적인 API입니다:

```
ngx.req.set_header("Content-Type", "text/css")
ngx.req.clear_header("Content-Type")
```

물론, 공식 문서에는 요청 헤더를 제거하는 다른 방법도 언급되어 있습니다. 예를 들어, 헤더 값을 `nil`로 설정하는 등입니다. 그러나 코드의 명확성을 위해 `clear_header`를 사용하는 것을 권장합니다.

### 요청 본문

마지막으로 요청 본문을 살펴보겠습니다. 성능상의 이유로 OpenResty는 요청 본문을 적극적으로 읽지 않습니다. `nginx.conf`에서 `lua_need_request_body` 지시문을 강제로 활성화하지 않는 한입니다. 또한, 큰 요청 본문의 경우 OpenResty는 내용을 디스크의 임시 파일에 저장하므로, 요청 본문을 읽는 전체 과정은 다음과 같습니다.

```
ngx.req.read_body()
local data = ngx.req.get_body_data()
if not data then
    local tmp_file = ngx.req.get_body_file()
     -- io.open(tmp_file)
     -- ...
 end
```

이 코드에는 디스크 파일을 읽는 IO 블로킹 작업이 포함되어 있습니다. `client_body_buffer_size`(64비트 시스템에서 기본값은 16KB)의 구성을 조정하여 블로킹 작업을 최소화해야 합니다; 또한 `client_body_buffer_size`와 `client_max_body_size`를 동일하게 구성하여 메모리에서 완전히 처리할 수도 있습니다. 이는 메모리 크기와 동시 요청 수에 따라 달라집니다.

또한, 요청 본문을 재작성할 수 있습니다. `ngx.req.set_body_data`와 `ngx.req.set_body_file` 두 API는 문자열과 로컬 디스크 파일을 입력 매개변수로 받아 요청 본문을 재작성합니다. 그러나 이러한 작업은 흔하지 않으며, 자세한 내용은 문서를 확인하시길 바랍니다.

## 응답

요청이 처리된 후, 클라이언트에게 응답을 보내야 합니다. 요청 메시지와 마찬가지로, 응답 메시지도 상태 라인, 응답 헤더, 응답 본문으로 구성됩니다. 이 세 부분에 따라 해당 API를 소개하겠습니다.

### 상태 라인

상태 라인에서 우리가 주로 관심을 갖는 것은 상태 코드입니다. 기본적으로 반환되는 HTTP 상태 코드는 200이며, OpenResty에 내장된 상수 `ngx.HTTP_OK`입니다. 그러나 코드 세계에서는 항상 예외적인 경우를 처리하는 코드가 많습니다.

요청 메시지를 검사하여 악의적인 요청임을 발견하면 요청을 종료해야 합니다:

```
ngx.exit(ngx.HTTP_BAD_REQUEST)
```

그러나 OpenResty의 HTTP 상태 코드에는 특별한 상수가 있습니다: `ngx.OK`. `ngx.exit(ngx.OK)` 상황에서 요청은 현재 처리 단계를 종료하고 다음 단계로 이동하며, 클라이언트에게 직접 반환되지 않습니다.

물론, 종료하지 않고 상태 코드를 재작성할 수도 있습니다. `ngx.status`를 사용하여 다음과 같이 작성할 수 있습니다.

```
ngx.status = ngx.HTTP_FORBIDDEN
```

상태 코드 상수에 대해 더 알고 싶다면 [문서](https://github.com/openresty/lua-nginx-module/#http-status-constants)를 참조하시길 바랍니다.

### 응답 헤더

응답 헤더에 대해 두 가지 방법으로 설정할 수 있습니다. 첫 번째 방법은 가장 간단합니다.

```
ngx.header.content_type = 'text/plain'
ngx.header["X-My-Header"] = 'blah blah'
ngx.header["X-My-Header"] = nil -- 삭제
```

여기서 `ngx.header`는 응답 헤더 정보를 담고 있으며, 읽기, 수정, 삭제가 가능합니다.

두 번째 방법은 `lua-resty-core` 저장소의 `ngx_resp.add_header`를 사용하여 헤더 메시지를 추가하는 것입니다. 다음과 같이 호출합니다:

```
local ngx_resp = require "ngx.resp"
ngx_resp.add_header("Foo", "bar")
```

첫 번째 방법과의 차이점은 `add_header`가 동일한 이름의 기존 필드를 덮어쓰지 않는다는 것입니다.

### 응답 본문

마지막으로 응답 본문을 살펴보겠습니다. OpenResty에서는 `ngx.say`와 `ngx.print`를 사용하여 응답 본문을 출력할 수 있습니다.

```
ngx.say('hello, world')
```

두 API의 기능은 동일하며, 유일한 차이점은 `ngx.say`가 끝에 줄 바꿈을 추가한다는 것입니다.

문자열 연결의 비효율성을 피하기 위해 `ngx.say / ngx.print`는 문자열과 배열 형식을 매개변수로 지원합니다.

```
$ resty -e 'ngx.say({"hello", ", ", "world"})'
 hello, world
```

이 방법은 Lua 수준에서의 문자열 연결을 건너뛰고 C 함수가 처리하도록 합니다.

## 요약

오늘의 내용을 다시 한번 살펴보겠습니다. 우리는 요청 및 응답 메시지와 관련된 OpenResty API를 소개했습니다. 보시다시피, OpenResty API는 NGINX 지시문보다 더 유연하고 강력합니다.

결론적으로, HTTP 요청을 처리할 때 OpenResty가 제공하는 Lua API가 여러분의 요구를 충족시키기에 충분한가요? 여러분의 의견을 남기고 이 글을 동료와 친구들과 공유하여 함께 소통하고 개선해 나가시길 바랍니다.

Share article link

Share

Related Posts

[![OpenResty FAQ | OpenResty의 실제 활용 사례](https://static.api7.ai/uploads/2023/02/12/jbv9M0zi_Hello%20World%20in%20OpenResty%20%283%29.png)](https://api7.ai/learning-center/openresty/faq-5)

## [OpenResty FAQ | OpenResty의 실제 활용 사례](https://api7.ai/learning-center/openresty/faq-5)

[![파트 3: OpenResty를 사용하여 마이크로서비스 API 게이트웨이 구축하기](https://static.api7.ai/uploads/2023/02/07/3XJXY9Jy_45.png)](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

## [파트 3: OpenResty를 사용하여 마이크로서비스 API 게이트웨이 구축하기](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

Table of Contents

- [API 카테고리](#api-%EC%B9%B4%ED%85%8C%EA%B3%A0%EB%A6%AC)

- [요청](#%EC%9A%94%EC%B2%AD)

  - [요청 라인](#%EC%9A%94%EC%B2%AD-%EB%9D%BC%EC%9D%B8)
  - [요청 헤더](#%EC%9A%94%EC%B2%AD-%ED%97%A4%EB%8D%94)
  - [요청 본문](#%EC%9A%94%EC%B2%AD-%EB%B3%B8%EB%AC%B8)

- [응답](#%EC%9D%91%EB%8B%B5)

  - [상태 라인](#%EC%83%81%ED%83%9C-%EB%9D%BC%EC%9D%B8)
  - [응답 헤더](#%EC%9D%91%EB%8B%B5-%ED%97%A4%EB%8D%94)
  - [응답 본문](#%EC%9D%91%EB%8B%B5-%EB%B3%B8%EB%AC%B8)

- [요약](#%EC%9A%94%EC%95%BD)
