用 Java 扩展 Nginx(nginx-clojure 入门实战)

简介: 让 Java 代码直接在 Nginx 上运行?这么有趣的功能,随本文一起来实战体验吧,图文并茂,一定能成功的那种实战

本篇概览

  • 今天咱们以 Java 程序员的视角,来聊聊如何用 Java 来扩展 Nginx 的能力,全文由以下内容组成:
  1. 什么是 Nginx
  2. 什么是 Nginx 扩展
  3. 扩展 Nginx,意义何在?
  4. 想用 Java 扩展 Nginx 会有问题吗?
  5. 主角登场:nginx-clojure 模块,让 Java 扩展 Nginx 成为显示
  • 总的来说这是一篇从概念到实践的入门文章,接下来,一起开始愉快的 nginx-clojure 之旅吧!

关于 Nginx

  • 以欣宸自己为例,nginx 对 java 程序员来说并不陌生,下图应该是最常见的使用场景了:反向代理

image.png

关于 Nginx 扩展

  • 除了反向代理,nginx 的模块化机制让 nginx 具备了更多丰富的特性,例如 ngx_http_log_module(日志)、ngx_http_rewrite_module(重定向)等
  • 除了官方模块,还有很多强大第三方模块可以选择,如下图,更多详情参考:https://www.nginx.com/resources/wiki/modules/

image.png

  • 大名鼎鼎的 OpenResty 就是基于 Nginx 扩展的,用 Lua 做二次开发 web 功能,得到了广泛的应用
  • 如此看来,在 nginx 上做二次开发并非神秘高深莫测,我们也可以按照自己的需要去定制和扩展 nginx 的功能
  • 作为一名 Java 程序员,自然就想到了:可以用 Java 去扩展 Nginx 的能力吗?

为什么要用 java 对 nginx 扩展?

  • 把 java 代码写在 nginx 上,让 nginx 与业务耦合的可能性变大,经验丰富的您一定会发现这是个危险的趋势,毕竟 nginx 已经接近后台服务的最外层了,稳定是首要考虑的,正常情况下,下图这种用法才是生产环境的常见 fang 方案:将 nginx 作为反向代理,业务功能独立部署

image.png

  • 这么看来,在 nginx 上写 java 代码是否合适呢?欣宸给不出权威答案,但是可以从大神的作品中得到启示
  • 开涛大神的《跟我学Nginx+Lua开发》讲述了如何用 OpenResty 在 Nginx 上用 Lua 进行开发,适用于 web 应用、接入网关、Web 防火墙、缓存服务器等场景下,并且在实体书《亿级流量网站架构核心技术》也有详细说明,例如京东商品详情页,就是在 nginx 上读取缓存直接返回的
  • 综上所述,欣宸不会因为学习了这个技术,就把业务逻辑全部在 nginx 上实现,但是在缓存、鉴权等场景,可以考虑用熟悉的 java 在 nginx 上实现

Java 程序员的尴尬

  • 定制 Nginx,打造高性能 Web 服务,带着这个美好憧憬,我打开了有关模块开发的 Nginx 官方资料
  • 然后,画风开始剧变,而且是朝着我不想面对的方向,如下图

image.png

  • 模块开发用的居然是 C 语言!然而,欣宸只是个精通 CRUD 的 Java 程序员
  • 这可怎么办?随手打开一个网页就让梦想破灭?

nginx-clojure,让 Java 扩展 nginx 成为现实

  • 经过一番搜索,终于找到了 nginx-clojure 模块,如下图,是它让 java 扩展 nginx 成为现实

image.png

  • nginx-clojure 的作用很好理解:OpenResty 支持用 Lua 扩展 nginx,nginx-clojure 支持用 Java 扩展 nginx

关于 nginx-clojure

  • nginx-clojure 是个第三方 Nginx 模块,官方的介绍是 Nginx module for embedding Clojure / Java / Groovy programs, typically those Ring based handlers
  • nginx-clojure 模块支持嵌入式 Clojure(闭包)、Java、Groovy 等基于 Ring 的处理器(handler),那什么是 Ring 呢?
  • Ring 在 Clojure 中是一个构建 Web 应用的底层接口和库. 它和 Ruby 的 Rack, Python 里面的 WSGI 或者 Java Servlet 规范相似
  • 从 java 开发者角度来看,就是开发 NginxJavaRingHandler 的实现类,然后该类可以在 nginx-clojure 模块中被运行
  • nginx-clojure 的最新版本是 v0.5.2,官网地址是:https://nginx-clojure.github.io

实战功能介绍

  • 尽管 nginx-clojure 支持很多功能,但是篇幅所限,因此本篇以了解为主,还是经典的 Hello world,更多精彩内容留给后续的系列文章
  • 今天的实战效果如下图,咱们编写 HelloHandler.java 并在 nginx 做好配置,然后用浏览器发起请求后,HelloHandler 的代码就会被执行,浏览器会收到 HelloHandler 返回的内容,这就证实了 java 代码可以在 nginx 上运行:

image.png

  • 为了实现上述功能,接下来的操作步骤如下图:

image.png

  • 感谢您听我唠叨了这么久,接下来,实战开始

环境信息

  • 这里给出我的实战环境信息供您参考,这个环境可以正常运行所有实战:
  1. 操作系统:macOS Big Sur 11.5.2 (20G95)
  2. JDK:1.8.0_281
  3. Maven:3.8.1

下载集成了 nginx-clojure 模块的 nginx 包

  • 咱们要做的第一件事是下载一个特别的 nginx,之所以说它特别,是因为它已集成了 nginx-clojure 模块,开箱即用
  • 下载地址:https://sourceforge.net/projects/nginx-clojure/files/ ,如下图红框,我这里选择的是最新的 0.5.2 版本:

image.png

解压 nginx 包

  • 下载完毕后,解压,得到名为 nginx-clojure-0.5.2 的文件夹,里面的内容如下

image.png

  • 接下来根据您的操作系统对可执行文件做重命名,我这边是 macOS,所以把 nginx-macosx 重命名为 nginx,如果是 linux,把 nginx-linux-64 重命名为 nginx,如果是 windows,就把 nginx-win64.exe 重命名为 nginx.exe
  • 上述重命名操作是 nginx-clojure 官方推荐的,统一可执行文件名,这样运行文档中的命令就统一了
  • 执行命令./nginx -v,控制台响应如下,可见 nginx 版本是 1.18.0:
./nginx -vnginx version: nginx/1.18.0


编码,开发 java 版 handler

  • 接下来开始写代码,先新建一个 maven 工程(我这里名叫 simple-hello),pom.xml 中需要配置 repository 节点,以及唯一的依赖 nginx-clojure,如下所示:
<repositories><repository><id>clojars.org</id><url>http://clojars.org/repo</url></repository></repositories><dependencies><dependency><groupId>nginx-clojure</groupId><artifactId>nginx-clojure</artifactId><version>0.5.2</version></dependency></dependencies>


  • 然后新增文件 HelloHandler.java,如下所示,代码非常简单,实现 NginxJavaRingHandler 接口,invoke 方法返回的数组中只有三个元素:返回码、响应 header 的键值对集合、响应 body 内容:
packagecom.bolingcavalry.simplehello;
importnginx.clojure.java.ArrayMap;
importnginx.clojure.java.NginxJavaRingHandler;
importjava.time.LocalDateTime;
importjava.util.Map;
importstaticnginx.clojure.MiniConstants.CONTENT_TYPE;
importstaticnginx.clojure.MiniConstants.NGX_HTTP_OK;
/*** @author zq2599@gmail.com* @Title: 产生内容的handler* @Package* @Description:* @date 2/1/22 12:41 PM*/publicclassHelloHandlerimplementsNginxJavaRingHandler {
@OverridepublicObject[] invoke(Map<String, Object>request) {
returnnewObject[] {
NGX_HTTP_OK, //http status 200ArrayMap.create(CONTENT_TYPE, "text/plain"), //headers map"Hello, Nginx clojure! "+LocalDateTime.now()  //response body can be string, File or Array/Collection of them        };
    }
}


  • 至此,编码完成,欣宸精通 Hello World 果然并非空穴来风…

编译,生成 jar

  • 在 pom.xml 所在目录执行编译构建的命令 mvn clean package -U,会在 target 目录下生成 jar 包,只有 3K 大小:

image.png

jar 放入 nginx 的 jars 目录

  • 将前面生成的 simple-hello-1.0-SNAPSHOT.jar 文件放入下图红框的 jars 文件夹内

image.png

修改 nginx 的配置

  • 打开 nginx-clojure-0.5.2/conf/nginx.conf 文件,在 server 配置中增加一个 location 配置,内容如下,指定了 handler 类型,以及对应的 java 类:
location /java {
         content_handler_type 'java';
         content_handler_name 'com.bolingcavalry.simplehello.HelloHandler';
}


  • 修改后,完整的 nginx.conf 内容如下,可见有很多 java 相关的配置,在本篇咱们都保持不动,留待后面的文章解锁:
###you can uncomment next two lines for easy debug###Warning: if master_process is off, there will be only one nginx worker running. Only use it for debug propose.#daemon  off;#master_process  off;#user  nobody;worker_processes1;
#error_log  logs/error.log;#error_log  logs/error.log  notice;#error_log  logs/error.log  info;#pid        logs/nginx.pid;events {
worker_connections1024;
}
http {
includemime.types;
default_typeapplication/octet-stream;
#log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '#                  '$status $body_bytes_sent "$http_referer" '#                  '"$http_user_agent" "$http_x_forwarded_for"';#access_log  logs/access.log  main;sendfileon;
#tcp_nopush     on;#keepalive_timeout  0;keepalive_timeout65;
#gzip  on;jvm_pathauto;
### Set my app jars and resources, it must include nginx-clojure runtime jar,e.g. nginx-clojure-0.5.1.jar and ### for clojure user clojure runtime jar is also needed.### See http://nginx-clojure.github.io/directives.html#jvm_classpathjvm_classpath"libs/*:jars/*";
###jvm heap memory#jvm_options "-Xms1024m";#jvm_options "-Xmx1024m";#for enable java remote debug uncomment next two lines#jvm_options "-Xdebug";#jvm_options "-Xrunjdwp:server=y,transport=dt_socket,address=840#{pno},suspend=n";###threads number for request handler thread pool on jvm, default is 0. ###check more details from #jvm_workers 8;server {
listen8080;
server_namelocalhost;
#charset koi8-r;#access_log  logs/host.access.log  main;location/ {
roothtml;
indexindex.htmlindex.htm;
        }
#error_page  404              /404.html;# redirect server error pages to the static page /50x.html#error_page500502503504/50x.html;
location=/50x.html {roothtml;
        }
location/clojure {
handler_type'clojure';
handler_code'             (fn[req]
              {
                :status200,
                :headers {"content-type""text/plain"},
                :body"Hello Clojure & Nginx!"                })
';       }
location/java {
content_handler_type'java';
content_handler_name'com.bolingcavalry.simplehello.HelloHandler';
       }
#      location /groovy {#          handler_type 'groovy';#          handler_code ' #               import nginx.clojure.java.NginxJavaRingHandler;#               import java.util.Map;#               public class HelloGroovy implements NginxJavaRingHandler {#                  public Object[] invoke(Map<String, Object> request){#                     return [200, //http status 200#                             ["Content-Type":"text/html"], //headers map#                             "Hello, Groovy & Nginx!"]; //response body can be string, File or Array/Collection of them#                  }#               }#          ';#       }#             }
}

启动 nginx

  • 启动命令很简单,在 nginx-clojure-0.5.2 目录下执行./nginx
  • 如果启动失败了,请打开 nginx-clojure-0.5.2/logs/error.log 查看问题,例如我这里遇到过端口占用导致启动失败:
2022/02/02 17:45:07 [emerg] 27703#0: bind() to 0.0.0.0:8080 failed (48: Address already in use)2022/02/02 17:45:07 [emerg] 27703#0: bind() to 0.0.0.0:8080 failed (48: Address already in use)2022/02/02 17:45:07 [emerg] 27703#0: bind() to 0.0.0.0:8080 failed (48: Address already in use)2022/02/02 17:45:07 [emerg] 27703#0: bind() to 0.0.0.0:8080 failed (48: Address already in use)2022/02/02 17:45:07 [emerg] 27703#0: bind() to 0.0.0.0:8080 failed (48: Address already in use)2022/02/02 17:45:07 [emerg] 27703#0: still could not bind()

验证

  • 打开 postman 验证服务是否正常,请求地址是 http://127.0.0.1:8080/java
  • 响应如下图所示,符合预期,返回的就是咱们定制的 HelloHandler 的内容

image.png

  • 至此,nginx-clojure 的入门操作就完成了,虽然寥寥几行代码,但却给 java 程序员打开了一扇窗:用咱们熟悉的技术去扩展 nginx,打造更符合业务的 web 服务器,以最短链路完成 web 响应
  • 本篇只是 nginx-clojure 之旅的开端,先对 nginx-clojure 有个清晰的认识,接下来的文章咱们会深度探索它,让它更好的服务于业务

源码下载

  • 本篇的完整源码可在 GitHub 下载到,地址和链接信息如下表所示(https://github.com/zq2599/blog_demos):

image.png

  • 这个 git 项目中有多个文件夹,本篇的源码在 nginx-clojure-tutorials 文件夹下,如下图红框所示:

image.png

欢迎关注阿里云开发者社区博客:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...
目录
打赏
0
4
1
1
492
分享
相关文章
|
26天前
|
《从头开始学java,一天一个知识点》之:控制流程:if-else条件语句实战
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问&quot;`a==b`和`equals()`的区别&quot;,大脑突然空白 - 写出的代码总是莫名报NPE,却不知道问题出在哪个运算符 这个系列为你打造Java「速效救心丸」!每天1分钟,地铁通勤、午休间隙即可完成学习。直击高频考点和实际开发中的「坑位」,拒绝冗长概念,每篇都有可运行的代码示例。明日预告:《for与while循环的使用场景》。 ---
55 19
|
2月前
|
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
175 60
【Java并发】【线程池】带你从0-1入门线程池
|
22天前
|
【Java并发】【synchronized】适合初学者体质入门的synchronized
欢迎来到我的Java线程同步入门指南!我不是外包员工,梦想是写高端CRUD。2025年我正在沉淀中,博客更新速度加快,欢迎点赞、收藏、关注。 本文介绍Java中的`synchronized`关键字,适合初学者。`synchronized`用于确保多个线程访问共享资源时不会发生冲突,避免竞态条件、保证内存可见性、防止原子性破坏及协调多线程有序访问。
53 8
【Java并发】【synchronized】适合初学者体质入门的synchronized
JVM实战—1.Java代码的运行原理
本文介绍了Java代码的运行机制、JVM类加载机制、JVM内存区域及其作用、垃圾回收机制,并汇总了一些常见问题。
JVM实战—1.Java代码的运行原理
|
23天前
|
《从头开始学java,一天一个知识点》之:数组入门:一维数组的定义与遍历
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问&quot;`a==b`和`equals()`的区别&quot;,大脑突然空白 - 写出的代码总是莫名报NPE,却不知道问题出在哪个运算符 这个系列就是为你打造的Java「速效救心丸」!我们承诺:每天1分钟,地铁通勤、午休间隙即可完成学习;直击痛点,只讲高频考点和实际开发中的「坑位」;拒绝臃肿,没有冗长概念堆砌,每篇都有可运行的代码标本。明日预告:《多维数组与常见操作》。 通过实例讲解数组的核心认知、趣味场景应用、企业级开发规范及优化技巧,帮助你快速掌握Java数组的精髓。
57 23
【Java并发】【AQS】适合初学者体质的AQS入门
AQS这是灰常重要的哈,很多JUC下的框架的核心,那都是我们的AQS,所以这里,我们直接开始先研究AQS。 那说到研究AQS,那我们应该,使用开始说起🤓 入门 什么是AQS? AQS(Abst
38 8
Java机器学习实战:基于DJL框架的手写数字识别全解析
在人工智能蓬勃发展的今天,Python凭借丰富的生态库(如TensorFlow、PyTorch)成为AI开发的首选语言。但Java作为企业级应用的基石,其在生产环境部署、性能优化和工程化方面的优势不容忽视。DJL(Deep Java Library)的出现完美填补了Java在深度学习领域的空白,它提供了一套统一的API,允许开发者无缝对接主流深度学习框架,将AI模型高效部署到Java生态中。本文将通过手写数字识别的完整流程,深入解析DJL框架的核心机制与应用实践。
43 3
课时11:综合实战:简单Java类
本次分享的主题是综合实战:简单 Java 类。主要分为两个部分: 1.简单 Java 类的含义 2.简单 Java 类的开发
|
2月前
|
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
204 3
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
课时37:综合实战:数据表与简单Java类映射转换
今天我分享的是数据表与简单 Java 类映射转换,主要分为以下四部分。 1. 映射关系基础 2. 映射步骤方法 3. 项目对象配置 4. 数据获取与调试
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等