---
title: "OpenResty에서 일반적으로 사용되는 API 소개 - API7.ai"
description: "이 글에서는 OpenResty의 일반적인 API를 사용하여 시간 조작, 정규 표현식 및 기타 일반적인 문제를 다루는 방법을 소개합니다."
url: "https://api7.ai/ko/learning-center/openresty/introduction-openresty-common-apis"
---

# OpenResty에서 일반적으로 사용되는 API 소개

API7.ai

November 4, 2022

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

이전 글들에서 여러분은 OpenResty의 중요한 Lua API들에 대해 익숙해졌을 것입니다. 오늘은 정규 표현식, 시간, 프로세스 등과 관련된 몇 가지 일반적인 API들에 대해 배워보겠습니다.

## 정규 표현식 관련 API

가장 흔히 사용되고 중요한 정규 표현식부터 살펴보겠습니다. OpenResty에서는 Lua 패턴 매칭 대신 `ngx.re.*`에서 제공하는 API 세트를 사용하여 정규 표현식 관련 로직을 처리해야 합니다. 이는 성능상의 이유뿐만 아니라 Lua 정규 표현식이 자체적으로 포함되어 있고 `PCRE` 사양이 아니기 때문에 대부분의 개발자에게는 번거로울 수 있기 때문입니다.

이전 글들에서 이미 `ngx.re.*` API 중 일부를 접해보았고, 그 문서는 매우 상세합니다. 따라서 여기서는 두 가지 API를 별도로 소개하겠습니다.

### `ngx.re.split`

첫 번째는 `ngx.re.split`입니다. 문자열 자르기는 매우 일반적인 기능이며, OpenResty도 이에 해당하는 API를 제공하지만 많은 개발자들이 이를 찾지 못하고 직접 구현하는 경우가 많습니다.

왜냐하면 `ngx.re.split` API는 `lua-nginx-module`에 있지 않고 `lua-resty-core`에 있으며, `lua-resty-core` 홈페이지 문서가 아닌 `lua-resty-core/lib/ngx/re.md` 세 번째 디렉토리의 문서에 있기 때문입니다. 결과적으로 많은 개발자들이 이 API의 존재를 전혀 모르고 있습니다.

마찬가지로 발견하기 어려운 API로는 `ngx_resp.add_header`, `enable_privileged_agent` 등이 있으며, 이전에 언급한 바 있습니다. 그렇다면 이 문제를 어떻게 빠르게 해결할 수 있을까요? `lua-resty-core` 홈페이지 문서를 읽는 것 외에도 `lua-resty-core/lib/ngx/` 디렉토리의 `*.md` 문서를 꼼꼼히 읽어보는 것이 필요합니다.

### `lua_regex_match_limit`

두 번째로 소개할 것은 `lua_regex_match_limit`입니다. 이전에는 OpenResty가 제공하는 NGINX 명령어에 대해 다루지 않았는데, 대부분의 경우 기본값으로 충분하며 런타임에 이를 수정할 필요가 없기 때문입니다. 예외는 정규 표현식과 관련된 `lua_regex_match_limit` 명령어입니다.

백트래킹 NFA 기반으로 구현된 정규 엔진을 사용하면 Catastrophic Backtracking의 위험이 있습니다. 이는 정규 표현식이 매칭 시 너무 많은 백트래킹을 하여 CPU가 100%가 되고 서비스가 차단되는 상황을 말합니다.

일단 Catastrophic Backtracking이 발생하면 `gdb`를 사용하여 덤프를 분석하거나 `systemtap`을 사용하여 온라인 환경을 분석하여 위치를 파악해야 합니다. 불행히도 이를 사전에 감지하는 것은 쉽지 않습니다. 특수한 요청만이 이를 유발하기 때문입니다. 이는 공격자가 이를 악용할 수 있게 하며, `ReDoS`(RegEx Denial of Service)는 이러한 유형의 공격을 말합니다.

여기서는 OpenResty에서 다음과 같은 코드를 사용하여 위의 문제를 간단하고 효과적으로 피하는 방법을 소개하겠습니다:

`lua_regex_match_limit`은 `PCRE` 정규 엔진의 백트래킹 횟수를 제한하는 데 사용됩니다. 이렇게 하면 Catastrophic Backtracking이 발생하더라도 그 결과가 CPU를 가득 차게 하지 않는 범위로 제한됩니다.

```
lua_regex_match_limit 100000;
```

## 시간 관련 API

가장 흔히 사용되는 시간 API는 `ngx.now`로, 현재 타임스탬프를 출력합니다. 예를 들어 다음과 같은 코드가 있습니다:

```
resty -e 'ngx.say(ngx.now())'
```

출력 결과에서 볼 수 있듯이, `ngx.now`는 소수 부분을 포함하므로 더 정확합니다. 관련된 `ngx.time` API는 값의 정수 부분만 반환합니다. 다른 API들인 `ngx.localtime`, `ngx.utctime`, `ngx.cookie_time`, `ngx.http_time`은 주로 다양한 형식의 시간을 반환하고 처리하는 데 사용됩니다. 이를 사용하려면 문서를 확인하면 되며, 이해하기 어렵지 않으므로 여기서는 다루지 않겠습니다.

그러나 주목할 점은 **현재 시간을 반환하는 이러한 API들은 비차단 네트워크 IO 작업에 의해 트리거되지 않으면 항상 캐시된 값을 반환하며, 우리가 원하는 현재 실시간 시간을 반환하지 않는다**는 것입니다. 다음 샘플 코드를 살펴보세요:

```
$ resty -e 'ngx.say(ngx.now())
os.execute("sleep 1")
ngx.say(ngx.now())'
```

`ngx.now`를 두 번 호출하는 사이에 Lua의 차단 함수를 사용하여 `1`초 동안 잠을 잤지만, 두 경우 모두 동일한 타임스탬프가 반환됩니다.

그렇다면 이를 비차단 sleep 함수로 대체하면 어떻게 될까요? 예를 들어 다음과 같은 새로운 코드입니다:

```
$ resty -e 'ngx.say(ngx.now())
ngx.sleep(1)
ngx.say(ngx.now())'
```

이 코드는 서로 다른 타임스탬프를 출력합니다. 이는 `ngx.sleep`이라는 비차단 sleep 함수를 사용하기 때문입니다. 이 함수는 지정된 시간 동안 잠을 자는 것 외에도 다른 특별한 용도가 있습니다.

예를 들어, 집중적인 계산을 수행하는 코드가 있어 많은 시간이 걸리는 경우, 이 코드에 해당하는 요청은 이 시간 동안 worker와 CPU 리소스를 계속 점유하게 되어 다른 요청들이 대기열에 쌓이고 제때 응답을 받지 못하게 됩니다. 이때 `ngx.sleep(0)`을 삽입하여 이 코드가 제어권을 포기하도록 하여 다른 요청들도 처리될 수 있게 할 수 있습니다.

## Worker 및 프로세스 API

OpenResty는 `ngx.worker.*`와 `ngx.process.*` API를 제공하여 worker와 프로세스에 대한 정보를 얻을 수 있습니다. 전자는 Nginx worker 프로세스와 관련이 있으며, 후자는 일반적으로 모든 Nginx 프로세스를 말하며, worker 프로세스뿐만 아니라 master 프로세스, privileged 프로세스 등을 포함합니다.

## `true`와 `null` 값 문제

마지막으로 `true`와 `null` 값 문제를 살펴보겠습니다. OpenResty에서 `true` 값과 `null` 값의 판단은 매우 번거롭고 혼란스러운 점이었습니다.

Lua에서 `true` 값의 정의를 살펴보겠습니다: `nil`과 `false`를 제외한 모든 값이 `true` 값입니다.

따라서 `true` 값에는 `0`, 빈 `string`, 빈 `table` 등도 포함됩니다.

Lua에서 `nil`은 `undefined`를 의미합니다. 예를 들어, 변수를 선언했지만 초기화하지 않았다면 그 값은 `nil`입니다.

```
$ resty -e 'local a
ngx.say(type(a))'
```

그리고 `nil`은 Lua에서 데이터 타입이기도 합니다. 이 두 가지를 이해했다면, 이제 이 두 정의에서 파생된 다른 문제들을 살펴보겠습니다.

### `ngx.null`

첫 번째 문제는 `ngx.null`입니다. Lua의 `nil`은 `table`의 값으로 사용할 수 없기 때문에, OpenResty는 `ngx.null`을 도입하여 테이블의 `null` 값으로 사용합니다.

```
$ resty -e  'print(ngx.null)'
null
```

```
$ resty -e 'print(type(ngx.null))'
userdata
```

위의 두 코드에서 볼 수 있듯이, `ngx.null`은 `null`로 출력되며, 그 타입은 `userdata`입니다. 그렇다면 이를 `false` 값으로 간주할 수 있을까요? 물론 아닙니다. `ngx.null`의 불리언 값은 `true`입니다.

```
$ resty -e 'if ngx.null then
ngx.say("true")
end'
```

따라서, `nil`과 `false`만이 `false` 값이라는 점을 기억하세요. 이 점을 놓치면 쉽게 함정에 빠질 수 있습니다. 예를 들어, `lua-resty-redis`를 사용할 때 다음과 같은 판단을 할 때:

```
local res, err = red:get("dog")
if not res then
    res = res + "test"
end
```

반환 값 `res`가 `nil`이면 함수 호출이 실패한 것이고, `res`가 `ngx.null`이면 redis에 `dog` 키가 존재하지 않는 것입니다. 이 경우 `dog` 키가 존재하지 않으면 코드가 충돌합니다.

### `cdata:NULL`

두 번째 문제는 `cdata:NULL`입니다. LuaJIT FFI 인터페이스를 통해 C 함수를 호출할 때, 함수가 `NULL` 포인터를 반환하면 `cdata:NULL`이라는 또 다른 종류의 `null` 값을 만나게 됩니다.

```
$ resty -e 'local ffi = require "ffi"
local cdata_null = ffi.new("void*", nil)
if cdata_null then
    ngx.say("true")
end'
```

`ngx.null`과 마찬가지로, `cdata:NULL`도 `true`입니다. 그러나 더 혼란스러운 점은 다음 코드가 `true`를 출력한다는 것입니다. 이는 `cdata:NULL`이 `nil`과 동등하다는 것을 의미합니다.

```
$ resty -e 'local ffi = require "ffi"
local cdata_null = ffi.new("void*", nil)
ngx.say(cdata_null == nil)'
```

그렇다면 `ngx.null`과 `cdata:NULL`을 어떻게 처리해야 할까요? 애플리케이션 레이어에서 이러한 문제를 신경 쓰는 것은 좋은 해결책이 아닙니다. 차라리 이차 래퍼를 만들어 호출자가 이러한 세부 사항을 알지 못하게 하는 것이 좋습니다.

### `cjson.null`

마지막으로 `cjson`에서 나타나는 `null` 값을 살펴보겠습니다. `cjson` 라이브러리는 json의 `NULL`을 Lua `lightuserdata`로 디코딩하며, `cjson.null`로 표현합니다.

```
$ resty -e 'local cjson = require "cjson"
local data = cjson.encode(nil)
local decode_null = cjson.decode(data)
ngx.say(decode_null == cjson.null)'
```

Lua의 `nil`은 JSON으로 인코딩되고 디코딩된 후 `cjson.null`이 됩니다. 이는 `ngx.null`과 같은 이유로 도입된 것으로, `nil`이 `table`의 값으로 사용될 수 없기 때문입니다.

지금까지 OpenResty에서 이렇게 많은 종류의 `null` 값들에 대해 혼란스러웠나요? 걱정하지 마세요. 이 부분을 몇 번 더 읽고 스스로 정리해보면 혼란스럽지 않을 것입니다. 물론, 앞으로 `if not foo then`과 같은 코드를 작성할 때 작동하는지 더 생각해봐야 합니다.

## 요약

오늘의 글에서는 OpenResty에서 흔히 사용되는 Lua API들을 소개했습니다.

마지막으로, `ngx.now` 예제에서 `yield` 작업이 없을 때 `ngx.now`의 값이 수정되지 않는 이유는 무엇인지 질문을 남기겠습니다. 여러분의 의견을 댓글로 공유해주시고, 이 글을 공유하여 함께 소통하고 발전할 수 있기를 바랍니다.

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](#%EC%A0%95%EA%B7%9C-%ED%91%9C%ED%98%84%EC%8B%9D-%EA%B4%80%EB%A0%A8-api)

  - [`ngx.re.split`](#ngxresplit)
  - [`lua_regex_match_limit`](#lua_regex_match_limit)

- [시간 관련 API](#%EC%8B%9C%EA%B0%84-%EA%B4%80%EB%A0%A8-api)

- [Worker 및 프로세스 API](#worker-%EB%B0%8F-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-api)

- [`true`와 `null` 값 문제](#true%EC%99%80-null-%EA%B0%92-%EB%AC%B8%EC%A0%9C)

  - [`ngx.null`](#ngxnull)
  - [`cdata:NULL`](#cdatanull)
  - [`cjson.null`](#cjsonnull)

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