Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/build_docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ done
platforms=${platforms::-1}
extra_tags=""

pre_release=`echo ${docker_tag}|egrep "(alpha|beta|rc|[T|t]est)"|wc -l`
pre_release=`echo ${docker_tag}|egrep "(alpha|[T|t]est)"|wc -l`
if [ ${pre_release} == 0 ]; then
extra_tags="-t ${docker_repository}/nginx:latest"
fi
Expand Down
61 changes: 61 additions & 0 deletions examples/ratelimit/nginx-ratelimit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
apiVersion: v1
kind: Service
metadata:
name: nginx-gateway
namespace: default
labels:
app: nginx-gateway
spec:
type: LoadBalancer
ports:
- port: 80
name: service-nginx
targetPort: 80
selector:
app: nginx-gateway
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
labels:
app: nginx-gateway
name: nginx-gateway
namespace: default
spec:
podManagementPolicy: OrderedReady
replicas: 2
selector:
matchLabels:
app: nginx-gateway
serviceName: nginx-gateway
template:
metadata:
labels:
app: nginx-gateway
spec:
containers:
- image: polarismesh/nginx:1.1.0-alpha.6
imagePullPolicy: Always
name: nginx-gateway
resources:
limits:
cpu: "500m"
memory: 1000Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
env:
- name: polaris_address
value: polaris.polaris-system:8091
- name: polaris_nginx_namespace
value: default
- name: polaris_nginx_service
value: nginx-gateway
- name: polaris_nginx_ratelimit_enable
value: "true"
restartPolicy: Always
updateStrategy:
rollingUpdate:
partition: 0
type: RollingUpdate

164 changes: 100 additions & 64 deletions source/nginx_polaris_limit_module/ngx_http_polaris_limit_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ static void split_query(std::string& s, std::map<std::string, std::string>& v);
static void parse_query_token(const std::string& token, std::map<std::string, std::string>& v);
static void get_labels_from_request(ngx_http_request_t* r, const std::set<std::string>*& label_keys,
std::map<std::string, std::string>& keyword_map);
static void join_set_str(const ngx_log_t *log, const std::set<std::string>*& label_keys, std::string& labels_str);
static void join_map_str(const ngx_log_t *log, const std::map<std::string, std::string>& labels, std::string& labels_str);
static void join_map_str(const std::map<std::string, std::string>& labels, std::string& labels_str);

static ngx_command_t ngx_http_polaris_limit_commands[] = {
{ ngx_string("polaris_rate_limiting"),
Expand Down Expand Up @@ -88,22 +87,28 @@ static ngx_int_t ngx_http_polaris_limit_handler(ngx_http_request_t *r) {
ngx_http_get_module_loc_conf(r, ngx_http_polaris_limit_module));

if (plcf->enable == 0) {
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[PolarisRateLimiting] RateLimit not enabled");
return NGX_DECLINED;
}
polaris::LimitApi* limit_api = Limit_API_SINGLETON.GetLimitApi();
polaris::LimitApi* limit_api = Limit_API_SINGLETON.GetLimitApi(r->connection->log);
if (NULL == limit_api) {
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[PolarisRateLimiting] RateLimit api not created");
return NGX_DECLINED;
}

polaris::ServiceKey serviceKey = {plcf->service_namespace, plcf->service_name};
std::string method = std::string(reinterpret_cast<char *>(r->uri.data), r->uri.len);
ret = Limit_API_SINGLETON.GetLimitApi()->FetchRuleLabelKeys(serviceKey, label_keys);
ret = limit_api->FetchRuleLabelKeys(serviceKey, label_keys);

if (ret != 0) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "[PolarisRateLimiting] fail to fetchRuleLabelKeys return is: %d", ret);
return NGX_DECLINED;
}

std::string labels_key_str;
if (label_keys != NULL) {
join_set_str(r->connection->log, label_keys, labels_key_str);
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[PolarisRateLimiting] FetchRuleLabelKeys labels count %d", label_keys->size());
}
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[PolarisRateLimiting] FetchRuleLabelKeys return is: %d, labels %s", ret, labels_key_str.c_str());

if (ret == polaris::kReturnTimeout) {
return NGX_DECLINED; // 拉取labelkey超时,不限流
} else if (ret != polaris::kReturnOk) {
Expand All @@ -117,12 +122,13 @@ static ngx_int_t ngx_http_polaris_limit_handler(ngx_http_request_t *r) {
quota_request.SetMethod(uri);
quota_request.SetLabels(labels); // 设置label用于匹配限流规则

std::string labels_values_str;
join_map_str(r->connection->log, labels, labels_values_str);
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"[PolarisRateLimiting] quota_request namespace %s, service %s, method %s, labels %s", plcf->service_namespace.c_str(), plcf->service_name.c_str(), uri.c_str(), labels_values_str.c_str());

ret = Limit_API_SINGLETON.GetLimitApi()->GetQuota(quota_request, result);
if (r->connection->log->log_level >= NGX_LOG_DEBUG) {
std::string labels_values_str;
join_map_str(labels, labels_values_str);
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"[PolarisRateLimiting] quota_request namespace %s, service %s, method %s, labels %s", plcf->service_namespace.c_str(), plcf->service_name.c_str(), uri.c_str(), labels_values_str.c_str());
}
ret = limit_api->GetQuota(quota_request, result);

ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[PolarisRateLimiting] GetQuota return is: %d", ret);
if (ret == polaris::kReturnTimeout) {
Expand All @@ -138,25 +144,12 @@ static ngx_int_t ngx_http_polaris_limit_handler(ngx_http_request_t *r) {
return NGX_DECLINED;
}

static void join_set_str(const ngx_log_t *log, const std::set<std::string>*& label_keys, std::string& labels_str) {
if (log->log_level < NGX_LOG_DEBUG) {
return;
}
for (std::set<std::string>::iterator it = label_keys->begin(); it != label_keys->end(); it++) {
labels_str += *it;
labels_str += " ";
}
}

static void join_map_str(const ngx_log_t *log, const std::map<std::string, std::string>& labels, std::string& labels_str) {
if (log->log_level < NGX_LOG_DEBUG) {
return;
}
static void join_map_str(const std::map<std::string, std::string>& labels, std::string& labels_str) {
for (std::map<std::string, std::string>::const_iterator it = labels.begin(); it != labels.end(); it++) {
labels_str += it->first;
labels_str += "=";
labels_str += it->second;
labels_str += " ";
labels_str += ",";
}
}

Expand Down Expand Up @@ -215,37 +208,38 @@ static char *ngx_http_polaris_limit_conf_set(ngx_conf_t *cf, ngx_command_t *cmd,
}
continue;
}

}

if (!has_namespace) {
char *namespace_env_value = getenv(ENV_NAMESPACE.c_str());
if (NULL != namespace_env_value) {
plcf->service_namespace = std::string(namespace_env_value);
} else {
plcf->service_namespace = DEFAULT_NAMESPACE;
}
ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, "[PolarisRateLimiting] use %s as nginx namespace", plcf->service_namespace.c_str());
}
if (!has_namespace) {
char *namespace_env_value = getenv(ENV_NAMESPACE.c_str());
if (NULL != namespace_env_value) {
plcf->service_namespace = std::string(namespace_env_value);
} else {
plcf->service_namespace = DEFAULT_NAMESPACE;
}
ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, "[PolarisRateLimiting] use %s as nginx namespace", plcf->service_namespace.c_str());
}

if (!has_service) {
char *service_env_value = getenv(ENV_SERVICE.c_str());
if (NULL != service_env_value) {
plcf->service_name = std::string(service_env_value);
} else {
plcf->service_name = DEFAULT_SERVICE;
}
ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, "[PolarisRateLimiting] use %s as nginx service name", plcf->service_name.c_str());
}
if (!has_service) {
char *service_env_value = getenv(ENV_SERVICE.c_str());
if (NULL != service_env_value) {
plcf->service_name = std::string(service_env_value);
} else {
plcf->service_name = DEFAULT_SERVICE;
}
ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, "[PolarisRateLimiting] use %s as nginx service name", plcf->service_name.c_str());
}

if (!has_enable) {
char *enable_env_value = getenv(ENV_RATELIMIT_ENABLE.c_str());
if (NULL != enable_env_value) {
std::string enable_str = std::string(enable_env_value);
plcf->enable = string2bool(enable_str) ? 1 : 0;
} else {
plcf->enable = 0;
}
ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, "[PolarisRateLimiting] use %V as nginx ratelimit enable", plcf->enable);
}
if (!has_enable) {
char *enable_env_value = getenv(ENV_RATELIMIT_ENABLE.c_str());
if (NULL != enable_env_value) {
std::string enable_str = std::string(enable_env_value);
plcf->enable = string2bool(enable_str) ? 1 : 0;
} else {
plcf->enable = 0;
}
ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, "[PolarisRateLimiting] use %d as nginx ratelimit enable", plcf->enable);
}

return static_cast<char *>(NGX_CONF_OK);
Expand Down Expand Up @@ -282,6 +276,8 @@ static void *ngx_http_polaris_limit_create_conf(ngx_conf_t *cf) {
}

conf->status_code = 429; // 限流默认返回429

Limit_API_SINGLETON.LoadPolarisConfig();
return conf;
}

Expand Down Expand Up @@ -418,18 +414,58 @@ const std::string defaultConfigContent = R"##(
serverConnector:
addresses:
- ${polaris_address}
rateLimiter:
rateLimitCluster:
namespace: Polaris
service: polaris.limiter
)##";

LimitApiWrapper::LimitApiWrapper() {
void LimitApiWrapper::Init(ngx_log_t *logger) {
ngx_log_error(NGX_LOG_NOTICE, logger, 0, "[PolarisRateLimiting] start to init polaris limit api, polaris config %s", m_polaris_config.c_str());
std::string err_msg("");
m_limit = polaris::LimitApi::CreateFromString(m_polaris_config, err_msg);
if (NULL == m_limit) {
ngx_log_error(NGX_LOG_ERR, logger, 0, "[PolarisRateLimiting] fail to create limit api, err: %s", err_msg.c_str());
} else {
ngx_log_error(NGX_LOG_NOTICE, logger, 0, "[PolarisRateLimiting] success to init polaris limit api");
}
m_created = true;
}

/// @brief 将文件内容读入字符串中
std::string readFileIntoString(const std::string& path) {
std::ifstream input_file(path);
return std::string((std::istreambuf_iterator<char>(input_file)), std::istreambuf_iterator<char>());
}

/// @brief 支持环境变量展开
static std::string expand_environment_variables( const std::string &s ) {
if( s.find( "${" ) == std::string::npos ) return s;

std::string pre = s.substr( 0, s.find( "${" ) );
std::string post = s.substr( s.find( "${" ) + 2 );

if( post.find( '}' ) == std::string::npos ) return s;

std::string variable = post.substr( 0, post.find( '}' ) );
std::string value = "";

post = post.substr( post.find( '}' ) + 1 );

const char *v = getenv( variable.c_str() );
if( v != NULL ) value = std::string( v );

return expand_environment_variables( pre + value + post );
}

void LimitApiWrapper::LoadPolarisConfig() {
std::string conf_path = get_polaris_conf_path();
std::string err_msg;
std::string content("");
if (exist_file(conf_path)) {
m_limit = polaris::LimitApi::CreateFromFile(conf_path, err_msg);
} else {
std::cout << "[polaris-limiter] config file " << conf_path << " not exists, create with default config" << std::endl;
m_limit = polaris::LimitApi::CreateFromString(defaultConfigContent, err_msg);
content = readFileIntoString(conf_path);
}
if (NULL == m_limit) {
std::cout << "[polaris-limiter] fail to create limit api, err: " << err_msg << std::endl;
if (content.size() == 0) {
content = defaultConfigContent;
}
m_polaris_config = expand_environment_variables(content);
}
32 changes: 27 additions & 5 deletions source/nginx_polaris_limit_module/ngx_http_polaris_limit_module.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ extern "C" {
#include <fstream>
#include <cstring>
#include <cstdlib>
#include <mutex>

static const char KEY_ENABLE[] = "enable";
static const uint32_t KEY_ENABLE_SIZE = sizeof(KEY_ENABLE) - 1;
Expand All @@ -35,9 +36,9 @@ static const uint32_t KEY_NAMESPACE_SIZE = sizeof(KEY_NAMESPACE) - 1;
static const char KEY_SERVICE_NAME[] = "service=";
static const uint32_t KEY_SERVICE_NAME_SIZE = sizeof(KEY_SERVICE_NAME) - 1;

static const std::string ENV_NAMESPACE = "polaris.nginx.namespace";
static const std::string ENV_SERVICE = "polaris.nginx.service";
static const std::string ENV_RATELIMIT_ENABLE = "polaris.nginx.ratelimit.enable";
static const std::string ENV_NAMESPACE = "polaris_nginx_namespace";
static const std::string ENV_SERVICE = "polaris_nginx_service";
static const std::string ENV_RATELIMIT_ENABLE = "polaris_nginx_ratelimit_enable";

static const std::string DEFAULT_NAMESPACE = "default";
static const std::string DEFAULT_SERVICE = "nginx-gateway";
Expand All @@ -50,17 +51,38 @@ static const std::string PATH_SBIN = "sbin";

class LimitApiWrapper {
public:
LimitApiWrapper();

LimitApiWrapper() {
m_created = false;
}

void LoadPolarisConfig();

void Init(ngx_log_t *ngx_log);

static LimitApiWrapper& Instance() {
static LimitApiWrapper limit_api;
return limit_api;
}

polaris::LimitApi* GetLimitApi() { return m_limit; }
polaris::LimitApi* GetLimitApi(ngx_log_t *ngx_log) {
if (m_created) {
return m_limit;
}
m_mtx.lock();
if (m_created) {
return m_limit;
}
Init(ngx_log);
m_mtx.unlock();
return m_limit;
}

private:
polaris::LimitApi* m_limit;
std::string m_polaris_config;
std::mutex m_mtx;
bool m_created;
};

#define Limit_API_SINGLETON LimitApiWrapper::Instance()
Expand Down