diff --git a/.gitignore b/.gitignore index adc0ae4c..bca40d49 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,9 @@ /.cproject /.project /.settings/ + + +#intellij idea files +.idea +*.iml +/nginx-tomcat9/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..1cdd4f27 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,145 @@ +sudo: required + +dist: xenial + +language: clojure + +lein: 2.9.1 + +jdk: + - openjdk8 + +compiler: + - gcc + +addons: + hosts: + - mysql-0 + - localhost +# apt: +# packages: +# - libgd-dev +# - libssl-dev +# - libgeoip-dev + +cache: + directories: + - local-caches + +env: + global: + - LD_LIBRARY_PATH="$JAVA_HOME/jre/lib/amd64/server" +# - PCRE_VER=8.41 + - NGINX_VER=1.24.0 + - NGINX_BRANCH=1.24 + +services: + - mysql + - redis-server + + + +before_install: + - cat /etc/hosts + +install: +# - if [ ! -f local-caches/pcre-$PCRE_VER.tar.gz ]; then wget -P local-caches http://ftp.cs.stanford.edu/pub/exim/pcre/pcre-$PCRE_VER.tar.gz; fi +# - if [ ! -f local-caches/nginx-$NGINX_VER.tar.gz ]; then wget -P local-caches https://nginx.org/download/nginx-$NGINX_VER.tar.gz; fi + - git clone -b stable-$NGINX_BRANCH --single-branch https://github.com/nginx-clojure/nginx nginx-$NGINX_VER + - export NC_PJ_HOME=$(pwd) + - sudo ln -s /home/travis /home/who + - sudo chown travis /home/who +# - sudo apt install -y libgd-dev libssl-dev libgeoip-dev + - mkdir -p /home/travis/git + - ln -s ${NC_PJ_HOME} /home/travis/git + +before_script: + - export NC_PJ_HOME=$(pwd) + - export NGX_SRC=${NC_PJ_HOME}/nginx-$NGINX_VER + - mysql -uroot -e 'create database nctest; grant all on nctest.* to "nginxclojure"@"%" identified by "111111"; flush privileges;' + # - tar zxf local-caches/pcre-$PCRE_VER.tar.gz + # - tar zxf local-caches/nginx-$NGINX_VER.tar.gz + - cd ${NGX_SRC} && auto/configure --with-http_v2_module --with-select_module --with-http_ssl_module --with-http_auth_request_module --with-debug --add-module=${NC_PJ_HOME}/src/c --with-http_stub_status_module --prefix= --sbin-path=nginx --conf-path=conf/nginx.conf --error-log-path=logs/error.log --http-log-path=logs/access.log --pid-path=logs/nginx.pid --lock-path=logs/nginx.lock --http-client-body-temp-path=temp/client_temp --http-proxy-temp-path=temp/proxy_temp --http-fastcgi-temp-path=temp/fastcgi_temp --http-uwsgi-temp-path=temp/uwsgi_temp --http-scgi-temp-path=temp/scgi_temp + - make + - cp objs/nginx ${NC_PJ_HOME}/test/nginx-working-dir + - cd /home/who/git/nginx-clojure/test/nginx-working-dir + - mkdir logs temp temp/client_temp temp/fastcgi_temp temp/proxy_temp temp/scgi_temp temp/uwsgi_temp + - cd /home/who/git/nginx-clojure/ + - mkdir bin + - lein with-profile +cljremotetest classpath + - lein install + - lein with-profile unittest junit compile + +jobs: + include: + - stage: unit test + script: + - lein with-profile unittest junit + - stage: integration test + script: + - cd /home/who/git/nginx-clojure/test/nginx-working-dir + - ./nginx -c /home/who/git/nginx-clojure/test/nginx-working-dir/conf/nginx-plain.conf & + - sleep 10 + - tail -f logs/error.log & + - curl -v http://localhost:8080/clojure + - killall tail + - cd /home/who/git/nginx-clojure/ + - lein with-profile cljremotetest test :all + - cd /home/who/git/nginx-clojure/test/nginx-working-dir + - ./nginx -c /home/who/git/nginx-clojure/test/nginx-working-dir/conf/nginx-plain.conf -s stop + - # stage name not required, will continue to use `integration test` + script: + - cd /home/who/git/nginx-clojure/test/nginx-working-dir + - ./nginx -c /home/who/git/nginx-clojure/test/nginx-working-dir/conf/nginx-threadpool.conf & + - sleep 10 + - tail -f logs/error.log & + - curl -v http://localhost:8080/clojure + - killall tail + - cd /home/who/git/nginx-clojure/ + - lein with-profile cljremotetest test :no-async + - cd /home/who/git/nginx-clojure/test/nginx-working-dir + - ./nginx -c /home/who/git/nginx-clojure/test/nginx-working-dir/conf/nginx-threadpool.conf -s stop + - # stage name not required, will continue to use `integration test` + script: + - cd /home/who/git/nginx-clojure/test/nginx-working-dir + - ./nginx -c /home/who/git/nginx-clojure/test/nginx-working-dir/conf/nginx-coroutine.conf & + - sleep 30 + - tail -f logs/error.log & + - curl -v http://localhost:8080/clojure + - killall tail + - cd /home/who/git/nginx-clojure/ + - lein with-profile cljremotetest test :all + - cd /home/who/git/nginx-clojure/test/nginx-working-dir + - ./nginx -c /home/who/git/nginx-clojure/test/nginx-working-dir/conf/nginx-coroutine.conf -s stop + - stage: jdk19 native coroutine test + script: + - wget https://download.oracle.com/java/19/archive/jdk-19.0.2_linux-x64_bin.tar.gz + - tar -xzf jdk-19.0.2_linux-x64_bin.tar.gz + - export JAVA_HOME=`pwd`/jdk-19.0.2 + - export PATH=$JAVA_HOME/bin:$PATH + - java -version + - cd /home/who/git/nginx-clojure/ + - lein with-profile nativeCoroutine jar && lein with-profile unittest junit compile + - cd /home/who/git/nginx-clojure/test/nginx-working-dir + - ./nginx -c /home/who/git/nginx-clojure/test/nginx-working-dir/conf/nginx-coroutine-jdk19.conf & + - sleep 30 + - tail -f logs/error.log & + - curl -v http://localhost:8080/clojure + - killall tail + - cd /home/who/git/nginx-clojure/ + - lein with-profile jdk19cljremotetest test :all + - cd /home/who/git/nginx-clojure/test/nginx-working-dir + - ./nginx -c /home/who/git/nginx-clojure/test/nginx-working-dir/conf/nginx-coroutine-jdk19.conf -s stop + +after_failure: + - echo "******************************** Test Failed (Start of Nginx error.log)*****************" + - cat logs/error.log + - echo "******************************** Test Failed (End of Nginx error.log)*******************" + +notifications: + email: false + +branches: + only: + - master + - stable diff --git a/HISTORY.md b/HISTORY.md index 000b1b8c..3ad5bc33 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,6 +4,39 @@ Downloads & Release History 1. [Binaries of Releases](http://sourceforge.net/projects/nginx-clojure/files/) 1. [Sources of Releases](https://github.com/nginx-clojure/nginx-clojure/releases) +## 0.6.0 (2023-03-18) + +1. New Feature: #270 All handlers can be used at http & server context +1. New Feature: #272 Support Nginx 1.23.X where some internals have been changed +1. New Feature: #250 Support to use jdk19 built-in coroutine viz. Continuation +1. Binaries Distribution: Built with Nginx v1.23.3 +1. Documents: Add build notes for nginx-clojure-embed + +## 0.5.3 (2022-03-10) + +1. Bug Fix: #256 NginxClojureAsynSocket isClosed is not return correct result +1. Binaries Distribution: Built with Nginx v1.20.2 + + +## 0.5.2 (2020-12-23) + +1. Bug Fix: #234 Try to fix no response when NGX_AGAIN return at next header filter +1. Bug Fix: #233 Fix compiler warnings when there's no zlib found +1. Enhancement: Delayed update to improve setVariable/set-ngx-var! performance at thread-pool mode +1. Example Project: Add an example project for Jersey & Spring DI +1. Example Project: Add example for integration with Spring framework +1. Binaries Distribution: Built with Nginx v1.18.0 + + +## 0.5.1 (2019-11-23) + +1. Bug Fix: Connection hangs with header filter at thread-pool mode #209 #153 +1. Bug Fix: Body filter hangs when body size is larger than the value specified in proxy_buffers #219 +1. Bug Fix: Segment fault caused by request is marked as closed too late #222 +1. Code Style: Fix generic warnings in java code code-style #223 (Thanks to Michael @mgoblin) +1. Bug Fix: NPE caused by damaged memory because unsafe modify response headers #198 +1. Bug Fix: Wrong response for uncompressed message with PMCE enabled +1. CI: both auto-triggered unit test and integration test use travis-ci ## 0.5.0 (2019-10-26) diff --git a/README.md b/README.md index cca6b11c..e60adf5f 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,30 @@ [Nginx-Clojure](http://nginx-clojure.github.io) ============= +[![Build Status](https://travis-ci.com/nginx-clojure/nginx-clojure.svg?branch=master)](https://app.travis-ci.com/github/nginx-clojure/nginx-clojure) +[![Clojars Project](https://img.shields.io/clojars/v/nginx-clojure.svg)](https://clojars.org/nginx-clojure) +[![BSD licensed](https://img.shields.io/badge/license-BSD-blue.svg)](./LICENSE) +![GitHub last commit](https://img.shields.io/github/last-commit/nginx-clojure/nginx-clojure) +[![SourceForge](https://img.shields.io/sourceforge/dt/nginx-clojure)](https://sourceforge.net/projects/nginx-clojure/files/) + + ![Alt text](logo.png) [Nginx-Clojure](http://nginx-clojure.github.io) is a [Nginx](http://nginx.org/) module for embedding Clojure or Java or Groovy programs, typically those [Ring](https://github.com/ring-clojure/ring/blob/master/SPEC) based handlers. Core Features ================= -The latest release is v0.5.0, more detail changes about it can be found from [Release History](//nginx-clojure.github.io/downloads.html). +The latest release is v0.6.0, more detail changes about it can be found from [Release History](//nginx-clojure.github.io/downloads.html). 1. Compatible with [Ring](https://github.com/ring-clojure/ring/blob/master/SPEC) and obviously supports those Ring based frameworks, such as Compojure etc. 1. Http Services by using Clojure / Java / Groovy to write simple handlers for http services. 1. Nginx Access Handler by Clojure / Java / Groovy 1. Nginx Header Filter by Clojure / Java / Groovy 1. Nginx Body Filter by Clojure / Java / Groovy -1. **_NEW_**: Nginx Log Handler by Clojure / Java / Groovy -1. **_NEW_**: HTTP V2 support in both standard edition and embedded edition which are compiled against Nginx 1.14.2 -1. **_NEW_**: Support Java 9, 10, 11, 12 +1. Nginx Log Handler by Clojure / Java / Groovy +1. HTTP V2 support in both standard edition and embedded edition which are compiled against Nginx 1.18.0+ +1. Support Java 8, 9, 10, 11, 12, 19 +1. Support to use jdk19 built-in coroutine viz. Continuation 1. Pub/Sub Among Nginx Worker Processes 1. Shared Map based on shared memory & Shared Map based Ring session store 1. Support Sente, see [this PR](https://github.com/ptaoussanis/sente/pull/160) @@ -33,13 +41,14 @@ With this feature one java main thread can handle thousands of connections. 1. Long Polling & Server Sent Events 1. Run initialization clojure code when nginx worker starting 1. Support user defined http request method -1. Compatible with the Nginx lastest most stable version 1.14.2. (Nginx 1.12.x, 1.8.x, 1.6.x, 1.4.x is also ok, older version is not tested and maybe works.) +1. Compatible with the Nginx lastest most mainline version 1.23.3. (Nginx 1.22.X, 1.20.X, 1.18.x, 1.14.x, 1.12.x, 1.8.x, 1.6.x, 1.4.x is also ok, older version is not tested and maybe works.) 1. One of benifits of [Nginx](http://nginx.org/) is worker processes are automatically restarted by a master process if they crash -1. Utilizes lazy headers and direct memory operation between [Nginx](http://nginx.org/) and JVM to fast handle dynamic contents from Clojure or Java code. -1. Utilizes [Nginx](http://nginx.org/) zero copy file sending mechanism to fast handle static contents controlled by Clojure or Java code. -1. Supports Linux x64, Linux x86 32bit, Win32, Win64 and Mac OS X. Freebsd version can also be got from Freebsd ports. +1. Utilize lazy headers and direct memory operation between [Nginx](http://nginx.org/) and JVM to fast handle dynamic contents from Clojure or Java code. +1. Utilize [Nginx](http://nginx.org/) zero copy file sending mechanism to fast handle static contents controlled by Clojure or Java code. +1. Support Linux x64, Linux x86 32bit, Win32, Win64 and Mac OS X. Freebsd version can also be got from Freebsd ports. By the way it is very fast, the benchmarks can be found [HERE(with wrk2)](https://github.com/ptaoussanis/clojure-web-server-benchmarks/). + Jar Repository ================ @@ -52,19 +61,19 @@ Nginx-Clojure has already been published to https://clojars.org/ whose maven rep ``` -After adding clojars repository, you can reference nginx-clojure 0.5.0 , e.g. +After adding clojars repository, you can reference nginx-clojure 0.6.0 , e.g. Leiningen (clojure, no need to add clojars repository which is a default repository for Leiningen) ----------------- ```clojure -[nginx-clojure "0.5.0"] +[nginx-clojure "0.6.0"] ``` Gradle (groovy/java) ----------------- ``` -compile "nginx-clojure:nginx-clojure:0.5.0" +compile "nginx-clojure:nginx-clojure:0.6.0" ``` Maven ----------------- @@ -73,20 +82,52 @@ Maven nginx-clojure nginx-clojure - 0.5.0 + 0.6.0 ``` -More Documents +Documents ================= -More Documents can be found from its web site [nginx-clojure.github.io](http://nginx-clojure.github.io/) + +- [Quick Start](http://nginx-clojure.github.io/quickstart.html) +- [Downloads](http://nginx-clojure.github.io/downloads.html) +- [Installation](http://nginx-clojure.github.io/installation.html) +- [Configuration](http://nginx-clojure.github.io/configuration.html) + - [JVM Path,Class Path & Other JVM Options](http://nginx-clojure.github.io/configuration.html#user-content-21-jvm-path--class-path--other-jvm-options) + - [Initialization Handler for nginx worker](http://nginx-clojure.github.io/configuration.html#user-content-22-initialization-handler-for-nginx-worker) + - [Content Ring Handler for Location](http://nginx-clojure.github.io/configuration.html#user-content-23-content-ring-handler-for-location) + - [Coroutine/Asynchronous Client Channel/Thread Pool](http://nginx-clojure.github.io/configuration.html#user-content-24-chose--coroutine-based-socket-or-asynchronous-socketchannel-or-thread-pool-for-slow-io-operations) +- [Nginx Handlers](http://nginx-clojure.github.io/configuration.html#user-content-25-nginx-rewrite-handler) + - [Nginx Rewrite Handler](http://nginx-clojure.github.io/configuration.html#user-content-25-nginx-rewrite-handler) + - [Nginx Access Handler](http://nginx-clojure.github.io/configuration.html#user-content-26-nginx-access-handler) + - [Nginx Header Filter](http://nginx-clojure.github.io/configuration.html#user-content-27-nginx-header-filter) + - [Nginx Body Filter](http://nginx-clojure.github.io/configuration.html#user-content-28-nginx-body-filter) + - [Nginx Log Handler](http://nginx-clojure.github.io/configuration.html#user-content-29-nginx-log-handler) +- Advanced Topic + - [Embedding Nginx-Clojure into a standard App](http://nginx-clojure.github.io/embed.html) + - [Server Channel for Long Polling & Server Sent Events](http://nginx-clojure.github.io/more.html#user-content-34-server-channel-for-long-polling--server-sent-events-sse) + - [Pub/Sub Among Nginx Worker Processes](http://nginx-clojure.github.io/subpub.html) + - [Shared Map & Session Store](http://nginx-clojure.github.io/sharedmap.html) + - [Asynchronous Client Channel](http://nginx-clojure.github.io/more.html#user-content-36-asynchronous-client-channel) + - [About Logging](http://nginx-clojure.github.io/more.html#user-content-37--about-logging) + - [Sever Side WebSocket](http://nginx-clojure.github.io/more.html#user-content-38--sever-side-websocket) + - [Java standard RESTful web services with Jersey](http://nginx-clojure.github.io/more.html#user-content-39--java-standard-restful-web-services-with-jersey) + - [Embedding Tomcat](http://nginx-clojure.github.io/more.html#user-content-310-embeding-tomcat) + - [Use Spring Framework with Nginx-Clojure Java Handlers](https://github.com/nginx-clojure/nginx-clojure/tree/master/example-projects/spring-core-example) + - [Example Project about Jersey & Spring with Nginx-Clojure](https://github.com/nginx-clojure/nginx-clojure/tree/master/example-projects/jersey-spring-example) + - [More about Nginx Worker Process](http://nginx-clojure.github.io/more.html#user-content-311-more-about-nginx-worker-process) + - [More about Nginx-Clojure](http://nginx-clojure.github.io/more.html) +- [Directives Reference](http://nginx-clojure.github.io/directives.html) +- [API Reference (Clojure)](http://nginx-clojure.github.io/api/) +- [Example Projects](https://github.com/nginx-clojure/nginx-clojure/tree/master/example-projects) +- [Github Repository of Documents](https://github.com/nginx-clojure/nginx-clojure.github.io) License ================= -Copyright © 2013-2019 Zhang, Yuexiang (xfeep) and released under the BSD 3-Clause license. +Copyright © 2013-2023 Zhang, Yuexiang (xfeep) and released under the BSD 3-Clause license. This program uses: * Re-rooted ASM bytecode engineering library which is distributed under the BSD 3-Clause license -* Modified Continuations Library Written by Matthias Mann is distributed under the BSD 3-Clause license +* Modified Continuations Library written by Matthias Mann is distributed under the BSD 3-Clause license diff --git a/example-projects/clojure-web-example/conf/mime.types b/example-projects/clojure-web-example/conf/mime.types new file mode 100644 index 00000000..52eebec9 --- /dev/null +++ b/example-projects/clojure-web-example/conf/mime.types @@ -0,0 +1,80 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/x-javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt c; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg svgz; + image/webp webp; + + application/java-archive jar war ear; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.ms-excel xls; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream eot; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/example-projects/clojure-web-example/conf/nginx.conf b/example-projects/clojure-web-example/conf/nginx.conf index 0add1fe9..e3f10274 100644 --- a/example-projects/clojure-web-example/conf/nginx.conf +++ b/example-projects/clojure-web-example/conf/nginx.conf @@ -52,6 +52,8 @@ http { jvm_options '-DMY_PNO=#{pno}'; + jvm_options "-Dnginx.clojure.logger.level=error"; + ### jvm heap memory #jvm_options "-Xms1024m"; #jvm_options "-Xmx1024m"; diff --git a/example-projects/clojure-web-example/project.clj b/example-projects/clojure-web-example/project.clj index c710c8f4..64cc20fa 100644 --- a/example-projects/clojure-web-example/project.clj +++ b/example-projects/clojure-web-example/project.clj @@ -2,7 +2,7 @@ :description "FIXME: write description" :url "https://github.com/nginx-clojure/nginx-clojure/tree/master/example-projects/clojure-web-example" :min-lein-version "2.0.0" - :dependencies [[org.clojure/clojure "1.7.0"] ;; v1.5.1+ is OK + :dependencies [[org.clojure/clojure "1.9.0"] ;; v1.5.1+ is OK [compojure "1.4.0"] [ring/ring-defaults "0.1.2"] [ring/ring-anti-forgery "1.0.0"] @@ -15,11 +15,11 @@ :aot [clojure-web-example.handler] :uberjar-name "clojure-web-example-default.jar" :profiles { - :provided {:dependencies [[nginx-clojure "0.5.0"]]} + :provided {:dependencies [[nginx-clojure "0.6.0"]]} :dev {:dependencies [[javax.servlet/servlet-api "2.5"] [ring-mock "0.1.5"]]} :embed {:dependencies - [[nginx-clojure/nginx-clojure-embed "0.5.0"]] + [[nginx-clojure/nginx-clojure-embed "0.6.0"]] :aot [clojure-web-example.embed-server] :main clojure-web-example.embed-server :uberjar-name "clojure-web-example-embed.jar" diff --git a/example-projects/jersey-example/README.md b/example-projects/jersey-example/README.md index aafd4f92..9711fd7a 100644 --- a/example-projects/jersey-example/README.md +++ b/example-projects/jersey-example/README.md @@ -4,4 +4,89 @@ ```bash mvn compile assembly:single -DskipTests +``` + +## Configuration + +in nginx.conf + +```nginx + location /jersey { + + content_handler_type java; + content_handler_name 'nginx.clojure.bridge.NginxBridgeHandler'; + + ##we can set system properties ,e.g. m2rep + #content_handler_property system.m2rep '/home/who/.m2/repository'; + + ##we can put jars into some dir then all of their path will be appended into the classpath + #content_handler_property bridge.lib.dirs 'my-jersey-libs-dir:myother-dir'; + + ##the path of nginx-jersey-x.x.x.jar must be included in the below classpath or one of above #{bridge.lib.dirs} + ##we can use maven assembly plugin to get a all-in-one jar file with dependencies, e.g. json-jackson-example-with-dependencies.jar. + content_handler_property bridge.lib.cp 'jars/nginx-jersey-0.1.0.jar:myjars/json-jackson-example-with-dependencies.jar'; + content_handler_property bridge.imp 'nginx.clojure.jersey.NginxJerseyContainer'; + + ##aplication path usually it is the same with nginx location + content_handler_property jersey.app.path '/jersey'; + + ##application resources which can be either of JAX-RS resources, providers + content_handler_property jersey.app.resources ' + org.glassfish.jersey.examples.jackson.EmptyArrayResource, + org.glassfish.jersey.examples.jackson.NonJaxbBeanResource, + org.glassfish.jersey.examples.jackson.CombinedAnnotationResource, + org.glassfish.jersey.examples.jackson.MyObjectMapperProvider, + org.glassfish.jersey.examples.jackson.ExceptionMappingTestResource, + org.glassfish.jersey.jackson.JacksonFeature + '; + gzip on; + gzip_types application/javascript application/xml text/plain text/css 'text/html;charset=UTF-8'; + } +``` + +All sources about this example can be found from jersey github repository 's example [json-jackson](https://github.com/jersey/jersey/tree/2.17/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson). + +then we test the JAX-RS services by curl + +```shell +$ curl -v http://localhost:8080/jersey/emptyArrayResource +> GET /jersey/emptyArrayResource HTTP/1.1 +> User-Agent: curl/7.35.0 +> Host: localhost:8080 +> Accept: */* +> +< HTTP/1.1 200 OK +< Date: Sat, 23 May 2015 17:47:14 GMT +< Content-Type: application/json +< Transfer-Encoding: chunked +< Connection: keep-alive +* Server nginx-clojure is not blacklisted +< Server: nginx-clojure +< +{ + "emtpyArray" : [ ] +} +``` + +```shell +$ curl -v http://localhost:8080/jersey/nonJaxbResource +> GET /jersey/nonJaxbResource HTTP/1.1 +> User-Agent: curl/7.35.0 +> Host: localhost:8080 +> Accept: */* +> +< HTTP/1.1 200 OK +< Date: Sat, 23 May 2015 17:46:17 GMT +< Content-Type: application/javascript +< Transfer-Encoding: chunked +< Connection: keep-alive +* Server nginx-clojure is not blacklisted +< Server: nginx-clojure +< +callback({ + "name" : "non-JAXB-bean", + "description" : "I am not a JAXB bean, just an unannotated POJO", + "array" : [ 1, 1, 2, 3, 5, 8, 13, 21 ] +* Connection #0 to host localhost left intact +}) ``` \ No newline at end of file diff --git a/example-projects/jersey-example/pom.xml b/example-projects/jersey-example/pom.xml index fd8b7e53..062e31da 100644 --- a/example-projects/jersey-example/pom.xml +++ b/example-projects/jersey-example/pom.xml @@ -33,7 +33,7 @@ junit junit - 4.11 + 4.13.1 test diff --git a/example-projects/jersey-spring-example/.gitignore b/example-projects/jersey-spring-example/.gitignore new file mode 100644 index 00000000..06a84e9d --- /dev/null +++ b/example-projects/jersey-spring-example/.gitignore @@ -0,0 +1,4 @@ +/target/ +/.classpath +/.project +/.settings diff --git a/example-projects/jersey-spring-example/README.adoc b/example-projects/jersey-spring-example/README.adoc new file mode 100644 index 00000000..4956729c --- /dev/null +++ b/example-projects/jersey-spring-example/README.adoc @@ -0,0 +1,60 @@ +# Jersey & Spring Example for Nginx-Clojure + +This example project is used to demo how to use jersey, a JAX-RS Reference Implementation, +and spring dependency injection with Nginx-Clojure + +. + +## Run Unit Test + +[source,shell] +.... +mvn test +.... + + + +## Run Application + +.Get the source +[source,bash] +.... +git clone https://github.com/nginx-clojure/nginx-clojure +.... + +.Package +[source,bash] +.... +cd nginx-clojure/example-projects/jersey-spring-example +## use mvn pakage to get jersey-spring-example-0.0.1-jar-with-dependencies.jar +mvn package +.... + +.Download Nginx-Clojure +[source,bash] +.... +cd nginx-work-dir +mkdir logs temp +wget https://sourceforge.net/projects/nginx-clojure/files/nginx-clojure-0.5.1.tar.gz +tar -zxvf nginx-clojure-0.5.1.tar.gz nginx-clojure-0.5.1/nginx-linux-x64 +mv nginx-clojure-0.5.1/nginx-linux-x64 nginx +.... + +.Run Nginx-Clojure +[source,bash] +.... +./nginx +.... + +.Add a student +[source,bash] +.... +curl -H "Content-Type: application/json" -X POST -d '{"name":"Tom", "grade":"A"}' http://localhost:8080/api/students +.... + + +.Find student by id +[source,bash] +.... +curl http://localhost:8080/api/students/1 +.... diff --git a/example-projects/jersey-spring-example/nginx-work-dir/.gitignore b/example-projects/jersey-spring-example/nginx-work-dir/.gitignore new file mode 100644 index 00000000..a3bff7ec --- /dev/null +++ b/example-projects/jersey-spring-example/nginx-work-dir/.gitignore @@ -0,0 +1,3 @@ +logs +/nginx +temp diff --git a/example-projects/jersey-spring-example/nginx-work-dir/conf/logback.xml b/example-projects/jersey-spring-example/nginx-work-dir/conf/logback.xml new file mode 100644 index 00000000..d51dae1d --- /dev/null +++ b/example-projects/jersey-spring-example/nginx-work-dir/conf/logback.xml @@ -0,0 +1,30 @@ + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-10contextName %logger{36} - %msg%n + + + + + + + + logs/clojure-web-example-%d{yyyy-MM-dd}-${MY_PNO}.log + + 30 + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-10contextName %logger{36} - %msg%n + + + + + + + + + + + + diff --git a/example-projects/jersey-spring-example/nginx-work-dir/conf/mime.types b/example-projects/jersey-spring-example/nginx-work-dir/conf/mime.types new file mode 100644 index 00000000..52eebec9 --- /dev/null +++ b/example-projects/jersey-spring-example/nginx-work-dir/conf/mime.types @@ -0,0 +1,80 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/x-javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt c; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg svgz; + image/webp webp; + + application/java-archive jar war ear; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.ms-excel xls; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream eot; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/example-projects/jersey-spring-example/nginx-work-dir/conf/nginx.conf b/example-projects/jersey-spring-example/nginx-work-dir/conf/nginx.conf new file mode 100644 index 00000000..ad69ef56 --- /dev/null +++ b/example-projects/jersey-spring-example/nginx-work-dir/conf/nginx.conf @@ -0,0 +1,84 @@ + +## Determines whether nginx should become a daemon and default is on. +#daemon off; + +## If master_process is off, there will be only one nginx worker running. Only use it for debug propose. +## Default is on. +#master_process off; + + +## Defines the number of worker processes every of which will be embedded one JVM instance. +## When auto is specified the number of worker processes will be the number of CPU hardware threads +worker_processes 1; + +error_log logs/error.log error; + +events { + ## Defines the number of connections per worker processes. + ## Note that this number includes all connections (e.g. connections with proxied servers, among others), + ## not only connections with clients. + worker_connections 1024; +} + + +http { + + ## include file mime.types which defines file type to mime type mappings + include mime.types; + + ## Default mime type for unknown file type + default_type application/octet-stream; + + ## access log, more details can be found from http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log + ## when do performance tests try to turn off it, viz. use `access_log off;` instead. + access_log logs/access.log combined; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + ## Enable gzip, default is off + #gzip on; + + ## Defines the path of JVM, when auto is used nginx-clojure will detect this by itself. + jvm_path auto; + + ## Define class path. When '/*' is used after a directory path all jar files and + ##sub-directories will be used as the jvm classpath + jvm_classpath "../target/*"; + + jvm_options "-Dnginx.clojure.logger.level=error"; + + ### jvm heap memory + #jvm_options "-Xms1024m"; + #jvm_options "-Xmx1024m"; + + ## Threads number for request handler thread pool on jvm, default is 0 which means disable + ## thread pool mode. Check more details from section 2.4 in http://nginx-clojure.github.io/configuration.html + jvm_workers 16; + + ## remote debug + #jvm_options "-Xdebug"; + #jvm_options "-Xrunjdwp:server=y,transport=dt_socket,address=840#{pno},suspend=n"; + + server { + listen 8080; + server_name localhost; + + location /api { + content_handler_type java; + content_handler_name nginx.clojure.jersey.spring.example.JerseySpringNginxHandler; + content_handler_property jersey-context-path /api; + } + + ## redirect server error pages to the static page /50x.html + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + + } + + +} diff --git a/example-projects/jersey-spring-example/pom.xml b/example-projects/jersey-spring-example/pom.xml new file mode 100644 index 00000000..7b4e8e20 --- /dev/null +++ b/example-projects/jersey-spring-example/pom.xml @@ -0,0 +1,133 @@ + + 4.0.0 + + nginx-clojure + jersey-spring-example + 0.0.1 + jar + + jersey-spring-example + + + 1.8 + 1.8 + 2.29 + 5.3.19 + + + + nginx-clojure + nginx-jersey + 0.1.8 + + + org.glassfish.jersey.core + jersey-server + ${jersey.version} + + + org.glassfish.jersey.ext + jersey-spring5 + ${jersey.version} + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${jersey.version} + + + org.springframework + spring-core + ${spring.version} + + + org.springframework + spring-context + ${spring.version} + + + org.springframework + spring-beans + ${spring.version} + + + org.springframework + spring-web + ${spring.version} + + + org.springframework + spring-aop + ${spring.version} + + + jakarta.servlet + jakarta.servlet-api + 4.0.3 + + + junit + junit + 4.13.1 + test + + + nginx-clojure + nginx-clojure-embed + 0.5.3 + test + + + com.github.jnr + jnr-posix + 3.0.51 + test + + + + + + maven-assembly-plugin + 3.0.0 + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + + + central + https://repo1.maven.org/maven2/ + + false + + + true + + + + clojars + https://repo.clojars.org/ + + true + + + true + + + + diff --git a/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/JerseyResourceConfig.java b/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/JerseyResourceConfig.java new file mode 100644 index 00000000..b9a4243a --- /dev/null +++ b/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/JerseyResourceConfig.java @@ -0,0 +1,23 @@ +package nginx.clojure.jersey.spring.example; + +import javax.annotation.PostConstruct; +import javax.ws.rs.ApplicationPath; + +import org.glassfish.jersey.jackson.JacksonFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ApplicationPath("/api") +public class JerseyResourceConfig extends ResourceConfig { + + public JerseyResourceConfig() { + } + + @PostConstruct + public void initialize() { + packages("nginx.clojure.jersey.spring.example") + .register(JacksonFeature.class); + } + +} diff --git a/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/JerseySpringNginxHandler.java b/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/JerseySpringNginxHandler.java new file mode 100644 index 00000000..32a2553b --- /dev/null +++ b/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/JerseySpringNginxHandler.java @@ -0,0 +1,36 @@ +package nginx.clojure.jersey.spring.example; + +import java.io.IOException; +import java.util.Map; + +import org.glassfish.jersey.server.ApplicationHandler; +import org.glassfish.jersey.server.ResourceConfig; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import nginx.clojure.Configurable; +import nginx.clojure.java.NginxJavaRequest; +import nginx.clojure.java.NginxJavaRingHandler; + +public class JerseySpringNginxHandler implements NginxJavaRingHandler, Configurable { + + NginxJerseySpringContainer nginxJerseySpringContainer; + + public JerseySpringNginxHandler() { + } + + @Override + public void config(Map properties) { + ApplicationContext context = new AnnotationConfigApplicationContext("nginx.clojure.jersey.spring.example"); + ResourceConfig resourceConfig = context.getBean(JerseyResourceConfig.class); + resourceConfig.property("contextConfig", context); + String jerseyContextPath = properties.get("jersey-context-path"); + nginxJerseySpringContainer = context.getBean(NginxJerseySpringContainer.class); + nginxJerseySpringContainer.setApplicationHandler(new ApplicationHandler(resourceConfig), jerseyContextPath); + } + + @Override + public Object[] invoke(Map request) throws IOException { + return nginxJerseySpringContainer.handle((NginxJavaRequest)request); + } +} diff --git a/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/NginxJerseySpringContainer.java b/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/NginxJerseySpringContainer.java new file mode 100644 index 00000000..45dfae34 --- /dev/null +++ b/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/NginxJerseySpringContainer.java @@ -0,0 +1,19 @@ +package nginx.clojure.jersey.spring.example; + +import org.glassfish.jersey.server.ApplicationHandler; +import org.springframework.stereotype.Component; + +import nginx.clojure.jersey.NginxJerseyContainer; + +@Component +public class NginxJerseySpringContainer extends NginxJerseyContainer { + + public NginxJerseySpringContainer() { + } + + public void setApplicationHandler(ApplicationHandler applicationHandler, String applicationPath) { + super.appHandler = applicationHandler; + super.appPath = applicationPath; + } + +} diff --git a/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/Student.java b/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/Student.java new file mode 100644 index 00000000..8e4c1095 --- /dev/null +++ b/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/Student.java @@ -0,0 +1,46 @@ +package nginx.clojure.jersey.spring.example; + +public class Student { + + private String id; + private String name; + private String grade; + + public Student() { + + } + + public Student(String id, String name, String grade) { + super(); + this.id = id; + this.name = name; + this.grade = grade; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getGrade() { + return grade; + } + + public void setGrade(String grade) { + this.grade = grade; + } + + + +} diff --git a/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/StudentResource.java b/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/StudentResource.java new file mode 100644 index 00000000..ec1882eb --- /dev/null +++ b/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/StudentResource.java @@ -0,0 +1,59 @@ +package nginx.clojure.jersey.spring.example; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@Path("students") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class StudentResource { + + @Autowired + private StudentService studentService; + + public StudentResource() { + } + + @POST + public Student create(Student student) { + return studentService.save(student); + } + + @PUT + @Path("{id}") + public Student update(@PathParam("id") String id, Student student) { + if (studentService.find(student.getId()) == null) { + return null; + } + return studentService.save(student); + } + + @GET + @Path("{id}") + public Student find(@PathParam("id") String id) { + return studentService.find(id); + } + + @DELETE + @Path("{id}") + public Response delete(@PathParam("id") String id) { + if (studentService.remove(id) == null) { + return Response.status(302).build(); + } else { + return Response.status(201).build(); + } + } + +} diff --git a/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/StudentService.java b/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/StudentService.java new file mode 100644 index 00000000..312b2702 --- /dev/null +++ b/example-projects/jersey-spring-example/src/main/java/nginx/clojure/jersey/spring/example/StudentService.java @@ -0,0 +1,33 @@ +package nginx.clojure.jersey.spring.example; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.springframework.stereotype.Component; + +@Component +public class StudentService { + + private AtomicInteger idCounter = new AtomicInteger(); + private ConcurrentHashMap studentStore = new ConcurrentHashMap<>(); + + public StudentService() { + } + + public Student save(Student student) { + if (student.getId() == null) { + student.setId(idCounter.incrementAndGet()+""); + } + studentStore.put(student.getId(), student); + return student; + } + + public Student find(String id) { + return studentStore.get(id); + } + + public Student remove(String id) { + return studentStore.remove(id); + } + +} diff --git a/example-projects/jersey-spring-example/src/test/java/nginx/clojure/jersey/spring/example/StudentResourceTest.java b/example-projects/jersey-spring-example/src/test/java/nginx/clojure/jersey/spring/example/StudentResourceTest.java new file mode 100644 index 00000000..ead5a912 --- /dev/null +++ b/example-projects/jersey-spring-example/src/test/java/nginx/clojure/jersey/spring/example/StudentResourceTest.java @@ -0,0 +1,57 @@ +package nginx.clojure.jersey.spring.example; + +import static org.junit.Assert.assertEquals; + +import java.io.File; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.glassfish.jersey.jackson.JacksonFeature; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import jnr.posix.POSIX; +import jnr.posix.POSIXFactory; +import nginx.clojure.embed.NginxEmbedServer; + +public class StudentResourceTest { + + WebTarget target; + + @Before + public void setUp() throws Exception { + String confPath = new File("nginx-work-dir/conf/nginx.conf").getAbsolutePath(); + System.setProperty("user.dir", new File("nginx-work-dir").getAbsolutePath()); + POSIX posix = POSIXFactory.getPOSIX(); + posix.chdir("nginx-work-dir"); + NginxEmbedServer.getServer().start(confPath); + Client c = ClientBuilder.newClient(); + c.register(new JacksonFeature()); + target = c.target("http://localhost:8080/api"); + } + + @After + public void tearDown() throws Exception { + NginxEmbedServer.getServer().stop(); + } + + @Test + public void testCreateAndFindStudent() { + Response response = target.path("students").request("application/json") + .post(Entity.entity(new Student(null, "Tom", "A"), MediaType.valueOf("application/json"))); + assertEquals(200, response.getStatus()); + Student s = (Student) response.readEntity(Student.class); + assertEquals("Tom", s.getName()); + + response = target.path("students").path(s.getId()).request("application/json").get(); + assertEquals(200, response.getStatus()); + assertEquals("A", response.readEntity(Student.class).getGrade()); + } + +} diff --git a/example-projects/spring-core-example/.gitignore b/example-projects/spring-core-example/.gitignore new file mode 100644 index 00000000..d98981cc --- /dev/null +++ b/example-projects/spring-core-example/.gitignore @@ -0,0 +1,4 @@ +/target/ +/.classpath +/.project +/.settings/ diff --git a/example-projects/spring-core-example/README.adoc b/example-projects/spring-core-example/README.adoc new file mode 100644 index 00000000..3d87df34 --- /dev/null +++ b/example-projects/spring-core-example/README.adoc @@ -0,0 +1,267 @@ +# Spring Core Example for Nginx-Clojure + +This example project is used to demo how to use spring dependency injection with Nginx-Clojure java rewrite handler. + + +## Step 1. Add Nginx JVM Init Handler + +In this example we use Nginx JVM initialization handler to start the spring application. + +.nginx.conf +[source,bash] +.... +jvm_handler_type 'java'; + +jvm_init_handler_name 'nginx.clojure.spring.core.example.NginxJvmInitHandler'; +.... + +.NginxJvmInitHandler +[source,java] +.... +package nginx.clojure.spring.core.example; + +import java.io.IOException; +import java.util.Map; + +import nginx.clojure.java.NginxJavaRingHandler; + +public class NginxJvmInitHandler implements NginxJavaRingHandler { + + public NginxJvmInitHandler() { + SpringExampleApplication.main(new String[0]); + } + + @Override + public Object[] invoke(Map request) throws IOException { + return null; + } + +} +.... + +The spring application java code need not be changed for Nginx-Clojure. + +.SpringExampleApplication +[source,java] +.... +package nginx.clojure.spring.core.example; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class SpringExampleApplication { + + public SpringExampleApplication() { + } + + public static void main(String[] args) { + ApplicationContext context = new AnnotationConfigApplicationContext("nginx.clojure.spring.core.example"); + } + +} + +.... + + +## Step 2. Add Spring Application Context Aware + +SpringApplicationContextAware is used to get spring application context gracefully without need to change existed code. + +.SpringApplicationContextAware +[source,java] +.... +package nginx.clojure.spring.core.example; + +import java.util.concurrent.CountDownLatch; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +@Component +public class SpringApplicationContextAware implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + private static CountDownLatch countDownLatch = new CountDownLatch(1); + + public static ApplicationContext getApplicationContext() { + try { + countDownLatch.await(); + } catch (InterruptedException e) { + throw new RuntimeException("SpringApplicationContextAware countDownLatch interrupted error", e); + } + return applicationContext; + } + + public SpringApplicationContextAware() { + } + + @Override + public void setApplicationContext(ApplicationContext ctx) throws BeansException { + applicationContext = ctx; + countDownLatch.countDown(); + } + +} + +.... + +## Step 3. Use Wrapper to Access Spring-styled Handlers + +.nginx.conf +[source,bash] +.... +set $proxy_target ""; + +location /hello { + rewrite_handler_type java; + rewrite_handler_name 'nginx.clojure.spring.core.example.NginxSpringHandlerWrapper'; + rewrite_handler_property 'spring.realHandler' 'myRewriteHandler'; + rewrite_handler_property 'spring.prefetched.vars' 'remote_addr,remote_port'; + proxy_pass http://$proxy_target; +} +.... + + +.NginxSpringHandlerWrapper +[source,java] +.... +package nginx.clojure.spring.core.example; + +import java.io.IOException; +import java.util.Map; + +import nginx.clojure.Configurable; +import nginx.clojure.java.NginxJavaRingHandler; + +public class NginxSpringHandlerWrapper implements NginxJavaRingHandler, Configurable { + + private NginxJavaRingHandler realHandler; + + private String[] prefetchedVars; + + + public NginxSpringHandlerWrapper() { + + } + + @Override + public void config(Map properties) { + String name = properties.get("spring.realHandler"); + realHandler = (NginxJavaRingHandler)SpringApplicationContextAware.getApplicationContext().getBean(name); + prefetchedVars = properties.get("spring.prefetched.vars").split(","); + } + + @Override + public Object[] invoke(Map request) throws IOException { + return realHandler.invoke(request); + } + + @Override + public String[] variablesNeedPrefetch() { + return prefetchedVars; + } + + +} + +.... + + + +.NginxSpringRewriteHandler +[source,java] +.... +package nginx.clojure.spring.core.example; + +import java.io.IOException; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import nginx.clojure.MiniConstants; +import nginx.clojure.java.ArrayMap; +import nginx.clojure.java.Constants; +import nginx.clojure.java.NginxJavaRequest; +import nginx.clojure.java.NginxJavaRingHandler; + + +@Service("myRewriteHandler") +public class NginxSpringRewriteHandler implements NginxJavaRingHandler { + + @Autowired + private ProxyTargetComputeService proxyTargetComputeService; + + public NginxSpringRewriteHandler() { + + } + + @Override + public Object[] invoke(Map r) throws IOException { + NginxJavaRequest req = (NginxJavaRequest)r; + String target = proxyTargetComputeService.computeTarget(req.getVariable("remote_addr"), req.getVariable("remote_port")); + req.setVariable("proxy_target", target); + return Constants.PHASE_DONE; + } + +} + +.... + + +.ProxyTargetComputeService +[source,java] +.... +package nginx.clojure.spring.core.example; + +import org.springframework.stereotype.Service; + +@Service("proxyTargetComputeService") +public class ProxyTargetComputeService { + + public String computeTarget(String ip, String port) { + int m = (ip + ":" + port).hashCode() % 2; + return m == 0 ? "127.0.0.1:8081" : "127.0.0.1:8082"; + } +} +.... + + +## How to Run + +[source,bash] +.... +git clone https://github.com/nginx-clojure/nginx-clojure +cd nginx-clojure/example-projects/spring-core-example + +## use mvn pakage to get spring-core-example-0.0.1-jar-with-dependencies.jar +mvn package + + +cd nginx-spring-work-dir +mkdir logs temp +wget https://sourceforge.net/projects/nginx-clojure/files/nginx-clojure-0.5.1.tar.gz +tar -zxvf nginx-clojure-0.5.1.tar.gz nginx-clojure-0.5.1/nginx-linux-x64 +mv nginx-clojure-0.5.1/nginx-linux-x64 nginx +./nginx +.... + +[source,bash] +.... +curl -v http://localhost:8080/hello +> GET /hello HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/7.64.0 +> Accept: */* +> +< HTTP/1.1 200 OK +< Date: Mon, 25 Nov 2019 03:21:11 GMT +< Content-Type: text/html +< Content-Length: 20 +< Connection: keep-alive +< Server: nginx-clojure/0.5.1 +Hello! 2 +.... diff --git a/example-projects/spring-core-example/nginx-spring-work-dir/.gitignore b/example-projects/spring-core-example/nginx-spring-work-dir/.gitignore new file mode 100644 index 00000000..d692e94f --- /dev/null +++ b/example-projects/spring-core-example/nginx-spring-work-dir/.gitignore @@ -0,0 +1,2 @@ +/logs/ +/nginx diff --git a/example-projects/spring-core-example/nginx-spring-work-dir/conf/logback.xml b/example-projects/spring-core-example/nginx-spring-work-dir/conf/logback.xml new file mode 100644 index 00000000..d51dae1d --- /dev/null +++ b/example-projects/spring-core-example/nginx-spring-work-dir/conf/logback.xml @@ -0,0 +1,30 @@ + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-10contextName %logger{36} - %msg%n + + + + + + + + logs/clojure-web-example-%d{yyyy-MM-dd}-${MY_PNO}.log + + 30 + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-10contextName %logger{36} - %msg%n + + + + + + + + + + + + diff --git a/example-projects/spring-core-example/nginx-spring-work-dir/conf/mime.types b/example-projects/spring-core-example/nginx-spring-work-dir/conf/mime.types new file mode 100644 index 00000000..52eebec9 --- /dev/null +++ b/example-projects/spring-core-example/nginx-spring-work-dir/conf/mime.types @@ -0,0 +1,80 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/x-javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt c; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg svgz; + image/webp webp; + + application/java-archive jar war ear; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.ms-excel xls; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream eot; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/example-projects/spring-core-example/nginx-spring-work-dir/conf/nginx.conf b/example-projects/spring-core-example/nginx-spring-work-dir/conf/nginx.conf new file mode 100644 index 00000000..323bbb5e --- /dev/null +++ b/example-projects/spring-core-example/nginx-spring-work-dir/conf/nginx.conf @@ -0,0 +1,115 @@ + +## Determines whether nginx should become a daemon and default is on. +daemon on; + +## If master_process is off, there will be only one nginx worker running. Only use it for debug propose. +## Default is on. +master_process on; + + +## Defines the number of worker processes every of which will be embedded one JVM instance. +## When auto is specified the number of worker processes will be the number of CPU hardware threads +worker_processes 1; + +error_log logs/error.log error; + +events { + ## Defines the number of connections per worker processes. + ## Note that this number includes all connections (e.g. connections with proxied servers, among others), + ## not only connections with clients. + worker_connections 1024; +} + + +http { + + ## include file mime.types which defines file type to mime type mappings + include mime.types; + + ## Default mime type for unknown file type + default_type application/octet-stream; + + ## access log, more details can be found from http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log + ## when do performance tests try to turn off it, viz. use `access_log off;` instead. + access_log logs/access.log combined; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + ## Enable gzip, default is off + #gzip on; + + ## Defines the path of JVM, when auto is used nginx-clojure will detect this by itself. + jvm_path auto; + + ## Define class path. When '/*' is used after a directory path all jar files and + ##sub-directories will be used as the jvm classpath + jvm_classpath "../target/*"; + + jvm_options "-Dnginx.clojure.logger.level=error"; + + ### jvm heap memory + #jvm_options "-Xms1024m"; + #jvm_options "-Xmx1024m"; + + ## Threads number for request handler thread pool on jvm, default is 0 which means disable + ## thread pool mode. Check more details from section 2.4 in http://nginx-clojure.github.io/configuration.html + jvm_workers 16; + + ## remote debug + #jvm_options "-Xdebug"; + #jvm_options "-Xrunjdwp:server=y,transport=dt_socket,address=840#{pno},suspend=n"; + + + jvm_handler_type 'java'; + + jvm_init_handler_name 'nginx.clojure.spring.core.example.NginxJvmInitHandler'; + + server { + listen 8080; + server_name localhost; + + set $proxy_target ""; + + location /hello { + rewrite_handler_type java; + rewrite_handler_name 'nginx.clojure.spring.core.example.NginxSpringHandlerWrapper'; + rewrite_handler_property 'spring.realHandler' 'myRewriteHandler'; + rewrite_handler_property 'spring.prefetched.vars' 'remote_addr,remote_port'; + proxy_pass http://$proxy_target; + } + + ## redirect server error pages to the static page /50x.html + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + + } + + + ## server 1 for test proxy pass + server { + listen 8081; + server_name localhost; + + location /hello { + alias html/h1.txt; + } + + } + + + ## server 2 for test proxy pass + server { + listen 8082; + server_name localhost; + + location /hello { + alias html/h2.txt; + } + + } +} diff --git a/example-projects/spring-core-example/nginx-spring-work-dir/html/h1.txt b/example-projects/spring-core-example/nginx-spring-work-dir/html/h1.txt new file mode 100644 index 00000000..142ee765 --- /dev/null +++ b/example-projects/spring-core-example/nginx-spring-work-dir/html/h1.txt @@ -0,0 +1 @@ +Hello! 1 \ No newline at end of file diff --git a/example-projects/spring-core-example/nginx-spring-work-dir/html/h2.txt b/example-projects/spring-core-example/nginx-spring-work-dir/html/h2.txt new file mode 100644 index 00000000..6bff9e37 --- /dev/null +++ b/example-projects/spring-core-example/nginx-spring-work-dir/html/h2.txt @@ -0,0 +1 @@ +Hello! 2 \ No newline at end of file diff --git a/example-projects/spring-core-example/pom.xml b/example-projects/spring-core-example/pom.xml new file mode 100644 index 00000000..ba5c15dc --- /dev/null +++ b/example-projects/spring-core-example/pom.xml @@ -0,0 +1,73 @@ + + 4.0.0 + nginx-clojure + spring-core-example + 0.0.1 + + 1.8 + 1.8 + + + + nginx-clojure + nginx-clojure + 0.5.3 + + + org.springframework + spring-core + 5.3.19 + + + org.springframework + spring-context + 5.3.19 + + + + + + maven-assembly-plugin + 3.0.0 + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + + + central + https://repo1.maven.org/maven2/ + + false + + + true + + + + clojars + https://repo.clojars.org/ + + true + + + true + + + + \ No newline at end of file diff --git a/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/NginxJvmInitHandler.java b/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/NginxJvmInitHandler.java new file mode 100644 index 00000000..550c75c9 --- /dev/null +++ b/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/NginxJvmInitHandler.java @@ -0,0 +1,19 @@ +package nginx.clojure.spring.core.example; + +import java.io.IOException; +import java.util.Map; + +import nginx.clojure.java.NginxJavaRingHandler; + +public class NginxJvmInitHandler implements NginxJavaRingHandler { + + public NginxJvmInitHandler() { + SpringExampleApplication.main(new String[0]); + } + + @Override + public Object[] invoke(Map request) throws IOException { + return null; + } + +} diff --git a/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/NginxSpringHandlerWrapper.java b/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/NginxSpringHandlerWrapper.java new file mode 100644 index 00000000..56200287 --- /dev/null +++ b/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/NginxSpringHandlerWrapper.java @@ -0,0 +1,38 @@ +package nginx.clojure.spring.core.example; + +import java.io.IOException; +import java.util.Map; + +import nginx.clojure.Configurable; +import nginx.clojure.java.NginxJavaRingHandler; + +public class NginxSpringHandlerWrapper implements NginxJavaRingHandler, Configurable { + + private NginxJavaRingHandler realHandler; + + private String[] prefetchedVars; + + + public NginxSpringHandlerWrapper() { + + } + + @Override + public void config(Map properties) { + String name = properties.get("spring.realHandler"); + realHandler = (NginxJavaRingHandler)SpringApplicationContextAware.getApplicationContext().getBean(name); + prefetchedVars = properties.get("spring.prefetched.vars").split(","); + } + + @Override + public Object[] invoke(Map request) throws IOException { + return realHandler.invoke(request); + } + + @Override + public String[] variablesNeedPrefetch() { + return prefetchedVars; + } + + +} diff --git a/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/NginxSpringRewriteHandler.java b/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/NginxSpringRewriteHandler.java new file mode 100644 index 00000000..e3be8750 --- /dev/null +++ b/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/NginxSpringRewriteHandler.java @@ -0,0 +1,32 @@ +package nginx.clojure.spring.core.example; + +import java.io.IOException; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import nginx.clojure.java.Constants; +import nginx.clojure.java.NginxJavaRequest; +import nginx.clojure.java.NginxJavaRingHandler; + + +@Service("myRewriteHandler") +public class NginxSpringRewriteHandler implements NginxJavaRingHandler { + + @Autowired + private ProxyTargetComputeService proxyTargetComputeService; + + public NginxSpringRewriteHandler() { + + } + + @Override + public Object[] invoke(Map r) throws IOException { + NginxJavaRequest req = (NginxJavaRequest)r; + String target = proxyTargetComputeService.computeTarget(req.getVariable("remote_addr"), req.getVariable("remote_port")); + req.setVariable("proxy_target", target); + return Constants.PHASE_DONE; + } + +} diff --git a/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/ProxyTargetComputeService.java b/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/ProxyTargetComputeService.java new file mode 100644 index 00000000..4845052f --- /dev/null +++ b/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/ProxyTargetComputeService.java @@ -0,0 +1,12 @@ +package nginx.clojure.spring.core.example; + +import org.springframework.stereotype.Service; + +@Service("proxyTargetComputeService") +public class ProxyTargetComputeService { + + public String computeTarget(String ip, String port) { + int m = (ip + ":" + port).hashCode() % 2; + return m == 0 ? "127.0.0.1:8081" : "127.0.0.1:8082"; + } +} \ No newline at end of file diff --git a/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/SpringApplicationContextAware.java b/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/SpringApplicationContextAware.java new file mode 100644 index 00000000..8505ee1c --- /dev/null +++ b/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/SpringApplicationContextAware.java @@ -0,0 +1,35 @@ +package nginx.clojure.spring.core.example; + +import java.util.concurrent.CountDownLatch; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +@Component +public class SpringApplicationContextAware implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + private static CountDownLatch countDownLatch = new CountDownLatch(1); + + public static ApplicationContext getApplicationContext() { + try { + countDownLatch.await(); + } catch (InterruptedException e) { + throw new RuntimeException("SpringApplicationContextAware countDownLatch interrupted error", e); + } + return applicationContext; + } + + public SpringApplicationContextAware() { + } + + @Override + public void setApplicationContext(ApplicationContext ctx) throws BeansException { + applicationContext = ctx; + countDownLatch.countDown(); + } + +} diff --git a/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/SpringExampleApplication.java b/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/SpringExampleApplication.java new file mode 100644 index 00000000..f0c89688 --- /dev/null +++ b/example-projects/spring-core-example/src/main/java/nginx/clojure/spring/core/example/SpringExampleApplication.java @@ -0,0 +1,17 @@ +package nginx.clojure.spring.core.example; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class SpringExampleApplication { + + public SpringExampleApplication() { + } + + public static void main(String[] args) { + ApplicationContext context = new AnnotationConfigApplicationContext("nginx.clojure.spring.core.example"); +// ProxyTargetComputeService proxyTargetComputeService = (ProxyTargetComputeService)context.getBean("proxyTargetComputeService"); +// System.out.println(proxyTargetComputeService.computeTarget("192.168.1.1", "5123")); + } + +} diff --git a/nginx-clojure-embed/.gitignore b/nginx-clojure-embed/.gitignore index 0d5f2eb0..fa39278e 100644 --- a/nginx-clojure-embed/.gitignore +++ b/nginx-clojure-embed/.gitignore @@ -11,3 +11,4 @@ /res /ngx-memoryleak /.lein-failures +/pom.xml.asc diff --git a/nginx-clojure-embed/README.md b/nginx-clojure-embed/README.md index 69aff146..a8eba933 100644 --- a/nginx-clojure-embed/README.md +++ b/nginx-clojure-embed/README.md @@ -102,7 +102,92 @@ User defined zones "location-user-defined", "" ``` +Build Notes +================ + +## build win64 dll + +### generate makefile + +``` +Administrator@who-8f29c72b513 ~/build-for-embed/nginx-clojure +$ cd nginx-clojure-embed/ + +$ export NGINX_SRC=c:/mingw/msys/1.0/home/administrator/build-for-embed/nginx-current + + +Administrator@who-8f29c72b513 ~/build-for-embed/nginx-clojure/nginx-clojure-embed +$ ./configure-win32 +javac is /c/Program Files/Java/jdk1.8.0_11/bin/javac +java is /c/Program Files/Java/jdk1.8.0_11/bin/java +checking for OS +``` + +### openssl 1.x + +modify auto/lib/openssl/makefile.msvc to + +``` +all: + cd $(OPENSSL) + + perl Configure VC-WIN64A no-shared \ + --prefix="%cd%/openssl" \ + --openssldir="%cd%/openssl/ssl" \ + $(OPENSSL_OPT) + + if exist ms\do_ms.bat ( \ + ms\do_win64a \ + && $(MAKE) -f ms\nt.mak \ + && $(MAKE) -f ms\nt.mak install \ + ) else ( \ + $(MAKE) \ + && $(MAKE) install_sw \ + ) +``` + +### avoid warning + +src\os\win32\nginx_win32_config.h + + +line 32 : + +``` + +#ifdef _MSC_VER +#pragma warning(disable:4201) +#pragma warning(disable:4306) +#pragma warning(disable:4244) +#pragma warning(disable:4267) +#pragma warning(disable:4334) +#pragma warning(disable:4018) +#pragma warning(disable:4133) +#pragma warning(disable:4214) +#endif + +``` + +## modify make file + +build-for-embed\nginx-current\objs\Makefile + +line 1561: +remove `rc` line + +``` +rc -foobjs/nginx.res $(CORE_INCS) src/os/win32/nginx.rc +``` + +### do make + +``` +$ cd ../.. +$ nmake -f objs/Makefile NGX_WIN64=1 embed +``` + + License ================ -Copyright © 2013-2017 Zhang, Yuexiang (xfeep) and released under the BSD 3-Clause license. \ No newline at end of file +Copyright © 2013-2022 Zhang, Yuexiang (xfeep) and released under the BSD 3-Clause license. \ No newline at end of file diff --git a/nginx-clojure-embed/macos-issue b/nginx-clojure-embed/macos-issue index 146c697a..65441fef 100644 --- a/nginx-clojure-embed/macos-issue +++ b/nginx-clojure-embed/macos-issue @@ -6,6 +6,6 @@ gcc -c -fpic -fvisibility=hidden -I "/Library/Java/JavaVirtualMa /Users/whomac/Dev/build-for-embed/nginx-clojure/nginx-clojure-embed/../src/c/${ncs}.c done -gcc -c -fpic -fvisibility=hidden -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include" -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include/darwin" -I "/Users/whomac/Dev/build-for-embed/nginx-clojure/nginx-clojure-embed/../src/c" -DNGX_CLOJURE_BE_SILENT_WITHOUT_JVM -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include" -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include/darwin" -pipe -O -Wall -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I ../pcre-8.40 -I objs -I src/http -I src/http/modules -I /Users/whomac/Dev/build-for-embed/nginx-clojure/nginx-clojure-embed/../src/c \ +gcc -c -fpic -fvisibility=hidden -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include" -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include/darwin" -I "/Users/whomac/Dev/build-for-embed/nginx-clojure/nginx-clojure-embed/../src/c" -DNGX_CLOJURE_BE_SILENT_WITHOUT_JVM -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include" -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include/darwin" -pipe -O -Wall -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I ../pcre-8.40 -I objs -I src/http -I src/http/modules -I src/http/v2 -I /Users/whomac/Dev/build-for-embed/nginx-clojure/nginx-clojure-embed/../src/c \ -o objs/addon/c/ngx_http_clojure_embed.o \ /Users/whomac/Dev/build-for-embed/nginx-clojure/nginx-clojure-embed/src/c/ngx_http_clojure_embed.c diff --git a/nginx-clojure-embed/pom.xml b/nginx-clojure-embed/pom.xml index 56f14af4..75500331 100644 --- a/nginx-clojure-embed/pom.xml +++ b/nginx-clojure-embed/pom.xml @@ -1,9 +1,10 @@ - + + 4.0.0 nginx-clojure nginx-clojure-embed jar - 0.5.0 + 0.6.1 nginx-clojure-embed Embeding Nginx-Clojure into a standard clojure/java/groovy app without additional Nginx process https://github.com/nginx-clojure/nginx-clojure/tree/master/nginx-clojure-embed @@ -86,7 +87,7 @@ nginx-clojure nginx-clojure - 0.5.0 + 0.6.1 org.clojure @@ -121,7 +122,7 @@ junit junit - 4.11 + 4.13.1 test @@ -130,4 +131,4 @@ + https://codeberg.org/leiningen/leiningen --> diff --git a/nginx-clojure-embed/project.clj b/nginx-clojure-embed/project.clj index e2e96e25..b6152d55 100644 --- a/nginx-clojure-embed/project.clj +++ b/nginx-clojure-embed/project.clj @@ -1,11 +1,11 @@ -(defproject nginx-clojure/nginx-clojure-embed "0.5.0" +(defproject nginx-clojure/nginx-clojure-embed "0.6.1" :description "Embeding Nginx-Clojure into a standard clojure/java/groovy app without additional Nginx process" :url "https://github.com/nginx-clojure/nginx-clojure/tree/master/nginx-clojure-embed" :license {:name "BSD 3-Clause license" :url "http://opensource.org/licenses/BSD-3-Clause"} :plugins [] :dependencies [ - [nginx-clojure/nginx-clojure "0.5.0"] + [nginx-clojure/nginx-clojure "0.6.1"] ] :source-paths ["src/clojure"] :java-source-paths ["src/java"] @@ -24,6 +24,6 @@ [compojure "1.1.6"] [clj-http "0.7.8"] [stylefruits/gniazdo "1.1.2"] - [junit/junit "4.11"] + [junit/junit "4.13.1"] ]}} ) diff --git a/nginx-clojure-embed/src/c/ngx_http_clojure_embed.c b/nginx-clojure-embed/src/c/ngx_http_clojure_embed.c index 14d8633b..709c168b 100644 --- a/nginx-clojure-embed/src/c/ngx_http_clojure_embed.c +++ b/nginx-clojure-embed/src/c/ngx_http_clojure_embed.c @@ -236,7 +236,12 @@ static ngx_int_t ngx_http_clojure_embed_start(int argc, char *const *argv){ ngx_pid = ngx_getpid(); +#if nginx_version < 1020000 log = ngx_log_init(NULL); +#else + log = ngx_log_init(NULL, NULL); +#endif + if (log == NULL) { ngx_http_clojure_embed_return_error("ngx_log_init"); } diff --git a/nginx-jersey/project.clj b/nginx-jersey/project.clj index f6d5aac8..59587e0f 100644 --- a/nginx-jersey/project.clj +++ b/nginx-jersey/project.clj @@ -1,4 +1,4 @@ -(defproject nginx-clojure/nginx-jersey "0.1.5" +(defproject nginx-clojure/nginx-jersey "0.2.1" :description "Intergrate Jersey into Nginx by Nignx-Clojure Module so that Nginx can Support Java standard RESTful Web Services (JAX-RS)" :url "https://github.com/nginx-clojure/nginx-clojure/nginx-jersey" @@ -6,7 +6,7 @@ :url "http://opensource.org/licenses/BSD-3-Clause"} :dependencies [ [javax.ws.rs/javax.ws.rs-api "2.0.1"] - [nginx-clojure/nginx-clojure "0.5.0"] + [nginx-clojure/nginx-clojure "0.6.1"] ] :source-paths ["src/clojure"] :java-source-paths ["src/java"] diff --git a/nginx-tomcat8/project.clj b/nginx-tomcat8/project.clj index 3249cea6..12805474 100644 --- a/nginx-tomcat8/project.clj +++ b/nginx-tomcat8/project.clj @@ -1,11 +1,11 @@ -(defproject nginx-clojure/nginx-tomcat8 "0.2.5" +(defproject nginx-clojure/nginx-tomcat8 "0.3.1" :description "Embed Tomcat into Nginx by Nignx-Clojure Module so that Nginx can Support Java Standard Web Applications" :url "https://github.com/nginx-clojure/nginx-clojure/nginx-tomcat8" :license {:name "BSD 3-Clause license" :url "http://opensource.org/licenses/BSD-3-Clause"} :plugins [] :dependencies [ - [nginx-clojure/nginx-clojure "0.5.0"] + [nginx-clojure/nginx-clojure "0.6.1"] [org.apache.tomcat/tomcat-catalina "8.0.27"] ] :source-paths ["src/clojure"] diff --git a/project.clj b/project.clj index 0db5a3bc..0eff6812 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject nginx-clojure/nginx-clojure "0.5.0" +(defproject nginx-clojure/nginx-clojure "0.6.1" :description "Nginx module for clojure or groovy or java programming" :url "https://github.com/nginx-clojure/nginx-clojure" :license {:name "BSD 3-Clause license" @@ -16,7 +16,12 @@ :global-vars {*warn-on-reflection* true *assert* false} :java-source-paths ["src/java"] - :javac-options ["-target" "1.8" "-source" "1.8" "-g" "-nowarn"] + :javac-options ["-target" "1.8" + "-source" "1.8" + "-g" + "-Xlint:unchecked" + ;;"-nowarn" + ] ;; Directory in which to place AOT-compiled files. Including %s will ;; splice the :target-path into this value. :compile-path "target/classes" @@ -35,7 +40,7 @@ } :codox {:source-paths ["src/clojure" "nginx-clojure-embed/src/clojure"] - :project {:name "nginx-clojure", :version "0.5.0", :description "N/A"} + :project {:name "nginx-clojure", :version "0.5.3", :description "N/A"} :output-path "../nginx-clojure.github.io/api" ;:metadata {:doc/format :markdown} :namespaces ["nginx.clojure.core" "nginx.clojure.session" "nginx.clojure.embed"] @@ -46,14 +51,18 @@ [org.clojure/clojure "1.9.0"] [org.clojure/tools.reader "0.8.1"]] } - :dev {:dependencies [;only for test / compile usage + :dev { + :test-paths ["src/test/clojure", "src/test/java"] + :dependencies [;only for test / compile usage [org.clojure/clojure "1.9.0"] [ring/ring-core "1.7.1"] [compojure "1.1.6"] [clj-http "0.7.8"] - [junit/junit "4.11"] + [clj-http-lite "0.3.0"] + [junit/junit "4.13.1"] [org.clojure/java.jdbc "0.3.3"] [mysql/mysql-connector-java "5.1.30"] + [redis.clients/jedis "3.1.0"] ;for test file upload with ring-core which need it [javax.servlet/servlet-api "2.5"] [org.clojure/data.json "0.2.5"] @@ -63,10 +72,39 @@ [javax.xml.bind/jaxb-api "2.3.1"] [org.clojure/tools.trace "0.7.10"] ]} - :unittest { - :jvm-opts ["-javaagent:target/nginx-clojure-0.5.0.jar=mb" - "-Dnginx.clojure.wave.udfs=pure-clj.txt,mysql-jdbc.txt,compojure.txt,compojure-http-clj.txt" - "-Xbootclasspath/a:target/nginx-clojure-0.5.0.jar"] + :nativeCoroutine { + :source-paths ["src/clojure"] + :target-path "target/" + :global-vars {*warn-on-reflection* true + *assert* false} + :java-source-paths ["src/java", "src/nativeCoroutine"] + :test-paths ["src/test/clojure", "src/test/java"] + :dependencies [;only for test / compile usage + [org.clojure/clojure "1.9.0"] + [ring/ring-core "1.7.1"] + [compojure "1.1.6"] + [clj-http "0.7.8"] + [clj-http-lite "0.3.0"] + [junit/junit "4.13.1"] + [org.clojure/java.jdbc "0.3.3"] + [mysql/mysql-connector-java "5.1.30"] + [redis.clients/jedis "3.1.0"] + ;for test file upload with ring-core which need it + [javax.servlet/servlet-api "2.5"] + [org.clojure/data.json "0.2.5"] + [org.codehaus.jackson/jackson-mapper-asl "1.9.13"] + [org.codehaus.groovy/groovy "2.5.8"] + [stylefruits/gniazdo "1.1.2"] + [javax.xml.bind/jaxb-api "2.3.1"] + [org.clojure/tools.trace "0.7.10"] + ] + } + :jdk17unittest { + :jvm-opts ["-javaagent:target/nginx-clojure-0.6.1.jar=mb" + "-Dfile.encoding=UTF-8" + "-Dnginx.clojure.wave.udfs=pure-clj.txt,compojure.txt,compojure-http-clj.txt,mysql-jdbc.txt,test-groovy.txt" + "--add-opens=java.base/java.lang=ALL-UNNAMED" "--add-opens=java.base/sun.nio.cs=ALL-UNNAMED" "--add-opens=java.base/sun.nio.ch=ALL-UNNAMED" + "-Xbootclasspath/a:target/nginx-clojure-0.6.1.jar"] :junit-options {:fork "on"} :java-source-paths ["test/java" "test/clojure"] :test-paths ["src/test/clojure"] @@ -78,11 +116,39 @@ [ring/ring-core "1.7.1"] [compojure "1.1.6"] [clj-http "0.7.8"] - [junit/junit "4.11"] + [clj-http-lite "0.3.0"] + [junit/junit "4.13.1"] [org.clojure/java.jdbc "0.3.3"] [org.codehaus.jackson/jackson-mapper-asl "1.9.13"] [javax.xml.bind/jaxb-api "2.3.1"] ;[mysql/mysql-connector-java "5.1.30"] + [redis.clients/jedis "3.1.0"] + [org.clojure/tools.trace "0.7.10"] + ] + } + :unittest { + :jvm-opts ["-javaagent:target/nginx-clojure-0.6.1.jar=mb" + "-Dfile.encoding=UTF-8" + "-Dnginx.clojure.wave.udfs=pure-clj.txt,compojure.txt,compojure-http-clj.txt,mysql-jdbc.txt,test-groovy.txt" + "-Xbootclasspath/a:target/nginx-clojure-0.6.1.jar"] + :junit-options {:fork "on"} + :java-source-paths ["test/java" "test/clojure"] + :test-paths ["src/test/clojure"] + :source-paths ["test/clojure" "test/java" "test/nginx-working-dir/coroutine-udfs"] + :junit ["test/java"] + :compile-path "target/testclasses" + :dependencies [ + [org.clojure/clojure "1.9.0"] + [ring/ring-core "1.7.1"] + [compojure "1.1.6"] + [clj-http "0.7.8"] + [clj-http-lite "0.3.0"] + [junit/junit "4.13.1"] + [org.clojure/java.jdbc "0.3.3"] + [org.codehaus.jackson/jackson-mapper-asl "1.9.13"] + [javax.xml.bind/jaxb-api "2.3.1"] + ;[mysql/mysql-connector-java "5.1.30"] + [redis.clients/jedis "3.1.0"] [org.clojure/tools.trace "0.7.10"] ] } @@ -105,7 +171,8 @@ [ring/ring-core "1.7.1"] [compojure "1.1.6"] [clj-http "0.7.8"] - [junit/junit "4.11"] + [clj-http-lite "0.3.0"] + [junit/junit "4.13.1"] [org.clojure/java.jdbc "0.3.3"] [org.clojure/tools.nrepl "0.2.3"] ;for test file upload with ring-core which need it @@ -115,7 +182,48 @@ [stylefruits/gniazdo "1.1.2"] [javax.xml.bind/jaxb-api "2.3.1"] ;[mysql/mysql-connector-java "5.1.30"] + [redis.clients/jedis "3.1.0"] [org.clojure/tools.trace "0.7.10"] ] - } + } + + :jdk19cljremotetest { + :jvm-opts ["--enable-preview" + "--add-opens=java.base/jdk.internal.vm=ALL-UNNAMED" + "--add-opens=java.base/java.lang=ALL-UNNAMED" + "--add-opens=java.base/sun.nio.cs=ALL-UNNAMED" + "--add-opens=java.base/sun.nio.ch=ALL-UNNAMED"] + :java-source-paths ["test/java" "test/clojure"] + :test-paths ["src/test/clojure"] + :source-paths ["test/clojure" "test/java" "test/nginx-working-dir/coroutine-udfs"] + :compile-path "target/testclasses" + :test-selectors {:default (fn [m] (and (:remote m) (not (:async m)) (not (:jdbc m)))) + :async :async + :jdbc :jdbc + :no-async (fn [m] (and (:remote m) (not (:async m)))) + :access-handler :access-handler + :rewrite-handler :rewrite-handler + :websocket :websocket + :keepalive :keepalive + :all :remote} + :dependencies [ + [org.clojure/clojure "1.9.0"] + [ring/ring-core "1.7.1"] + [compojure "1.1.6"] + [clj-http "0.7.8"] + [clj-http-lite "0.3.0"] + [junit/junit "4.13.1"] + [org.clojure/java.jdbc "0.3.3"] + [org.clojure/tools.nrepl "0.2.3"] + ;for test file upload with ring-core which need it + [javax.servlet/servlet-api "2.5"] + [org.codehaus.jackson/jackson-mapper-asl "1.9.13"] + [org.clojure/data.json "0.2.5"] + [stylefruits/gniazdo "1.1.2"] + [javax.xml.bind/jaxb-api "2.3.1"] + ;[mysql/mysql-connector-java "5.1.30"] + [redis.clients/jedis "3.1.0"] + [org.clojure/tools.trace "0.7.10"] + ] + } }) diff --git a/src/c/ngx_http_clojure_mem.c b/src/c/ngx_http_clojure_mem.c index c598f86e..4a0be636 100644 --- a/src/c/ngx_http_clojure_mem.c +++ b/src/c/ngx_http_clojure_mem.c @@ -164,8 +164,9 @@ static u_char WS_CLOSE_NO_EXTENSION[] = { 0x03, 0xf2 };*/ /*1011 indicates that a server is terminating the connection * because it encountered an unexpected condition that prevented it from fulfilling the request.*/ +#if (NGX_ZLIB) static u_char WS_CLOSE_UNEXPECTED_CONDITION[] = { 0x03, 0xf3 }; - +#endif /*1012 indicates that the service will be restarted. static u_char WS_CLOSE_SERVICE_RESTART[] = { 0x03, 0xf4 };*/ @@ -174,8 +175,9 @@ static u_char WS_CLOSE_TRY_AGAIN_LATER[] = { 0x03, 0xf5 };*/ /*1015 is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. static u_char WS_CLOSE_TLS_HANDSHAKE_FAILURE[] = { 0x03, 0xf7 };*/ - +#if (NGX_ZLIB) static u_char WS_PMCE_TAIL_DATA[] = {0x00, 0x00, 0xff, 0xff}; +#endif ngx_int_t ngx_http_clojure_set_elt_header(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_table_elt_t **ph; @@ -240,6 +242,9 @@ ngx_int_t ngx_http_clojure_prepare_server_header(ngx_http_request_t *r) { return NGX_ERROR; } h->hash = 1; +#if (nginx_version >= 1023000) + h->next = NULL; +#endif r->headers_out.server = h; ngx_str_set(&h->key, "Server"); } @@ -672,6 +677,7 @@ static ngx_chain_t * ngx_http_clojure_get_and_copy_bufs(size_t page_size, ngx_po return cl; } +#if defined(nginx_version) && (nginx_version < 1025000) /*copy from ngx_http_request.c*/ static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc) @@ -704,6 +710,7 @@ ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc) ngx_http_free_request(r, rc); ngx_http_close_connection(c); } +#endif static void ngx_http_clojure_hijack_async_timeout_handler(ngx_http_request_t *r) { ngx_connection_t *c = r->connection; @@ -1516,7 +1523,10 @@ static ngx_int_t ngx_http_clojure_hijack_send(ngx_http_request_t *r, u_char *me ngx_http_clojure_module_ctx_t *ctx; ngx_chain_t *in; size_t page_size; + +#if (NGX_ZLIB) int need_reset_deflate_ctx = 0; +#endif if (r->pool == NULL) { if (message) { /*message can be NULL if send a close event without additional data*/ @@ -1564,14 +1574,16 @@ static ngx_int_t ngx_http_clojure_hijack_send(ngx_http_request_t *r, u_char *me } } if (!(flag & (NGX_CLOJURE_BUF_WEBSOCKET_CLOSE_FRAME | NGX_CLOJURE_BUF_WEBSOCKET_PONG_FRAME))) { - if (!ctx->wsctx->ffm) { + if (!wsctx->ffm) { flag |= NGX_CLOJURE_BUF_WEBSOCKET_CONTINUE_FRAME; - }else { + } else { +#if (NGX_ZLIB) need_reset_deflate_ctx = 1; - ctx->wsctx->ffm = 0; +#endif + wsctx->ffm = 0; } if (flag & NGX_CLOJURE_BUF_FLUSH_FLAG) { - ctx->wsctx->ffm = 1; + wsctx->ffm = 1; } #if (NGX_ZLIB) if (wsctx->premsg_deflate) { @@ -1884,6 +1896,14 @@ TOP_WHILE : buf->pos += wsctx->left; } wsctx->opcode = buf->pos[0] & 0x0f; + +#if (NGX_ZLIB) + if (wsctx->premsg_deflate && wsctx->fin) { + /*the last frame is FIN frame so we need update compressed flag*/ + wsctx->compressed = (buf->pos[0] & 0x40) >> 6; + } +#endif + if (wsctx->fin || (buf->pos[0] & 0x0f) == NGX_HTTP_CLOJURE_WEBSOCKET_OPCODE_CLOSE) { /*last frame is FIN frame*/ wsctx->cont = 0; }else { @@ -1899,8 +1919,8 @@ TOP_WHILE : if ( !(wsctx->opcode & 0x8) ) { /*is not control frame*/ wsctx->fin = (buf->pos[0] >> 7) & 1; - }else { - if (!(buf->pos[0] >> 7) & 1) { /*control frame must be FIN*/ + } else { + if ((!(buf->pos[0] >> 7)) & 1) { /*control frame must be FIN*/ goto_close(WS_CLOSE_PROTOCOL_ERROR); } @@ -2066,7 +2086,7 @@ TOP_WHILE : wsctx->len -= size; pc = buf->pos; #if (NGX_ZLIB) - if (wsctx->premsg_deflate && !(wsctx->opcode & 0x8)) { /*PMCEs operate only on data messages.*/ + if (wsctx->compressed && !(wsctx->opcode & 0x8)) { /*PMCEs operate only on data messages.*/ u_char deflate_buf[8192]; u_char *deflate_buf_pos; int zrc = 0; @@ -2285,9 +2305,9 @@ static void ngx_http_clojure_websocket_free(void *opaque, void *address) { ngx_int_t ngx_http_clojure_websocket_upgrade(ngx_http_request_t * r) { ngx_http_clojure_module_ctx_t *ctx; #if (NGX_HAVE_SHA1 || nginx_version >= 1011002) - ngx_http_clojure_websocket_ctx_t *wsctx = NULL; - ngx_int_t rc = NGX_OK; - ngx_table_elt_t *key = NULL; + ngx_http_clojure_websocket_ctx_t *wsctx = NULL; + ngx_int_t rc = NGX_OK; + ngx_table_elt_t *key = NULL; ngx_table_elt_t *accept; ngx_table_elt_t *cver = NULL; ngx_sha1_t sha1_ctx; @@ -2333,6 +2353,9 @@ ngx_int_t ngx_http_clojure_websocket_upgrade(ngx_http_request_t * r) { accept = ngx_list_push(&r->headers_out.headers); accept->hash = 1; +#if (nginx_version >= 1023000) + accept->next = NULL; +#endif ngx_str_set(&accept->key, "Sec-WebSocket-Accept"); accept->value.len = 28; accept->value.data = ngx_palloc(r->pool, 28); @@ -2346,20 +2369,22 @@ ngx_int_t ngx_http_clojure_websocket_upgrade(ngx_http_request_t * r) { wsctx = ctx->wsctx; if (wsctx == NULL) { +#if (NGX_ZLIB) ngx_table_elt_t *in_extensions = NULL; ngx_table_elt_t *out_extensions = NULL; int in_max_window_bits = 15; int out_max_window_bits = 15; /*TODO: negotiate in_max_window_bits & out_max_window_bits, e.g. "permessage-deflate;server_max_window_bits=11;client_max_window_bits=11"*/ char out_extensions_tmp_value[1024] = "permessage-deflate"; - +#endif wsctx = ngx_pcalloc(r->pool, sizeof(ngx_http_clojure_websocket_ctx_t)); - wsctx->ffm = 1; - wsctx->fin = 1; - #if (NGX_ZLIB) + wsctx->ffm = 1; + wsctx->fin = 1; +#if (NGX_ZLIB) ngx_http_clojure_get_header(r->headers_in.headers, "Sec-WebSocket-Extensions", in_extensions); if (in_extensions != NULL && ngx_strcasestrn(in_extensions->value.data, "permessage-deflate", 18-1)) { wsctx->premsg_deflate = 1; + wsctx->compressed = 0; if (ngx_strcasestrn(in_extensions->value.data, "client_no_context_takeover", 26-1)) { wsctx->out_no_ctx_takeover = 1; sprintf(out_extensions_tmp_value+strlen(out_extensions_tmp_value), ";%s", "client_no_context_takeover"); @@ -2371,6 +2396,9 @@ ngx_int_t ngx_http_clojure_websocket_upgrade(ngx_http_request_t * r) { /*TODO: negotiate in_max_window_bits & out_max_window_bits*/ out_extensions = ngx_list_push(&r->headers_out.headers); out_extensions->hash = 1; +#if (nginx_version >= 1023000) + out_extensions->next = NULL; +#endif ngx_str_set(&out_extensions->key, "Sec-WebSocket-Extensions"); out_extensions->value.data = ngx_pcalloc(r->pool, strlen(out_extensions_tmp_value)+1); out_extensions->value.len = strlen(out_extensions_tmp_value); @@ -2387,7 +2415,7 @@ ngx_int_t ngx_http_clojure_websocket_upgrade(ngx_http_request_t * r) { goto UPGRADE_DONE; } } - #endif +#endif } @@ -3053,25 +3081,81 @@ ngx_int_t ngx_http_clojure_filter_continue_next_body_filter(ngx_http_request_t * return ngx_http_clojure_next_body_filter(r, in); } -static jlong JNICALL jni_ngx_http_filter_continue_next(JNIEnv *env, jclass cls, jlong req, jlong chain) { +static jlong JNICALL jni_ngx_http_filter_continue_next(JNIEnv *env, jclass cls, jlong req, jlong chain, jlong old_chain) { ngx_http_request_t *r = (ngx_http_request_t*) (uintptr_t) req; ngx_http_clojure_module_ctx_t *ctx; ngx_chain_t *in = (ngx_chain_t *) (uintptr_t) chain; + ngx_chain_t *old_in = (ngx_chain_t*)(uintptr_t) old_chain; ngx_int_t rc; ngx_http_clojure_get_ctx(r, ctx); ngx_http_clojure_try_unset_reload_delay_timer(ctx, "jni_ngx_http_filter_continue_next"); + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "jni_ngx_http_filter_continue_next, chain=%" PRIu64 ", old_chain=%" PRIu64, chain, old_chain); + if (chain < 0) { /*header filter*/ rc = ngx_http_clojure_next_header_filter(r); + + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + return rc; + } + ctx->wait_for_header_filter = 0; if (ctx->pending_body_filter) { rc = ngx_http_clojure_next_body_filter(r, ctx->pending); } + + + if (r->upstream && (chain == NGX_HTTP_HEADER_FILTER_IN_THREADPOOL) && r->upstream->peer.connection) { + r->upstream->read_event_handler(r, r->upstream); + r->write_event_handler(r); + } + return rc; } else { - return ngx_http_clojure_filter_continue_next_body_filter(r, in); +#if (NGX_DEBUG) + int len = 0; +#endif + ngx_chain_t *ci = in; + int is_last = 0; + while (ci) { + if (ci->buf->last_buf) { + is_last = 1; + } +#if (NGX_DEBUG) + len += ngx_buf_size(ci->buf); +#endif + ci = ci->next; + } + +#if (NGX_DEBUG) + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "jni_ngx_http_filter_continue_next, chain=%" PRIu64 ", size=%d, is_last=%d", chain, len, is_last); +#endif + rc = ngx_http_clojure_filter_continue_next_body_filter(r, in); + + if (!is_last && old_in) { + /*Mark them as consumed*/ + while (old_in) { + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "make consumed, r=%" PRIu64 ", size=%d flush=%d last=%d count=%d", + (uintptr_t)r, ngx_buf_size(old_in->buf), old_in->buf->flush, old_in->buf->last_in_chain, r->count); + old_in->buf->pos = old_in->buf->last; + old_in->buf->file_pos = old_in->buf->file_last; + old_in = old_in->next; + } + + if (!ctx->wait_for_header_filter) { + if (r->upstream && r->count > 1 && r->upstream->peer.connection) { + ngx_chain_update_chains(r->pool, &r->upstream->free_bufs, &r->upstream->busy_bufs, &r->upstream->out_bufs, r->upstream->output.tag); + r->upstream->read_event_handler(r, r->upstream); + r->write_event_handler(r); + } + } + } + + return rc; } } @@ -3476,6 +3560,11 @@ static jlong JNICALL jni_ngx_http_clojure_mem_get_headers_items(JNIEnv *env, jcl h = (ngx_table_elt_t *)(uintptr_t)*pvalue; h->key.data = (u_char*)"Content-Type"; h->key.len = sizeof("Content-Type") - 1; + h->value.data = hout->content_type.data; + h->value.len = hout->content_type.len; +#if (nginx_version >= 1023000) + h->next = NULL; +#endif return 1; } i--; @@ -3686,6 +3775,7 @@ static jlong JNICALL jni_ngx_http_clojure_mem_inc_req_count(JNIEnv *env, jclass ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "jni_ngx_http_clojure_mem_inc_req_count, old : %d, new : %d", old, n); return old; } + ngx_log_error(NGX_LOG_ALERT, ngx_http_clojure_global_cycle->log, 0, "jni_ngx_http_clojure_mem_inc_req_count invoke on a released request!"); return -1; } @@ -4186,7 +4276,7 @@ int ngx_http_clojure_init_memory_util(ngx_core_conf_t *ccf, ngx_http_core_srv_c {"ngx_http_output_filter", "(JJ)J", jni_ngx_http_output_filter}, {"ngx_http_finalize_request", "(JJ)V", jni_ngx_http_finalize_request}, {"ngx_http_filter_finalize_request", "(JJ)V", jni_ngx_http_filter_finalize_request}, - {"ngx_http_filter_continue_next", "(JJ)J", jni_ngx_http_filter_continue_next}, + {"ngx_http_filter_continue_next", "(JJJ)J", jni_ngx_http_filter_continue_next}, {"ngx_http_discard_request_body", "(J)J", jni_ngx_http_discard_request_body}, {"ngx_http_clojure_mem_init_ngx_buf", "(JLjava/lang/Object;JJI)J", jni_ngx_http_clojure_mem_init_ngx_buf}, //jlong buf, jlong obj, jlong offset, jlong len, jint last_buf {"ngx_http_clojure_mem_build_temp_chain", "(JJLjava/lang/Object;JJ)J", jni_ngx_http_clojure_mem_build_temp_chain}, @@ -4267,6 +4357,8 @@ int ngx_http_clojure_init_memory_util(ngx_core_conf_t *ccf, ngx_http_core_srv_c MEM_INDEX[NGX_HTTP_CLOJURE_TEL_VALUE_IDX] = NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET; MEM_INDEX[NGX_HTTP_CLOJURE_TEL_LOWCASE_KEY_IDX] = NGX_HTTP_CLOJURE_TEL_LOWCASE_KEY_OFFSET; + MEM_INDEX[NGX_HTTP_CLOJURE_TEL_NEXT_IDX] = NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET; + MEM_INDEX[NGX_HTTP_CLOJURE_CHAINT_SIZE_IDX] = NGX_HTTP_CLOJURE_CHAINT_SIZE; MEM_INDEX[NGX_HTTP_CLOJURE_CHAIN_BUF_IDX] = NGX_HTTP_CLOJURE_CHAIN_BUF_OFFSET; MEM_INDEX[NGX_HTTP_CLOJURE_CHAIN_NEXT_IDX] = NGX_HTTP_CLOJURE_CHAIN_NEXT_OFFSET; @@ -4481,7 +4573,7 @@ int ngx_http_clojure_register_script(ngx_int_t phase, ngx_str_t *handler_type, return NGX_HTTP_CLOJURE_JVM_OK; } -int ngx_http_clojure_eval(int cid, ngx_http_request_t *r, ngx_chain_t *c) { +int ngx_http_clojure_eval(int cid, ngx_http_request_t *r, void *c) { JNIEnv *env = jvm_env; int rc; /* log_debug1(ngx_http_clojure_global_cycle->log, "ngx clojure eval request: %ul", (uintptr_t)r);*/ diff --git a/src/c/ngx_http_clojure_mem.h b/src/c/ngx_http_clojure_mem.h index 9c399261..01983d1b 100644 --- a/src/c/ngx_http_clojure_mem.h +++ b/src/c/ngx_http_clojure_mem.h @@ -41,21 +41,28 @@ typedef unsigned __int64 uint64_t; #define JVM_CP_SEP_S ":" #endif -#define nginx_clojure_ver 5000 /*0.5.0*/ +#define nginx_clojure_ver 6001 /*0.6.1*/ /*the least jar version required*/ -#define nginx_clojure_required_rt_lver 5000 +#define nginx_clojure_required_rt_lver 5002 -#define NGINX_CLOJURE_VER_NUM_STR "0.5.0" +#define NGINX_CLOJURE_VER_NUM_STR "0.6.1" #define NGINX_CLOJURE_VER "nginx-clojure/" NGINX_CLOJURE_VER_NUM_STR +/*fake phase for load balance handler*/ +#define NGX_HTTP_LOAD_BALANCE_PHASE 16 + /*fake phase for filter*/ #define NGX_HTTP_INIT_PROCESS_PHASE 17 #define NGX_HTTP_HEADER_FILTER_PHASE 18 #define NGX_HTTP_BODY_FILTER_PHASE 19 #define NGX_HTTP_EXIT_PROCESS_PHASE 20 +/*fake chain for header filter*/ +#define NGX_HTTP_HEADER_FILTER -1 +#define NGX_HTTP_HEADER_FILTER_IN_THREADPOOL -2 + typedef struct { ngx_str_t name; ngx_http_header_handler_pt handler; @@ -82,13 +89,16 @@ typedef struct { unsigned enable_body_filter :1; unsigned enable_access_handler : 1; unsigned enable_log_handler : 1; + unsigned enable_load_balancer : 1; ngx_str_t jvm_handler_type; ngx_str_t jvm_init_handler_code; ngx_int_t jvm_init_handler_id; ngx_str_t jvm_init_handler_name; + ngx_array_t *jvm_init_handler_properties; ngx_str_t jvm_exit_handler_code; ngx_int_t jvm_exit_handler_id; ngx_str_t jvm_exit_handler_name; + ngx_array_t *jvm_exit_handler_properties; ngx_hash_t headers_out_holder_hash; } ngx_http_clojure_main_conf_t; @@ -140,6 +150,27 @@ typedef struct { size_t write_page_size; } ngx_http_clojure_loc_conf_t; + +typedef struct { + unsigned enable_load_balancer :1; + ngx_str_t load_balancer_type; + ngx_str_t load_balancer_code; + ngx_int_t load_balancer_id; + ngx_str_t load_balancer_name; + ngx_array_t *load_balancer_properties; +} ngx_http_clojure_srv_conf_t; + +typedef struct { + /* the round robin data must be first */ + ngx_http_upstream_rr_peer_data_t rrp; + ngx_http_clojure_srv_conf_t *conf; + ngx_http_request_t *r; + ngx_uint_t peer_pos_or_len; + u_char *peer_url; + /* ngx_uint_t tries; */ + /* ngx_event_get_peer_pt get_rr_peer; */ +} ngx_http_clojure_upstream_load_balancer_peer_data_t; + typedef struct ngx_http_clojure_listener_node_s { void *listener; void *data; @@ -172,6 +203,7 @@ typedef struct { #if (NGX_ZLIB) /*for premessage-deflate --websocket compression extension*/ unsigned premsg_deflate : 1; + unsigned compressed : 1; unsigned in_no_ctx_takeover : 1; unsigned out_no_ctx_takeover : 1; unsigned part_written : 1; @@ -280,6 +312,14 @@ typedef struct { #define NGX_HTTP_CLOJURE_TEL_LOWCASE_KEY_IDX 15 #define NGX_HTTP_CLOJURE_TEL_LOWCASE_KEY_OFFSET offsetof(ngx_table_elt_t,lowcase_key) +#if (nginx_version >= 1023000) +#define NGX_HTTP_CLOJURE_TEL_NEXT_IDX 96 +#define NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET offsetof(ngx_table_elt_t,next) +#else +#define NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET -1 +#endif + + #define NGX_HTTP_CLOJURE_CHAINT_SIZE_IDX 16 #define NGX_HTTP_CLOJURE_CHAINT_SIZE sizeof(ngx_chain_t) #define NGX_HTTP_CLOJURE_CHAIN_BUF_IDX 17 @@ -416,7 +456,13 @@ extern ngx_cycle_t *ngx_http_clojure_global_cycle; #define NGX_HTTP_CLOJURE_HEADERSI_PASSWD_IDX 90 #define NGX_HTTP_CLOJURE_HEADERSI_PASSWD_OFFSET offsetof(ngx_http_headers_in_t, passwd) #define NGX_HTTP_CLOJURE_HEADERSI_COOKIE_IDX 91 + +#if (nginx_version < 1023000) #define NGX_HTTP_CLOJURE_HEADERSI_COOKIE_OFFSET offsetof(ngx_http_headers_in_t, cookies) +#else +#define NGX_HTTP_CLOJURE_HEADERSI_COOKIE_OFFSET offsetof(ngx_http_headers_in_t, cookie) +#endif + #define NGX_HTTP_CLOJURE_HEADERSI_SERVER_IDX 92 #define NGX_HTTP_CLOJURE_HEADERSI_SERVER_OFFSET offsetof(ngx_http_headers_in_t, server) #define NGX_HTTP_CLOJURE_HEADERSI_CONTENT_LENGTH_N_IDX 93 @@ -426,6 +472,7 @@ extern ngx_cycle_t *ngx_http_clojure_global_cycle; #define NGX_HTTP_CLOJURE_HEADERSI_HEADERS_IDX 95 #define NGX_HTTP_CLOJURE_HEADERSI_HEADERS_OFFSET offsetof(ngx_http_headers_in_t, headers) +#define NGX_HTTP_CLOJURE_TEL_NEXT_IDX 96 /*index for size of ngx_http_headers_out_t */ #define NGX_HTTP_CLOJURE_HEADERSOT_SIZE_IDX 128 @@ -504,16 +551,31 @@ extern ngx_cycle_t *ngx_http_clojure_global_cycle; #define NGX_BUF_LAST_OF_CHAIN 1 #define NGX_BUF_LAST_OF_RESPONSE 2 +#if (nginx_version >= 1023000) #define ngx_http_clojure_add_const_header(headers, hn, hv) \ - do { \ - ngx_table_elt_t *hxx = ngx_list_push(&headers); \ - if (hxx == NULL) { \ - return NGX_ERROR; \ - } \ - hxx->hash = 1; \ - ngx_str_set(&hxx->key, hn); \ - ngx_str_set(&hxx->value, hv); \ - } while(0) + do { \ + ngx_table_elt_t *hxx = ngx_list_push(&headers); \ + if (hxx == NULL) { \ + return NGX_ERROR; \ + } \ + hxx->hash = 1; \ + hxx->next = NULL; \ + ngx_str_set(&hxx->key, hn); \ + ngx_str_set(&hxx->value, hv); \ + } while(0) +#else +#define ngx_http_clojure_add_const_header(headers, hn, hv) \ + do { \ + ngx_table_elt_t *hxx = ngx_list_push(&headers); \ + if (hxx == NULL) { \ + return NGX_ERROR; \ + } \ + hxx->hash = 1; \ + ngx_str_set(&hxx->key, hn); \ + ngx_str_set(&hxx->value, hv); \ + } while(0) +#endif + #define ngx_http_clojure_get_header(headers, hn, /*out*/hr) \ @@ -569,7 +631,7 @@ int ngx_http_clojure_destroy_memory_util(ngx_log_t *log); int ngx_http_clojure_register_script(ngx_int_t phase, ngx_str_t *handler_type, ngx_str_t *handler, ngx_str_t *code, ngx_array_t *pros, ngx_int_t *pcid); -int ngx_http_clojure_eval(int cid, ngx_http_request_t *r, ngx_chain_t *c); +int ngx_http_clojure_eval(int cid, ngx_http_request_t *r, void *c); ngx_int_t ngx_http_clojure_hijack_send_header(ngx_http_request_t *r, ngx_int_t flag); diff --git a/src/c/ngx_http_clojure_module.c b/src/c/ngx_http_clojure_module.c index c2f39a1d..6629e306 100644 --- a/src/c/ngx_http_clojure_module.c +++ b/src/c/ngx_http_clojure_module.c @@ -42,14 +42,28 @@ static char* ngx_http_clojure_set_str_slot_and_enable_access_handler_tag(ngx_con static char* ngx_http_clojure_set_str_slot_and_enable_log_handler_tag(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ; +static char* ngx_http_clojure_set_str_slot_and_enable_load_balancer_tag(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + +static ngx_int_t ngx_http_clojure_upstream_init_load_balancer(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us); + +static ngx_int_t ngx_http_clojure_upstream_init_load_balancer_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us); + +static ngx_int_t ngx_http_clojure_upstream_get_load_balancer_peer(ngx_peer_connection_t *pc, void *data); + +static void ngx_http_clojure_upstream_free_load_balancer_peer(ngx_peer_connection_t *pc, void *data, ngx_uint_t state); + static char* ngx_http_clojure_set_always_read_body(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static void* ngx_http_clojure_create_loc_conf(ngx_conf_t *cf); +static void* ngx_http_clojure_create_srv_conf(ngx_conf_t *cf); + static void * ngx_http_clojure_create_main_conf(ngx_conf_t *cf); static char* ngx_http_clojure_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); +static char* ngx_http_clojure_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); + static ngx_int_t ngx_http_clojure_module_init(ngx_cycle_t *cycle); static void ngx_http_clojure_module_exit(ngx_cycle_t *cycle); @@ -82,6 +96,8 @@ static ngx_int_t ngx_http_clojure_init_locations_handlers_helper(ngx_http_core_l static ngx_int_t ngx_http_clojure_init_locations_handlers_in_tree(ngx_http_location_tree_node_t *lt) ; +static ngx_int_t ngx_http_clojure_init_upstreams_load_balancer_helper(ngx_http_upstream_main_conf_t *umcf); + static ngx_int_t ngx_http_clojure_init_socket(ngx_http_clojure_main_conf_t *mcf, ngx_log_t *log); static ngx_int_t ngx_http_clojure_init_clojure_script(ngx_int_t phase, char *type, ngx_str_t *handler_type, ngx_str_t *handler, @@ -224,6 +240,14 @@ static ngx_command_t ngx_http_clojure_commands[] = { offsetof(ngx_http_clojure_main_conf_t, jvm_init_handler_code), NULL }, + { + ngx_string("jvm_init_handler_property"), + NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE2, + ngx_conf_set_keyval_slot, + NGX_HTTP_MAIN_CONF_OFFSET, + offsetof(ngx_http_clojure_main_conf_t, jvm_init_handler_properties), + NULL + }, { ngx_string("jvm_exit_handler_name"), NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1, @@ -240,9 +264,17 @@ static ngx_command_t ngx_http_clojure_commands[] = { offsetof(ngx_http_clojure_main_conf_t, jvm_exit_handler_code), NULL }, + { + ngx_string("jvm_exit_handler_property"), + NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE2, + ngx_conf_set_keyval_slot, + NGX_HTTP_MAIN_CONF_OFFSET, + offsetof(ngx_http_clojure_main_conf_t, jvm_exit_handler_properties), + NULL + }, { ngx_string("handlers_lazy_init"), - NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, handlers_lazy_init), @@ -250,7 +282,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("auto_upgrade_ws"), - NGX_HTTP_MAIN_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, auto_upgrade_ws), @@ -282,7 +314,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("content_handler_type"), - NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, content_handler_type), @@ -290,7 +322,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("content_handler_name"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_http_clojure_set_str_slot_and_enable_content_handler_tag, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, content_handler_name), @@ -298,7 +330,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("content_handler_code"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_http_clojure_set_str_slot_and_enable_content_handler_tag, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, content_handler_code), @@ -306,7 +338,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("rewrite_handler_type"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, rewrite_handler_type), @@ -314,7 +346,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("rewrite_handler_name"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_http_clojure_set_str_slot_and_enable_rewrite_handler_tag, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, rewrite_handler_name), @@ -322,7 +354,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("rewrite_handler_code"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_http_clojure_set_str_slot_and_enable_rewrite_handler_tag, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, rewrite_handler_code), @@ -331,7 +363,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { { ngx_string("access_handler_type"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, access_handler_type), @@ -339,7 +371,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("access_handler_name"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_http_clojure_set_str_slot_and_enable_access_handler_tag, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, access_handler_name), @@ -347,7 +379,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("access_handler_code"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_http_clojure_set_str_slot_and_enable_access_handler_tag, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, access_handler_code), @@ -356,7 +388,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { { ngx_string("header_filter_type"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, header_filter_type), @@ -364,7 +396,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("header_filter_name"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_http_clojure_set_str_slot_and_enable_header_filter_tag, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, header_filter_name), @@ -372,7 +404,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("header_filter_code"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_http_clojure_set_str_slot_and_enable_header_filter_tag, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, header_filter_code), @@ -381,7 +413,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { { ngx_string("body_filter_type"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, body_filter_type), @@ -389,7 +421,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("body_filter_name"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_http_clojure_set_str_slot_and_enable_body_filter_tag, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, body_filter_name), @@ -397,7 +429,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("body_filter_code"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_http_clojure_set_str_slot_and_enable_body_filter_tag, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, body_filter_code), @@ -406,7 +438,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { { ngx_string("log_handler_type"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, log_handler_type), @@ -414,7 +446,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("log_handler_name"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_http_clojure_set_str_slot_and_enable_log_handler_tag, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, log_handler_name), @@ -422,16 +454,41 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("log_handler_code"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_http_clojure_set_str_slot_and_enable_log_handler_tag, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, log_handler_code), NULL }, + { + ngx_string("load_balancer_type"), + NGX_HTTP_UPS_CONF | NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_clojure_srv_conf_t, load_balancer_type), + NULL + }, + { + ngx_string("load_balancer_name"), + NGX_HTTP_UPS_CONF | NGX_CONF_TAKE1, + ngx_http_clojure_set_str_slot_and_enable_load_balancer_tag, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_clojure_srv_conf_t, load_balancer_name), + NULL + }, + { + ngx_string("load_balancer_code"), + NGX_HTTP_UPS_CONF | NGX_CONF_TAKE1, + ngx_http_clojure_set_str_slot_and_enable_load_balancer_tag, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_clojure_srv_conf_t, load_balancer_code), + NULL + }, + { ngx_string("content_handler_property"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, content_handler_properties), @@ -439,7 +496,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("rewrite_handler_property"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, rewrite_handler_properties), @@ -447,7 +504,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("access_handler_property"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, access_handler_properties), @@ -455,7 +512,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("header_filter_property"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, header_filter_properties), @@ -463,7 +520,7 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("body_filter_property"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, body_filter_properties), @@ -471,12 +528,20 @@ static ngx_command_t ngx_http_clojure_commands[] = { }, { ngx_string("log_handler_property"), - NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2, + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_clojure_loc_conf_t, log_handler_properties), NULL }, + { + ngx_string("load_balancer_property"), + NGX_HTTP_UPS_CONF | NGX_CONF_TAKE2, + ngx_conf_set_keyval_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_clojure_srv_conf_t, load_balancer_properties), + NULL + }, { ngx_string("always_read_body"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, @@ -516,7 +581,11 @@ static ngx_http_clojure_header_holder_t ngx_http_clojure_headers_out_holders[] = {ngx_string("WWW-Authenticate"), ngx_http_clojure_set_elt_header, offsetof(ngx_http_headers_out_t, www_authenticate)}, {ngx_string("Expires"), ngx_http_clojure_set_elt_header, offsetof(ngx_http_headers_out_t, expires)}, {ngx_string("Etag"), ngx_http_clojure_set_elt_header, offsetof(ngx_http_headers_out_t, etag)}, +#if (nginx_version >= 1023000) + {ngx_string("Cache-Control"), ngx_http_clojure_set_elt_header, offsetof(ngx_http_headers_out_t, cache_control)}, +#else {ngx_string("Cache-Control"), ngx_http_clojure_set_array_header, offsetof(ngx_http_headers_out_t, cache_control)}, +#endif {ngx_string("Content-Type"), ngx_http_clojure_set_content_type_header, 0}, {ngx_string("Content-Length"), ngx_http_clojure_set_content_len_header, 0}, {ngx_null_string, NULL, 0}, @@ -529,8 +598,8 @@ static ngx_http_module_t ngx_http_clojure_module_ctx = { ngx_http_clojure_create_main_conf, /* create main configuration */ NULL, /* init main configuration */ - NULL, /* create server configuration */ - NULL, /* merge server configuration */ + ngx_http_clojure_create_srv_conf, /* create server configuration */ + ngx_http_clojure_merge_srv_conf, /* merge server configuration */ ngx_http_clojure_create_loc_conf, /* create location configuration */ ngx_http_clojure_merge_loc_conf /* merge location configuration */ @@ -631,6 +700,18 @@ static void * ngx_http_clojure_create_loc_conf(ngx_conf_t *cf) { return conf; } +static void * ngx_http_clojure_create_srv_conf(ngx_conf_t *cf) { + ngx_http_clojure_srv_conf_t *conf; + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_clojure_srv_conf_t)); + if (conf == NULL){ + return NGX_CONF_ERROR; + } + conf->load_balancer_id = -1; + return conf; +} + + + static ngx_int_t ngx_http_clojure_init_clojure_script(ngx_int_t phase, char *type, ngx_str_t *handler_type, ngx_str_t *handler, ngx_str_t *code, ngx_array_t *pros,ngx_int_t *pcid , ngx_log_t *log) { if (*pcid < 0 && (code->len > 0 || handler->len > 0)) { if (ngx_http_clojure_register_script(phase, handler_type, handler, code, pros, pcid) != NGX_HTTP_CLOJURE_JVM_OK){ @@ -825,13 +906,13 @@ static ngx_int_t ngx_http_clojure_init_jvm_and_mem(ngx_core_conf_t *ccf, ngx_ht if (ngx_http_clojure_check_memory_util() != NGX_HTTP_CLOJURE_JVM_OK){ if (ngx_http_clojure_init_memory_util(ccf, cscf, mcf, log) != NGX_HTTP_CLOJURE_JVM_OK) { - ngx_log_error(NGX_LOG_ERR, log, 0, "can not initialize jvm memory util"); + ngx_log_error(NGX_LOG_ERR, log, 0, "nginx-clojure:can not initialize jvm memory util"); return NGX_HTTP_CLOJURE_JVM_ERR_INIT_MEMIDX; } } if (ngx_http_clojure_init_shared_map_util() != NGX_HTTP_CLOJURE_JVM_OK) { - ngx_log_error(NGX_LOG_ERR, log, 0, "can not initialize jvm memory util"); + ngx_log_error(NGX_LOG_ERR, log, 0, "nginx-clojure:can not initialize shared map util"); return NGX_HTTP_CLOJURE_JVM_ERR_INIT_SHAREDMAP; } @@ -932,6 +1013,10 @@ static char* ngx_http_clojure_merge_loc_conf(ngx_conf_t *cf, void *parent, void return NGX_CONF_OK; } +static char* ngx_http_clojure_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) { + return NGX_CONF_OK; +} + static ngx_int_t ngx_http_clojure_module_init(ngx_cycle_t *cycle) { ngx_core_conf_t *ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); @@ -1188,6 +1273,19 @@ static ngx_int_t ngx_http_clojure_init_locations_handlers(ngx_http_core_main_con return NGX_OK; } +static ngx_int_t ngx_http_clojure_init_upstreams_load_balancer_helper(ngx_http_upstream_main_conf_t *umcf) { + ngx_http_upstream_srv_conf_t **uscf = umcf->upstreams.elts; + ngx_http_clojure_srv_conf_t *scf; + ngx_uint_t s; + for (s = 0; s < umcf->upstreams.nelts; s++) { + if (uscf[s]->srv_conf != NULL) { + scf = uscf[s]->srv_conf[ngx_http_clojure_module.ctx_index]; + ngx_http_clojure_init_handler_script(scf, NGX_HTTP_LOAD_BALANCE_PHASE, load_balancer); + } + } + return NGX_OK; +} + static ngx_int_t ngx_http_clojure_process_init(ngx_cycle_t *cycle) { ngx_http_conf_ctx_t *ctx = (ngx_http_conf_ctx_t *)ngx_get_conf(cycle->conf_ctx, ngx_http_module); ngx_int_t rc = 0; @@ -1195,6 +1293,7 @@ static ngx_int_t ngx_http_clojure_process_init(ngx_cycle_t *cycle) { ngx_http_core_main_conf_t *cmcf; ngx_http_core_srv_conf_t *cscf; ngx_http_clojure_main_conf_t *mcf; + ngx_http_upstream_main_conf_t *umcf; ngx_core_conf_t *ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); ngx_int_t jvm_num = 0; @@ -1205,6 +1304,7 @@ static ngx_int_t ngx_http_clojure_process_init(ngx_cycle_t *cycle) { cmcf = ctx->main_conf[ngx_http_core_module.ctx_index]; cscf = ctx->srv_conf[ngx_http_core_module.ctx_index]; mcf = ctx->main_conf[ngx_http_clojure_module.ctx_index]; + umcf = ctx->main_conf[ngx_http_upstream_module.ctx_index]; /*Fix issue #64 about proxy cache manger process * We won't initialize jvm unless the current process is worker process or single process*/ @@ -1294,16 +1394,20 @@ static ngx_int_t ngx_http_clojure_process_init(ngx_cycle_t *cycle) { if (mcf->enable_init_handler && ngx_http_clojure_init_clojure_script(NGX_HTTP_INIT_PROCESS_PHASE, "init-process", &mcf->jvm_handler_type, &mcf->jvm_init_handler_name, - &mcf->jvm_init_handler_code, NULL, &mcf->jvm_init_handler_id, cycle->log) != NGX_HTTP_CLOJURE_JVM_OK) { + &mcf->jvm_init_handler_code, mcf->jvm_init_handler_properties, &mcf->jvm_init_handler_id, cycle->log) != NGX_HTTP_CLOJURE_JVM_OK) { return NGX_ERROR; } if (mcf->enable_exit_handler && ngx_http_clojure_init_clojure_script(NGX_HTTP_EXIT_PROCESS_PHASE, "exit-process", &mcf->jvm_handler_type, &mcf->jvm_exit_handler_name, - &mcf->jvm_exit_handler_code, NULL, &mcf->jvm_exit_handler_id, cycle->log) != NGX_HTTP_CLOJURE_JVM_OK) { + &mcf->jvm_exit_handler_code, mcf->jvm_exit_handler_properties, &mcf->jvm_exit_handler_id, cycle->log) != NGX_HTTP_CLOJURE_JVM_OK) { return NGX_ERROR; } + if (mcf->enable_load_balancer && ngx_http_clojure_init_upstreams_load_balancer_helper(umcf) != NGX_OK) { + return NGX_ERROR; + } + if (ngx_http_clojure_init_locations_handlers(cmcf) != NGX_OK) { return NGX_ERROR; } @@ -1542,9 +1646,8 @@ static ngx_int_t ngx_http_clojure_check_access_jvm_cp(ngx_http_clojure_main_conf ngx_uid_t ouid = geteuid(); ngx_gid_t ogid = getegid(); char *username = ccf->username; - struct passwd *pw; - /*TODO: remove this check nwhen we merge -Djava.class.path with jvm_classpath.*/ + /*TODO: remove this check when we merge -Djava.class.path with jvm_classpath.*/ if (!mcf->jvm_cp) { return rc; } @@ -1566,23 +1669,20 @@ static ngx_int_t ngx_http_clojure_check_access_jvm_cp(ngx_http_clojure_main_conf return NGX_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_CORE, log, 0, "geteuid now %ud:%ud", geteuid(), getegid()); - }else if (ccf->user == (uid_t) NGX_CONF_UNSET_UINT) { - pw = getpwuid (ouid); - username = pw->pw_name; } for (i = 0; i < mcf->jvm_cp->nelts; i++) { - ngx_log_debug2(NGX_LOG_DEBUG_CORE, log, 0, "checking %V, nginx user:%s", &elts[i], username); + ngx_log_debug2(NGX_LOG_DEBUG_CORE, log, 0, "checking %V, nginx user with id : %ud ", &elts[i], ouid); if (ngx_http_clojure_faccessat((char *)elts[i].data, log) != 0) { err = ngx_errno; - ngx_log_error(NGX_LOG_EMERG, log, err, "check access jvm classpath file \"%V\" failed by os user \"%s\"", &elts[i], username); + ngx_log_error(NGX_LOG_EMERG, log, err, "check access jvm classpath file \"%V\" failed by os user with id \"%ud\"", &elts[i], ouid); rc = NGX_ERROR; if (err == EACCES) { ngx_log_error(NGX_LOG_EMERG, log, 0, - "it is caused by os user \"%s\" has no direct access permission, " + "it is caused by os user with id \"%ud\" has no direct access permission, " "or search permission (viz. x-permission for a directory) is denied " - "for one of the directories in the path prefix of pathname", username); + "for one of the directories in the path prefix of pathname", ouid); } break; } @@ -1679,7 +1779,7 @@ static ngx_int_t ngx_http_clojure_postconfiguration(ngx_conf_t *cf) { if ((mcf->enable_access_handler | mcf->enable_body_filter | mcf->enable_content_handler | mcf->enable_header_filter | mcf->enable_init_handler | mcf->enable_rewrite_handler - | mcf->enable_log_handler) == 0) { + | mcf->enable_log_handler | mcf->enable_load_balancer) == 0) { mcf->jvm_disable_all = 1; return NGX_OK; } @@ -1687,10 +1787,16 @@ static ngx_int_t ngx_http_clojure_postconfiguration(ngx_conf_t *cf) { if (mcf->jvm_path.len == NGX_CONF_UNSET_SIZE) { mcf->jvm_disable_all = 1; #if defined(NGX_CLOJURE_BE_SILENT_WITHOUT_JVM) + if ((mcf->enable_access_handler | mcf->enable_body_filter | mcf->enable_content_handler + | mcf->enable_header_filter | mcf->enable_init_handler | mcf->enable_rewrite_handler + | mcf->enable_log_handler | mcf->enable_load_balancer) != 0) { + ngx_log_error(NGX_LOG_ERR, cf->log, 0, "nginx-clojure handlers are used but no jvm_path configured!"); + return NGX_ERROR; + } return NGX_OK; #else ngx_log_error(NGX_LOG_ERR, cf->log, 0, "no jvm_path configured!"); - return NGX_ERROR ; + return NGX_ERROR; #endif } @@ -2122,8 +2228,18 @@ static ngx_int_t ngx_http_clojure_body_filter(ngx_http_request_t *r, ngx_chain_ return NGX_OK; } - if (chain == NULL) { - return ngx_http_clojure_filter_continue_next_body_filter(r, NULL); + if (chain != NULL) { + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx clojure body filter, r=%" PRIu64 ", size=%d flush=%d last=%d", + (uintptr_t)r, ngx_buf_size(chain->buf), chain->buf->flush, chain->buf->last_buf); + + } else { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx clojure body filter, r=%" PRIu64 ", meets NULL chain", + (uintptr_t)r); + } + + + if (chain == NULL || (ngx_buf_size(chain->buf) == 0 && chain->next == NULL && !chain->buf->last_buf)) { + return ngx_http_clojure_filter_continue_next_body_filter(r, chain); } lcf = ngx_http_get_module_loc_conf(r, ngx_http_clojure_module); @@ -2163,15 +2279,16 @@ static ngx_int_t ngx_http_clojure_body_filter(ngx_http_request_t *r, ngx_chain_ rc = ngx_http_clojure_eval(lcf->body_filter_id, r, chain); ctx->phase = src_phase; -/*if we copied them we need mark them consumed*/ -// while (chain) { -// chain->buf->pos = chain->buf->last; -// chain = chain->next; -// } - - if (rc == NGX_DONE) { ngx_http_clojure_try_set_reload_delay_timer(ctx, "ngx_http_clojure_body_filter"); + } else { + /*** Mark them as consumed*/ + /**** moved to jni_ngx_http_filter_continue_next*/ + /* while (chain && chain->buf->recycled) { + chain->buf->pos = chain->buf->last; + chain->buf->file_pos = chain->buf->file_last; + chain = chain->next; + }*/ } return rc; @@ -2182,7 +2299,9 @@ static ngx_int_t ngx_http_clojure_body_filter(ngx_http_request_t *r, ngx_chain_ * where the return value of log handler will be ignored. */ static ngx_int_t ngx_http_clojure_log_handler(ngx_http_request_t * r) { +#if (NGX_DEBUG) ngx_int_t rc; +#endif ngx_http_clojure_module_ctx_t *ctx; ngx_http_clojure_loc_conf_t *lcf = ngx_http_get_module_loc_conf(r, ngx_http_clojure_module); @@ -2202,15 +2321,22 @@ static ngx_int_t ngx_http_clojure_log_handler(ngx_http_request_t * r) { ngx_http_clojure_init_ctx(ctx, NGX_HTTP_LOG_PHASE, r); ngx_http_set_ctx(r, ctx, ngx_http_clojure_module); +#if (NGX_DEBUG) rc = ngx_http_clojure_eval(lcf->log_handler_id, r, 0); - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_http_clojure_global_cycle->log, 0, "ngx clojure log (null ctx) request: %" PRIu64 ", rc: %d", (jlong)(uintptr_t)r, rc); +#else + (void)ngx_http_clojure_eval(lcf->log_handler_id, r, 0); +#endif return NGX_OK; } else { ctx->hijacked_or_async = 0; ctx->phase = NGX_HTTP_LOG_PHASE; +#if (NGX_DEBUG) rc = ngx_http_clojure_eval(lcf->log_handler_id, r, 0); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_http_clojure_global_cycle->log, 0, "ngx clojure log (else) request: %" PRIu64 ", rc: %d", (jlong)(uintptr_t)r, rc); +#else + (void)ngx_http_clojure_eval(lcf->log_handler_id, r, 0); +#endif return NGX_OK; } @@ -2367,6 +2493,170 @@ static char* ngx_http_clojure_set_str_slot_and_enable_log_handler_tag(ngx_conf_t return ngx_conf_set_str_slot(cf, cmd, conf); } +static ngx_int_t ngx_http_clojure_upstream_init_load_balancer(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, "init clojure balancer conn"); + + if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) { + return NGX_ERROR; + } + + us->peer.init = ngx_http_clojure_upstream_init_load_balancer_peer; + + return NGX_OK; +} + +static ngx_int_t ngx_http_clojure_upstream_init_load_balancer_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us) { + ngx_http_clojure_upstream_load_balancer_peer_data_t *pd; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "init clojure balancer peer"); + + pd = ngx_palloc(r->pool, sizeof(ngx_http_clojure_upstream_load_balancer_peer_data_t)); + if (pd == NULL) { + return NGX_ERROR; + } + + pd->peer_pos_or_len = NGX_CONF_UNSET_UINT; + pd->peer_url = NULL; + + r->upstream->peer.data = &pd->rrp; + + if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) { + return NGX_ERROR; + } + + r->upstream->peer.get = ngx_http_clojure_upstream_get_load_balancer_peer; + r->upstream->peer.free = ngx_http_clojure_upstream_free_load_balancer_peer; + pd->conf = ngx_http_conf_upstream_srv_conf(us, ngx_http_clojure_module); + pd->r = r; + + return NGX_OK; +} + +static ngx_int_t ngx_http_clojure_upstream_get_load_balancer_peer(ngx_peer_connection_t *pc, void *data) { + ngx_int_t rc; + uintptr_t pi[2]; + ngx_http_clojure_module_ctx_t *ctx; + ngx_http_clojure_upstream_load_balancer_peer_data_t *pd = data; + ngx_http_request_t *r = pd->r; +// ngx_http_clojure_loc_conf_t *lcf = ngx_http_get_module_loc_conf(r, ngx_http_clojure_module); + ngx_http_clojure_srv_conf_t *scf = pd->conf; + ngx_http_upstream_rr_peer_data_t *rrp = &pd->rrp; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, "get clojure balancer peer, try: %ui", pc->tries); + + ngx_http_clojure_get_ctx(r, ctx); + ngx_http_clojure_init_handler_script(scf, NGX_HTTP_LOAD_BALANCE_PHASE, load_balancer); + + if (!scf->enable_load_balancer || (scf->load_balancer_code.len == 0 && scf->load_balancer_name.len == 0)) { + return ngx_http_upstream_get_round_robin_peer(pc, data); + } + + pi[0] = (uintptr_t)&pd->peer_pos_or_len; + pi[1] = (uintptr_t)&pd->peer_url; + + if (ctx == NULL) { + ctx = ngx_palloc(r->pool, sizeof(ngx_http_clojure_module_ctx_t)); + if (ctx == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OutOfMemory of create ngx_http_clojure_module_ctx_t"); + return NGX_ERROR; + } + + ngx_http_clojure_init_ctx(ctx, NGX_HTTP_LOAD_BALANCE_PHASE, r); + ngx_http_set_ctx(r, ctx, ngx_http_clojure_module); + rc = ngx_http_clojure_eval(scf->load_balancer_id, r, pi); +#if (NGX_DEBUG) + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_http_clojure_global_cycle->log, 0, "ngx clojure balancer (null ctx) request: %" PRIu64 ", rc: %d", (jlong)(uintptr_t)r, rc); +#endif + } else { + ctx->hijacked_or_async = 0; + ctx->phase = NGX_HTTP_LOAD_BALANCE_PHASE; + rc = ngx_http_clojure_eval(scf->load_balancer_id, r, pi); +#if (NGX_DEBUG) + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_http_clojure_global_cycle->log, 0, "ngx clojure balancer (else) request: %" PRIu64 ", rc: %d", (jlong)(uintptr_t)r, rc); +#endif + } + + if (rc != NGX_OK) { + /* return ngx_http_upstream_get_round_robin_peer(pc, data); */ + ngx_log_error(NGX_LOG_ERR, r->connection->log , 0, "%s %" PRIu64 ", rc: %d in ngx clojure balancer \"%V\"", "eval error: ", (jlong)(uintptr_t)r, rc, &scf->load_balancer_name); + return NGX_ERROR; + } + + if (pd->peer_url != NULL) { + ngx_url_t *url = ngx_pcalloc(r->pool, sizeof(ngx_url_t)); + if (url == NULL){ + return NGX_ERROR; + } + + url->url.data = pd->peer_url; + url->url.len = (size_t)pd->peer_pos_or_len; + + if (ngx_parse_url(r->pool, url) != NGX_OK ) { + if (url->err) { + ngx_log_error(NGX_LOG_ERR, r->connection->log , 0, "%s in resolver \"%V\"", url->err, &url->url); + } + return NGX_ERROR; + } + + if (url->addrs && url->addrs[0].sockaddr) { + pc->sockaddr = url->addrs[0].sockaddr; + pc->socklen = url->addrs[0].socklen; + pc->name = &url->addrs[0].name; + return NGX_OK; + } + } else if (pd->peer_pos_or_len > 0 && pd->peer_pos_or_len < rrp->peers->number) { + ngx_http_upstream_rr_peer_t *peer; + ngx_uint_t i; + + for (peer = rrp->peers->peer, i = 0; peer; peer = peer->next, i++) { + if (pd->peer_pos_or_len == i) { + pc->sockaddr = peer->sockaddr; + pc->socklen = peer->socklen; + pc->name = &peer->name; + rrp->current = peer; + return NGX_OK; + } + } + } + + return NGX_ERROR; +} + +static void ngx_http_clojure_upstream_free_load_balancer_peer(ngx_peer_connection_t *pc, void *data, ngx_uint_t state) { + ngx_http_clojure_upstream_load_balancer_peer_data_t *pd = data; + + if (pd->peer_url != NULL) { + if (pc->tries) { + pc->tries--; + } + return; + } + + ngx_http_upstream_free_round_robin_peer(pc, data, state); +} + +static char* ngx_http_clojure_set_str_slot_and_enable_load_balancer_tag(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { + ngx_http_clojure_srv_conf_t *lcf = conf; + ngx_http_clojure_main_conf_t *mcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_clojure_module); + ngx_http_upstream_srv_conf_t *uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module); + mcf->enable_load_balancer = lcf->enable_load_balancer = 1; + + if (uscf->peer.init_upstream) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "load balancing method redefined"); + } + + uscf->peer.init_upstream = ngx_http_clojure_upstream_init_load_balancer; + + uscf->flags = NGX_HTTP_UPSTREAM_CREATE + |NGX_HTTP_UPSTREAM_WEIGHT + |NGX_HTTP_UPSTREAM_MAX_CONNS + |NGX_HTTP_UPSTREAM_MAX_FAILS + |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT + |NGX_HTTP_UPSTREAM_DOWN; + + return ngx_conf_set_str_slot(cf, cmd, conf); +} + static char* ngx_http_clojure_set_always_read_body(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_clojure_loc_conf_t *lcf = conf; ngx_http_clojure_main_conf_t *mcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_clojure_module); diff --git a/src/clojure/nginx/clojure/core.clj b/src/clojure/nginx/clojure/core.clj index 290c3ed3..ca98ac64 100644 --- a/src/clojure/nginx/clojure/core.clj +++ b/src/clojure/nginx/clojure/core.clj @@ -339,3 +339,15 @@ When a message comes the callback function will be invoked. e.g. (callback message att))))] (fn [] (.unsubscribe topic pd)))) (destory! [topic] (.destory topic))) + +(defn balancer-result + "Build a balancer result for a load balancer. + `idx-or-url can be an index of the upstream servers list or the url string. + e.g. (balancer-result 3) , (balancer-result \"192.168.3.5:8071\") + " + [idx-or-url] + (cond + (instance? String idx-or-url) {:status 200, :body idx-or-url} + (instance? Integer idx-or-url) {:status 200, :body idx-or-url} + :else + {:status 500})) diff --git a/src/java/nginx/clojure/AbstractHeaderHolder.java b/src/java/nginx/clojure/AbstractHeaderHolder.java index 80d59de9..a1426515 100644 --- a/src/java/nginx/clojure/AbstractHeaderHolder.java +++ b/src/java/nginx/clojure/AbstractHeaderHolder.java @@ -40,6 +40,7 @@ public long headersOffset() { return headersOffset; } + @SuppressWarnings("rawtypes") public final String pickString(Object v) { if (v == null) { return null; diff --git a/src/java/nginx/clojure/ArrayHeaderHolder.java b/src/java/nginx/clojure/ArrayHeaderHolder.java index 807af8ab..fb634917 100644 --- a/src/java/nginx/clojure/ArrayHeaderHolder.java +++ b/src/java/nginx/clojure/ArrayHeaderHolder.java @@ -35,6 +35,7 @@ public ArrayHeaderHolder(String name, long offset, long headersOffset) { super(name, offset, headersOffset); } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public void push(long h, long pool, Object v) { long haddr = h + offset; @@ -119,7 +120,7 @@ public Object fetch(long h) { if (lp == 0) { return null; } - int c = fetchNGXInt(haddr+NGX_HTTP_CLOJURE_ARRAY_NELTS_OFFSET); + int c = fetchNGXInt(haddr + NGX_HTTP_CLOJURE_ARRAY_NELTS_OFFSET); if (c == 0) { return null; } @@ -129,7 +130,7 @@ public Object fetch(long h) { if (tp == 0) { return null; } - return fetchNGXString(tp+NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET, DEFAULT_ENCODING); + return fetchNGXString(tp + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET, DEFAULT_ENCODING); } String[] vals = new String[c]; for (int i = 0; i < c; i++) { @@ -137,7 +138,7 @@ public Object fetch(long h) { if (tp == 0) { return null; } - vals[i] = fetchNGXString(tp+ NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET, DEFAULT_ENCODING); + vals[i] = fetchNGXString(tp + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET, DEFAULT_ENCODING); lp += NGX_HTTP_CLOJURE_PTR_SIZE; } return vals; @@ -153,7 +154,7 @@ public boolean exists(long h) { if (lp == 0) { return false; } - int c = fetchNGXInt(haddr+NGX_HTTP_CLOJURE_ARRAY_NELTS_OFFSET); + int c = fetchNGXInt(haddr + NGX_HTTP_CLOJURE_ARRAY_NELTS_OFFSET); if (c == 0) { return false; } diff --git a/src/java/nginx/clojure/Coroutine.java b/src/java/nginx/clojure/Coroutine.java index e61cde32..1e0a61b2 100644 --- a/src/java/nginx/clojure/Coroutine.java +++ b/src/java/nginx/clojure/Coroutine.java @@ -32,6 +32,8 @@ import java.io.IOException; import java.io.Serializable; +import nginx.clojure.NativeCoroutineBuilder.NativeCoroutine; + /** *

A Coroutine is used to run a CoroutineProto.

@@ -53,6 +55,12 @@ public class Coroutine implements Runnable, Serializable { private static final long serialVersionUID = 2783452871536981L; + private static boolean useNative = false; + + private static NativeCoroutineBuilder nativeCoroutineBuilder; + + private NativeCoroutine nativeCoroutine; + public enum State { /** The Coroutine has not yet been executed */ NEW, @@ -79,6 +87,10 @@ public interface FinishAwaredRunnable extends Runnable { private Object locals; private Object inheritableLocals; + public static boolean isUseNative() { + return useNative; + } + /** * Suspend the currently running Coroutine on the calling thread. * @@ -86,7 +98,11 @@ public interface FinishAwaredRunnable extends Runnable { * @throws java.lang.IllegalStateException If not called from a Coroutine */ public static void yield() throws SuspendExecution, IllegalStateException { - throw new Error("Calling function not instrumented"); + if (useNative) { + nativeCoroutineBuilder.yield(); + } else { + throw new Error("Calling function not instrumented"); + } } /** @@ -121,29 +137,40 @@ public Coroutine(Runnable proto) { */ public Coroutine(Runnable proto, int stackSize) { this.proto = proto; - this.stack = new Stack(this, stackSize); - this.cstack = new SuspendableConstructorUtilStack(stackSize/8); this.state = State.NEW; Thread thread = Thread.currentThread(); Object currentLocals = HackUtils.getThreadLocals(Thread.currentThread()); this.locals = HackUtils.cloneThreadLocalMap(currentLocals); - try { - HackUtils.setThreadLocals(thread, this.locals); - Stack.setStack(this.stack); - SuspendableConstructorUtilStack.setStack(this.cstack); - }finally { - HackUtils.setThreadLocals(thread, currentLocals); - } - Object inheritableLocals = HackUtils.getInheritableThreadLocals(Thread.currentThread()); - if (inheritableLocals != null) { - this.inheritableLocals = HackUtils.createInheritedMap(inheritableLocals); - } - - if(proto == null) { - throw new NullPointerException("proto"); + if (useNative) { + this.nativeCoroutine = nativeCoroutineBuilder.build(proto); + this.stack = new Stack(this, 0);; + try { + HackUtils.setThreadLocals(thread, this.locals); + Stack.setStack(this.stack); + } finally { + HackUtils.setThreadLocals(thread, currentLocals); + } + + this.cstack = null; + } else { + this.stack = new Stack(this, stackSize); + this.cstack = new SuspendableConstructorUtilStack(stackSize/8); + try { + HackUtils.setThreadLocals(thread, this.locals); + Stack.setStack(this.stack); + SuspendableConstructorUtilStack.setStack(this.cstack); + } finally { + HackUtils.setThreadLocals(thread, currentLocals); + } + + Object inheritableLocals = HackUtils.getInheritableThreadLocals(Thread.currentThread()); + if (inheritableLocals != null) { + this.inheritableLocals = HackUtils.createInheritedMap(inheritableLocals); + } + + assert isInstrumented(proto) : "Not instrumented"; } - assert isInstrumented(proto) : "Not instrumented"; } public void reset() { @@ -153,8 +180,13 @@ public void reset() { this.locals = HackUtils.cloneThreadLocalMap(currentLocals); try { HackUtils.setThreadLocals(thread, this.locals); - Stack.setStack(this.stack); - SuspendableConstructorUtilStack.setStack(this.cstack); + if (useNative) { + this.nativeCoroutine = nativeCoroutineBuilder.build(proto); + Stack.setStack(this.stack); + } else { + Stack.setStack(this.stack); + SuspendableConstructorUtilStack.setStack(this.cstack); + } }finally { HackUtils.setThreadLocals(thread, currentLocals); } @@ -204,7 +236,12 @@ public State getState() { return state; } - + /** + * @param state the state to set + */ + protected void setState(State state) { + this.state = state; + } /** * Runs the Coroutine until it is finished or suspended. This method must only @@ -241,14 +278,20 @@ public void resume() { state = State.RUNNING; // Stack.setStack(stack); // SuspendableConstructorUtilStack.setStack(cstack); - - try { + if (useNative) { + nativeCoroutine.resume(); + if (state == State.SUSPENDED) { + result = State.SUSPENDED; + } + } else { + try { proto.run(); - } catch (SuspendExecution ex) { - assert ex == SuspendExecution.instance; - result = State.SUSPENDED; - //stack.dump(); - stack.resumeStack(); + } catch (SuspendExecution ex) { + assert ex == SuspendExecution.instance; + result = State.SUSPENDED; + //stack.dump(); + stack.resumeStack(); + } } } finally { if (result == State.FINISHED) { @@ -322,7 +365,7 @@ public SuspendableConstructorUtilStack getCStack() { return cstack; } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) private boolean isInstrumented(Runnable proto) { try { Class clz = Class.forName("nginx.clojure.wave.AlreadyInstrumented"); @@ -333,4 +376,13 @@ private boolean isInstrumented(Runnable proto) { return true; // it's just a check - make sure we don't fail if something goes wrong } } + + public static void prepareNative() { + useNative = true; + try { + nativeCoroutineBuilder = (NativeCoroutineBuilder) Coroutine.class.forName("nginx.clojure.NativeCoroutineBuilderImp").newInstance(); + } catch (Throwable e) { + throw new IllegalStateException("can not load nginx.clojure.NativeCoroutineBuilderImp", e); + } + } } diff --git a/src/java/nginx/clojure/EtlListHeaderHolder.java b/src/java/nginx/clojure/EtlListHeaderHolder.java new file mode 100644 index 00000000..9c0aafd8 --- /dev/null +++ b/src/java/nginx/clojure/EtlListHeaderHolder.java @@ -0,0 +1,138 @@ +/** + * Copyright (C) Zhang,Yuexiang (xfeep) + * + */ +package nginx.clojure; + +import static nginx.clojure.MiniConstants.DEFAULT_ENCODING; +import static nginx.clojure.MiniConstants.HEADERS_NAMES; +import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_ARRAY_ELTS_OFFSET; +import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_ARRAY_NELTS_OFFSET; +import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET; +import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_PTR_SIZE; +import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_HASH_OFFSET; +import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_KEY_OFFSET; +import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET; +import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET; +import static nginx.clojure.MiniConstants.NGX_OK; +import static nginx.clojure.NginxClojureRT.UNSAFE; +import static nginx.clojure.NginxClojureRT.fetchNGXInt; +import static nginx.clojure.NginxClojureRT.fetchNGXString; +import static nginx.clojure.NginxClojureRT.ngx_array_destory; +import static nginx.clojure.NginxClojureRT.ngx_array_init; +import static nginx.clojure.NginxClojureRT.ngx_array_push_n; +import static nginx.clojure.NginxClojureRT.ngx_http_clojure_mem_shadow_copy_ngx_str; +import static nginx.clojure.NginxClojureRT.pushNGXInt; +import static nginx.clojure.NginxClojureRT.pushNGXString; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + + +public class EtlListHeaderHolder extends AbstractHeaderHolder { + + public EtlListHeaderHolder(String name, long offset, long headersOffset) { + this.offset = offset; + this.name = name; + this.headersOffset = headersOffset; + if (offset < 0) { + throw new IllegalArgumentException("offset of " + name + " is invalid, must >=0 but meets " + offset ); + } + if (headersOffset < 0) { + throw new IllegalArgumentException("headersOffset of " + name + " is invalid, must >=0 but meets " + headersOffset ); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void push(long h, long pool, Object v) { + + List seq = null; + if (v == null || v instanceof String) { + String val = (String) v; + seq = Arrays.asList(val); + }else if (v instanceof List) { + seq = (List) v; + }else if (v.getClass().isArray()){ + seq = (List)Arrays.asList((Object[])v); + } + + int c = seq.size(); + if (c == 0) { + clear(h); + return; + } + + long lp = h + offset; + long p = UNSAFE.getAddress(lp); + long pname = HEADERS_NAMES.get(name); + + if (p == 0) { + for (String val : seq) { + if (val != null) { + p = NginxClojureRT.ngx_list_push(h + NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET); + if (p == 0) { + throw new RuntimeException("can not push ngx etl list for headers"); + } + UNSAFE.putAddress(lp, p); + pushNGXInt(p + NGX_HTTP_CLOJURE_TEL_HASH_OFFSET, 1); + ngx_http_clojure_mem_shadow_copy_ngx_str(pname, p + NGX_HTTP_CLOJURE_TEL_KEY_OFFSET); + pushNGXString(p + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET, val, DEFAULT_ENCODING, pool); + lp = p + NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET; + } + } + } else { + for (String val : seq) { + if (val != null) { + if (p == 0) { + p = NginxClojureRT.ngx_list_push(h + NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET); + if (p == 0) { + throw new RuntimeException("can not push ngx etl list for headers"); + } + UNSAFE.putAddress(lp, p); + + } + pushNGXInt(p + NGX_HTTP_CLOJURE_TEL_HASH_OFFSET, 1); + ngx_http_clojure_mem_shadow_copy_ngx_str(pname, p + NGX_HTTP_CLOJURE_TEL_KEY_OFFSET); + pushNGXString(p + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET, val, DEFAULT_ENCODING, pool); + lp = p + NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET; + p = UNSAFE.getAddress(lp); + } + } + } + + UNSAFE.putAddress(lp, 0); + } + + @Override + public void clear(long h) { + long p = UNSAFE.getAddress(h + offset); + if (p != 0) { + NginxClojureRT.pushNGXInt(p + NGX_HTTP_CLOJURE_TEL_HASH_OFFSET, 0); + UNSAFE.putAddress(h + offset, 0); + } + } + + @Override + public Object fetch(long h) { + long p = UNSAFE.getAddress(h + offset); + if (p == 0) { + return null; + } + + ArrayList list = new ArrayList(2); + while (p != 0) { + list.add(fetchNGXString(p + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET , DEFAULT_ENCODING)); + p = UNSAFE.getAddress(p + NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET); + } + return list.size() == 1 ? list.get(0) : list.toArray(new String[list.size()]); + } + + @Override + public boolean exists(long h) { + return h != 0 && UNSAFE.getAddress(h + offset) != 0; + } + +} diff --git a/src/java/nginx/clojure/HackUtils.java b/src/java/nginx/clojure/HackUtils.java index bb408e7c..655d42e5 100644 --- a/src/java/nginx/clojure/HackUtils.java +++ b/src/java/nginx/clojure/HackUtils.java @@ -32,19 +32,23 @@ public class HackUtils { private static final long contextClassLoaderOffset; private static final long inheritedAccessControlContextOffset; private static final Method createInheritedMap; - private static final Class threadLocalMapClass; + @SuppressWarnings("rawtypes") + private static final Class threadLocalMapClass; private static final long threadLocalMapTableFieldOffset;// = UNSAFE.objectFieldOffset(threadLocalMapTableField); private static final long threadLocalMapSizeFieldOffset;// = UNSAFE.objectFieldOffset(threadLocalMapSizeField); private static final long threadLocalMapThresholdFieldOffset;// = UNSAFE.objectFieldOffset(threadLocalMapThresholdField); - private static final Class threadLocalMapEntryClass; + @SuppressWarnings("rawtypes") + private static final Class threadLocalMapEntryClass; private static final long threadLocalMapEntryValueFieldOffset; private static final long threadLocalMapEntryReferentFieldOffset; private static final long threadLocalMapEntryQueueFieldOffset; + @SuppressWarnings("rawtypes") private static final Class randomAccessFileClass; private static final long randomAccessFileFdFieldOffset; + @SuppressWarnings("rawtypes") private static final Class fileDescriptorClass; private static final long fileDescriptorClassFdFieldOffset; diff --git a/src/java/nginx/clojure/MiniConstants.java b/src/java/nginx/clojure/MiniConstants.java index a9cd7dac..fdd45d7f 100644 --- a/src/java/nginx/clojure/MiniConstants.java +++ b/src/java/nginx/clojure/MiniConstants.java @@ -163,6 +163,12 @@ public class MiniConstants { public static long NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET; public static int NGX_HTTP_CLOJURE_TEL_LOWCASE_KEY_IDX = 15; public static long NGX_HTTP_CLOJURE_TEL_LOWCASE_KEY_OFFSET; + + //#if (nginx_version >= 1023000) + public static int NGX_HTTP_CLOJURE_TEL_NEXT_IDX = 96; + public static long NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET; + //#endif + public static int NGX_HTTP_CLOJURE_CHAINT_SIZE_IDX = 16; public static long NGX_HTTP_CLOJURE_CHAINT_SIZE; @@ -383,8 +389,8 @@ public class MiniConstants { public static int NGX_HTTP_CLOJURE_MEM_IDX_END = 255; //nginx clojure java runtime required the lowest version of nginx-clojure c module - public static long NGINX_CLOJURE_RT_REQUIRED_LVER = 5000; - public static long NGINX_CLOJURE_RT_VER = 5000; + public static long NGINX_CLOJURE_RT_REQUIRED_LVER = 5002; + public static long NGINX_CLOJURE_RT_VER = 6001; //ngx_core.h public final static int NGX_OK = 0; @@ -407,11 +413,18 @@ public class MiniConstants { public final static int NGX_HTTP_CONTENT_PHASE = 9; public final static int NGX_HTTP_LOG_PHASE = 10; + //fake phase for load balance handler + public final static int NGX_HTTP_LOAD_BALANCE_PHASE = 16; + //fake phase for filter - public final static int NGX_HTTP_INIT_PROCESS_PHASE= 17; - public final static int NGX_HTTP_HEADER_FILTER_PHASE= 18; - public final static int NGX_HTTP_BODY_FILTER_PHASE= 19; - public final static int NGX_HTTP_EXIT_PROCESS_PHASE= 20; + public final static int NGX_HTTP_INIT_PROCESS_PHASE = 17; + public final static int NGX_HTTP_HEADER_FILTER_PHASE = 18; + public final static int NGX_HTTP_BODY_FILTER_PHASE = 19; + public final static int NGX_HTTP_EXIT_PROCESS_PHASE = 20; + + /*fake chain for header filter*/ + public final static int NGX_HTTP_HEADER_FILTER = -1; + public final static int NGX_HTTP_HEADER_FILTER_IN_THREADPOOL = -2; //ngx_http_request.h diff --git a/src/java/nginx/clojure/NativeCoroutineBuilder.java b/src/java/nginx/clojure/NativeCoroutineBuilder.java new file mode 100644 index 00000000..68bd5815 --- /dev/null +++ b/src/java/nginx/clojure/NativeCoroutineBuilder.java @@ -0,0 +1,20 @@ +/** + * Copyright (C) Zhang,Yuexiang (xfeep) + * + */ +package nginx.clojure; + +/** + * @author Zhang,Yuexiang (xfeep) + * + */ +public interface NativeCoroutineBuilder { + + static interface NativeCoroutine { + void resume(); + } + + public NativeCoroutine build(Runnable r); + + public boolean yield(); +} diff --git a/src/java/nginx/clojure/NginxChainWrappedInputStream.java b/src/java/nginx/clojure/NginxChainWrappedInputStream.java index 37e9a742..4c057a6a 100644 --- a/src/java/nginx/clojure/NginxChainWrappedInputStream.java +++ b/src/java/nginx/clojure/NginxChainWrappedInputStream.java @@ -217,12 +217,20 @@ public int read(byte[] b, int off, int len) throws IOException { return 0; } - int c = streams[index].read(b, off, len); + int total = 0; + int c = 0; - while (c <= 0 && ++index < streams.length) { - c = streams[index].read(b, off, len); + //now it is more eager than 0.5.0 and read more bytes as possible. + while (index < streams.length && total < len) { + c = streams[index].read(b, off + total, len - total); + if (c <= 0) { + index ++; + } else { + total += c; + } } - return c; + + return total; } public long nativeChain() { diff --git a/src/java/nginx/clojure/NginxClojureRT.java b/src/java/nginx/clojure/NginxClojureRT.java index ae2ce074..93e36e61 100644 --- a/src/java/nginx/clojure/NginxClojureRT.java +++ b/src/java/nginx/clojure/NginxClojureRT.java @@ -6,6 +6,10 @@ +import static nginx.clojure.MiniConstants.NGINX_VER; +import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_REQ_POOL_OFFSET; +import static nginx.clojure.NginxClojureRT.UNSAFE; + import java.io.IOException; import java.lang.management.ManagementFactory; import java.net.Socket; @@ -131,7 +135,7 @@ public static void ngx_http_clear_header_and_reset_ctx_phase(long r, long phase) * @param chain -1 means continue next header filter otherwise continue next body filter * @return */ - public native static long ngx_http_filter_continue_next(long r, long chain); + public native static long ngx_http_filter_continue_next(long r, long chain, long oldChain); /** * last_buf can be either of {@link MiniConstants#NGX_CLOJURE_BUF_LAST_OF_NONE} {@link MiniConstants#NGX_CLOJURE_BUF_LAST_OF_CHAIN}, {@link MiniConstants#NGX_CLOJURE_BUF_LAST_OF_RESPONSE} @@ -195,7 +199,7 @@ public static void ngx_http_clear_header_and_reset_ctx_phase(long r, long phase) /** * @deprecated */ - public static long ngx_http_cleanup_add(long r, final ChannelListener listener, Object data) { + public static long ngx_http_cleanup_add(long r, final ChannelListener listener, Object data) { return ngx_http_clojure_add_listener(r, new ChannelCloseAdapter() { @Override public void onClose(Object data) throws IOException { @@ -204,12 +208,13 @@ public void onClose(Object data) throws IOException { }, data, 0); } - private native static long ngx_http_clojure_add_listener(long r, ChannelListener listener, Object data, int replace); + private native static long ngx_http_clojure_add_listener(long r, @SuppressWarnings("rawtypes") ChannelListener listener, Object data, int replace); - public static void addListener(NginxRequest r, ChannelListener listener, Object data, int replace) { + public static void addListener(NginxRequest r, @SuppressWarnings("rawtypes") ChannelListener listener, Object data, int replace) { addListener(r.nativeRequest(), listener, data, replace); } + @SuppressWarnings("rawtypes") public static void addListener(long r, ChannelListener listener, Object data, int replace) { if ( ngx_http_clojure_add_listener(r, listener, data, replace) != 0) { throw new IllegalStateException("invalid request which is cleaned!"); @@ -273,10 +278,15 @@ public static long ngx_http_clojure_websocket_upgrade(long req) { static { //be friendly to lein ring testing - getLog(); - initUnsafe(); - appEventListenerManager = new AppEventListenerManager(); - processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; + try { + getLog(); + initUnsafe(); + appEventListenerManager = new AppEventListenerManager(); + processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; + } catch (Throwable e) { + //to be friendly to nginx jni error log + e.printStackTrace(); + } } public static AppEventListenerManager getAppEventListenerManager() { @@ -443,8 +453,11 @@ private static void initWorkers(int n) { "worker won't be blocked when access services provide by the same nginx instance"); n = Runtime.getRuntime().availableProcessors() * 2; } - }else { + } else { log.info("java agent configured so we turn on coroutine support!"); + if (JavaAgent.db.isEnableNativeCoroutine()) { + Coroutine.prepareNative(); + } if (n > 0) { log.warn("found jvm_workers = %d, and not = 0 we just ignored!", n); } @@ -541,6 +554,9 @@ private static NginxHeaderHolder safeBuildKnownTableEltHeaderHolder(String name, private static NginxHeaderHolder safeBuildKnownArrayHeaderHolder(String name, long offset, long headersOffset) { if (offset >= 0) { + if (NGINX_VER >= 1023000) { + return new EtlListHeaderHolder(name, offset, headersOffset); + } return new ArrayHeaderHolder(name, offset, headersOffset); } return new UnknownHeaderHolder(name, headersOffset); @@ -559,7 +575,11 @@ public static void initStringAddrMapsByNativeAddr(Map map, long ad private static synchronized void initMemIndex(long idxpt) { getLog(); - initUnsafe(); + initUnsafe(); + + if (log.isDebugEnabled()) { + log.debug("jvm classpath:\n " + System.getProperty("java.class.path")); + } NGINX_MAIN_THREAD = Thread.currentThread(); @@ -596,6 +616,7 @@ private static synchronized void initMemIndex(long idxpt) { NGX_HTTP_CLOJURE_TEL_KEY_OFFSET = MEM_INDEX[NGX_HTTP_CLOJURE_TEL_KEY_IDX]; NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET = MEM_INDEX[NGX_HTTP_CLOJURE_TEL_VALUE_IDX]; NGX_HTTP_CLOJURE_TEL_LOWCASE_KEY_OFFSET = MEM_INDEX[NGX_HTTP_CLOJURE_TEL_LOWCASE_KEY_IDX]; + NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET = MEM_INDEX[NGX_HTTP_CLOJURE_TEL_NEXT_IDX]; NGX_HTTP_CLOJURE_REQT_SIZE = MEM_INDEX[NGX_HTTP_CLOJURE_REQT_SIZE_IDX]; NGX_HTTP_CLOJURE_REQ_METHOD_OFFSET = MEM_INDEX[NGX_HTTP_CLOJURE_REQ_METHOD_IDX]; @@ -780,7 +801,7 @@ private static synchronized void initMemIndex(long idxpt) { KNOWN_RESP_HEADERS.put("WWW-Authenticate", safeBuildKnownTableEltHeaderHolder("WWW-Authenticate", NGX_HTTP_CLOJURE_HEADERSO_WWW_AUTHENTICATE_OFFSET, NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET)); KNOWN_RESP_HEADERS.put("Expires", safeBuildKnownTableEltHeaderHolder("Expires", NGX_HTTP_CLOJURE_HEADERSO_EXPIRES_OFFSET, NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET)); KNOWN_RESP_HEADERS.put("Etag", safeBuildKnownTableEltHeaderHolder("Etag", NGX_HTTP_CLOJURE_HEADERSO_ETAG_OFFSET, NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET)); - KNOWN_RESP_HEADERS.put("Cache-Control", new ArrayHeaderHolder("Cache-Control", NGX_HTTP_CLOJURE_HEADERSO_CACHE_CONTROL_OFFSET, NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET)); + KNOWN_RESP_HEADERS.put("Cache-Control", safeBuildKnownArrayHeaderHolder("Cache-Control", NGX_HTTP_CLOJURE_HEADERSO_CACHE_CONTROL_OFFSET, NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET)); KNOWN_RESP_HEADERS.put("Content-Type", RESP_CONTENT_TYPE_HOLDER = new ResponseContentTypeHolder()); KNOWN_RESP_HEADERS.put("Content-Length", new OffsetHeaderHolder("Content-Length", NGX_HTTP_CLOJURE_HEADERSO_CONTENT_LENGTH_N_OFFSET, NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET) ); @@ -856,29 +877,38 @@ public static synchronized int registerCode(int phase, long typeNStr, long nameN String type = fetchNGXString(typeNStr, DEFAULT_ENCODING); String name = fetchNGXString(nameNStr, DEFAULT_ENCODING); String code = fetchNGXString(codeNStr, DEFAULT_ENCODING); - NginxHandler handler = NginxHandlerFactory.fetchHandler(phase, type, name, code); HANDLERS.add(handler); - if (pros != 0) { - Map properties = new ArrayMap(); - int size = fetchNGXInt(pros + NGX_HTTP_CLOJURE_ARRAY_NELTS_OFFSET); - long ele = UNSAFE.getAddress(pros + NGX_HTTP_CLOJURE_ARRAY_ELTS_OFFSET); - for (int i = 0; i < size; i++) { - long kv = ele + i * NGX_HTTP_CLOJURE_KEYVALT_SIZE; - properties.put(fetchNGXString(kv + NGX_HTTP_CLOJURE_KEYVALT_KEY_OFFSET, DEFAULT_ENCODING), - fetchNGXString(kv + NGX_HTTP_CLOJURE_KEYVALT_VALUE_OFFSET, DEFAULT_ENCODING)); - } - for (Entry en : properties.entrySet()) { - en.setValue(evalSimpleExp(en.getValue(), properties)); - } - if (handler instanceof Configurable) { - Configurable cr = (Configurable) handler; - cr.config(properties); - }else { - log.warn("%s is not an instance of nginx.clojure.Configurable, so properties will be ignored!", - handler.getClass()); + Runnable runnable = new Runnable() { + public void run() { + if (pros != 0) { + Map properties = new ArrayMap(); + int size = fetchNGXInt(pros + NGX_HTTP_CLOJURE_ARRAY_NELTS_OFFSET); + long ele = UNSAFE.getAddress(pros + NGX_HTTP_CLOJURE_ARRAY_ELTS_OFFSET); + for (int i = 0; i < size; i++) { + long kv = ele + i * NGX_HTTP_CLOJURE_KEYVALT_SIZE; + properties.put(fetchNGXString(kv + NGX_HTTP_CLOJURE_KEYVALT_KEY_OFFSET, DEFAULT_ENCODING), + fetchNGXString(kv + NGX_HTTP_CLOJURE_KEYVALT_VALUE_OFFSET, DEFAULT_ENCODING)); + } + for (Entry en : properties.entrySet()) { + en.setValue(evalSimpleExp(en.getValue(), properties)); + } + if (handler instanceof Configurable) { + Configurable cr = (Configurable) handler; + cr.config(properties); + }else { + log.warn("%s is not an instance of nginx.clojure.Configurable, so properties will be ignored!", + handler.getClass()); + } + } } + }; + if (coroutineEnabled) { + new Coroutine(runnable).resume(); + } else { + runnable.run(); } + return HANDLERS.size() - 1; } @@ -909,6 +939,7 @@ public static final String fetchNGXString(long address, Charset encoding, ByteBu if (len <= 0){ return null; } + return fetchString(address + NGX_HTTP_CLOJURE_STR_DATA_OFFSET, len, encoding, bb, cb); } @@ -986,6 +1017,11 @@ public static final String fetchString(long paddress, int size, Charset encoding if (size > bb.limit()) { size = bb.limit(); } + + if (size == 7168) { + System.err.println("too long value??"); + } + ngx_http_clojure_mem_copy_to_obj(UNSAFE.getAddress(paddress), bb.array(), BYTE_ARRAY_OFFSET, size); bb.limit(size); return HackUtils.decode(bb, encoding, cb); @@ -1099,7 +1135,7 @@ public Integer call() throws Exception { } } - protected static int unsafeSetNginxVariable(long r, String name, String val) throws OutOfMemoryError { + public static int unsafeSetNginxVariable(long r, String name, String val) throws OutOfMemoryError { long np = CORE_VARS.containsKey(name) ? CORE_VARS.get(name) : 0; long pool = UNSAFE.getAddress(r + NGX_HTTP_CLOJURE_REQ_POOL_OFFSET); @@ -1397,7 +1433,7 @@ private static void handleChannelEvent(int type, long status, Object data, Chann break; default: if (listener instanceof RawMessageListener) { - RawMessageListener rawListener = (RawMessageListener) listener; + RawMessageListener rawListener = (RawMessageListener) listener; if ( (type & NGX_HTTP_CLOJURE_CHANNEL_EVENT_MSGTEXT) != 0) { rawListener.onTextMessage(data, status, (type & NGX_HTTP_CLOJURE_CHANNEL_EVENT_MSGREMAIN) != 0, (type & NGX_HTTP_CLOJURE_CHANNEL_EVENT_MSGFIRST) != 0); }else if ( (type & NGX_HTTP_CLOJURE_CHANNEL_EVENT_MSGBIN) != 0) { @@ -1428,14 +1464,17 @@ public static int handlePostedResponse(long r) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } + ctx.request.applyDelayed(); + if (resp.type() == NginxResponse.TYPE_FAKE_PHASE_DONE) { if (ctx.request.phase() == NGX_HTTP_HEADER_FILTER_PHASE) { - rc = ngx_http_filter_continue_next(r, -1); + rc = ngx_http_filter_continue_next(r, NGX_HTTP_HEADER_FILTER_IN_THREADPOOL, 0); ngx_http_finalize_request(r, rc); return NGX_OK; - }else if (ctx.request.phase() == NGX_HTTP_BODY_FILTER_PHASE) { + } else if (ctx.request.phase() == NGX_HTTP_BODY_FILTER_PHASE) { ctx.chain = req.handler().buildOutputChain(resp); - rc = ngx_http_filter_continue_next(r, ctx.chain); + NginxFilterRequest fr = (NginxFilterRequest)req; + rc = ngx_http_filter_continue_next(r, ctx.chain, fr.isLast() ? 0 : fr.chunkChain()); if (resp.isLast()) { ngx_http_finalize_request(r, rc); } @@ -1443,9 +1482,10 @@ public static int handlePostedResponse(long r) { } ngx_http_clojure_mem_continue_current_phase(r, NGX_DECLINED); return NGX_OK; - }else if (ctx.request.phase() == NGX_HTTP_BODY_FILTER_PHASE) { + } else if (ctx.request.phase() == NGX_HTTP_BODY_FILTER_PHASE) { ctx.chain = req.handler().buildOutputChain(resp); - rc = ngx_http_filter_continue_next(r, ctx.chain); + NginxFilterRequest fr = (NginxFilterRequest)req; + rc = ngx_http_filter_continue_next(r, ctx.chain, fr.isLast() ? 0 : fr.chunkChain()); if (resp.isLast()) { ngx_http_finalize_request(r, rc); } else { @@ -1454,6 +1494,7 @@ public static int handlePostedResponse(long r) { return NGX_OK; } + // the handler returns direct body and doesn't want to continue next phase. long chain = ctx.chain; int phase = req.phase(); long nr = req.nativeRequest(); @@ -1511,6 +1552,38 @@ protected static long handleReturnCodeFromHandler(long r, int phase, long rc, in return rc; } + public static int handleLoadBalancerResponse(NginxRequest req, long c, NginxResponse resp) { + if (resp == null) { + return NGX_HTTP_NOT_FOUND; + } + + int status = resp.fetchStatus(NGX_ERROR); + if (status != NGX_HTTP_OK) { + return status; + } + + Object body = resp.fetchBody(); + if (body == null) { + return NGX_HTTP_NOT_FOUND; + } + + long idxOrLenAddr = UNSAFE.getAddress(c); + long urlAddr = UNSAFE.getAddress(c + NGX_HTTP_CLOJURE_UINT_SIZE); + long pool = UNSAFE.getAddress(req.nativeRequest() + NGX_HTTP_CLOJURE_REQ_POOL_OFFSET); + if (body instanceof String) { + String url = (String)body; + pushNGXInt(idxOrLenAddr, url.length()); + pushString(urlAddr, url, DEFAULT_ENCODING, pool); + } else if (body instanceof Integer) { + pushNGXInt(idxOrLenAddr, (Integer)body); + } else { + log.error("bad load balancer result type :" + body.getClass() + ", should be integer or string"); + return NGX_ERROR; + } + + return NGX_OK; + } + public static int handleResponse(NginxRequest r, final NginxResponse resp) { if (Thread.currentThread() != NGINX_MAIN_THREAD) { throw new RuntimeException("handleResponse can not be called out of nginx clojure main thread!"); @@ -1520,12 +1593,13 @@ public static int handleResponse(NginxRequest r, final NginxResponse resp) { return NGX_HTTP_NOT_FOUND; } int phase = r.phase(); + if (resp.type() == NginxResponse.TYPE_FAKE_PHASE_DONE) { if (phase == NGX_HTTP_REWRITE_PHASE || phase == NGX_HTTP_ACCESS_PHASE) { return NGX_DECLINED; } //header filter - return (int)ngx_http_filter_continue_next(r.nativeRequest(), -1); + return (int)ngx_http_filter_continue_next(r.nativeRequest(), NGX_HTTP_HEADER_FILTER, 0); } NginxHandler handler = r.handler(); @@ -1540,8 +1614,9 @@ public static int handleResponse(NginxRequest r, final NginxResponse resp) { if (phase == NGX_HTTP_HEADER_FILTER_PHASE) { ngx_http_clear_header_and_reset_ctx_phase(nr, ~phase); }else if (phase == NGX_HTTP_BODY_FILTER_PHASE) { + NginxFilterRequest fr = (NginxFilterRequest)r; ngx_http_clear_header_and_reset_ctx_phase(nr, ~phase, false); - return (int)ngx_http_filter_continue_next(r.nativeRequest(), chain); + return (int)ngx_http_filter_continue_next(r.nativeRequest(), chain, fr.isLast() ? 0 : fr.chunkChain()); } handler.prepareHeaders(r, status, resp.fetchHeaders()); long rc = ngx_http_send_header(r.nativeRequest()); @@ -1574,11 +1649,13 @@ public static void completeAsyncResponse(NginxRequest req, final NginxResponse r return; } + req.applyDelayed(); + long rc; int phase = req.phase(); if (resp.type() == NginxResponse.TYPE_FAKE_PHASE_DONE) { if (phase == NGX_HTTP_HEADER_FILTER_PHASE) { - rc = ngx_http_filter_continue_next(r, -1); + rc = ngx_http_filter_continue_next(r, NGX_HTTP_HEADER_FILTER, 0); ngx_http_finalize_request(r, rc); return; } @@ -1740,10 +1817,12 @@ public static int broadcastEvent(String message) { public static final class BatchCallRunner implements Runnable { Coroutine parent; int[] counter; + @SuppressWarnings("rawtypes") Callable handler; int order; Object[] results; + @SuppressWarnings("rawtypes") public BatchCallRunner(Coroutine parent, int[] counter, Callable handler, int order, Object[] results) { super(); @@ -1768,7 +1847,7 @@ public void run() throws SuspendExecution { } } - public static final Object[] coBatchCall(Callable ...calls) { + public static final Object[] coBatchCall(@SuppressWarnings("unchecked") Callable ...calls) { int c = calls.length; int[] counter = new int[] {c}; @@ -1777,6 +1856,7 @@ public static final Object[] coBatchCall(Callable ...calls) { if (parent == null && (JavaAgent.db == null || !JavaAgent.db.isRunTool())) { log.warn("we are not in coroutine enabled context, so we turn to use thread for only testing usage!"); + @SuppressWarnings("rawtypes") Future[] futures = new Future[c]; for (int i = 0; i < c ; i++) { BatchCallRunner bcr = new BatchCallRunner(parent, counter, calls[i], i, results); @@ -1785,7 +1865,7 @@ public static final Object[] coBatchCall(Callable ...calls) { } futures[i] = threadPoolOnlyForTestingUsage.submit(bcr); } - for (Future f : futures) { + for (@SuppressWarnings("rawtypes") Future f : futures) { try { f.get(); } catch (Throwable e) { diff --git a/src/java/nginx/clojure/NginxFilterRequest.java b/src/java/nginx/clojure/NginxFilterRequest.java index e1e6dfd4..6aacdda9 100644 --- a/src/java/nginx/clojure/NginxFilterRequest.java +++ b/src/java/nginx/clojure/NginxFilterRequest.java @@ -8,4 +8,7 @@ public interface NginxFilterRequest extends NginxRequest { public Map responseHeaders(); + public long chunkChain(); + + public boolean isLast(); } diff --git a/src/java/nginx/clojure/NginxHandlerFactory.java b/src/java/nginx/clojure/NginxHandlerFactory.java index f3417fcb..c690ed81 100644 --- a/src/java/nginx/clojure/NginxHandlerFactory.java +++ b/src/java/nginx/clojure/NginxHandlerFactory.java @@ -28,6 +28,7 @@ public static synchronized NginxHandlerFactory fetchFactory(String type) { return null; } try { + @SuppressWarnings("rawtypes") Class clz = Thread.currentThread().getContextClassLoader().loadClass(factoryName); factory = (NginxHandlerFactory) clz.newInstance(); handlerFactoryMap.put(type, factory); diff --git a/src/java/nginx/clojure/NginxHttpServerChannel.java b/src/java/nginx/clojure/NginxHttpServerChannel.java index 5cb853af..7f95f9dd 100644 --- a/src/java/nginx/clojure/NginxHttpServerChannel.java +++ b/src/java/nginx/clojure/NginxHttpServerChannel.java @@ -16,7 +16,6 @@ import java.util.Collections; import java.util.Map; import java.util.Map.Entry; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; @@ -25,15 +24,15 @@ import sun.nio.ch.DirectBuffer; public class NginxHttpServerChannel implements Closeable { - + protected NginxRequest request; protected boolean ignoreFilter; protected volatile boolean closed; protected Object context; protected long asyncTimeout; - protected Object closeLock = new Object[0]; + protected final Object closeLock = new Object[0]; - private static ChannelListener closeListener = new ChannelCloseAdapter() { + private static final ChannelListener closeListener = new ChannelCloseAdapter() { @Override public void onClose(NginxHttpServerChannel sc) { synchronized (sc.closeLock) { @@ -44,20 +43,20 @@ public void onClose(NginxHttpServerChannel sc) { } } }; - + public NginxHttpServerChannel(NginxRequest request, boolean ignoreFilter) { this.request = request; this.ignoreFilter = ignoreFilter; request.addListener(this, closeListener); } - + public void addListener(T data, ChannelListener listener) { this.request.addListener(data, listener); } - + /** - * turn on event handler - * @throws IOException + * turn on event handler + * @throws IOException */ public void turnOnEventHandler(boolean read, boolean write, boolean nokeepalive) throws IOException { checkValid(); @@ -73,49 +72,42 @@ public void turnOnEventHandler(boolean read, boolean write, boolean nokeepalive) } if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) { final int fflag = flag; - NginxClojureRT.postPollTaskEvent(new Runnable() { - @Override - public void run() { - NginxClojureRT.ngx_http_hijack_turn_on_event_handler(request.nativeRequest(), fflag); - } - }); + NginxClojureRT.postPollTaskEvent(() -> NginxClojureRT.ngx_http_hijack_turn_on_event_handler(request.nativeRequest(), fflag)); }else { NginxClojureRT.ngx_http_hijack_turn_on_event_handler(request.nativeRequest(), flag); } } - - + + protected int send(byte[] message, long off, int len, int flag) { if (message == null) { return (int)NginxClojureRT.ngx_http_hijack_send(request.nativeRequest(), null, 0, 0, flag); } return (int)NginxClojureRT.ngx_http_hijack_send(request.nativeRequest(), message, MiniConstants.BYTE_ARRAY_OFFSET + off, len, flag); } - + protected int send(ByteBuffer message, int flag) { if (message == null) { return (int) NginxClojureRT.ngx_http_hijack_send(request.nativeRequest(), null, 0, 0, flag); } - int rc = 0; - if (message.isDirect()) { - rc = (int) NginxClojureRT.ngx_http_hijack_send(request.nativeRequest(), null, - ((DirectBuffer) message).address() + message.position(), message.remaining(), flag); - } else { - rc = (int) NginxClojureRT.ngx_http_hijack_send(request.nativeRequest(), message.array(), - MiniConstants.BYTE_ARRAY_OFFSET + message.arrayOffset()+message.position(), message.remaining(), flag); - } + int rc = (int) (message.isDirect() ? + NginxClojureRT.ngx_http_hijack_send(request.nativeRequest(), null, + ((DirectBuffer) message).address() + message.position(), message.remaining(), flag) : + NginxClojureRT.ngx_http_hijack_send(request.nativeRequest(), message.array(), + MiniConstants.BYTE_ARRAY_OFFSET + message.arrayOffset()+message.position(), message.remaining(), flag)); + if (rc == MiniConstants.NGX_OK) { message.position(message.limit()); } return rc; } - + private final void checkValid() throws IOException { if (closed) { throw new IOException("Op on a closed NginxHttpServerChannel with request :" + request); } } - + /** * If message is null when flush is true it will do flush, when last is true it will close channel. */ @@ -146,7 +138,7 @@ public int computeFlag(boolean flush, boolean last) { } return flag; } - + public void flush() throws IOException { checkValid(); int flag = computeFlag(true, false); @@ -156,7 +148,7 @@ public void flush() throws IOException { send(null, 0, 0, flag); } } - + public void send(String message, boolean flush, boolean last) throws IOException { checkValid(); if (last) { @@ -175,7 +167,7 @@ public void send(String message, boolean flush, boolean last) throws IOException send(bs, 0, bs == null ? 0 : bs.length, flag); } } - + public void send(ByteBuffer message, boolean flush, boolean last) throws IOException { checkValid(); if (last) { @@ -198,10 +190,10 @@ public void send(ByteBuffer message, boolean flush, boolean last) throws IOExcep send(message, flag); } } - + public long read(ByteBuffer buf) throws IOException { long rc = 0; - + if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) { synchronized (closeLock) { if (closed) { @@ -227,11 +219,11 @@ public long read(ByteBuffer buf) throws IOException { + buf.arrayOffset() + buf.position(), buf.remaining()); } } - + if (NginxClojureRT.log.isDebugEnabled()) { NginxClojureRT.log.debug("NginxHttpServerChannel read rc=%d", rc); } - + if (rc == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_AGAIN) { return 0; }else if (rc == 0) { @@ -241,13 +233,13 @@ public long read(ByteBuffer buf) throws IOException { }else { buf.position(buf.position() + (int)rc); } - + return rc; } - + public long read(byte[] buf, long off, long size) throws IOException { long rc; - + if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) { synchronized (closeLock) { if (closed) { @@ -261,7 +253,7 @@ public long read(byte[] buf, long off, long size) throws IOException { } rc = NginxClojureRT.ngx_http_hijack_read(request.nativeRequest(), buf, MiniConstants.BYTE_ARRAY_OFFSET + off, size); } - + if (NginxClojureRT.log.isDebugEnabled()) { NginxClojureRT.log.debug("NginxHttpServerChannel read rc=%d", rc); } @@ -274,11 +266,11 @@ public long read(byte[] buf, long off, long size) throws IOException { } return rc; } - + protected long unsafeWrite(byte[] buf, long off, long size) { return NginxClojureRT.ngx_http_hijack_write(request.nativeRequest(), buf, MiniConstants.BYTE_ARRAY_OFFSET + off, size); } - + protected long unsafeWrite(ByteBuffer buf) { long rc; if (buf.isDirect()) { @@ -288,27 +280,27 @@ protected long unsafeWrite(ByteBuffer buf) { rc = NginxClojureRT.ngx_http_hijack_write(request.nativeRequest(), buf.array(), MiniConstants.BYTE_ARRAY_OFFSET + buf.arrayOffset() + buf.position(), buf.remaining()); } - + if (rc > 0) { buf.position(buf.position() + (int)rc); } return rc; } - + public long write(byte[] buf, long off, int size) throws IOException { checkValid(); long rc; - + if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) { rc = NginxClojureRT.postHijackWriteEvent(this, buf, off, size); }else { rc = unsafeWrite(buf, off, size); } - + if (NginxClojureRT.log.isDebugEnabled()) { NginxClojureRT.log.debug("NginxHttpServerChannel write rc=%d", rc); } - + if (rc == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_AGAIN) { return 0; }else if (rc == 0) { @@ -316,24 +308,24 @@ public long write(byte[] buf, long off, int size) throws IOException { }else if (rc < 0) { throw new IOException(NginxClojureAsynSocket.errorCodeToString(rc)); } - + return (int)rc; } - + public long write(ByteBuffer buf) throws IOException { checkValid(); long rc; - + if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) { rc = NginxClojureRT.postHijackWriteEvent(this, buf, 0, buf.remaining()); }else { rc = unsafeWrite(buf); } - + if (NginxClojureRT.log.isDebugEnabled()) { NginxClojureRT.log.debug("NginxHttpServerChannel write rc=%d", rc); } - + if (rc == NginxClojureAsynSocket.NGX_HTTP_CLOJURE_SOCKET_ERR_AGAIN) { return 0; }else if (rc == 0) { @@ -343,11 +335,11 @@ public long write(ByteBuffer buf) throws IOException { } return rc; } - + protected void sendHeader(int flag) { NginxClojureRT.ngx_http_hijack_send_header(request.nativeRequest(), flag); } - + protected int sendHeader(byte[] message, long off, int len, int flag) { int rc = (int)NginxClojureRT.ngx_http_hijack_send_header(request.nativeRequest(), message, MiniConstants.BYTE_ARRAY_OFFSET + off, len, flag); if (rc < 0) { @@ -355,14 +347,14 @@ protected int sendHeader(byte[] message, long off, int len, int flag) { } return rc; } - + protected int sendHeader(ByteBuffer message, int flag) { int rc = 0; if (message.isDirect()) { - rc = (int) NginxClojureRT.ngx_http_hijack_send_header(request.nativeRequest(), null, + rc = (int) NginxClojureRT.ngx_http_hijack_send_header(request.nativeRequest(), null, ((DirectBuffer) message).address() + message.position(), message.remaining(), flag); } else { - rc = (int) NginxClojureRT.ngx_http_hijack_send_header(request.nativeRequest(), message.array(), + rc = (int) NginxClojureRT.ngx_http_hijack_send_header(request.nativeRequest(), message.array(), MiniConstants.BYTE_ARRAY_OFFSET + message.arrayOffset()+message.position(), message.remaining(), flag); } if (rc == MiniConstants.NGX_OK) { @@ -372,7 +364,7 @@ protected int sendHeader(ByteBuffer message, int flag) { } return rc; } - + public void sendHeader(long status, Collection> headers, boolean flush, boolean last) throws IOException { checkValid(); if (last) { @@ -382,12 +374,11 @@ public void sendHeader(long status, Collection> headers, request.handler().prepareHeaders(request, status, headers); if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) { NginxClojureRT.postHijackSendHeaderEvent(this, flag); - return; - }else { + } else { sendHeader(flag); } } - + public void sendHeader(byte[] buf, int pos, int len, boolean flush, boolean last) throws IOException { checkValid(); if (last) { @@ -396,31 +387,32 @@ public void sendHeader(byte[] buf, int pos, int len, boolean flush, boolean last int flag = computeFlag(flush, last); if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) { NginxClojureRT.postHijackSendHeaderEvent(this, buf, pos, len, flag); - return; - }else { + } else { sendHeader(buf, pos, len, flag); } } - - + + protected long sendResponseHelp(NginxResponse resp, long chain) { NginxRequest req = resp.request(); if (req.isReleased()) { if (resp.type() > 0) { - log.error("#%d: request is release! and we alos meet an unhandled exception! %s", req.nativeRequest(), resp.fetchBody()); + log.error("#%d: request is release! and we also meet an unhandled exception! %s", req.nativeRequest(), resp.fetchBody()); }else { log.error("#%d: request is release! ", req.nativeRequest()); } return MiniConstants.NGX_HTTP_INTERNAL_SERVER_ERROR; } + + req.applyDelayed(); long rc = NGX_OK; long r = req.nativeRequest(); - + if (resp.type() == NginxResponse.TYPE_FAKE_PHASE_DONE) { if (req.phase() == MiniConstants.NGX_HTTP_HEADER_FILTER_PHASE) { - rc = NginxClojureRT.ngx_http_filter_continue_next(r, -1); + rc = NginxClojureRT.ngx_http_filter_continue_next(r, -1, 0); NginxClojureRT.ngx_http_finalize_request(r, rc); return NGX_OK; } @@ -431,7 +423,7 @@ protected long sendResponseHelp(NginxResponse resp, long chain) { NginxClojureRT.ngx_http_clojure_mem_continue_current_phase(r, MiniConstants.NGX_DECLINED); return NGX_OK; } - + int phase = req.phase(); long nr = req.nativeRequest(); if (chain < 0) { @@ -446,8 +438,7 @@ protected long sendResponseHelp(NginxResponse resp, long chain) { } req.handler().prepareHeaders(req, status, resp.fetchHeaders()); rc = NginxClojureRT.ngx_http_hijack_send_header(r, computeFlag(false, false)); - if (rc == MiniConstants.NGX_ERROR || rc > NGX_OK) { - }else { + if (rc != MiniConstants.NGX_ERROR && rc <= NGX_OK) { //close will be done by handleReturnCodeFromHandler, so we do not need pass close flag rc = NginxClojureRT.ngx_http_hijack_send_chain(r, chain, computeFlag(true, false)); if (rc == NGX_OK && phase != -1) { @@ -462,7 +453,7 @@ protected long sendResponseHelp(NginxResponse resp, long chain) { } } } - + if (phase == -1 || phase == MiniConstants.NGX_HTTP_HEADER_FILTER_PHASE) { NginxClojureRT.ngx_http_finalize_request(r, rc); }else if (rc != MiniConstants.NGX_DONE){ @@ -470,7 +461,7 @@ protected long sendResponseHelp(NginxResponse resp, long chain) { } return NGX_OK; } - + public void sendResponse(Object resp) throws IOException { checkValid(); NginxResponse response = request.handler().toNginxResponse(request, resp); @@ -480,10 +471,10 @@ public void sendResponse(Object resp) throws IOException { sendResponseHelp(response, request.handler().buildOutputChain(response)); } } - + public void sendBody(final Object body, boolean last) throws IOException { checkValid(); - + if (last) { closed = true; } @@ -492,12 +483,12 @@ public void sendBody(final Object body, boolean last) throws IOException { public Object fetchBody() { return body; } - + @Override public Collection> fetchHeaders() { - return Collections.EMPTY_LIST; + return Collections.emptyList(); } - + @Override public int fetchStatus(int defaultStatus) { return 200; @@ -510,7 +501,7 @@ public int fetchStatus(int defaultStatus) { NginxClojureRT.ngx_http_hijack_send_chain(request.nativeRequest(), chain, computeFlag(false, last)); } } - + public void sendResponse(int status) throws IOException { checkValid(); if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) { @@ -521,7 +512,7 @@ public void sendResponse(int status) throws IOException { NginxClojureRT.ngx_http_finalize_request(request.nativeRequest(), status); } } - + public void close() throws IOException { int flag = computeFlag(false, true); if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) { @@ -541,63 +532,53 @@ public void close() throws IOException { send(null, 0, 0, flag); } } - + public void tagClose() { closed = true; } - + public boolean isIgnoreFilter() { return ignoreFilter; } - + public void setIgnoreFilter(boolean ignoreFilter) { this.ignoreFilter = ignoreFilter; } - + public NginxRequest request() { return request; } - + public boolean isClosed() { return closed; } - + public Object getContext() { return context; } - + public void setContext(Object context) { this.context = context; } - + public long getAsyncTimeout() { return asyncTimeout; } - + public void setAsyncTimeout(final long asyncTimeout) throws IOException { checkValid(); this.asyncTimeout = asyncTimeout; - + if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) { - NginxClojureRT.postPollTaskEvent(new Runnable() { - @Override - public void run() { - NginxClojureRT.ngx_http_hijack_set_async_timeout(request.nativeRequest(), asyncTimeout); - } - }); + NginxClojureRT.postPollTaskEvent(() -> NginxClojureRT.ngx_http_hijack_set_async_timeout(request.nativeRequest(), asyncTimeout)); }else { NginxClojureRT.ngx_http_hijack_set_async_timeout(request.nativeRequest(), asyncTimeout); } } - + public boolean webSocketUpgrade(final boolean sendErrorForNonWebSocket) { if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) { - FutureTask task = new FutureTask(new Callable() { - @Override - public Boolean call() throws Exception { - return NginxClojureRT.ngx_http_clojure_websocket_upgrade(request.nativeRequest(), sendErrorForNonWebSocket ? 1 : 0) == 0; - } - }); + FutureTask task = new FutureTask<>(() -> NginxClojureRT.ngx_http_clojure_websocket_upgrade(request.nativeRequest(), sendErrorForNonWebSocket ? 1 : 0) == 0); NginxClojureRT.postPollTaskEvent(task); try { return task.get(); diff --git a/src/java/nginx/clojure/NginxRequest.java b/src/java/nginx/clojure/NginxRequest.java index 43cf707f..9e05aa6c 100644 --- a/src/java/nginx/clojure/NginxRequest.java +++ b/src/java/nginx/clojure/NginxRequest.java @@ -16,7 +16,9 @@ public interface NginxRequest { //for safe access with another thread public void prefetchAll(); - public void prefetchAll(String[] headers, String[] variables); + public void prefetchAll(String[] headers, String[] variables, String[] outHeaders); + + public void applyDelayed(); public NginxHandler handler(); diff --git a/src/java/nginx/clojure/NginxResponse.java b/src/java/nginx/clojure/NginxResponse.java index 7e4dfe02..435a77e1 100644 --- a/src/java/nginx/clojure/NginxResponse.java +++ b/src/java/nginx/clojure/NginxResponse.java @@ -11,6 +11,7 @@ public interface NginxResponse { public static int TYPE_NORMAL = 0; public static int TYPE_ERROR = 1; public static int TYPE_FATAL = 2; + public static Object[] EMPTY_RESPONSE = new Object[] {null, null, null}; public int type(); diff --git a/src/java/nginx/clojure/NginxSimpleHandler.java b/src/java/nginx/clojure/NginxSimpleHandler.java index 97714663..0f8af0d1 100644 --- a/src/java/nginx/clojure/NginxSimpleHandler.java +++ b/src/java/nginx/clojure/NginxSimpleHandler.java @@ -16,6 +16,7 @@ import static nginx.clojure.MiniConstants.NGX_HTTP_HEADER_FILTER_PHASE; import static nginx.clojure.MiniConstants.NGX_HTTP_INTERNAL_SERVER_ERROR; import static nginx.clojure.MiniConstants.NGX_HTTP_LOG_PHASE; +import static nginx.clojure.MiniConstants.NGX_HTTP_LOAD_BALANCE_PHASE; import static nginx.clojure.MiniConstants.NGX_HTTP_NO_CONTENT; import static nginx.clojure.MiniConstants.NGX_HTTP_OK; import static nginx.clojure.MiniConstants.NGX_HTTP_SWITCHING_PROTOCOLS; @@ -24,6 +25,7 @@ import static nginx.clojure.NginxClojureRT.UNSAFE; import static nginx.clojure.NginxClojureRT.coroutineEnabled; import static nginx.clojure.NginxClojureRT.handleResponse; +import static nginx.clojure.NginxClojureRT.handleLoadBalancerResponse; import static nginx.clojure.NginxClojureRT.log; import static nginx.clojure.NginxClojureRT.ngx_http_clojure_mem_build_file_chain; import static nginx.clojure.NginxClojureRT.ngx_http_clojure_mem_build_temp_chain; @@ -51,7 +53,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Future; @@ -59,6 +60,7 @@ import nginx.clojure.Coroutine.FinishAwaredRunnable; import nginx.clojure.NginxClojureRT.WorkerResponseContext; import nginx.clojure.java.Constants; +import nginx.clojure.java.DefinedPrefetch; import nginx.clojure.java.NginxJavaResponse; import sun.nio.ch.DirectBuffer; import sun.nio.cs.ThreadLocalCoders; @@ -66,9 +68,9 @@ public abstract class NginxSimpleHandler implements NginxHandler, Configurable { - protected static ConcurrentLinkedQueue pooledCoroutines = new ConcurrentLinkedQueue(); + protected static ConcurrentLinkedQueue pooledCoroutines = new ConcurrentLinkedQueue<>(); - protected static ConcurrentHashMap> lastRequestEvalFutures = new ConcurrentHashMap>(); + protected static ConcurrentHashMap> lastRequestEvalFutures = new ConcurrentHashMap<>(); private static final boolean ONLY_CONTENT_HENADLER_SUPPORT_THREADS = Boolean.parseBoolean(System.getProperty("nc.threads.only_for_content", "false")); @@ -78,6 +80,8 @@ public abstract class NginxSimpleHandler implements NginxHandler, Configurable { public abstract String[] variablesNeedPrefetch(); + public abstract String[] responseHeadersNeedPrefetch(); + protected boolean forcePrefetchAllProperties = false; @Override @@ -101,9 +105,16 @@ public int execute(final long r, final long c) { final int phase = req.phase(); boolean isWebSocket = req.isWebSocket(); + if (phase == NGX_HTTP_LOAD_BALANCE_PHASE) { + NginxResponse resp = handleRequest(req); + return handleLoadBalancerResponse(req, c, resp); + } + if (forcePrefetchAllProperties) { //for safe access with another thread - req.prefetchAll(headersNeedPrefetch(), variablesNeedPrefetch()); + req.prefetchAll(DefinedPrefetch.ALL_HEADERS, + variablesNeedPrefetch() == DefinedPrefetch.NO_VARS ? DefinedPrefetch.CORE_VARS : variablesNeedPrefetch(), + responseHeadersNeedPrefetch()); } if (workers == null || (isWebSocket && phase == -1) @@ -136,9 +147,9 @@ public int execute(final long r, final long c) { } } - if (!forcePrefetchAllProperties) { + if (!forcePrefetchAllProperties && !coroutineEnabled) { //for safe access with another thread - req.prefetchAll(headersNeedPrefetch(), variablesNeedPrefetch()); + req.prefetchAll(headersNeedPrefetch(), variablesNeedPrefetch(), responseHeadersNeedPrefetch()); } if (phase == NGX_HTTP_LOG_PHASE) { @@ -154,7 +165,7 @@ public int execute(final long r, final long c) { //with thread pool mode we need make it safe if (!forcePrefetchAllProperties) { - req.prefetchAll(headersNeedPrefetch(), variablesNeedPrefetch()); + req.prefetchAll(headersNeedPrefetch(), variablesNeedPrefetch(), responseHeadersNeedPrefetch()); } if (phase == -1 || phase == NGX_HTTP_HEADER_FILTER_PHASE @@ -169,17 +180,14 @@ public int execute(final long r, final long c) { } final Future lastFuture = lastRequestEvalFutures.get(req.nativeRequest()); - Future future = workers.submit(new Callable() { - @Override - public WorkerResponseContext call() throws Exception { - NginxClojureRT.getLog().debug("req %s, c %s, phase %s", req.nativeRequest(), req.nativeCount(), req.phase()); - if (lastFuture != null) { - lastFuture.get(); - } - NginxResponse resp = handleRequest(req); - //let output chain built before entering the main thread - return new WorkerResponseContext(resp, req); + Future future = workers.submit(() -> { + NginxClojureRT.getLog().debug("req %s, c %s, phase %s", req.nativeRequest(), req.nativeCount(), req.phase()); + if (lastFuture != null) { + lastFuture.get(); } + NginxResponse resp = handleRequest(req); + //let output chain built before entering the main thread + return new WorkerResponseContext(resp, req); }); lastRequestEvalFutures.put(req.nativeRequest(), future); @@ -221,23 +229,21 @@ public static NginxResponse handleRequest(final NginxRequest req) { } } - public static interface SimpleEntrySetter { - public Object setValue(Object value); + public interface SimpleEntrySetter { + T setValue(T value); } - public final static SimpleEntrySetter readOnlyEntrySetter = new SimpleEntrySetter() { - public Object setValue(Object value) { - throw new UnsupportedOperationException("read only entry can not set!"); - } + public final static SimpleEntrySetter readOnlyEntrySetter = value -> { + throw new UnsupportedOperationException("read only entry can not set!"); }; public static class SimpleEntry implements Entry { public K key; public V value; - public SimpleEntrySetter setter; + public SimpleEntrySetter setter; - public SimpleEntry(K key, V value, SimpleEntrySetter simpleEntrySetter) { + public SimpleEntry(K key, V value, SimpleEntrySetter simpleEntrySetter) { this.key = key; this.value = value; this.setter = simpleEntrySetter; @@ -255,7 +261,7 @@ public V getValue() { @Override public V setValue(V value) { - return (V)setter.setValue(value); + return setter.setValue(value); } } @@ -280,6 +286,7 @@ public int fetchStatus(int defaultStatus) { return 500; } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public Collection> fetchHeaders() { return (List)Arrays.asList(new SimpleEntry(CONTENT_TYPE, "text/plain", readOnlyEntrySetter)); @@ -314,7 +321,6 @@ public CoroutineRunner(NginxRequest request) { this.request = request; } - @SuppressWarnings("rawtypes") @Override public void run() throws SuspendExecution { try { @@ -351,7 +357,6 @@ public long prepareHeaders(NginxRequest req, long status, Collection hen : headers) { Object nameObj = hen.getKey(); @@ -395,7 +400,7 @@ public long prepareHeaders(NginxRequest req, long status, Collection> 1; if (db.meetTraceTargetClassMethod(classAndMethod)) { - Object fv = value; db.info(buildMessage(s, "#%d pushVObject %s, tos=%d midx=%d sp=%d idx=%d v=%s", s.verifyInfo.vid, classAndMethod, s.methodTOS, midx, s.curMethodSP, idx, takeValueWithoutRealizeIt(value))); } s.checkClassAndMethod(midx, "pushV", classAndMethod); @@ -400,12 +405,12 @@ public final int getIntV(int idx, String classAndMethod) { }else if (ort instanceof Character) { prt = ((Character)ort).charValue(); }else { - printErrorMessage(this.db, this,"#%d getIntV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, rt, ort); + printErrorMessage(db, this,"#%d getIntV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, rt, ort); return rt; } if (rt != prt) { - printErrorMessage(this.db, this ,"#%d getIntV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, rt, prt); + printErrorMessage(db, this ,"#%d getIntV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, rt, prt); } return rt; } @@ -422,11 +427,11 @@ public final float getFloatV(int idx, String classAndMethod) { if (ort instanceof Float) { prt = (Float)ort; }else { - printErrorMessage(this.db, this,"#%d getFloatV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx,curMethodSP, idx, rt, ort); + printErrorMessage(db, this,"#%d getFloatV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx,curMethodSP, idx, rt, ort); return rt; } if (rt != prt) { - printErrorMessage(this.db, this ,"#%d getFloatV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx,rt, prt); + printErrorMessage(db, this ,"#%d getFloatV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx,rt, prt); } return rt; } @@ -443,11 +448,11 @@ public final long getLongV(int idx, String classAndMethod) { if (ort instanceof Long) { prt = (Long)ort; }else { - printErrorMessage(this.db, this ,"#%d getLongV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, rt, ort); + printErrorMessage(db, this ,"#%d getLongV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, rt, ort); return rt; } if (rt != prt) { - printErrorMessage(this.db, this ,"#%d getLongV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx,rt, prt); + printErrorMessage(db, this ,"#%d getLongV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx,rt, prt); } return rt; } @@ -464,11 +469,11 @@ public final double getDoubleV(int idx, String classAndMethod) { if (ort instanceof Double) { prt = (Double)ort; }else { - printErrorMessage(this.db, this ,"#%d getDoubleV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS,midx, curMethodSP, idx, rt, ort); + printErrorMessage(db, this ,"#%d getDoubleV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS,midx, curMethodSP, idx, rt, ort); return rt; } if (rt != prt) { - printErrorMessage(this.db, this ,"#%d getDoubleV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx,rt, prt); + printErrorMessage(db, this ,"#%d getDoubleV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx,rt, prt); } return rt; } @@ -481,7 +486,7 @@ public final Object getObjectV(int idx, String classAndMethod) { } Object prt = verifyInfo.methodIdxInfos[midx].vars[idx].value; if (rt != prt) { - printErrorMessage(this.db, this ,"#%d getObjectV %s tos=%d midx=%d, sp=%d idx=%d %s != %s" , verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, takeValueWithoutRealizeIt(rt), takeValueWithoutRealizeIt(prt)); + printErrorMessage(db, this ,"#%d getObjectV %s tos=%d midx=%d, sp=%d idx=%d %s != %s" , verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, takeValueWithoutRealizeIt(rt), takeValueWithoutRealizeIt(prt)); } return rt; } @@ -543,12 +548,18 @@ public boolean allObjsAreNull() { } protected void release() { + + if (Coroutine.isUseNative()) { + return; + } + methodTOS = -1; if (verifyInfo != null) { verifyInfo.tracerStacks.clear(); fillNull(verifyInfo.methodIdxInfos, 0, verifyInfo.methodIdxInfos.length); } - fillNull(dataObject, 0, dataObject.length); + + fillNull(dataObject, 0, dataObject.length); } public static void fillNull(Object[] array, int s, int len) { diff --git a/src/java/nginx/clojure/SuspendExecution.java b/src/java/nginx/clojure/SuspendExecution.java index 3199b622..63929e3c 100644 --- a/src/java/nginx/clojure/SuspendExecution.java +++ b/src/java/nginx/clojure/SuspendExecution.java @@ -45,7 +45,9 @@ */ public final class SuspendExecution extends RuntimeException { - static final SuspendExecution instance = new SuspendExecution(); + private static final long serialVersionUID = 1L; + + static final SuspendExecution instance = new SuspendExecution(); private SuspendExecution() { } diff --git a/src/java/nginx/clojure/TableEltHeaderHolder.java b/src/java/nginx/clojure/TableEltHeaderHolder.java index c358fd00..38e20b2d 100644 --- a/src/java/nginx/clojure/TableEltHeaderHolder.java +++ b/src/java/nginx/clojure/TableEltHeaderHolder.java @@ -9,6 +9,9 @@ import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_HASH_OFFSET; import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_KEY_OFFSET; import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET; +import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET; +import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_STR_LEN_OFFSET; +import static nginx.clojure.MiniConstants.NGINX_VER; import static nginx.clojure.NginxClojureRT.UNSAFE; import static nginx.clojure.NginxClojureRT.fetchNGXString; import static nginx.clojure.NginxClojureRT.ngx_http_clojure_mem_shadow_copy_ngx_str; @@ -42,6 +45,11 @@ public void push(long h, long pool, Object v) { ngx_http_clojure_mem_shadow_copy_ngx_str(HEADERS_NAMES.get(name), p + NGX_HTTP_CLOJURE_TEL_KEY_OFFSET); } pushNGXString(p + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET, pickString(v), DEFAULT_ENCODING, pool); + + if (NGINX_VER >= 1023000) { + UNSAFE.putAddress(p + NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET, 0); + } + UNSAFE.putAddress(h + offset, p); } @@ -51,7 +59,8 @@ public void clear(long h) { long p = UNSAFE.getAddress(h + offset); if (p != 0) { NginxClojureRT.pushNGXInt(p + NGX_HTTP_CLOJURE_TEL_HASH_OFFSET, 0); - UNSAFE.putAddress(h+offset, 0); + NginxClojureRT.pushNGXInt(p + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET + NGX_HTTP_CLOJURE_STR_LEN_OFFSET, 0); + UNSAFE.putAddress(h + offset, 0); } } @Override @@ -60,7 +69,7 @@ public Object fetch(long h) { if (p == 0) { return null; } - return fetchNGXString(p +NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET , DEFAULT_ENCODING); + return fetchNGXString(p + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET , DEFAULT_ENCODING); } @Override diff --git a/src/java/nginx/clojure/UnknownHeaderHolder.java b/src/java/nginx/clojure/UnknownHeaderHolder.java index 7bef4225..a1995b17 100644 --- a/src/java/nginx/clojure/UnknownHeaderHolder.java +++ b/src/java/nginx/clojure/UnknownHeaderHolder.java @@ -11,6 +11,7 @@ import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_HASH_OFFSET; import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_KEY_OFFSET; import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET; +import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_STR_LEN_OFFSET; import static nginx.clojure.NginxClojureRT.fetchNGXString; import static nginx.clojure.NginxClojureRT.ngx_http_clojure_mem_get_header; import static nginx.clojure.NginxClojureRT.ngx_http_clojure_mem_shadow_copy_ngx_str; @@ -47,6 +48,7 @@ public long knownOffset() { return -1; } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public void push(long h, long pool, Object v) { @@ -101,10 +103,12 @@ public void clear(long h) { int c = (int)ngx_http_clojure_mem_get_header(h, array, BYTE_ARRAY_OFFSET , nameLen, valuesOffset, kbb.capacity()); kbb.clear(); kbb.position(NGINX_CLOJURE_CORE_CLIENT_HEADER_MAX_LINE_SIZE); - LongBuffer lbb =kbb.order(ByteOrder.nativeOrder()).asLongBuffer(); + LongBuffer lbb = kbb.order(ByteOrder.nativeOrder()).asLongBuffer(); for (; c > 0; c--) { - pushNGXInt(lbb.get() + NGX_HTTP_CLOJURE_TEL_HASH_OFFSET, 0); + long p = lbb.get(); + pushNGXInt(p + NGX_HTTP_CLOJURE_TEL_HASH_OFFSET, 0); + pushNGXInt(p + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET + NGX_HTTP_CLOJURE_STR_LEN_OFFSET, 0); } } diff --git a/src/java/nginx/clojure/asm/AnnotationVisitor.java b/src/java/nginx/clojure/asm/AnnotationVisitor.java index 33110974..ae76fb12 100644 --- a/src/java/nginx/clojure/asm/AnnotationVisitor.java +++ b/src/java/nginx/clojure/asm/AnnotationVisitor.java @@ -38,8 +38,8 @@ public abstract class AnnotationVisitor { /** - * The ASM API version implemented by this visitor. The value of this field must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + * The ASM API version implemented by this visitor. The value of this field must be one of the + * {@code ASM}x values in {@link Opcodes}. */ protected final int api; @@ -52,36 +52,49 @@ public abstract class AnnotationVisitor { /** * Constructs a new {@link AnnotationVisitor}. * - * @param api the ASM API version implemented by this visitor. Must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. */ - public AnnotationVisitor(final int api) { + protected AnnotationVisitor(final int api) { this(api, null); } /** * Constructs a new {@link AnnotationVisitor}. * - * @param api the ASM API version implemented by this visitor. Must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. * @param annotationVisitor the annotation visitor to which this visitor must delegate method * calls. May be {@literal null}. */ - public AnnotationVisitor(final int api, final AnnotationVisitor annotationVisitor) { - if (api != Opcodes.ASM7 + protected AnnotationVisitor(final int api, final AnnotationVisitor annotationVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 - && api != Opcodes.ASM8_EXPERIMENTAL) { + && api != Opcodes.ASM10_EXPERIMENTAL) { throw new IllegalArgumentException("Unsupported api " + api); } - if (api == Opcodes.ASM8_EXPERIMENTAL) { - Constants.checkAsm8Experimental(this); + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); } this.api = api; this.av = annotationVisitor; } + /** + * The annotation visitor to which this visitor must delegate method calls. May be {@literal + * null}. + * + * @return the annotation visitor to which this visitor must delegate method calls, or {@literal + * null}. + */ + public AnnotationVisitor getDelegate() { + return av; + } + /** * Visits a primitive value of the annotation. * @@ -129,9 +142,9 @@ public AnnotationVisitor visitAnnotation(final String name, final String descrip } /** - * Visits an array value of the annotation. Note that arrays of primitive types (such as byte, + * Visits an array value of the annotation. Note that arrays of primitive values (such as byte, * boolean, short, char, int, long, float or double) can be passed as value to {@link #visit - * visit}. This is what {@link ClassReader} does. + * visit}. This is what {@link ClassReader} does for non empty arrays of primitive values. * * @param name the value name. * @return a visitor to visit the actual array value elements, or {@literal null} if this visitor diff --git a/src/java/nginx/clojure/asm/AnnotationWriter.java b/src/java/nginx/clojure/asm/AnnotationWriter.java index 03d23dab..d85252bf 100644 --- a/src/java/nginx/clojure/asm/AnnotationWriter.java +++ b/src/java/nginx/clojure/asm/AnnotationWriter.java @@ -112,7 +112,7 @@ final class AnnotationWriter extends AnnotationVisitor { final boolean useNamedValues, final ByteVector annotation, final AnnotationWriter previousAnnotation) { - super(/* latest api = */ Opcodes.ASM7); + super(/* latest api = */ Opcodes.ASM9); this.symbolTable = symbolTable; this.useNamedValues = useNamedValues; this.annotation = annotation; diff --git a/src/java/nginx/clojure/asm/ByteVector.java b/src/java/nginx/clojure/asm/ByteVector.java index 83618e4d..f06678c8 100644 --- a/src/java/nginx/clojure/asm/ByteVector.java +++ b/src/java/nginx/clojure/asm/ByteVector.java @@ -65,6 +65,15 @@ public ByteVector(final int initialCapacity) { this.length = data.length; } + /** + * Returns the actual number of bytes in this vector. + * + * @return the actual number of bytes in this vector. + */ + public int size() { + return length; + } + /** * Puts a byte into this byte vector. The byte vector is automatically enlarged if necessary. * @@ -352,6 +361,9 @@ public ByteVector putByteArray( * @param size number of additional bytes that this byte vector should be able to receive. */ private void enlarge(final int size) { + if (length > data.length) { + throw new AssertionError("Internal error"); + } int doubleCapacity = 2 * data.length; int minimalCapacity = length + size; byte[] newData = new byte[doubleCapacity > minimalCapacity ? doubleCapacity : minimalCapacity]; diff --git a/src/java/nginx/clojure/asm/ClassReader.java b/src/java/nginx/clojure/asm/ClassReader.java index ad0564b4..8a3aeb20 100644 --- a/src/java/nginx/clojure/asm/ClassReader.java +++ b/src/java/nginx/clojure/asm/ClassReader.java @@ -50,10 +50,11 @@ public class ClassReader { public static final int SKIP_CODE = 1; /** - * A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable, LocalVariableTypeTable - * and LineNumberTable attributes. If this flag is set these attributes are neither parsed nor - * visited (i.e. {@link ClassVisitor#visitSource}, {@link MethodVisitor#visitLocalVariable} and - * {@link MethodVisitor#visitLineNumber} are not called). + * A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable, + * LocalVariableTypeTable, LineNumberTable and MethodParameters attributes. If this flag is set + * these attributes are neither parsed nor visited (i.e. {@link ClassVisitor#visitSource}, {@link + * MethodVisitor#visitLocalVariable}, {@link MethodVisitor#visitLineNumber} and {@link + * MethodVisitor#visitParameter} are not called). */ public static final int SKIP_DEBUG = 2; @@ -87,6 +88,9 @@ public class ClassReader { */ static final int EXPAND_ASM_INSNS = 256; + /** The maximum size of array to allocate. */ + private static final int MAX_BUFFER_SIZE = 1024 * 1024; + /** The size of the temporary byte array used to read class input streams chunk by chunk. */ private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096; @@ -100,6 +104,9 @@ public class ClassReader { // DontCheck(MemberName): can't be renamed (for backward binary compatibility). public final byte[] b; + /** The offset in bytes of the ClassFile's access_flags field. */ + public final int header; + /** * A byte array containing the JVMS ClassFile structure to be parsed. The content of this array * must not be modified. This field is intended for {@link Attribute} sub classes, and is normally @@ -146,9 +153,6 @@ public class ClassReader { */ private final int maxStringLength; - /** The offset in bytes of the ClassFile's access_flags field. */ - public final int header; - // ----------------------------------------------------------------------------------------------- // Constructors // ----------------------------------------------------------------------------------------------- @@ -190,7 +194,7 @@ public ClassReader( this.b = classFileBuffer; // Check the class' major_version. This field is after the magic and minor_version fields, which // use 4 and 2 bytes respectively. - if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V14) { + if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V20) { throw new IllegalArgumentException( "Unsupported class file major version " + readShort(classFileOffset + 6)); } @@ -304,18 +308,25 @@ public ClassReader(final String className) throws IOException { * @return the content of the given input stream. * @throws IOException if a problem occurs during reading. */ + @SuppressWarnings("PMD.UseTryWithResources") private static byte[] readStream(final InputStream inputStream, final boolean close) throws IOException { if (inputStream == null) { throw new IOException("Class not found"); } + int bufferSize = computeBufferSize(inputStream); try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - byte[] data = new byte[INPUT_STREAM_DATA_CHUNK_SIZE]; + byte[] data = new byte[bufferSize]; int bytesRead; - while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) { + int readCount = 0; + while ((bytesRead = inputStream.read(data, 0, bufferSize)) != -1) { outputStream.write(data, 0, bytesRead); + readCount++; } outputStream.flush(); + if (readCount == 1) { + return data; + } return outputStream.toByteArray(); } finally { if (close) { @@ -324,6 +335,19 @@ private static byte[] readStream(final InputStream inputStream, final boolean cl } } + private static int computeBufferSize(final InputStream inputStream) throws IOException { + int expectedLength = inputStream.available(); + /* + * Some implementations can return 0 while holding available data (e.g. new + * FileInputStream("/proc/a_file")). Also in some pathological cases a very small number might + * be returned, and in this case we use a default size. + */ + if (expectedLength < 256) { + return INPUT_STREAM_DATA_CHUNK_SIZE; + } + return Math.min(expectedLength, MAX_BUFFER_SIZE); + } + // ----------------------------------------------------------------------------------------------- // Accessors // ----------------------------------------------------------------------------------------------- @@ -351,7 +375,7 @@ public String getClassName() { } /** - * Returns the internal of name of the super class (see {@link Type#getInternalName()}). For + * Returns the internal name of the super class (see {@link Type#getInternalName()}). For * interfaces, the super class is {@link Object}. * * @return the internal name of the super class, or {@literal null} for {@link Object} class. @@ -466,8 +490,10 @@ public void accept( String nestHostClass = null; // - The offset of the NestMembers attribute, or 0. int nestMembersOffset = 0; - // - The offset of the PermittedSubtypes attribute, or 0 - int permittedSubtypesOffset = 0; + // - The offset of the PermittedSubclasses attribute, or 0 + int permittedSubclassesOffset = 0; + // - The offset of the Record attribute, or 0. + int recordOffset = 0; // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). // This list in the reverse order or their order in the ClassFile structure. Attribute attributes = null; @@ -490,8 +516,8 @@ public void accept( nestHostClass = readClass(currentAttributeOffset, charBuffer); } else if (Constants.NEST_MEMBERS.equals(attributeName)) { nestMembersOffset = currentAttributeOffset; - } else if (Constants.PERMITTED_SUBTYPES.equals(attributeName)) { - permittedSubtypesOffset = currentAttributeOffset; + } else if (Constants.PERMITTED_SUBCLASSES.equals(attributeName)) { + permittedSubclassesOffset = currentAttributeOffset; } else if (Constants.SIGNATURE.equals(attributeName)) { signature = readUTF8(currentAttributeOffset, charBuffer); } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { @@ -503,12 +529,18 @@ public void accept( } else if (Constants.SYNTHETIC.equals(attributeName)) { accessFlags |= Opcodes.ACC_SYNTHETIC; } else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) { + if (attributeLength > classFileBuffer.length - currentAttributeOffset) { + throw new IllegalArgumentException(); + } sourceDebugExtension = readUtf(currentAttributeOffset, attributeLength, new char[attributeLength]); } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { runtimeInvisibleAnnotationsOffset = currentAttributeOffset; } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { runtimeInvisibleTypeAnnotationsOffset = currentAttributeOffset; + } else if (Constants.RECORD.equals(attributeName)) { + recordOffset = currentAttributeOffset; + accessFlags |= Opcodes.ACC_RECORD; } else if (Constants.MODULE.equals(attributeName)) { moduleOffset = currentAttributeOffset; } else if (Constants.MODULE_MAIN_CLASS.equals(attributeName)) { @@ -666,14 +698,14 @@ public void accept( } } - // Visit the PermittedSubtypes attribute. - if (permittedSubtypesOffset != 0) { - int numberOfPermittedSubtypes = readUnsignedShort(permittedSubtypesOffset); - int currentPermittedSubtypeOffset = permittedSubtypesOffset + 2; - while (numberOfPermittedSubtypes-- > 0) { - classVisitor.visitPermittedSubtypeExperimental( - readClass(currentPermittedSubtypeOffset, charBuffer)); - currentPermittedSubtypeOffset += 2; + // Visit the PermittedSubclasses attribute. + if (permittedSubclassesOffset != 0) { + int numberOfPermittedSubclasses = readUnsignedShort(permittedSubclassesOffset); + int currentPermittedSubclassesOffset = permittedSubclassesOffset + 2; + while (numberOfPermittedSubclasses-- > 0) { + classVisitor.visitPermittedSubclass( + readClass(currentPermittedSubclassesOffset, charBuffer)); + currentPermittedSubclassesOffset += 2; } } @@ -691,6 +723,15 @@ public void accept( } } + // Visit Record components. + if (recordOffset != 0) { + int recordComponentsCount = readUnsignedShort(recordOffset); + recordOffset += 2; + while (recordComponentsCount-- > 0) { + recordOffset = readRecordComponent(classVisitor, context, recordOffset); + } + } + // Visit the fields and methods. int fieldsCount = readUnsignedShort(currentOffset); currentOffset += 2; @@ -818,7 +859,7 @@ private void readModuleAttributes( currentOffset += 2; } - // Read the 'provides_count' and 'provides' fields. + // Read the 'provides_count' and 'provides' fields. int providesCount = readUnsignedShort(currentOffset); currentOffset += 2; while (providesCount-- > 0) { @@ -838,6 +879,180 @@ private void readModuleAttributes( moduleVisitor.visitEnd(); } + /** + * Reads a record component and visit it. + * + * @param classVisitor the current class visitor + * @param context information about the class being parsed. + * @param recordComponentOffset the offset of the current record component. + * @return the offset of the first byte following the record component. + */ + private int readRecordComponent( + final ClassVisitor classVisitor, final Context context, final int recordComponentOffset) { + char[] charBuffer = context.charBuffer; + + int currentOffset = recordComponentOffset; + String name = readUTF8(currentOffset, charBuffer); + String descriptor = readUTF8(currentOffset + 2, charBuffer); + currentOffset += 4; + + // Read the record component attributes (the variables are ordered as in Section 4.7 of the + // JVMS). + + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The string corresponding to the Signature attribute, or null. + String signature = null; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.SIGNATURE.equals(attributeName)) { + signature = readUTF8(currentOffset, charBuffer); + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentOffset; + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } + + RecordComponentVisitor recordComponentVisitor = + classVisitor.visitRecordComponent(name, descriptor, signature); + if (recordComponentVisitor == null) { + return currentOffset; + } + + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in FieldWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + recordComponentVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the end of the field. + recordComponentVisitor.visitEnd(); + return currentOffset; + } + /** * Reads a JVMS field_info structure and makes the given visitor visit it. * @@ -1164,7 +1379,7 @@ private int readMethod( } // Visit the MethodParameters attribute. - if (methodParametersOffset != 0) { + if (methodParametersOffset != 0 && (context.parsingOptions & SKIP_DEBUG) == 0) { int parametersCount = readByte(methodParametersOffset); int currentParameterOffset = methodParametersOffset + 1; while (parametersCount-- > 0) { @@ -1327,6 +1542,9 @@ private void readCode( final int maxLocals = readUnsignedShort(currentOffset + 2); final int codeLength = readInt(currentOffset + 4); currentOffset += 8; + if (codeLength > classFileBuffer.length - currentOffset) { + throw new IllegalArgumentException(); + } // Read the bytecode 'code' array to create a label for each referenced instruction. final int bytecodeStartOffset = currentOffset; @@ -1336,113 +1554,113 @@ private void readCode( final int bytecodeOffset = currentOffset - bytecodeStartOffset; final int opcode = classBuffer[currentOffset] & 0xFF; switch (opcode) { - case Constants.NOP: - case Constants.ACONST_NULL: - case Constants.ICONST_M1: - case Constants.ICONST_0: - case Constants.ICONST_1: - case Constants.ICONST_2: - case Constants.ICONST_3: - case Constants.ICONST_4: - case Constants.ICONST_5: - case Constants.LCONST_0: - case Constants.LCONST_1: - case Constants.FCONST_0: - case Constants.FCONST_1: - case Constants.FCONST_2: - case Constants.DCONST_0: - case Constants.DCONST_1: - case Constants.IALOAD: - case Constants.LALOAD: - case Constants.FALOAD: - case Constants.DALOAD: - case Constants.AALOAD: - case Constants.BALOAD: - case Constants.CALOAD: - case Constants.SALOAD: - case Constants.IASTORE: - case Constants.LASTORE: - case Constants.FASTORE: - case Constants.DASTORE: - case Constants.AASTORE: - case Constants.BASTORE: - case Constants.CASTORE: - case Constants.SASTORE: - case Constants.POP: - case Constants.POP2: - case Constants.DUP: - case Constants.DUP_X1: - case Constants.DUP_X2: - case Constants.DUP2: - case Constants.DUP2_X1: - case Constants.DUP2_X2: - case Constants.SWAP: - case Constants.IADD: - case Constants.LADD: - case Constants.FADD: - case Constants.DADD: - case Constants.ISUB: - case Constants.LSUB: - case Constants.FSUB: - case Constants.DSUB: - case Constants.IMUL: - case Constants.LMUL: - case Constants.FMUL: - case Constants.DMUL: - case Constants.IDIV: - case Constants.LDIV: - case Constants.FDIV: - case Constants.DDIV: - case Constants.IREM: - case Constants.LREM: - case Constants.FREM: - case Constants.DREM: - case Constants.INEG: - case Constants.LNEG: - case Constants.FNEG: - case Constants.DNEG: - case Constants.ISHL: - case Constants.LSHL: - case Constants.ISHR: - case Constants.LSHR: - case Constants.IUSHR: - case Constants.LUSHR: - case Constants.IAND: - case Constants.LAND: - case Constants.IOR: - case Constants.LOR: - case Constants.IXOR: - case Constants.LXOR: - case Constants.I2L: - case Constants.I2F: - case Constants.I2D: - case Constants.L2I: - case Constants.L2F: - case Constants.L2D: - case Constants.F2I: - case Constants.F2L: - case Constants.F2D: - case Constants.D2I: - case Constants.D2L: - case Constants.D2F: - case Constants.I2B: - case Constants.I2C: - case Constants.I2S: - case Constants.LCMP: - case Constants.FCMPL: - case Constants.FCMPG: - case Constants.DCMPL: - case Constants.DCMPG: - case Constants.IRETURN: - case Constants.LRETURN: - case Constants.FRETURN: - case Constants.DRETURN: - case Constants.ARETURN: - case Constants.RETURN: - case Constants.ARRAYLENGTH: - case Constants.ATHROW: - case Constants.MONITORENTER: - case Constants.MONITOREXIT: + case Opcodes.NOP: + case Opcodes.ACONST_NULL: + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.IALOAD: + case Opcodes.LALOAD: + case Opcodes.FALOAD: + case Opcodes.DALOAD: + case Opcodes.AALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + case Opcodes.IASTORE: + case Opcodes.LASTORE: + case Opcodes.FASTORE: + case Opcodes.DASTORE: + case Opcodes.AASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.POP: + case Opcodes.POP2: + case Opcodes.DUP: + case Opcodes.DUP_X1: + case Opcodes.DUP_X2: + case Opcodes.DUP2: + case Opcodes.DUP2_X1: + case Opcodes.DUP2_X2: + case Opcodes.SWAP: + case Opcodes.IADD: + case Opcodes.LADD: + case Opcodes.FADD: + case Opcodes.DADD: + case Opcodes.ISUB: + case Opcodes.LSUB: + case Opcodes.FSUB: + case Opcodes.DSUB: + case Opcodes.IMUL: + case Opcodes.LMUL: + case Opcodes.FMUL: + case Opcodes.DMUL: + case Opcodes.IDIV: + case Opcodes.LDIV: + case Opcodes.FDIV: + case Opcodes.DDIV: + case Opcodes.IREM: + case Opcodes.LREM: + case Opcodes.FREM: + case Opcodes.DREM: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.ISHL: + case Opcodes.LSHL: + case Opcodes.ISHR: + case Opcodes.LSHR: + case Opcodes.IUSHR: + case Opcodes.LUSHR: + case Opcodes.IAND: + case Opcodes.LAND: + case Opcodes.IOR: + case Opcodes.LOR: + case Opcodes.IXOR: + case Opcodes.LXOR: + case Opcodes.I2L: + case Opcodes.I2F: + case Opcodes.I2D: + case Opcodes.L2I: + case Opcodes.L2F: + case Opcodes.L2D: + case Opcodes.F2I: + case Opcodes.F2L: + case Opcodes.F2D: + case Opcodes.D2I: + case Opcodes.D2L: + case Opcodes.D2F: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.LCMP: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + case Opcodes.IRETURN: + case Opcodes.LRETURN: + case Opcodes.FRETURN: + case Opcodes.DRETURN: + case Opcodes.ARETURN: + case Opcodes.RETURN: + case Opcodes.ARRAYLENGTH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: case Constants.ILOAD_0: case Constants.ILOAD_1: case Constants.ILOAD_2: @@ -1485,24 +1703,24 @@ private void readCode( case Constants.ASTORE_3: currentOffset += 1; break; - case Constants.IFEQ: - case Constants.IFNE: - case Constants.IFLT: - case Constants.IFGE: - case Constants.IFGT: - case Constants.IFLE: - case Constants.IF_ICMPEQ: - case Constants.IF_ICMPNE: - case Constants.IF_ICMPLT: - case Constants.IF_ICMPGE: - case Constants.IF_ICMPGT: - case Constants.IF_ICMPLE: - case Constants.IF_ACMPEQ: - case Constants.IF_ACMPNE: - case Constants.GOTO: - case Constants.JSR: - case Constants.IFNULL: - case Constants.IFNONNULL: + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.GOTO: + case Opcodes.JSR: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: createLabel(bytecodeOffset + readShort(currentOffset + 1), labels); currentOffset += 3; break; @@ -1535,27 +1753,27 @@ private void readCode( break; case Constants.WIDE: switch (classBuffer[currentOffset + 1] & 0xFF) { - case Constants.ILOAD: - case Constants.FLOAD: - case Constants.ALOAD: - case Constants.LLOAD: - case Constants.DLOAD: - case Constants.ISTORE: - case Constants.FSTORE: - case Constants.ASTORE: - case Constants.LSTORE: - case Constants.DSTORE: - case Constants.RET: + case Opcodes.ILOAD: + case Opcodes.FLOAD: + case Opcodes.ALOAD: + case Opcodes.LLOAD: + case Opcodes.DLOAD: + case Opcodes.ISTORE: + case Opcodes.FSTORE: + case Opcodes.ASTORE: + case Opcodes.LSTORE: + case Opcodes.DSTORE: + case Opcodes.RET: currentOffset += 4; break; - case Constants.IINC: + case Opcodes.IINC: currentOffset += 6; break; default: throw new IllegalArgumentException(); } break; - case Constants.TABLESWITCH: + case Opcodes.TABLESWITCH: // Skip 0 to 3 padding bytes. currentOffset += 4 - (bytecodeOffset & 3); // Read the default label and the number of table entries. @@ -1568,7 +1786,7 @@ private void readCode( currentOffset += 4; } break; - case Constants.LOOKUPSWITCH: + case Opcodes.LOOKUPSWITCH: // Skip 0 to 3 padding bytes. currentOffset += 4 - (bytecodeOffset & 3); // Read the default label and the number of switch cases. @@ -1581,44 +1799,44 @@ private void readCode( currentOffset += 8; } break; - case Constants.ILOAD: - case Constants.LLOAD: - case Constants.FLOAD: - case Constants.DLOAD: - case Constants.ALOAD: - case Constants.ISTORE: - case Constants.LSTORE: - case Constants.FSTORE: - case Constants.DSTORE: - case Constants.ASTORE: - case Constants.RET: - case Constants.BIPUSH: - case Constants.NEWARRAY: - case Constants.LDC: + case Opcodes.ILOAD: + case Opcodes.LLOAD: + case Opcodes.FLOAD: + case Opcodes.DLOAD: + case Opcodes.ALOAD: + case Opcodes.ISTORE: + case Opcodes.LSTORE: + case Opcodes.FSTORE: + case Opcodes.DSTORE: + case Opcodes.ASTORE: + case Opcodes.RET: + case Opcodes.BIPUSH: + case Opcodes.NEWARRAY: + case Opcodes.LDC: currentOffset += 2; break; - case Constants.SIPUSH: + case Opcodes.SIPUSH: case Constants.LDC_W: case Constants.LDC2_W: - case Constants.GETSTATIC: - case Constants.PUTSTATIC: - case Constants.GETFIELD: - case Constants.PUTFIELD: - case Constants.INVOKEVIRTUAL: - case Constants.INVOKESPECIAL: - case Constants.INVOKESTATIC: - case Constants.NEW: - case Constants.ANEWARRAY: - case Constants.CHECKCAST: - case Constants.INSTANCEOF: - case Constants.IINC: + case Opcodes.GETSTATIC: + case Opcodes.PUTSTATIC: + case Opcodes.GETFIELD: + case Opcodes.PUTFIELD: + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.NEW: + case Opcodes.ANEWARRAY: + case Opcodes.CHECKCAST: + case Opcodes.INSTANCEOF: + case Opcodes.IINC: currentOffset += 3; break; - case Constants.INVOKEINTERFACE: - case Constants.INVOKEDYNAMIC: + case Opcodes.INVOKEINTERFACE: + case Opcodes.INVOKEDYNAMIC: currentOffset += 5; break; - case Constants.MULTIANEWARRAY: + case Opcodes.MULTIANEWARRAY: currentOffset += 4; break; default: @@ -1885,113 +2103,113 @@ private void readCode( // Visit the instruction at this bytecode offset. int opcode = classBuffer[currentOffset] & 0xFF; switch (opcode) { - case Constants.NOP: - case Constants.ACONST_NULL: - case Constants.ICONST_M1: - case Constants.ICONST_0: - case Constants.ICONST_1: - case Constants.ICONST_2: - case Constants.ICONST_3: - case Constants.ICONST_4: - case Constants.ICONST_5: - case Constants.LCONST_0: - case Constants.LCONST_1: - case Constants.FCONST_0: - case Constants.FCONST_1: - case Constants.FCONST_2: - case Constants.DCONST_0: - case Constants.DCONST_1: - case Constants.IALOAD: - case Constants.LALOAD: - case Constants.FALOAD: - case Constants.DALOAD: - case Constants.AALOAD: - case Constants.BALOAD: - case Constants.CALOAD: - case Constants.SALOAD: - case Constants.IASTORE: - case Constants.LASTORE: - case Constants.FASTORE: - case Constants.DASTORE: - case Constants.AASTORE: - case Constants.BASTORE: - case Constants.CASTORE: - case Constants.SASTORE: - case Constants.POP: - case Constants.POP2: - case Constants.DUP: - case Constants.DUP_X1: - case Constants.DUP_X2: - case Constants.DUP2: - case Constants.DUP2_X1: - case Constants.DUP2_X2: - case Constants.SWAP: - case Constants.IADD: - case Constants.LADD: - case Constants.FADD: - case Constants.DADD: - case Constants.ISUB: - case Constants.LSUB: - case Constants.FSUB: - case Constants.DSUB: - case Constants.IMUL: - case Constants.LMUL: - case Constants.FMUL: - case Constants.DMUL: - case Constants.IDIV: - case Constants.LDIV: - case Constants.FDIV: - case Constants.DDIV: - case Constants.IREM: - case Constants.LREM: - case Constants.FREM: - case Constants.DREM: - case Constants.INEG: - case Constants.LNEG: - case Constants.FNEG: - case Constants.DNEG: - case Constants.ISHL: - case Constants.LSHL: - case Constants.ISHR: - case Constants.LSHR: - case Constants.IUSHR: - case Constants.LUSHR: - case Constants.IAND: - case Constants.LAND: - case Constants.IOR: - case Constants.LOR: - case Constants.IXOR: - case Constants.LXOR: - case Constants.I2L: - case Constants.I2F: - case Constants.I2D: - case Constants.L2I: - case Constants.L2F: - case Constants.L2D: - case Constants.F2I: - case Constants.F2L: - case Constants.F2D: - case Constants.D2I: - case Constants.D2L: - case Constants.D2F: - case Constants.I2B: - case Constants.I2C: - case Constants.I2S: - case Constants.LCMP: - case Constants.FCMPL: - case Constants.FCMPG: - case Constants.DCMPL: - case Constants.DCMPG: - case Constants.IRETURN: - case Constants.LRETURN: - case Constants.FRETURN: - case Constants.DRETURN: - case Constants.ARETURN: - case Constants.RETURN: - case Constants.ARRAYLENGTH: - case Constants.ATHROW: - case Constants.MONITORENTER: - case Constants.MONITOREXIT: + case Opcodes.NOP: + case Opcodes.ACONST_NULL: + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.IALOAD: + case Opcodes.LALOAD: + case Opcodes.FALOAD: + case Opcodes.DALOAD: + case Opcodes.AALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + case Opcodes.IASTORE: + case Opcodes.LASTORE: + case Opcodes.FASTORE: + case Opcodes.DASTORE: + case Opcodes.AASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.POP: + case Opcodes.POP2: + case Opcodes.DUP: + case Opcodes.DUP_X1: + case Opcodes.DUP_X2: + case Opcodes.DUP2: + case Opcodes.DUP2_X1: + case Opcodes.DUP2_X2: + case Opcodes.SWAP: + case Opcodes.IADD: + case Opcodes.LADD: + case Opcodes.FADD: + case Opcodes.DADD: + case Opcodes.ISUB: + case Opcodes.LSUB: + case Opcodes.FSUB: + case Opcodes.DSUB: + case Opcodes.IMUL: + case Opcodes.LMUL: + case Opcodes.FMUL: + case Opcodes.DMUL: + case Opcodes.IDIV: + case Opcodes.LDIV: + case Opcodes.FDIV: + case Opcodes.DDIV: + case Opcodes.IREM: + case Opcodes.LREM: + case Opcodes.FREM: + case Opcodes.DREM: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.ISHL: + case Opcodes.LSHL: + case Opcodes.ISHR: + case Opcodes.LSHR: + case Opcodes.IUSHR: + case Opcodes.LUSHR: + case Opcodes.IAND: + case Opcodes.LAND: + case Opcodes.IOR: + case Opcodes.LOR: + case Opcodes.IXOR: + case Opcodes.LXOR: + case Opcodes.I2L: + case Opcodes.I2F: + case Opcodes.I2D: + case Opcodes.L2I: + case Opcodes.L2F: + case Opcodes.L2D: + case Opcodes.F2I: + case Opcodes.F2L: + case Opcodes.F2D: + case Opcodes.D2I: + case Opcodes.D2L: + case Opcodes.D2F: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.LCMP: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + case Opcodes.IRETURN: + case Opcodes.LRETURN: + case Opcodes.FRETURN: + case Opcodes.DRETURN: + case Opcodes.ARETURN: + case Opcodes.RETURN: + case Opcodes.ARRAYLENGTH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: methodVisitor.visitInsn(opcode); currentOffset += 1; break; @@ -2043,24 +2261,24 @@ private void readCode( methodVisitor.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), opcode & 0x3); currentOffset += 1; break; - case Constants.IFEQ: - case Constants.IFNE: - case Constants.IFLT: - case Constants.IFGE: - case Constants.IFGT: - case Constants.IFLE: - case Constants.IF_ICMPEQ: - case Constants.IF_ICMPNE: - case Constants.IF_ICMPLT: - case Constants.IF_ICMPGE: - case Constants.IF_ICMPGT: - case Constants.IF_ICMPLE: - case Constants.IF_ACMPEQ: - case Constants.IF_ACMPNE: - case Constants.GOTO: - case Constants.JSR: - case Constants.IFNULL: - case Constants.IFNONNULL: + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.GOTO: + case Opcodes.JSR: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: methodVisitor.visitJumpInsn( opcode, labels[currentBytecodeOffset + readShort(currentOffset + 1)]); currentOffset += 3; @@ -2141,7 +2359,7 @@ private void readCode( currentOffset += 4; } break; - case Constants.TABLESWITCH: + case Opcodes.TABLESWITCH: { // Skip 0 to 3 padding bytes. currentOffset += 4 - (currentBytecodeOffset & 3); @@ -2158,7 +2376,7 @@ private void readCode( methodVisitor.visitTableSwitchInsn(low, high, defaultLabel, table); break; } - case Constants.LOOKUPSWITCH: + case Opcodes.LOOKUPSWITCH: { // Skip 0 to 3 padding bytes. currentOffset += 4 - (currentBytecodeOffset & 3); @@ -2176,30 +2394,30 @@ private void readCode( methodVisitor.visitLookupSwitchInsn(defaultLabel, keys, values); break; } - case Constants.ILOAD: - case Constants.LLOAD: - case Constants.FLOAD: - case Constants.DLOAD: - case Constants.ALOAD: - case Constants.ISTORE: - case Constants.LSTORE: - case Constants.FSTORE: - case Constants.DSTORE: - case Constants.ASTORE: - case Constants.RET: + case Opcodes.ILOAD: + case Opcodes.LLOAD: + case Opcodes.FLOAD: + case Opcodes.DLOAD: + case Opcodes.ALOAD: + case Opcodes.ISTORE: + case Opcodes.LSTORE: + case Opcodes.FSTORE: + case Opcodes.DSTORE: + case Opcodes.ASTORE: + case Opcodes.RET: methodVisitor.visitVarInsn(opcode, classBuffer[currentOffset + 1] & 0xFF); currentOffset += 2; break; - case Constants.BIPUSH: - case Constants.NEWARRAY: + case Opcodes.BIPUSH: + case Opcodes.NEWARRAY: methodVisitor.visitIntInsn(opcode, classBuffer[currentOffset + 1]); currentOffset += 2; break; - case Constants.SIPUSH: + case Opcodes.SIPUSH: methodVisitor.visitIntInsn(opcode, readShort(currentOffset + 1)); currentOffset += 3; break; - case Constants.LDC: + case Opcodes.LDC: methodVisitor.visitLdcInsn(readConst(classBuffer[currentOffset + 1] & 0xFF, charBuffer)); currentOffset += 2; break; @@ -2208,14 +2426,14 @@ private void readCode( methodVisitor.visitLdcInsn(readConst(readUnsignedShort(currentOffset + 1), charBuffer)); currentOffset += 3; break; - case Constants.GETSTATIC: - case Constants.PUTSTATIC: - case Constants.GETFIELD: - case Constants.PUTFIELD: - case Constants.INVOKEVIRTUAL: - case Constants.INVOKESPECIAL: - case Constants.INVOKESTATIC: - case Constants.INVOKEINTERFACE: + case Opcodes.GETSTATIC: + case Opcodes.PUTSTATIC: + case Opcodes.GETFIELD: + case Opcodes.PUTFIELD: + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.INVOKEINTERFACE: { int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)]; int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; @@ -2236,7 +2454,7 @@ private void readCode( } break; } - case Constants.INVOKEDYNAMIC: + case Opcodes.INVOKEDYNAMIC: { int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)]; int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; @@ -2258,19 +2476,19 @@ private void readCode( currentOffset += 5; break; } - case Constants.NEW: - case Constants.ANEWARRAY: - case Constants.CHECKCAST: - case Constants.INSTANCEOF: + case Opcodes.NEW: + case Opcodes.ANEWARRAY: + case Opcodes.CHECKCAST: + case Opcodes.INSTANCEOF: methodVisitor.visitTypeInsn(opcode, readClass(currentOffset + 1, charBuffer)); currentOffset += 3; break; - case Constants.IINC: + case Opcodes.IINC: methodVisitor.visitIincInsn( classBuffer[currentOffset + 1] & 0xFF, classBuffer[currentOffset + 2]); currentOffset += 3; break; - case Constants.MULTIANEWARRAY: + case Opcodes.MULTIANEWARRAY: methodVisitor.visitMultiANewArrayInsn( readClass(currentOffset + 1, charBuffer), classBuffer[currentOffset + 3] & 0xFF); currentOffset += 4; @@ -2778,7 +2996,7 @@ private int readElementValues( // Parse the array_value array. while (numElementValuePairs-- > 0) { currentOffset = - readElementValue(annotationVisitor, currentOffset, /* named = */ null, charBuffer); + readElementValue(annotationVisitor, currentOffset, /* elementName= */ null, charBuffer); } } if (annotationVisitor != null) { @@ -3256,7 +3474,6 @@ final int getFirstAttributeOffset() { private int[] readBootstrapMethodsAttribute(final int maxStringLength) { char[] charBuffer = new char[maxStringLength]; int currentAttributeOffset = getFirstAttributeOffset(); - int[] currentBootstrapMethodOffsets = null; for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { // Read the attribute_info's attribute_name and attribute_length fields. String attributeName = readUTF8(currentAttributeOffset, charBuffer); @@ -3264,17 +3481,17 @@ private int[] readBootstrapMethodsAttribute(final int maxStringLength) { currentAttributeOffset += 6; if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) { // Read the num_bootstrap_methods field and create an array of this size. - currentBootstrapMethodOffsets = new int[readUnsignedShort(currentAttributeOffset)]; + int[] result = new int[readUnsignedShort(currentAttributeOffset)]; // Compute and store the offset of each 'bootstrap_methods' array field entry. int currentBootstrapMethodOffset = currentAttributeOffset + 2; - for (int j = 0; j < currentBootstrapMethodOffsets.length; ++j) { - currentBootstrapMethodOffsets[j] = currentBootstrapMethodOffset; + for (int j = 0; j < result.length; ++j) { + result[j] = currentBootstrapMethodOffset; // Skip the bootstrap_method_ref and num_bootstrap_arguments fields (2 bytes each), // as well as the bootstrap_arguments array field (of size num_bootstrap_arguments * 2). currentBootstrapMethodOffset += 4 + readUnsignedShort(currentBootstrapMethodOffset + 2) * 2; } - return currentBootstrapMethodOffsets; + return result; } currentAttributeOffset += attributeLength; } diff --git a/src/java/nginx/clojure/asm/ClassTooLargeException.java b/src/java/nginx/clojure/asm/ClassTooLargeException.java index 9a8e152f..517a6771 100644 --- a/src/java/nginx/clojure/asm/ClassTooLargeException.java +++ b/src/java/nginx/clojure/asm/ClassTooLargeException.java @@ -42,7 +42,8 @@ public final class ClassTooLargeException extends IndexOutOfBoundsException { /** * Constructs a new {@link ClassTooLargeException}. * - * @param className the internal name of the class. + * @param className the internal name of the class (see {@link + * nginx.clojure.asm.Type#getInternalName()}). * @param constantPoolCount the number of constant pool items of the class. */ public ClassTooLargeException(final String className, final int constantPoolCount) { @@ -52,7 +53,7 @@ public ClassTooLargeException(final String className, final int constantPoolCoun } /** - * Returns the internal name of the class. + * Returns the internal name of the class (see {@link nginx.clojure.asm.Type#getInternalName()}). * * @return the internal name of the class. */ diff --git a/src/java/nginx/clojure/asm/ClassVisitor.java b/src/java/nginx/clojure/asm/ClassVisitor.java index a7ee8453..8cf6ad1d 100644 --- a/src/java/nginx/clojure/asm/ClassVisitor.java +++ b/src/java/nginx/clojure/asm/ClassVisitor.java @@ -30,17 +30,18 @@ /** * A visitor to visit a Java class. The methods of this class must be called in the following order: * {@code visit} [ {@code visitSource} ] [ {@code visitModule} ][ {@code visitNestHost} ][ {@code - * visitPermittedSubtype} ][ {@code visitOuterClass} ] ( {@code visitAnnotation} | {@code - * visitTypeAnnotation} | {@code visitAttribute} )* ( {@code visitNestMember} | {@code - * visitInnerClass} | {@code visitField} | {@code visitMethod} )* {@code visitEnd}. + * visitOuterClass} ] ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code + * visitAttribute} )* ( {@code visitNestMember} | [ {@code * visitPermittedSubclass} ] | {@code + * visitInnerClass} | {@code visitRecordComponent} | {@code visitField} | {@code visitMethod} )* + * {@code visitEnd}. * * @author Eric Bruneton */ public abstract class ClassVisitor { /** - * The ASM API version implemented by this visitor. The value of this field must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + * The ASM API version implemented by this visitor. The value of this field must be one of the + * {@code ASM}x values in {@link Opcodes}. */ protected final int api; @@ -50,43 +51,55 @@ public abstract class ClassVisitor { /** * Constructs a new {@link ClassVisitor}. * - * @param api the ASM API version implemented by this visitor. Must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. */ - public ClassVisitor(final int api) { + protected ClassVisitor(final int api) { this(api, null); } /** * Constructs a new {@link ClassVisitor}. * - * @param api the ASM API version implemented by this visitor. Must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. * @param classVisitor the class visitor to which this visitor must delegate method calls. May be * null. */ - public ClassVisitor(final int api, final ClassVisitor classVisitor) { - if (api != Opcodes.ASM7 + protected ClassVisitor(final int api, final ClassVisitor classVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 - && api != Opcodes.ASM8_EXPERIMENTAL) { + && api != Opcodes.ASM10_EXPERIMENTAL) { throw new IllegalArgumentException("Unsupported api " + api); } - if (api == Opcodes.ASM8_EXPERIMENTAL) { - Constants.checkAsm8Experimental(this); + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); } this.api = api; this.cv = classVisitor; } + /** + * The class visitor to which this visitor must delegate method calls. May be {@literal null}. + * + * @return the class visitor to which this visitor must delegate method calls, or {@literal null}. + */ + public ClassVisitor getDelegate() { + return cv; + } + /** * Visits the header of the class. * * @param version the class version. The minor version is stored in the 16 most significant bits, * and the major version in the 16 least significant bits. * @param access the class's access flags (see {@link Opcodes}). This parameter also indicates if - * the class is deprecated. + * the class is deprecated {@link Opcodes#ACC_DEPRECATED} or a record {@link + * Opcodes#ACC_RECORD}. * @param name the internal name of the class (see {@link Type#getInternalName()}). * @param signature the signature of this class. May be {@literal null} if the class is not a * generic one, and does not extend or implement generic classes or interfaces. @@ -103,6 +116,9 @@ public void visit( final String signature, final String superName, final String[] interfaces) { + if (api < Opcodes.ASM8 && (access & Opcodes.ACC_RECORD) != 0) { + throw new UnsupportedOperationException("Records requires ASM8"); + } if (cv != null) { cv.visit(version, access, name, signature, superName, interfaces); } @@ -134,7 +150,7 @@ public void visitSource(final String source, final String debug) { */ public ModuleVisitor visitModule(final String name, final int access, final String version) { if (api < Opcodes.ASM6) { - throw new UnsupportedOperationException("This feature requires ASM6"); + throw new UnsupportedOperationException("Module requires ASM6"); } if (cv != null) { return cv.visitModule(name, access, version); @@ -150,11 +166,12 @@ public ModuleVisitor visitModule(final String name, final int access, final Stri * implicitly its own nest, so it's invalid to call this method with the visited class name as * argument. * - * @param nestHost the internal name of the host class of the nest. + * @param nestHost the internal name of the host class of the nest (see {@link + * Type#getInternalName()}). */ public void visitNestHost(final String nestHost) { if (api < Opcodes.ASM7) { - throw new UnsupportedOperationException("This feature requires ASM7"); + throw new UnsupportedOperationException("NestHost requires ASM7"); } if (cv != null) { cv.visitNestHost(nestHost); @@ -162,14 +179,19 @@ public void visitNestHost(final String nestHost) { } /** - * Visits the enclosing class of the class. This method must be called only if the class has an - * enclosing class. + * Visits the enclosing class of the class. This method must be called only if this class is a + * local or anonymous class. See the JVMS 4.7.7 section for more details. * - * @param owner internal name of the enclosing class of the class. + * @param owner internal name of the enclosing class of the class (see {@link + * Type#getInternalName()}). * @param name the name of the method that contains the class, or {@literal null} if the class is - * not enclosed in a method of its enclosing class. + * not enclosed in a method or constructor of its enclosing class (e.g. if it is enclosed in + * an instance initializer, static initializer, instance variable initializer, or class + * variable initializer). * @param descriptor the descriptor of the method that contains the class, or {@literal null} if - * the class is not enclosed in a method of its enclosing class. + * the class is not enclosed in a method or constructor of its enclosing class (e.g. if it is + * enclosed in an instance initializer, static initializer, instance variable initializer, or + * class variable initializer). */ public void visitOuterClass(final String owner, final String name, final String descriptor) { if (cv != null) { @@ -210,7 +232,7 @@ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean public AnnotationVisitor visitTypeAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { if (api < Opcodes.ASM5) { - throw new UnsupportedOperationException("This feature requires ASM5"); + throw new UnsupportedOperationException("TypeAnnotation requires ASM5"); } if (cv != null) { return cv.visitTypeAnnotation(typeRef, typePath, descriptor, visible); @@ -236,11 +258,11 @@ public void visitAttribute(final Attribute attribute) { * the visited class is the host of a nest. A nest host is implicitly a member of its own nest, so * it's invalid to call this method with the visited class name as argument. * - * @param nestMember the internal name of a nest member. + * @param nestMember the internal name of a nest member (see {@link Type#getInternalName()}). */ public void visitNestMember(final String nestMember) { if (api < Opcodes.ASM7) { - throw new UnsupportedOperationException("This feature requires ASM7"); + throw new UnsupportedOperationException("NestMember requires ASM7"); } if (cv != null) { cv.visitNestMember(nestMember); @@ -248,34 +270,35 @@ public void visitNestMember(final String nestMember) { } /** - * Experimental, use at your own risk. This method will be renamed when it becomes stable, this - * will break existing code using it. Visits a permitted subtypes. A permitted subtypes is one - * of the allowed subtypes of the current class. + * Visits a permitted subclasses. A permitted subclass is one of the allowed subclasses of the + * current class. * - * @param permittedSubtype the internal name of a permitted subtype. - * @deprecated this API is experimental. + * @param permittedSubclass the internal name of a permitted subclass (see {@link + * Type#getInternalName()}). */ - @Deprecated - public void visitPermittedSubtypeExperimental(final String permittedSubtype) { - if (api != Opcodes.ASM8_EXPERIMENTAL) { - throw new UnsupportedOperationException("This feature requires ASM8_EXPERIMENTAL"); + public void visitPermittedSubclass(final String permittedSubclass) { + if (api < Opcodes.ASM9) { + throw new UnsupportedOperationException("PermittedSubclasses requires ASM9"); } if (cv != null) { - cv.visitPermittedSubtypeExperimental(permittedSubtype); + cv.visitPermittedSubclass(permittedSubclass); } } /** * Visits information about an inner class. This inner class is not necessarily a member of the - * class being visited. + * class being visited. More precisely, every class or interface C which is referenced by this + * class and which is not a package member must be visited with this method. This class must + * reference its nested class or interface members, and its enclosing class, if any. See the JVMS + * 4.7.6 section for more details. * - * @param name the internal name of an inner class (see {@link Type#getInternalName()}). - * @param outerName the internal name of the class to which the inner class belongs (see {@link - * Type#getInternalName()}). May be {@literal null} for not member classes. - * @param innerName the (simple) name of the inner class inside its enclosing class. May be - * {@literal null} for anonymous inner classes. - * @param access the access flags of the inner class as originally declared in the enclosing - * class. + * @param name the internal name of C (see {@link Type#getInternalName()}). + * @param outerName the internal name of the class or interface C is a member of (see {@link + * Type#getInternalName()}). Must be {@literal null} if C is not the member of a class or + * interface (e.g. for local or anonymous classes). + * @param innerName the (simple) name of C. Must be {@literal null} for anonymous inner classes. + * @param access the access flags of C originally declared in the source code from which this + * class was compiled. */ public void visitInnerClass( final String name, final String outerName, final String innerName, final int access) { @@ -284,6 +307,27 @@ public void visitInnerClass( } } + /** + * Visits a record component of the class. + * + * @param name the record component name. + * @param descriptor the record component descriptor (see {@link Type}). + * @param signature the record component signature. May be {@literal null} if the record component + * type does not use generic types. + * @return a visitor to visit this record component annotations and attributes, or {@literal null} + * if this class visitor is not interested in visiting these annotations and attributes. + */ + public RecordComponentVisitor visitRecordComponent( + final String name, final String descriptor, final String signature) { + if (api < Opcodes.ASM8) { + throw new UnsupportedOperationException("Record requires ASM8"); + } + if (cv != null) { + return cv.visitRecordComponent(name, descriptor, signature); + } + return null; + } + /** * Visits a field of the class. * diff --git a/src/java/nginx/clojure/asm/ClassWriter.java b/src/java/nginx/clojure/asm/ClassWriter.java index f4351cbb..17273461 100644 --- a/src/java/nginx/clojure/asm/ClassWriter.java +++ b/src/java/nginx/clojure/asm/ClassWriter.java @@ -65,6 +65,12 @@ public class ClassWriter extends ClassVisitor { */ public static final int COMPUTE_FRAMES = 2; + /** + * The flags passed to the constructor. Must be zero or more of {@link #COMPUTE_MAXS} and {@link + * #COMPUTE_FRAMES}. + */ + private final int flags; + // Note: fields are ordered as in the ClassFile structure, and those related to attributes are // ordered as in Section 4.7 of the JVMS. @@ -79,8 +85,8 @@ public class ClassWriter extends ClassVisitor { /** * The access_flags field of the JVMS ClassFile structure. This field can contain ASM specific - * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the - * ClassFile structure. + * access flags, such as {@link Opcodes#ACC_DEPRECATED} or {@link Opcodes#ACC_RECORD}, which are + * removed when generating the ClassFile structure. */ private int accessFlags; @@ -177,11 +183,25 @@ public class ClassWriter extends ClassVisitor { /** The 'classes' array of the NestMembers attribute, or {@literal null}. */ private ByteVector nestMemberClasses; - /** The number_of_classes field of the PermittedSubtypes attribute, or 0. */ - private int numberOfPermittedSubtypeClasses; + /** The number_of_classes field of the PermittedSubclasses attribute, or 0. */ + private int numberOfPermittedSubclasses; + + /** The 'classes' array of the PermittedSubclasses attribute, or {@literal null}. */ + private ByteVector permittedSubclasses; - /** The 'classes' array of the PermittedSubtypes attribute, or {@literal null}. */ - private ByteVector permittedSubtypeClasses; + /** + * The record components of this class, stored in a linked list of {@link RecordComponentWriter} + * linked via their {@link RecordComponentWriter#delegate} field. This field stores the first + * element of this list. + */ + private RecordComponentWriter firstRecordComponent; + + /** + * The record components of this class, stored in a linked list of {@link RecordComponentWriter} + * linked via their {@link RecordComponentWriter#delegate} field. This field stores the last + * element of this list. + */ + private RecordComponentWriter lastRecordComponent; /** * The first non standard attribute of this class. The next ones can be accessed with the {@link @@ -234,23 +254,39 @@ public ClassWriter(final int flags) { * @param classReader the {@link ClassReader} used to read the original class. It will be used to * copy the entire constant pool and bootstrap methods from the original class and also to * copy other fragments of original bytecode where applicable. - * @param flags option flags that can be used to modify the default behavior of this class.Must be - * zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}. These option flags do - * not affect methods that are copied as is in the new class. This means that neither the + * @param flags option flags that can be used to modify the default behavior of this class. Must + * be zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}. These option flags + * do not affect methods that are copied as is in the new class. This means that neither the * maximum stack size nor the stack frames will be computed for these methods. */ public ClassWriter(final ClassReader classReader, final int flags) { - super(/* latest api = */ Opcodes.ASM7); + super(/* latest api = */ Opcodes.ASM9); + this.flags = flags; symbolTable = classReader == null ? new SymbolTable(this) : new SymbolTable(this, classReader); if ((flags & COMPUTE_FRAMES) != 0) { - this.compute = MethodWriter.COMPUTE_ALL_FRAMES; + compute = MethodWriter.COMPUTE_ALL_FRAMES; } else if ((flags & COMPUTE_MAXS) != 0) { - this.compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL; + compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL; } else { - this.compute = MethodWriter.COMPUTE_NOTHING; + compute = MethodWriter.COMPUTE_NOTHING; } } + // ----------------------------------------------------------------------------------------------- + // Accessors + // ----------------------------------------------------------------------------------------------- + + /** + * Returns true if all the given flags were passed to the constructor. + * + * @param flags some option flags. Must be zero or more of {@link #COMPUTE_MAXS} and {@link + * #COMPUTE_FRAMES}. + * @return true if all the given flags, or more, were passed to the constructor. + */ + public boolean hasFlags(final int flags) { + return (this.flags & flags) == flags; + } + // ----------------------------------------------------------------------------------------------- // Implementation of the ClassVisitor abstract class // ----------------------------------------------------------------------------------------------- @@ -359,12 +395,12 @@ public final void visitNestMember(final String nestMember) { } @Override - public final void visitPermittedSubtypeExperimental(final String permittedSubtype) { - if (permittedSubtypeClasses == null) { - permittedSubtypeClasses = new ByteVector(); + public final void visitPermittedSubclass(final String permittedSubclass) { + if (permittedSubclasses == null) { + permittedSubclasses = new ByteVector(); } - ++numberOfPermittedSubtypeClasses; - permittedSubtypeClasses.putShort(symbolTable.addConstantClass(permittedSubtype).index); + ++numberOfPermittedSubclasses; + permittedSubclasses.putShort(symbolTable.addConstantClass(permittedSubclass).index); } @Override @@ -392,6 +428,19 @@ public final void visitInnerClass( // and throw an exception if there is a difference? } + @Override + public final RecordComponentVisitor visitRecordComponent( + final String name, final String descriptor, final String signature) { + RecordComponentWriter recordComponentWriter = + new RecordComponentWriter(symbolTable, name, descriptor, signature); + if (firstRecordComponent == null) { + firstRecordComponent = recordComponentWriter; + } else { + lastRecordComponent.delegate = recordComponentWriter; + } + return lastRecordComponent = recordComponentWriter; + } + @Override public final FieldVisitor visitField( final int access, @@ -462,6 +511,7 @@ public byte[] toByteArray() { size += methodWriter.computeMethodInfoSize(); methodWriter = (MethodWriter) methodWriter.mv; } + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. int attributesCount = 0; if (innerClasses != null) { @@ -541,10 +591,23 @@ public byte[] toByteArray() { size += 8 + nestMemberClasses.length; symbolTable.addConstantUtf8(Constants.NEST_MEMBERS); } - if (permittedSubtypeClasses != null) { + if (permittedSubclasses != null) { ++attributesCount; - size += 8 + permittedSubtypeClasses.length; - symbolTable.addConstantUtf8(Constants.PERMITTED_SUBTYPES); + size += 8 + permittedSubclasses.length; + symbolTable.addConstantUtf8(Constants.PERMITTED_SUBCLASSES); + } + int recordComponentCount = 0; + int recordSize = 0; + if ((accessFlags & Opcodes.ACC_RECORD) != 0 || firstRecordComponent != null) { + RecordComponentWriter recordComponentWriter = firstRecordComponent; + while (recordComponentWriter != null) { + ++recordComponentCount; + recordSize += recordComponentWriter.computeRecordComponentInfoSize(); + recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate; + } + ++attributesCount; + size += 8 + recordSize; + symbolTable.addConstantUtf8(Constants.RECORD); } if (firstAttribute != null) { attributesCount += firstAttribute.getAttributeCount(); @@ -650,12 +713,23 @@ public byte[] toByteArray() { .putShort(numberOfNestMemberClasses) .putByteArray(nestMemberClasses.data, 0, nestMemberClasses.length); } - if (permittedSubtypeClasses != null) { + if (permittedSubclasses != null) { result - .putShort(symbolTable.addConstantUtf8(Constants.PERMITTED_SUBTYPES)) - .putInt(permittedSubtypeClasses.length + 2) - .putShort(numberOfPermittedSubtypeClasses) - .putByteArray(permittedSubtypeClasses.data, 0, permittedSubtypeClasses.length); + .putShort(symbolTable.addConstantUtf8(Constants.PERMITTED_SUBCLASSES)) + .putInt(permittedSubclasses.length + 2) + .putShort(numberOfPermittedSubclasses) + .putByteArray(permittedSubclasses.data, 0, permittedSubclasses.length); + } + if ((accessFlags & Opcodes.ACC_RECORD) != 0 || firstRecordComponent != null) { + result + .putShort(symbolTable.addConstantUtf8(Constants.RECORD)) + .putInt(recordSize + 2) + .putShort(recordComponentCount); + RecordComponentWriter recordComponentWriter = firstRecordComponent; + while (recordComponentWriter != null) { + recordComponentWriter.putRecordComponentInfo(result); + recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate; + } } if (firstAttribute != null) { firstAttribute.putAttributes(symbolTable, result); @@ -693,8 +767,10 @@ private byte[] replaceAsmInstructions(final byte[] classFile, final boolean hasF nestHostClassIndex = 0; numberOfNestMemberClasses = 0; nestMemberClasses = null; - numberOfPermittedSubtypeClasses = 0; - permittedSubtypeClasses = null; + numberOfPermittedSubclasses = 0; + permittedSubclasses = null; + firstRecordComponent = null; + lastRecordComponent = null; firstAttribute = null; compute = hasFrames ? MethodWriter.COMPUTE_INSERTED_FRAMES : MethodWriter.COMPUTE_NOTHING; new ClassReader(classFile, 0, /* checkClassVersion = */ false) @@ -723,6 +799,11 @@ private Attribute[] getAttributePrototypes() { methodWriter.collectAttributePrototypes(attributePrototypes); methodWriter = (MethodWriter) methodWriter.mv; } + RecordComponentWriter recordComponentWriter = firstRecordComponent; + while (recordComponentWriter != null) { + recordComponentWriter.collectAttributePrototypes(attributePrototypes); + recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate; + } return attributePrototypes.toArray(); } @@ -761,7 +842,7 @@ public int newUTF8(final String value) { * constant pool already contains a similar item. This method is intended for {@link Attribute} * sub classes, and is normally not needed by class generators or adapters. * - * @param value the internal name of the class. + * @param value the internal name of the class (see {@link Type#getInternalName()}). * @return the index of a new or already existing class reference item. */ public int newClass(final String value) { @@ -813,7 +894,8 @@ public int newPackage(final String packageName) { * Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. - * @param owner the internal name of the field or method owner class. + * @param owner the internal name of the field or method owner class (see {@link + * Type#getInternalName()}). * @param name the name of the field or method. * @param descriptor the descriptor of the field or method. * @return the index of a new or already existing method type reference item. @@ -835,7 +917,8 @@ public int newHandle( * Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. - * @param owner the internal name of the field or method owner class. + * @param owner the internal name of the field or method owner class (see {@link + * Type#getInternalName()}). * @param name the name of the field or method. * @param descriptor the descriptor of the field or method. * @param isInterface true if the owner is an interface. @@ -897,7 +980,7 @@ public int newInvokeDynamic( * constant pool already contains a similar item. This method is intended for {@link Attribute} * sub classes, and is normally not needed by class generators or adapters. * - * @param owner the internal name of the field's owner class. + * @param owner the internal name of the field's owner class (see {@link Type#getInternalName()}). * @param name the field's name. * @param descriptor the field's descriptor. * @return the index of a new or already existing field reference item. @@ -911,7 +994,8 @@ public int newField(final String owner, final String name, final String descript * constant pool already contains a similar item. This method is intended for {@link Attribute} * sub classes, and is normally not needed by class generators or adapters. * - * @param owner the internal name of the method's owner class. + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). * @param name the method's name. * @param descriptor the method's descriptor. * @param isInterface {@literal true} if {@code owner} is an interface. @@ -947,9 +1031,10 @@ public int newNameType(final String name, final String descriptor) { * currently being generated by this ClassWriter, which can of course not be loaded since it is * under construction. * - * @param type1 the internal name of a class. - * @param type2 the internal name of another class. - * @return the internal name of the common super class of the two given classes. + * @param type1 the internal name of a class (see {@link Type#getInternalName()}). + * @param type2 the internal name of another class (see {@link Type#getInternalName()}). + * @return the internal name of the common super class of the two given classes (see {@link + * Type#getInternalName()}). */ protected String getCommonSuperClass(final String type1, final String type2) { ClassLoader classLoader = getClassLoader(); diff --git a/src/java/nginx/clojure/asm/Constants.java b/src/java/nginx/clojure/asm/Constants.java index beec1c13..fe402185 100644 --- a/src/java/nginx/clojure/asm/Constants.java +++ b/src/java/nginx/clojure/asm/Constants.java @@ -30,6 +30,7 @@ import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.regex.Pattern; /** * Defines additional JVM opcodes, access flags and constants which are not part of the ASM public @@ -38,7 +39,7 @@ * @see JVMS 6 * @author Eric Bruneton */ -final class Constants implements Opcodes { +final class Constants { // The ClassFile attribute names, in the order they are defined in // https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7-300. @@ -72,7 +73,8 @@ final class Constants implements Opcodes { static final String MODULE_MAIN_CLASS = "ModuleMainClass"; static final String NEST_HOST = "NestHost"; static final String NEST_MEMBERS = "NestMembers"; - static final String PERMITTED_SUBTYPES = "PermittedSubtypes"; + static final String PERMITTED_SUBCLASSES = "PermittedSubclasses"; + static final String RECORD = "Record"; // ASM specific access flags. // WARNING: the 16 least significant bits must NOT be used, to avoid conflicts with standard @@ -145,7 +147,7 @@ final class Constants implements Opcodes { // Constants to convert between normal and wide jump instructions. // The delta between the GOTO_W and JSR_W opcodes and GOTO and JUMP. - static final int WIDE_JUMP_OPCODE_DELTA = GOTO_W - GOTO; + static final int WIDE_JUMP_OPCODE_DELTA = GOTO_W - Opcodes.GOTO; // Constants to convert JVM opcodes to the equivalent ASM specific opcodes, and vice versa. @@ -158,48 +160,62 @@ final class Constants implements Opcodes { // ASM specific opcodes, used for long forward jump instructions. - static final int ASM_IFEQ = IFEQ + ASM_OPCODE_DELTA; - static final int ASM_IFNE = IFNE + ASM_OPCODE_DELTA; - static final int ASM_IFLT = IFLT + ASM_OPCODE_DELTA; - static final int ASM_IFGE = IFGE + ASM_OPCODE_DELTA; - static final int ASM_IFGT = IFGT + ASM_OPCODE_DELTA; - static final int ASM_IFLE = IFLE + ASM_OPCODE_DELTA; - static final int ASM_IF_ICMPEQ = IF_ICMPEQ + ASM_OPCODE_DELTA; - static final int ASM_IF_ICMPNE = IF_ICMPNE + ASM_OPCODE_DELTA; - static final int ASM_IF_ICMPLT = IF_ICMPLT + ASM_OPCODE_DELTA; - static final int ASM_IF_ICMPGE = IF_ICMPGE + ASM_OPCODE_DELTA; - static final int ASM_IF_ICMPGT = IF_ICMPGT + ASM_OPCODE_DELTA; - static final int ASM_IF_ICMPLE = IF_ICMPLE + ASM_OPCODE_DELTA; - static final int ASM_IF_ACMPEQ = IF_ACMPEQ + ASM_OPCODE_DELTA; - static final int ASM_IF_ACMPNE = IF_ACMPNE + ASM_OPCODE_DELTA; - static final int ASM_GOTO = GOTO + ASM_OPCODE_DELTA; - static final int ASM_JSR = JSR + ASM_OPCODE_DELTA; - static final int ASM_IFNULL = IFNULL + ASM_IFNULL_OPCODE_DELTA; - static final int ASM_IFNONNULL = IFNONNULL + ASM_IFNULL_OPCODE_DELTA; + static final int ASM_IFEQ = Opcodes.IFEQ + ASM_OPCODE_DELTA; + static final int ASM_IFNE = Opcodes.IFNE + ASM_OPCODE_DELTA; + static final int ASM_IFLT = Opcodes.IFLT + ASM_OPCODE_DELTA; + static final int ASM_IFGE = Opcodes.IFGE + ASM_OPCODE_DELTA; + static final int ASM_IFGT = Opcodes.IFGT + ASM_OPCODE_DELTA; + static final int ASM_IFLE = Opcodes.IFLE + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPEQ = Opcodes.IF_ICMPEQ + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPNE = Opcodes.IF_ICMPNE + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPLT = Opcodes.IF_ICMPLT + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPGE = Opcodes.IF_ICMPGE + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPGT = Opcodes.IF_ICMPGT + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPLE = Opcodes.IF_ICMPLE + ASM_OPCODE_DELTA; + static final int ASM_IF_ACMPEQ = Opcodes.IF_ACMPEQ + ASM_OPCODE_DELTA; + static final int ASM_IF_ACMPNE = Opcodes.IF_ACMPNE + ASM_OPCODE_DELTA; + static final int ASM_GOTO = Opcodes.GOTO + ASM_OPCODE_DELTA; + static final int ASM_JSR = Opcodes.JSR + ASM_OPCODE_DELTA; + static final int ASM_IFNULL = Opcodes.IFNULL + ASM_IFNULL_OPCODE_DELTA; + static final int ASM_IFNONNULL = Opcodes.IFNONNULL + ASM_IFNULL_OPCODE_DELTA; static final int ASM_GOTO_W = 220; private Constants() {} - static void checkAsm8Experimental(final Object caller) { + static void checkAsmExperimental(final Object caller) { Class callerClass = caller.getClass(); - if (callerClass.getName().startsWith("nginx.clojure.asm.")) { - return; + String internalName = callerClass.getName().replace('.', '/'); + if (!isWhitelisted(internalName)) { + checkIsPreview(callerClass.getClassLoader().getResourceAsStream(internalName + ".class")); } - String callerClassResource = callerClass.getName().replace('.', '/') + ".class"; - InputStream inputStream = callerClass.getClassLoader().getResourceAsStream(callerClassResource); - if (inputStream == null) { + } + + static boolean isWhitelisted(final String internalName) { + if (!internalName.startsWith("org/objectweb/asm/")) { + return false; + } + String member = "(Annotation|Class|Field|Method|Module|RecordComponent|Signature)"; + return internalName.contains("Test$") + || Pattern.matches( + "org/objectweb/asm/util/Trace" + member + "Visitor(\\$.*)?", internalName) + || Pattern.matches( + "org/objectweb/asm/util/Check" + member + "Adapter(\\$.*)?", internalName); + } + + static void checkIsPreview(final InputStream classInputStream) { + if (classInputStream == null) { throw new IllegalStateException("Bytecode not available, can't check class version"); } int minorVersion; - try (DataInputStream callerClassStream = new DataInputStream(inputStream); ) { + try (DataInputStream callerClassStream = new DataInputStream(classInputStream); ) { callerClassStream.readInt(); minorVersion = callerClassStream.readUnsignedShort(); } catch (IOException ioe) { - throw new IllegalStateException("i/O error, can't check class version", ioe); + throw new IllegalStateException("I/O error, can't check class version", ioe); } if (minorVersion != 0xFFFF) { throw new IllegalStateException( - "ASM8_EXPERIMENTAL can only be used by classes compiled with --enable-preview"); + "ASM9_EXPERIMENTAL can only be used by classes compiled with --enable-preview"); } } } diff --git a/src/java/nginx/clojure/asm/FieldVisitor.java b/src/java/nginx/clojure/asm/FieldVisitor.java index 38f0d2e1..468aada8 100644 --- a/src/java/nginx/clojure/asm/FieldVisitor.java +++ b/src/java/nginx/clojure/asm/FieldVisitor.java @@ -37,8 +37,8 @@ public abstract class FieldVisitor { /** - * The ASM API version implemented by this visitor. The value of this field must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + * The ASM API version implemented by this visitor. The value of this field must be one of the + * {@code ASM}x values in {@link Opcodes}. */ protected final int api; @@ -48,36 +48,47 @@ public abstract class FieldVisitor { /** * Constructs a new {@link FieldVisitor}. * - * @param api the ASM API version implemented by this visitor. Must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. */ - public FieldVisitor(final int api) { + protected FieldVisitor(final int api) { this(api, null); } /** * Constructs a new {@link FieldVisitor}. * - * @param api the ASM API version implemented by this visitor. Must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. * @param fieldVisitor the field visitor to which this visitor must delegate method calls. May be * null. */ - public FieldVisitor(final int api, final FieldVisitor fieldVisitor) { - if (api != Opcodes.ASM7 + protected FieldVisitor(final int api, final FieldVisitor fieldVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 - && api != Opcodes.ASM8_EXPERIMENTAL) { + && api != Opcodes.ASM10_EXPERIMENTAL) { throw new IllegalArgumentException("Unsupported api " + api); } - if (api == Opcodes.ASM8_EXPERIMENTAL) { - Constants.checkAsm8Experimental(this); + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); } this.api = api; this.fv = fieldVisitor; } + /** + * The field visitor to which this visitor must delegate method calls. May be {@literal null}. + * + * @return the field visitor to which this visitor must delegate method calls, or {@literal null}. + */ + public FieldVisitor getDelegate() { + return fv; + } + /** * Visits an annotation of the field. * diff --git a/src/java/nginx/clojure/asm/FieldWriter.java b/src/java/nginx/clojure/asm/FieldWriter.java index aef87843..4447a2b2 100644 --- a/src/java/nginx/clojure/asm/FieldWriter.java +++ b/src/java/nginx/clojure/asm/FieldWriter.java @@ -124,7 +124,7 @@ final class FieldWriter extends FieldVisitor { final String descriptor, final String signature, final Object constantValue) { - super(/* latest api = */ Opcodes.ASM7); + super(/* latest api = */ Opcodes.ASM9); this.symbolTable = symbolTable; this.accessFlags = access; this.nameIndex = symbolTable.addConstantUtf8(name); diff --git a/src/java/nginx/clojure/asm/Handle.java b/src/java/nginx/clojure/asm/Handle.java index ced6f3e9..5581b30c 100644 --- a/src/java/nginx/clojure/asm/Handle.java +++ b/src/java/nginx/clojure/asm/Handle.java @@ -65,7 +65,7 @@ public final class Handle { * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link * Opcodes#H_INVOKEINTERFACE}. * @param owner the internal name of the class that owns the field or method designated by this - * handle. + * handle (see {@link Type#getInternalName()}). * @param name the name of the field or method designated by this handle. * @param descriptor the descriptor of the field or method designated by this handle. * @deprecated this constructor has been superseded by {@link #Handle(int, String, String, String, @@ -85,7 +85,7 @@ public Handle(final int tag, final String owner, final String name, final String * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link * Opcodes#H_INVOKEINTERFACE}. * @param owner the internal name of the class that owns the field or method designated by this - * handle. + * handle (see {@link Type#getInternalName()}). * @param name the name of the field or method designated by this handle. * @param descriptor the descriptor of the field or method designated by this handle. * @param isInterface whether the owner is an interface or not. @@ -118,7 +118,8 @@ public int getTag() { /** * Returns the internal name of the class that owns the field or method designated by this handle. * - * @return the internal name of the class that owns the field or method designated by this handle. + * @return the internal name of the class that owns the field or method designated by this handle + * (see {@link Type#getInternalName()}). */ public String getOwner() { return owner; diff --git a/src/java/nginx/clojure/asm/MethodTooLargeException.java b/src/java/nginx/clojure/asm/MethodTooLargeException.java index 417fbc07..e6231ebe 100644 --- a/src/java/nginx/clojure/asm/MethodTooLargeException.java +++ b/src/java/nginx/clojure/asm/MethodTooLargeException.java @@ -44,7 +44,7 @@ public final class MethodTooLargeException extends IndexOutOfBoundsException { /** * Constructs a new {@link MethodTooLargeException}. * - * @param className the internal name of the owner class. + * @param className the internal name of the owner class (see {@link Type#getInternalName()}). * @param methodName the name of the method. * @param descriptor the descriptor of the method. * @param codeSize the size of the method's Code attribute, in bytes. @@ -64,7 +64,7 @@ public MethodTooLargeException( /** * Returns the internal name of the owner class. * - * @return the internal name of the owner class. + * @return the internal name of the owner class (see {@link Type#getInternalName()}). */ public String getClassName() { return className; diff --git a/src/java/nginx/clojure/asm/MethodVisitor.java b/src/java/nginx/clojure/asm/MethodVisitor.java index 2aacedd5..40196478 100644 --- a/src/java/nginx/clojure/asm/MethodVisitor.java +++ b/src/java/nginx/clojure/asm/MethodVisitor.java @@ -51,8 +51,8 @@ public abstract class MethodVisitor { private static final String REQUIRES_ASM5 = "This feature requires ASM5"; /** - * The ASM API version implemented by this visitor. The value of this field must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + * The ASM API version implemented by this visitor. The value of this field must be one of the + * {@code ASM}x values in {@link Opcodes}. */ protected final int api; @@ -64,36 +64,48 @@ public abstract class MethodVisitor { /** * Constructs a new {@link MethodVisitor}. * - * @param api the ASM API version implemented by this visitor. Must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. */ - public MethodVisitor(final int api) { + protected MethodVisitor(final int api) { this(api, null); } /** * Constructs a new {@link MethodVisitor}. * - * @param api the ASM API version implemented by this visitor. Must be one of {@link - * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. * @param methodVisitor the method visitor to which this visitor must delegate method calls. May * be null. */ - public MethodVisitor(final int api, final MethodVisitor methodVisitor) { - if (api != Opcodes.ASM7 + protected MethodVisitor(final int api, final MethodVisitor methodVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 - && api != Opcodes.ASM8_EXPERIMENTAL) { + && api != Opcodes.ASM10_EXPERIMENTAL) { throw new IllegalArgumentException("Unsupported api " + api); } - if (api == Opcodes.ASM8_EXPERIMENTAL) { - Constants.checkAsm8Experimental(this); + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); } this.api = api; this.mv = methodVisitor; } + /** + * The method visitor to which this visitor must delegate method calls. May be {@literal null}. + * + * @return the method visitor to which this visitor must delegate method calls, or {@literal + * null}. + */ + public MethodVisitor getDelegate() { + return mv; + } + // ----------------------------------------------------------------------------------------------- // Parameters, annotations and non standard attributes // ----------------------------------------------------------------------------------------------- @@ -120,7 +132,7 @@ public void visitParameter(final String name, final int access) { * @return a visitor to the visit the actual default value of this annotation interface method, or * {@literal null} if this visitor is not interested in visiting this default value. The * 'name' parameters passed to the methods of this annotation visitor are ignored. Moreover, - * exacly one visit method must be called on this annotation visitor, followed by visitEnd. + * exactly one visit method must be called on this annotation visitor, followed by visitEnd. */ public AnnotationVisitor visitAnnotationDefault() { if (mv != null) { @@ -273,15 +285,17 @@ public void visitCode() { * @param type the type of this stack map frame. Must be {@link Opcodes#F_NEW} for expanded * frames, or {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link * Opcodes#F_SAME} or {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for compressed frames. - * @param numLocal the number of local variables in the visited frame. + * @param numLocal the number of local variables in the visited frame. Long and double values + * count for one variable. * @param local the local variable types in this frame. This array must not be modified. Primitive * types are represented by {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL} or * {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented by a single element). - * Reference types are represented by String objects (representing internal names), and - * uninitialized types by Label objects (this label designates the NEW instruction that - * created this uninitialized value). - * @param numStack the number of operand stack elements in the visited frame. + * Reference types are represented by String objects (representing internal names, see {@link + * Type#getInternalName()}), and uninitialized types by Label objects (this label designates + * the NEW instruction that created this uninitialized value). + * @param numStack the number of operand stack elements in the visited frame. Long and double + * values count for one stack element. * @param stack the operand stack types in this frame. This array must not be modified. Its * content has the same format as the "local" array. * @throws IllegalStateException if a frame is visited just after another one, without any @@ -349,18 +363,18 @@ public void visitIntInsn(final int opcode, final int operand) { * * @param opcode the opcode of the local variable instruction to be visited. This opcode is either * ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET. - * @param var the operand of the instruction to be visited. This operand is the index of a local - * variable. + * @param varIndex the operand of the instruction to be visited. This operand is the index of a + * local variable. */ - public void visitVarInsn(final int opcode, final int var) { + public void visitVarInsn(final int opcode, final int varIndex) { if (mv != null) { - mv.visitVarInsn(opcode, var); + mv.visitVarInsn(opcode, varIndex); } } /** * Visits a type instruction. A type instruction is an instruction that takes the internal name of - * a class as parameter. + * a class as parameter (see {@link Type#getInternalName()}). * * @param opcode the opcode of the type instruction to be visited. This opcode is either NEW, * ANEWARRAY, CHECKCAST or INSTANCEOF. @@ -552,12 +566,12 @@ public void visitLdcInsn(final Object value) { /** * Visits an IINC instruction. * - * @param var index of the local variable to be incremented. + * @param varIndex index of the local variable to be incremented. * @param increment amount to increment the local variable by. */ - public void visitIincInsn(final int var, final int increment) { + public void visitIincInsn(final int varIndex, final int increment) { if (mv != null) { - mv.visitIincInsn(var, increment); + mv.visitIincInsn(varIndex, increment); } } @@ -643,8 +657,9 @@ public AnnotationVisitor visitInsnAnnotation( * @param start the beginning of the exception handler's scope (inclusive). * @param end the end of the exception handler's scope (exclusive). * @param handler the beginning of the exception handler's code. - * @param type the internal name of the type of exceptions handled by the handler, or {@literal - * null} to catch any exceptions (for "finally" blocks). + * @param type the internal name of the type of exceptions handled by the handler (see {@link + * Type#getInternalName()}), or {@literal null} to catch any exceptions (for "finally" + * blocks). * @throws IllegalArgumentException if one of the labels has already been visited by this visitor * (by the {@link #visitLabel} method). */ diff --git a/src/java/nginx/clojure/asm/MethodWriter.java b/src/java/nginx/clojure/asm/MethodWriter.java index c53ac476..355af173 100644 --- a/src/java/nginx/clojure/asm/MethodWriter.java +++ b/src/java/nginx/clojure/asm/MethodWriter.java @@ -466,7 +466,8 @@ final class MethodWriter extends MethodVisitor { /** * Indicates what must be computed. Must be one of {@link #COMPUTE_ALL_FRAMES}, {@link - * #COMPUTE_INSERTED_FRAMES}, {@link #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_NOTHING}. + * #COMPUTE_INSERTED_FRAMES}, {@link COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link + * #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_NOTHING}. */ private final int compute; @@ -592,7 +593,7 @@ final class MethodWriter extends MethodVisitor { final String signature, final String[] exceptions, final int compute) { - super(/* latest api = */ Opcodes.ASM7); + super(/* latest api = */ Opcodes.ASM9); this.symbolTable = symbolTable; this.accessFlags = "".equals(name) ? access | Constants.ACC_CONSTRUCTOR : access; this.nameIndex = symbolTable.addConstantUtf8(name); @@ -904,26 +905,26 @@ public void visitIntInsn(final int opcode, final int operand) { } @Override - public void visitVarInsn(final int opcode, final int var) { + public void visitVarInsn(final int opcode, final int varIndex) { lastBytecodeOffset = code.length; // Add the instruction to the bytecode of the method. - if (var < 4 && opcode != Opcodes.RET) { + if (varIndex < 4 && opcode != Opcodes.RET) { int optimizedOpcode; if (opcode < Opcodes.ISTORE) { - optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + var; + optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + varIndex; } else { - optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + var; + optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + varIndex; } code.putByte(optimizedOpcode); - } else if (var >= 256) { - code.putByte(Constants.WIDE).put12(opcode, var); + } else if (varIndex >= 256) { + code.putByte(Constants.WIDE).put12(opcode, varIndex); } else { - code.put11(opcode, var); + code.put11(opcode, varIndex); } // If needed, update the maximum stack size and number of locals, and stack map frames. if (currentBasicBlock != null) { if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { - currentBasicBlock.frame.execute(opcode, var, null, null); + currentBasicBlock.frame.execute(opcode, varIndex, null, null); } else { if (opcode == Opcodes.RET) { // No stack size delta. @@ -945,9 +946,9 @@ public void visitVarInsn(final int opcode, final int var) { || opcode == Opcodes.DLOAD || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE) { - currentMaxLocals = var + 2; + currentMaxLocals = varIndex + 2; } else { - currentMaxLocals = var + 1; + currentMaxLocals = varIndex + 1; } if (currentMaxLocals > maxLocals) { maxLocals = currentMaxLocals; @@ -1307,21 +1308,21 @@ public void visitLdcInsn(final Object value) { } @Override - public void visitIincInsn(final int var, final int increment) { + public void visitIincInsn(final int varIndex, final int increment) { lastBytecodeOffset = code.length; // Add the instruction to the bytecode of the method. - if ((var > 255) || (increment > 127) || (increment < -128)) { - code.putByte(Constants.WIDE).put12(Opcodes.IINC, var).putShort(increment); + if ((varIndex > 255) || (increment > 127) || (increment < -128)) { + code.putByte(Constants.WIDE).put12(Opcodes.IINC, varIndex).putShort(increment); } else { - code.putByte(Opcodes.IINC).put11(var, increment); + code.putByte(Opcodes.IINC).put11(varIndex, increment); } // If needed, update the maximum stack size and number of locals, and stack map frames. if (currentBasicBlock != null && (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES)) { - currentBasicBlock.frame.execute(Opcodes.IINC, var, null, null); + currentBasicBlock.frame.execute(Opcodes.IINC, varIndex, null, null); } if (compute != COMPUTE_NOTHING) { - int currentMaxLocals = var + 1; + int currentMaxLocals = varIndex + 1; if (currentMaxLocals > maxLocals) { maxLocals = currentMaxLocals; } diff --git a/src/java/nginx/clojure/asm/ModuleVisitor.java b/src/java/nginx/clojure/asm/ModuleVisitor.java index 35c2c88a..358b9c95 100644 --- a/src/java/nginx/clojure/asm/ModuleVisitor.java +++ b/src/java/nginx/clojure/asm/ModuleVisitor.java @@ -53,7 +53,7 @@ public abstract class ModuleVisitor { * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6} * or {@link Opcodes#ASM7}. */ - public ModuleVisitor(final int api) { + protected ModuleVisitor(final int api) { this(api, null); } @@ -65,25 +65,38 @@ public ModuleVisitor(final int api) { * @param moduleVisitor the module visitor to which this visitor must delegate method calls. May * be null. */ - public ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) { - if (api != Opcodes.ASM7 + protected ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 - && api != Opcodes.ASM8_EXPERIMENTAL) { + && api != Opcodes.ASM10_EXPERIMENTAL) { throw new IllegalArgumentException("Unsupported api " + api); } - if (api == Opcodes.ASM8_EXPERIMENTAL) { - Constants.checkAsm8Experimental(this); + if (api == Opcodes.ASM10_EXPERIMENTAL) { + Constants.checkAsmExperimental(this); } this.api = api; this.mv = moduleVisitor; } + /** + * The module visitor to which this visitor must delegate method calls. May be {@literal null}. + * + * @return the module visitor to which this visitor must delegate method calls, or {@literal + * null}. + */ + public ModuleVisitor getDelegate() { + return mv; + } + /** * Visit the main class of the current module. * - * @param mainClass the internal name of the main class of the current module. + * @param mainClass the internal name of the main class of the current module (see {@link + * Type#getInternalName()}). */ public void visitMainClass(final String mainClass) { if (mv != null) { @@ -94,7 +107,7 @@ public void visitMainClass(final String mainClass) { /** * Visit a package of the current module. * - * @param packaze the internal name of a package. + * @param packaze the internal name of a package (see {@link Type#getInternalName()}). */ public void visitPackage(final String packaze) { if (mv != null) { @@ -119,7 +132,7 @@ public void visitRequire(final String module, final int access, final String ver /** * Visit an exported package of the current module. * - * @param packaze the internal name of the exported package. + * @param packaze the internal name of the exported package (see {@link Type#getInternalName()}). * @param access the access flag of the exported package, valid values are among {@code * ACC_SYNTHETIC} and {@code ACC_MANDATED}. * @param modules the fully qualified names (using dots) of the modules that can access the public @@ -134,7 +147,7 @@ public void visitExport(final String packaze, final int access, final String... /** * Visit an open package of the current module. * - * @param packaze the internal name of the opened package. + * @param packaze the internal name of the opened package (see {@link Type#getInternalName()}). * @param access the access flag of the opened package, valid values are among {@code * ACC_SYNTHETIC} and {@code ACC_MANDATED}. * @param modules the fully qualified names (using dots) of the modules that can use deep @@ -150,7 +163,7 @@ public void visitOpen(final String packaze, final int access, final String... mo * Visit a service used by the current module. The name must be the internal name of an interface * or a class. * - * @param service the internal name of the service. + * @param service the internal name of the service (see {@link Type#getInternalName()}). */ public void visitUse(final String service) { if (mv != null) { @@ -161,9 +174,9 @@ public void visitUse(final String service) { /** * Visit an implementation of a service. * - * @param service the internal name of the service. - * @param providers the internal names of the implementations of the service (there is at least - * one provider). + * @param service the internal name of the service (see {@link Type#getInternalName()}). + * @param providers the internal names (see {@link Type#getInternalName()}) of the implementations + * of the service (there is at least one provider). */ public void visitProvide(final String service, final String... providers) { if (mv != null) { diff --git a/src/java/nginx/clojure/asm/ModuleWriter.java b/src/java/nginx/clojure/asm/ModuleWriter.java index c1144dcb..954d83d4 100644 --- a/src/java/nginx/clojure/asm/ModuleWriter.java +++ b/src/java/nginx/clojure/asm/ModuleWriter.java @@ -94,7 +94,7 @@ final class ModuleWriter extends ModuleVisitor { private int mainClassIndex; ModuleWriter(final SymbolTable symbolTable, final int name, final int access, final int version) { - super(/* latest api = */ Opcodes.ASM7); + super(/* latest api = */ Opcodes.ASM9); this.symbolTable = symbolTable; this.moduleNameIndex = name; this.moduleFlags = access; diff --git a/src/java/nginx/clojure/asm/Opcodes.java b/src/java/nginx/clojure/asm/Opcodes.java index f0c5fe58..d2fa3ceb 100644 --- a/src/java/nginx/clojure/asm/Opcodes.java +++ b/src/java/nginx/clojure/asm/Opcodes.java @@ -47,6 +47,8 @@ public interface Opcodes { int ASM5 = 5 << 16 | 0 << 8; int ASM6 = 6 << 16 | 0 << 8; int ASM7 = 7 << 16 | 0 << 8; + int ASM8 = 8 << 16 | 0 << 8; + int ASM9 = 9 << 16 | 0 << 8; /** * Experimental, use at your own risk. This field will be renamed when it becomes stable, this @@ -54,7 +56,7 @@ public interface Opcodes { * * @deprecated This API is experimental. */ - @Deprecated int ASM8_EXPERIMENTAL = 1 << 24 | 8 << 16 | 0 << 8; + @Deprecated int ASM10_EXPERIMENTAL = 1 << 24 | 10 << 16 | 0 << 8; /* * Internal flags used to redirect calls to deprecated methods. For instance, if a visitOldStuff @@ -131,7 +133,7 @@ public interface Opcodes { *
    * public class StuffVisitor {
    *   @Deprecated public void visitOldStuff(int arg, ...) {
-   *     visitNewStuf(arg | SOURCE_DEPRECATED, ...);
+   *     visitNewStuff(arg | SOURCE_DEPRECATED, ...);
    *   }
    *   public void visitNewStuff(int argAndSource...) {
    *     if ((argAndSource & SOURCE_DEPRECATED) == 0) {
@@ -153,7 +155,7 @@ public interface Opcodes {
    * 

and there are two cases: * *