跳转至

link: 应用程序与 Nginx 的动态链接

安装

您可以在任何基于 RHEL 的发行版中安装此模块,包括但不限于:

  • RedHat Enterprise Linux 7、8、9 和 10
  • CentOS 7、8、9
  • AlmaLinux 8、9
  • Rocky Linux 8、9
  • Amazon Linux 2 和 Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-link
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install nginx-module-link

通过在 /etc/nginx/nginx.conf 的顶部添加以下内容来启用模块:

load_module modules/ngx_http_link_func_module.so;

本文档描述了 nginx-module-link v3.2.4,于 2020 年 10 月 13 日发布。


nginx link function Logo

nginx-link-function

应用程序与 Nginx 的动态链接

介绍

nginx-link-function 是一个 Nginx 模块,它为您的应用程序提供动态链接,允许在位置指令中调用您的应用程序的函数。您可以直接将 C/C++ 应用程序(或任何接受动态链接的其他应用程序)链接到 Nginx 服务器。

工作原理

nginx-link-function 的图像

用法

## nginx.conf

server {
  listen 8888;
  ...
  ngx_link_func_lib "/path/to/your/libcfuntest.so";
  ...
  ...
  location = /testCFunGreeting {
      ngx_link_func_call "my_app_simple_get_greeting"; 
  }
}

server {
  listen 8989;
  aio threads;
  ...
  ngx_link_func_lib "/path/to/your/libcfuntest.so"; # 如果路径与服务器 1 相同,则与服务器 1 共享数据内存
  ...
  ...
  location = /testCFunGreeting {
      ngx_link_func_call "my_app_simple_get_greeting" 
  }
}

server {
  listen 8999;
  aio threads;
  ...
  ngx_link_func_lib "/path/to/your/libcfuntest2.so"; # 另一个应用程序
  ...
  ...
  location = /testPost {
      add_header Allow "GET, POST, HEAD" always;
      if ( $request_method !~ ^(POST)$ ) {
        return 405;
      }
      ngx_link_func_call "my_2nd_app_simple_get_token";
  }
}

server {
  listen 9888;
  aio threads;
  ...
  ## 从云仓库下载应用程序,例如 ngx_link_func_download_link_lib <download_link> <dest_link_file>
  ngx_link_func_download_link_lib "http://abc.com/repos/libcfuntest.so" "/etc/nginx/libcfuntest3.so"
  ...
  ...
  location = /testPost {
      add_header Allow "GET, POST, HEAD" always;
      if ( $request_method !~ ^(POST)$ ) {
        return 405;
      }
      ngx_link_func_call "my_3rd_app_simple_get_token";
  }
}

server {
  listen 9898;
  aio threads;
  ...
  ## 从云仓库下载应用程序并附加额外头部,例如 ngx_link_func_download_link_lib <download_link> <headers> <dest_link_file>
  ngx_link_func_download_link_lib "https://abc.com/repos/libcfuntest.so" "Accept-Language:en_US\r\nAuthorization:Bearer KA.eyJ2ZXJzaadlasdlaldhjHJ2h3ldjklsjaklcjkljasdklcmasaskdaJxdkL3ftjM\r\n" "/etc/nginx/libcfuntest4.so"
  ...
  ...
  location = /testPost {
      add_header Allow "GET, POST, HEAD" always;
      if ( $request_method !~ ^(POST)$ ) {
        return 405;
      }
      ngx_link_func_call "my_other_app_simple_get_token";
  }
}

向客户端应用程序暴露的接口

这是您可以用来获取更多 Nginx 服务器详细信息的接口,所有内容都在 ngx_link_func_module.h 中。

#define ngx_link_func_content_type_plaintext "text/plain"
#define ngx_link_func_content_type_html "text/html; charset=utf-8"
#define ngx_link_func_content_type_json "application/json"
#define ngx_link_func_content_type_jsonp "application/javascript"
#define ngx_link_func_content_type_xformencoded "application/x-www-form-urlencoded"

typedef struct {
  char *req_args; // Uri 参数
  u_char *req_body; // 请求体
  size_t req_body_len; // 请求体长度
  void *shared_mem;

  /* 内部 */
  void* __r__;
  void* __pl__;
  void* __log__;
} ngx_link_func_ctx_t;

extern void ngx_link_func_log_debug(ngx_link_func_ctx_t *ctx, const char* msg);
extern void ngx_link_func_log_info(ngx_link_func_ctx_t *ctx, const char* msg);
extern void ngx_link_func_log_warn(ngx_link_func_ctx_t *ctx, const char* msg);
extern void ngx_link_func_log_err(ngx_link_func_ctx_t *ctx, const char* msg);
extern u_char* ngx_link_func_get_header(ngx_link_func_ctx_t *ctx, const char*key);
extern void* ngx_link_func_get_query_param(ngx_link_func_ctx_t *ctx, const char *key);
extern void* ngx_link_func_palloc(ngx_link_func_ctx_t *ctx, size_t size);
extern void* ngx_link_func_pcalloc(ngx_link_func_ctx_t *ctx, size_t size);

extern void ngx_link_func_write_resp(
    ngx_link_func_ctx_t *ctx,
    uintptr_t status_code,
    const char* status_line,
    const char* content_type,
    const char* resp_content,
    size_t resp_len
);


// 共享内存和缓存范围
extern void ngx_link_func_shmtx_lock(void *shared_mem);
extern void ngx_link_func_shmtx_unlock(void *shared_mem);
extern void* ngx_link_func_shm_alloc(void *shared_mem, size_t size);
extern void ngx_link_func_shm_free(void *shared_mem, void *ptr);
extern void* ngx_link_func_cache_get(void *shared_mem, const char* key);
extern void* ngx_link_func_cache_put(void *shared_mem, const char* key, void* value);
extern void* ngx_link_func_cache_new(void *shared_mem, const char* key, size_t size);
extern void ngx_link_func_cache_remove(void *shared_mem, const char* key);

接口细节分析

从 Nginx 池中分配内存

void* ngx_link_func_palloc(ngx_link_func_ctx_t *ctx, size_t size);
void* ngx_link_func_pcalloc(ngx_link_func_ctx_t *ctx, size_t size);

获取请求头参数

extern u_char* ngx_link_func_get_header(ngx_link_func_ctx_t *ctx, const char*key);

获取 URI 参数

ctx->req_args;

获取查询参数

extern void* ngx_link_func_get_query_param(ngx_link_func_ctx_t *ctx, const char *key);

获取请求体

ctx->req_body;

获取请求体长度

ctx->req_body_len;

日志记录到 Nginx 服务器

extern void ngx_link_func_log_debug(ngx_link_func_ctx_t *ctx, const char* msg);
extern void ngx_link_func_log_info(ngx_link_func_ctx_t *ctx, const char* msg);
extern void ngx_link_func_log_warn(ngx_link_func_ctx_t *ctx, const char* msg);
extern void ngx_link_func_log_err(ngx_link_func_ctx_t *ctx, const char* msg);

响应输出

extern void ngx_link_func_write_resp(
    ngx_link_func_ctx_t *ctx,
    uintptr_t status_code, // 状态码
    const char* status_line, // 状态行
    const char* content_type, // 响应内容类型
    const char* resp_content, // 响应内容
    size_t resp_len // 响应内容长度
);

示例应用程序开发

欢迎克隆示例项目

使用 cmake 的简单项目

链接函数 Google 指南 linkfunc-jwt-auth

#include <stdio.h>
#include <ngx_link_func_module.h>

/*** 将程序构建为 .so 库并复制到 Nginx 链接此库的首选位置 ***/
/*** gcc -shared -o libcfuntest.so -fPIC cfuntest.c ***/
/*** cp libcfuntest.so /etc/nginx/ ***/

int is_service_on = 0;

void ngx_link_func_init_cycle(ngx_link_func_cycle_t* cycle) {
    ngx_link_func_cyc_log(info, cycle, "%s", "启动应用程序");

    is_service_on=1;
}

void my_app_simple_get_greeting(ngx_link_func_ctx_t *ctx) {
    ngx_link_func_log_info(ctx, "从 my_app_simple_get 调用并记录");

    ngx_link_func_write_resp(
        ctx,
        200,
        "200 OK",
        "text/plain",
        "来自 ngx_link_func 测试的问候",
        sizeof("来自 ngx_link_func 测试的问候")-1
    );
}

void my_app_simple_get_args(ngx_link_func_ctx_t *ctx) {
    ngx_link_func_log_info(ctx, "从 my_app_simple_get_args 调用并记录");

    if(ctx->req_args) {
        ngx_link_func_write_resp(
            ctx,
            200,
            "200 OK",
            "text/plain",
            ctx->req_args,
            strlen(ctx->req_args)
        );
    } else {
        ngx_link_func_write_resp(
            ctx,
            204,
            "",
            "text/plain",
            NULL,
            0
        );
    }
}

void my_app_simple_get_token_args(ngx_link_func_ctx_t *ctx) {
    ngx_link_func_log_info(ctx, "从 my_app_simple_get_token_args 调用并记录");

    char * tokenArgs = (char*)ngx_link_func_get_query_param(ctx, "token");
    if (! tokenArgs) {
        ngx_link_func_write_resp(
            ctx,
            401,
            "401 未授权",
            "text/plain",
            "未找到令牌",
            sizeof("未找到令牌")-1
        );
    } else {
        ngx_link_func_write_resp(
            ctx,
            401,
            "401 未授权",
            "text/plain",
            tokenArgs,
            strlen(tokenArgs)
        );
    }
}

void my_app_simple_post(ngx_link_func_ctx_t *ctx) {
    ngx_link_func_log_info(ctx, "从 my_app_simple_post 调用并记录");

    ngx_link_func_write_resp(
        ctx,
        202,
        "202 已接受并正在处理",
        "text/plain",
        NULL,
        0
    );
}

void my_app_simple_get_no_resp(ngx_link_func_ctx_t *ctx) {
    ngx_link_func_log_info(ctx, "从 my_app_simple_get_no_resp 调用并记录");

    // 返回 404 
}

void ngx_link_func_exit_cycle(ngx_link_func_cycle_t* cyc) {
    ngx_link_func_cyc_log(info, cyc, "%s\n", "关闭/重新加载应用程序");

    is_service_on = 0;
}

注意:

初始化和退出周期的函数是保留函数,当 Nginx 启动时会调用初始化周期函数,当停止/重新加载 Nginx 时会调用退出周期函数。

void ngx_link_func_init_cycle(ngx_link_func_cycle_t* cyc){}
void ngx_link_func_exit_cycle(ngx_link_func_cycle_t* cyc){}
参考:应用层的初始化和退出周期钩子

日志级别

可以调用日志,记录的消息将存储在您在 nginx.conf 中配置的错误日志位置。

ngx_link_func_log_info(ctx, "这是信息直接消息");
ngx_link_func_log(info, ctx, "%s", "这是格式化的消息");
ngx_link_func_log_debug(ctx, "这是调试直接消息");
ngx_link_func_log(debug, ctx, "%s", "这是格式化的调试消息");

ngx_link_func_log_info(ctx, "%s", "这是格式化的消息"); // 格式错误
ngx_link_func_log_debug(ctx, "%s", "这是格式化的消息"); // 格式错误

提供 CA 证书以下载您的应用程序 (.so)?请将其嵌入到 nginx.conf 的服务器上下文中。

ngx_link_func_ca_cert "/etc/ssl/certs/ca-cert.crt"
ngx_link_func_download_link_lib "https://abc.com/repos/libcfuntest.so" "/etc/nginx/libcfuntest4.so"

提供 CA 证书和额外头部以下载您的应用程序 (.so)?请将其嵌入到 nginx.conf 的服务器上下文中。

ngx_link_func_ca_cert "/etc/ssl/certs/ca-cert.crt"
ngx_link_func_download_link_lib "https://abc.com/repos/libcfuntest.so" "Accept-Language:en_US\r\nAuthorization:Bearer KA.eyJ2ZXJzaadlasdlaldhjHJ2h3ldjklsjaklcjkljasdklcmasaskdaJxdkL3ftjM\r\n" "/etc/nginx/libcfuntest4.so"

测试

它依赖于 Nginx 测试套件库,请参考 test-nginx 进行安装。

cd /path/to/nginx-link-function
export PATH=/path/to/nginx-dirname:$PATH 
sudo prove -r t/

支持

请提出 问题,我会尽快修复。

或者,您可以发送电子邮件至 minikawoon2017@gmail.com

Wiki

查看 wiki 以获取更多详细信息。

GitHub

您可以在 nginx-module-link 的 GitHub 仓库 中找到此模块的其他配置提示和文档。