From fac0ef29d564dbd8f59c6c8aaa89f0d656f1f786 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Mon, 24 Dec 2018 20:31:45 +0300 Subject: Version bump. --- src/nxt_main.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nxt_main.h b/src/nxt_main.h index 71ee6599..dc821e07 100644 --- a/src/nxt_main.h +++ b/src/nxt_main.h @@ -11,8 +11,8 @@ #include -#define NXT_VERSION "1.7" -#define NXT_VERNUM 10700 +#define NXT_VERSION "1.8" +#define NXT_VERNUM 10800 #define NXT_SERVER "Unit/" NXT_VERSION -- cgit From f9a3328a2510e7237b8bd8f35a57914f6a22bbd2 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Mon, 24 Dec 2018 20:35:18 +0300 Subject: Tests: more QUERY_STRING tests. --- test/php/query_string/index.php | 4 ++++ test/test_perl_application.py | 19 +++++++++++++++++++ test/test_php_application.py | 27 +++++++++++++++++++++++++++ test/test_python_application.py | 19 +++++++++++++++++++ test/test_ruby_application.py | 19 +++++++++++++++++++ 5 files changed, 88 insertions(+) create mode 100644 test/php/query_string/index.php diff --git a/test/php/query_string/index.php b/test/php/query_string/index.php new file mode 100644 index 00000000..5691324d --- /dev/null +++ b/test/php/query_string/index.php @@ -0,0 +1,4 @@ + diff --git a/test/test_perl_application.py b/test/test_perl_application.py index c9cb3f0c..2dfae66c 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -55,6 +55,25 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): self.assertEqual(resp['headers']['Query-String'], 'var1=val1&var2=val2', 'Query-String header') + def test_perl_application_query_string_empty(self): + self.load('query_string') + + resp = self.get(url='/?') + + self.assertEqual(resp['status'], 200, 'query string empty status') + self.assertEqual(resp['headers']['Query-String'], '', + 'query string empty') + + @unittest.expectedFailure + def test_perl_application_query_string_absent(self): + self.load('query_string') + + resp = self.get() + + self.assertEqual(resp['status'], 200, 'query string absent status') + self.assertEqual(resp['headers']['Query-String'], '', + 'query string absent') + @unittest.expectedFailure def test_perl_application_server_port(self): self.load('server_port') diff --git a/test/test_php_application.py b/test/test_php_application.py index e0058d9a..ba9c03b3 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -48,6 +48,33 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP): }, 'headers') self.assertEqual(resp['body'], body, 'body') + def test_php_application_query_string(self): + self.load('query_string') + + resp = self.get(url='/?var1=val1&var2=val2') + + self.assertEqual(resp['headers']['Query-String'], 'var1=val1&var2=val2', + 'query string') + + def test_php_application_query_string_empty(self): + self.load('query_string') + + resp = self.get(url='/?') + + self.assertEqual(resp['status'], 200, 'query string empty status') + self.assertEqual(resp['headers']['Query-String'], '', + 'query string empty') + + @unittest.expectedFailure + def test_php_application_query_string_absent(self): + self.load('query_string') + + resp = self.get() + + self.assertEqual(resp['status'], 200, 'query string absent status') + self.assertEqual(resp['headers']['Query-String'], '', + 'query string absent') + def test_php_application_phpinfo(self): self.load('phpinfo') diff --git a/test/test_python_application.py b/test/test_python_application.py index 667047bc..9c2c762d 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -53,6 +53,25 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): self.assertEqual(resp['headers']['Query-String'], 'var1=val1&var2=val2', 'Query-String header') + def test_python_application_query_string_empty(self): + self.load('query_string') + + resp = self.get(url='/?') + + self.assertEqual(resp['status'], 200, 'query string empty status') + self.assertEqual(resp['headers']['Query-String'], '', + 'query string empty') + + @unittest.expectedFailure + def test_python_application_query_string_absent(self): + self.load('query_string') + + resp = self.get() + + self.assertEqual(resp['status'], 200, 'query string absent status') + self.assertEqual(resp['headers']['Query-String'], '', + 'query string absent') + @unittest.expectedFailure def test_python_application_server_port(self): self.load('server_port') diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py index 57ab88cd..b88e043d 100644 --- a/test/test_ruby_application.py +++ b/test/test_ruby_application.py @@ -56,6 +56,25 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): self.assertEqual(resp['headers']['Query-String'], 'var1=val1&var2=val2', 'Query-String header') + def test_ruby_application_query_string_empty(self): + self.load('query_string') + + resp = self.get(url='/?') + + self.assertEqual(resp['status'], 200, 'query string empty status') + self.assertEqual(resp['headers']['Query-String'], '', + 'query string empty') + + @unittest.expectedFailure + def test_ruby_application_query_string_absent(self): + self.load('query_string') + + resp = self.get() + + self.assertEqual(resp['status'], 200, 'query string absent status') + self.assertEqual(resp['headers']['Query-String'], '', + 'query string absent') + @unittest.expectedFailure def test_ruby_application_server_port(self): self.load('server_port') -- cgit From f57e729a119660182d333f513b70b78177eb2b62 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Tue, 25 Dec 2018 16:35:35 +0300 Subject: Tests: expectedFailure's removed from test_node_application.py. Also removed alert skip in test_node_application_write_before_writeHead. Fixes committed in 1340e3539362, 1e008ef94f43, 5df32621af19, dae402cb243f and 41f561b3a178. --- test/test_node_application.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/test_node_application.py b/test/test_node_application.py index 5dedb5a3..a9f5b8a3 100644 --- a/test/test_node_application.py +++ b/test/test_node_application.py @@ -112,7 +112,6 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): self.assertEqual(self.get()['body'], '6\r\nbuffer\r\n0\r\n\r\n', 'write buffer') - @unittest.expectedFailure def test_node_application_write_callback(self): self.load('write_callback') @@ -122,7 +121,6 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): 'write callback') def test_node_application_write_before_writeHead(self): - self.skip_alerts.append(r'process \d+ exited on signal') self.load('write_before_write_head') self.get() @@ -182,7 +180,6 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): self.assertEqual(self.get()['headers']['X-Type'], 'number', 'get header type') - @unittest.expectedFailure def test_node_application_header_name_case(self): self.load('header_name_case') @@ -202,9 +199,7 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): self.assertTrue(self.waitforfiles(self.testdir + '/node/callback'), 'promise handler') - @unittest.expectedFailure def test_node_application_promise_handler_write_after_end(self): - self.skip_alerts.append(r'process \d+ exited on signal') self.load('promise_handler') self.assertEqual(self.post(headers={ @@ -249,13 +244,11 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): self.assertNotIn('status', self.get(), 'header name valid') - @unittest.expectedFailure def test_node_application_header_value_object(self): self.load('header_value_object') self.assertIn('X-Header', self.get()['headers'], 'header value object') - @unittest.expectedFailure def test_node_application_get_header_names(self): self.load('get_header_names') -- cgit From d625691ff45bd6f352a504694629af2407a7d1a1 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Tue, 25 Dec 2018 16:35:35 +0300 Subject: Tests: test_node_application_write_before_writeHead reworked. Added res.end() call in application and response status check. Also, renamed to test_node_application_write_before_write_head. --- test/node/write_before_write_head/app.js | 1 + test/test_node_application.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/node/write_before_write_head/app.js b/test/node/write_before_write_head/app.js index 9fe3a58d..6e3fb9a9 100755 --- a/test/node/write_before_write_head/app.js +++ b/test/node/write_before_write_head/app.js @@ -3,4 +3,5 @@ require('unit-http').createServer(function (req, res) { res.write('blah'); res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end(); }).listen(7080); diff --git a/test/test_node_application.py b/test/test_node_application.py index a9f5b8a3..05b518f5 100644 --- a/test/test_node_application.py +++ b/test/test_node_application.py @@ -120,10 +120,10 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): self.assertTrue(self.waitforfiles(self.testdir + '/node/callback'), 'write callback') - def test_node_application_write_before_writeHead(self): + def test_node_application_write_before_write_head(self): self.load('write_before_write_head') - self.get() + self.assertEqual(self.get()['status'], 200, 'write before writeHead') def test_node_application_double_end(self): self.load('double_end') -- cgit From f5b7fee128dfabc3225e05a69a5c9458b63c315b Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Tue, 25 Dec 2018 16:35:35 +0300 Subject: Tests: set TCP_NODELAY option by default. --- test/unit.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/unit.py b/test/unit.py index e88ed684..2b95b792 100644 --- a/test/unit.py +++ b/test/unit.py @@ -309,6 +309,9 @@ class TestUnitHTTP(TestUnit): if 'sock' not in kwargs: sock = socket.socket(sock_types[sock_type], socket.SOCK_STREAM) + if sock_type == sock_types['ipv4'] or sock_type == sock_types['ipv6']: + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + if 'wrapper' in kwargs: sock = kwargs['wrapper'](sock) -- cgit From 4ccf0c8a17d4f481cbb9b4047da30cbec5497c46 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Tue, 25 Dec 2018 16:35:35 +0300 Subject: Tests: fixed test_tls_reconfigure. Previously, order of applying TLS configuration and sending partial request to the application was uncertain. These changes make sure that client-application connection was established before reconfiguration. Additionally, added test to check that non-TLS connection works correctly after reconfiguration. --- test/test_tls.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/test/test_tls.py b/test/test_tls.py index fa5c9754..dc2f4505 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -306,23 +306,25 @@ basicConstraints = critical,CA:TRUE""" % { self.assertEqual(self.get_ssl()['status'], 200, 'certificate chain intermediate server') + @unittest.expectedFailure def test_tls_reconfigure(self): self.load('empty') self.certificate() - (resp, sock) = self.http(b"""GET / HTTP/1.1 -""", start=True, raw=True, no_recv=True) - - self.add_tls() + (resp, sock) = self.get(headers={ + 'Connection': 'keep-alive', + 'Host': 'localhost' + }, start=True) - resp = self.http(b"""Host: localhost -Connection: close + self.assertEqual(resp['status'], 200, 'initial status') -""", sock=sock, raw=True) + self.add_tls() - self.assertEqual(resp['status'], 200, 'update status') - self.assertEqual(self.get_ssl()['status'], 200, 'update tls status') + self.assertEqual(self.get(sock=sock)['status'], 200, + 'reconfigure status') + self.assertEqual(self.get_ssl()['status'], 200, + 'reconfigure tls status') def test_tls_keepalive(self): self.load('mirror') -- cgit From 59644130668c19522488932fbe894587e5616a94 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Tue, 25 Dec 2018 17:22:32 +0300 Subject: Tests: added delay to wait for error logging. --- test/test_python_application.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_python_application.py b/test/test_python_application.py index 9c2c762d..b28675f9 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -1,3 +1,4 @@ +import time import unittest import unit @@ -105,6 +106,8 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): self.stop() + time.sleep(0.2) + self.assertIsNotNone(self.search_in_log(r'RuntimeError'), 'ctx iter atexit') -- cgit From 17068d2cdc1f0287f4d072e0d3a5694c8dd38e22 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Tue, 25 Dec 2018 21:28:02 +0300 Subject: Tests: removed skipping Go tests on x86. --- test/test_go_application.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/test_go_application.py b/test/test_go_application.py index fd80bf5b..90785851 100644 --- a/test/test_go_application.py +++ b/test/test_go_application.py @@ -4,12 +4,7 @@ import unit class TestUnitGoApplication(unit.TestUnitApplicationGo): def setUpClass(): - u = unit.TestUnit() - - if u.architecture == '32bit': - raise unittest.SkipTest('Skip Go tests for x86') - - u.check_modules('go') + unit.TestUnit().check_modules('go') def test_go_application_variables(self): self.load('variables') -- cgit From 325421eac71e83a95edf178fc6ea5ee36c8da1bb Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Thu, 27 Dec 2018 11:06:43 +0300 Subject: Packages: URL fixed in examples to work with latest curl. --- pkg/deb/Makefile.go | 2 +- pkg/deb/Makefile.go110 | 2 +- pkg/deb/Makefile.go17 | 2 +- pkg/deb/Makefile.go18 | 2 +- pkg/deb/Makefile.go19 | 2 +- pkg/deb/Makefile.perl | 2 +- pkg/deb/Makefile.php | 2 +- pkg/deb/Makefile.python | 2 +- pkg/deb/Makefile.python27 | 2 +- pkg/deb/Makefile.python34 | 2 +- pkg/deb/Makefile.python35 | 2 +- pkg/deb/Makefile.python36 | 2 +- pkg/deb/Makefile.ruby | 2 +- pkg/rpm/Makefile.go | 2 +- pkg/rpm/Makefile.perl | 2 +- pkg/rpm/Makefile.php | 2 +- pkg/rpm/Makefile.python | 2 +- pkg/rpm/Makefile.python27 | 2 +- pkg/rpm/Makefile.python34 | 2 +- pkg/rpm/Makefile.python35 | 2 +- pkg/rpm/Makefile.python36 | 2 +- pkg/rpm/Makefile.ruby | 2 +- 22 files changed, 22 insertions(+), 22 deletions(-) diff --git a/pkg/deb/Makefile.go b/pkg/deb/Makefile.go index bbade7a3..3399f5ca 100644 --- a/pkg/deb/Makefile.go +++ b/pkg/deb/Makefile.go @@ -37,7 +37,7 @@ To check out the sample app, run these commands: GOPATH=/usr/share/gocode go build -o /tmp/go-app /usr/share/doc/unit-$(MODULE_SUFFIX_go)/examples/go-app/let-my-people.go sudo service unit restart cd /usr/share/doc/unit-$(MODULE_SUFFIX_go)/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8500/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/deb/Makefile.go110 b/pkg/deb/Makefile.go110 index 9fb3da9d..0e956d9f 100644 --- a/pkg/deb/Makefile.go110 +++ b/pkg/deb/Makefile.go110 @@ -37,7 +37,7 @@ To check out the sample app, run these commands: GOPATH=/usr/share/gocode /usr/lib/go-1.10/bin/go build -o /tmp/go1.10-app /usr/share/doc/unit-$(MODULE_SUFFIX_go110)/examples/go-app/let-my-people.go sudo service unit restart cd /usr/share/doc/unit-$(MODULE_SUFFIX_go110)/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8500/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/deb/Makefile.go17 b/pkg/deb/Makefile.go17 index 4c3cc73f..d014edd1 100644 --- a/pkg/deb/Makefile.go17 +++ b/pkg/deb/Makefile.go17 @@ -37,7 +37,7 @@ To check out the sample app, run these commands: GOPATH=/usr/share/gocode /usr/lib/go-1.7/bin/go build -o /tmp/go1.7-app /usr/share/doc/unit-$(MODULE_SUFFIX_go17)/examples/go-app/let-my-people.go sudo service unit restart cd /usr/share/doc/unit-$(MODULE_SUFFIX_go17)/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8500/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/deb/Makefile.go18 b/pkg/deb/Makefile.go18 index a9db87e0..597f4804 100644 --- a/pkg/deb/Makefile.go18 +++ b/pkg/deb/Makefile.go18 @@ -37,7 +37,7 @@ To check out the sample app, run these commands: GOPATH=/usr/share/gocode /usr/lib/go-1.8/bin/go build -o /tmp/go1.8-app /usr/share/doc/unit-$(MODULE_SUFFIX_go18)/examples/go-app/let-my-people.go sudo service unit restart cd /usr/share/doc/unit-$(MODULE_SUFFIX_go18)/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8500/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/deb/Makefile.go19 b/pkg/deb/Makefile.go19 index 43611560..60d4b239 100644 --- a/pkg/deb/Makefile.go19 +++ b/pkg/deb/Makefile.go19 @@ -37,7 +37,7 @@ To check out the sample app, run these commands: GOPATH=/usr/share/gocode /usr/lib/go-1.9/bin/go build -o /tmp/go1.9-app /usr/share/doc/unit-$(MODULE_SUFFIX_go19)/examples/go-app/let-my-people.go sudo service unit restart cd /usr/share/doc/unit-$(MODULE_SUFFIX_go19)/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8500/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/deb/Makefile.perl b/pkg/deb/Makefile.perl index 186b8e5a..7ac3ea11 100644 --- a/pkg/deb/Makefile.perl +++ b/pkg/deb/Makefile.perl @@ -35,7 +35,7 @@ To check out the sample app, run these commands: sudo service unit restart cd /usr/share/doc/unit-$(MODULE_SUFFIX_perl)/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8600/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/deb/Makefile.php b/pkg/deb/Makefile.php index 193670e7..d05d5ad5 100644 --- a/pkg/deb/Makefile.php +++ b/pkg/deb/Makefile.php @@ -42,7 +42,7 @@ To check out the sample app, run these commands: sudo service unit restart cd /usr/share/doc/unit-$(MODULE_SUFFIX_php)/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8300/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/deb/Makefile.python b/pkg/deb/Makefile.python index dec64b26..9bf02bac 100644 --- a/pkg/deb/Makefile.python +++ b/pkg/deb/Makefile.python @@ -35,7 +35,7 @@ To check out the sample app, run these commands: sudo service unit restart cd /usr/share/doc/unit-$(MODULE_SUFFIX_python)/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/deb/Makefile.python27 b/pkg/deb/Makefile.python27 index ae1ae4c0..cf4f49d9 100644 --- a/pkg/deb/Makefile.python27 +++ b/pkg/deb/Makefile.python27 @@ -35,7 +35,7 @@ To check out the sample app, run these commands: sudo service unit restart cd /usr/share/doc/unit-$(MODULE_SUFFIX_python27)/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/deb/Makefile.python34 b/pkg/deb/Makefile.python34 index 5e043ef4..03c496bd 100644 --- a/pkg/deb/Makefile.python34 +++ b/pkg/deb/Makefile.python34 @@ -35,7 +35,7 @@ To check out the sample app, run these commands: sudo service unit restart cd /usr/share/doc/unit-$(MODULE_SUFFIX_python34)/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/deb/Makefile.python35 b/pkg/deb/Makefile.python35 index 557690a9..514a4bf6 100644 --- a/pkg/deb/Makefile.python35 +++ b/pkg/deb/Makefile.python35 @@ -35,7 +35,7 @@ To check out the sample app, run these commands: sudo service unit restart cd /usr/share/doc/unit-$(MODULE_SUFFIX_python35)/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/deb/Makefile.python36 b/pkg/deb/Makefile.python36 index 07cbba90..4fd898c6 100644 --- a/pkg/deb/Makefile.python36 +++ b/pkg/deb/Makefile.python36 @@ -35,7 +35,7 @@ To check out the sample app, run these commands: sudo service unit restart cd /usr/share/doc/unit-$(MODULE_SUFFIX_python36)/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/deb/Makefile.ruby b/pkg/deb/Makefile.ruby index 1cb6baab..dd85cd38 100644 --- a/pkg/deb/Makefile.ruby +++ b/pkg/deb/Makefile.ruby @@ -37,7 +37,7 @@ To check out the sample app, run these commands: sudo service unit restart cd /usr/share/doc/unit-$(MODULE_SUFFIX_ruby)/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8700/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.go b/pkg/rpm/Makefile.go index d13e8d1a..f031c7ba 100644 --- a/pkg/rpm/Makefile.go +++ b/pkg/rpm/Makefile.go @@ -64,7 +64,7 @@ To check the sample app, run these commands: GOPATH=%{gopath} go build -o /tmp/go-app /usr/share/doc/unit-go/examples/go-app/let-my-people.go sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8500/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.perl b/pkg/rpm/Makefile.perl index 90259e9e..84c15260 100644 --- a/pkg/rpm/Makefile.perl +++ b/pkg/rpm/Makefile.perl @@ -51,7 +51,7 @@ To check out the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8600/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.php b/pkg/rpm/Makefile.php index b4f093b7..eedcce4e 100644 --- a/pkg/rpm/Makefile.php +++ b/pkg/rpm/Makefile.php @@ -46,7 +46,7 @@ To check out the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8300/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.python b/pkg/rpm/Makefile.python index 0b0eb7bd..bb408bb5 100644 --- a/pkg/rpm/Makefile.python +++ b/pkg/rpm/Makefile.python @@ -46,7 +46,7 @@ To check the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.python27 b/pkg/rpm/Makefile.python27 index 7d15bdd9..c6a2ec65 100644 --- a/pkg/rpm/Makefile.python27 +++ b/pkg/rpm/Makefile.python27 @@ -48,7 +48,7 @@ To check the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.python34 b/pkg/rpm/Makefile.python34 index 152007a2..1dc10f9c 100644 --- a/pkg/rpm/Makefile.python34 +++ b/pkg/rpm/Makefile.python34 @@ -46,7 +46,7 @@ To check the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.python35 b/pkg/rpm/Makefile.python35 index 3f075702..3e4e0a2e 100644 --- a/pkg/rpm/Makefile.python35 +++ b/pkg/rpm/Makefile.python35 @@ -41,7 +41,7 @@ To check the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.python36 b/pkg/rpm/Makefile.python36 index fd55840e..d674d8fe 100644 --- a/pkg/rpm/Makefile.python36 +++ b/pkg/rpm/Makefile.python36 @@ -46,7 +46,7 @@ To check the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.ruby b/pkg/rpm/Makefile.ruby index 5702fb44..92416338 100644 --- a/pkg/rpm/Makefile.ruby +++ b/pkg/rpm/Makefile.ruby @@ -55,7 +55,7 @@ To check the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock :/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config curl http://localhost:8700/ Online documentation is available at https://unit.nginx.org -- cgit From be049932b100c7a64c0dd261d21b73ea817f8100 Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Thu, 27 Dec 2018 11:07:24 +0300 Subject: Packages: Ubuntu 18.10 "cosmic" support added. --- docs/Makefile | 2 +- docs/changes.xml | 13 ++++++ pkg/deb/Makefile | 12 ++++++ pkg/deb/Makefile.python37 | 46 ++++++++++++++++++++++ .../debian.module/unit.example-python3.7-config | 17 ++++++++ 5 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 pkg/deb/Makefile.python37 create mode 100644 pkg/deb/debian.module/unit.example-python3.7-config diff --git a/docs/Makefile b/docs/Makefile index 52ed1c65..1f0ba451 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -8,7 +8,7 @@ XSLS?= xslscript.pl PACKAGES= unit \ unit-php \ unit-python unit-python2.7 unit-python3.4 \ - unit-python3.5 unit-python3.6 \ + unit-python3.5 unit-python3.6 unit-python3.7 \ unit-go unit-go1.7 unit-go1.8 unit-go1.9 unit-go1.10 \ unit-perl \ unit-ruby diff --git a/docs/changes.xml b/docs/changes.xml index 7443795b..6a1f8a9b 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -5,6 +5,19 @@ + + + + +Initial release of Python 3.7 module for NGINX Unit. + + + + + + Date: Wed, 9 Jan 2019 18:03:48 +0300 Subject: Year 2019. --- NOTICE | 9 +++++---- pkg/deb/debian/copyright | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/NOTICE b/NOTICE index 613f1612..35f52459 100644 --- a/NOTICE +++ b/NOTICE @@ -1,10 +1,11 @@ NGINX Unit. - Copyright 2017-2018 NGINX, Inc. - Copyright 2017-2018 Igor Sysoev - Copyright 2017-2018 Valentin V. Bartenev - Copyright 2017-2018 Max Romanov + Copyright 2017-2019 NGINX, Inc. + Copyright 2017-2019 Igor Sysoev + Copyright 2017-2019 Valentin V. Bartenev + Copyright 2017-2019 Max Romanov + Copyright 2018-2019 Alexander Borisov Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/deb/debian/copyright b/pkg/deb/debian/copyright index b795c3a0..fe52d6aa 100644 --- a/pkg/deb/debian/copyright +++ b/pkg/deb/debian/copyright @@ -1,10 +1,11 @@ NGINX Unit. - Copyright 2017-2018 NGINX, Inc. - Copyright 2017-2018 Igor Sysoev - Copyright 2017-2018 Valentin V. Bartenev - Copyright 2017-2018 Max Romanov + Copyright 2017-2019 NGINX, Inc. + Copyright 2017-2019 Igor Sysoev + Copyright 2017-2019 Valentin V. Bartenev + Copyright 2017-2019 Max Romanov + Copyright 2018-2019 Alexander Borisov Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. -- cgit From 23a6a8e45177a88f36e6b860a2db4848a9e3eada Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Fri, 18 Jan 2019 16:18:36 +0300 Subject: Testing correct value. --- src/nxt_http_error.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nxt_http_error.c b/src/nxt_http_error.c index 65f8ba38..99d27903 100644 --- a/src/nxt_http_error.c +++ b/src/nxt_http_error.c @@ -37,7 +37,7 @@ nxt_http_request_error(nxt_task_t *task, nxt_http_request_t *r, r->status = status; r->resp.fields = nxt_list_create(r->mem_pool, 8, sizeof(nxt_http_field_t)); - if (nxt_slow_path(r == NULL)) { + if (nxt_slow_path(r->resp.fields == NULL)) { goto fail; } -- cgit From 2677bd5604345d3fe40e821699d1a26a7d1c31a7 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Mon, 21 Jan 2019 18:13:00 +0300 Subject: Go: fixed module installation, broken in ed8b1aaefdd1. Added the nxt_unit_version.h dependency. This closes #214 issue on GitHub. --- auto/modules/go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auto/modules/go b/auto/modules/go index 7abd1e73..e9b2321d 100644 --- a/auto/modules/go +++ b/auto/modules/go @@ -103,7 +103,7 @@ ${NXT_GO}: ${NXT_GO}-install: ${NXT_GO}-install-build -${NXT_GO}-install-src: +${NXT_GO}-install-src: ${NXT_BUILD_DIR}/nxt_unit_version.h install -d \$(DESTDIR)\$(NXT_GO_DST)/src/nginx/unit install -p -m644 ./src/*.h ./build/*.h ./src/go/unit/* \ ./src/nxt_unit.c ./src/nxt_lvlhsh.c ./src/nxt_murmur_hash.c \ -- cgit From fd6a6a5514c420dae2b393f62fee65f87758dec9 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Mon, 21 Jan 2019 18:39:19 +0300 Subject: Fixed processing Unix listening socket failures. This is related to issue #198 on GitHub. --- src/nxt_main_process.c | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index 819ed44c..a4c3f119 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -1116,24 +1116,23 @@ nxt_main_listening_socket(nxt_sockaddr_t *sa, nxt_listening_socket_t *ls) break; } - goto next; - } - + } else #endif + { + switch (err) { - switch (err) { - - case EACCES: - ls->error = NXT_SOCKET_ERROR_PORT; - break; + case EACCES: + ls->error = NXT_SOCKET_ERROR_PORT; + break; - case EADDRINUSE: - ls->error = NXT_SOCKET_ERROR_INUSE; - break; + case EADDRINUSE: + ls->error = NXT_SOCKET_ERROR_INUSE; + break; - case EADDRNOTAVAIL: - ls->error = NXT_SOCKET_ERROR_NOADDR; - break; + case EADDRNOTAVAIL: + ls->error = NXT_SOCKET_ERROR_NOADDR; + break; + } } ls->end = nxt_sprintf(ls->start, ls->end, "bind(\\\"%*s\\\") failed %E", @@ -1143,8 +1142,6 @@ nxt_main_listening_socket(nxt_sockaddr_t *sa, nxt_listening_socket_t *ls) #if (NXT_HAVE_UNIX_DOMAIN) -next: - if (sa->u.sockaddr.sa_family == AF_UNIX) { char *filename; mode_t access; -- cgit From 2bc8fb7b43a5106ab8cdf8adae4834837ae7fc16 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 23 Jan 2019 17:47:53 +0300 Subject: Node.js: fixed module version on installation from sources. --- auto/modules/nodejs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/auto/modules/nodejs b/auto/modules/nodejs index e0208f5d..57bf9d3a 100644 --- a/auto/modules/nodejs +++ b/auto/modules/nodejs @@ -145,6 +145,8 @@ cat << END >> $NXT_MAKEFILE .PHONY: ${NXT_NODE}-build .PHONY: ${NXT_NODE}-publish +NXT_UNIT_VERSION=\$(shell grep 'define NXT_VERSION' \ + src/nxt_main.h | sed -e 's/^.*"\(.*\)".*/\1/') ${NXT_NODE}: ${NXT_NODE}-copy $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC ${NXT_NODE_EXPORTS} && \\ @@ -161,6 +163,9 @@ ${NXT_NODE_VERSION_FILE}: src/nxt_main.h | sed -e 's/[^0-9]//g' >> $NXT_NODE_VERSION_FILE ${NXT_NODE_TARBALL}: ${NXT_NODE}-copy + sed -e 's/"version"\s*:.*/"version": "\$(NXT_UNIT_VERSION).0",/' \ + ${NXT_NODE_TMP}/package.json > ${NXT_NODE_TMP}/package.json.tmp + mv ${NXT_NODE_TMP}/package.json.tmp ${NXT_NODE_TMP}/package.json tar -zcvf ${NXT_NODE_TARBALL} -C ${NXT_NODE_TMP} . -- cgit From b1649bce9ed1521a48e18b22fb58aaf09f9637b0 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Thu, 24 Jan 2019 13:03:25 +0300 Subject: Packages: Fedora >= 29 have python 3.7. Closes #211. --- pkg/rpm/Makefile | 4 ++ pkg/rpm/Makefile.python37 | 57 ++++++++++++++++++++++ .../rpmbuild/SOURCES/unit.example-python37-config | 17 +++++++ 3 files changed, 78 insertions(+) create mode 100644 pkg/rpm/Makefile.python37 create mode 100644 pkg/rpm/rpmbuild/SOURCES/unit.example-python37-config diff --git a/pkg/rpm/Makefile b/pkg/rpm/Makefile index ef7f3361..86b3e3f1 100644 --- a/pkg/rpm/Makefile +++ b/pkg/rpm/Makefile @@ -89,7 +89,11 @@ endif ifeq ($(OSVER), fedora) include Makefile.php include Makefile.python27 +ifeq ($(shell test `rpm --eval '0%{?fedora} -ge 29'`; echo $$?),0) +include Makefile.python37 +else include Makefile.python36 +endif include Makefile.go include Makefile.perl include Makefile.ruby diff --git a/pkg/rpm/Makefile.python37 b/pkg/rpm/Makefile.python37 new file mode 100644 index 00000000..48f0b5c1 --- /dev/null +++ b/pkg/rpm/Makefile.python37 @@ -0,0 +1,57 @@ +MODULES+= python37 +MODULE_SUFFIX_python37= python3.7 + +MODULE_SUMMARY_python37= Python 3.7 module for NGINX Unit + +MODULE_VERSION_python37= $(VERSION) +MODULE_RELEASE_python37= 1 + +MODULE_CONFARGS_python37= python --config=python3.7-config +MODULE_MAKEARGS_python37= python3.7 +MODULE_INSTARGS_python37= python3.7-install + +MODULE_SOURCES_python37= unit.example-python-app \ + unit.example-python37-config + +ifneq (,$(findstring $(OSVER),opensuse-tumbleweed sles fedora)) +BUILD_DEPENDS_python37= python3-devel +else +BUILD_DEPENDS_python37= python37-devel +endif + +BUILD_DEPENDS+= $(BUILD_DEPENDS_python37) + +define MODULE_PREINSTALL_python37 +%{__mkdir} -p %{buildroot}%{_datadir}/doc/unit-python37/examples/python-app +%{__install} -m 644 -p %{SOURCE100} \ + %{buildroot}%{_datadir}/doc/unit-python37/examples/python-app/wsgi.py +%{__install} -m 644 -p %{SOURCE101} \ + %{buildroot}%{_datadir}/doc/unit-python37/examples/unit.config +endef +export MODULE_PREINSTALL_python37 + +define MODULE_FILES_python37 +%{_libdir}/unit/modules/* +%{_libdir}/unit/debug-modules/* +endef +export MODULE_FILES_python37 + +define MODULE_POST_python37 +cat < Date: Thu, 24 Jan 2019 14:05:58 +0300 Subject: Packages: systemd and other improvements. See the following links for details: https://github.com/nginx/unit/pull/212 https://github.com/nginx/unit/issues/213 Closes #155, closes #212, closes #213. --- pkg/rpm/Makefile | 2 +- pkg/rpm/rpmbuild/SOURCES/unit.logrotate | 10 +++++++++ pkg/rpm/rpmbuild/SOURCES/unit.service | 20 +++++++++++++---- pkg/rpm/rpmbuild/SOURCES/unit.sysconf | 2 +- pkg/rpm/unit.spec.in | 38 ++++++++++++++++++++++++--------- 5 files changed, 56 insertions(+), 16 deletions(-) create mode 100644 pkg/rpm/rpmbuild/SOURCES/unit.logrotate diff --git a/pkg/rpm/Makefile b/pkg/rpm/Makefile index 86b3e3f1..d817b434 100644 --- a/pkg/rpm/Makefile +++ b/pkg/rpm/Makefile @@ -102,7 +102,7 @@ endif CONFIGURE_ARGS=\ --prefix=/usr \ --state=%{_sharedstatedir}/unit \ - --control="unix:/var/run/control.unit.sock" \ + --control="unix:/var/run/unit/control.sock" \ --pid=/var/run/unit.pid \ --log=/var/log/unit.log \ --tests \ diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.logrotate b/pkg/rpm/rpmbuild/SOURCES/unit.logrotate new file mode 100644 index 00000000..8fb00199 --- /dev/null +++ b/pkg/rpm/rpmbuild/SOURCES/unit.logrotate @@ -0,0 +1,10 @@ +/var/log/unit/*.log { + missingok + nocreate + notifempty + postrotate + if [ -f /var/run/unit/unit.pid ]; then + /bin/kill -SIGUSR1 `cat /var/run/unit/unit.pid` + fi + endscript +} diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.service b/pkg/rpm/rpmbuild/SOURCES/unit.service index 4aaf70cd..f888685f 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.service +++ b/pkg/rpm/rpmbuild/SOURCES/unit.service @@ -1,14 +1,26 @@ +# Modifying this file in-place is not recommended, because changes +# will be overwritten during package upgrades. To customize the +# behaviour, run "systemctl edit unit" to create an override unit. + +# For example, to change options given to the unitd binary at startup, +# create an override unit (as is done by systemctl edit) and enter +# the following: + +# [Service] +# Environment="UNITD_OPTIONS=--log /var/log/unit/unit.log --pid /run/unit/unit.pid" + [Unit] Description=NGINX Unit Wants=network-online.target After=network-online.target [Service] -Type=forking -PIDFile=/run/unit.pid -EnvironmentFile=-/etc/sysconfig/unit -ExecStart=/usr/sbin/unitd $UNITD_OPTIONS +Type=simple +Environment="UNITD_OPTIONS=--log /var/log/unit/unit.log --pid /run/unit/unit.pid" +ExecStart=/usr/sbin/unitd $UNITD_OPTIONS --no-daemon ExecReload= +RuntimeDirectory=unit +RuntimeDirectoryMode=0755 [Install] WantedBy=multi-user.target diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.sysconf b/pkg/rpm/rpmbuild/SOURCES/unit.sysconf index 0b28558f..9146bdac 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.sysconf +++ b/pkg/rpm/rpmbuild/SOURCES/unit.sysconf @@ -1 +1 @@ -UNITD_OPTIONS="--log /var/log/unit.log --pid /run/unit.pid" +UNITD_OPTIONS="--log /var/log/unit/unit.log --pid /var/run/unit/unit.pid" diff --git a/pkg/rpm/unit.spec.in b/pkg/rpm/unit.spec.in index 1ef02881..d69a3dc9 100644 --- a/pkg/rpm/unit.spec.in +++ b/pkg/rpm/unit.spec.in @@ -1,25 +1,31 @@ # distribution specific definitions -%define use_systemd (0%{?rhel} && 0%{?rhel} >= 7) || (0%{?suse_version} >= 1315) +%define use_systemd (0%{?rhel} >= 7 || 0%{?fedora} >= 19 || 0%{?suse_version} >= 1315) %define bdir %{_builddir}/%{name}-%{version} %define dotests 0 %if ( 0%{?rhel} == 5 || 0%{?rhel} == 6 ) Requires: initscripts >= 8.36 -BuildRequires: openssl-devel %endif -%if 0%{?rhel} == 7 -Requires: systemd -BuildRequires: systemd-units +%if %{use_systemd} +BuildRequires: systemd +Requires(post): systemd +Requires(preun): systemd +Requires(postun): systemd +%endif + +%if 0%{?rhel}%{?fedora} +BuildRequires: gcc BuildRequires: openssl-devel +%endif + +%if 0%{?rhel} %if 0%{?amzn} == 0 -%define dist .el7 +%define dist .el%{?rhel} %endif %endif %if 0%{?suse_version} >= 1315 -BuildRequires: systemd -Requires: systemd BuildRequires: libopenssl-devel %endif @@ -45,6 +51,7 @@ Source1: unit.service Source2: unit.init Source3: unit.sysconf Source4: unit.example.config +Source5: unit.logrotate BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -103,9 +110,16 @@ DESTDIR=%{buildroot} make unitd-install libunit-install %{__mkdir} -p %{buildroot}%{_libdir}/unit/modules %{__mkdir} -p %{buildroot}%{_libdir}/unit/debug-modules %{__mkdir} -p %{buildroot}%{_sharedstatedir}/unit +%{__mkdir} -p %{buildroot}%{_localstatedir}/log/unit +%{__mkdir} -p %{buildroot}%{_localstatedir}/run/unit +%if ! %{use_systemd} %{__mkdir} -p %{buildroot}%{_sysconfdir}/sysconfig %{__install} -m 644 -p %{SOURCE3} \ - %{buildroot}%{_sysconfdir}/sysconfig/unit + %{buildroot}%{_sysconfdir}/sysconfig/unitd +%endif +%{__mkdir} -p %{buildroot}%{_sysconfdir}/logrotate.d +%{__install} -m 644 -p %{SOURCE5} \ + %{buildroot}%{_sysconfdir}/logrotate.d/unit %{__mkdir} -p %{buildroot}%{_sysconfdir}/unit %{__mkdir} -p %{buildroot}%{_datadir}/doc/unit/examples %{__install} -m 644 -p %{SOURCE4} \ @@ -185,11 +199,13 @@ fi %defattr(-,root,root,-) %attr(0755,root,root) %{_sbindir}/unitd %attr(0755,root,root) %{_sbindir}/unitd-debug -%config(noreplace) %{_sysconfdir}/sysconfig/unit %dir %{_sysconfdir}/unit %if %{use_systemd} %{_unitdir}/unit.service +%dir %attr(0755,root,root) %ghost %{_localstatedir}/run/unit %else +%config(noreplace) %{_sysconfdir}/sysconfig/unitd +%dir %attr(0755,root,root) %{_localstatedir}/run/unit %{_initrddir}/unit %endif %dir %{_datadir}/doc/unit @@ -197,6 +213,8 @@ fi %dir %{_libdir}/unit/modules %dir %{_libdir}/unit/debug-modules %dir %{_sharedstatedir}/unit +%dir %attr(0700,root,root) %{_localstatedir}/log/unit +%config(noreplace) %{_sysconfdir}/logrotate.d/%{name} %files devel %{_libdir}/libunit.a -- cgit From 3b7f28f23cf9592a24272bccfba325414cc07fb1 Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Thu, 24 Jan 2019 15:18:19 +0300 Subject: Packages: fixed control socket path after 7012b4fd5113. --- pkg/rpm/Makefile.go | 2 +- pkg/rpm/Makefile.perl | 2 +- pkg/rpm/Makefile.php | 2 +- pkg/rpm/Makefile.python | 2 +- pkg/rpm/Makefile.python27 | 2 +- pkg/rpm/Makefile.python34 | 2 +- pkg/rpm/Makefile.python35 | 2 +- pkg/rpm/Makefile.python36 | 2 +- pkg/rpm/Makefile.python37 | 2 +- pkg/rpm/Makefile.ruby | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/rpm/Makefile.go b/pkg/rpm/Makefile.go index f031c7ba..09dffd21 100644 --- a/pkg/rpm/Makefile.go +++ b/pkg/rpm/Makefile.go @@ -64,7 +64,7 @@ To check the sample app, run these commands: GOPATH=%{gopath} go build -o /tmp/go-app /usr/share/doc/unit-go/examples/go-app/let-my-people.go sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config curl http://localhost:8500/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.perl b/pkg/rpm/Makefile.perl index 84c15260..f59b7353 100644 --- a/pkg/rpm/Makefile.perl +++ b/pkg/rpm/Makefile.perl @@ -51,7 +51,7 @@ To check out the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config curl http://localhost:8600/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.php b/pkg/rpm/Makefile.php index eedcce4e..8f39efc0 100644 --- a/pkg/rpm/Makefile.php +++ b/pkg/rpm/Makefile.php @@ -46,7 +46,7 @@ To check out the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config curl http://localhost:8300/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.python b/pkg/rpm/Makefile.python index bb408bb5..334d62c1 100644 --- a/pkg/rpm/Makefile.python +++ b/pkg/rpm/Makefile.python @@ -46,7 +46,7 @@ To check the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.python27 b/pkg/rpm/Makefile.python27 index c6a2ec65..005eff17 100644 --- a/pkg/rpm/Makefile.python27 +++ b/pkg/rpm/Makefile.python27 @@ -48,7 +48,7 @@ To check the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.python34 b/pkg/rpm/Makefile.python34 index 1dc10f9c..83c0bdb6 100644 --- a/pkg/rpm/Makefile.python34 +++ b/pkg/rpm/Makefile.python34 @@ -46,7 +46,7 @@ To check the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.python35 b/pkg/rpm/Makefile.python35 index 3e4e0a2e..6f866771 100644 --- a/pkg/rpm/Makefile.python35 +++ b/pkg/rpm/Makefile.python35 @@ -41,7 +41,7 @@ To check the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.python36 b/pkg/rpm/Makefile.python36 index d674d8fe..25e33968 100644 --- a/pkg/rpm/Makefile.python36 +++ b/pkg/rpm/Makefile.python36 @@ -46,7 +46,7 @@ To check the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.python37 b/pkg/rpm/Makefile.python37 index 48f0b5c1..ed9462b8 100644 --- a/pkg/rpm/Makefile.python37 +++ b/pkg/rpm/Makefile.python37 @@ -46,7 +46,7 @@ To check the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config curl http://localhost:8400/ Online documentation is available at https://unit.nginx.org diff --git a/pkg/rpm/Makefile.ruby b/pkg/rpm/Makefile.ruby index 92416338..51c2949d 100644 --- a/pkg/rpm/Makefile.ruby +++ b/pkg/rpm/Makefile.ruby @@ -55,7 +55,7 @@ To check the sample app, run these commands: sudo service unit start cd /usr/share/doc/%{name}/examples - sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config curl http://localhost:8700/ Online documentation is available at https://unit.nginx.org -- cgit From 9e383ecaf21138e51bceee89eb1cd1b342a980ad Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Thu, 24 Jan 2019 16:47:32 +0300 Subject: Packages: disabled debugsource generation on relevant platforms. In particular, this fixes unit-go package building on Fedora >= 29. --- pkg/rpm/unit.module.spec.in | 11 +++++++++++ pkg/rpm/unit.spec.in | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/pkg/rpm/unit.module.spec.in b/pkg/rpm/unit.module.spec.in index 03d4addb..8b8a3433 100644 --- a/pkg/rpm/unit.module.spec.in +++ b/pkg/rpm/unit.module.spec.in @@ -44,6 +44,10 @@ This package contains %%SUMMARY%%. %debug_package %endif +%if 0%{?fedora} +%define _debugsource_template %{nil} +%endif + %prep %setup -qcTn %{name}-%{unit_version} tar --strip-components=1 -zxf %{SOURCE0} @@ -78,6 +82,13 @@ DESTDIR=%{buildroot} make %%MODULE_INSTARGS%% DESTDIR=%{buildroot} make %%MODULE_INSTARGS%% %check +%{__rm} -rf %{buildroot}/usr/src +cd %{bdir} +grep -v 'usr/src' debugfiles.list > debugfiles.list.new && mv debugfiles.list.new debugfiles.list +cat /dev/null > debugsources.list +%if 0%{?suse_version} >= 1500 +cat /dev/null > debugsourcefiles.list +%endif %clean %{__rm} -rf %{buildroot} diff --git a/pkg/rpm/unit.spec.in b/pkg/rpm/unit.spec.in index d69a3dc9..05ee79af 100644 --- a/pkg/rpm/unit.spec.in +++ b/pkg/rpm/unit.spec.in @@ -67,6 +67,10 @@ dynamically via an API. %debug_package %endif +%if 0%{?fedora} +%define _debugsource_template %{nil} +%endif + %package devel Summary: NGINX Unit (development files) Version: %%VERSION%% @@ -147,6 +151,13 @@ export QA_SKIP_BUILD_ROOT %if %{dotests} cd %{bdir} && make tests && ./build/tests %endif +%{__rm} -rf %{buildroot}/usr/src +cd %{bdir} +grep -v 'usr/src' debugfiles.list > debugfiles.list.new && mv debugfiles.list.new debugfiles.list +cat /dev/null > debugsources.list +%if 0%{?suse_version} >= 1500 +cat /dev/null > debugsourcefiles.list +%endif %clean %{__rm} -rf %{buildroot} -- cgit From eced6bc97284478d67bfec04e204e4cd212167d9 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Mon, 28 Jan 2019 15:11:50 +0300 Subject: Tests: removed blocking mode customization for sockets. This customization was added in 0e12b17e512d and left unused. Also, set blocking mode by default for all sockets. --- test/unit.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/unit.py b/test/unit.py index 2b95b792..55b3ce18 100644 --- a/test/unit.py +++ b/test/unit.py @@ -285,7 +285,6 @@ class TestUnitHTTP(TestUnit): port = 7080 if 'port' not in kwargs else kwargs['port'] url = '/' if 'url' not in kwargs else kwargs['url'] http = 'HTTP/1.0' if 'http_10' in kwargs else 'HTTP/1.1' - blocking = False if 'blocking' not in kwargs else kwargs['blocking'] headers = ({ 'Host': 'localhost', @@ -322,8 +321,6 @@ class TestUnitHTTP(TestUnit): sock.close() return None - sock.setblocking(blocking) - else: sock = kwargs['sock'] @@ -640,11 +637,11 @@ class TestUnitApplicationTLS(TestUnitApplicationProto): return self.conf(k.read() + c.read(), '/certificates/' + crt) def get_ssl(self, **kwargs): - return self.get(blocking=True, wrapper=self.context.wrap_socket, + return self.get(wrapper=self.context.wrap_socket, **kwargs) def post_ssl(self, **kwargs): - return self.post(blocking=True, wrapper=self.context.wrap_socket, + return self.post(wrapper=self.context.wrap_socket, **kwargs) def get_server_certificate(self, addr=('127.0.0.1', 7080)): -- cgit From 7dddfe2143e28dcde147d8420f0bd129babaec3a Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Mon, 28 Jan 2019 17:16:50 +0300 Subject: Tests: added test for reading body from IO::Handle-like object. --- test/perl/body_io_fake/IOFake.pm | 33 +++++++++++++++++++++++++++++++++ test/perl/body_io_fake/psgi.pl | 11 +++++++++++ test/test_perl_application.py | 14 ++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 test/perl/body_io_fake/IOFake.pm create mode 100644 test/perl/body_io_fake/psgi.pl diff --git a/test/perl/body_io_fake/IOFake.pm b/test/perl/body_io_fake/IOFake.pm new file mode 100644 index 00000000..d2542aa5 --- /dev/null +++ b/test/perl/body_io_fake/IOFake.pm @@ -0,0 +1,33 @@ +package IOFake; + +sub new { + my $class = shift; + my $errors = shift; + my $self = {}; + + $self->{_count} = 2; + $self->{_errors} = $errors; + + bless $self, $class; + return $self; +} + +sub getline() { + my $self = shift; + + if ($self->{_count} > 0) { + return $self->{_count}--; + } + + $self->{_errors}->print('IOFake getline() $/ is ' . ${ $/ }); + + return; +} + +sub close() { + my $self = shift; + + $self->{_errors}->print('IOFake close() called'); +}; + +1; diff --git a/test/perl/body_io_fake/psgi.pl b/test/perl/body_io_fake/psgi.pl new file mode 100644 index 00000000..6990bfaf --- /dev/null +++ b/test/perl/body_io_fake/psgi.pl @@ -0,0 +1,11 @@ +use File::Basename; +use lib dirname (__FILE__); +use IOFake; + +my $app = sub { + my ($environ) = @_; + + my $io = IOFake->new($environ->{'psgi.errors'}); + + return ['200', [ 'Content-Length' => '2' ], $io]; +}; diff --git a/test/test_perl_application.py b/test/test_perl_application.py index 2dfae66c..7850e231 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -185,5 +185,19 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') + @unittest.expectedFailure + def test_perl_body_io_fake(self): + self.load('body_io_fake') + + self.assertEqual(self.get()['body'], '21', 'body io fake') + + self.assertIsNotNone( + self.search_in_log(r'\[error\].+IOFake getline\(\) \$\/ is \d+'), + 'body io fake $/ value') + + self.assertIsNotNone( + self.search_in_log(r'\[error\].+IOFake close\(\) called'), + 'body io fake close') + if __name__ == '__main__': TestUnitPerlApplication.main() -- cgit From d60fbc6d4476982fb683d485859acd2c29a6837c Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Mon, 28 Jan 2019 17:17:23 +0300 Subject: Tests: large configuration tests. --- test/test_configuration.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/test_configuration.py b/test/test_configuration.py index 02705afe..7f81b86b 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -217,5 +217,52 @@ class TestUnitConfiguration(unit.TestUnitControl): } }), 'no port') + @unittest.expectedFailure + def test_json_application_name_large(self): + self.skip_alerts.append(r'epoll_ctl.+failed') + name = "X" * 1024 * 1024 + + self.assertIn('success', self.conf({ + "listeners": { + "*:7080": { + "application": name + } + }, + "applications": { + name: { + "type": "python", + "processes": { "spare": 0 }, + "path": "/app", + "module": "wsgi" + } + } + })) + + @unittest.expectedFailure + def test_json_application_many(self): + self.skip_alerts.extend([ + r'eventfd.+failed', + r'failed to apply new conf' + ]) + apps = 1000 + + conf = { + "applications": + {"app-" + str(a): { + "type": "python", + "processes": { "spare": 0 }, + "path": "/app", + "module": "wsgi" + } for a in range(apps) + }, + "listeners": { + "*:" + str(7080 + a): { + "application": "app-" + str(a) + } for a in range(apps) + } + } + + self.assertIn('success', self.conf(conf)) + if __name__ == '__main__': TestUnitConfiguration.main() -- cgit From a2cbe890a19ca2341de5b54ffe9c9f5924cf10ec Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 7 Feb 2019 17:40:27 +0300 Subject: Rejecting requests with invalid "Content-Length". --- src/nxt_http_request.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index b80998cb..c8adb499 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -79,14 +79,20 @@ nxt_int_t nxt_http_request_content_length(void *ctx, nxt_http_field_t *field, uintptr_t data) { + nxt_off_t n; nxt_http_request_t *r; r = ctx; - r->content_length = field; - r->content_length_n = nxt_off_t_parse(field->value, field->value_length); - return NXT_OK; + n = nxt_off_t_parse(field->value, field->value_length); + + if (nxt_fast_path(n >= 0)) { + r->content_length_n = n; + return NXT_OK; + } + + return NXT_ERROR; } -- cgit From 7ce9f61cb66ddd78e2a6778970cd7c4845c79f00 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Thu, 14 Feb 2019 16:09:19 +0300 Subject: Tests: minor fixes. --- test/test_settings.py | 2 -- test/unit.py | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/test/test_settings.py b/test/test_settings.py index b4ac33dc..d272f701 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -28,8 +28,6 @@ Connection: close def test_settings_header_read_timeout_update(self): self.load('empty') - r = None - self.conf({'http': { 'header_read_timeout': 4 }}, 'settings') (resp, sock) = self.http(b"""GET / HTTP/1.1 diff --git a/test/unit.py b/test/unit.py index 55b3ce18..c0c65d4a 100644 --- a/test/unit.py +++ b/test/unit.py @@ -235,7 +235,7 @@ class TestUnit(unittest.TestCase): if sanitizer_errors: self._print_path_to_log() - self.assertFalse(sanitizer_error, 'sanitizer error(s)') + self.assertFalse(sanitizer_errors, 'sanitizer error(s)') if found: print('skipped.') @@ -350,8 +350,8 @@ class TestUnitHTTP(TestUnit): resp = '' if 'no_recv' not in kwargs: - enc = 'utf-8' if 'encoding' not in kwargs else kwargs['encoding'] - resp = self.recvall(sock).decode(enc) + enc = 'utf-8' if 'encoding' not in kwargs else kwargs['encoding'] + resp = self.recvall(sock).decode(enc) if TestUnit.detailed: print('<<<', resp.encode('utf-8'), sep='\n') -- cgit From 0e5aaf60d4ca2da509a6a73bebe71139f8aae306 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Thu, 14 Feb 2019 16:09:54 +0300 Subject: Tests: headers with equal header fields allowed. --- test/unit.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/unit.py b/test/unit.py index c0c65d4a..bd6128cc 100644 --- a/test/unit.py +++ b/test/unit.py @@ -335,7 +335,12 @@ class TestUnitHTTP(TestUnit): headers['Content-Length'] = len(body) for header, value in headers.items(): - req += header + ': ' + str(value) + crlf + if isinstance(value, list): + for v in value: + req += header + ': ' + str(v) + crlf + + else: + req += header + ': ' + str(value) + crlf req = (req + crlf).encode() + body -- cgit From ab40732c0886b979090da6245cc1a7f0ba52de49 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Thu, 14 Feb 2019 16:09:58 +0300 Subject: Tests: added tests for "Content-Length" header. --- test/test_http_header.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/test_http_header.py b/test/test_http_header.py index b850831d..7894c94e 100644 --- a/test/test_http_header.py +++ b/test/test_http_header.py @@ -162,5 +162,51 @@ a self.assertEqual(resp['status'], 200, 'transfer encoding chunked') + def test_http_header_content_length_big(self): + self.load('empty') + + self.assertEqual(self.post(headers={ + 'Content-Length': str(2 ** 64), + 'Connection': 'close', + 'Host': 'localhost' + }, body='X' * 1000)['status'], 400, 'Content-Length big') + + def test_http_header_content_length_negative(self): + self.load('empty') + + self.assertEqual(self.post(headers={ + 'Content-Length': '-100', + 'Connection': 'close', + 'Host': 'localhost' + }, body='X' * 1000)['status'], 400, 'Content-Length negative') + + def test_http_header_content_length_text(self): + self.load('empty') + + self.assertEqual(self.post(headers={ + 'Content-Length': 'blah', + 'Connection': 'close', + 'Host': 'localhost' + }, body='X' * 1000)['status'], 400, 'Content-Length text') + + def test_http_header_content_length_multiple_values(self): + self.load('empty') + + self.assertEqual(self.post(headers={ + 'Content-Length': '41, 42', + 'Connection': 'close', + 'Host': 'localhost' + }, body='X' * 1000)['status'], 400, 'Content-Length multiple value') + + @unittest.expectedFailure + def test_http_header_content_length_multiple_fields(self): + self.load('empty') + + self.assertEqual(self.post(headers={ + 'Content-Length': ['41', '42'], + 'Connection': 'close', + 'Host': 'localhost' + }, body='X' * 1000)['status'], 400, 'Content-Length multiple fields') + if __name__ == '__main__': TestUnitHTTPHeader.main() -- cgit From 43ad6be2f04d4212bd97ffc9d8a2fe833d8f218c Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Thu, 14 Feb 2019 17:21:36 +0300 Subject: Packages: removed unit-perl on CentOS 6. CentOS 6 has Perl version 5.10, which is unsupported by Unit. --- pkg/rpm/Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/rpm/Makefile b/pkg/rpm/Makefile index d817b434..fdc31302 100644 --- a/pkg/rpm/Makefile +++ b/pkg/rpm/Makefile @@ -46,7 +46,13 @@ BUILD_DEPENDS = $(BUILD_DEPENDS_unit) MODULES= -ifneq (,$(findstring $(OSVER),centos6 centos7 amazonlinux2)) +ifeq ($(OSVER), centos6) +include Makefile.php +include Makefile.python +include Makefile.go +endif + +ifneq (,$(findstring $(OSVER),centos7 amazonlinux2)) include Makefile.php include Makefile.python include Makefile.go -- cgit From 1ba49d925031a74f1a6b6f31991562583f3271b5 Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Thu, 14 Feb 2019 17:21:37 +0300 Subject: Packages: added "-fno-strict-aliasing" flag on CentOS 6 x86_64. Closes #221 on GitHub. --- pkg/rpm/unit.module.spec.in | 4 ++++ pkg/rpm/unit.spec.in | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/pkg/rpm/unit.module.spec.in b/pkg/rpm/unit.module.spec.in index 8b8a3433..d06db231 100644 --- a/pkg/rpm/unit.module.spec.in +++ b/pkg/rpm/unit.module.spec.in @@ -10,7 +10,11 @@ %define unit_version %%UNIT_VERSION%% %define unit_release %%UNIT_RELEASE%%%{?dist}.ngx +%if (0%{?rhel} == 6) && (%{_arch} == x86_64) +%define CC_OPT %{optflags} -fno-strict-aliasing +%else %define CC_OPT %{optflags} +%endif %define CONFIGURE_ARGS $(echo "%%CONFIGURE_ARGS%%") diff --git a/pkg/rpm/unit.spec.in b/pkg/rpm/unit.spec.in index 05ee79af..2d5c1bd1 100644 --- a/pkg/rpm/unit.spec.in +++ b/pkg/rpm/unit.spec.in @@ -29,7 +29,12 @@ BuildRequires: openssl-devel BuildRequires: libopenssl-devel %endif +%if (0%{?rhel} == 6) && (%{_arch} == x86_64) +%define CC_OPT %{optflags} -fno-strict-aliasing -fPIC +%else %define CC_OPT %{optflags} -fPIC +%endif + %define LD_OPT -Wl,-z,relro -Wl,-z,now -pie %define CONFIGURE_ARGS $(echo "%%CONFIGURE_ARGS%%") -- cgit From 2a6b54c23020a24eaadfb4006235ca17dfa877f8 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Mon, 18 Feb 2019 16:51:30 +0300 Subject: Rejecting requests with duplicate "Content-Length". --- src/nxt_http_request.c | 13 ++++++++----- test/test_http_header.py | 1 - 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index c8adb499..bbc138a7 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -83,13 +83,16 @@ nxt_http_request_content_length(void *ctx, nxt_http_field_t *field, nxt_http_request_t *r; r = ctx; - r->content_length = field; - n = nxt_off_t_parse(field->value, field->value_length); + if (nxt_fast_path(r->content_length == NULL)) { + r->content_length = field; - if (nxt_fast_path(n >= 0)) { - r->content_length_n = n; - return NXT_OK; + n = nxt_off_t_parse(field->value, field->value_length); + + if (nxt_fast_path(n >= 0)) { + r->content_length_n = n; + return NXT_OK; + } } return NXT_ERROR; diff --git a/test/test_http_header.py b/test/test_http_header.py index 7894c94e..5a982217 100644 --- a/test/test_http_header.py +++ b/test/test_http_header.py @@ -198,7 +198,6 @@ a 'Host': 'localhost' }, body='X' * 1000)['status'], 400, 'Content-Length multiple value') - @unittest.expectedFailure def test_http_header_content_length_multiple_fields(self): self.load('empty') -- cgit From bb11e9036f8c5bf6f36899bb462709bdbf55bfa4 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Mon, 18 Feb 2019 17:28:55 +0300 Subject: Fixed memory leak on response body sending failure. --- src/nxt_http.h | 1 - src/nxt_http_request.c | 4 +++- src/nxt_router.c | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/nxt_http.h b/src/nxt_http.h index b2111f90..10c6a9f1 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -176,7 +176,6 @@ nxt_buf_t *nxt_http_buf_mem(nxt_task_t *task, nxt_http_request_t *r, size_t size); nxt_buf_t *nxt_http_buf_last(nxt_http_request_t *r); void nxt_http_request_error_handler(nxt_task_t *task, void *obj, void *data); -void nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data); nxt_int_t nxt_http_request_host(void *ctx, nxt_http_field_t *field, uintptr_t data); diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index bbc138a7..ed65a592 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -13,6 +13,8 @@ static void nxt_http_app_request(nxt_task_t *task, void *obj, void *data); static void nxt_http_request_mem_buf_completion(nxt_task_t *task, void *obj, void *data); static void nxt_http_request_done(nxt_task_t *task, void *obj, void *data); +static void nxt_http_request_close_handler(nxt_task_t *task, void *obj, + void *data); static u_char *nxt_http_date(u_char *buf, nxt_realtime_t *now, struct tm *tm, size_t size, const char *format); @@ -449,7 +451,7 @@ nxt_http_request_error_handler(nxt_task_t *task, void *obj, void *data) } -void +static void nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data) { nxt_http_proto_t proto; diff --git a/src/nxt_router.c b/src/nxt_router.c index 7ecbca81..e9233554 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -3500,7 +3500,7 @@ static const nxt_http_request_state_t nxt_http_request_send_state nxt_aligned(64) = { .ready_handler = nxt_http_request_send_body, - .error_handler = nxt_http_request_close_handler, + .error_handler = nxt_http_request_error_handler, }; -- cgit From acb5b0aad7f9aca966f4d40d05c104f4df68b036 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Mon, 18 Feb 2019 18:34:20 +0300 Subject: Ignoring HUP signal in main process. --- src/nxt_main_process.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index a4c3f119..a33117e2 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -52,6 +52,8 @@ static void nxt_main_process_sigusr1_handler(nxt_task_t *task, void *obj, void *data); static void nxt_main_process_sigchld_handler(nxt_task_t *task, void *obj, void *data); +static void nxt_main_process_signal_handler(nxt_task_t *task, void *obj, + void *data); static void nxt_main_cleanup_worker_process(nxt_task_t *task, nxt_pid_t pid); static void nxt_main_stop_worker_processes(nxt_task_t *task, nxt_runtime_t *rt); static void nxt_main_port_socket_handler(nxt_task_t *task, @@ -68,6 +70,7 @@ static void nxt_main_port_access_log_handler(nxt_task_t *task, const nxt_sig_event_t nxt_main_process_signals[] = { + nxt_event_signal(SIGHUP, nxt_main_process_signal_handler), nxt_event_signal(SIGINT, nxt_main_process_sigterm_handler), nxt_event_signal(SIGQUIT, nxt_main_process_sigquit_handler), nxt_event_signal(SIGTERM, nxt_main_process_sigterm_handler), @@ -888,6 +891,14 @@ nxt_main_process_sigchld_handler(nxt_task_t *task, void *obj, void *data) } +static void +nxt_main_process_signal_handler(nxt_task_t *task, void *obj, void *data) +{ + nxt_trace(task, "signal signo:%d (%s) recevied, ignored", + (int) (uintptr_t) obj, data); +} + + static void nxt_main_cleanup_worker_process(nxt_task_t *task, nxt_pid_t pid) { -- cgit From 2d4697dbbec7dd51ed5aeebb10e543038e15a359 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Tue, 19 Feb 2019 20:25:25 +0300 Subject: Validation and normalization of request host. --- src/nxt_h1proto.c | 4 +- src/nxt_http.h | 2 +- src/nxt_http_request.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 112 insertions(+), 11 deletions(-) diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c index 2194e56f..4e1b22e9 100644 --- a/src/nxt_h1proto.c +++ b/src/nxt_h1proto.c @@ -526,9 +526,7 @@ nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj, void *data) return; } - /* ret == NXT_ERROR */ - status = NXT_HTTP_BAD_REQUEST; - + status = ret; goto error; case NXT_AGAIN: diff --git a/src/nxt_http.h b/src/nxt_http.h index 10c6a9f1..af0b2cd6 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -119,12 +119,12 @@ struct nxt_http_request_s { nxt_str_t *args; nxt_list_t *fields; - nxt_http_field_t *host; nxt_http_field_t *content_type; nxt_http_field_t *content_length; nxt_http_field_t *cookie; nxt_http_field_t *referer; nxt_http_field_t *user_agent; + nxt_str_t host; nxt_off_t content_length_n; nxt_sockaddr_t *remote; diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index ed65a592..6029c5c0 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -8,6 +8,7 @@ #include +static nxt_int_t nxt_http_validate_host(nxt_str_t *host, nxt_mp_t *mp); static void nxt_http_request_start(nxt_task_t *task, void *obj, void *data); static void nxt_http_app_request(nxt_task_t *task, void *obj, void *data); static void nxt_http_request_mem_buf_completion(nxt_task_t *task, void *obj, @@ -52,13 +53,118 @@ nxt_http_init(nxt_task_t *task, nxt_runtime_t *rt) nxt_int_t nxt_http_request_host(void *ctx, nxt_http_field_t *field, uintptr_t data) { + nxt_int_t ret; + nxt_str_t host; nxt_http_request_t *r; r = ctx; - /* TODO: validate host. */ + if (nxt_slow_path(r->host.start != NULL)) { + return NXT_HTTP_BAD_REQUEST; + } + + host.length = field->value_length; + host.start = field->value; + + ret = nxt_http_validate_host(&host, r->mem_pool); + + if (nxt_fast_path(ret == NXT_OK)) { + r->host = host; + } + + return ret; +} + + +static nxt_int_t +nxt_http_validate_host(nxt_str_t *host, nxt_mp_t *mp) +{ + u_char *h, ch; + size_t i, dot_pos, host_length; + nxt_bool_t lowcase; + + enum { + sw_usual, + sw_literal, + sw_rest + } state; + + dot_pos = host->length; + host_length = host->length; + + h = host->start; + + lowcase = 0; + state = sw_usual; + + for (i = 0; i < host->length; i++) { + ch = h[i]; + + if (ch > ']') { + /* Short path. */ + continue; + } + + switch (ch) { + + case '.': + if (dot_pos == i - 1) { + return NXT_HTTP_BAD_REQUEST; + } - r->host = field; + dot_pos = i; + break; + + case ':': + if (state == sw_usual) { + host_length = i; + state = sw_rest; + } + + break; + + case '[': + if (i == 0) { + state = sw_literal; + } + + break; + + case ']': + if (state == sw_literal) { + host_length = i + 1; + state = sw_rest; + } + + break; + + case '/': + case '\0': + return NXT_HTTP_BAD_REQUEST; + + default: + if (ch >= 'A' && ch <= 'Z') { + lowcase = 1; + } + + break; + } + } + + if (dot_pos == host_length - 1) { + host_length--; + } + + host->length = host_length; + + if (lowcase) { + host->start = nxt_mp_nget(mp, host_length); + if (nxt_slow_path(host->start == NULL)) { + return NXT_HTTP_INTERNAL_SERVER_ERROR; + } + + nxt_memcpy_lowcase(host->start, h, host_length); + } return NXT_OK; } @@ -97,7 +203,7 @@ nxt_http_request_content_length(void *ctx, nxt_http_field_t *field, } } - return NXT_ERROR; + return NXT_HTTP_BAD_REQUEST; } @@ -237,10 +343,7 @@ nxt_http_app_request(nxt_task_t *task, void *obj, void *data) ar->r.header.query = *r->args; } - if (r->host != NULL) { - ar->r.header.host.length = r->host->value_length; - ar->r.header.host.start = r->host->value; - } + ar->r.header.host = r->host; if (r->content_type != NULL) { ar->r.header.content_type.length = r->content_type->value_length; -- cgit From ac10bf8c7b4cbfb9421956ba8ee3005bb7f4fbfa Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 20 Feb 2019 20:19:55 +0300 Subject: Tests: fixed ports range. --- test/test_configuration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_configuration.py b/test/test_configuration.py index 7f81b86b..d6c9cd5c 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -244,7 +244,7 @@ class TestUnitConfiguration(unit.TestUnitControl): r'eventfd.+failed', r'failed to apply new conf' ]) - apps = 1000 + apps = 999 conf = { "applications": @@ -256,7 +256,7 @@ class TestUnitConfiguration(unit.TestUnitControl): } for a in range(apps) }, "listeners": { - "*:" + str(7080 + a): { + "*:" + str(7000 + a): { "application": "app-" + str(a) } for a in range(apps) } -- cgit From b2ca342902ec84b4415982be08d89c8bfe0ed431 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 20 Feb 2019 20:20:02 +0300 Subject: Tests: JSON array allowed. --- test/unit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit.py b/test/unit.py index bd6128cc..2af3e8f6 100644 --- a/test/unit.py +++ b/test/unit.py @@ -433,7 +433,7 @@ class TestUnitControl(TestUnitHTTP): # TODO http client def conf(self, conf, path='/config'): - if isinstance(conf, dict): + if isinstance(conf, dict) or isinstance(conf, list): conf = json.dumps(conf) if path[:1] != '/': -- cgit From 09fb847cc731d133b2016ff6982ab95aa43794ca Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 20 Feb 2019 20:24:05 +0300 Subject: Tests: removed test_http_header_transfer_encoding_chunked. --- test/test_http_header.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/test/test_http_header.py b/test/test_http_header.py index 5a982217..0544f3b3 100644 --- a/test/test_http_header.py +++ b/test/test_http_header.py @@ -145,23 +145,6 @@ Connection: close self.assertEqual(resp['status'], 400, 'field trailing htab') - @unittest.expectedFailure - def test_http_header_transfer_encoding_chunked(self): - self.load('empty') - - resp = self.http(b"""GET / HTTP/1.1 -Host: localhost -Transfer-Encoding: chunked -Connection: close - -a -0123456789 -0 - -""", raw=True) - - self.assertEqual(resp['status'], 200, 'transfer encoding chunked') - def test_http_header_content_length_big(self): self.load('empty') -- cgit From 955050aea3148595fde027319fd427369856ebc9 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 20 Feb 2019 20:28:29 +0300 Subject: Tests: skip sendmsg()/recvmsg() alerts for all tests. Currently, these alerts may appear in the log when any application exits. --- test/test_configuration.py | 1 - test/test_python_application.py | 8 -------- test/test_ruby_application.py | 1 - test/unit.py | 3 ++- 4 files changed, 2 insertions(+), 11 deletions(-) diff --git a/test/test_configuration.py b/test/test_configuration.py index d6c9cd5c..32c5a0b7 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -132,7 +132,6 @@ class TestUnitConfiguration(unit.TestUnitControl): self.skip_sanitizer = True self.skip_alerts.extend([ r'failed to apply previous configuration', - r'sendmsg.+failed', r'process \d+ exited on signal' ]) diff --git a/test/test_python_application.py b/test/test_python_application.py index b28675f9..d37af0c4 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -87,7 +87,6 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): '204 header transfer encoding') def test_python_application_ctx_iter_atexit(self): - self.skip_alerts.append(r'sendmsg.+failed') self.load('ctx_iter_atexit') resp = self.post(headers={ @@ -131,10 +130,6 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') def test_python_keepalive_reconfigure(self): - self.skip_alerts.extend([ - r'sendmsg.+failed', - r'recvmsg.+failed' - ]) self.load('mirror') body = '0123456789' @@ -183,7 +178,6 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): }, 'applications/mirror/processes'), 'reconfigure 3') def test_python_keepalive_reconfigure_2(self): - self.skip_alerts.append(r'sendmsg.+failed') self.load('mirror') body = '0123456789' @@ -217,7 +211,6 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): self.assertEqual(resp, {}, 'reconfigure 2 keep-alive 3') def test_python_keepalive_reconfigure_3(self): - self.skip_alerts.append(r'sendmsg.+failed') self.load('empty') (resp, sock) = self.http(b"""GET / HTTP/1.1 @@ -236,7 +229,6 @@ Connection: close self.assertEqual(resp['status'], 200, 'reconfigure 3') def test_python_atexit(self): - self.skip_alerts.append(r'sendmsg.+failed') self.load('atexit') self.get() diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py index b88e043d..9eed3e4a 100644 --- a/test/test_ruby_application.py +++ b/test/test_ruby_application.py @@ -208,7 +208,6 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): 'errors write int') def test_ruby_application_at_exit(self): - self.skip_alerts.append(r'sendmsg.+failed') self.load('at_exit') self.get() diff --git a/test/unit.py b/test/unit.py index 2af3e8f6..80e26e0d 100644 --- a/test/unit.py +++ b/test/unit.py @@ -179,7 +179,8 @@ class TestUnit(unittest.TestCase): self._started = True - self.skip_alerts = [r'read signalfd\(4\) failed'] + self.skip_alerts = [r'read signalfd\(4\) failed', r'sendmsg.+failed', + r'recvmsg.+failed'] self.skip_sanitizer = False def _stop(self): -- cgit From 499096a55a681609a9b655059e2f56638bcf104e Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 20 Feb 2019 20:46:23 +0300 Subject: Tests: one more alert skipped in test_json_application_many. --- test/test_configuration.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_configuration.py b/test/test_configuration.py index 32c5a0b7..52a67d38 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -241,6 +241,7 @@ class TestUnitConfiguration(unit.TestUnitControl): def test_json_application_many(self): self.skip_alerts.extend([ r'eventfd.+failed', + r'epoll_create.+failed', r'failed to apply new conf' ]) apps = 999 -- cgit From 3280b826e373bf88c16640cb434771df378bbb07 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 21 Feb 2019 16:30:59 +0300 Subject: Initializing incoming buffer queue in a proper place. In case nxt_unit_tracking_read() failed, execution would jump to the error path, where it could try to release buffers from uninitialized yet incoming_buf queue. --- src/nxt_unit.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nxt_unit.c b/src/nxt_unit.c index 24e51075..fa1fb1d5 100644 --- a/src/nxt_unit.c +++ b/src/nxt_unit.c @@ -541,6 +541,8 @@ nxt_unit_process_msg(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id, memcpy(&fd, CMSG_DATA(cm), sizeof(int)); } + nxt_queue_init(&incoming_buf); + if (nxt_slow_path(buf_size < sizeof(nxt_port_msg_t))) { nxt_unit_warn(ctx, "message too small (%d bytes)", (int) buf_size); goto fail; @@ -570,8 +572,6 @@ nxt_unit_process_msg(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id, } if (port_msg->mmap) { - nxt_queue_init(&incoming_buf); - if (nxt_unit_mmap_read(ctx, &recv_msg, &incoming_buf) != NXT_UNIT_OK) { goto fail; } -- cgit From c96b2baca50fe2f5c17f96406e6d741e88267921 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Fri, 22 Feb 2019 17:32:38 +0300 Subject: TLS certificates should be freed per listener. This fixes memory leak if configuration uses more than one TLS cerificate. --- src/nxt_router.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/nxt_router.c b/src/nxt_router.c index e9233554..8b93e15d 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -2910,6 +2910,7 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) rtcf, rtcf->count); if (--skcf->count != 0) { + skcf = NULL; rtcf = NULL; app = NULL; @@ -2923,6 +2924,14 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) nxt_thread_spin_unlock(lock); + if (skcf != NULL) { +#if (NXT_TLS) + if (skcf->tls != NULL) { + task->thread->runtime->tls->server_free(task, skcf->tls); + } +#endif + } + if (app != NULL) { nxt_router_app_use(task, app, -1); } @@ -2933,12 +2942,6 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) if (rtcf != NULL) { nxt_debug(task, "old router conf is destroyed"); -#if (NXT_TLS) - if (skcf->tls != NULL) { - task->thread->runtime->tls->server_free(task, skcf->tls); - } -#endif - nxt_router_access_log_release(task, lock, rtcf->access_log); nxt_mp_thread_adopt(rtcf->mem_pool); -- cgit From 608e09e9def182756e2f1650c6f3416b209fbf58 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Fri, 22 Feb 2019 16:31:44 +0300 Subject: Improvement and unification of version processing in build scripts. This also eliminates expressions that incompatible with BSD make, thus fixing installation of Node.js module on FreeBSD (broken by dace60fc4926). --- auto/make | 28 ++++++++++++++++++---------- auto/modules/go | 2 +- auto/modules/nodejs | 11 +++-------- configure | 1 + pkg/Makefile | 4 ++-- pkg/deb/Makefile | 5 ++--- pkg/docker/Makefile | 6 +++--- pkg/npm/Makefile | 10 +++------- pkg/rpm/Makefile | 5 ++--- src/nodejs/unit-http/unit.h | 2 +- src/nxt_main.h | 5 +---- src/nxt_unit.h | 2 +- version | 5 +++++ 13 files changed, 43 insertions(+), 43 deletions(-) create mode 100644 version diff --git a/auto/make b/auto/make index 4f716b93..1eee2a78 100644 --- a/auto/make +++ b/auto/make @@ -67,6 +67,22 @@ done $echo >> $NXT_MAKEFILE +# The version file. + +cat << END >> $NXT_MAKEFILE + +include version + +$NXT_VERSION_H: version + $echo '#define NXT_VERSION "\$(NXT_VERSION)"' > $NXT_VERSION_H + $echo '#define NXT_VERNUM \$(NXT_VERNUM)' >> $NXT_VERSION_H + +$NXT_BUILD_DIR/src/nxt_unit.o: $NXT_VERSION_H +$NXT_BUILD_DIR/src/nxt_lib.o: $NXT_VERSION_H + +END + + # Shared and static library. cat << END >> $NXT_MAKEFILE @@ -82,14 +98,6 @@ $NXT_BUILD_DIR/$NXT_LIB_STATIC: \$(NXT_LIB_OBJS) $NXT_STATIC_LINK $NXT_BUILD_DIR/$NXT_LIB_STATIC \\ \$(NXT_LIB_OBJS) -$NXT_BUILD_DIR/nxt_unit_version.h: src/nxt_main.h - $echo -n '#define NXT_UNIT_VERNUM ' > $NXT_BUILD_DIR/nxt_unit_version.h - grep 'define NXT_VERNUM' src/nxt_main.h \\ - | sed -e 's/[^0-9]//g' >> $NXT_BUILD_DIR/nxt_unit_version.h - -$NXT_BUILD_DIR/src/nxt_unit.o: $NXT_BUILD_DIR/nxt_unit_version.h -$NXT_BUILD_DIR/src/nxt_lib.o: $NXT_BUILD_DIR/nxt_unit_version.h - $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC: \$(NXT_LIB_UNIT_OBJS) $NXT_STATIC_LINK $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC \\ \$(NXT_LIB_UNIT_OBJS) @@ -296,7 +304,7 @@ libunit-install: $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC install -d \$(DESTDIR)$NXT_INCDIR install -p -m u=rw,go=r src/nxt_unit.h src/nxt_unit_field.h \ src/nxt_unit_request.h src/nxt_unit_response.h src/nxt_unit_sptr.h \ - src/nxt_unit_typedefs.h $NXT_BUILD_DIR/nxt_unit_version.h \ + src/nxt_unit_typedefs.h $NXT_BUILD_DIR/nxt_version.h \ \$(DESTDIR)$NXT_INCDIR/ libunit-uninstall: @@ -308,7 +316,7 @@ libunit-uninstall: \$(DESTDIR)$NXT_INCDIR/nxt_unit_response.h \ \$(DESTDIR)$NXT_INCDIR/nxt_unit_sptr.h \ \$(DESTDIR)$NXT_INCDIR/nxt_unit_typedefs.h \ - \$(DESTDIR)$NXT_INCDIR/nxt_unit_version.h + \$(DESTDIR)$NXT_INCDIR/nxt_version.h @rmdir -p \$(DESTDIR)$NXT_INCDIR 2>/dev/null || true END diff --git a/auto/modules/go b/auto/modules/go index e9b2321d..62c3743f 100644 --- a/auto/modules/go +++ b/auto/modules/go @@ -103,7 +103,7 @@ ${NXT_GO}: ${NXT_GO}-install: ${NXT_GO}-install-build -${NXT_GO}-install-src: ${NXT_BUILD_DIR}/nxt_unit_version.h +${NXT_GO}-install-src: ${NXT_VERSION_H} install -d \$(DESTDIR)\$(NXT_GO_DST)/src/nginx/unit install -p -m644 ./src/*.h ./build/*.h ./src/go/unit/* \ ./src/nxt_unit.c ./src/nxt_lvlhsh.c ./src/nxt_murmur_hash.c \ diff --git a/auto/modules/nodejs b/auto/modules/nodejs index 57bf9d3a..6fd00496 100644 --- a/auto/modules/nodejs +++ b/auto/modules/nodejs @@ -145,9 +145,6 @@ cat << END >> $NXT_MAKEFILE .PHONY: ${NXT_NODE}-build .PHONY: ${NXT_NODE}-publish -NXT_UNIT_VERSION=\$(shell grep 'define NXT_VERSION' \ - src/nxt_main.h | sed -e 's/^.*"\(.*\)".*/\1/') - ${NXT_NODE}: ${NXT_NODE}-copy $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC ${NXT_NODE_EXPORTS} && \\ cd ${NXT_NODE_TMP} && ${NXT_NODE_GYP} configure build clean @@ -156,14 +153,12 @@ ${NXT_NODE}-copy: ${NXT_NODE_VERSION_FILE} mkdir -p ${NXT_BUILD_DIR}/src/ cp -rp src/nodejs/* ${NXT_BUILD_DIR}/src/${NXT_NODE} -${NXT_NODE_VERSION_FILE}: src/nxt_main.h +${NXT_NODE_VERSION_FILE}: ${NXT_VERSION_H} mkdir -p ${NXT_NODE_TMP} - $echo -n '#define NXT_NODE_VERNUM ' > $NXT_NODE_VERSION_FILE - grep 'define NXT_VERNUM' src/nxt_main.h \\ - | sed -e 's/[^0-9]//g' >> $NXT_NODE_VERSION_FILE + $echo '#define NXT_NODE_VERNUM \$(NXT_VERNUM)' > $NXT_NODE_VERSION_FILE ${NXT_NODE_TARBALL}: ${NXT_NODE}-copy - sed -e 's/"version"\s*:.*/"version": "\$(NXT_UNIT_VERSION).0",/' \ + sed -e 's/"version"\s*:.*/"version": "\$(NXT_VERSION).0",/' \ ${NXT_NODE_TMP}/package.json > ${NXT_NODE_TMP}/package.json.tmp mv ${NXT_NODE_TMP}/package.json.tmp ${NXT_NODE_TMP}/package.json tar -zcvf ${NXT_NODE_TARBALL} -C ${NXT_NODE_TMP} . diff --git a/configure b/configure index 13f2db6b..335a8c88 100755 --- a/configure +++ b/configure @@ -25,6 +25,7 @@ NXT_AUTOTEST=$NXT_BUILD_DIR/autotest NXT_AUTOCONF_ERR=$NXT_BUILD_DIR/autoconf.err NXT_AUTOCONF_DATA=$NXT_BUILD_DIR/autoconf.data NXT_AUTO_CONFIG_H=$NXT_BUILD_DIR/nxt_auto_config.h +NXT_VERSION_H=$NXT_BUILD_DIR/nxt_version.h NXT_MAKEFILE=$NXT_BUILD_DIR/Makefile CC=${CC:-cc} diff --git a/pkg/Makefile b/pkg/Makefile index 6001a034..7926606d 100644 --- a/pkg/Makefile +++ b/pkg/Makefile @@ -1,8 +1,8 @@ #!/usr/bin/make -VERSION ?= $(shell grep 'define NXT_VERSION' ../src/nxt_main.h \ - | sed -e 's/^.*"\(.*\)".*/\1/') +include ../version +VERSION ?= $(NXT_VERSION) RELEASE ?= 1 default: diff --git a/pkg/deb/Makefile b/pkg/deb/Makefile index 1a16b6ee..29792f90 100644 --- a/pkg/deb/Makefile +++ b/pkg/deb/Makefile @@ -1,11 +1,10 @@ #!/usr/bin/make -DEFAULT_VERSION := $(shell grep 'define NXT_VERSION' ../../src/nxt_main.h \ - | sed -e 's/^.*"\(.*\)".*/\1/') +include ../../version DEFAULT_RELEASE := 1 -VERSION ?= $(DEFAULT_VERSION) +VERSION ?= $(NXT_VERSION) RELEASE ?= $(DEFAULT_RELEASE) SRCDIR= unit-$(VERSION) diff --git a/pkg/docker/Makefile b/pkg/docker/Makefile index 140ac5b3..cf6de78d 100644 --- a/pkg/docker/Makefile +++ b/pkg/docker/Makefile @@ -1,10 +1,10 @@ #!/usr/bin/make -DEFAULT_VERSION := $(shell grep 'define NXT_VERSION' ../../src/nxt_main.h \ - | sed -e 's/^.*"\(.*\)".*/\1/') +include ../../version + DEFAULT_RELEASE := 1 -VERSION ?= $(DEFAULT_VERSION) +VERSION ?= $(NXT_VERSION) RELEASE ?= $(DEFAULT_RELEASE) CODENAME := stretch diff --git a/pkg/npm/Makefile b/pkg/npm/Makefile index dfa9ccdc..8636dd0b 100644 --- a/pkg/npm/Makefile +++ b/pkg/npm/Makefile @@ -1,13 +1,9 @@ #!/usr/bin/make -DEFAULT_VERSION := $(shell grep 'define NXT_VERSION' ../../src/nxt_main.h \ - | sed -e 's/^.*"\(.*\)".*/\1/') +include ../../version -DEFAULT_VERNUM := $(shell grep 'define NXT_VERNUM' ../../src/nxt_main.h \ - | sed -e 's/[^0-9]//g') - -VERSION ?= $(DEFAULT_VERSION) -VERNUM ?= $(DEFAULT_VERNUM) +VERSION ?= $(NXT_VERSION) +VERNUM ?= $(NXT_VERNUM) NPM ?= npm default: diff --git a/pkg/rpm/Makefile b/pkg/rpm/Makefile index fdc31302..2157e808 100644 --- a/pkg/rpm/Makefile +++ b/pkg/rpm/Makefile @@ -1,11 +1,10 @@ #!/usr/bin/make -DEFAULT_VERSION := $(shell grep 'define NXT_VERSION' ../../src/nxt_main.h \ - | sed -e 's/^.*"\(.*\)".*/\1/') +include ../../version DEFAULT_RELEASE := 1 -VERSION ?= $(DEFAULT_VERSION) +VERSION ?= $(NXT_VERSION) RELEASE ?= $(DEFAULT_RELEASE) ifeq ($(shell rpm --eval "%{?rhel}"), 6) diff --git a/src/nodejs/unit-http/unit.h b/src/nodejs/unit-http/unit.h index 8baeb967..db85e85c 100644 --- a/src/nodejs/unit-http/unit.h +++ b/src/nodejs/unit-http/unit.h @@ -15,7 +15,7 @@ extern "C" { #include "version.h" #include -#if NXT_UNIT_VERNUM != NXT_NODE_VERNUM +#if NXT_VERNUM != NXT_NODE_VERNUM #error "libunit version mismatch." #endif diff --git a/src/nxt_main.h b/src/nxt_main.h index dc821e07..23c55002 100644 --- a/src/nxt_main.h +++ b/src/nxt_main.h @@ -9,10 +9,7 @@ #include - - -#define NXT_VERSION "1.8" -#define NXT_VERNUM 10800 +#include #define NXT_SERVER "Unit/" NXT_VERSION diff --git a/src/nxt_unit.h b/src/nxt_unit.h index a3fcc541..532de20d 100644 --- a/src/nxt_unit.h +++ b/src/nxt_unit.h @@ -11,7 +11,7 @@ #include #include -#include "nxt_unit_version.h" +#include "nxt_version.h" #include "nxt_unit_typedefs.h" diff --git a/version b/version new file mode 100644 index 00000000..36e0b1b3 --- /dev/null +++ b/version @@ -0,0 +1,5 @@ + +# Copyright (C) NGINX, Inc. + +NXT_VERSION=1.8 +NXT_VERNUM=10800 -- cgit From c5563d142dd189050aee08a00e3079e3e7ff14a7 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Sat, 23 Feb 2019 14:48:33 +0300 Subject: Removed surplus check for NUL in nxt_http_validate_host(). Such header fields are already rejected by HTTP parser. --- src/nxt_http_request.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index 6029c5c0..b7d46c72 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -139,7 +139,6 @@ nxt_http_validate_host(nxt_str_t *host, nxt_mp_t *mp) break; case '/': - case '\0': return NXT_HTTP_BAD_REQUEST; default: -- cgit From d8c0a0dbe25c2467b6b8ba866cfd67613ff2014a Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Mon, 25 Feb 2019 15:10:44 +0300 Subject: Disabled useless code in nxt_router_listen_socket_error(). It doesn't do anything useful, among creating a JSON message and logging it to debug log. Besides that it causes segmentation fault if the RPC handler is triggered with an empty buffer due to exiting of the main process. --- src/nxt_router.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/nxt_router.c b/src/nxt_router.c index 8b93e15d..5621abec 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -1982,13 +1982,18 @@ static void nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data) { + nxt_socket_rpc_t *rpc; + nxt_router_temp_conf_t *tmcf; + + rpc = data; + tmcf = rpc->temp_conf; + +#if 0 u_char *p; size_t size; uint8_t error; nxt_buf_t *in, *out; nxt_sockaddr_t *sa; - nxt_socket_rpc_t *rpc; - nxt_router_temp_conf_t *tmcf; static nxt_str_t socket_errors[] = { nxt_string("ListenerSystem"), @@ -2000,9 +2005,7 @@ nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, nxt_string("ListenerPath"), }; - rpc = data; sa = rpc->socket_conf->listen->sockaddr; - tmcf = rpc->temp_conf; in = nxt_buf_chk_make_plain(tmcf->mem_pool, msg->buf, msg->size); @@ -2030,6 +2033,7 @@ nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, &socket_errors[error], in->mem.free - p, p); nxt_debug(task, "%*s", out->mem.free - out->mem.pos, out->mem.pos); +#endif nxt_router_conf_error(task, tmcf); } -- cgit From bd1eb7b502e3c9b3c3854fafd6ad360eca11c50a Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Mon, 25 Feb 2019 18:09:37 +0300 Subject: RPC error message size fixed. In case of RPC error, special error message passed to handler. Field 'size' expected to be 0 in this case because in contains fake empty buffer. --- src/nxt_port_rpc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nxt_port_rpc.c b/src/nxt_port_rpc.c index 401707f0..77e8af45 100644 --- a/src/nxt_port_rpc.c +++ b/src/nxt_port_rpc.c @@ -489,7 +489,7 @@ nxt_port_rpc_close(nxt_task_t *task, nxt_port_t *port) msg.port_msg.nf = 0; msg.port_msg.mf = 0; msg.port_msg.tracking = 0; - msg.size = sizeof(msg.port_msg); + msg.size = 0; msg.cancelled = 0; msg.u.data = NULL; -- cgit From 79e3185f3318654285766d849c0c06d713517251 Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Tue, 26 Feb 2019 14:39:08 +0300 Subject: Packages: fixed packages build, broken in 00d8049418cf. --- pkg/deb/debian.module/rules.in | 2 ++ pkg/deb/debian/rules.in | 2 ++ pkg/rpm/Makefile | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/deb/debian.module/rules.in b/pkg/deb/debian.module/rules.in index 22e381fe..e41b05d4 100755 --- a/pkg/deb/debian.module/rules.in +++ b/pkg/deb/debian.module/rules.in @@ -22,6 +22,7 @@ config.env.%: cp -Pa $(CURDIR)/configure $(BUILDDIR_$*)/ cp -Pa $(CURDIR)/src $(BUILDDIR_$*)/ cp -Pa $(CURDIR)/test $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/version $(BUILDDIR_$*)/ touch $@ configure.unit: config.env.unit @@ -65,6 +66,7 @@ clean: dh_testroot dh_clean find $(CURDIR) -maxdepth 1 -size 0 -delete + rm -rf $(BUILDDIR_unit) $(BUILDDIR_unit_debug) install: build dh_testdir diff --git a/pkg/deb/debian/rules.in b/pkg/deb/debian/rules.in index 7eab391d..a8c4c948 100644 --- a/pkg/deb/debian/rules.in +++ b/pkg/deb/debian/rules.in @@ -26,6 +26,7 @@ config.env.%: cp -Pa $(CURDIR)/configure $(BUILDDIR_$*)/ cp -Pa $(CURDIR)/src $(BUILDDIR_$*)/ cp -Pa $(CURDIR)/test $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/version $(BUILDDIR_$*)/ touch $@ configure.unit: config.env.unit @@ -83,6 +84,7 @@ clean: dh_testroot dh_clean find $(CURDIR) -maxdepth 1 -size 0 -delete + rm -rf $(BUILDDIR_unit) $(BUILDDIR_unit_debug) install: build do.tests dh_testdir diff --git a/pkg/rpm/Makefile b/pkg/rpm/Makefile index 2157e808..56cf31b5 100644 --- a/pkg/rpm/Makefile +++ b/pkg/rpm/Makefile @@ -170,7 +170,7 @@ endif rpmbuild/SOURCES/unit-$(VERSION).tar.gz: cd ../.. && tar -czf pkg/rpm/rpmbuild/SOURCES/unit-$(VERSION).tar.gz \ --transform "s#^#unit-$(VERSION)/#" \ - LICENSE NOTICE CHANGES README configure auto src test + LICENSE NOTICE CHANGES README configure auto src test version unit: check-build-depends-unit rpmbuild/SPECS/unit.spec rpmbuild/SOURCES/unit-$(VERSION).tar.gz @echo "===> Building $@ package" ; \ -- cgit From aa7478267aa53667a29a021ddc0d425cf877b89e Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Tue, 26 Feb 2019 14:53:28 +0300 Subject: Packages: yet another fix added, missed in a7af31b69692. --- pkg/deb/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/deb/Makefile b/pkg/deb/Makefile index 29792f90..ffd9aa2e 100644 --- a/pkg/deb/Makefile +++ b/pkg/deb/Makefile @@ -170,7 +170,7 @@ endif debuild/unit_$(VERSION).orig.tar.gz: | debuild/$(SRCDIR)/debian cd ../.. && tar -czf pkg/deb/debuild/$(SRCDIR).tar.gz \ --transform "s#^#$(SRCDIR)/#" \ - LICENSE NOTICE CHANGES README configure auto src test + LICENSE NOTICE CHANGES README configure auto src test version mv debuild/$(SRCDIR).tar.gz debuild/unit_$(VERSION).orig.tar.gz cd debuild && tar zxf unit_$(VERSION).orig.tar.gz -- cgit From ce6ce15c2042421588cd7f0635744239caf1cb31 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Tue, 26 Feb 2019 17:42:20 +0300 Subject: Fixed violation of the strict aliasing rules in 5d0edd35c4ce. In order to reduce number of operations over rb-tree and process them in batches simultaneously, all the timers changes are temporary stored in array. While processing of these changes, the same memory is also used for storing pointers to postpone timers adding. As the same block of memory has been referenced by two different types of pointers (nxt_timer_change_t * and nxt_timer_t **), some compilers may reorder operations with these pointers and produce broken code. See ticket #221 on GitHub for a particular case. Now the same "nxt_timer_change_t" structure is used in both cases. Also, reverted the -fno-strict-aliasing flag, which has been introduced in ef76227ec159 as a workaround for this issue. --- pkg/rpm/unit.module.spec.in | 4 ---- pkg/rpm/unit.spec.in | 5 ----- src/nxt_timer.c | 11 ++++++----- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/pkg/rpm/unit.module.spec.in b/pkg/rpm/unit.module.spec.in index d06db231..8b8a3433 100644 --- a/pkg/rpm/unit.module.spec.in +++ b/pkg/rpm/unit.module.spec.in @@ -10,11 +10,7 @@ %define unit_version %%UNIT_VERSION%% %define unit_release %%UNIT_RELEASE%%%{?dist}.ngx -%if (0%{?rhel} == 6) && (%{_arch} == x86_64) -%define CC_OPT %{optflags} -fno-strict-aliasing -%else %define CC_OPT %{optflags} -%endif %define CONFIGURE_ARGS $(echo "%%CONFIGURE_ARGS%%") diff --git a/pkg/rpm/unit.spec.in b/pkg/rpm/unit.spec.in index 2d5c1bd1..05ee79af 100644 --- a/pkg/rpm/unit.spec.in +++ b/pkg/rpm/unit.spec.in @@ -29,12 +29,7 @@ BuildRequires: openssl-devel BuildRequires: libopenssl-devel %endif -%if (0%{?rhel} == 6) && (%{_arch} == x86_64) -%define CC_OPT %{optflags} -fno-strict-aliasing -fPIC -%else %define CC_OPT %{optflags} -fPIC -%endif - %define LD_OPT -Wl,-z,relro -Wl,-z,now -pie %define CONFIGURE_ARGS $(echo "%%CONFIGURE_ARGS%%") diff --git a/src/nxt_timer.c b/src/nxt_timer.c index cba4755b..cb94b77c 100644 --- a/src/nxt_timer.c +++ b/src/nxt_timer.c @@ -159,9 +159,9 @@ nxt_timer_change(nxt_event_engine_t *engine, nxt_timer_t *timer, static void nxt_timer_changes_commit(nxt_event_engine_t *engine) { - nxt_timer_t *timer, **add, **add_end; + nxt_timer_t *timer; nxt_timers_t *timers; - nxt_timer_change_t *ch, *end; + nxt_timer_change_t *ch, *end, *add, *add_end; timers = &engine->timers; @@ -170,7 +170,7 @@ nxt_timer_changes_commit(nxt_event_engine_t *engine) ch = timers->changes; end = ch + timers->nchanges; - add = (nxt_timer_t **) ch; + add = ch; add_end = add; while (ch < end) { @@ -185,7 +185,8 @@ nxt_timer_changes_commit(nxt_event_engine_t *engine) timer->time = ch->time; - *add_end++ = timer; + add_end->timer = timer; + add_end++; if (!nxt_timer_is_in_tree(timer)) { break; @@ -209,7 +210,7 @@ nxt_timer_changes_commit(nxt_event_engine_t *engine) } while (add < add_end) { - timer = *add; + timer = add->timer; nxt_debug(timer->task, "timer rbtree insert: %M±%d", timer->time, timer->bias); -- cgit From 444b9ffea90c3d3f8c06421a03f027c889b4a1e2 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Tue, 26 Feb 2019 19:12:16 +0300 Subject: Keepalive mode is disabled on HTTP header parsing errors. --- src/nxt_h1proto.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c index 4e1b22e9..b5b5d6e6 100644 --- a/src/nxt_h1proto.c +++ b/src/nxt_h1proto.c @@ -563,6 +563,8 @@ nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj, void *data) error: + h1p->keepalive = 0; + nxt_http_request_error(task, r, status); } -- cgit From 547082171e5e78561964e0e64e16a47a5f9d92df Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Tue, 26 Feb 2019 19:48:38 +0300 Subject: Introduced nxt_conf_array_elements_count(). --- src/nxt_conf.c | 7 +++++++ src/nxt_conf.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/nxt_conf.c b/src/nxt_conf.c index 2255e12f..f69133cc 100644 --- a/src/nxt_conf.c +++ b/src/nxt_conf.c @@ -360,6 +360,13 @@ nxt_conf_set_element_string_dup(nxt_conf_value_t *array, nxt_mp_t *mp, } +nxt_uint_t +nxt_conf_array_elements_count(nxt_conf_value_t *value) +{ + return value->u.array->count; +} + + nxt_uint_t nxt_conf_type(nxt_conf_value_t *value) { diff --git a/src/nxt_conf.h b/src/nxt_conf.h index 48521917..7c360dfe 100644 --- a/src/nxt_conf.h +++ b/src/nxt_conf.h @@ -125,6 +125,7 @@ void nxt_conf_set_element(nxt_conf_value_t *array, nxt_uint_t index, nxt_conf_value_t *value); nxt_int_t nxt_conf_set_element_string_dup(nxt_conf_value_t *array, nxt_mp_t *mp, nxt_uint_t index, nxt_str_t *value); +nxt_uint_t nxt_conf_array_elements_count(nxt_conf_value_t *value); #endif /* _NXT_CONF_INCLUDED_ */ -- cgit From ce650ea0f703f9228638e88ccb62d207aa647bf2 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Tue, 26 Feb 2019 19:48:41 +0300 Subject: Introduced nxt_conf_array_qsort(). --- src/nxt_conf.c | 16 ++++++++++++++++ src/nxt_conf.h | 2 ++ 2 files changed, 18 insertions(+) diff --git a/src/nxt_conf.c b/src/nxt_conf.c index f69133cc..4c6d8839 100644 --- a/src/nxt_conf.c +++ b/src/nxt_conf.c @@ -720,6 +720,22 @@ nxt_conf_get_array_element(nxt_conf_value_t *value, uint32_t index) } +void +nxt_conf_array_qsort(nxt_conf_value_t *value, + int (*compare)(const void *, const void *)) +{ + nxt_conf_array_t *array; + + if (value->type != NXT_CONF_VALUE_ARRAY) { + return; + } + + array = value->u.array; + + nxt_qsort(array->elements, array->count, sizeof(nxt_conf_value_t), compare); +} + + nxt_int_t nxt_conf_op_compile(nxt_mp_t *mp, nxt_conf_op_t **ops, nxt_conf_value_t *root, nxt_str_t *path, nxt_conf_value_t *value) diff --git a/src/nxt_conf.h b/src/nxt_conf.h index 7c360dfe..052334fe 100644 --- a/src/nxt_conf.h +++ b/src/nxt_conf.h @@ -126,6 +126,8 @@ void nxt_conf_set_element(nxt_conf_value_t *array, nxt_uint_t index, nxt_int_t nxt_conf_set_element_string_dup(nxt_conf_value_t *array, nxt_mp_t *mp, nxt_uint_t index, nxt_str_t *value); nxt_uint_t nxt_conf_array_elements_count(nxt_conf_value_t *value); +void nxt_conf_array_qsort(nxt_conf_value_t *value, + int (*compare)(const void *, const void *)); #endif /* _NXT_CONF_INCLUDED_ */ -- cgit From 95c9bba33bcb1d0e8e9e7c64d2591187ce11bab1 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Tue, 26 Feb 2019 19:48:44 +0300 Subject: Introduced nxt_memcpy_upcase(). --- src/nxt_string.c | 13 +++++++++++++ src/nxt_string.h | 2 ++ 2 files changed, 15 insertions(+) diff --git a/src/nxt_string.c b/src/nxt_string.c index 1858b58b..7d8c1ce3 100644 --- a/src/nxt_string.c +++ b/src/nxt_string.c @@ -96,6 +96,19 @@ nxt_memcpy_lowcase(u_char *dst, const u_char *src, size_t length) } +void +nxt_memcpy_upcase(u_char *dst, const u_char *src, size_t length) +{ + u_char c; + + while (length != 0) { + c = *src++; + *dst++ = nxt_upcase(c); + length--; + } +} + + u_char * nxt_cpystrn(u_char *dst, const u_char *src, size_t length) { diff --git a/src/nxt_string.h b/src/nxt_string.h index 5f82cca8..22a63a17 100644 --- a/src/nxt_string.h +++ b/src/nxt_string.h @@ -43,6 +43,8 @@ nxt_memcpy(dst, src, length) \ NXT_EXPORT void nxt_memcpy_lowcase(u_char *dst, const u_char *src, size_t length); +NXT_EXPORT void nxt_memcpy_upcase(u_char *dst, const u_char *src, + size_t length); /* -- cgit From d4ccaae900f78b13923a9bd9ee7bbaa33c99b18b Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Wed, 27 Feb 2019 16:41:11 +0300 Subject: Initial routing implementation. --- auto/sources | 1 + src/nxt_http.h | 32 +- src/nxt_http_request.c | 53 ++- src/nxt_http_route.c | 849 +++++++++++++++++++++++++++++++++++++++++++++++++ src/nxt_router.c | 74 +++-- src/nxt_router.h | 10 +- 6 files changed, 979 insertions(+), 40 deletions(-) create mode 100644 src/nxt_http_route.c diff --git a/auto/sources b/auto/sources index bc979fe2..4c4fd742 100644 --- a/auto/sources +++ b/auto/sources @@ -82,6 +82,7 @@ NXT_LIB_SRCS=" \ src/nxt_http_request.c \ src/nxt_http_response.c \ src/nxt_http_error.c \ + src/nxt_http_route.c \ src/nxt_application.c \ src/nxt_external.c \ src/nxt_port_hash.c \ diff --git a/src/nxt_http.h b/src/nxt_http.h index af0b2cd6..23c406d3 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -21,6 +21,7 @@ typedef enum { NXT_HTTP_NOT_MODIFIED = 304, NXT_HTTP_BAD_REQUEST = 400, + NXT_HTTP_NOT_FOUND = 404, NXT_HTTP_REQUEST_TIMEOUT = 408, NXT_HTTP_LENGTH_REQUIRED = 411, NXT_HTTP_PAYLOAD_TOO_LARGE = 413, @@ -112,6 +113,7 @@ struct nxt_http_request_s { nxt_buf_t *out; const nxt_http_request_state_t *state; + nxt_str_t host; nxt_str_t target; nxt_str_t version; nxt_str_t *method; @@ -124,7 +126,6 @@ struct nxt_http_request_s { nxt_http_field_t *cookie; nxt_http_field_t *referer; nxt_http_field_t *user_agent; - nxt_str_t host; nxt_off_t content_length_n; nxt_sockaddr_t *remote; @@ -136,6 +137,7 @@ struct nxt_http_request_s { nxt_http_status_t status:16; + uint8_t pass_count; /* 8 bits */ uint8_t protocol; /* 2 bits */ uint8_t logged; /* 1 bit */ uint8_t header_sent; /* 1 bit */ @@ -143,6 +145,22 @@ struct nxt_http_request_s { }; +typedef struct nxt_http_route_s nxt_http_route_t; + + +struct nxt_http_pass_s { + nxt_http_pass_t *(*handler)(nxt_task_t *task, + nxt_http_request_t *r, + nxt_http_pass_t *pass); + union { + nxt_http_route_t *route; + nxt_app_t *application; + } u; + + nxt_str_t name; +}; + + typedef void (*nxt_http_proto_body_read_t)(nxt_task_t *task, nxt_http_request_t *r); typedef void (*nxt_http_proto_local_addr_t)(nxt_task_t *task, @@ -184,6 +202,18 @@ nxt_int_t nxt_http_request_field(void *ctx, nxt_http_field_t *field, nxt_int_t nxt_http_request_content_length(void *ctx, nxt_http_field_t *field, uintptr_t data); +nxt_http_routes_t *nxt_http_routes_create(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *routes_conf); +nxt_http_pass_t *nxt_http_pass_create(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_str_t *name); +void nxt_http_routes_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf); +nxt_http_pass_t *nxt_http_pass_application(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_str_t *name); +void nxt_http_routes_cleanup(nxt_task_t *task, nxt_http_routes_t *routes); +void nxt_http_pass_cleanup(nxt_task_t *task, nxt_http_pass_t *pass); + +nxt_http_pass_t *nxt_http_request_application(nxt_task_t *task, + nxt_http_request_t *r, nxt_http_pass_t *pass); extern nxt_time_string_t nxt_http_date_cache; diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index b7d46c72..f6c14df9 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -10,7 +10,7 @@ static nxt_int_t nxt_http_validate_host(nxt_str_t *host, nxt_mp_t *mp); static void nxt_http_request_start(nxt_task_t *task, void *obj, void *data); -static void nxt_http_app_request(nxt_task_t *task, void *obj, void *data); +static void nxt_http_request_pass(nxt_task_t *task, void *obj, void *data); static void nxt_http_request_mem_buf_completion(nxt_task_t *task, void *obj, void *data); static void nxt_http_request_done(nxt_task_t *task, void *obj, void *data); @@ -278,25 +278,60 @@ nxt_http_request_start(nxt_task_t *task, void *obj, void *data) static const nxt_http_request_state_t nxt_http_request_body_state nxt_aligned(64) = { - .ready_handler = nxt_http_app_request, + .ready_handler = nxt_http_request_pass, .error_handler = nxt_http_request_close_handler, }; static void -nxt_http_app_request(nxt_task_t *task, void *obj, void *data) +nxt_http_request_pass(nxt_task_t *task, void *obj, void *data) +{ + nxt_http_pass_t *pass; + nxt_http_request_t *r; + + r = obj; + + pass = r->conf->socket_conf->pass; + + if (nxt_slow_path(pass == NULL)) { + goto fail; + } + + for ( ;; ) { + nxt_debug(task, "http request route: %V", &pass->name); + + pass = pass->handler(task, r, pass); + if (pass == NULL) { + break; + } + + if (nxt_slow_path(r->pass_count++ == 255)) { + goto fail; + } + } + + return; + +fail: + + nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); +} + + +nxt_http_pass_t * +nxt_http_request_application(nxt_task_t *task, nxt_http_request_t *r, + nxt_http_pass_t *pass) { nxt_int_t ret; nxt_event_engine_t *engine; - nxt_http_request_t *r; nxt_app_parse_ctx_t *ar; - r = obj; + nxt_debug(task, "http request application"); ar = nxt_mp_zget(r->mem_pool, sizeof(nxt_app_parse_ctx_t)); if (nxt_slow_path(ar == NULL)) { nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); - return; + return NULL; } ar->request = r; @@ -370,10 +405,12 @@ nxt_http_app_request(nxt_task_t *task, void *obj, void *data) ret = nxt_http_parse_request_init(&ar->resp_parser, r->mem_pool); if (nxt_slow_path(ret != NXT_OK)) { nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); - return; + return NULL; } - nxt_router_process_http_request(task, ar); + nxt_router_process_http_request(task, ar, pass->u.application); + + return NULL; } diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c new file mode 100644 index 00000000..133c39ab --- /dev/null +++ b/src/nxt_http_route.c @@ -0,0 +1,849 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include + + +typedef enum { + NXT_HTTP_ROUTE_STRING = 0, + NXT_HTTP_ROUTE_STRING_PTR, + NXT_HTTP_ROUTE_FIELD, + NXT_HTTP_ROUTE_HEADER, + NXT_HTTP_ROUTE_ARGUMENT, + NXT_HTTP_ROUTE_COOKIE, +} nxt_http_route_object_t; + + +typedef enum { + NXT_HTTP_ROUTE_PATTERN_EXACT = 0, + NXT_HTTP_ROUTE_PATTERN_BEGIN, + NXT_HTTP_ROUTE_PATTERN_END, + NXT_HTTP_ROUTE_PATTERN_SUBSTRING, +} nxt_http_route_pattern_type_t; + + +typedef enum { + NXT_HTTP_ROUTE_PATTERN_NOCASE = 0, + NXT_HTTP_ROUTE_PATTERN_LOWCASE, + NXT_HTTP_ROUTE_PATTERN_UPCASE, +} nxt_http_route_pattern_case_t; + + +typedef struct { + nxt_conf_value_t *host; + nxt_conf_value_t *uri; + nxt_conf_value_t *method; +} nxt_http_route_match_conf_t; + + +typedef struct { + nxt_str_t test; + uint32_t min_length; + + nxt_http_route_pattern_type_t type:8; + uint8_t case_sensitive; /* 1 bit */ + uint8_t negative; /* 1 bit */ + uint8_t any; /* 1 bit */ +} nxt_http_route_pattern_t; + + +typedef struct { + uintptr_t offset; + uint32_t items; + nxt_http_route_object_t object:8; + nxt_http_route_pattern_t pattern[0]; +} nxt_http_route_rule_t; + + +typedef struct { + uint32_t items; + nxt_http_pass_t pass; + nxt_http_route_rule_t *rule[0]; +} nxt_http_route_match_t; + + +struct nxt_http_route_s { + nxt_str_t name; + uint32_t items; + nxt_http_route_match_t *match[0]; +}; + + +struct nxt_http_routes_s { + uint32_t items; + nxt_http_route_t *route[0]; +}; + + +static nxt_http_route_t *nxt_http_route_create(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv); +static nxt_http_route_match_t *nxt_http_route_match_create(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv); +static nxt_http_route_rule_t *nxt_http_route_rule_create(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, + nxt_bool_t case_sensitive, nxt_http_route_pattern_case_t pattern_case); +static int nxt_http_pattern_compare(const void *one, const void *two); +static nxt_int_t nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, + nxt_conf_value_t *cv, nxt_http_route_pattern_t *pattern, + nxt_http_route_pattern_case_t pattern_case); + +static void nxt_http_route_resolve(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_http_route_t *route); +static void nxt_http_pass_resolve(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_http_pass_t *pass); +static nxt_http_route_t *nxt_http_route_find(nxt_http_routes_t *routes, + nxt_str_t *name); +static void nxt_http_route_cleanup(nxt_task_t *task, nxt_http_route_t *routes); + +static nxt_http_pass_t *nxt_http_route_pass(nxt_task_t *task, + nxt_http_request_t *r, nxt_http_pass_t *start); +static nxt_http_pass_t *nxt_http_route_match(nxt_http_request_t *r, + nxt_http_route_match_t *match); +static nxt_bool_t nxt_http_route_rule(nxt_http_request_t *r, + nxt_http_route_rule_t *rule); +static nxt_bool_t nxt_http_route_pattern(nxt_http_request_t *r, + nxt_http_route_pattern_t *pattern, u_char *start, size_t length); + + +nxt_http_routes_t * +nxt_http_routes_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_conf_value_t *routes_conf) +{ + size_t size; + uint32_t i, n, next; + nxt_mp_t *mp; + nxt_str_t name, *string; + nxt_bool_t object; + nxt_conf_value_t *route_conf; + nxt_http_route_t *route; + nxt_http_routes_t *routes; + + object = (nxt_conf_type(routes_conf) == NXT_CONF_OBJECT); + n = object ? nxt_conf_object_members_count(routes_conf) : 1; + size = sizeof(nxt_http_routes_t) + n * sizeof(nxt_http_route_t *); + + mp = tmcf->router_conf->mem_pool; + + routes = nxt_mp_alloc(mp, size); + if (nxt_slow_path(routes == NULL)) { + return NULL; + } + + routes->items = n; + + if (object) { + next = 0; + + for (i = 0; i < n; i++) { + route_conf = nxt_conf_next_object_member(routes_conf, &name, &next); + + route = nxt_http_route_create(task, tmcf, route_conf); + if (nxt_slow_path(route == NULL)) { + return NULL; + } + + routes->route[i] = route; + + string = nxt_str_dup(mp, &route->name, &name); + if (nxt_slow_path(string == NULL)) { + return NULL; + } + } + + } else { + route = nxt_http_route_create(task, tmcf, routes_conf); + if (nxt_slow_path(route == NULL)) { + return NULL; + } + + routes->route[0] = route; + + route->name.length = 0; + route->name.start = NULL; + } + + return routes; +} + + +static nxt_conf_map_t nxt_http_route_match_conf[] = { + { + nxt_string("host"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_match_conf_t, host), + }, + + { + nxt_string("uri"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_match_conf_t, uri), + }, + + { + nxt_string("method"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_match_conf_t, method), + }, +}; + + +static nxt_http_route_t * +nxt_http_route_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_conf_value_t *cv) +{ + size_t size; + uint32_t i, n; + nxt_conf_value_t *value; + nxt_http_route_t *route; + nxt_http_route_match_t *match, **m; + + n = nxt_conf_array_elements_count(cv); + size = sizeof(nxt_http_route_t) + n * sizeof(nxt_http_route_match_t *); + + route = nxt_mp_alloc(tmcf->router_conf->mem_pool, size); + if (nxt_slow_path(route == NULL)) { + return NULL; + } + + route->items = n; + m = &route->match[0]; + + for (i = 0; i < n; i++) { + value = nxt_conf_get_array_element(cv, i); + + match = nxt_http_route_match_create(task, tmcf, value); + if (match == NULL) { + return NULL; + } + + *m++ = match; + } + + return route; +} + + +static nxt_http_route_match_t * +nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_conf_value_t *cv) +{ + size_t size; + uint32_t n; + nxt_int_t ret; + nxt_str_t pass, *string; + nxt_conf_value_t *match_conf, *pass_conf; + nxt_http_route_rule_t *rule, **p; + nxt_http_route_match_t *match; + nxt_http_route_match_conf_t mtcf; + + static nxt_str_t pass_path = nxt_string("/action/pass"); + static nxt_str_t match_path = nxt_string("/match"); + + pass_conf = nxt_conf_get_path(cv, &pass_path); + if (nxt_slow_path(pass_conf == NULL)) { + return NULL; + } + + nxt_conf_get_string(pass_conf, &pass); + + match_conf = nxt_conf_get_path(cv, &match_path); + + n = (match_conf != NULL) ? nxt_conf_object_members_count(match_conf) : 0; + size = sizeof(nxt_http_route_match_t) + n * sizeof(nxt_http_route_rule_t *); + + match = nxt_mp_alloc(tmcf->router_conf->mem_pool, size); + if (nxt_slow_path(match == NULL)) { + return NULL; + } + + match->pass.u.route = NULL; + match->pass.handler = NULL; + match->items = n; + + string = nxt_str_dup(tmcf->router_conf->mem_pool, &match->pass.name, &pass); + if (nxt_slow_path(string == NULL)) { + return NULL; + } + + if (n == 0) { + return match; + } + + nxt_memzero(&mtcf, sizeof(mtcf)); + + ret = nxt_conf_map_object(tmcf->mem_pool, + match_conf, nxt_http_route_match_conf, + nxt_nitems(nxt_http_route_match_conf), &mtcf); + if (ret != NXT_OK) { + return NULL; + } + + p = &match->rule[0]; + + if (mtcf.host != NULL) { + rule = nxt_http_route_rule_create(task, tmcf, mtcf.host, 1, + NXT_HTTP_ROUTE_PATTERN_LOWCASE); + if (rule == NULL) { + return NULL; + } + + rule->offset = offsetof(nxt_http_request_t, host); + rule->object = NXT_HTTP_ROUTE_STRING; + *p++ = rule; + } + + if (mtcf.uri != NULL) { + rule = nxt_http_route_rule_create(task, tmcf, mtcf.uri, 1, + NXT_HTTP_ROUTE_PATTERN_NOCASE); + if (rule == NULL) { + return NULL; + } + + rule->offset = offsetof(nxt_http_request_t, path); + rule->object = NXT_HTTP_ROUTE_STRING_PTR; + *p++ = rule; + } + + if (mtcf.method != NULL) { + rule = nxt_http_route_rule_create(task, tmcf, mtcf.method, 1, + NXT_HTTP_ROUTE_PATTERN_UPCASE); + if (rule == NULL) { + return NULL; + } + + rule->offset = offsetof(nxt_http_request_t, method); + rule->object = NXT_HTTP_ROUTE_STRING_PTR; + *p++ = rule; + } + + return match; +} + + +static nxt_http_route_rule_t * +nxt_http_route_rule_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_conf_value_t *cv, nxt_bool_t case_sensitive, + nxt_http_route_pattern_case_t pattern_case) +{ + size_t size; + uint32_t i, n; + nxt_mp_t *mp; + nxt_int_t ret; + nxt_bool_t string; + nxt_conf_value_t *value; + nxt_http_route_rule_t *rule; + nxt_http_route_pattern_t *pattern; + + mp = tmcf->router_conf->mem_pool; + + string = (nxt_conf_type(cv) != NXT_CONF_ARRAY); + n = string ? 1 : nxt_conf_array_elements_count(cv); + size = sizeof(nxt_http_route_rule_t) + n * sizeof(nxt_http_route_pattern_t); + + rule = nxt_mp_alloc(mp, size); + if (nxt_slow_path(rule == NULL)) { + return NULL; + } + + rule->items = n; + + pattern = &rule->pattern[0]; + + if (string) { + pattern[0].case_sensitive = case_sensitive; + ret = nxt_http_route_pattern_create(task, mp, cv, &pattern[0], + pattern_case); + if (nxt_slow_path(ret != NXT_OK)) { + return NULL; + } + + return rule; + } + + nxt_conf_array_qsort(cv, nxt_http_pattern_compare); + + for (i = 0; i < n; i++) { + pattern[i].case_sensitive = case_sensitive; + value = nxt_conf_get_array_element(cv, i); + + ret = nxt_http_route_pattern_create(task, mp, value, &pattern[i], + pattern_case); + if (nxt_slow_path(ret != NXT_OK)) { + return NULL; + } + } + + return rule; +} + + +static int +nxt_http_pattern_compare(const void *one, const void *two) +{ + nxt_str_t test; + nxt_bool_t negative1, negative2; + nxt_conf_value_t *value; + + value = (nxt_conf_value_t *) one; + nxt_conf_get_string(value, &test); + negative1 = (test.length != 0 && test.start[0] == '!'); + + value = (nxt_conf_value_t *) two; + nxt_conf_get_string(value, &test); + negative2 = (test.length != 0 && test.start[0] == '!'); + + return (negative2 - negative1); +} + + +static nxt_int_t +nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, + nxt_conf_value_t *cv, nxt_http_route_pattern_t *pattern, + nxt_http_route_pattern_case_t pattern_case) +{ + u_char *start; + nxt_str_t test; + nxt_http_route_pattern_type_t type; + + type = NXT_HTTP_ROUTE_PATTERN_EXACT; + + nxt_conf_get_string(cv, &test); + + pattern->negative = 0; + pattern->any = 1; + + if (test.length != 0) { + + if (test.start[0] == '!') { + test.start++; + test.length--; + + pattern->negative = 1; + pattern->any = 0; + } + + if (test.length != 0) { + + if (test.start[0] == '*') { + test.start++; + test.length--; + + if (test.length != 0) { + if (test.start[test.length - 1] == '*') { + test.length--; + type = NXT_HTTP_ROUTE_PATTERN_SUBSTRING; + + } else { + type = NXT_HTTP_ROUTE_PATTERN_END; + } + + } else { + type = NXT_HTTP_ROUTE_PATTERN_BEGIN; + } + + } else if (test.start[test.length - 1] == '*') { + test.length--; + type = NXT_HTTP_ROUTE_PATTERN_BEGIN; + } + } + } + + pattern->type = type; + pattern->min_length = test.length; + pattern->test.length = test.length; + + start = nxt_mp_nget(mp, test.length); + if (nxt_slow_path(start == NULL)) { + return NXT_ERROR; + } + + pattern->test.start = start; + + switch (pattern_case) { + + case NXT_HTTP_ROUTE_PATTERN_UPCASE: + nxt_memcpy_upcase(start, test.start, test.length); + break; + + case NXT_HTTP_ROUTE_PATTERN_LOWCASE: + nxt_memcpy_lowcase(start, test.start, test.length); + break; + + case NXT_HTTP_ROUTE_PATTERN_NOCASE: + nxt_memcpy(start, test.start, test.length); + break; + } + + return NXT_OK; +} + + +void +nxt_http_routes_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf) +{ + nxt_uint_t items; + nxt_http_route_t **route; + nxt_http_routes_t *routes; + + routes = tmcf->router_conf->routes; + if (routes != NULL) { + items = routes->items; + route = &routes->route[0]; + + while (items != 0) { + nxt_http_route_resolve(task, tmcf, *route); + + route++; + items--; + } + } +} + + +static void +nxt_http_route_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_http_route_t *route) +{ + nxt_uint_t items; + nxt_http_route_match_t **match; + + items = route->items; + match = &route->match[0]; + + while (items != 0) { + nxt_http_pass_resolve(task, tmcf, &(*match)->pass); + + match++; + items--; + } +} + + +static void +nxt_http_pass_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_http_pass_t *pass) +{ + nxt_str_t name; + + name = pass->name; + + if (nxt_str_start(&name, "applications/", 13)) { + name.length -= 13; + name.start += 13; + + pass->u.application = nxt_router_listener_application(tmcf, &name); + nxt_router_app_use(task, pass->u.application, 1); + + pass->handler = nxt_http_request_application; + + } else if (nxt_str_start(&name, "routes", 6)) { + + if (name.length == 6) { + name.length = 0; + name.start = NULL; + + } else if (name.start[6] == '/') { + name.length -= 7; + name.start += 7; + } + + pass->u.route = nxt_http_route_find(tmcf->router_conf->routes, &name); + + pass->handler = nxt_http_route_pass; + } +} + + +static nxt_http_route_t * +nxt_http_route_find(nxt_http_routes_t *routes, nxt_str_t *name) +{ + nxt_uint_t items; + nxt_http_route_t **route; + + items = routes->items; + route = &routes->route[0]; + + do { + if (nxt_strstr_eq(&(*route)->name, name)) { + return *route; + } + + route++; + items--; + + } while (items != 0); + + return NULL; +} + + +nxt_http_pass_t * +nxt_http_pass_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_str_t *name) +{ + nxt_http_pass_t *pass; + + pass = nxt_mp_alloc(tmcf->router_conf->mem_pool, sizeof(nxt_http_pass_t)); + if (nxt_slow_path(pass == NULL)) { + return NULL; + } + + pass->name = *name; + + nxt_http_pass_resolve(task, tmcf, pass); + + return pass; +} + + +/* COMPATIBILITY: listener application. */ + +nxt_http_pass_t * +nxt_http_pass_application(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_str_t *name) +{ + nxt_http_pass_t *pass; + + pass = nxt_mp_alloc(tmcf->router_conf->mem_pool, sizeof(nxt_http_pass_t)); + if (nxt_slow_path(pass == NULL)) { + return NULL; + } + + pass->name = *name; + + pass->u.application = nxt_router_listener_application(tmcf, name); + nxt_router_app_use(task, pass->u.application, 1); + + pass->handler = nxt_http_request_application; + + return pass; +} + + +void +nxt_http_routes_cleanup(nxt_task_t *task, nxt_http_routes_t *routes) +{ + nxt_uint_t items; + nxt_http_route_t **route; + + if (routes != NULL) { + items = routes->items; + route = &routes->route[0]; + + do { + nxt_http_route_cleanup(task, *route); + + route++; + items--; + + } while (items != 0); + } +} + + +static void +nxt_http_route_cleanup(nxt_task_t *task, nxt_http_route_t *route) +{ + nxt_uint_t items; + nxt_http_route_match_t **match; + + items = route->items; + match = &route->match[0]; + + do { + nxt_http_pass_cleanup(task, &(*match)->pass); + + match++; + items--; + + } while (items != 0); +} + + +void +nxt_http_pass_cleanup(nxt_task_t *task, nxt_http_pass_t *pass) +{ + if (pass->handler == nxt_http_request_application) { + nxt_router_app_use(task, pass->u.application, -1); + } +} + + +static nxt_http_pass_t * +nxt_http_route_pass(nxt_task_t *task, nxt_http_request_t *r, + nxt_http_pass_t *start) +{ + nxt_uint_t items; + nxt_http_pass_t *pass; + nxt_http_route_t *route; + nxt_http_route_match_t **match; + + route = start->u.route; + items = route->items; + match = &route->match[0]; + + while (items != 0) { + pass = nxt_http_route_match(r, *match); + if (pass != NULL) { + return pass; + } + + match++; + items--; + } + + nxt_http_request_error(task, r, NXT_HTTP_NOT_FOUND); + + return NULL; +} + + +static nxt_http_pass_t * +nxt_http_route_match(nxt_http_request_t *r, nxt_http_route_match_t *match) +{ + nxt_uint_t items; + nxt_http_route_rule_t **rule; + + rule = &match->rule[0]; + items = match->items; + + while (items != 0) { + if (!nxt_http_route_rule(r, *rule)) { + return NULL; + } + + rule++; + items--; + } + + return &match->pass; +} + + +static nxt_bool_t +nxt_http_route_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule) +{ + void *p, **pp; + u_char *start; + size_t length; + nxt_str_t *s; + nxt_uint_t items; + nxt_bool_t ret; + nxt_http_field_t *f; + nxt_http_route_pattern_t *pattern; + + p = nxt_pointer_to(r, rule->offset); + + if (rule->object == NXT_HTTP_ROUTE_STRING) { + s = p; + length = s->length; + start = s->start; + + } else { + pp = p; + p = *pp; + + if (p == NULL) { + return 0; + } + + switch (rule->object) { + + case NXT_HTTP_ROUTE_STRING_PTR: + s = p; + length = s->length; + start = s->start; + break; + + case NXT_HTTP_ROUTE_FIELD: + f = p; + length = f->value_length; + start = f->value; + break; + + case NXT_HTTP_ROUTE_HEADER: + return 0; + + case NXT_HTTP_ROUTE_ARGUMENT: + return 0; + + case NXT_HTTP_ROUTE_COOKIE: + return 0; + + default: + nxt_unreachable(); + return 0; + } + } + + items = rule->items; + pattern = &rule->pattern[0]; + + do { + ret = nxt_http_route_pattern(r, pattern, start, length); + + ret ^= pattern->negative; + + if (pattern->any == ret) { + return ret; + } + + pattern++; + items--; + + } while (items != 0); + + return ret; +} + + +static nxt_bool_t +nxt_http_route_pattern(nxt_http_request_t *r, nxt_http_route_pattern_t *pattern, + u_char *start, size_t length) +{ + nxt_str_t *test; + + if (length < pattern->min_length) { + return 0; + } + + test = &pattern->test; + + switch (pattern->type) { + + case NXT_HTTP_ROUTE_PATTERN_EXACT: + if (length != test->length) { + return 0; + } + + break; + + case NXT_HTTP_ROUTE_PATTERN_BEGIN: + break; + + case NXT_HTTP_ROUTE_PATTERN_END: + start += length - test->length; + break; + + case NXT_HTTP_ROUTE_PATTERN_SUBSTRING: + if (pattern->case_sensitive) { + return (nxt_memstrn(start, start + length, + (char *) test->start, test->length) + != NULL); + } + + return (nxt_memcasestrn(start, start + length, + (char *) test->start, test->length) + != NULL); + } + + if (pattern->case_sensitive) { + return (nxt_memcmp(start, test->start, test->length) == 0); + } + + return (nxt_memcasecmp(start, test->start, test->length) == 0); +} diff --git a/src/nxt_router.c b/src/nxt_router.c index 5621abec..e81a2f96 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -31,7 +31,8 @@ typedef struct { typedef struct { - nxt_str_t application; + nxt_str_t pass; + nxt_str_t application; } nxt_router_listener_conf_t; @@ -163,8 +164,6 @@ static void nxt_router_conf_send(nxt_task_t *task, static nxt_int_t nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, u_char *start, u_char *end); static nxt_app_t *nxt_router_app_find(nxt_queue_t *queue, nxt_str_t *name); -static nxt_app_t *nxt_router_listener_application(nxt_router_temp_conf_t *tmcf, - nxt_str_t *name); static void nxt_router_listen_socket_rpc_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_socket_conf_t *skcf); static void nxt_router_listen_socket_ready(nxt_task_t *task, @@ -1174,11 +1173,14 @@ nxt_router_conf_error(nxt_task_t *task, nxt_router_temp_conf_t *tmcf) nxt_queue_add(&new_socket_confs, &tmcf->pending); nxt_queue_add(&new_socket_confs, &tmcf->creating); + rtcf = tmcf->router_conf; + + nxt_http_routes_cleanup(task, rtcf->routes); + nxt_queue_each(skcf, &new_socket_confs, nxt_socket_conf_t, link) { - if (skcf->application != NULL) { - nxt_router_app_use(task, skcf->application, -1); - skcf->application = NULL; + if (skcf->pass != NULL) { + nxt_http_pass_cleanup(task, skcf->pass); } } nxt_queue_loop; @@ -1189,7 +1191,6 @@ nxt_router_conf_error(nxt_task_t *task, nxt_router_temp_conf_t *tmcf) } nxt_queue_loop; - rtcf = tmcf->router_conf; router = rtcf->router; nxt_queue_add(&router->sockets, &tmcf->keeping); @@ -1298,9 +1299,15 @@ static nxt_conf_map_t nxt_router_app_processes_conf[] = { static nxt_conf_map_t nxt_router_listener_conf[] = { + { + nxt_string("pass"), + NXT_CONF_MAP_STR_COPY, + offsetof(nxt_router_listener_conf_t, pass), + }, + { nxt_string("application"), - NXT_CONF_MAP_STR, + NXT_CONF_MAP_STR_COPY, offsetof(nxt_router_listener_conf_t, application), }, }; @@ -1379,7 +1386,9 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *conf, *http, *value; nxt_conf_value_t *applications, *application; nxt_conf_value_t *listeners, *listener; + nxt_conf_value_t *routes_conf; nxt_socket_conf_t *skcf; + nxt_http_routes_t *routes; nxt_event_engine_t *engine; nxt_app_lang_module_t *lang; nxt_router_app_conf_t apcf; @@ -1392,6 +1401,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, static nxt_str_t http_path = nxt_string("/settings/http"); static nxt_str_t applications_path = nxt_string("/applications"); static nxt_str_t listeners_path = nxt_string("/listeners"); + static nxt_str_t routes_path = nxt_string("/routes"); static nxt_str_t access_log_path = nxt_string("/access_log"); #if (NXT_TLS) static nxt_str_t certificate_path = nxt_string("/tls/certificate"); @@ -1589,6 +1599,15 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, app_joint->free_app_work.obj = app_joint; } + routes_conf = nxt_conf_get_path(conf, &routes_path); + if (nxt_fast_path(routes_conf != NULL)) { + routes = nxt_http_routes_create(task, tmcf, routes_conf); + if (nxt_slow_path(routes == NULL)) { + return NXT_ERROR; + } + tmcf->router_conf->routes = routes; + } + http = nxt_conf_get_path(conf, &http_path); #if 0 if (http == NULL) { @@ -1671,10 +1690,13 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, skcf->router_conf = tmcf->router_conf; skcf->router_conf->count++; - if (lscf.application.length > 0) { - skcf->application = nxt_router_listener_application(tmcf, - &lscf.application); - nxt_router_app_use(task, skcf->application, 1); + if (lscf.pass.length != 0) { + skcf->pass = nxt_http_pass_create(task, tmcf, &lscf.pass); + + /* COMPATIBILITY: listener application. */ + } else if (lscf.application.length > 0) { + skcf->pass = nxt_http_pass_application(task, tmcf, + &lscf.application); } } @@ -1712,6 +1734,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, tmcf->router_conf->access_log = access_log; } + nxt_http_routes_resolve(task, tmcf); + nxt_queue_add(&tmcf->deleting, &router->sockets); nxt_queue_init(&router->sockets); @@ -1752,7 +1776,7 @@ nxt_router_app_find(nxt_queue_t *queue, nxt_str_t *name) } -static nxt_app_t * +nxt_app_t * nxt_router_listener_application(nxt_router_temp_conf_t *tmcf, nxt_str_t *name) { nxt_app_t *app; @@ -2885,7 +2909,6 @@ nxt_router_listen_event_release(nxt_task_t *task, nxt_listen_event_t *lev, void nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) { - nxt_app_t *app; nxt_socket_conf_t *skcf; nxt_router_conf_t *rtcf; nxt_thread_spinlock_t *lock; @@ -2904,7 +2927,6 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) * be already destroyed by another thread. */ skcf = joint->socket_conf; - app = skcf->application; rtcf = skcf->router_conf; lock = &rtcf->router->lock; @@ -2916,7 +2938,6 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) if (--skcf->count != 0) { skcf = NULL; rtcf = NULL; - app = NULL; } else { nxt_queue_remove(&skcf->link); @@ -2929,6 +2950,10 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) nxt_thread_spin_unlock(lock); if (skcf != NULL) { + if (skcf->pass != NULL) { + nxt_http_pass_cleanup(task, skcf->pass); + } + #if (NXT_TLS) if (skcf->tls != NULL) { task->thread->runtime->tls->server_free(task, skcf->tls); @@ -2936,16 +2961,14 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) #endif } - if (app != NULL) { - nxt_router_app_use(task, app, -1); - } - /* TODO remove engine->port */ /* TODO excude from connected ports */ if (rtcf != NULL) { nxt_debug(task, "old router conf is destroyed"); + nxt_http_routes_cleanup(task, rtcf->routes); + nxt_router_access_log_release(task, lock, rtcf->access_log); nxt_mp_thread_adopt(rtcf->mem_pool); @@ -4432,10 +4455,10 @@ nxt_router_app_port(nxt_task_t *task, nxt_app_t *app, nxt_req_app_link_t *ra) void -nxt_router_process_http_request(nxt_task_t *task, nxt_app_parse_ctx_t *ar) +nxt_router_process_http_request(nxt_task_t *task, nxt_app_parse_ctx_t *ar, + nxt_app_t *app) { nxt_int_t res; - nxt_app_t *app; nxt_port_t *port; nxt_event_engine_t *engine; nxt_http_request_t *r; @@ -4443,13 +4466,6 @@ nxt_router_process_http_request(nxt_task_t *task, nxt_app_parse_ctx_t *ar) nxt_req_conn_link_t *rc; r = ar->request; - app = r->conf->socket_conf->application; - - if (app == NULL) { - nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); - return; - } - engine = task->thread->engine; rc = nxt_port_rpc_register_handler_ex(task, engine->port, diff --git a/src/nxt_router.h b/src/nxt_router.h index a2da8ff9..dec56bd5 100644 --- a/src/nxt_router.h +++ b/src/nxt_router.h @@ -16,6 +16,8 @@ typedef struct nxt_http_request_s nxt_http_request_t; #include +typedef struct nxt_http_pass_s nxt_http_pass_t; +typedef struct nxt_http_routes_s nxt_http_routes_t; typedef struct nxt_router_access_log_s nxt_router_access_log_t; @@ -34,6 +36,7 @@ typedef struct { uint32_t count; uint32_t threads; nxt_router_t *router; + nxt_http_routes_t *routes; nxt_mp_t *mem_pool; nxt_router_access_log_t *access_log; @@ -137,7 +140,7 @@ typedef struct { nxt_queue_link_t link; nxt_router_conf_t *router_conf; - nxt_app_t *application; + nxt_http_pass_t *pass; /* * A listen socket time can be shorter than socket configuration life @@ -189,8 +192,11 @@ void nxt_router_remove_pid_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); void nxt_router_access_log_reopen_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); -void nxt_router_process_http_request(nxt_task_t *task, nxt_app_parse_ctx_t *ar); +void nxt_router_process_http_request(nxt_task_t *task, nxt_app_parse_ctx_t *ar, + nxt_app_t *app); void nxt_router_app_port_close(nxt_task_t *task, nxt_port_t *port); +nxt_app_t *nxt_router_listener_application(nxt_router_temp_conf_t *tmcf, + nxt_str_t *name); void nxt_router_app_use(nxt_task_t *task, nxt_app_t *app, int i); void nxt_router_listen_event_release(nxt_task_t *task, nxt_listen_event_t *lev, nxt_socket_conf_joint_t *joint); -- cgit From c24c0deb19034726b6450efcd5bce17632177f1b Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Wed, 27 Feb 2019 16:44:52 +0300 Subject: Controller: added "pass" configuration option. --- src/nxt_conf_validation.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index b1e30955..3485b4e3 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -54,6 +54,8 @@ static nxt_int_t nxt_conf_vldt_listener(nxt_conf_validation_t *vldt, static nxt_int_t nxt_conf_vldt_certificate(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); #endif +static nxt_int_t nxt_conf_vldt_pass(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_app_name(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_app(nxt_conf_validation_t *vldt, @@ -158,6 +160,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_tls_members[] = { static nxt_conf_vldt_object_t nxt_conf_vldt_listener_members[] = { + { nxt_string("pass"), + NXT_CONF_VLDT_STRING, + &nxt_conf_vldt_pass, + NULL }, + { nxt_string("application"), NXT_CONF_VLDT_STRING, &nxt_conf_vldt_app_name, @@ -495,6 +502,61 @@ nxt_conf_vldt_listener(nxt_conf_validation_t *vldt, nxt_str_t *name, } +static nxt_int_t +nxt_conf_vldt_pass(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + u_char *p; + nxt_str_t pass, first, second; + + nxt_conf_get_string(value, &pass); + + p = nxt_memchr(pass.start, '/', pass.length); + + if (p != NULL) { + first.length = p - pass.start; + first.start = pass.start; + + if (pass.length - first.length == 1) { + goto error; + } + + second.length = pass.length - first.length - 1; + second.start = p + 1; + + } else { + first = pass; + second.length = 0; + } + + if (nxt_str_eq(&first, "applications", 12)) { + + if (second.length == 0) { + goto error; + } + + value = nxt_conf_get_object_member(vldt->conf, &first, NULL); + + if (nxt_slow_path(value == NULL)) { + goto error; + } + + value = nxt_conf_get_object_member(value, &second, NULL); + + if (nxt_slow_path(value == NULL)) { + goto error; + } + + return NXT_OK; + } + +error: + + return nxt_conf_vldt_error(vldt, "Request \"pass\" points to invalid " + "location \"%V\".", &pass); +} + + #if (NXT_TLS) static nxt_int_t -- cgit From a881c31abdb9fda2ba0d2e05c45c2ce890a6cfab Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Wed, 27 Feb 2019 16:50:37 +0300 Subject: Controller: added "routes" configuration. --- src/nxt_conf_validation.c | 186 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 3485b4e3..655097fd 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -56,6 +56,16 @@ static nxt_int_t nxt_conf_vldt_certificate(nxt_conf_validation_t *vldt, #endif static nxt_int_t nxt_conf_vldt_pass(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_routes(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_routes_member(nxt_conf_validation_t *vldt, + nxt_str_t *name, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_route(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_match_patterns(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_match_pattern(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_app_name(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_app(nxt_conf_validation_t *vldt, @@ -131,6 +141,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_root_members[] = { &nxt_conf_vldt_object_iterator, (void *) &nxt_conf_vldt_listener }, + { nxt_string("routes"), + NXT_CONF_VLDT_ARRAY | NXT_CONF_VLDT_OBJECT, + &nxt_conf_vldt_routes, + NULL }, + { nxt_string("applications"), NXT_CONF_VLDT_OBJECT, &nxt_conf_vldt_object_iterator, @@ -183,6 +198,51 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_listener_members[] = { }; +static nxt_conf_vldt_object_t nxt_conf_vldt_match_members[] = { + { nxt_string("method"), + NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, + &nxt_conf_vldt_match_patterns, + NULL }, + + { nxt_string("host"), + NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, + &nxt_conf_vldt_match_patterns, + NULL }, + + { nxt_string("uri"), + NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, + &nxt_conf_vldt_match_patterns, + NULL }, + + NXT_CONF_VLDT_END +}; + + +static nxt_conf_vldt_object_t nxt_conf_vldt_action_members[] = { + { nxt_string("pass"), + NXT_CONF_VLDT_STRING, + &nxt_conf_vldt_pass, + NULL }, + + NXT_CONF_VLDT_END +}; + + +static nxt_conf_vldt_object_t nxt_conf_vldt_route_members[] = { + { nxt_string("match"), + NXT_CONF_VLDT_OBJECT, + &nxt_conf_vldt_object, + (void *) &nxt_conf_vldt_match_members }, + + { nxt_string("action"), + NXT_CONF_VLDT_OBJECT, + &nxt_conf_vldt_object, + (void *) &nxt_conf_vldt_action_members }, + + NXT_CONF_VLDT_END +}; + + static nxt_conf_vldt_object_t nxt_conf_vldt_app_limits_members[] = { { nxt_string("timeout"), NXT_CONF_VLDT_INTEGER, @@ -550,6 +610,34 @@ nxt_conf_vldt_pass(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, return NXT_OK; } + if (nxt_str_eq(&first, "routes", 6)) { + value = nxt_conf_get_object_member(vldt->conf, &first, NULL); + + if (nxt_slow_path(value == NULL)) { + goto error; + } + + if (second.length == 0) { + if (nxt_conf_type(value) != NXT_CONF_ARRAY) { + goto error; + } + + return NXT_OK; + } + + if (nxt_conf_type(value) != NXT_CONF_OBJECT) { + goto error; + } + + value = nxt_conf_get_object_member(value, &second, NULL); + + if (nxt_slow_path(value == NULL)) { + goto error; + } + + return NXT_OK; + } + error: return nxt_conf_vldt_error(vldt, "Request \"pass\" points to invalid " @@ -557,6 +645,104 @@ error: } +static nxt_int_t +nxt_conf_vldt_routes(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + if (nxt_conf_type(value) == NXT_CONF_ARRAY) { + return nxt_conf_vldt_array_iterator(vldt, value, + &nxt_conf_vldt_route); + } + + /* NXT_CONF_OBJECT */ + + return nxt_conf_vldt_object_iterator(vldt, value, + &nxt_conf_vldt_routes_member); +} + + +static nxt_int_t +nxt_conf_vldt_routes_member(nxt_conf_validation_t *vldt, nxt_str_t *name, + nxt_conf_value_t *value) +{ + nxt_int_t ret; + + ret = nxt_conf_vldt_type(vldt, name, value, NXT_CONF_VLDT_ARRAY); + + if (ret != NXT_OK) { + return ret; + } + + return nxt_conf_vldt_array_iterator(vldt, value, &nxt_conf_vldt_route); +} + + +static nxt_int_t +nxt_conf_vldt_route(nxt_conf_validation_t *vldt, nxt_conf_value_t *value) +{ + if (nxt_conf_type(value) != NXT_CONF_OBJECT) { + return nxt_conf_vldt_error(vldt, "The \"routes\" array must contain " + "only object values."); + } + + return nxt_conf_vldt_object(vldt, value, nxt_conf_vldt_route_members); +} + + +static nxt_int_t +nxt_conf_vldt_match_patterns(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data) +{ + if (nxt_conf_type(value) == NXT_CONF_ARRAY) { + return nxt_conf_vldt_array_iterator(vldt, value, + &nxt_conf_vldt_match_pattern); + } + + /* NXT_CONF_STRING */ + + return nxt_conf_vldt_match_pattern(vldt, value); +} + + +static nxt_int_t +nxt_conf_vldt_match_pattern(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value) +{ + u_char ch; + nxt_str_t pattern; + nxt_uint_t i, first, last; + + if (nxt_conf_type(value) != NXT_CONF_STRING) { + return nxt_conf_vldt_error(vldt, + "The \"match\" patterns must be strings."); + } + + nxt_conf_get_string(value, &pattern); + + if (pattern.length == 0) { + return NXT_OK; + } + + first = (pattern.start[0] == '!'); + last = pattern.length - 1; + + for (i = first; i != pattern.length; i++) { + ch = pattern.start[i]; + + if (ch != '*') { + continue; + } + + if (i != first && i != last) { + return nxt_conf_vldt_error(vldt, "The \"match\" patterns can only " + "contain \"*\" markers at the sides."); + } + } + + return NXT_OK; +} + + #if (NXT_TLS) static nxt_int_t -- cgit From e929d08201aae949db25ae8c1051a91e96ba3011 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Wed, 27 Feb 2019 17:25:07 +0300 Subject: Fixed processing of SERVER_NAME after 77aad2c142a0. Previously, the nxt_router_prepare_msg() function expected server host among other headers unmodified. It's not true anymore since normalization of the Host header has been introduced in 77aad2c142a0. The nxt_unit_split_host() function was removed. It didn't work correctly with IPv6 literals. Anyway, after 77aad2c142a0 the port splitting is done in router while Host header processing. --- src/go/unit/nxt_cgo_lib.c | 7 ++---- src/nxt_application.h | 2 +- src/nxt_http_request.c | 9 ++++++-- src/nxt_php_sapi.c | 31 ++++++-------------------- src/nxt_python_wsgi.c | 29 +++++++----------------- src/nxt_router.c | 24 +++++++++++--------- src/nxt_unit.c | 57 ----------------------------------------------- src/nxt_unit_field.h | 1 - src/nxt_unit_request.h | 3 ++- src/perl/nxt_perl_psgi.c | 30 ++++++------------------- src/ruby/nxt_ruby.c | 28 ++++++----------------- 11 files changed, 54 insertions(+), 167 deletions(-) diff --git a/src/go/unit/nxt_cgo_lib.c b/src/go/unit/nxt_cgo_lib.c index 172bef88..98a23482 100644 --- a/src/go/unit/nxt_cgo_lib.c +++ b/src/go/unit/nxt_cgo_lib.c @@ -75,14 +75,11 @@ nxt_cgo_request_handler(nxt_unit_request_info_t *req) nxt_go_request_add_header(go_req, nxt_cgo_str_init(&name, &f->name, f->name_length), nxt_cgo_str_init(&value, &f->value, f->value_length)); - - if (f->hash == NXT_UNIT_HASH_HOST) { - host = value; - } } nxt_go_request_set_content_length(go_req, r->content_length); - nxt_go_request_set_host(go_req, &host); + nxt_go_request_set_host(go_req, + nxt_cgo_str_init(&host, &r->server_name, r->server_name_length)); nxt_go_request_set_remote_addr(go_req, nxt_cgo_str_init(&remote_addr, &r->remote, r->remote_length)); diff --git a/src/nxt_application.h b/src/nxt_application.h index 10f5a922..fe8113c6 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -95,13 +95,13 @@ typedef struct { nxt_str_t version; nxt_str_t path; nxt_str_t query; + nxt_str_t server_name; nxt_list_t *fields; nxt_str_t cookie; nxt_str_t content_length; nxt_str_t content_type; - nxt_str_t host; off_t parsed_content_length; nxt_bool_t done; diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index f6c14df9..724b0808 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -367,6 +367,13 @@ nxt_http_request_application(nxt_task_t *task, nxt_http_request_t *r, ar->r.header.method = *r->method; } + if (r->host.length != 0) { + ar->r.header.server_name = r->host; + + } else { + nxt_str_set(&ar->r.header.server_name, "localhost"); + } + ar->r.header.target = r->target; if (r->path != NULL) { @@ -377,8 +384,6 @@ nxt_http_request_application(nxt_task_t *task, nxt_http_request_t *r, ar->r.header.query = *r->args; } - ar->r.header.host = r->host; - if (r->content_type != NULL) { ar->r.header.content_type.length = r->content_type->value_length; ar->r.header.content_type.start = r->content_type->value; diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index 8c25f82a..559a3e37 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -62,7 +62,7 @@ static void nxt_php_set_sptr(nxt_unit_request_info_t *req, const char *name, nxt_inline void nxt_php_set_str(nxt_unit_request_info_t *req, const char *name, nxt_str_t *s, zval *track_vars_array TSRMLS_DC); static void nxt_php_set_cstr(nxt_unit_request_info_t *req, const char *name, - char *str, uint32_t len, zval *track_vars_array TSRMLS_DC); + const char *str, uint32_t len, zval *track_vars_array TSRMLS_DC); static void nxt_php_register_variables(zval *track_vars_array TSRMLS_DC); #ifdef NXT_HAVE_PHP_LOG_MESSAGE_WITH_SYSLOG_TYPE static void nxt_php_log_message(char *message, int syslog_type_int); @@ -846,8 +846,6 @@ nxt_php_read_cookies(TSRMLS_D) static void nxt_php_register_variables(zval *track_vars_array TSRMLS_DC) { - char *host_start, *port_start; - uint32_t host_length, port_length; const char *name; nxt_unit_field_t *f, *f_end; nxt_php_run_ctx_t *ctx; @@ -928,6 +926,10 @@ nxt_php_register_variables(zval *track_vars_array TSRMLS_DC) nxt_php_set_sptr(req, "SERVER_ADDR", &r->local, r->local_length, track_vars_array TSRMLS_CC); + nxt_php_set_sptr(req, "SERVER_NAME", &r->server_name, r->server_name_length, + track_vars_array TSRMLS_CC); + nxt_php_set_cstr(req, "SERVER_PORT", "80", 2, track_vars_array TSRMLS_CC); + f_end = r->fields + r->fields_count; for (f = r->fields; f < f_end; f++) { name = nxt_unit_sptr_get(&f->name); @@ -949,25 +951,6 @@ nxt_php_register_variables(zval *track_vars_array TSRMLS_DC) nxt_php_set_sptr(req, "CONTENT_TYPE", &f->value, f->value_length, track_vars_array TSRMLS_CC); } - - if (r->host_field != NXT_UNIT_NONE_FIELD) { - f = r->fields + r->host_field; - - host_start = nxt_unit_sptr_get(&f->value); - host_length = f->value_length; - - } else { - host_start = NULL; - host_length = 0; - } - - nxt_unit_split_host(host_start, host_length, &host_start, &host_length, - &port_start, &port_length); - - nxt_php_set_cstr(req, "SERVER_NAME", host_start, host_length, - track_vars_array TSRMLS_CC); - nxt_php_set_cstr(req, "SERVER_PORT", port_start, port_length, - track_vars_array TSRMLS_CC); } @@ -997,7 +980,7 @@ nxt_php_set_str(nxt_unit_request_info_t *req, const char *name, static void nxt_php_set_cstr(nxt_unit_request_info_t *req, const char *name, - char *cstr, uint32_t len, zval *track_vars_array TSRMLS_DC) + const char *cstr, uint32_t len, zval *track_vars_array TSRMLS_DC) { if (nxt_slow_path(cstr == NULL)) { return; @@ -1005,7 +988,7 @@ nxt_php_set_cstr(nxt_unit_request_info_t *req, const char *name, nxt_unit_req_debug(req, "php: register %s='%.*s'", name, (int) len, cstr); - php_register_variable_safe((char *) name, cstr, len, + php_register_variable_safe((char *) name, (char *) cstr, len, track_vars_array TSRMLS_CC); } diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c index bd3a2cb2..871c8e91 100644 --- a/src/nxt_python_wsgi.c +++ b/src/nxt_python_wsgi.c @@ -72,7 +72,7 @@ static PyObject *nxt_python_get_environ(nxt_python_run_ctx_t *ctx); static int nxt_python_add_sptr(nxt_python_run_ctx_t *ctx, const char *name, nxt_unit_sptr_t *sptr, uint32_t size); static int nxt_python_add_str(nxt_python_run_ctx_t *ctx, const char *name, - char *str, uint32_t size); + const char *str, uint32_t size); static PyObject *nxt_py_start_resp(PyObject *self, PyObject *args); static int nxt_python_response_add_field(nxt_python_run_ctx_t *ctx, @@ -690,8 +690,8 @@ static PyObject * nxt_python_get_environ(nxt_python_run_ctx_t *ctx) { int rc; - char *name, *host_start, *port_start; - uint32_t i, host_length, port_length; + char *name; + uint32_t i; PyObject *environ; nxt_unit_field_t *f; nxt_unit_request_t *r; @@ -732,6 +732,10 @@ nxt_python_get_environ(nxt_python_run_ctx_t *ctx) RC(nxt_python_add_sptr(ctx, "SERVER_PROTOCOL", &r->version, r->version_length)); + RC(nxt_python_add_sptr(ctx, "SERVER_NAME", &r->server_name, + r->server_name_length)); + RC(nxt_python_add_str(ctx, "SERVER_PORT", "80", 2)); + for (i = 0; i < r->fields_count; i++) { f = r->fields + i; name = nxt_unit_sptr_get(&f->name); @@ -753,23 +757,6 @@ nxt_python_get_environ(nxt_python_run_ctx_t *ctx) f->value_length)); } - if (r->host_field != NXT_UNIT_NONE_FIELD) { - f = r->fields + r->host_field; - - host_start = nxt_unit_sptr_get(&f->value); - host_length = f->value_length; - - } else { - host_start = NULL; - host_length = 0; - } - - nxt_unit_split_host(host_start, host_length, &host_start, &host_length, - &port_start, &port_length); - - RC(nxt_python_add_str(ctx, "SERVER_NAME", host_start, host_length)); - RC(nxt_python_add_str(ctx, "SERVER_PORT", port_start, port_length)); - #undef RC return environ; @@ -818,7 +805,7 @@ nxt_python_add_sptr(nxt_python_run_ctx_t *ctx, const char *name, static int nxt_python_add_str(nxt_python_run_ctx_t *ctx, const char *name, - char *str, uint32_t size) + const char *str, uint32_t size) { PyObject *value; diff --git a/src/nxt_router.c b/src/nxt_router.c index e81a2f96..7cd28416 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -4662,12 +4662,13 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, h = &r->header; req_size = sizeof(nxt_unit_request_t) - + h->method.length + 1 - + h->version.length + 1 - + r->remote.length + 1 - + r->local.length + 1 - + h->target.length + 1 - + (h->path.start != h->target.start ? h->path.length + 1 : 0); + + h->method.length + 1 + + h->version.length + 1 + + r->remote.length + 1 + + r->local.length + 1 + + h->server_name.length + 1 + + h->target.length + 1 + + (h->path.start != h->target.start ? h->path.length + 1 : 0); fields_count = 0; @@ -4722,6 +4723,11 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, p = nxt_cpymem(p, r->local.start, r->local.length); *p++ = '\0'; + req->server_name_length = h->server_name.length; + nxt_unit_sptr_set(&req->server_name, p); + p = nxt_cpymem(p, h->server_name.start, h->server_name.length); + *p++ = '\0'; + target_pos = p; req->target_length = h->target.length; nxt_unit_sptr_set(&req->target, p); @@ -4749,7 +4755,6 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, req->query.offset = 0; } - req->host_field = NXT_UNIT_NONE_FIELD; req->content_length_field = NXT_UNIT_NONE_FIELD; req->content_type_field = NXT_UNIT_NONE_FIELD; req->cookie_field = NXT_UNIT_NONE_FIELD; @@ -4769,10 +4774,7 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, dst_field->name_length = field->name_length + prefix->length; dst_field->value_length = field->value_length; - if (field->value == h->host.start) { - req->host_field = dst_field - req->fields; - - } else if (field->value == h->content_length.start) { + if (field->value == h->content_length.start) { req->content_length_field = dst_field - req->fields; } else if (field->value == h->content_type.start) { diff --git a/src/nxt_unit.c b/src/nxt_unit.c index fa1fb1d5..ec8ba641 100644 --- a/src/nxt_unit.c +++ b/src/nxt_unit.c @@ -889,59 +889,6 @@ nxt_unit_field_hash(const char *name, size_t name_length) } -void -nxt_unit_split_host(char *host, uint32_t host_length, - char **name, uint32_t *name_length, char **port, uint32_t *port_length) -{ - char *cpos; - - static char default_host[] = "localhost"; - static char default_port[] = "80"; - - if (nxt_slow_path(host == NULL || host_length == 0)) { - *name = default_host; - *name_length = nxt_length(default_host); - - *port = default_port; - *port_length = nxt_length(default_port); - - return; - } - - cpos = memchr(host, ':', host_length); - - if (nxt_slow_path(cpos == NULL)) { - *name = host; - *name_length = host_length; - - *port = default_port; - *port_length = nxt_length(default_port); - - return; - } - - if (nxt_slow_path(cpos == host)) { - *name = default_host; - *name_length = nxt_length(default_host); - - } else { - *name = host; - *name_length = cpos - host; - } - - cpos++; - - if (nxt_slow_path(host + host_length == cpos)) { - *port = default_port; - *port_length = nxt_length(default_port); - - } else { - *port = cpos; - *port_length = host_length - (cpos - host); - } -} - - void nxt_unit_request_group_dup_fields(nxt_unit_request_info_t *req) { @@ -957,10 +904,6 @@ nxt_unit_request_group_dup_fields(nxt_unit_request_info_t *req) for (i = 0; i < r->fields_count; i++) { switch (fields[i].hash) { - case NXT_UNIT_HASH_HOST: - r->host_field = i; - break; - case NXT_UNIT_HASH_CONTENT_LENGTH: r->content_length_field = i; break; diff --git a/src/nxt_unit_field.h b/src/nxt_unit_field.h index 0d490f31..d19db0f0 100644 --- a/src/nxt_unit_field.h +++ b/src/nxt_unit_field.h @@ -12,7 +12,6 @@ #include "nxt_unit_sptr.h" enum { - NXT_UNIT_HASH_HOST = 0xE6EB, NXT_UNIT_HASH_CONTENT_LENGTH = 0x1EA0, NXT_UNIT_HASH_CONTENT_TYPE = 0x5F7D, NXT_UNIT_HASH_COOKIE = 0x23F2, diff --git a/src/nxt_unit_request.h b/src/nxt_unit_request.h index af5c29a1..88d569a6 100644 --- a/src/nxt_unit_request.h +++ b/src/nxt_unit_request.h @@ -19,12 +19,12 @@ struct nxt_unit_request_s { uint8_t version_length; uint8_t remote_length; uint8_t local_length; + uint32_t server_name_length; uint32_t target_length; uint32_t path_length; uint32_t query_length; uint32_t fields_count; - uint32_t host_field; uint32_t content_length_field; uint32_t content_type_field; uint32_t cookie_field; @@ -35,6 +35,7 @@ struct nxt_unit_request_s { nxt_unit_sptr_t version; nxt_unit_sptr_t remote; nxt_unit_sptr_t local; + nxt_unit_sptr_t server_name; nxt_unit_sptr_t target; nxt_unit_sptr_t path; nxt_unit_sptr_t query; diff --git a/src/perl/nxt_perl_psgi.c b/src/perl/nxt_perl_psgi.c index da4a3864..a86fd0de 100644 --- a/src/perl/nxt_perl_psgi.c +++ b/src/perl/nxt_perl_psgi.c @@ -68,7 +68,7 @@ static SV *nxt_perl_psgi_env_create(PerlInterpreter *my_perl, nxt_inline int nxt_perl_psgi_add_sptr(PerlInterpreter *my_perl, HV *hash_env, const char *name, uint32_t name_len, nxt_unit_sptr_t *sptr, uint32_t len); nxt_inline int nxt_perl_psgi_add_str(PerlInterpreter *my_perl, HV *hash_env, - const char *name, uint32_t name_len, char *str, uint32_t len); + const char *name, uint32_t name_len, const char *str, uint32_t len); nxt_inline int nxt_perl_psgi_add_value(PerlInterpreter *my_perl, HV *hash_env, const char *name, uint32_t name_len, void *value); @@ -450,8 +450,7 @@ nxt_perl_psgi_env_create(PerlInterpreter *my_perl, { HV *hash_env; AV *array_version; - char *host_start, *port_start; - uint32_t i, host_length, port_length; + uint32_t i; nxt_unit_field_t *f; nxt_unit_request_t *r; @@ -519,6 +518,10 @@ nxt_perl_psgi_env_create(PerlInterpreter *my_perl, RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("SERVER_ADDR"), &r->local, r->local_length)); + RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("SERVER_NAME"), + &r->server_name, r->server_name_length)); + RC(nxt_perl_psgi_add_str(my_perl, hash_env, NL("SERVER_PORT"), "80", 2)); + for (i = 0; i < r->fields_count; i++) { f = r->fields + i; @@ -541,25 +544,6 @@ nxt_perl_psgi_env_create(PerlInterpreter *my_perl, &f->value, f->value_length)); } - if (r->host_field != NXT_UNIT_NONE_FIELD) { - f = r->fields + r->host_field; - - host_start = nxt_unit_sptr_get(&f->value); - host_length = f->value_length; - - } else { - host_start = NULL; - host_length = 0; - } - - nxt_unit_split_host(host_start, host_length, &host_start, &host_length, - &port_start, &port_length); - - RC(nxt_perl_psgi_add_str(my_perl, hash_env, NL("SERVER_NAME"), - host_start, host_length)); - RC(nxt_perl_psgi_add_str(my_perl, hash_env, NL("SERVER_PORT"), - port_start, port_length)); - #undef NL #undef RC @@ -584,7 +568,7 @@ nxt_perl_psgi_add_sptr(PerlInterpreter *my_perl, HV *hash_env, nxt_inline int nxt_perl_psgi_add_str(PerlInterpreter *my_perl, HV *hash_env, - const char *name, uint32_t name_len, char *str, uint32_t len) + const char *name, uint32_t name_len, const char *str, uint32_t len) { SV **ha; diff --git a/src/ruby/nxt_ruby.c b/src/ruby/nxt_ruby.c index a08b8189..d099a338 100644 --- a/src/ruby/nxt_ruby.c +++ b/src/ruby/nxt_ruby.c @@ -45,7 +45,7 @@ static int nxt_ruby_read_request(VALUE hash_env); nxt_inline void nxt_ruby_add_sptr(VALUE hash_env, const char *name, uint32_t name_len, nxt_unit_sptr_t *sptr, uint32_t len); nxt_inline void nxt_ruby_add_str(VALUE hash_env, - const char *name, uint32_t name_len, char *str, uint32_t len); + const char *name, uint32_t name_len, const char *str, uint32_t len); static nxt_int_t nxt_ruby_rack_result_status(VALUE result); static int nxt_ruby_rack_result_headers(VALUE result, nxt_int_t status); static int nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg); @@ -428,8 +428,7 @@ fail: static int nxt_ruby_read_request(VALUE hash_env) { - char *host_start, *port_start; - uint32_t i, host_length, port_length; + uint32_t i; nxt_unit_field_t *f; nxt_unit_request_t *r; @@ -452,6 +451,10 @@ nxt_ruby_read_request(VALUE hash_env) r->remote_length); nxt_ruby_add_sptr(hash_env, NL("SERVER_ADDR"), &r->local, r->local_length); + nxt_ruby_add_sptr(hash_env, NL("SERVER_NAME"), &r->server_name, + r->server_name_length); + nxt_ruby_add_str(hash_env, NL("SERVER_PORT"), "80", 2); + for (i = 0; i < r->fields_count; i++) { f = r->fields + i; @@ -473,23 +476,6 @@ nxt_ruby_read_request(VALUE hash_env) &f->value, f->value_length); } - if (r->host_field != NXT_UNIT_NONE_FIELD) { - f = r->fields + r->host_field; - - host_start = nxt_unit_sptr_get(&f->value); - host_length = f->value_length; - - } else { - host_start = NULL; - host_length = 0; - } - - nxt_unit_split_host(host_start, host_length, &host_start, &host_length, - &port_start, &port_length); - - nxt_ruby_add_str(hash_env, NL("SERVER_NAME"), host_start, host_length); - nxt_ruby_add_str(hash_env, NL("SERVER_PORT"), port_start, port_length); - #undef NL return NXT_UNIT_OK; @@ -510,7 +496,7 @@ nxt_ruby_add_sptr(VALUE hash_env, nxt_inline void nxt_ruby_add_str(VALUE hash_env, - const char *name, uint32_t name_len, char *str, uint32_t len) + const char *name, uint32_t name_len, const char *str, uint32_t len) { rb_hash_aset(hash_env, rb_str_new(name, name_len), rb_str_new(str, len)); } -- cgit From 5c9fe8c3060f8204b143abae6d81ee8363aba060 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Wed, 27 Feb 2019 17:25:28 +0300 Subject: Added trailing zero to version string. While it looks nicer without zero 3-rd version number, this should improve interoperability. Version string can be parsed or used for sorting. And it is easier to handle and less confusing when there is constant number of version parts. Moreover, NPM also expects version format with 3 parts. So ".0" has already been used in Node.js module version. --- auto/modules/nodejs | 2 +- pkg/npm/Makefile | 2 +- version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/auto/modules/nodejs b/auto/modules/nodejs index 6fd00496..edaf99ac 100644 --- a/auto/modules/nodejs +++ b/auto/modules/nodejs @@ -158,7 +158,7 @@ ${NXT_NODE_VERSION_FILE}: ${NXT_VERSION_H} $echo '#define NXT_NODE_VERNUM \$(NXT_VERNUM)' > $NXT_NODE_VERSION_FILE ${NXT_NODE_TARBALL}: ${NXT_NODE}-copy - sed -e 's/"version"\s*:.*/"version": "\$(NXT_VERSION).0",/' \ + sed -e 's/"version"\s*:.*/"version": "\$(NXT_VERSION)",/' \ ${NXT_NODE_TMP}/package.json > ${NXT_NODE_TMP}/package.json.tmp mv ${NXT_NODE_TMP}/package.json.tmp ${NXT_NODE_TMP}/package.json tar -zcvf ${NXT_NODE_TARBALL} -C ${NXT_NODE_TMP} . diff --git a/pkg/npm/Makefile b/pkg/npm/Makefile index 8636dd0b..ef8ef7b5 100644 --- a/pkg/npm/Makefile +++ b/pkg/npm/Makefile @@ -13,7 +13,7 @@ copy: cp -rp ../../src/nodejs/unit-http . echo '#define NXT_NODE_VERNUM ${VERNUM}' > unit-http/version.h mv unit-http/binding_pub.gyp unit-http/binding.gyp - sed -e 's/"version"\s*:.*/"version": "${VERSION}.0",/' \ + sed -e 's/"version"\s*:.*/"version": "${VERSION}",/' \ unit-http/package.json > unit-http/package.json.tmp mv unit-http/package.json.tmp unit-http/package.json diff --git a/version b/version index 36e0b1b3..07985001 100644 --- a/version +++ b/version @@ -1,5 +1,5 @@ # Copyright (C) NGINX, Inc. -NXT_VERSION=1.8 +NXT_VERSION=1.8.0 NXT_VERNUM=10800 -- cgit From 379e4c75fdf99439e481f739324d9c6d6d1b18ab Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 27 Feb 2019 17:27:41 +0300 Subject: Perl: added processing for IO:Handle-like object. The application can return the body as an IO:Handle-like object without file descriptor. --- src/perl/nxt_perl_psgi.c | 112 ++++++++++++++++++++++++++++++++++++++++-- test/test_perl_application.py | 1 - 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/src/perl/nxt_perl_psgi.c b/src/perl/nxt_perl_psgi.c index a86fd0de..275ed913 100644 --- a/src/perl/nxt_perl_psgi.c +++ b/src/perl/nxt_perl_psgi.c @@ -51,6 +51,8 @@ static void nxt_perl_psgi_xs_init(pTHX); static SV *nxt_perl_psgi_call_var_application(PerlInterpreter *my_perl, SV *env, SV *app, nxt_unit_request_info_t *req); +static SV *nxt_perl_psgi_call_method(PerlInterpreter *my_perl, SV *obj, + const char *method, nxt_unit_request_info_t *req); /* For currect load XS modules */ EXTERN_C void boot_DynaLoader(pTHX_ CV *cv); @@ -84,8 +86,10 @@ static int nxt_perl_psgi_result_body(PerlInterpreter *my_perl, SV *result, nxt_unit_request_info_t *req); static int nxt_perl_psgi_result_body_ref(PerlInterpreter *my_perl, SV *sv_body, nxt_unit_request_info_t *req); -static ssize_t nxt_perl_psgi_io_read(nxt_unit_read_info_t *read_info, - void *dst, size_t size); +static int nxt_perl_psgi_result_body_fh(PerlInterpreter *my_perl, SV *sv_body, + nxt_unit_request_info_t *req); +static ssize_t nxt_perl_psgi_io_read(nxt_unit_read_info_t *read_info, void *dst, + size_t size); static int nxt_perl_psgi_result_array(PerlInterpreter *my_perl, SV *result, nxt_unit_request_info_t *req); @@ -251,6 +255,42 @@ nxt_perl_psgi_call_var_application(PerlInterpreter *my_perl, } +static SV * +nxt_perl_psgi_call_method(PerlInterpreter *my_perl, SV *obj, const char *method, + nxt_unit_request_info_t *req) +{ + SV *result; + + dSP; + + ENTER; + SAVETMPS; + + PUSHMARK(sp); + XPUSHs(obj); + PUTBACK; + + call_method(method, G_EVAL|G_SCALAR); + + SPAGAIN; + + if (SvTRUE(ERRSV)) { + nxt_unit_req_error(req, "PSGI: Failed to call method '%s':\n%s", + method, SvPV_nolen(ERRSV)); + result = NULL; + + } else { + result = SvREFCNT_inc(POPs); + } + + PUTBACK; + FREETMPS; + LEAVE; + + return result; +} + + static u_char * nxt_perl_psgi_module_create(nxt_task_t *task, const char *script) { @@ -749,6 +789,68 @@ nxt_perl_psgi_result_body(PerlInterpreter *my_perl, SV *sv_body, } +static int +nxt_perl_psgi_result_body_ref(PerlInterpreter *my_perl, SV *sv_body, + nxt_unit_request_info_t *req) +{ + SV *data, *old_rs, *old_perl_rs; + int rc; + size_t len; + const char *body; + + /* + * Servers should set the $/ special variable to the buffer size + * when reading content from $body using the getline method. + * This is done by setting $/ with a reference to an integer ($/ = \8192). + */ + + old_rs = PL_rs; + old_perl_rs = get_sv("/", GV_ADD); + + PL_rs = sv_2mortal(newRV_noinc(newSViv(nxt_unit_buf_min()))); + + sv_setsv(old_perl_rs, PL_rs); + + rc = NXT_UNIT_OK; + + for ( ;; ) { + data = nxt_perl_psgi_call_method(my_perl, sv_body, "getline", req); + if (nxt_slow_path(data == NULL)) { + rc = NXT_UNIT_ERROR; + break; + } + + body = SvPV(data, len); + + if (len == 0) { + SvREFCNT_dec(data); + + data = nxt_perl_psgi_call_method(my_perl, sv_body, "close", req); + if (nxt_fast_path(data != NULL)) { + SvREFCNT_dec(data); + } + + break; + } + + rc = nxt_unit_response_write(req, body, len); + + SvREFCNT_dec(data); + + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + nxt_unit_req_error(req, "PSGI: Failed to write content from " + "Perl Application"); + break; + } + }; + + PL_rs = old_rs; + sv_setsv(get_sv("/", GV_ADD), old_perl_rs); + + return rc; +} + + typedef struct { PerlInterpreter *my_perl; PerlIO *fp; @@ -756,7 +858,7 @@ typedef struct { static int -nxt_perl_psgi_result_body_ref(PerlInterpreter *my_perl, SV *sv_body, +nxt_perl_psgi_result_body_fh(PerlInterpreter *my_perl, SV *sv_body, nxt_unit_request_info_t *req) { IO *io; @@ -866,6 +968,10 @@ nxt_perl_psgi_result_array(PerlInterpreter *my_perl, SV *result, return nxt_perl_psgi_result_body(my_perl, *sv_temp, req); } + if (SvTYPE(SvRV(*sv_temp)) == SVt_PVGV) { + return nxt_perl_psgi_result_body_fh(my_perl, *sv_temp, req); + } + return nxt_perl_psgi_result_body_ref(my_perl, *sv_temp, req); } diff --git a/test/test_perl_application.py b/test/test_perl_application.py index 7850e231..f9acbd32 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -185,7 +185,6 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') - @unittest.expectedFailure def test_perl_body_io_fake(self): self.load('body_io_fake') -- cgit From 4de2c8b56719fce6b95b6f8a613b349590a3676f Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 27 Feb 2019 19:15:02 +0300 Subject: Tests: added "Host" and "Connetion" headers where necessary. Also minor header organizing. --- test/test_access_log.py | 8 +++--- test/test_go_application.py | 18 ++++++------ test/test_http_header.py | 64 +++++++++++++++++++++++++++-------------- test/test_node_application.py | 43 ++++++++++++++++----------- test/test_perl_application.py | 12 ++++---- test/test_php_application.py | 12 ++++---- test/test_python_application.py | 36 ++++++++++++----------- test/test_ruby_application.py | 12 ++++---- test/test_settings.py | 9 +++--- test/test_tls.py | 28 +++++++++--------- 10 files changed, 143 insertions(+), 99 deletions(-) diff --git a/test/test_access_log.py b/test/test_access_log.py index c8464796..d6741c28 100644 --- a/test/test_access_log.py +++ b/test/test_access_log.py @@ -23,9 +23,9 @@ class TestUnitAccessLog(unit.TestUnitApplicationPython): self.load('mirror') (resp, sock) = self.post(headers={ + 'Host': 'localhost', 'Connection': 'keep-alive', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, start=True, body='01234') time.sleep(0.2) @@ -34,9 +34,9 @@ class TestUnitAccessLog(unit.TestUnitApplicationPython): self.search_in_log(r'"POST / HTTP/1.1" 200 5'), 'keepalive 1') resp = self.post(headers={ + 'Host': 'localhost', 'Connection': 'close', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, sock=sock, body='0123456789') time.sleep(0.2) diff --git a/test/test_go_application.py b/test/test_go_application.py index 90785851..1ecc2536 100644 --- a/test/test_go_application.py +++ b/test/test_go_application.py @@ -14,7 +14,8 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo): resp = self.post(headers={ 'Host': 'localhost', 'Content-Type': 'text/html', - 'Custom-Header': 'blah' + 'Custom-Header': 'blah', + 'Connection': 'close' }, body=body) self.assertEqual(resp['status'], 200, 'status') @@ -36,7 +37,8 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo): 'Server-Protocol': 'HTTP/1.1', 'Server-Protocol-Major': '1', 'Server-Protocol-Minor': '1', - 'Custom-Header': 'blah' + 'Custom-Header': 'blah', + 'Connection': 'close' }, 'headers') self.assertEqual(resp['body'], body, 'body') @@ -52,8 +54,8 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo): self.load('post_variables') resp = self.post(headers={ - 'Content-Type': 'application/x-www-form-urlencoded', 'Host': 'localhost', + 'Content-Type': 'application/x-www-form-urlencoded', 'Connection': 'close' }, body='var1=val1&var2=&var3') @@ -74,17 +76,17 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo): self.load('mirror') (resp, sock) = self.post(headers={ + 'Host': 'localhost', 'Connection': 'keep-alive', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, start=True, body='0123456789' * 500) self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') resp = self.post(headers={ - 'Connection': 'close', + 'Host': 'localhost', 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Connection': 'close' }, sock=sock, body='0123456789') self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') @@ -93,8 +95,8 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo): self.load('cookies') resp = self.get(headers={ - 'Cookie': 'var1=val1; var2=val2', 'Host': 'localhost', + 'Cookie': 'var1=val1; var2=val2', 'Connection': 'close' }) diff --git a/test/test_http_header.py b/test/test_http_header.py index 0544f3b3..a7638b8d 100644 --- a/test/test_http_header.py +++ b/test/test_http_header.py @@ -10,7 +10,9 @@ class TestUnitHTTPHeader(unit.TestUnitApplicationPython): self.load('custom_header') resp = self.get(headers={ - 'Custom-Header': ' ,' + 'Host': 'localhost', + 'Custom-Header': ' ,', + 'Connection': 'close' }) self.assertEqual(resp['status'], 200, 'value leading sp status') @@ -21,7 +23,9 @@ class TestUnitHTTPHeader(unit.TestUnitApplicationPython): self.load('custom_header') resp = self.get(headers={ - 'Custom-Header': '\t,' + 'Host': 'localhost', + 'Custom-Header': '\t,', + 'Connection': 'close' }) self.assertEqual(resp['status'], 200, 'value leading htab status') @@ -32,7 +36,9 @@ class TestUnitHTTPHeader(unit.TestUnitApplicationPython): self.load('custom_header') resp = self.get(headers={ - 'Custom-Header': ', ' + 'Host': 'localhost', + 'Custom-Header': ', ', + 'Connection': 'close' }) self.assertEqual(resp['status'], 200, 'value trailing sp status') @@ -43,7 +49,9 @@ class TestUnitHTTPHeader(unit.TestUnitApplicationPython): self.load('custom_header') resp = self.get(headers={ - 'Custom-Header': ',\t' + 'Host': 'localhost', + 'Custom-Header': ',\t', + 'Connection': 'close' }) self.assertEqual(resp['status'], 200, 'value trailing htab status') @@ -54,7 +62,9 @@ class TestUnitHTTPHeader(unit.TestUnitApplicationPython): self.load('custom_header') resp = self.get(headers={ - 'Custom-Header': ' , ' + 'Host': 'localhost', + 'Custom-Header': ' , ', + 'Connection': 'close' }) self.assertEqual(resp['status'], 200, 'value both sp status') @@ -65,7 +75,9 @@ class TestUnitHTTPHeader(unit.TestUnitApplicationPython): self.load('custom_header') resp = self.get(headers={ - 'Custom-Header': '\t,\t' + 'Host': 'localhost', + 'Custom-Header': '\t,\t', + 'Connection': 'close' }) self.assertEqual(resp['status'], 200, 'value both htab status') @@ -76,7 +88,9 @@ class TestUnitHTTPHeader(unit.TestUnitApplicationPython): self.load('custom_header') resp = self.get(headers={ - 'Custom-Header': '(),/:;<=>?@[\]{}\t !#$%&\'*+-.^_`|~' + 'Host': 'localhost', + 'Custom-Header': '(),/:;<=>?@[\]{}\t !#$%&\'*+-.^_`|~', + 'Connection': 'close' }) self.assertEqual(resp['status'], 200, 'value chars status') @@ -113,7 +127,9 @@ Connection: close self.load('empty') resp = self.get(headers={ - ' Custom-Header': 'blah' + 'Host': 'localhost', + ' Custom-Header': 'blah', + 'Connection': 'close' }) self.assertEqual(resp['status'], 400, 'field leading sp') @@ -122,7 +138,9 @@ Connection: close self.load('empty') resp = self.get(headers={ - '\tCustom-Header': 'blah' + 'Host': 'localhost', + '\tCustom-Header': 'blah', + 'Connection': 'close' }) self.assertEqual(resp['status'], 400, 'field leading htab') @@ -131,7 +149,9 @@ Connection: close self.load('empty') resp = self.get(headers={ - 'Custom-Header ': 'blah' + 'Host': 'localhost', + 'Custom-Header ': 'blah', + 'Connection': 'close' }) self.assertEqual(resp['status'], 400, 'field trailing sp') @@ -140,7 +160,9 @@ Connection: close self.load('empty') resp = self.get(headers={ - 'Custom-Header\t': 'blah' + 'Host': 'localhost', + 'Custom-Header\t': 'blah', + 'Connection': 'close' }) self.assertEqual(resp['status'], 400, 'field trailing htab') @@ -149,45 +171,45 @@ Connection: close self.load('empty') self.assertEqual(self.post(headers={ + 'Host': 'localhost', 'Content-Length': str(2 ** 64), - 'Connection': 'close', - 'Host': 'localhost' + 'Connection': 'close' }, body='X' * 1000)['status'], 400, 'Content-Length big') def test_http_header_content_length_negative(self): self.load('empty') self.assertEqual(self.post(headers={ + 'Host': 'localhost', 'Content-Length': '-100', - 'Connection': 'close', - 'Host': 'localhost' + 'Connection': 'close' }, body='X' * 1000)['status'], 400, 'Content-Length negative') def test_http_header_content_length_text(self): self.load('empty') self.assertEqual(self.post(headers={ + 'Host': 'localhost', 'Content-Length': 'blah', - 'Connection': 'close', - 'Host': 'localhost' + 'Connection': 'close' }, body='X' * 1000)['status'], 400, 'Content-Length text') def test_http_header_content_length_multiple_values(self): self.load('empty') self.assertEqual(self.post(headers={ + 'Host': 'localhost', 'Content-Length': '41, 42', - 'Connection': 'close', - 'Host': 'localhost' + 'Connection': 'close' }, body='X' * 1000)['status'], 400, 'Content-Length multiple value') def test_http_header_content_length_multiple_fields(self): self.load('empty') self.assertEqual(self.post(headers={ + 'Host': 'localhost', 'Content-Length': ['41', '42'], - 'Connection': 'close', - 'Host': 'localhost' + 'Connection': 'close' }, body='X' * 1000)['status'], 400, 'Content-Length multiple fields') if __name__ == '__main__': diff --git a/test/test_node_application.py b/test/test_node_application.py index 05b518f5..cd64fefa 100644 --- a/test/test_node_application.py +++ b/test/test_node_application.py @@ -28,7 +28,8 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): resp = self.post(headers={ 'Host': 'localhost', 'Content-Type': 'text/html', - 'Custom-Header': 'blah' + 'Custom-Header': 'blah', + 'Connection': 'close' }, body=body) self.assertEqual(resp['status'], 200, 'status') @@ -43,10 +44,11 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): raw_headers = headers.pop('Request-Raw-Headers') self.assertRegex(raw_headers, r'^(?:Host|localhost|Content-Type|' \ - 'text\/html|Custom-Header|blah|Content-Length|17|,)+$', - 'raw headers') + 'text\/html|Custom-Header|blah|Content-Length|17|Connection|' \ + 'close|,)+$', 'raw headers') self.assertDictEqual(headers, { + 'Connection': 'close', 'Content-Length': str(len(body)), 'Content-Type': 'text/html', 'Request-Method': 'POST', @@ -91,17 +93,17 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): self.load('mirror') (resp, sock) = self.post(headers={ + 'Host': 'localhost', 'Connection': 'keep-alive', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, start=True, body='0123456789' * 500) self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') resp = self.post(headers={ + 'Host': 'localhost', 'Connection': 'close', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, sock=sock, body='0123456789') self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') @@ -142,7 +144,8 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): resp = self.get(headers={ 'Host': 'localhost', - 'X-Remove': 'X-Header' + 'X-Remove': 'X-Header', + 'Connection': 'close' }) self.assertEqual(resp['headers']['Was-Header'], 'true', 'was header') self.assertEqual(resp['headers']['Has-Header'], 'false', 'has header') @@ -153,7 +156,8 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): self.assertEqual(self.get(headers={ 'Host': 'localhost', - 'X-Remove': 'blah' + 'X-Remove': 'blah', + 'Connection': 'close' })['headers']['Has-Header'], 'true', 'remove header nonexisting') def test_node_application_update_header(self): @@ -194,7 +198,8 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): self.assertEqual(self.post(headers={ 'Host': 'localhost', - 'Content-Type': 'text/html' + 'Content-Type': 'text/html', + 'Connection': 'close' }, body='callback')['status'], 200, 'promise handler request') self.assertTrue(self.waitforfiles(self.testdir + '/node/callback'), 'promise handler') @@ -205,7 +210,8 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): self.assertEqual(self.post(headers={ 'Host': 'localhost', 'Content-Type': 'text/html', - 'X-Write-Call': '1' + 'X-Write-Call': '1', + 'Connection': 'close' }, body='callback')['status'], 200, 'promise handler request write after end') @@ -214,7 +220,8 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): self.assertEqual(self.post(headers={ 'Host': 'localhost', - 'Content-Type': 'text/html' + 'Content-Type': 'text/html', + 'Connection': 'close' }, body='end')['status'], 200, 'promise end request') self.assertTrue(self.waitforfiles(self.testdir + '/node/callback'), 'promise end') @@ -224,7 +231,8 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): self.post(headers={ 'Host': 'localhost', - 'Content-Type': 'text/html' + 'Content-Type': 'text/html', + 'Connection': 'close' }, body='callback1') self.assertTrue(self.waitforfiles(self.testdir + '/node/callback1'), @@ -232,7 +240,8 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): self.post(headers={ 'Host': 'localhost', - 'Content-Type': 'text/html' + 'Content-Type': 'text/html', + 'Connection': 'close' }, body='callback2') self.assertTrue(self.waitforfiles(self.testdir + '/node/callback2'), @@ -260,12 +269,14 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): self.assertEqual(self.get(headers={ 'Host': 'localhost', - 'X-Header': 'length' + 'X-Header': 'length', + 'Connection': 'close' })['headers']['X-Has-Header'], 'false', 'has header length') self.assertEqual(self.get(headers={ 'Host': 'localhost', - 'X-Header': 'Date' + 'X-Header': 'Date', + 'Connection': 'close' })['headers']['X-Has-Header'], 'false', 'has header date') def test_node_application_write_multiple(self): diff --git a/test/test_perl_application.py b/test/test_perl_application.py index f9acbd32..6fd0f78e 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -14,7 +14,8 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): resp = self.post(headers={ 'Host': 'localhost', 'Content-Type': 'text/html', - 'Custom-Header': 'blah' + 'Custom-Header': 'blah', + 'Connection': 'close' }, body=body) self.assertEqual(resp['status'], 200, 'status') @@ -30,6 +31,7 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): 'date header') self.assertDictEqual(headers, { + 'Connection': 'close', 'Content-Length': str(len(body)), 'Content-Type': 'text/html', 'Request-Method': 'POST', @@ -170,17 +172,17 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): self.load('variables') (resp, sock) = self.post(headers={ + 'Host': 'localhost', 'Connection': 'keep-alive', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, start=True, body='0123456789' * 500) self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') resp = self.post(headers={ + 'Host': 'localhost', 'Connection': 'close', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, sock=sock, body='0123456789') self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') diff --git a/test/test_php_application.py b/test/test_php_application.py index ba9c03b3..f077cd3b 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -19,7 +19,8 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP): resp = self.post(headers={ 'Host': 'localhost', 'Content-Type': 'text/html', - 'Custom-Header': 'blah' + 'Custom-Header': 'blah', + 'Connection': 'close' }, body=body) self.assertEqual(resp['status'], 200, 'status') @@ -39,6 +40,7 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP): headers.pop('Content-type') self.assertDictEqual(headers, { + 'Connection': 'close', 'Content-Length': str(len(body)), 'Request-Method': 'POST', 'Request-Uri': '/', @@ -96,17 +98,17 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP): self.load('mirror') (resp, sock) = self.post(headers={ + 'Host': 'localhost', 'Connection': 'keep-alive', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, start=True, body='0123456789' * 500) self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') resp = self.post(headers={ + 'Host': 'localhost', 'Connection': 'close', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, sock=sock, body='0123456789') self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') diff --git a/test/test_python_application.py b/test/test_python_application.py index d37af0c4..da5d3ba2 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -15,7 +15,8 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): resp = self.post(headers={ 'Host': 'localhost', 'Content-Type': 'text/html', - 'Custom-Header': 'blah' + 'Custom-Header': 'blah', + 'Connection': 'close' }, body=body) self.assertEqual(resp['status'], 200, 'status') @@ -31,6 +32,7 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): 'date header') self.assertDictEqual(headers, { + 'Connection': 'close', 'Content-Length': str(len(body)), 'Content-Type': 'text/html', 'Request-Method': 'POST', @@ -90,9 +92,9 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): self.load('ctx_iter_atexit') resp = self.post(headers={ + 'Host': 'localhost', 'Connection': 'close', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, body='0123456789') self.assertEqual(resp['status'], 200, 'ctx iter status') @@ -114,17 +116,17 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): self.load('mirror') (resp, sock) = self.post(headers={ + 'Host': 'localhost', 'Connection': 'keep-alive', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, start=True, body='0123456789' * 500) self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') resp = self.post(headers={ + 'Host': 'localhost', 'Connection': 'close', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, sock=sock, body='0123456789') self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') @@ -138,9 +140,9 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): for i in range(conns): (resp, sock) = self.post(headers={ + 'Host': 'localhost', 'Connection': 'keep-alive', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, start=True, body=body) self.assertEqual(resp['body'], body, 'keep-alive open') @@ -153,9 +155,9 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): for i in range(conns): (resp, sock) = self.post(headers={ + 'Host': 'localhost', 'Connection': 'keep-alive', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, start=True, sock=socks[i], body=body) self.assertEqual(resp['body'], body, 'keep-alive request') @@ -166,9 +168,9 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): for i in range(conns): resp = self.post(headers={ + 'Host': 'localhost', 'Connection': 'close', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, sock=socks[i], body=body) self.assertEqual(resp['body'], body, 'keep-alive close') @@ -183,9 +185,9 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): body = '0123456789' (resp, sock) = self.post(headers={ + 'Host': 'localhost', 'Connection': 'keep-alive', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, start=True, body=body) self.assertEqual(resp['body'], body, 'reconfigure 2 keep-alive 1') @@ -193,9 +195,9 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): self.load('empty') (resp, sock) = self.post(headers={ + 'Host': 'localhost', 'Connection': 'close', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, start=True, sock=sock, body=body) self.assertEqual(resp['status'], 200, 'reconfigure 2 keep-alive 2') diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py index 9eed3e4a..262fc497 100644 --- a/test/test_ruby_application.py +++ b/test/test_ruby_application.py @@ -14,7 +14,8 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): resp = self.post(headers={ 'Host': 'localhost', 'Content-Type': 'text/html', - 'Custom-Header': 'blah' + 'Custom-Header': 'blah', + 'Connection': 'close' }, body=body) self.assertEqual(resp['status'], 200, 'status') @@ -30,6 +31,7 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): 'date header') self.assertDictEqual(headers, { + 'Connection': 'close', 'Content-Length': str(len(body)), 'Content-Type': 'text/html', 'Request-Method': 'POST', @@ -286,17 +288,17 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): self.load('mirror') (resp, sock) = self.post(headers={ + 'Host': 'localhost', 'Connection': 'keep-alive', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, start=True, body='0123456789' * 500) self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') resp = self.post(headers={ + 'Host': 'localhost', 'Connection': 'close', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, sock=sock, body='0123456789') self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') diff --git a/test/test_settings.py b/test/test_settings.py index d272f701..324042f9 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -118,6 +118,7 @@ Connection: close Host: localhost Content-Type: text/html Content-Length: %d +Connection: close """ % data_len + ('X' * data_len) @@ -140,15 +141,15 @@ Content-Length: %d self.conf({'http': { 'idle_timeout': 2 }}, 'settings') (resp, sock) = self.get(headers={ - 'Connection': 'keep-alive', - 'Host': 'localhost' + 'Host': 'localhost', + 'Connection': 'keep-alive' }, start=True) time.sleep(3) resp = self.get(headers={ - 'Connection': 'close', - 'Host': 'localhost' + 'Host': 'localhost', + 'Connection': 'close' }, sock=sock) self.assertEqual(resp['status'], 408, 'status idle timeout') diff --git a/test/test_tls.py b/test/test_tls.py index dc2f4505..2131bf30 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -313,8 +313,8 @@ basicConstraints = critical,CA:TRUE""" % { self.certificate() (resp, sock) = self.get(headers={ - 'Connection': 'keep-alive', - 'Host': 'localhost' + 'Host': 'localhost', + 'Connection': 'keep-alive' }, start=True) self.assertEqual(resp['status'], 200, 'initial status') @@ -334,17 +334,17 @@ basicConstraints = critical,CA:TRUE""" % { self.add_tls(application='mirror') (resp, sock) = self.post_ssl(headers={ + 'Host': 'localhost', 'Connection': 'keep-alive', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, start=True, body='0123456789') self.assertEqual(resp['body'], '0123456789', 'keepalive 1') resp = self.post_ssl(headers={ + 'Host': 'localhost', 'Connection': 'close', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, sock=sock, body='0123456789') self.assertEqual(resp['body'], '0123456789', 'keepalive 2') @@ -358,8 +358,8 @@ basicConstraints = critical,CA:TRUE""" % { self.add_tls() (resp, sock) = self.get_ssl(headers={ - 'Connection': 'keep-alive', - 'Host': 'localhost' + 'Host': 'localhost', + 'Connection': 'keep-alive' }, start=True) self.conf({ @@ -369,8 +369,8 @@ basicConstraints = critical,CA:TRUE""" % { try: resp = self.get_ssl(headers={ - 'Connection': 'close', - 'Host': 'localhost' + 'Host': 'localhost', + 'Connection': 'close' }, sock=sock) except: resp = None @@ -397,9 +397,9 @@ basicConstraints = critical,CA:TRUE""" % { self.add_tls(application='mirror') (resp, sock) = self.post_ssl(headers={ + 'Host': 'localhost', 'Connection': 'keep-alive', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, start=True, body='0123456789') app_id = self.findall(r'(\d+)#\d+ "mirror" application started')[0] @@ -410,9 +410,9 @@ basicConstraints = critical,CA:TRUE""" % { '#)(\d+)#\d+ "mirror" application started')) resp = self.post_ssl(headers={ + 'Host': 'localhost', 'Connection': 'close', - 'Content-Type': 'text/html', - 'Host': 'localhost' + 'Content-Type': 'text/html' }, sock=sock, body='0123456789') self.assertEqual(resp['status'], 200, 'application respawn status') -- cgit From bd77c9a4d2b0f6f609e033060a5486c0afe6b19e Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 27 Feb 2019 19:43:14 +0300 Subject: Tests: read_timeout option introduced. Also, increased default select() timeout from 1s to 5s. --- test/test_python_procman.py | 2 +- test/test_settings.py | 20 +++++++++++--------- test/unit.py | 7 ++++--- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/test/test_python_procman.py b/test/test_python_procman.py index 65268d49..2efe59c0 100644 --- a/test/test_python_procman.py +++ b/test/test_python_procman.py @@ -226,7 +226,7 @@ class TestUnitPythonProcman(unit.TestUnitApplicationPython): (resp, sock) = self.get(headers={ 'Host': 'localhost', 'Connection': 'keep-alive' - }, start=True) + }, start=True, read_timeout=1) self.assertEqual(len(self.pids_for_process()), 1, 'keepalive connection 1') diff --git a/test/test_settings.py b/test/test_settings.py index 324042f9..13bfad49 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -14,7 +14,7 @@ class TestUnitSettings(unit.TestUnitApplicationPython): self.conf({'http': { 'header_read_timeout': 2 }}, 'settings') (resp, sock) = self.http(b"""GET / HTTP/1.1 -""", start=True, raw=True) +""", start=True, read_timeout=1, raw=True) time.sleep(3) @@ -31,17 +31,17 @@ Connection: close self.conf({'http': { 'header_read_timeout': 4 }}, 'settings') (resp, sock) = self.http(b"""GET / HTTP/1.1 -""", start=True, raw=True, no_recv=True) +""", start=True, read_timeout=1, raw=True, no_recv=True) time.sleep(2) (resp, sock) = self.http(b"""Host: localhost -""", start=True, sock=sock, raw=True, no_recv=True) +""", start=True, sock=sock, read_timeout=1, raw=True, no_recv=True) time.sleep(2) (resp, sock) = self.http(b"""X-Blah: blah -""", start=True, sock=sock, raw=True) +""", start=True, sock=sock, read_timeout=1, raw=True) if len(resp) != 0: sock.close() @@ -66,7 +66,7 @@ Host: localhost Content-Length: 10 Connection: close -""", start=True, raw_resp=True, raw=True) +""", start=True, raw_resp=True, read_timeout=1, raw=True) time.sleep(3) @@ -84,15 +84,17 @@ Host: localhost Content-Length: 10 Connection: close -""", start=True, raw=True) +""", start=True, read_timeout=1, raw=True) time.sleep(2) - (resp, sock) = self.http(b"""012""", start=True, sock=sock, raw=True) + (resp, sock) = self.http(b"""012""", start=True, sock=sock, + read_timeout=1, raw=True) time.sleep(2) - (resp, sock) = self.http(b"""345""", start=True, sock=sock, raw=True) + (resp, sock) = self.http(b"""345""", start=True, sock=sock, + read_timeout=1, raw=True) time.sleep(2) @@ -143,7 +145,7 @@ Connection: close (resp, sock) = self.get(headers={ 'Host': 'localhost', 'Connection': 'keep-alive' - }, start=True) + }, start=True, read_timeout=1) time.sleep(3) diff --git a/test/unit.py b/test/unit.py index 80e26e0d..7a51eb20 100644 --- a/test/unit.py +++ b/test/unit.py @@ -357,7 +357,8 @@ class TestUnitHTTP(TestUnit): if 'no_recv' not in kwargs: enc = 'utf-8' if 'encoding' not in kwargs else kwargs['encoding'] - resp = self.recvall(sock).decode(enc) + read_timeout = 5 if 'read_timeout' not in kwargs else kwargs['read_timeout'] + resp = self.recvall(sock, read_timeout=read_timeout).decode(enc) if TestUnit.detailed: print('<<<', resp.encode('utf-8'), sep='\n') @@ -383,9 +384,9 @@ class TestUnitHTTP(TestUnit): def put(self, **kwargs): return self.http('PUT', **kwargs) - def recvall(self, sock, buff_size=4096): + def recvall(self, sock, read_timeout=5, buff_size=4096): data = b'' - while select.select([sock], [], [], 1)[0]: + while select.select([sock], [], [], read_timeout)[0]: try: part = sock.recv(buff_size) except: -- cgit From 8488666d606582c63f1f88a84520195cdc2ba0d7 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 27 Feb 2019 19:46:27 +0300 Subject: Tests: routing. --- test/test_routing.py | 458 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 test/test_routing.py diff --git a/test/test_routing.py b/test/test_routing.py new file mode 100644 index 00000000..07097fc8 --- /dev/null +++ b/test/test_routing.py @@ -0,0 +1,458 @@ +import unittest +import unit + +class TestUnitRouting(unit.TestUnitApplicationProto): + + def setUpClass(): + unit.TestUnit().check_modules('python') + + def setUp(self): + super().setUp() + + self.conf({ + "listeners": { + "*:7080": { + "pass": "routes" + } + }, + "routes": [{ + "match": { "method": "GET" }, + "action": { "pass": "applications/empty" } + }], + "applications": { + "empty": { + "type": "python", + "processes": { "spare": 0 }, + "path": self.current_dir + '/python/empty', + "working_directory": self.current_dir + '/python/empty', + "module": "wsgi" + }, + "mirror": { + "type": "python", + "processes": { "spare": 0 }, + "path": self.current_dir + '/python/mirror', + "working_directory": self.current_dir + '/python/mirror', + "module": "wsgi" + } + } + }) + + def test_routes_match_method_positive(self): + self.assertEqual(self.get()['status'], 200, 'method positive GET') + self.assertEqual(self.post()['status'], 404, 'method positive POST') + + def test_routes_match_method_positive_many(self): + self.assertIn('success', self.conf([{ + "match": { "method": ["GET", "POST"] }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'method positive many configure') + + self.assertEqual(self.get()['status'], 200, 'method positive many GET') + self.assertEqual(self.post()['status'], 200, + 'method positive many POST') + self.assertEqual(self.delete()['status'], 404, + 'method positive many DELETE') + + def test_routes_match_method_negative(self): + self.assertIn('success', self.conf([{ + "match": { "method": "!GET" }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'method negative configure') + + self.assertEqual(self.get()['status'], 404, 'method negative GET') + self.assertEqual(self.post()['status'], 200, 'method negative POST') + + def test_routes_match_method_negative_many(self): + self.assertIn('success', self.conf([{ + "match": { "method": ["!GET", "!POST"] }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'method negative many configure') + + self.assertEqual(self.get()['status'], 404, 'method negative many GET') + self.assertEqual(self.post()['status'], 404, + 'method negative many POST') + self.assertEqual(self.delete()['status'], 200, + 'method negative many DELETE') + + def test_routes_match_method_wildcard_left(self): + self.assertIn('success', self.conf([{ + "match": { "method": "*ET" }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'method wildcard left configure') + + self.assertEqual(self.get()['status'], 200, 'method wildcard left GET') + self.assertEqual(self.post()['status'], 404, + 'method wildcard left POST') + + def test_routes_match_method_wildcard_right(self): + self.assertIn('success', self.conf([{ + "match": { "method": "GE*" }, + "action": { "pass": "applications/empty"} + }], 'routes'), 'method wildcard right configure') + + self.assertEqual(self.get()['status'], 200, + 'method wildcard right GET') + self.assertEqual(self.post()['status'], 404, + 'method wildcard right POST') + + def test_routes_match_method_wildcard_left_right(self): + self.assertIn('success', self.conf([{ + "match": { "method": "*GET*" }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'method wildcard left right configure') + + self.assertEqual(self.get()['status'], 200, + 'method wildcard right GET') + self.assertEqual(self.post()['status'], 404, + 'method wildcard right POST') + + def test_routes_match_method_wildcard(self): + self.assertIn('success', self.conf([{ + "match": { "method": "*" }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'method wildcard configure') + + self.assertEqual(self.get()['status'], 200, 'method wildcard') + + def test_routes_match_method_case_insensitive(self): + self.assertIn('success', self.conf([{ + "match": { "method": "get" }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'method case insensitive configure') + + self.assertEqual(self.get()['status'], 200, 'method case insensitive') + + def test_routes_absent(self): + self.conf({ + "listeners": { + "*:7081": { + "pass": "applications/empty" + } + }, + "applications": { + "empty": { + "type": "python", + "processes": { "spare": 0 }, + "path": self.current_dir + '/python/empty', + "working_directory": self.current_dir + '/python/empty', + "module": "wsgi" + } + } + }) + + self.assertEqual(self.get(port=7081)['status'], 200, 'routes absent') + + def test_routes_pass_invalid(self): + self.assertIn('error', self.conf({ "pass": "routes/blah" }, + 'listeners/*:7080'), 'routes invalid') + + def test_route_empty(self): + self.assertIn('success', self.conf({ + "listeners": { + "*:7080": { + "pass": "routes/main" + } + }, + "routes": {"main": []}, + "applications": { + "empty": { + "type": "python", + "processes": { "spare": 0 }, + "path": self.current_dir + '/python/empty', + "working_directory": self.current_dir + '/python/empty', + "module": "wsgi" + }, + "mirror": { + "type": "python", + "processes": { "spare": 0 }, + "path": self.current_dir + '/python/mirror', + "working_directory": self.current_dir + '/python/mirror', + "module": "wsgi" + } + } + }), 'route empty configure') + + self.assertEqual(self.get()['status'], 404, 'route empty') + + def test_routes_route_empty(self): + self.assertIn('success', self.conf({}, 'listeners'), + 'routes empty listeners configure') + + self.assertIn('success', self.conf({}, 'routes'), + 'routes empty configure') + + def test_routes_route_match_absent(self): + self.assertIn('success', self.conf([{ + "action": { "pass": "applications/empty" } + }], 'routes'), 'route match absent configure') + + self.assertEqual(self.get()['status'], 200, 'route match absent') + + def test_routes_route_action_absent(self): + self.skip_alerts.append(r'failed to apply new conf') + + self.assertIn('error', self.conf([{ + "match": { "method": "GET" } + }], 'routes'), 'route pass absent configure') + + def test_routes_route_pass_absent(self): + self.skip_alerts.append(r'failed to apply new conf') + + self.assertIn('error', self.conf([{ + "match": { "method": "GET" }, + "action": {} + }], 'routes'), 'route pass absent configure') + + def test_routes_rules_two(self): + self.assertIn('success', self.conf([{ + "match": { "method": "GET" }, + "action": { "pass": "applications/empty" } + }, + { + "match": { "method": "POST" }, + "action": { "pass": "applications/mirror" } + }], 'routes'), 'rules two configure') + + self.assertEqual(self.get()['status'], 200, 'rules two match first') + self.assertEqual(self.post(headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Connection': 'close' + }, body='X')['status'], 200, 'rules two match second') + + def test_routes_two(self): + self.assertIn('success', self.conf({ + "listeners": { + "*:7080": { + "pass": "routes/first" + } + }, + "routes": { + "first": [{ + "match": { "method": "GET" }, + "action": { "pass": "routes/second" } + }], + "second": [{ + "match": { "host": "localhost" }, + "action": { "pass": "applications/empty" } + }], + }, + "applications": { + "empty": { + "type": "python", + "processes": { "spare": 0 }, + "path": self.current_dir + '/python/empty', + "working_directory": self.current_dir + '/python/empty', + "module": "wsgi" + } + } + }), 'routes two configure') + + self.assertEqual(self.get()['status'], 200, 'routes two') + + def test_routes_match_host_positive(self): + self.assertIn('success', self.conf([{ + "match": { "host": "localhost" }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'match host positive configure') + + self.assertEqual(self.get()['status'], 200, + 'match host positive localhost') + + self.assertEqual(self.get(headers={'Connection': 'close'})['status'], + 404, 'match host positive empty') + + self.assertEqual(self.get(headers={ + 'Host': 'localhost.', + 'Connection': 'close' + })['status'], 200, 'match host positive trailing dot') + + self.assertEqual(self.get(headers={ + 'Host': 'www.localhost', + 'Connection': 'close' + })['status'], 404, 'match host positive www.localhost') + + self.assertEqual(self.get(headers={ + 'Host': 'localhost1', + 'Connection': 'close' + })['status'], 404, 'match host positive localhost1') + + self.assertEqual(self.get(headers={ + 'Host': 'example.com', + 'Connection': 'close' + })['status'], 404, 'match host positive example.com') + + def test_routes_match_host_ipv4(self): + self.assertIn('success', self.conf([{ + "match": { "host": "127.0.0.1" }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'match host ipv4 configure') + + self.assertEqual(self.get(headers={ + 'Host': '127.0.0.1', + 'Connection': 'close' + })['status'], 200, 'match host ipv4') + + def test_routes_match_host_ipv6(self): + self.assertIn('success', self.conf([{ + "match": { "host": "[::1]" }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'match host ipv6 configure') + + self.assertEqual(self.get(headers={ + 'Host': '[::1]', + 'Connection': 'close' + })['status'], 200, 'match host ipv6') + + self.assertEqual(self.get(headers={ + 'Host': '[::1]:7080', + 'Connection': 'close' + })['status'], 200, 'match host ipv6 port') + + def test_routes_match_host_positive_many(self): + self.assertIn('success', self.conf([{ + "match": { "host": ["localhost", "example.com"] }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'match host positive many configure') + + self.assertEqual(self.get()['status'], 200, + 'match host positive many localhost') + + self.assertEqual(self.get(headers={ + 'Host': 'example.com', + 'Connection': 'close' + })['status'], 200, 'match host positive many example.com') + + def test_routes_match_host_positive_and_negative(self): + self.assertIn('success', self.conf([{ + "match": { "host": ["*example.com", "!www.example.com"] }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'match host positive and negative configure') + + self.assertEqual(self.get()['status'], 404, + 'match host positive and negative localhost') + + self.assertEqual(self.get(headers={ + 'Host': 'example.com', + 'Connection': 'close' + })['status'], 200, 'match host positive and negative example.com') + + self.assertEqual(self.get(headers={ + 'Host': 'www.example.com', + 'Connection': 'close' + })['status'], 404, 'match host positive and negative www.example.com') + + self.assertEqual(self.get(headers={ + 'Host': '!www.example.com', + 'Connection': 'close' + })['status'], 200, 'match host positive and negative !www.example.com') + + def test_routes_match_host_positive_and_negative_wildcard(self): + self.assertIn('success', self.conf([{ + "match": { "host": ["*example*", "!www.example*"] }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'match host positive and negative wildcard configure') + + self.assertEqual(self.get(headers={ + 'Host': 'example.com', + 'Connection': 'close' + })['status'], 200, + 'match host positive and negative wildcard example.com') + + self.assertEqual(self.get(headers={ + 'Host': 'www.example.com', + 'Connection': 'close' + })['status'], 404, + 'match host positive and negative wildcard www.example.com') + + def test_routes_match_host_case_insensitive(self): + self.assertIn('success', self.conf([{ + "match": { "host": "Example.com" }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'host case insensitive configure') + + self.assertEqual(self.get(headers={ + 'Host': 'example.com', + 'Connection': 'close' + })['status'], 200, 'host case insensitive example.com') + + self.assertEqual(self.get(headers={ + 'Host': 'EXAMPLE.COM', + 'Connection': 'close' + })['status'], 200, 'host case insensitive EXAMPLE.COM') + + def test_routes_match_host_port(self): + self.assertIn('success', self.conf([{ + "match": { "host": "example.com" }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'match host port configure') + + self.assertEqual(self.get(headers={ + 'Host': 'example.com:7080', + 'Connection': 'close' + })['status'], 200, 'match host port') + + def test_routes_match_uri_positive(self): + self.assertIn('success', self.conf([{ + "match": { "uri": "/" }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'match uri positive configure') + + self.assertEqual(self.get()['status'], 200, 'match uri positive') + self.assertEqual(self.get(url='/blah')['status'], 404, + 'match uri positive blah') + self.assertEqual(self.get(url='/#blah')['status'], 200, + 'match uri positive #blah') + self.assertEqual(self.get(url='/?var')['status'], 200, + 'match uri params') + self.assertEqual(self.get(url='//')['status'], 200, + 'match uri adjacent slashes') + self.assertEqual(self.get(url='/blah/../')['status'], 200, + 'match uri relative path') + self.assertEqual(self.get(url='/./')['status'], 200, + 'match uri relative path') + + def test_routes_match_uri_case_sensitive(self): + self.assertIn('success', self.conf([{ + "match": { "uri": "/BLAH" }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'match uri case sensitive configure') + + self.assertEqual(self.get(url='/blah')['status'], 404, + 'match uri case sensitive blah') + self.assertEqual(self.get(url='/BlaH')['status'], 404, + 'match uri case sensitive BlaH') + self.assertEqual(self.get(url='/BLAH')['status'], 200, + 'match uri case sensitive BLAH') + + def test_routes_match_uri_normalize(self): + self.assertIn('success', self.conf([{ + "match": { "uri": "/blah" }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'match uri normalize configure') + + self.assertEqual(self.get(url='/%62%6c%61%68')['status'], 200, + 'match uri normalize') + + def test_routes_match_rules(self): + self.assertIn('success', self.conf([{ + "match": { + "method": "GET", + "host": "localhost", + "uri": "/" + }, + "action": { "pass": "applications/empty" } + }], 'routes'), 'routes match rules configure') + + self.assertEqual(self.get()['status'], 200, 'routes match rules') + + def test_routes_loop(self): + self.assertIn('success', self.conf([{ + "match": { "uri": "/" }, + "action": { "pass": "routes" } + }], 'routes'), 'routes loop configure') + + self.assertEqual(self.get()['status'], 500, 'routes loop') + +if __name__ == '__main__': + TestUnitRouting.main() -- cgit From 4b925865303de419af13f046eb4e3ebeb53a8a25 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 27 Feb 2019 20:41:30 +0300 Subject: Tests: "Host" header tests. --- test/python/host/wsgi.py | 7 +++ test/test_http_header.py | 147 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 test/python/host/wsgi.py diff --git a/test/python/host/wsgi.py b/test/python/host/wsgi.py new file mode 100644 index 00000000..db7de306 --- /dev/null +++ b/test/python/host/wsgi.py @@ -0,0 +1,7 @@ +def application(env, start_response): + start_response('200', [ + ('Content-Length', '0'), + ('X-Server-Name', env.get('SERVER_NAME')), + ('X-Http-Host', str(env.get('HTTP_HOST'))) + ]) + return [] diff --git a/test/test_http_header.py b/test/test_http_header.py index a7638b8d..f2294371 100644 --- a/test/test_http_header.py +++ b/test/test_http_header.py @@ -212,5 +212,152 @@ Connection: close 'Connection': 'close' }, body='X' * 1000)['status'], 400, 'Content-Length multiple fields') + def test_http_header_host_absent(self): + self.load('host') + + resp = self.get(headers={'Connection': 'close'}) + + self.assertEqual(resp['status'], 200, 'Host absent status') + self.assertNotEqual(resp['headers']['X-Server-Name'], '', + 'Host absent SERVER_NAME') + + def test_http_header_host_empty(self): + self.load('host') + + resp = self.get(headers={ + 'Host': '', + 'Connection': 'close' + }) + + self.assertEqual(resp['status'], 200, 'Host empty status') + self.assertNotEqual(resp['headers']['X-Server-Name'], '', + 'Host empty SERVER_NAME') + + def test_http_header_host_big(self): + self.load('empty') + + self.assertEqual(self.get(headers={ + 'Host': 'X' * 10000, + 'Connection': 'close' + })['status'], 431, 'Host big') + + def test_http_header_host_port(self): + self.load('host') + + resp = self.get(headers={ + 'Host': 'exmaple.com:7080', + 'Connection': 'close' + }) + + self.assertEqual(resp['status'], 200, 'Host port status') + self.assertEqual(resp['headers']['X-Server-Name'], 'exmaple.com', + 'Host port SERVER_NAME') + self.assertEqual(resp['headers']['X-Http-Host'], 'exmaple.com:7080', + 'Host port HTTP_HOST') + + def test_http_header_host_port_empty(self): + self.load('host') + + resp = self.get(headers={ + 'Host': 'exmaple.com:', + 'Connection': 'close' + }) + + self.assertEqual(resp['status'], 200, 'Host port empty status') + self.assertEqual(resp['headers']['X-Server-Name'], 'exmaple.com', + 'Host port empty SERVER_NAME') + self.assertEqual(resp['headers']['X-Http-Host'], 'exmaple.com:', + 'Host port empty HTTP_HOST') + + def test_http_header_host_literal(self): + self.load('host') + + resp = self.get(headers={ + 'Host': '127.0.0.1', + 'Connection': 'close' + }) + + self.assertEqual(resp['status'], 200, 'Host literal status') + self.assertEqual(resp['headers']['X-Server-Name'], '127.0.0.1', + 'Host literal SERVER_NAME') + + def test_http_header_host_literal_ipv6(self): + self.load('host') + + resp = self.get(headers={ + 'Host': '[::1]:7080', + 'Connection': 'close' + }) + + self.assertEqual(resp['status'], 200, 'Host literal ipv6 status') + self.assertEqual(resp['headers']['X-Server-Name'], '[::1]', + 'Host literal ipv6 SERVER_NAME') + self.assertEqual(resp['headers']['X-Http-Host'], '[::1]:7080', + 'Host literal ipv6 HTTP_HOST') + + def test_http_header_host_trailing_period(self): + self.load('host') + + resp = self.get(headers={ + 'Host': '127.0.0.1.', + 'Connection': 'close' + }) + + self.assertEqual(resp['status'], 200, 'Host trailing period status') + self.assertEqual(resp['headers']['X-Server-Name'], '127.0.0.1', + 'Host trailing period SERVER_NAME') + self.assertEqual(resp['headers']['X-Http-Host'], '127.0.0.1.', + 'Host trailing period HTTP_HOST') + + def test_http_header_host_trailing_period_2(self): + self.load('host') + + resp = self.get(headers={ + 'Host': 'EXAMPLE.COM.', + 'Connection': 'close' + }) + + self.assertEqual(resp['status'], 200, 'Host trailing period 2 status') + self.assertEqual(resp['headers']['X-Server-Name'], 'example.com', + 'Host trailing period 2 SERVER_NAME') + self.assertEqual(resp['headers']['X-Http-Host'], 'EXAMPLE.COM.', + 'Host trailing period 2 HTTP_HOST') + + def test_http_header_host_case_insensitive(self): + self.load('host') + + resp = self.get(headers={ + 'Host': 'EXAMPLE.COM', + 'Connection': 'close' + }) + + self.assertEqual(resp['status'], 200, 'Host case insensitive') + self.assertEqual(resp['headers']['X-Server-Name'], 'example.com', + 'Host case insensitive SERVER_NAME') + + def test_http_header_host_double_dot(self): + self.load('empty') + + self.assertEqual(self.get(headers={ + 'Host': '127.0.0..1', + 'Connection': 'close' + })['status'], 400, 'Host double dot') + + def test_http_header_host_slash(self): + self.load('empty') + + self.assertEqual(self.get(headers={ + 'Host': '/localhost', + 'Connection': 'close' + })['status'], 400, 'Host slash') + + def test_http_header_host_multiple_fields(self): + self.load('empty') + + self.assertEqual(self.get(headers={ + 'Host': ['localhost', 'example.com'], + 'Connection': 'close' + })['status'], 400, 'Host multiple fields') + if __name__ == '__main__': TestUnitHTTPHeader.main() -- cgit From 0c917b4d348cc9ce78a6d4ad7bd4bddde739f402 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 28 Feb 2019 13:39:31 +0300 Subject: Reusing fragmented message buffers. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fragmented message non-mmap buffer chain not freed nor reused before this fix. This closes #206 on GitHub. Thanks to 洪志道 (Hong Zhi Dao). --- src/nxt_port_socket.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/nxt_port_socket.c b/src/nxt_port_socket.c index a545013f..aed3a292 100644 --- a/src/nxt_port_socket.c +++ b/src/nxt_port_socket.c @@ -799,6 +799,14 @@ nxt_port_read_msg_process(nxt_task_t *task, nxt_port_t *port, msg->buf = fmsg->buf; msg->fd = fmsg->fd; + + /* + * To disable instant completion or buffer re-usage, + * handler should reset 'msg.buf'. + */ + if (!msg->port_msg.mmap && msg->buf == b) { + nxt_port_buf_free(port, b); + } } } @@ -895,7 +903,7 @@ nxt_port_buf_alloc(nxt_port_t *port) static void nxt_port_buf_free(nxt_port_t *port, nxt_buf_t *b) { - b->next = port->free_bufs; + nxt_buf_chain_add(&b, port->free_bufs); port->free_bufs = b; } -- cgit From b92dc6036cceeecf36d34a3792487adade92d8b6 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 28 Feb 2019 14:37:39 +0300 Subject: Removing app data debug message. This message produces too many noise in log and complicates analysis. --- src/nxt_router.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/nxt_router.c b/src/nxt_router.c index 7cd28416..51c84df9 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -3393,10 +3393,6 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, dump_size = 300; } - nxt_debug(task, "%srouter app data (%uz): %*s", - msg->port_msg.last ? "last " : "", msg->size, dump_size, - b->mem.pos); - if (msg->size == 0) { b = NULL; } -- cgit From ec7319d32c9c41597a99a9422ff324c97a92bb21 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 28 Feb 2019 15:52:36 +0300 Subject: Various libunit fixes. This patch contains various logging improvements and bugfixes found during Java module development. --- src/nxt_unit.c | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/nxt_unit.c b/src/nxt_unit.c index ec8ba641..6339aec5 100644 --- a/src/nxt_unit.c +++ b/src/nxt_unit.c @@ -1047,8 +1047,11 @@ nxt_unit_response_realloc(nxt_unit_request_info_t *req, + max_fields_count * sizeof(nxt_unit_field_t) + max_fields_size; + nxt_unit_req_debug(req, "realloc %"PRIu32"", buf_size); + buf = nxt_unit_response_buf_alloc(req, buf_size); if (nxt_slow_path(buf == NULL)) { + nxt_unit_req_warn(req, "realloc: new buf allocation failed"); return NXT_UNIT_ERROR; } @@ -1063,23 +1066,29 @@ nxt_unit_response_realloc(nxt_unit_request_info_t *req, f = resp->fields; for (i = 0; i < req->response->fields_count; i++) { - src = req->request->fields + i; + src = req->response->fields + i; if (nxt_slow_path(src->skip != 0)) { continue; } - if (nxt_slow_path(src->name_length + src->value_length + if (nxt_slow_path(src->name_length + src->value_length + 2 > (uint32_t) (buf->end - p))) { + nxt_unit_req_warn(req, "realloc: not enough space for field" + " #%"PRIu32" (%p), (%"PRIu32" + %"PRIu32") required", + i, src, src->name_length, src->value_length); + goto fail; } nxt_unit_sptr_set(&f->name, p); p = nxt_cpymem(p, nxt_unit_sptr_get(&src->name), src->name_length); + *p++ = '\0'; nxt_unit_sptr_set(&f->value, p); p = nxt_cpymem(p, nxt_unit_sptr_get(&src->value), src->value_length); + *p++ = '\0'; f->hash = src->hash; f->skip = 0; @@ -1094,6 +1103,10 @@ nxt_unit_response_realloc(nxt_unit_request_info_t *req, if (nxt_slow_path(req->response->piggyback_content_length > (uint32_t) (buf->end - p))) { + nxt_unit_req_warn(req, "realloc: not enought space for content" + " #%"PRIu32", %"PRIu32" required", + i, req->response->piggyback_content_length); + goto fail; } @@ -1162,7 +1175,7 @@ nxt_unit_response_add_field(nxt_unit_request_info_t *req, buf = req->response_buf; - if (nxt_slow_path(name_length + value_length + if (nxt_slow_path(name_length + value_length + 2 > (uint32_t) (buf->end - buf->free))) { nxt_unit_req_warn(req, "add_field: response buffer overflow"); @@ -1179,9 +1192,11 @@ nxt_unit_response_add_field(nxt_unit_request_info_t *req, nxt_unit_sptr_set(&f->name, buf->free); buf->free = nxt_cpymem(buf->free, name, name_length); + *buf->free++ = '\0'; nxt_unit_sptr_set(&f->value, buf->free); buf->free = nxt_cpymem(buf->free, value, value_length); + *buf->free++ = '\0'; f->hash = nxt_unit_field_hash(name, name_length); f->skip = 0; @@ -1565,7 +1580,9 @@ nxt_unit_buf_next(nxt_unit_buf_t *buf) lnk = &mmap_buf->link; - if (lnk == nxt_queue_last(&req_impl->incoming_buf)) { + if (lnk == nxt_queue_last(&req_impl->incoming_buf) + || lnk == nxt_queue_last(&req_impl->outgoing_buf)) + { return NULL; } @@ -1710,6 +1727,9 @@ nxt_unit_response_write_cb(nxt_unit_request_info_t *req, } while (!read_info->eof) { + nxt_unit_req_debug(req, "write_cb, alloc %"PRIu32"", + read_info->buf_size); + buf = nxt_unit_response_buf_alloc(req, nxt_min(read_info->buf_size, PORT_MMAP_DATA_SIZE)); if (nxt_slow_path(buf == NULL)) { @@ -2449,14 +2469,12 @@ nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg, b->buf.end = b->buf.start + size; b->hdr = hdr; - nxt_unit_debug(ctx, "#%"PRIu32": mmap_read: [%p,%d] %d->%d,(%d,%d,%d)\n" - "%.*s", + nxt_unit_debug(ctx, "#%"PRIu32": mmap_read: [%p,%d] %d->%d,(%d,%d,%d)", recv_msg->port_msg.stream, start, (int) size, (int) hdr->src_pid, (int) hdr->dst_pid, (int) hdr->id, (int) mmap_msg->chunk_id, - (int) mmap_msg->size, - (int) size, (char *) start); + (int) mmap_msg->size); } pthread_mutex_unlock(&process->incoming.mutex); -- cgit From 5bfdebb9e4161a689113d73775498949a09d7fb5 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 28 Feb 2019 18:02:42 +0300 Subject: Introducing Java Servlet Container beta. --- auto/modules/conf | 4 + auto/modules/java | 458 +++ auto/modules/java_get_jar | 33 + src/java/nginx/unit/Context.java | 3502 +++++++++++++++++++++ src/java/nginx/unit/DynamicDispatcherRequest.java | 8 + src/java/nginx/unit/DynamicPathRequest.java | 15 + src/java/nginx/unit/ForwardRequestWrapper.java | 150 + src/java/nginx/unit/HeaderNamesEnumeration.java | 42 + src/java/nginx/unit/HeadersEnumeration.java | 40 + src/java/nginx/unit/IncludeRequestWrapper.java | 88 + src/java/nginx/unit/IncludeResponseWrapper.java | 117 + src/java/nginx/unit/InitParams.java | 7 + src/java/nginx/unit/InputStream.java | 90 + src/java/nginx/unit/JspPropertyGroup.java | 169 + src/java/nginx/unit/OutputStream.java | 68 + src/java/nginx/unit/Request.java | 1123 +++++++ src/java/nginx/unit/RequestAttrProxy.java | 40 + src/java/nginx/unit/Response.java | 817 +++++ src/java/nginx/unit/Session.java | 251 ++ src/java/nginx/unit/SessionAttrProxy.java | 40 + src/java/nginx/unit/Taglib.java | 44 + src/java/nginx/unit/UnitSessionCookieConfig.java | 110 + src/java/nxt_jni.c | 175 + src/java/nxt_jni.h | 55 + src/java/nxt_jni_Context.c | 164 + src/java/nxt_jni_Context.h | 23 + src/java/nxt_jni_HeaderNamesEnumeration.c | 153 + src/java/nxt_jni_HeaderNamesEnumeration.h | 19 + src/java/nxt_jni_HeadersEnumeration.c | 148 + src/java/nxt_jni_HeadersEnumeration.h | 19 + src/java/nxt_jni_InputStream.c | 230 ++ src/java/nxt_jni_InputStream.h | 15 + src/java/nxt_jni_OutputStream.c | 236 ++ src/java/nxt_jni_OutputStream.h | 17 + src/java/nxt_jni_Request.c | 658 ++++ src/java/nxt_jni_Request.h | 18 + src/java/nxt_jni_Response.c | 1105 +++++++ src/java/nxt_jni_Response.h | 18 + src/java/nxt_jni_Thread.c | 94 + src/java/nxt_jni_Thread.h | 20 + src/java/nxt_jni_URLClassLoader.c | 187 ++ src/java/nxt_jni_URLClassLoader.h | 27 + src/nxt_application.c | 14 + src/nxt_application.h | 12 + src/nxt_conf.h | 2 +- src/nxt_conf_validation.c | 71 + src/nxt_external.c | 1 + src/nxt_java.c | 462 +++ src/nxt_main_process.c | 26 + src/nxt_php_sapi.c | 1 + src/nxt_python_wsgi.c | 1 + src/nxt_router.c | 1 + src/perl/nxt_perl_psgi.c | 1 + src/ruby/nxt_ruby.c | 1 + test/java/content_type/app.java | 89 + test/java/cookies/app.java | 30 + test/java/empty/app.java | 18 + test/java/filter/app.java | 54 + test/java/forward/app.java | 138 + test/java/forward/index.html | 1 + test/java/forward/web.xml | 38 + test/java/get_header/app.java | 21 + test/java/get_header_names/app.java | 27 + test/java/get_headers/app.java | 27 + test/java/get_params/app.java | 50 + test/java/header/app.java | 34 + test/java/header_date/app.java | 22 + test/java/header_int/app.java | 22 + test/java/include/app.java | 136 + test/java/include/index.html | 1 + test/java/include/web.xml | 37 + test/java/jsp/index.jsp | 2 + test/java/mirror/app.java | 37 + test/java/path_translation/app.java | 56 + test/java/path_translation/index.html | 1 + test/java/post_params/app.java | 22 + test/java/query_string/app.java | 20 + test/java/request_listeners/app.java | 79 + test/java/session/app.java | 30 + test/java/session_inactive/app.java | 27 + test/java/session_invalidate/app.java | 23 + test/java/session_listeners/app.java | 80 + test/java/session_listeners/web.xml | 14 + test/java/url_pattern/app.java | 39 + test/java/url_pattern/web.xml | 75 + test/java/welcome_files/app.java | 67 + test/java/welcome_files/dir1/index.txt | 1 + test/java/welcome_files/dir2/default.jsp | 3 + test/java/welcome_files/dir2/index.html | 1 + test/java/welcome_files/dir3/index.txt | 1 + test/java/welcome_files/dir4/index.html | 1 + test/java/welcome_files/index.htm | 1 + test/java/welcome_files/web.xml | 27 + test/test_java_application.py | 753 +++++ test/unit.py | 66 + 95 files changed, 13360 insertions(+), 1 deletion(-) create mode 100644 auto/modules/java create mode 100644 auto/modules/java_get_jar create mode 100644 src/java/nginx/unit/Context.java create mode 100644 src/java/nginx/unit/DynamicDispatcherRequest.java create mode 100644 src/java/nginx/unit/DynamicPathRequest.java create mode 100644 src/java/nginx/unit/ForwardRequestWrapper.java create mode 100644 src/java/nginx/unit/HeaderNamesEnumeration.java create mode 100644 src/java/nginx/unit/HeadersEnumeration.java create mode 100644 src/java/nginx/unit/IncludeRequestWrapper.java create mode 100644 src/java/nginx/unit/IncludeResponseWrapper.java create mode 100644 src/java/nginx/unit/InitParams.java create mode 100644 src/java/nginx/unit/InputStream.java create mode 100644 src/java/nginx/unit/JspPropertyGroup.java create mode 100644 src/java/nginx/unit/OutputStream.java create mode 100644 src/java/nginx/unit/Request.java create mode 100644 src/java/nginx/unit/RequestAttrProxy.java create mode 100644 src/java/nginx/unit/Response.java create mode 100644 src/java/nginx/unit/Session.java create mode 100644 src/java/nginx/unit/SessionAttrProxy.java create mode 100644 src/java/nginx/unit/Taglib.java create mode 100644 src/java/nginx/unit/UnitSessionCookieConfig.java create mode 100644 src/java/nxt_jni.c create mode 100644 src/java/nxt_jni.h create mode 100644 src/java/nxt_jni_Context.c create mode 100644 src/java/nxt_jni_Context.h create mode 100644 src/java/nxt_jni_HeaderNamesEnumeration.c create mode 100644 src/java/nxt_jni_HeaderNamesEnumeration.h create mode 100644 src/java/nxt_jni_HeadersEnumeration.c create mode 100644 src/java/nxt_jni_HeadersEnumeration.h create mode 100644 src/java/nxt_jni_InputStream.c create mode 100644 src/java/nxt_jni_InputStream.h create mode 100644 src/java/nxt_jni_OutputStream.c create mode 100644 src/java/nxt_jni_OutputStream.h create mode 100644 src/java/nxt_jni_Request.c create mode 100644 src/java/nxt_jni_Request.h create mode 100644 src/java/nxt_jni_Response.c create mode 100644 src/java/nxt_jni_Response.h create mode 100644 src/java/nxt_jni_Thread.c create mode 100644 src/java/nxt_jni_Thread.h create mode 100644 src/java/nxt_jni_URLClassLoader.c create mode 100644 src/java/nxt_jni_URLClassLoader.h create mode 100644 src/nxt_java.c create mode 100644 test/java/content_type/app.java create mode 100644 test/java/cookies/app.java create mode 100644 test/java/empty/app.java create mode 100644 test/java/filter/app.java create mode 100644 test/java/forward/app.java create mode 100644 test/java/forward/index.html create mode 100644 test/java/forward/web.xml create mode 100644 test/java/get_header/app.java create mode 100644 test/java/get_header_names/app.java create mode 100644 test/java/get_headers/app.java create mode 100644 test/java/get_params/app.java create mode 100644 test/java/header/app.java create mode 100644 test/java/header_date/app.java create mode 100644 test/java/header_int/app.java create mode 100644 test/java/include/app.java create mode 100644 test/java/include/index.html create mode 100644 test/java/include/web.xml create mode 100644 test/java/jsp/index.jsp create mode 100644 test/java/mirror/app.java create mode 100644 test/java/path_translation/app.java create mode 100644 test/java/path_translation/index.html create mode 100644 test/java/post_params/app.java create mode 100644 test/java/query_string/app.java create mode 100644 test/java/request_listeners/app.java create mode 100644 test/java/session/app.java create mode 100644 test/java/session_inactive/app.java create mode 100644 test/java/session_invalidate/app.java create mode 100644 test/java/session_listeners/app.java create mode 100644 test/java/session_listeners/web.xml create mode 100644 test/java/url_pattern/app.java create mode 100644 test/java/url_pattern/web.xml create mode 100644 test/java/welcome_files/app.java create mode 100644 test/java/welcome_files/dir1/index.txt create mode 100644 test/java/welcome_files/dir2/default.jsp create mode 100644 test/java/welcome_files/dir2/index.html create mode 100644 test/java/welcome_files/dir3/index.txt create mode 100644 test/java/welcome_files/dir4/index.html create mode 100644 test/java/welcome_files/index.htm create mode 100644 test/java/welcome_files/web.xml create mode 100644 test/test_java_application.py diff --git a/auto/modules/conf b/auto/modules/conf index 409b4bea..7e004703 100644 --- a/auto/modules/conf +++ b/auto/modules/conf @@ -29,6 +29,10 @@ case "$nxt_module" in . auto/modules/nodejs ;; + java) + . auto/modules/java + ;; + *) echo echo $0: error: invalid module \"$nxt_module\". diff --git a/auto/modules/java b/auto/modules/java new file mode 100644 index 00000000..27030da1 --- /dev/null +++ b/auto/modules/java @@ -0,0 +1,458 @@ + +# Copyright (C) NGINX, Inc. + + +shift + +NXT_JAVA_HOME=${JAVA_HOME-} +NXT_JAR_REPO=http://central.maven.org/maven2/ +NXT_JAR_LOCAL_REPO=$HOME/.m2/repository/ + +for nxt_option; do + + case "$nxt_option" in + -*=*) value=`echo "$nxt_option" | sed -e 's/[-_a-zA-Z0-9]*=//'` ;; + *) value="" ;; + esac + + case "$nxt_option" in + + --module=*) NXT_JAVA_MODULE="$value" ;; + --home=*) NXT_JAVA_HOME="$value" ;; + --lib-path=*) NXT_JAVA_LIB_PATH="$value" ;; + --repo=*) NXT_JAR_REPO="$value" ;; + --local-repo=*) NXT_JAR_LOCAL_REPO="$value" ;; + --jars=*) NXT_JARS="$value" ;; + + --help) + cat << END + + --module=NAME set unit Java module name + --home=DIR set Java home directory + --lib-path=DIRECTORY set directory path to libjvm.so library + --repo=URL set Maven remote repository URL + default: "$NXT_JAR_REPO" + --local-repo=DIR set local repository directory + default: "$NXT_JAR_LOCAL_REPO" + --jars=DIR set jars install/search directory + +END + exit 0 + ;; + + *) + echo + echo $0: error: invalid Java option \"$nxt_option\" + echo + exit 1 + ;; + esac + +done + + +if [ ! -f $NXT_AUTOCONF_DATA ]; then + echo + echo Please run common $0 before configuring module \"$nxt_module\". + echo + exit 1 +fi + +. $NXT_AUTOCONF_DATA + + +NXT_JARS=${NXT_JARS=$NXT_MODULES} +NXT_JAVA_MODULE=${NXT_JAVA_MODULE=java} +NXT_JAVA_LIB_PATH=${NXT_JAVA_LIB_PATH=} + +$echo "configuring Java module" +$echo "configuring Java module ..." >> $NXT_AUTOCONF_ERR + +if [ -n "${NXT_JAVA_HOME}" ]; then + $echo "using java.home ${NXT_JAVA_HOME}" + $echo "using java.home ${NXT_JAVA_HOME}" >> $NXT_AUTOCONF_ERR + + if [ ! -d "${NXT_JAVA_HOME}" ]; then + $echo + $echo $0: error: Java home directory not found. + $echo + exit 1 + fi + + NXT_JAVA="${NXT_JAVA_HOME}/bin/java" + +else + $echo -n "checking for java executable" + $echo "checking for java executable ..." >> $NXT_AUTOCONF_ERR + + NXT_JAVA=`which java || :` + if [ -z "$NXT_JAVA" -o ! -x "$NXT_JAVA" ]; then + $echo + $echo $0: error: java executable not found. + $echo + exit 1 + fi + + $echo " found $NXT_JAVA" + $echo "found $NXT_JAVA" >> $NXT_AUTOCONF_ERR + + "$NXT_JAVA" -version + + $echo -n "checking java.home" + $echo "checking java.home ..." >> $NXT_AUTOCONF_ERR + + NXT_JAVA_HOME=`$NXT_JAVA -XshowSettings 2>&1 | grep -F -e java.home | sed -e 's/^.*= //'` + if [ -z "$NXT_JAVA_HOME" ]; then + $echo + $echo $0: error: java.home not found. + $echo + exit 1 + fi + + $echo " $NXT_JAVA_HOME" + $echo "got java.home $NXT_JAVA_HOME" >> $NXT_AUTOCONF_ERR + + if [ ! -x "${NXT_JAVA_HOME}/bin/javac" ]; then + NXT_JAVA_HOME_=${NXT_JAVA_HOME%/jre} + if [ -x "${NXT_JAVA_HOME_}/bin/javac" ]; then + $echo "adjust java.home $NXT_JAVA_HOME_" + $echo "adjust java.home $NXT_JAVA_HOME_" >> $NXT_AUTOCONF_ERR + + NXT_JAVA_HOME="$NXT_JAVA_HOME_" + fi + fi +fi + +NXT_JAVAC="${NXT_JAVA_HOME}/bin/javac" + +if [ ! -x "$NXT_JAVAC" ]; then + $echo + $echo $0: error: javac not found. + $echo + exit 1 +fi + +NXT_JAVA_INCLUDE="-I${NXT_JAVA_HOME}/include" + +case "$NXT_SYSTEM" in + Linux) + NXT_JAVA_INCLUDE="${NXT_JAVA_INCLUDE} -I${NXT_JAVA_HOME}/include/linux" + ;; + Darwin) + NXT_JAVA_INCLUDE="${NXT_JAVA_INCLUDE} -I${NXT_JAVA_HOME}/include/darwin" + ;; + FreeBSD) + NXT_JAVA_INCLUDE="${NXT_JAVA_INCLUDE} -I${NXT_JAVA_HOME}/include/freebsd" + ;; +esac + +if [ -z "$NXT_JAVA_LIB_PATH" ]; then + $echo -n "checking library path" + $echo "checking library path ..." >> $NXT_AUTOCONF_ERR + + if [ ! -x "$NXT_JAVA" ]; then + $echo + $echo $0: error: java executable not found. + $echo + exit 1 + fi + + NXT_JAVA_LIB_PATH=`$NXT_JAVA -XshowSettings 2>&1 | grep -F -e sun.boot.library.path | sed -e 's/^.*= //'` + + if [ -z "$NXT_JAVA_LIB_PATH" ]; then + $echo + $echo $0: error: library path not found. + $echo + exit 1 + fi + + NXT_JAVA_LIB_PATH="${NXT_JAVA_LIB_PATH}/server" + + $echo " $NXT_JAVA_LIB_PATH" + $echo "got library path $NXT_JAVA_LIB_PATH" >> $NXT_AUTOCONF_ERR +fi + +NXT_JAVA_LDFLAGS="-L${NXT_JAVA_LIB_PATH} -Wl,-rpath ${NXT_JAVA_LIB_PATH} -ljvm" + + +nxt_found=no + +nxt_feature="JNI" +nxt_feature_name="" +nxt_feature_run=no +nxt_feature_incs="${NXT_JAVA_INCLUDE}" +nxt_feature_libs="${NXT_JAVA_LDFLAGS}" +nxt_feature_test=" + #include + + int main() { + JNI_CreateJavaVM(NULL, NULL, NULL); + return 0; + }" + +. auto/feature + + +if [ $nxt_found = no ]; then + $echo + $echo $0: error: no JNI found. + $echo + exit 1; +fi + +NXT_JAVA_VERSION=`$NXT_JAVAC -version 2>&1` +NXT_JAVA_VERSION=${NXT_JAVA_VERSION#javac } +NXT_JAVA_INCLUDE="$NXT_JAVA_INCLUDE -I$NXT_BUILD_DIR/$NXT_JAVA_MODULE -DNXT_JAVA_VERSION=$NXT_JAVA_VERSION" + +if grep ^$NXT_JAVA_MODULE: $NXT_MAKEFILE 2>&1 > /dev/null; then + $echo + $echo $0: error: duplicate \"$NXT_JAVA_MODULE\" module configured. + $echo + exit 1; +fi + +. ./version + +NXT_UNIT_JAR=nginx-unit-jsc-${NXT_JAVA_MODULE}-$NXT_VERSION.jar + +NXT_JAVA_BUILD_CP=$NXT_BUILD_DIR/$NXT_JAVA_MODULE +NXT_JAVA_INSTALL_JARS= +NXT_JAVA_UNINSTALL_JARS= + +NXT_JAVA_JARS=$NXT_BUILD_DIR/$NXT_JAVA_MODULE/nxt_jars.h +mkdir -p $NXT_BUILD_DIR/$NXT_JAVA_MODULE + +cat << END > $NXT_JAVA_JARS +#ifndef _NXT_JAVA_JARS_INCLUDED_ +#define _NXT_JAVA_JARS_INCLUDED_ + +#define NXT_JARS "$NXT_JARS" + +static const char *nxt_java_system_jars[] = { +END + +NXT_TOMCAT_VERSION=9.0.13 + +NXT_JAR_VERSION=$NXT_TOMCAT_VERSION + +NXT_JAR_NAME=tomcat-servlet-api +NXT_JAR_NAMESPACE=org/apache/tomcat/ +. auto/modules/java_get_jar + +NXT_JAR_NAME=tomcat-el-api +. auto/modules/java_get_jar + +NXT_JAR_NAME=tomcat-jsp-api +. auto/modules/java_get_jar + +NXT_JAR_NAME=tomcat-jasper +. auto/modules/java_get_jar + +NXT_JAR_NAME=tomcat-jasper-el +. auto/modules/java_get_jar + +NXT_JAR_NAME=tomcat-juli +. auto/modules/java_get_jar + +NXT_JAR_NAME=tomcat-api +. auto/modules/java_get_jar + +NXT_JAR_NAME=tomcat-util-scan +. auto/modules/java_get_jar + +NXT_JAR_NAME=tomcat-util +. auto/modules/java_get_jar + +NXT_JAR_NAME=ecj +NXT_JAR_VERSION=3.13.102 +NXT_JAR_NAMESPACE=org/eclipse/jdt/ +. auto/modules/java_get_jar + +cat << END >> $NXT_JAVA_JARS + NULL +}; + +static const char *nxt_java_unit_jars[] = { + "$NXT_UNIT_JAR", +END + +NXT_JAR_VERSION=9.4.12.v20180830 +NXT_JAR_NAMESPACE=org/eclipse/jetty/ + +NXT_JAR_NAME=jetty-util +. auto/modules/java_get_jar + +NXT_JAR_NAME=jetty-server +. auto/modules/java_get_jar + +NXT_JAR_NAME=jetty-http +. auto/modules/java_get_jar + +NXT_JAR_NAME=classgraph +NXT_JAR_VERSION=4.4.11 +NXT_JAR_NAMESPACE=io/github/classgraph/ +. auto/modules/java_get_jar + +cat << END >> $NXT_JAVA_JARS + NULL +}; + +#endif /* _NXT_JAVA_JARS_INCLUDED_ */ +END + +$echo " + Java module: ${NXT_JAVA_MODULE}.unit.so" + +. auto/cc/deps + +$echo >> $NXT_MAKEFILE + +NXT_JAVA_MODULE_SRCS=" \ + src/nxt_java.c \ + src/java/nxt_jni.c \ + src/java/nxt_jni_Context.c \ + src/java/nxt_jni_HeaderNamesEnumeration.c \ + src/java/nxt_jni_HeadersEnumeration.c \ + src/java/nxt_jni_InputStream.c \ + src/java/nxt_jni_OutputStream.c \ + src/java/nxt_jni_Request.c \ + src/java/nxt_jni_Response.c \ + src/java/nxt_jni_Thread.c \ + src/java/nxt_jni_URLClassLoader.c \ +" + +# The Java module object files. + +nxt_objs=$NXT_BUILD_DIR/src/nxt_unit.o + +for nxt_src in $NXT_JAVA_MODULE_SRCS; do + + nxt_obj=${nxt_src%.c}-$NXT_JAVA_MODULE.o + nxt_dep=${nxt_src%.c}-$NXT_JAVA_MODULE.dep + nxt_dep_flags=`nxt_gen_dep_flags` + nxt_dep_post=`nxt_gen_dep_post` + nxt_objs="$nxt_objs $NXT_BUILD_DIR/$nxt_obj" + + cat << END >> $NXT_MAKEFILE + +$NXT_BUILD_DIR/$nxt_obj: $nxt_src + mkdir -p $NXT_BUILD_DIR/src/java + \$(CC) -c \$(CFLAGS) \$(NXT_INCS) $NXT_JAVA_INCLUDE \\ + $nxt_dep_flags \\ + -o $NXT_BUILD_DIR/$nxt_obj $nxt_src + $nxt_dep_post + +-include $NXT_BUILD_DIR/$nxt_dep + +END + +done + +NXT_JAVA_SRCS=" \ + src/java/nginx/unit/Context.java \ + src/java/nginx/unit/DynamicDispatcherRequest.java \ + src/java/nginx/unit/DynamicPathRequest.java \ + src/java/nginx/unit/ForwardRequestWrapper.java \ + src/java/nginx/unit/HeaderNamesEnumeration.java \ + src/java/nginx/unit/HeadersEnumeration.java \ + src/java/nginx/unit/IncludeRequestWrapper.java \ + src/java/nginx/unit/IncludeResponseWrapper.java \ + src/java/nginx/unit/InitParams.java \ + src/java/nginx/unit/InputStream.java \ + src/java/nginx/unit/JspPropertyGroup.java \ + src/java/nginx/unit/OutputStream.java \ + src/java/nginx/unit/Request.java \ + src/java/nginx/unit/RequestAttrProxy.java \ + src/java/nginx/unit/Response.java \ + src/java/nginx/unit/Session.java \ + src/java/nginx/unit/SessionAttrProxy.java \ + src/java/nginx/unit/Taglib.java \ + src/java/nginx/unit/UnitSessionCookieConfig.java \ +" + +cat << END >> $NXT_MAKEFILE + +.PHONY: ${NXT_JAVA_MODULE} +.PHONY: ${NXT_JAVA_MODULE}-install +.PHONY: ${NXT_JAVA_MODULE}-uninstall + +all: ${NXT_JAVA_MODULE} + +${NXT_JAVA_MODULE}: $NXT_BUILD_DIR/${NXT_JAVA_MODULE}.unit.so \ + $NXT_BUILD_DIR/$NXT_UNIT_JAR + +$NXT_BUILD_DIR/${NXT_JAVA_MODULE}.unit.so: $nxt_objs + \$(NXT_MODULE_LINK) -o $NXT_BUILD_DIR/${NXT_JAVA_MODULE}.unit.so \\ + $nxt_objs $NXT_JAVA_LDFLAGS $NXT_LD_OPT + + +install: ${NXT_JAVA_MODULE}-install + +${NXT_JAVA_MODULE}-install: $NXT_BUILD_DIR/${NXT_JAVA_MODULE}.unit.so \\ + $NXT_BUILD_DIR/$NXT_UNIT_JAR java-shared-install + install -d \$(DESTDIR)$NXT_MODULES + install -p $NXT_BUILD_DIR/${NXT_JAVA_MODULE}.unit.so \\ + \$(DESTDIR)$NXT_MODULES/ + install -d \$(DESTDIR)$NXT_JARS + install -p -m 0644 $NXT_BUILD_DIR/$NXT_UNIT_JAR \$(DESTDIR)$NXT_JARS/ + + +uninstall: ${NXT_JAVA_MODULE}-uninstall + +${NXT_JAVA_MODULE}-uninstall: java-shared-uninstall + rm -f \$(DESTDIR)$NXT_MODULES/${NXT_JAVA_MODULE}.unit.so + @rmdir -p \$(DESTDIR)$NXT_MODULES 2>/dev/null || true + rm -f \$(DESTDIR)$NXT_JARS/$NXT_UNIT_JAR + @rmdir -p \$(DESTDIR)$NXT_JARS 2>/dev/null || true + +END + +if ! grep ^$NXT_BUILD_DIR/$NXT_UNIT_JAR: $NXT_MAKEFILE 2>&1 > /dev/null; then + + cat << END >> $NXT_MAKEFILE + +.INTERMEDIATE: $NXT_BUILD_DIR/$NXT_JAVA_MODULE/.classes + +NXT_JAVA_SRCS = $NXT_JAVA_SRCS + +$NXT_BUILD_DIR/$NXT_JAVA_MODULE/.classes: \$(NXT_JAVA_SRCS) + rm -rf $NXT_BUILD_DIR/$NXT_JAVA_MODULE/nginx + $NXT_JAVAC -d $NXT_BUILD_DIR/$NXT_JAVA_MODULE -cp $NXT_JAVA_BUILD_CP \\ + \$(NXT_JAVA_SRCS) + +$NXT_BUILD_DIR/$NXT_JAVA_MODULE/META-INF/LICENSE: LICENSE + mkdir -p $NXT_BUILD_DIR/$NXT_JAVA_MODULE/META-INF + cp -p LICENSE \$@ + +$NXT_BUILD_DIR/$NXT_JAVA_MODULE/META-INF/NOTICE: NOTICE + mkdir -p $NXT_BUILD_DIR/$NXT_JAVA_MODULE/META-INF + cp -p NOTICE \$@ + + +$NXT_BUILD_DIR/$NXT_UNIT_JAR: $NXT_BUILD_DIR/$NXT_JAVA_MODULE/.classes \\ + $NXT_BUILD_DIR/$NXT_JAVA_MODULE/META-INF/LICENSE \\ + $NXT_BUILD_DIR/$NXT_JAVA_MODULE/META-INF/NOTICE + $NXT_JAVA_HOME/bin/jar c -C $NXT_BUILD_DIR/$NXT_JAVA_MODULE META-INF \\ + -C $NXT_BUILD_DIR/$NXT_JAVA_MODULE nginx/unit > \$@ + +END + +fi + +if ! grep ^java-shared-install: $NXT_MAKEFILE 2>&1 > /dev/null; then + + cat << END >> $NXT_MAKEFILE + +.PHONY: java-shared-install +.PHONY: java-shared-uninstall + +java-shared-install: $NXT_JAVA_INSTALL_JARS + install -d \$(DESTDIR)$NXT_JARS + install -p -m 0644 $NXT_JAVA_INSTALL_JARS \$(DESTDIR)$NXT_JARS/ + +java-shared-uninstall: + rm -f $NXT_JAVA_UNINSTALL_JARS + @rmdir -p \$(DESTDIR)$NXT_JARS 2>/dev/null || true + +END + +fi diff --git a/auto/modules/java_get_jar b/auto/modules/java_get_jar new file mode 100644 index 00000000..c61d0a53 --- /dev/null +++ b/auto/modules/java_get_jar @@ -0,0 +1,33 @@ + +# Copyright (C) NGINX, Inc. + +# NXT_JAR_NAME= +# NXT_JAR_VERSION= +# NXT_JAR_NAMESPACE= +# NXT_JAR_REPO=http://central.maven.org/maven2/ +# NXT_JAR_LOCAL_REPO=$HOME/.m2/repository/ + +NXT_JAR_FILE=${NXT_JAR_NAME}-${NXT_JAR_VERSION}.jar +NXT_JAR_LOCAL="${NXT_JAR_LOCAL_REPO}${NXT_JAR_NAMESPACE}${NXT_JAR_NAME}/${NXT_JAR_VERSION}/${NXT_JAR_FILE}" +NXT_JAR_URL=${NXT_JAR_REPO}${NXT_JAR_NAMESPACE}${NXT_JAR_NAME}/${NXT_JAR_VERSION}/${NXT_JAR_FILE} + +if [ ! -f "$NXT_BUILD_DIR/$NXT_JAR_FILE" ]; then + if [ ! -f "$NXT_JAR_LOCAL" ]; then + $echo "getting remote $NXT_JAR_FILE ... " + $echo "getting remote $NXT_JAR_FILE ..." >> $NXT_AUTOCONF_ERR + + mkdir -p "${NXT_JAR_LOCAL_REPO}${NXT_JAR_NAMESPACE}${NXT_JAR_NAME}/${NXT_JAR_VERSION}/" + curl --progress-bar "$NXT_JAR_URL" -o "$NXT_JAR_LOCAL" + else + $echo "getting local $NXT_JAR_FILE" + $echo "getting local $NXT_JAR_FILE ..." >> $NXT_AUTOCONF_ERR + fi + + cp "$NXT_JAR_LOCAL" "$NXT_BUILD_DIR/$NXT_JAR_FILE" +fi + +NXT_JAVA_BUILD_CP="${NXT_JAVA_BUILD_CP}:$NXT_BUILD_DIR/$NXT_JAR_FILE" +NXT_JAVA_INSTALL_JARS="$NXT_JAVA_INSTALL_JARS $NXT_BUILD_DIR/$NXT_JAR_FILE" +NXT_JAVA_UNINSTALL_JARS="$NXT_JAVA_UNINSTALL_JARS \$(DESTDIR)$NXT_JARS/$NXT_JAR_FILE" + +$echo " \"$NXT_JAR_FILE\"," >> $NXT_JAVA_JARS diff --git a/src/java/nginx/unit/Context.java b/src/java/nginx/unit/Context.java new file mode 100644 index 00000000..643a336b --- /dev/null +++ b/src/java/nginx/unit/Context.java @@ -0,0 +1,3502 @@ +package nginx.unit; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ClassInfoList; +import io.github.classgraph.ScanResult; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; + +import java.lang.ClassLoader; +import java.lang.ClassNotFoundException; +import java.lang.IllegalArgumentException; +import java.lang.IllegalStateException; +import java.lang.reflect.Constructor; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; + +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.UUID; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.zip.ZipEntry; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.FilterRegistration.Dynamic; +import javax.servlet.FilterRegistration; +import javax.servlet.MultipartConfigElement; +import javax.servlet.Registration; +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextAttributeEvent; +import javax.servlet.ServletContextAttributeListener; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.ServletRegistration; +import javax.servlet.ServletResponse; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.ServletSecurityElement; +import javax.servlet.SessionCookieConfig; +import javax.servlet.SessionTrackingMode; +import javax.servlet.annotation.HandlesTypes; +import javax.servlet.annotation.WebInitParam; +import javax.servlet.annotation.WebServlet; +import javax.servlet.annotation.WebFilter; +import javax.servlet.annotation.WebListener; +import javax.servlet.descriptor.JspConfigDescriptor; +import javax.servlet.descriptor.JspPropertyGroupDescriptor; +import javax.servlet.descriptor.TaglibDescriptor; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionIdListener; +import javax.servlet.http.HttpSessionListener; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.eclipse.jetty.http.MimeTypes; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import org.apache.jasper.servlet.JspServlet; +import org.apache.jasper.servlet.JasperInitializer; + + +public class Context implements ServletContext, InitParams +{ + public final static int SERVLET_MAJOR_VERSION = 3; + public final static int SERVLET_MINOR_VERSION = 1; + + private String context_path_ = ""; + private String server_info_ = "unit"; + private String app_version_ = ""; + private MimeTypes mime_types_; + private boolean metadata_complete_ = false; + private boolean welcome_files_list_found_ = false; + private boolean ctx_initialized_ = false; + + private ClassLoader loader_; + private File webapp_; + private File extracted_dir_; + private File temp_dir_; + + private final Map init_params_ = new HashMap<>(); + private final Map attributes_ = new HashMap<>(); + + private final Map parsed_patterns_ = new HashMap<>(); + + private final List filters_ = new ArrayList<>(); + private final Map name2filter_ = new HashMap<>(); + private final List filter_maps_ = new ArrayList<>(); + + private final List servlets_ = new ArrayList<>(); + private final Map name2servlet_ = new HashMap<>(); + private final Map pattern2servlet_ = new HashMap<>(); + private final Map exact2servlet_ = new HashMap<>(); + private final List prefix_patterns_ = new ArrayList<>(); + private final Map suffix2servlet_ = new HashMap<>(); + private ServletReg default_servlet_; + private ServletReg system_default_servlet_; + + private final List welcome_files_ = new ArrayList<>(); + + private final Map exception2location_ = new HashMap<>(); + private final Map error2location_ = new HashMap<>(); + + public static final Class[] LISTENER_TYPES = new Class[] { + ServletContextListener.class, + ServletContextAttributeListener.class, + ServletRequestListener.class, + ServletRequestAttributeListener.class, + HttpSessionAttributeListener.class, + HttpSessionIdListener.class, + HttpSessionListener.class + }; + + private final List pending_listener_classnames_ = new ArrayList<>(); + private final Set listener_classnames_ = new HashSet<>(); + + private final List ctx_listeners_ = new ArrayList<>(); + private final List destroy_listeners_ = new ArrayList<>(); + private final List ctx_attr_listeners_ = new ArrayList<>(); + private final List req_init_listeners_ = new ArrayList<>(); + private final List req_destroy_listeners_ = new ArrayList<>(); + private final List req_attr_listeners_ = new ArrayList<>(); + + private ServletRequestAttributeListener req_attr_proxy_ = null; + + private final List sess_attr_listeners_ = new ArrayList<>(); + private final List sess_id_listeners_ = new ArrayList<>(); + private final List sess_listeners_ = new ArrayList<>(); + + private HttpSessionAttributeListener sess_attr_proxy_ = null; + + private final SessionCookieConfig session_cookie_config_ = new UnitSessionCookieConfig(); + private final Set default_session_tracking_modes_ = new HashSet<>(); + private Set session_tracking_modes_ = default_session_tracking_modes_; + private int session_timeout_ = 60; + + private final Map sessions_ = new HashMap<>(); + + private static final String WEB_INF = "WEB-INF/"; + private static final String WEB_INF_CLASSES = WEB_INF + "classes/"; + private static final String WEB_INF_LIB = WEB_INF + "lib/"; + + private class PrefixPattern implements Comparable + { + public final String pattern; + public final ServletReg servlet; + + public PrefixPattern(String p, ServletReg s) + { + pattern = p; + servlet = s; + } + + public boolean match(String url) + { + return url.startsWith(pattern) && ( + url.length() == pattern.length() + || url.charAt(pattern.length()) == '/'); + } + + @Override + public int compareTo(PrefixPattern p) + { + return p.pattern.length() - pattern.length(); + } + } + + private class StaticServlet extends HttpServlet + { + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + doGet(request, response); + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + String path = null; + + if (request.getDispatcherType() == DispatcherType.INCLUDE) { + path = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); + } + + if (path == null) { + path = request.getServletPath(); + } + + /* + 10.6 Web Application Archive File + ... + This directory [META-INF] must not be directly served as + content by the container in response to a Web client's request, + though its contents are visible to servlet code via the + getResource and getResourceAsStream calls on the + ServletContext. Also, any requests to access the resources in + META-INF directory must be returned with a SC_NOT_FOUND(404) + response. + */ + if (request.getDispatcherType() == DispatcherType.REQUEST + && (path.equals("/WEB-INF") || path.startsWith("/WEB-INF/") + || path.equals("/META-INF") || path.startsWith("/META-INF/"))) + { + response.sendError(response.SC_NOT_FOUND); + return; + } + + if (path.startsWith("/")) { + path = path.substring(1); + } + + File f = new File(webapp_, path); + if (!f.exists()) { + if (request.getDispatcherType() == DispatcherType.INCLUDE) { + /* + 9.3 The Include Method + ... + If the default servlet is the target of a + RequestDispatch.include() and the requested resource + does not exist, then the default servlet MUST throw + FileNotFoundException. + */ + + throw new FileNotFoundException(); + } + + response.sendError(response.SC_NOT_FOUND); + return; + } + + long ims = request.getDateHeader("If-Modified-Since"); + long lm = f.lastModified(); + + if (lm < ims) { + response.sendError(response.SC_NOT_MODIFIED); + return; + } + + response.setDateHeader("Last-Modified", f.lastModified()); + + if (f.isDirectory()) { + String url = request.getRequestURL().toString(); + if (!url.endsWith("/")) { + response.setHeader("Location", url + "/"); + response.sendError(response.SC_FOUND); + return; + } + + String[] ls = f.list(); + + PrintWriter writer = response.getWriter(); + + for (String n : ls) { + writer.println("" + n + "
"); + } + + writer.close(); + + } else { + response.setContentLengthLong(f.length()); + + InputStream is = new FileInputStream(f); + byte[] buffer = new byte[response.getBufferSize()]; + ServletOutputStream os = response.getOutputStream(); + while (true) { + int read = is.read(buffer); + if (read == -1) { + break; + } + os.write(buffer, 0, read); + } + + os.close(); + } + } + } + + public static Context start(String webapp, URL[] classpaths) + throws Exception + { + Context ctx = new Context(); + + ctx.loadApp(webapp, classpaths); + ctx.initialized(); + + return ctx; + } + + public Context() + { + default_session_tracking_modes_.add(SessionTrackingMode.COOKIE); + + context_path_ = System.getProperty("nginx.unit.context.path", "").trim(); + + if (context_path_.endsWith("/")) { + context_path_ = context_path_.substring(0, context_path_.length() - 1); + } + + if (!context_path_.isEmpty() && !context_path_.startsWith("/")) { + context_path_ = "/" + context_path_; + } + + if (context_path_.isEmpty()) { + session_cookie_config_.setPath("/"); + } else { + session_cookie_config_.setPath(context_path_); + } + } + + public void loadApp(String webapp, URL[] classpaths) + throws Exception + { + File root = new File(webapp); + if (!root.exists()) { + throw new FileNotFoundException( + "Unable to determine code source archive from " + root); + } + + ArrayList url_list = new ArrayList<>(); + + for (URL u : classpaths) { + url_list.add(u); + } + + if (!root.isDirectory()) { + root = extractWar(root); + extracted_dir_ = root; + } + + webapp_ = root; + + Path tmpDir = Files.createTempDirectory("webapp"); + temp_dir_ = tmpDir.toFile(); + setAttribute(ServletContext.TEMPDIR, temp_dir_); + + File web_inf_classes = new File(root, WEB_INF_CLASSES); + if (web_inf_classes.exists() && web_inf_classes.isDirectory()) { + url_list.add(new URL("file:" + root.getAbsolutePath() + "/" + WEB_INF_CLASSES)); + } + + File lib = new File(root, WEB_INF_LIB); + File[] libs = lib.listFiles(); + + if (libs != null) { + for (File l : libs) { + url_list.add(new URL("file:" + l.getAbsolutePath())); + } + } + + URL[] urls = new URL[url_list.size()]; + + for (int i = 0; i < url_list.size(); i++) { + urls[i] = url_list.get(i); + trace("archives: " + urls[i]); + } + + String custom_listener = System.getProperty("nginx.unit.context.listener", "").trim(); + if (!custom_listener.isEmpty()) { + pending_listener_classnames_.add(custom_listener); + } + + processWebXml(root); + + loader_ = new AppClassLoader(urls, + Context.class.getClassLoader().getParent()); + + ClassLoader old = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(loader_); + + try { + for (String listener_classname : pending_listener_classnames_) { + addListener(listener_classname); + } + + ScanResult scan_res = null; + + if (!metadata_complete_) { + ClassGraph classgraph = new ClassGraph() + //.verbose() + .overrideClassLoaders(loader_) + .ignoreParentClassLoaders() + .enableClassInfo() + .enableAnnotationInfo() + //.enableSystemPackages() + .whitelistModules("javax.*") + //.enableAllInfo() + ; + + String verbose = System.getProperty("nginx.unit.context.classgraph.verbose", "").trim(); + + if (verbose.equals("true")) { + classgraph.verbose(); + } + + scan_res = classgraph.scan(); + + loadInitializers(scan_res); + } + + if (!metadata_complete_) { + scanClasses(scan_res); + } + + /* + 8.1.6 Other annotations / conventions + ... + By default all applications will have index.htm(l) and index.jsp + in the list of welcome-file-list. The descriptor may to be used + to override these default settings. + */ + if (!welcome_files_list_found_) { + welcome_files_.add("index.htm"); + welcome_files_.add("index.html"); + welcome_files_.add("index.jsp"); + } + + ServletReg jsp_servlet = name2servlet_.get("jsp"); + if (jsp_servlet == null) { + jsp_servlet = new ServletReg("jsp", JspServlet.class); + jsp_servlet.system_jsp_servlet_ = true; + servlets_.add(jsp_servlet); + name2servlet_.put("jsp", jsp_servlet); + } + + if (jsp_servlet.getClassName() == null) { + jsp_servlet.setClass(JspServlet.class); + jsp_servlet.system_jsp_servlet_ = true; + } + + if (jsp_servlet.patterns_.isEmpty()) { + parseURLPattern("*.jsp", jsp_servlet); + parseURLPattern("*.jspx", jsp_servlet); + } + + ServletReg def_servlet = name2servlet_.get("default"); + if (def_servlet == null) { + def_servlet = new ServletReg("default", new StaticServlet()); + def_servlet.servlet_ = new StaticServlet(); + servlets_.add(def_servlet); + name2servlet_.put("default", def_servlet); + } + + if (def_servlet.getClassName() == null) { + def_servlet.setClass(StaticServlet.class); + def_servlet.servlet_ = new StaticServlet(); + } + + system_default_servlet_ = def_servlet; + + for (PrefixPattern p : prefix_patterns_) { + /* + Optimization: add prefix patterns to exact2servlet_ map. + This should not affect matching result because full path + is the longest matched prefix. + */ + if (!exact2servlet_.containsKey(p.pattern)) { + trace("adding prefix pattern " + p.pattern + " to exact patterns map"); + exact2servlet_.put(p.pattern, p.servlet); + } + } + + Collections.sort(prefix_patterns_); + } finally { + Thread.currentThread().setContextClassLoader(old); + } + } + + private static class AppClassLoader extends URLClassLoader + { + static { + ClassLoader.registerAsParallelCapable(); + } + + private final static String[] system_prefix = + { + "java/", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2) + "javax/", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2) + "org/w3c/", // needed by javax.xml + "org/xml/", // needed by javax.xml + }; + + private ClassLoader system_loader; + + public AppClassLoader(URL[] urls, ClassLoader parent) + { + super(urls, parent); + + ClassLoader j = String.class.getClassLoader(); + if (j == null) { + j = getSystemClassLoader(); + while (j.getParent() != null) { + j = j.getParent(); + } + } + system_loader = j; + } + + private boolean isSystemPath(String path) + { + int i = Arrays.binarySearch(system_prefix, path); + + if (i >= 0) { + return true; + } + + i = -i - 1; + + if (i > 0) { + return path.startsWith(system_prefix[i - 1]); + } + + return false; + } + + @Override + public URL getResource(String name) + { + URL res; + + String s = "getResource: " + name; + trace(0, s, s.length()); + + /* + This is a required for compatibility with Tomcat which + stores all resources prefixed with '/' and application code + may try to get resource with leading '/' (like Jira). Jetty + also has such workaround in WebAppClassLoader.getResource(). + */ + if (name.startsWith("/")) { + name = name.substring(1); + } + + if (isSystemPath(name)) { + return super.getResource(name); + } + + res = system_loader.getResource(name); + if (res != null) { + return res; + } + + res = findResource(name); + if (res != null) { + return res; + } + + return super.getResource(name); + } + + @Override + protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + synchronized (this) { + Class res = findLoadedClass(name); + if (res != null) { + return res; + } + + try { + res = system_loader.loadClass(name); + + if (resolve) { + resolveClass(res); + } + + return res; + } catch (ClassNotFoundException e) { + } + + String path = name.replace('.', '/').concat(".class"); + + if (isSystemPath(path)) { + return super.loadClass(name, resolve); + } + + URL url = findResource(path); + + if (url != null) { + res = super.findClass(name); + + if (resolve) { + resolveClass(res); + } + + return res; + } + + return super.loadClass(name, resolve); + } + + } + } + + private File extractWar(File war) throws IOException + { + Path tmpDir = Files.createTempDirectory("webapp"); + + JarFile jf = new JarFile(war); + + for (Enumeration en = jf.entries(); en.hasMoreElements();) { + JarEntry e = en.nextElement(); + long mod_time = e.getTime(); + Path ep = tmpDir.resolve(e.getName()); + Path p; + if (e.isDirectory()) { + p = ep; + } else { + p = ep.getParent(); + } + + if (!p.toFile().isDirectory()) { + Files.createDirectories(p); + } + + if (!e.isDirectory()) { + Files.copy(jf.getInputStream(e), ep, + StandardCopyOption.REPLACE_EXISTING); + } + + if (mod_time > 0) { + ep.toFile().setLastModified(mod_time); + } + } + + return tmpDir.toFile(); + } + + private class CtxFilterChain implements FilterChain + { + private int filter_index_ = 0; + private final ServletReg servlet_; + private final List filters_; + + CtxFilterChain(ServletReg servlet, String path, DispatcherType dtype) + { + servlet_ = servlet; + + List filters = new ArrayList<>(); + + for (FilterMap m : filter_maps_) { + if (filters.indexOf(m.filter_) != -1) { + continue; + } + + if (!m.dtypes_.contains(dtype)) { + continue; + } + + if (m.pattern_.match(path)) { + filters.add(m.filter_); + + trace("add filter (matched): " + m.filter_.getName()); + } + } + + for (FilterMap m : servlet.filters_) { + if (filters.indexOf(m.filter_) != -1) { + continue; + } + + if (!m.dtypes_.contains(dtype)) { + continue; + } + + filters.add(m.filter_); + + trace("add filter (servlet): " + m.filter_.getName()); + } + + filters_ = filters; + } + + @Override + public void doFilter (ServletRequest request, ServletResponse response) + throws IOException, ServletException + { + if (filter_index_ < filters_.size()) { + filters_.get(filter_index_++).filter_.doFilter(request, response, this); + + return; + } + + servlet_.service(request, response); + } + } + + private ServletReg findServlet(String path, DynamicPathRequest req) + { + /* + 12.1 Use of URL Paths + ... + 1. The container will try to find an exact match of the path of the + request to the path of the servlet. A successful match selects + the servlet. + */ + ServletReg servlet = exact2servlet_.get(path); + if (servlet != null) { + trace("findServlet: '" + path + "' exact matched pattern"); + req.setServletPath(path, null); + return servlet; + } + + /* + 2. The container will recursively try to match the longest + path-prefix. This is done by stepping down the path tree a + directory at a time, using the '/' character as a path separator. + The longest match determines the servlet selected. + */ + for (PrefixPattern p : prefix_patterns_) { + if (p.match(path)) { + trace("findServlet: '" + path + "' matched prefix pattern '" + p.pattern + "'"); + if (p.pattern.length() == path.length()) { + log("findServlet: WARNING: it is expected '" + path + "' exactly matches " + p.pattern); + req.setServletPath(path, p.pattern, null); + } else { + req.setServletPath(path, p.pattern, path.substring(p.pattern.length())); + } + return p.servlet; + } + } + + /* + 3. If the last segment in the URL path contains an extension + (e.g. .jsp), the servlet container will try to match a servlet + that handles requests for the extension. An extension is defined + as the part of the last segment after the last '.' character. + */ + int suffix_start = path.lastIndexOf('.'); + if (suffix_start != -1) { + String suffix = path.substring(suffix_start); + servlet = suffix2servlet_.get(suffix); + if (servlet != null) { + trace("findServlet: '" + path + "' matched suffix pattern"); + req.setServletPath(path, null); + return servlet; + } + } + + /* + 4. If neither of the previous three rules result in a servlet match, + the container will attempt to serve content appropriate for the + resource requested. If a "default" servlet is defined for the + application, it will be used. ... + */ + if (default_servlet_ != null) { + trace("findServlet: '" + path + "' matched default servlet"); + req.setServletPath(path, null); + return default_servlet_; + } + + trace("findServlet: '" + path + "' no servlet found"); + + /* + 10.10 Welcome Files + ... + If a Web container receives a valid partial request, the Web + container must examine the welcome file list defined in the + deployment descriptor. + ... + */ + if (path.endsWith("/")) { + + /* + The Web server must append each welcome file in the order + specified in the deployment descriptor to the partial request + and check whether a static resource in the WAR is mapped to + that request URI. + */ + for (String wf : welcome_files_) { + String wpath = path + wf; + + File f = new File(webapp_, wpath.substring(1)); + if (!f.exists()) { + continue; + } + + trace("findServlet: '" + path + "' found static welcome " + + "file '" + wf + "'"); + + /* + Even if static file found, we should try to find matching + servlet for JSP serving etc. + */ + servlet = findWelcomeServlet(wpath, true, req); + if (servlet != null) { + return servlet; + } + + req.setServletPath(wpath, null); + + return system_default_servlet_; + } + + /* + If no match is found, the Web server MUST again append each + welcome file in the order specified in the deployment + descriptor to the partial request and check if a servlet is + mapped to that request URI. The Web container must send the + request to the first resource in the WAR that matches. + */ + for (String wf : welcome_files_) { + String wpath = path + wf; + + servlet = findWelcomeServlet(wpath, false, req); + if (servlet != null) { + return servlet; + } + } + } + + trace("findServlet: '" + path + "' fallback to system default servlet"); + req.setServletPath(path, null); + + return system_default_servlet_; + } + + private ServletReg findWelcomeServlet(String path, boolean exists, + DynamicPathRequest req) + { + ServletReg servlet = exact2servlet_.get(path); + if (servlet != null) { + trace("findWelcomeServlet: '" + path + "' exact matched pattern"); + req.setServletPath(path, null); + + return servlet; + } + + int suffix_start = path.lastIndexOf('.'); + if (suffix_start == -1) { + return null; + } + + String suffix = path.substring(suffix_start); + servlet = suffix2servlet_.get(suffix); + if (servlet == null) { + return null; + } + + trace("findWelcomeServlet: '" + path + "' matched suffix pattern"); + + /* + If we want to show the directory content when + index.jsp is absent, then we have to check file + presence here. Otherwise user will get 404. + */ + + if (servlet.system_jsp_servlet_ && !exists) { + trace("findWelcomeServlet: '" + path + "' not exists"); + return null; + } + + req.setServletPath(path, null); + + return servlet; + } + + public void service(Request req, Response resp) + throws ServletException, IOException + { + ClassLoader old = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(loader_); + + ServletRequestEvent sre = null; + + try { + if (!req_init_listeners_.isEmpty()) { + sre = new ServletRequestEvent(this, req); + + for (ServletRequestListener l : req_init_listeners_) { + l.requestInitialized(sre); + } + } + + URI uri = new URI(req.getRequestURI()); + String path = uri.getPath(); + + if (!path.startsWith(context_path_) + || (path.length() > context_path_.length() + && path.charAt(context_path_.length()) != '/')) + { + trace("service: '" + path + "' not started with '" + context_path_ + "'"); + + resp.sendError(resp.SC_NOT_FOUND); + return; + } + + if (path.equals(context_path_)) { + String url = req.getRequestURL().toString(); + if (!url.endsWith("/")) { + resp.setHeader("Location", url + "/"); + resp.sendError(resp.SC_FOUND); + return; + } + } + + path = path.substring(context_path_.length()); + + ServletReg servlet = findServlet(path, req); + + FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.REQUEST); + + fc.doFilter(req, resp); + + Object code = req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); + if (code != null && code instanceof Integer) { + handleStatusCode((Integer) code, req, resp); + } + } catch (Throwable e) { + trace("service: caught " + e); + + try { + if (!resp.isCommitted() && !exception2location_.isEmpty()) { + handleException(e, req, resp); + } + + if (!resp.isCommitted()) { + resp.reset(); + resp.setStatus(resp.SC_INTERNAL_SERVER_ERROR); + resp.setContentType("text/plain"); + + PrintWriter w = resp.getWriter(); + w.println("Unhandled exception: " + e); + e.printStackTrace(w); + + w.close(); + } + } finally { + throw new ServletException(e); + } + } finally { + resp.flushBuffer(); + + try { + if (!req_destroy_listeners_.isEmpty()) { + for (ServletRequestListener l : req_destroy_listeners_) { + l.requestDestroyed(sre); + } + } + } finally { + Thread.currentThread().setContextClassLoader(old); + } + } + } + + private void handleException(Throwable e, Request req, Response resp) + throws ServletException, IOException + { + String location; + + Class cls = e.getClass(); + while (cls != null && !cls.equals(Throwable.class)) { + location = exception2location_.get(cls.getName()); + + if (location != null) { + trace("Exception " + e + " matched. Error page location: " + location); + + req.setAttribute_(RequestDispatcher.ERROR_EXCEPTION, e); + req.setAttribute_(RequestDispatcher.ERROR_EXCEPTION_TYPE, e.getClass()); + req.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI, req.getRequestURI()); + req.setAttribute_(RequestDispatcher.ERROR_STATUS_CODE, resp.SC_INTERNAL_SERVER_ERROR); + + handleError(location, req, resp); + + return; + } + + cls = cls.getSuperclass(); + } + + if (ServletException.class.isAssignableFrom(e.getClass())) { + ServletException se = (ServletException) e; + + handleException(se.getRootCause(), req, resp); + } + } + + private void handleStatusCode(int code, Request req, Response resp) + throws ServletException, IOException + { + String location; + + location = error2location_.get(code); + + if (location != null) { + trace("Status " + code + " matched. Error page location: " + location); + + req.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI, req.getRequestURI()); + + handleError(location, req, resp); + } + } + + public void handleError(String location, Request req, Response resp) + throws ServletException, IOException + { + try { + log("handleError: " + location); + + String filter_path = req.getFilterPath(); + String servlet_path = req.getServletPath(); + String path_info = req.getPathInfo(); + String req_uri = req.getRequestURI(); + DispatcherType dtype = req.getDispatcherType(); + + URI uri; + + if (location.startsWith("/")) { + uri = new URI(context_path_ + location); + } else { + uri = new URI(req_uri).resolve(location); + } + + req.setRequestURI(uri.getRawPath()); + req.setDispatcherType(DispatcherType.ERROR); + + String path = uri.getPath().substring(context_path_.length()); + + ServletReg servlet = findServlet(path, req); + + FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.ERROR); + + fc.doFilter(req, resp); + + req.setServletPath(filter_path, servlet_path, path_info); + req.setRequestURI(req_uri); + req.setDispatcherType(dtype); + } catch (URISyntaxException e) { + throw new ServletException(e); + } + } + + private void processWebXml(File root) throws Exception + { + if (root.isDirectory()) { + File web_xml = new File(root, "WEB-INF/web.xml"); + if (web_xml.exists()) { + trace("start: web.xml file found"); + + InputStream is = new FileInputStream(web_xml); + + processWebXml(is); + + is.close(); + } + } else { + JarFile jf = new JarFile(root); + ZipEntry ze = jf.getEntry("WEB-INF/web.xml"); + + if (ze == null) { + trace("start: web.xml entry NOT found"); + } else { + trace("start: web.xml entry found"); + + processWebXml(jf.getInputStream(ze)); + } + + jf.close(); + } + } + + private void processWebXml(InputStream is) + throws ParserConfigurationException, SAXException, IOException + { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + + Document doc = builder.parse(is); + + Element doc_elem = doc.getDocumentElement(); + String doc_elem_name = doc_elem.getNodeName(); + if (!doc_elem_name.equals("web-app")) { + throw new RuntimeException("Invalid web.xml: 'web-app' element expected, not '" + doc_elem_name + "'"); + } + + metadata_complete_ = doc_elem.getAttribute("metadata-complete").equals("true"); + app_version_ = doc_elem.getAttribute("version"); + + NodeList welcome_file_lists = doc_elem.getElementsByTagName("welcome-file-list"); + + if (welcome_file_lists.getLength() > 0) { + welcome_files_list_found_ = true; + } + + for (int i = 0; i < welcome_file_lists.getLength(); i++) { + Element list_el = (Element) welcome_file_lists.item(i); + NodeList files = list_el.getElementsByTagName("welcome-file"); + for (int j = 0; j < files.getLength(); j++) { + Node node = files.item(j); + String wf = node.getTextContent().trim(); + + /* + 10.10 Welcome Files + ... + The welcome file list is an ordered list of partial URLs + with no trailing or leading /. + */ + + if (wf.startsWith("/") || wf.endsWith("/")) { + log("invalid welcome file: " + wf); + continue; + } + + welcome_files_.add(wf); + } + } + + NodeList context_params = doc_elem.getElementsByTagName("context-param"); + for (int i = 0; i < context_params.getLength(); i++) { + processXmlInitParam(this, (Element) context_params.item(i)); + } + + NodeList filters = doc_elem.getElementsByTagName("filter"); + + for (int i = 0; i < filters.getLength(); i++) { + Element filter_el = (Element) filters.item(i); + NodeList names = filter_el.getElementsByTagName("filter-name"); + if (names == null || names.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'filter-name' tag not found"); + } + + String filter_name = names.item(0).getTextContent().trim(); + trace("filter-name=" + filter_name); + + FilterReg reg = new FilterReg(filter_name); + + NodeList child_nodes = filter_el.getChildNodes(); + for(int j = 0; j < child_nodes.getLength(); j++) { + Node child_node = child_nodes.item(j); + String tag_name = child_node.getNodeName(); + + if (tag_name.equals("filter-class")) { + reg.setClassName(child_node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("async-supported")) { + reg.setAsyncSupported(child_node.getTextContent().trim() + .equals("true")); + continue; + } + + if (tag_name.equals("init-param")) { + processXmlInitParam(reg, (Element) child_node); + continue; + } + } + + filters_.add(reg); + name2filter_.put(filter_name, reg); + } + + NodeList filter_mappings = doc_elem.getElementsByTagName("filter-mapping"); + + for(int i = 0; i < filter_mappings.getLength(); i++) { + Element mapping_el = (Element) filter_mappings.item(i); + NodeList names = mapping_el.getElementsByTagName("filter-name"); + if (names == null || names.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'filter-name' tag not found"); + } + + String filter_name = names.item(0).getTextContent().trim(); + trace("filter-name=" + filter_name); + + FilterReg reg = name2filter_.get(filter_name); + if (reg == null) { + throw new RuntimeException("Invalid web.xml: filter '" + filter_name + "' not found"); + } + + EnumSet dtypes = EnumSet.noneOf(DispatcherType.class); + NodeList dispatchers = mapping_el.getElementsByTagName("dispatcher"); + for (int j = 0; j < dispatchers.getLength(); j++) { + Node child_node = dispatchers.item(j); + dtypes.add(DispatcherType.valueOf(child_node.getTextContent().trim())); + } + + if (dtypes.isEmpty()) { + dtypes.add(DispatcherType.REQUEST); + } + + boolean match_after = false; + + NodeList child_nodes = mapping_el.getChildNodes(); + for (int j = 0; j < child_nodes.getLength(); j++) { + Node child_node = child_nodes.item(j); + String tag_name = child_node.getNodeName(); + + if (tag_name.equals("url-pattern")) { + reg.addMappingForUrlPatterns(dtypes, match_after, child_node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("servlet-name")) { + reg.addMappingForServletNames(dtypes, match_after, child_node.getTextContent().trim()); + continue; + } + } + } + + NodeList servlets = doc_elem.getElementsByTagName("servlet"); + + for (int i = 0; i < servlets.getLength(); i++) { + Element servlet_el = (Element) servlets.item(i); + NodeList names = servlet_el.getElementsByTagName("servlet-name"); + if (names == null || names.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'servlet-name' tag not found"); + } + + String servlet_name = names.item(0).getTextContent().trim(); + trace("servlet-name=" + servlet_name); + + ServletReg reg = new ServletReg(servlet_name); + + NodeList child_nodes = servlet_el.getChildNodes(); + for(int j = 0; j < child_nodes.getLength(); j++) { + Node child_node = child_nodes.item(j); + String tag_name = child_node.getNodeName(); + + if (tag_name.equals("servlet-class")) { + reg.setClassName(child_node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("async-supported")) { + reg.setAsyncSupported(child_node.getTextContent().trim() + .equals("true")); + continue; + } + + if (tag_name.equals("init-param")) { + processXmlInitParam(reg, (Element) child_node); + continue; + } + + if (tag_name.equals("load-on-startup")) { + reg.setLoadOnStartup(Integer.parseInt(child_node.getTextContent().trim())); + continue; + } + } + + servlets_.add(reg); + name2servlet_.put(servlet_name, reg); + } + + NodeList servlet_mappings = doc_elem.getElementsByTagName("servlet-mapping"); + + for(int i = 0; i < servlet_mappings.getLength(); i++) { + Element mapping_el = (Element) servlet_mappings.item(i); + NodeList names = mapping_el.getElementsByTagName("servlet-name"); + if (names == null || names.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'servlet-name' tag not found"); + } + + String servlet_name = names.item(0).getTextContent().trim(); + trace("servlet-name=" + servlet_name); + + ServletReg reg = name2servlet_.get(servlet_name); + if (reg == null) { + throw new RuntimeException("Invalid web.xml: servlet '" + servlet_name + "' not found"); + } + + NodeList child_nodes = mapping_el.getElementsByTagName("url-pattern"); + String patterns[] = new String[child_nodes.getLength()]; + for(int j = 0; j < child_nodes.getLength(); j++) { + Node child_node = child_nodes.item(j); + patterns[j] = child_node.getTextContent().trim(); + } + + reg.addMapping(patterns); + } + + NodeList listeners = doc_elem.getElementsByTagName("listener"); + + for (int i = 0; i < listeners.getLength(); i++) { + Element listener_el = (Element) listeners.item(i); + NodeList classes = listener_el.getElementsByTagName("listener-class"); + if (classes == null || classes.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'listener-class' tag not found"); + } + + String class_name = classes.item(0).getTextContent().trim(); + trace("listener-class=" + class_name); + + pending_listener_classnames_.add(class_name); + } + + NodeList error_pages = doc_elem.getElementsByTagName("error-page"); + + for (int i = 0; i < error_pages.getLength(); i++) { + Element error_page_el = (Element) error_pages.item(i); + NodeList locations = error_page_el.getElementsByTagName("location"); + if (locations == null || locations.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'location' tag not found"); + } + + String location = locations.item(0).getTextContent().trim(); + + NodeList child_nodes = error_page_el.getChildNodes(); + for(int j = 0; j < child_nodes.getLength(); j++) { + Node child_node = child_nodes.item(j); + String tag_name = child_node.getNodeName(); + + if (tag_name.equals("exception-type")) { + String ex = child_node.getTextContent().trim(); + + exception2location_.put(ex, location); + trace("error-page: exception " + ex + " -> " + location); + continue; + } + + if (tag_name.equals("error-code")) { + Integer code = Integer.parseInt(child_node.getTextContent().trim()); + + error2location_.put(code, location); + trace("error-page: code " + code + " -> " + location); + continue; + } + } + } + + NodeList session_config = doc_elem.getElementsByTagName("session-config"); + + for (int i = 0; i < session_config.getLength(); i++) { + Element session_config_el = (Element) session_config.item(i); + NodeList session_timeout = session_config_el.getElementsByTagName("session-timeout"); + if (session_timeout != null) { + String timeout = session_timeout.item(0).getTextContent().trim(); + + trace("session_timeout: " + timeout); + session_timeout_ = Integer.parseInt(timeout); + break; + } + } + + NodeList jsp_configs = doc_elem.getElementsByTagName("jsp-config"); + + for (int i = 0; i < jsp_configs.getLength(); i++) { + Element jsp_config_el = (Element) jsp_configs.item(i); + + NodeList jsp_nodes = jsp_config_el.getChildNodes(); + + for(int j = 0; j < jsp_nodes.getLength(); j++) { + Node jsp_node = jsp_nodes.item(j); + String tag_name = jsp_node.getNodeName(); + + if (tag_name.equals("taglib")) { + NodeList tl_nodes = ((Element) jsp_node).getChildNodes(); + Taglib tl = new Taglib(tl_nodes); + + trace("add taglib"); + + taglibs_.add(tl); + continue; + } + + if (tag_name.equals("jsp-property-group")) { + NodeList jpg_nodes = ((Element) jsp_node).getChildNodes(); + JspPropertyGroup conf = new JspPropertyGroup(jpg_nodes); + + trace("add prop group"); + + prop_groups_.add(conf); + continue; + } + } + } + } + + private static int compareVersion(String ver1, String ver2) + { + String[] varr1 = ver1.split("\\."); + String[] varr2 = ver2.split("\\."); + + int max_len = varr1.length > varr2.length ? varr1.length : varr2.length; + for (int i = 0; i < max_len; i++) { + int l = i < varr1.length ? Integer.parseInt(varr1[i]) : 0; + int r = i < varr2.length ? Integer.parseInt(varr2[i]) : 0; + + int res = l - r; + + if (res != 0) { + return res; + } + } + + return 0; + } + + private void processXmlInitParam(InitParams params, Element elem) + throws RuntimeException + { + NodeList n = elem.getElementsByTagName("param-name"); + if (n == null || n.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'param-name' tag not found"); + } + + NodeList v = elem.getElementsByTagName("param-value"); + if (v == null || v.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'param-value' tag not found"); + } + params.setInitParameter(n.item(0).getTextContent().trim(), + v.item(0).getTextContent().trim()); + } + + private void loadInitializers(ScanResult scan_res) + { + trace("load initializer(s)"); + + ServiceLoader initializers = + ServiceLoader.load(ServletContainerInitializer.class, loader_); + + for (ServletContainerInitializer sci : initializers) { + + trace("loadInitializers: initializer: " + sci.getClass().getName()); + + HandlesTypes ann = sci.getClass().getAnnotation(HandlesTypes.class); + if (ann == null) { + trace("loadInitializers: no HandlesTypes annotation"); + continue; + } + + Class[] classes = ann.value(); + if (classes == null) { + trace("loadInitializers: no handles classes"); + continue; + } + + Set> handles_classes = new HashSet<>(); + + for (Class c : classes) { + trace("loadInitializers: find handles: " + c.getName()); + + ClassInfoList handles = c.isInterface() + ? scan_res.getClassesImplementing(c.getName()) + : scan_res.getSubclasses(c.getName()); + + for (ClassInfo ci : handles) { + if (ci.isInterface() + || ci.isAnnotation() + || ci.isAbstract()) + { + continue; + } + + trace("loadInitializers: handles class: " + ci.getName()); + handles_classes.add(ci.loadClass()); + } + } + + if (handles_classes.isEmpty()) { + trace("loadInitializers: no handles implementations"); + continue; + } + + try { + sci.onStartup(handles_classes, this); + metadata_complete_ = true; + } catch(Exception e) { + System.err.println("loadInitializers: exception caught: " + e.toString()); + } + } + } + + private void scanClasses(ScanResult scan_res) + throws ReflectiveOperationException + { + ClassInfoList filters = scan_res.getClassesWithAnnotation(WebFilter.class.getName()); + + for (ClassInfo ci : filters) { + if (ci.isInterface() + || ci.isAnnotation() + || ci.isAbstract() + || !ci.implementsInterface(Filter.class.getName())) + { + trace("scanClasses: ignoring Filter impl: " + ci.getName()); + continue; + } + + trace("scanClasses: found Filter class: " + ci.getName()); + + Class cls = ci.loadClass(); + if (!Filter.class.isAssignableFrom(cls)) { + trace("scanClasses: " + ci.getName() + " cannot be assigned to Filter"); + continue; + } + + WebFilter ann = cls.getAnnotation(WebFilter.class); + + if (ann == null) { + trace("scanClasses: no WebFilter annotation for " + ci.getName()); + continue; + } + + String filter_name = ann.filterName(); + + if (filter_name.isEmpty()) { + filter_name = ci.getName(); + } + + FilterReg reg = name2filter_.get(filter_name); + + if (reg == null) { + reg = new FilterReg(filter_name, cls); + filters_.add(reg); + name2filter_.put(filter_name, reg); + } else { + reg.setClass(cls); + } + + EnumSet dtypes = EnumSet.noneOf(DispatcherType.class); + DispatcherType[] dispatchers = ann.dispatcherTypes(); + for (DispatcherType d : dispatchers) { + dtypes.add(d); + } + + if (dtypes.isEmpty()) { + dtypes.add(DispatcherType.REQUEST); + } + + boolean match_after = false; + + reg.addMappingForUrlPatterns(dtypes, match_after, ann.value()); + reg.addMappingForUrlPatterns(dtypes, match_after, ann.urlPatterns()); + reg.addMappingForServletNames(dtypes, match_after, ann.servletNames()); + + for (WebInitParam p : ann.initParams()) { + reg.setInitParameter(p.name(), p.value()); + } + + reg.setAsyncSupported(ann.asyncSupported()); + } + + ClassInfoList servlets = scan_res.getClassesWithAnnotation(WebServlet.class.getName()); + + for (ClassInfo ci : servlets) { + if (ci.isInterface() + || ci.isAnnotation() + || ci.isAbstract() + || !ci.extendsSuperclass(HttpServlet.class.getName())) + { + trace("scanClasses: ignoring HttpServlet subclass: " + ci.getName()); + continue; + } + + trace("scanClasses: found HttpServlet class: " + ci.getName()); + + Class cls = ci.loadClass(); + if (!HttpServlet.class.isAssignableFrom(cls)) { + trace("scanClasses: " + ci.getName() + " cannot be assigned to HttpFilter"); + continue; + } + + WebServlet ann = cls.getAnnotation(WebServlet.class); + + if (ann == null) { + trace("scanClasses: no WebServlet annotation"); + continue; + } + + String servlet_name = ann.name(); + + if (servlet_name.isEmpty()) { + servlet_name = ci.getName(); + } + + ServletReg reg = name2servlet_.get(servlet_name); + + if (reg == null) { + reg = new ServletReg(servlet_name, cls); + servlets_.add(reg); + name2servlet_.put(servlet_name, reg); + } else { + reg.setClass(cls); + } + + reg.addMapping(ann.value()); + reg.addMapping(ann.urlPatterns()); + + for (WebInitParam p : ann.initParams()) { + reg.setInitParameter(p.name(), p.value()); + } + + reg.setAsyncSupported(ann.asyncSupported()); + } + + + ClassInfoList lstnrs = scan_res.getClassesWithAnnotation(WebListener.class.getName()); + + for (ClassInfo ci : lstnrs) { + if (ci.isInterface() + || ci.isAnnotation() + || ci.isAbstract()) + { + trace("scanClasses: listener impl: " + ci.getName()); + continue; + } + + trace("scanClasses: listener class: " + ci.getName()); + + if (listener_classnames_.contains(ci.getName())) { + trace("scanClasses: " + ci.getName() + " already added as listener"); + continue; + } + + Class cls = ci.loadClass(); + Class lclass = null; + for (Class c : LISTENER_TYPES) { + if (c.isAssignableFrom(cls)) { + lclass = c; + break; + } + } + + if (lclass == null) { + log("scanClasses: " + ci.getName() + " implements none of known listener interfaces"); + continue; + } + + WebListener ann = cls.getAnnotation(WebListener.class); + + if (ann == null) { + log("scanClasses: no WebListener annotation"); + continue; + } + + Constructor ctor = cls.getConstructor(); + EventListener listener = (EventListener) ctor.newInstance(); + + addListener(listener); + + listener_classnames_.add(ci.getName()); + } + } + + public void stop() throws IOException + { + ClassLoader old = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(loader_); + + try { + for (ServletReg s : servlets_) { + s.destroy(); + } + + for (FilterReg f : filters_) { + f.destroy(); + } + + if (!destroy_listeners_.isEmpty()) { + ServletContextEvent event = new ServletContextEvent(this); + for (ServletContextListener listener : destroy_listeners_) { + listener.contextDestroyed(event); + } + } + + if (extracted_dir_ != null) { + removeDir(extracted_dir_); + } + + if (temp_dir_ != null) { + removeDir(temp_dir_); + } + } finally { + Thread.currentThread().setContextClassLoader(old); + } + } + + private void removeDir(File dir) throws IOException + { + Files.walkFileTree(dir.toPath(), + new SimpleFileVisitor() { + @Override + public FileVisitResult postVisitDirectory( + Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile( + Path file, BasicFileAttributes attrs) + throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + }); + } + + private class CtxInitParams implements InitParams + { + private final Map init_params_ = + new HashMap(); + + public boolean setInitParameter(String name, String value) + { + trace("CtxInitParams.setInitParameter " + name + " = " + value); + + return init_params_.putIfAbsent(name, value) == null; + } + + public String getInitParameter(String name) + { + trace("CtxInitParams.getInitParameter for " + name); + + return init_params_.get(name); + } + + public Set setInitParameters(Map initParameters) + { + // illegalStateIfContextStarted(); + Set clash = null; + for (Map.Entry entry : initParameters.entrySet()) + { + if (entry.getKey() == null) { + throw new IllegalArgumentException("init parameter name required"); + } + + if (entry.getValue() == null) { + throw new IllegalArgumentException("non-null value required for init parameter " + entry.getKey()); + } + + if (init_params_.get(entry.getKey()) != null) + { + if (clash == null) + clash = new HashSet(); + clash.add(entry.getKey()); + } + + trace("CtxInitParams.setInitParameters " + entry.getKey() + " = " + entry.getValue()); + } + + if (clash != null) { + return clash; + } + + init_params_.putAll(initParameters); + return Collections.emptySet(); + } + + public Map getInitParameters() + { + trace("CtxInitParams.getInitParameters"); + return init_params_; + } + + public Enumeration getInitParameterNames() + { + return Collections.enumeration(init_params_.keySet()); + } + } + + private class NamedReg extends CtxInitParams + implements Registration + { + private final String name_; + private String class_name_; + + public NamedReg(String name) + { + name_ = name; + } + + public NamedReg(String name, String class_name) + { + name_ = name; + class_name_ = class_name; + } + + @Override + public String getName() + { + return name_; + } + + @Override + public String getClassName() + { + return class_name_; + } + + public void setClassName(String class_name) + { + class_name_ = class_name; + } + } + + private class ServletReg extends NamedReg + implements ServletRegistration.Dynamic, ServletConfig + { + private Class servlet_class_; + private Servlet servlet_; + private String role_; + private boolean async_supported_ = false; + private final List patterns_ = new ArrayList<>(); + private int load_on_startup_ = -1; + private boolean initialized_ = false; + private final List filters_ = new ArrayList<>(); + private boolean system_jsp_servlet_ = false; + + public ServletReg(String name, Class servlet_class) + { + super(name, servlet_class.getName()); + servlet_class_ = servlet_class; + } + + public ServletReg(String name, Servlet servlet) + { + super(name, servlet.getClass().getName()); + servlet_ = servlet; + } + + public ServletReg(String name, String servlet_class_name) + { + super(name, servlet_class_name); + } + + public ServletReg(String name) + { + super(name); + } + + private void init() throws ServletException + { + if (initialized_) { + return; + } + + trace("ServletReg.init(): " + getName()); + + if (system_jsp_servlet_) { + JasperInitializer ji = new JasperInitializer(); + + ji.onStartup(Collections.emptySet(), Context.this); + } + + if (servlet_ == null) { + try { + if (servlet_class_ == null) { + servlet_class_ = loader_.loadClass(getClassName()); + } + + Constructor ctor = servlet_class_.getConstructor(); + servlet_ = (Servlet) ctor.newInstance(); + } catch(Exception e) { + log("ServletReg.init() failed " + e); + throw new ServletException(e); + } + } + + servlet_.init((ServletConfig) this); + + initialized_ = true; + } + + public void startup() throws ServletException + { + if (load_on_startup_ < 0) { + return; + } + + init(); + } + + public void destroy() + { + if (initialized_) { + servlet_.destroy(); + } + } + + public void setClassName(String class_name) throws IllegalStateException + { + if (servlet_ != null + || servlet_class_ != null + || getClassName() != null) + { + throw new IllegalStateException("Class already initialized"); + } + + super.setClassName(class_name); + } + + public void setClass(Class servlet_class) + throws IllegalStateException + { + if (servlet_ != null + || servlet_class_ != null + || getClassName() != null) + { + throw new IllegalStateException("Class already initialized"); + } + + super.setClassName(servlet_class.getName()); + servlet_class_ = servlet_class; + } + + public void service(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + init(); + + servlet_.service(request, response); + } + + public void addFilter(FilterMap fmap) + { + filters_.add(fmap); + } + + @Override + public Set addMapping(String... urlPatterns) + { + checkContextState(); + + Set clash = null; + for (String pattern : urlPatterns) { + trace("ServletReg.addMapping: " + pattern); + + if (pattern2servlet_.containsKey(pattern)) { + if (clash == null) { + clash = new HashSet(); + } + clash.add(pattern); + } + } + + /* if there were any clashes amongst the urls, return them */ + if (clash != null) { + return clash; + } + + for (String pattern : urlPatterns) { + patterns_.add(pattern); + pattern2servlet_.put(pattern, this); + parseURLPattern(pattern, this); + } + + return Collections.emptySet(); + } + + @Override + public Collection getMappings() + { + trace("ServletReg.getMappings"); + return patterns_; + } + + @Override + public String getRunAsRole() + { + return role_; + } + + @Override + public void setLoadOnStartup(int loadOnStartup) + { + checkContextState(); + + trace("ServletReg.setLoadOnStartup: " + loadOnStartup); + load_on_startup_ = loadOnStartup; + } + + @Override + public Set setServletSecurity(ServletSecurityElement constraint) + { + log("ServletReg.setServletSecurity"); + return Collections.emptySet(); + } + + @Override + public void setMultipartConfig( + MultipartConfigElement multipartConfig) + { + log("ServletReg.setMultipartConfig"); + } + + @Override + public void setRunAsRole(String roleName) + { + log("ServletReg.setRunAsRole: " + roleName); + role_ = roleName; + } + + @Override + public void setAsyncSupported(boolean isAsyncSupported) + { + log("ServletReg.setAsyncSupported: " + isAsyncSupported); + async_supported_ = isAsyncSupported; + } + + @Override + public String getServletName() + { + return getName(); + } + + @Override + public ServletContext getServletContext() + { + return (ServletContext) Context.this; + } + } + + public void checkContextState() throws IllegalStateException + { + if (ctx_initialized_) { + throw new IllegalStateException("Context already initialized"); + } + } + + public void parseURLPattern(String p, ServletReg servlet) + throws IllegalArgumentException + { + URLPattern pattern = parseURLPattern(p); + + switch (pattern.type_) { + case PREFIX: + prefix_patterns_.add(new PrefixPattern(pattern.pattern_, servlet)); + return; + + case SUFFIX: + suffix2servlet_.put(pattern.pattern_, servlet); + return; + + case EXACT: + exact2servlet_.put(pattern.pattern_, servlet); + return; + + case DEFAULT: + default_servlet_ = servlet; + return; + } + + /* TODO process other cases, throw IllegalArgumentException */ + } + + public URLPattern parseURLPattern(String p) + throws IllegalArgumentException + { + URLPattern pattern = parsed_patterns_.get(p); + if (pattern == null) { + pattern = new URLPattern(p); + parsed_patterns_.put(p, pattern); + } + + return pattern; + } + + private static enum URLPatternType { + PREFIX, + SUFFIX, + DEFAULT, + EXACT, + }; + + private class URLPattern + { + private final String pattern_; + private final URLPatternType type_; + + public URLPattern(String p) + throws IllegalArgumentException + { + /* + 12.2 Specification of Mappings + ... + A string beginning with a '/' character and ending with a '/*' + suffix is used for path mapping. + */ + if (p.startsWith("/") && p.endsWith("/*")) { + trace("URLPattern: '" + p + "' is a prefix pattern"); + pattern_ = p.substring(0, p.length() - 2); + type_ = URLPatternType.PREFIX; + return; + } + + /* + A string beginning with a '*.' prefix is used as an extension + mapping. + */ + if (p.startsWith("*.")) { + trace("URLPattern: '" + p + "' is a suffix pattern"); + pattern_ = p.substring(1, p.length()); + type_ = URLPatternType.SUFFIX; + return; + } + + /* + The empty string ("") is a special URL pattern that exactly maps to + the application's context root, i.e., requests of the form + http://host:port//. In this case the path info is '/' + and the servlet path and context path is empty string (""). + */ + if (p.isEmpty()) { + trace("URLPattern: '" + p + "' is a root"); + pattern_ = "/"; + type_ = URLPatternType.EXACT; + return; + } + + /* + A string containing only the '/' character indicates the "default" + servlet of the application. In this case the servlet path is the + request URI minus the context path and the path info is null. + */ + if (p.equals("/")) { + trace("URLPattern: '" + p + "' is a default"); + pattern_ = p; + type_ = URLPatternType.DEFAULT; + return; + } + + /* + All other strings are used for exact matches only. + */ + trace("URLPattern: '" + p + "' is an exact pattern"); + pattern_ = p; + type_ = URLPatternType.EXACT; + + /* TODO process other cases, throw IllegalArgumentException */ + } + + public boolean match(String url) + { + switch (type_) { + case PREFIX: + return url.startsWith(pattern_) && ( + url.length() == pattern_.length() + || url.charAt(pattern_.length()) == '/'); + + case SUFFIX: + return url.endsWith(pattern_); + + case EXACT: + return url.equals(pattern_); + + case DEFAULT: + return true; + } + + return false; + } + } + + private class FilterReg extends NamedReg + implements FilterRegistration.Dynamic, FilterConfig + { + private Class filter_class_; + private Filter filter_; + private boolean async_supported_ = false; + private boolean initialized_ = false; + + public FilterReg(String name, Class filter_class) + { + super(name, filter_class.getName()); + filter_class_ = filter_class; + } + + public FilterReg(String name, Filter filter) + { + super(name, filter.getClass().getName()); + filter_ = filter; + } + + public FilterReg(String name, String filter_class_name) + { + super(name, filter_class_name); + } + + public FilterReg(String name) + { + super(name); + } + + public void setClassName(String class_name) throws IllegalStateException + { + if (filter_ != null + || filter_class_ != null + || getClassName() != null) + { + throw new IllegalStateException("Class already initialized"); + } + + super.setClassName(class_name); + } + + public void setClass(Class filter_class) throws IllegalStateException + { + if (filter_ != null + || filter_class_ != null + || getClassName() != null) + { + throw new IllegalStateException("Class already initialized"); + } + + super.setClassName(filter_class.getName()); + filter_class_ = filter_class; + } + + public void init() throws ServletException + { + if (filter_ == null) { + try { + if (filter_class_ == null) { + filter_class_ = loader_.loadClass(getClassName()); + } + + Constructor ctor = filter_class_.getConstructor(); + filter_ = (Filter) ctor.newInstance(); + } catch(Exception e) { + log("FilterReg.init() failed " + e); + throw new ServletException(e); + } + } + + filter_.init((FilterConfig) this); + + initialized_ = true; + } + + public void destroy() + { + if (initialized_) { + filter_.destroy(); + } + } + + @Override + public void addMappingForServletNames( + EnumSet dispatcherTypes, boolean isMatchAfter, + String... servletNames) + { + checkContextState(); + + for (String n : servletNames) { + trace("FilterReg.addMappingForServletNames: ... " + n); + + ServletReg sreg = name2servlet_.get(n); + if (sreg == null) { + sreg = new ServletReg(n); + servlets_.add(sreg); + name2servlet_.put(n, sreg); + } + + FilterMap map = new FilterMap(this, sreg, dispatcherTypes, + isMatchAfter); + + sreg.addFilter(map); + } + } + + @Override + public Collection getServletNameMappings() + { + checkContextState(); + + log("FilterReg.getServletNameMappings"); + return Collections.emptySet(); + } + + @Override + public void addMappingForUrlPatterns( + EnumSet dispatcherTypes, boolean isMatchAfter, + String... urlPatterns) + { + checkContextState(); + + for (String u : urlPatterns) { + trace("FilterReg.addMappingForUrlPatterns: ... " + u); + + URLPattern p = parseURLPattern(u); + FilterMap map = new FilterMap(this, p, dispatcherTypes, + isMatchAfter); + + filter_maps_.add(map); + } + } + + @Override + public Collection getUrlPatternMappings() + { + log("FilterReg.getUrlPatternMappings"); + return Collections.emptySet(); + } + + @Override + public void setAsyncSupported(boolean isAsyncSupported) + { + log("FilterReg.setAsyncSupported: " + isAsyncSupported); + async_supported_ = isAsyncSupported; + } + + @Override + public String getFilterName() + { + return getName(); + } + + @Override + public ServletContext getServletContext() + { + return (ServletContext) Context.this; + } + } + + private class FilterMap + { + private final FilterReg filter_; + private final ServletReg servlet_; + private final URLPattern pattern_; + private final EnumSet dtypes_; + private final boolean match_after_; + + public FilterMap(FilterReg filter, ServletReg servlet, + EnumSet dtypes, boolean match_after) + { + filter_ = filter; + servlet_ = servlet; + pattern_ = null; + dtypes_ = dtypes; + match_after_ = match_after; + } + + public FilterMap(FilterReg filter, URLPattern pattern, + EnumSet dtypes, boolean match_after) + { + filter_ = filter; + servlet_ = null; + pattern_ = pattern; + dtypes_ = dtypes; + match_after_ = match_after; + } + } + + private void initialized() + { + if (!sess_attr_listeners_.isEmpty()) { + sess_attr_proxy_ = new SessionAttrProxy(sess_attr_listeners_); + } + + if (!req_attr_listeners_.isEmpty()) { + req_attr_proxy_ = new RequestAttrProxy(req_attr_listeners_); + } + + ClassLoader old = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(loader_); + + try { + // Call context listeners + destroy_listeners_.clear(); + if (!ctx_listeners_.isEmpty()) { + ServletContextEvent event = new ServletContextEvent(this); + for (ServletContextListener listener : ctx_listeners_) + { + try { + listener.contextInitialized(event); + } catch(AbstractMethodError e) { + log("initialized: AbstractMethodError exception caught: " + e); + } + destroy_listeners_.add(0, listener); + } + } + + for (ServletReg sr : servlets_) { + try { + sr.startup(); + } catch(ServletException e) { + log("initialized: exception caught: " + e); + } + } + + for (FilterReg fr : filters_) { + try { + fr.init(); + } catch(ServletException e) { + log("initialized: exception caught: " + e); + } + } + + ctx_initialized_ = true; + } finally { + Thread.currentThread().setContextClassLoader(old); + } + } + + @Override + public ServletContext getContext(String uripath) + { + trace("getContext for " + uripath); + return this; + } + + @Override + public int getMajorVersion() + { + trace("getMajorVersion"); + return SERVLET_MAJOR_VERSION; + } + + @Override + public String getMimeType(String file) + { + log("getMimeType for " + file); + if (mime_types_ == null) { + mime_types_ = new MimeTypes(); + } + return mime_types_.getMimeByExtension(file); + } + + @Override + public int getMinorVersion() + { + trace("getMinorVersion"); + return SERVLET_MINOR_VERSION; + } + + private class URIRequestDispatcher implements RequestDispatcher + { + private final URI uri_; + + public URIRequestDispatcher(URI uri) + { + uri_ = uri; + } + + public URIRequestDispatcher(String uri) + throws URISyntaxException + { + uri_ = new URI(uri); + } + + @Override + public void forward(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + /* + 9.4 The Forward Method + ... + If the response has been committed, an IllegalStateException + must be thrown. + */ + if (response.isCommitted()) { + throw new IllegalStateException("Response already committed"); + } + + ForwardRequestWrapper req = new ForwardRequestWrapper(request); + + try { + trace("URIRequestDispatcher.forward"); + + String path = uri_.getPath().substring(context_path_.length()); + + ServletReg servlet = findServlet(path, req); + + req.setRequestURI(uri_.getRawPath()); + req.setQueryString(uri_.getRawQuery()); + req.setDispatcherType(DispatcherType.FORWARD); + + /* + 9.4 The Forward Method + ... + If output data exists in the response buffer that has not + been committed, the content must be cleared before the + target servlet's service method is called. + */ + response.resetBuffer(); + + FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.FORWARD); + + fc.doFilter(request, response); + + /* + 9.4 The Forward Method + ... + Before the forward method of the RequestDispatcher interface + returns without exception, the response content must be sent + and committed, and closed by the servlet container, unless + the request was put into the asynchronous mode. If an error + occurs in the target of the RequestDispatcher.forward() the + exception may be propagated back through all the calling + filters and servlets and eventually back to the container + */ + if (!request.isAsyncStarted()) { + response.flushBuffer(); + } + + /* + 9.5 Error Handling + + If the servlet that is the target of a request dispatcher + throws a runtime exception or a checked exception of type + ServletException or IOException, it should be propagated + to the calling servlet. All other exceptions should be + wrapped as ServletExceptions and the root cause of the + exception set to the original exception, as it should + not be propagated. + */ + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e); + } finally { + req.close(); + + trace("URIRequestDispatcher.forward done"); + } + } + + @Override + public void include(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + IncludeRequestWrapper req = new IncludeRequestWrapper(request); + + try { + trace("URIRequestDispatcher.include"); + + String path = uri_.getPath().substring(context_path_.length()); + + ServletReg servlet = findServlet(path, req); + + req.setRequestURI(uri_.getRawPath()); + req.setQueryString(uri_.getRawQuery()); + req.setDispatcherType(DispatcherType.INCLUDE); + + FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.INCLUDE); + + fc.doFilter(request, new IncludeResponseWrapper(response)); + + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e); + } finally { + req.close(); + + trace("URIRequestDispatcher.include done"); + } + } + } + + private class ServletDispatcher implements RequestDispatcher + { + private final ServletReg servlet_; + + public ServletDispatcher(ServletReg servlet) + { + servlet_ = servlet; + } + + @Override + public void forward(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + /* + 9.4 The Forward Method + ... + If the response has been committed, an IllegalStateException + must be thrown. + */ + if (response.isCommitted()) { + throw new IllegalStateException("Response already committed"); + } + + trace("ServletDispatcher.forward"); + + DispatcherType dtype = request.getDispatcherType(); + + Request req; + if (request instanceof Request) { + req = (Request) request; + } else { + req = (Request) request.getAttribute(Request.BARE); + } + + try { + req.setDispatcherType(DispatcherType.FORWARD); + + /* + 9.4 The Forward Method + ... + If output data exists in the response buffer that has not + been committed, the content must be cleared before the + target servlet's service method is called. + */ + response.resetBuffer(); + + servlet_.service(request, response); + + /* + 9.4 The Forward Method + ... + Before the forward method of the RequestDispatcher interface + returns without exception, the response content must be sent + and committed, and closed by the servlet container, unless + the request was put into the asynchronous mode. If an error + occurs in the target of the RequestDispatcher.forward() the + exception may be propagated back through all the calling + filters and servlets and eventually back to the container + */ + if (!request.isAsyncStarted()) { + response.flushBuffer(); + } + + /* + 9.5 Error Handling + + If the servlet that is the target of a request dispatcher + throws a runtime exception or a checked exception of type + ServletException or IOException, it should be propagated + to the calling servlet. All other exceptions should be + wrapped as ServletExceptions and the root cause of the + exception set to the original exception, as it should + not be propagated. + */ + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e); + } finally { + req.setDispatcherType(dtype); + + trace("ServletDispatcher.forward done"); + } + } + + @Override + public void include(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + trace("ServletDispatcher.include"); + + DispatcherType dtype = request.getDispatcherType(); + + Request req; + if (request instanceof Request) { + req = (Request) request; + } else { + req = (Request) request.getAttribute(Request.BARE); + } + + try { + req.setDispatcherType(DispatcherType.INCLUDE); + + servlet_.service(request, new IncludeResponseWrapper(response)); + + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e); + } finally { + req.setDispatcherType(dtype); + + trace("ServletDispatcher.include done"); + } + } + } + + @Override + public RequestDispatcher getNamedDispatcher(String name) + { + trace("getNamedDispatcher for " + name); + + ServletReg servlet = name2servlet_.get(name); + if (servlet != null) { + return new ServletDispatcher(servlet); + } + + return null; + } + + @Override + public RequestDispatcher getRequestDispatcher(String uriInContext) + { + trace("getRequestDispatcher for " + uriInContext); + try { + return new URIRequestDispatcher(context_path_ + uriInContext); + } catch (URISyntaxException e) { + log("getRequestDispatcher: failed to create dispatcher: " + e); + } + + return null; + } + + public RequestDispatcher getRequestDispatcher(URI uri) + { + trace("getRequestDispatcher for " + uri.getRawPath()); + return new URIRequestDispatcher(uri); + } + + @Override + public String getRealPath(String path) + { + trace("getRealPath for " + path); + + File f = new File(webapp_, path.substring(1)); + + return f.getAbsolutePath(); + } + + @Override + public URL getResource(String path) throws MalformedURLException + { + trace("getResource for " + path); + + File f = new File(webapp_, path.substring(1)); + + if (f.exists()) { + return new URL("file:" + f.getAbsolutePath()); + } + + return null; + } + + @Override + public InputStream getResourceAsStream(String path) + { + trace("getResourceAsStream for " + path); + + try { + File f = new File(webapp_, path.substring(1)); + + return new FileInputStream(f); + } catch (FileNotFoundException e) { + log("getResourceAsStream: failed " + e); + + return null; + } + } + + @Override + public Set getResourcePaths(String path) + { + trace("getResourcePaths for " + path); + + File dir = new File(webapp_, path.substring(1)); + File[] list = dir.listFiles(); + + if (list == null) { + return null; + } + + Set res = new HashSet<>(); + Path root = webapp_.toPath(); + + for (File f : list) { + String r = "/" + root.relativize(f.toPath()); + if (f.isDirectory()) { + r += "/"; + } + + trace("getResourcePaths: " + r); + + res.add(r); + } + + return res; + } + + @Override + public String getServerInfo() + { + trace("getServerInfo: " + server_info_); + return server_info_; + } + + @Override + @Deprecated + public Servlet getServlet(String name) throws ServletException + { + log("getServlet for " + name); + return null; + } + + @SuppressWarnings("unchecked") + @Override + @Deprecated + public Enumeration getServletNames() + { + log("getServletNames"); + return Collections.enumeration(Collections.EMPTY_LIST); + } + + @SuppressWarnings("unchecked") + @Override + @Deprecated + public Enumeration getServlets() + { + log("getServlets"); + return Collections.enumeration(Collections.EMPTY_LIST); + } + + @Override + @Deprecated + public void log(Exception exception, String msg) + { + log(msg, exception); + } + + @Override + public void log(String msg) + { + msg = "Context." + msg; + log(0, msg, msg.length()); + } + + @Override + public void log(String message, Throwable throwable) + { + log(message); + } + + private static native void log(long ctx_ptr, String msg, int msg_len); + + + public static void trace(String msg) + { + msg = "Context." + msg; + trace(0, msg, msg.length()); + } + + private static native void trace(long ctx_ptr, String msg, int msg_len); + + @Override + public String getInitParameter(String name) + { + trace("getInitParameter for " + name); + return init_params_.get(name); + } + + @SuppressWarnings("unchecked") + @Override + public Enumeration getInitParameterNames() + { + trace("getInitParameterNames"); + return Collections.enumeration(Collections.EMPTY_LIST); + } + + @Override + public String getServletContextName() + { + log("getServletContextName"); + return "No Context"; + } + + @Override + public String getContextPath() + { + trace("getContextPath"); + return context_path_; + } + + @Override + public boolean setInitParameter(String name, String value) + { + trace("setInitParameter " + name + " = " + value); + return init_params_.putIfAbsent(name, value) == null; + } + + @Override + public Object getAttribute(String name) + { + trace("getAttribute " + name); + + return attributes_.get(name); + } + + @Override + public Enumeration getAttributeNames() + { + trace("getAttributeNames"); + + Set names = attributes_.keySet(); + return Collections.enumeration(names); + } + + @Override + public void setAttribute(String name, Object object) + { + trace("setAttribute " + name); + + Object prev = attributes_.put(name, object); + + if (ctx_attr_listeners_.isEmpty()) { + return; + } + + ServletContextAttributeEvent scae = new ServletContextAttributeEvent( + this, name, prev == null ? object : prev); + + for (ServletContextAttributeListener l : ctx_attr_listeners_) { + if (prev == null) { + l.attributeAdded(scae); + } else { + l.attributeReplaced(scae); + } + } + } + + @Override + public void removeAttribute(String name) + { + trace("removeAttribute " + name); + + Object value = attributes_.remove(name); + + if (ctx_attr_listeners_.isEmpty()) { + return; + } + + ServletContextAttributeEvent scae = new ServletContextAttributeEvent( + this, name, value); + + for (ServletContextAttributeListener l : ctx_attr_listeners_) { + l.attributeRemoved(scae); + } + } + + @Override + public FilterRegistration.Dynamic addFilter(String name, + Class filterClass) + { + log("addFilter " + name + ", " + filterClass.getName()); + + checkContextState(); + + FilterReg reg = new FilterReg(name, filterClass); + filters_.add(reg); + name2filter_.put(name, reg); + return reg; + } + + @Override + public FilterRegistration.Dynamic addFilter(String name, Filter filter) + { + log("addFilter " + name); + + checkContextState(); + + FilterReg reg = new FilterReg(name, filter); + filters_.add(reg); + name2filter_.put(name, reg); + return reg; + } + + @Override + public FilterRegistration.Dynamic addFilter(String name, String className) + { + log("addFilter " + name + ", " + className); + + checkContextState(); + + FilterReg reg = new FilterReg(name, className); + filters_.add(reg); + name2filter_.put(name, reg); + return reg; + } + + @Override + public ServletRegistration.Dynamic addServlet(String name, + Class servletClass) + { + log("addServlet " + name + ", " + servletClass.getName()); + + checkContextState(); + + ServletReg reg = null; + try { + reg = new ServletReg(name, servletClass); + servlets_.add(reg); + name2servlet_.put(name, reg); + } catch(Exception e) { + System.err.println("addServlet: exception caught: " + e.toString()); + } + + return reg; + } + + @Override + public ServletRegistration.Dynamic addServlet(String name, Servlet servlet) + { + log("addServlet " + name); + + checkContextState(); + + ServletReg reg = null; + try { + reg = new ServletReg(name, servlet); + servlets_.add(reg); + name2servlet_.put(name, reg); + } catch(Exception e) { + System.err.println("addServlet: exception caught: " + e.toString()); + } + + return reg; + } + + @Override + public ServletRegistration.Dynamic addServlet(String name, String className) + { + log("addServlet " + name + ", " + className); + + checkContextState(); + + ServletReg reg = null; + try { + reg = new ServletReg(name, className); + servlets_.add(reg); + name2servlet_.put(name, reg); + } catch(Exception e) { + System.err.println("addServlet: exception caught: " + e.toString()); + } + + return reg; + } + + @Override + public ServletRegistration.Dynamic addJspFile(String jspName, String jspFile) + { + log("addJspFile: " + jspName + " " + jspFile); + + return null; + } + + @Override + public T createFilter(Class c) throws ServletException + { + log("createFilter " + c.getName()); + + checkContextState(); + + try { + Constructor ctor = c.getConstructor(); + T filter = ctor.newInstance(); + return filter; + } catch (Exception e) { + log("createFilter() failed " + e); + + throw new ServletException(e); + } + } + + @Override + public T createServlet(Class c) throws ServletException + { + log("createServlet " + c.getName()); + + checkContextState(); + + try { + Constructor ctor = c.getConstructor(); + T servlet = ctor.newInstance(); + return servlet; + } catch (Exception e) { + log("createServlet() failed " + e); + + throw new ServletException(e); + } + } + + @Override + public Set getDefaultSessionTrackingModes() + { + log("getDefaultSessionTrackingModes"); + + return default_session_tracking_modes_; + } + + @Override + public Set getEffectiveSessionTrackingModes() + { + log("getEffectiveSessionTrackingModes"); + + return session_tracking_modes_; + } + + public boolean isSessionIdValid(String id) + { + synchronized (sessions_) { + return sessions_.containsKey(id); + } + } + + public Session getSession(String id) + { + synchronized (sessions_) { + Session s = sessions_.get(id); + + if (s != null) { + s.accessed(); + + if (s.checkTimeOut()) { + s.invalidate(); + return null; + } + } + + return s; + } + } + + public Session createSession() + { + Session session = new Session(this, generateSessionId(), + sess_attr_proxy_, session_timeout_ * 60); + + if (!sess_listeners_.isEmpty()) + { + HttpSessionEvent event = new HttpSessionEvent(session); + + for (HttpSessionListener l : sess_listeners_) + { + l.sessionCreated(event); + } + } + + synchronized (sessions_) { + sessions_.put(session.getId(), session); + + return session; + } + } + + public void invalidateSession(Session session) + { + synchronized (sessions_) { + sessions_.remove(session.getId()); + } + + if (!sess_listeners_.isEmpty()) + { + HttpSessionEvent event = new HttpSessionEvent(session); + + for (int i = sess_listeners_.size() - 1; i >= 0; i--) + { + sess_listeners_.get(i).sessionDestroyed(event); + } + } + } + + public void changeSessionId(Session session) + { + String old_id; + + synchronized (sessions_) { + old_id = session.getId(); + sessions_.remove(old_id); + + session.setId(generateSessionId()); + + sessions_.put(session.getId(), session); + } + + if (!sess_id_listeners_.isEmpty()) + { + HttpSessionEvent event = new HttpSessionEvent(session); + for (HttpSessionIdListener l : sess_id_listeners_) + { + l.sessionIdChanged(event, old_id); + } + } + } + + private String generateSessionId() + { + return UUID.randomUUID().toString(); + } + + @Override + public FilterRegistration getFilterRegistration(String filterName) + { + log("getFilterRegistration " + filterName); + return name2filter_.get(filterName); + } + + @Override + public Map getFilterRegistrations() + { + log("getFilterRegistrations"); + return name2filter_; + } + + @Override + public ServletRegistration getServletRegistration(String servletName) + { + log("getServletRegistration " + servletName); + return name2servlet_.get(servletName); + } + + @Override + public Map getServletRegistrations() + { + log("getServletRegistrations"); + return name2servlet_; + } + + @Override + public SessionCookieConfig getSessionCookieConfig() + { + log("getSessionCookieConfig"); + + return session_cookie_config_; + } + + @Override + public void setSessionTrackingModes(Set modes) + { + log("setSessionTrackingModes"); + + session_tracking_modes_ = modes; + } + + @Override + public void addListener(String className) + { + trace("addListener " + className); + + checkContextState(); + + if (listener_classnames_.contains(className)) { + log("addListener " + className + " already added as listener"); + return; + } + + try { + Class cls = loader_.loadClass(className); + + Constructor ctor = cls.getConstructor(); + EventListener listener = (EventListener) ctor.newInstance(); + + addListener(listener); + + listener_classnames_.add(className); + } catch (Exception e) { + log("addListener: exception caught: " + e.toString()); + } + } + + @Override + public void addListener(T t) + { + trace("addListener " + t.getClass().getName()); + + checkContextState(); + + for (int i = 0; i < LISTENER_TYPES.length; i++) { + Class c = LISTENER_TYPES[i]; + if (c.isAssignableFrom(t.getClass())) { + trace("addListener: assignable to " + c.getName()); + } + } + + if (t instanceof ServletContextListener) { + ctx_listeners_.add((ServletContextListener) t); + } + + if (t instanceof ServletContextAttributeListener) { + ctx_attr_listeners_.add((ServletContextAttributeListener) t); + } + + if (t instanceof ServletRequestListener) { + req_init_listeners_.add((ServletRequestListener) t); + req_destroy_listeners_.add(0, (ServletRequestListener) t); + } + + if (t instanceof ServletRequestAttributeListener) { + req_attr_listeners_.add((ServletRequestAttributeListener) t); + } + + if (t instanceof HttpSessionAttributeListener) { + sess_attr_listeners_.add((HttpSessionAttributeListener) t); + } + + if (t instanceof HttpSessionIdListener) { + sess_id_listeners_.add((HttpSessionIdListener) t); + } + + if (t instanceof HttpSessionListener) { + sess_listeners_.add((HttpSessionListener) t); + } + } + + @Override + public void addListener(Class listenerClass) + { + String className = listenerClass.getName(); + trace("addListener " + className); + + checkContextState(); + + if (listener_classnames_.contains(className)) { + log("addListener " + className + " already added as listener"); + return; + } + + try { + Constructor ctor = listenerClass.getConstructor(); + EventListener listener = (EventListener) ctor.newInstance(); + + addListener(listener); + + listener_classnames_.add(className); + } catch (Exception e) { + log("addListener: exception caught: " + e.toString()); + } + } + + @Override + public T createListener(Class clazz) + throws ServletException + { + trace("createListener " + clazz.getName()); + + checkContextState(); + + try + { + return clazz.getDeclaredConstructor().newInstance(); + } + catch (Exception e) + { + throw new ServletException(e); + } + } + + @Override + public ClassLoader getClassLoader() + { + trace("getClassLoader"); + return loader_; + } + + @Override + public int getEffectiveMajorVersion() + { + log("getEffectiveMajorVersion"); + return SERVLET_MAJOR_VERSION; + } + + @Override + public int getEffectiveMinorVersion() + { + log("getEffectiveMinorVersion"); + return SERVLET_MINOR_VERSION; + } + + private final List taglibs_ = new ArrayList<>(); + private final List prop_groups_ = new ArrayList<>(); + + private class JspConfig implements JspConfigDescriptor + { + @Override + public Collection getTaglibs() + { + trace("getTaglibs"); + return taglibs_; + } + + @Override + public Collection getJspPropertyGroups() + { + trace("getJspPropertyGroups"); + return prop_groups_; + } + } + + private final JspConfig jsp_config_ = new JspConfig(); + + @Override + public JspConfigDescriptor getJspConfigDescriptor() + { + trace("getJspConfigDescriptor"); + + return jsp_config_; + } + + @Override + public void declareRoles(String... roleNames) + { + log("declareRoles"); + //LOG.warn(__unimplmented); + } + + @Override + public String getVirtualServerName() + { + log("getVirtualServerName"); + return null; + } + + @Override + public int getSessionTimeout() + { + trace("getSessionTimeout"); + + return session_timeout_; + } + + @Override + public void setSessionTimeout(int sessionTimeout) + { + trace("setSessionTimeout: " + sessionTimeout); + + session_timeout_ = sessionTimeout; + } + + @Override + public String getRequestCharacterEncoding() + { + log("getRequestCharacterEncoding"); + + return null; + } + + @Override + public void setRequestCharacterEncoding(String encoding) + { + log("setRequestCharacterEncoding: " + encoding); + } + + @Override + public String getResponseCharacterEncoding() + { + log("getResponseCharacterEncoding"); + + return null; + } + + @Override + public void setResponseCharacterEncoding(String encoding) + { + log("setResponseCharacterEncoding: " + encoding); + } + + public ServletRequestAttributeListener getRequestAttributeListener() + { + return req_attr_proxy_; + } +} diff --git a/src/java/nginx/unit/DynamicDispatcherRequest.java b/src/java/nginx/unit/DynamicDispatcherRequest.java new file mode 100644 index 00000000..af0747eb --- /dev/null +++ b/src/java/nginx/unit/DynamicDispatcherRequest.java @@ -0,0 +1,8 @@ +package nginx.unit; + +import javax.servlet.DispatcherType; + +public interface DynamicDispatcherRequest +{ + public void setDispatcherType(DispatcherType type); +} diff --git a/src/java/nginx/unit/DynamicPathRequest.java b/src/java/nginx/unit/DynamicPathRequest.java new file mode 100644 index 00000000..efc1bcd1 --- /dev/null +++ b/src/java/nginx/unit/DynamicPathRequest.java @@ -0,0 +1,15 @@ +package nginx.unit; + +public interface DynamicPathRequest + extends DynamicDispatcherRequest +{ + public void setServletPath(String servlet_path, String path_info); + + public void setServletPath(String filter_path, String servlet_path, String path_info); + + public void setRequestURI(String uri); + + public void setQueryString(String query); + + public String getFilterPath(); +} diff --git a/src/java/nginx/unit/ForwardRequestWrapper.java b/src/java/nginx/unit/ForwardRequestWrapper.java new file mode 100644 index 00000000..f88b6aef --- /dev/null +++ b/src/java/nginx/unit/ForwardRequestWrapper.java @@ -0,0 +1,150 @@ +package nginx.unit; + +import java.util.List; +import java.util.Map; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.UrlEncoded; + +public class ForwardRequestWrapper implements DynamicPathRequest +{ + private final Request request_; + + private final boolean keep_attrs; + + private final String orig_filter_path; + private final String orig_servlet_path; + private final String orig_path_info; + private final String orig_uri; + private final String orig_context_path; + private final String orig_query; + + private final DispatcherType orig_dtype; + + private MultiMap orig_parameters; + + public ForwardRequestWrapper(ServletRequest request) + { + if (request instanceof Request) { + request_ = (Request) request; + } else { + request_ = (Request) request.getAttribute(Request.BARE); + } + + keep_attrs = request_.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI) != null; + + orig_dtype = request_.getDispatcherType(); + + orig_filter_path = request_.getFilterPath(); + orig_servlet_path = request_.getServletPath(); + orig_path_info = request_.getPathInfo(); + orig_uri = request_.getRequestURI(); + orig_context_path = request_.getContextPath(); + orig_query = request_.getQueryString(); + } + + @Override + public void setDispatcherType(DispatcherType type) + { + request_.setDispatcherType(type); + + /* + 9.4.2 Forwarded Request Parameters + ... + Note that these attributes must always reflect the information in + the original request even under the situation that multiple + forwards and subsequent includes are called. + */ + + if (keep_attrs) { + return; + } + + /* + 9.4.2 Forwarded Request Parameters + ... + The values of these attributes must be equal to the return values + of the HttpServletRequest methods getRequestURI, getContextPath, + getServletPath, getPathInfo, getQueryString respectively, invoked + on the request object passed to the first servlet object in the + call chain that received the request from the client. + */ + + request_.setAttribute_(RequestDispatcher.FORWARD_SERVLET_PATH, orig_servlet_path); + request_.setAttribute_(RequestDispatcher.FORWARD_PATH_INFO, orig_path_info); + request_.setAttribute_(RequestDispatcher.FORWARD_REQUEST_URI, orig_uri); + request_.setAttribute_(RequestDispatcher.FORWARD_CONTEXT_PATH, orig_context_path); + request_.setAttribute_(RequestDispatcher.FORWARD_QUERY_STRING, orig_query); + } + + @Override + public void setServletPath(String servlet_path, String path_info) + { + request_.setServletPath(servlet_path, path_info); + } + + @Override + public void setServletPath(String filter_path, String servlet_path, String path_info) + { + request_.setServletPath(filter_path, servlet_path, path_info); + } + + @Override + public void setRequestURI(String uri) + { + request_.setRequestURI(uri); + } + + @Override + public void setQueryString(String query) + { + if (query != null) { + orig_parameters = request_.getParameters(); + + MultiMap parameters = new MultiMap<>(); + UrlEncoded.decodeUtf8To(query, parameters); + + for (Map.Entry> e: orig_parameters.entrySet()) { + parameters.addValues(e.getKey(), e.getValue()); + } + + request_.setParameters(parameters); + + request_.setQueryString(query); + } + } + + @Override + public String getFilterPath() + { + return request_.getFilterPath(); + } + + public void close() + { + request_.setDispatcherType(orig_dtype); + + request_.setRequestURI(orig_uri); + request_.setServletPath(orig_filter_path, orig_servlet_path, orig_path_info); + request_.setQueryString(orig_query); + + if (orig_parameters != null) { + request_.setParameters(orig_parameters); + } + + if (keep_attrs) { + return; + } + + request_.setAttribute_(RequestDispatcher.FORWARD_SERVLET_PATH, null); + request_.setAttribute_(RequestDispatcher.FORWARD_PATH_INFO, null); + request_.setAttribute_(RequestDispatcher.FORWARD_REQUEST_URI, null); + request_.setAttribute_(RequestDispatcher.FORWARD_CONTEXT_PATH, null); + request_.setAttribute_(RequestDispatcher.FORWARD_QUERY_STRING, null); + } +} diff --git a/src/java/nginx/unit/HeaderNamesEnumeration.java b/src/java/nginx/unit/HeaderNamesEnumeration.java new file mode 100644 index 00000000..d81b8778 --- /dev/null +++ b/src/java/nginx/unit/HeaderNamesEnumeration.java @@ -0,0 +1,42 @@ +package nginx.unit; + +import java.lang.String; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +public class HeaderNamesEnumeration implements Enumeration { + + private long headers_ptr; + private long size; + private long pos = 0; + + public HeaderNamesEnumeration(long _headers_ptr, long _size) { + headers_ptr = _headers_ptr; + size = _size; + } + + @Override + public boolean hasMoreElements() + { + if (pos >= size) { + return false; + } + + pos = nextElementPos(headers_ptr, size, pos); + return pos < size; + } + + static private native long nextElementPos(long headers_ptr, long size, long pos); + + @Override + public String nextElement() + { + if (pos >= size) { + throw new NoSuchElementException(); + } + + return nextElement(headers_ptr, size, pos++); + } + + static private native String nextElement(long headers_ptr, long size, long pos); +} diff --git a/src/java/nginx/unit/HeadersEnumeration.java b/src/java/nginx/unit/HeadersEnumeration.java new file mode 100644 index 00000000..31b5ae24 --- /dev/null +++ b/src/java/nginx/unit/HeadersEnumeration.java @@ -0,0 +1,40 @@ +package nginx.unit; + +import java.lang.String; +import java.util.Enumeration; + +public class HeadersEnumeration implements Enumeration { + + private long headers_ptr; + private long size; + private long initial_pos; + private long pos; + + public HeadersEnumeration(long _headers_ptr, long _size, long _initial_pos) { + headers_ptr = _headers_ptr; + size = _size; + initial_pos = _initial_pos; + pos = _initial_pos; + } + + @Override + public boolean hasMoreElements() + { + if (pos >= size) { + return false; + } + + pos = nextElementPos(headers_ptr, size, initial_pos, pos); + return pos < size; + } + + static private native long nextElementPos(long headers_ptr, long size, long initial_pos, long pos); + + @Override + public String nextElement() + { + return nextElement(headers_ptr, size, initial_pos, pos++); + } + + static private native String nextElement(long headers_ptr, long size, long initial_pos, long pos); +} diff --git a/src/java/nginx/unit/IncludeRequestWrapper.java b/src/java/nginx/unit/IncludeRequestWrapper.java new file mode 100644 index 00000000..67a51b24 --- /dev/null +++ b/src/java/nginx/unit/IncludeRequestWrapper.java @@ -0,0 +1,88 @@ +package nginx.unit; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletRequest; + +public class IncludeRequestWrapper implements DynamicPathRequest +{ + private final Request request_; + + private final Object orig_servlet_path_attr; + private final Object orig_path_info_attr; + private final Object orig_uri_attr; + private final Object orig_context_path_attr; + private final Object orig_query_string_attr; + + private final DispatcherType orig_dtype; + + private String filter_path_; + + public IncludeRequestWrapper(ServletRequest request) + { + if (request instanceof Request) { + request_ = (Request) request; + } else { + request_ = (Request) request.getAttribute(Request.BARE); + } + + orig_servlet_path_attr = request_.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); + orig_path_info_attr = request_.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO); + orig_uri_attr = request_.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI); + orig_context_path_attr = request_.getAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH); + orig_query_string_attr = request_.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING); + + orig_dtype = request_.getDispatcherType(); + + request_.setAttribute_(RequestDispatcher.INCLUDE_CONTEXT_PATH, request_.getContextPath()); + } + + @Override + public void setDispatcherType(DispatcherType type) + { + request_.setDispatcherType(type); + } + + @Override + public void setServletPath(String servlet_path, String path_info) + { + setServletPath(servlet_path, servlet_path, path_info); + } + + @Override + public void setServletPath(String filter_path, String servlet_path, String path_info) + { + request_.setAttribute_(RequestDispatcher.INCLUDE_SERVLET_PATH, servlet_path); + request_.setAttribute_(RequestDispatcher.INCLUDE_PATH_INFO, path_info); + filter_path_ = filter_path; + } + + @Override + public void setRequestURI(String uri) + { + request_.setAttribute_(RequestDispatcher.INCLUDE_REQUEST_URI, uri); + } + + @Override + public void setQueryString(String query) + { + request_.setAttribute_(RequestDispatcher.INCLUDE_QUERY_STRING, query); + } + + @Override + public String getFilterPath() + { + return filter_path_; + } + + public void close() + { + request_.setDispatcherType(orig_dtype); + + request_.setAttribute_(RequestDispatcher.INCLUDE_SERVLET_PATH, orig_servlet_path_attr); + request_.setAttribute_(RequestDispatcher.INCLUDE_PATH_INFO, orig_path_info_attr); + request_.setAttribute_(RequestDispatcher.INCLUDE_REQUEST_URI, orig_uri_attr); + request_.setAttribute_(RequestDispatcher.INCLUDE_CONTEXT_PATH, orig_context_path_attr); + request_.setAttribute_(RequestDispatcher.INCLUDE_QUERY_STRING, orig_query_string_attr); + } +} diff --git a/src/java/nginx/unit/IncludeResponseWrapper.java b/src/java/nginx/unit/IncludeResponseWrapper.java new file mode 100644 index 00000000..4537114a --- /dev/null +++ b/src/java/nginx/unit/IncludeResponseWrapper.java @@ -0,0 +1,117 @@ +package nginx.unit; + +import java.io.IOException; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +public class IncludeResponseWrapper extends HttpServletResponseWrapper { + private static final Charset UTF_8 = StandardCharsets.UTF_8; + + public IncludeResponseWrapper(ServletResponse response) + { + super((HttpServletResponse) response); + } + + @Override + public void addCookie(Cookie cookie) + { + trace("addCookie: " + cookie.getName() + "=" + cookie.getValue()); + } + + @Override + public void addDateHeader(String name, long date) + { + trace("addDateHeader: " + name + ": " + date); + } + + @Override + public void addHeader(String name, String value) + { + trace("addHeader: " + name + ": " + value); + } + + @Override + public void addIntHeader(String name, int value) + { + trace("addIntHeader: " + name + ": " + value); + } + + @Override + public void sendRedirect(String location) throws IOException + { + trace("sendRedirect: " + location); + } + + @Override + public void setDateHeader(String name, long date) + { + trace("setDateHeader: " + name + ": " + date); + } + + @Override + public void setHeader(String name, String value) + { + trace("setHeader: " + name + ": " + value); + } + + @Override + public void setIntHeader(String name, int value) + { + trace("setIntHeader: " + name + ": " + value); + } + + @Override + public void setStatus(int sc) + { + trace("setStatus: " + sc); + } + + @Override + @Deprecated + public void setStatus(int sc, String sm) + { + trace("setStatus: " + sc + "; " + sm); + } + + @Override + public void reset() + { + trace("reset"); + } + + @Override + public void setCharacterEncoding(String charset) + { + trace("setCharacterEncoding " + charset); + } + + @Override + public void setContentLength(int len) + { + trace("setContentLength: " + len); + } + + @Override + public void setContentLengthLong(long len) + { + trace("setContentLengthLong: " + len); + } + + @Override + public void setContentType(String type) + { + trace("setContentType: " + type); + } + + private void trace(String msg) + { + msg = "IncludeResponse." + msg; + Response.trace(0, msg.getBytes(UTF_8)); + } +} diff --git a/src/java/nginx/unit/InitParams.java b/src/java/nginx/unit/InitParams.java new file mode 100644 index 00000000..2f5dcbf9 --- /dev/null +++ b/src/java/nginx/unit/InitParams.java @@ -0,0 +1,7 @@ +package nginx.unit; + +public interface InitParams { + public boolean setInitParameter(String name, String value); + + public String getInitParameter(String name); +} diff --git a/src/java/nginx/unit/InputStream.java b/src/java/nginx/unit/InputStream.java new file mode 100644 index 00000000..6fe72ace --- /dev/null +++ b/src/java/nginx/unit/InputStream.java @@ -0,0 +1,90 @@ +package nginx.unit; + +import java.io.IOException; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; + +public class InputStream extends ServletInputStream { + + private long req_info_ptr; + + public InputStream(long ptr) + { + req_info_ptr = ptr; + } + + @Override + public int readLine(byte[] b, int off, int len) throws IOException { + + if (len <= 0) { + return 0; + } + return readLine(req_info_ptr, b, off, len); + } + + private static native int readLine(long req_info_ptr, byte[] b, int off, int len); + + + @Override + public boolean isFinished() + { + return isFinished(req_info_ptr); + } + + private static native boolean isFinished(long req_info_ptr); + + + @Override + public boolean isReady() + { + return true; + } + + + @Override + public void setReadListener(ReadListener listener) + { + } + + + @Override + public int read() throws IOException + { + return read(req_info_ptr); + } + + private static native int read(long req_info_ptr); + + + @Override + public int read(byte b[], int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + return read(req_info_ptr, b, off, len); + } + + private static native int read(long req_info_ptr, byte b[], int off, int len); + + + @Override + public long skip(long n) throws IOException { + return skip(req_info_ptr, n); + } + + private static native long skip(long req_info_ptr, long n); + + + @Override + public int available() throws IOException { + return available(req_info_ptr); + } + + private static native int available(long req_info_ptr); +} diff --git a/src/java/nginx/unit/JspPropertyGroup.java b/src/java/nginx/unit/JspPropertyGroup.java new file mode 100644 index 00000000..2df27718 --- /dev/null +++ b/src/java/nginx/unit/JspPropertyGroup.java @@ -0,0 +1,169 @@ +package nginx.unit; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.servlet.descriptor.JspPropertyGroupDescriptor; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class JspPropertyGroup implements JspPropertyGroupDescriptor +{ + private final List url_patterns_ = new ArrayList<>(); + private String el_ignored_ = null; + private String page_encoding_ = null; + private String scripting_invalid_ = null; + private String is_xml_ = null; + private final List include_preludes_ = new ArrayList<>(); + private final List include_codas_ = new ArrayList<>(); + + private String deffered_syntax_allowed_as_literal_ = null; + private String trim_directive_whitespaces_ = null; + private String default_content_type_ = null; + private String buffer_ = null; + private String error_on_undeclared_namespace_ = null; + + public JspPropertyGroup(NodeList nodes) + { + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + String tag_name = node.getNodeName(); + + if (tag_name.equals("url-pattern")) { + url_patterns_.add(node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("el-ignored")) { + el_ignored_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("page-encoding")) { + page_encoding_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("scripting-invalid")) { + scripting_invalid_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("is-xml")) { + is_xml_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("include-prelude")) { + include_preludes_.add(node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("include-coda")) { + include_codas_.add(node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("deferred-syntax-allowed-as-literal")) { + deffered_syntax_allowed_as_literal_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("trim-directive-whitespaces")) { + trim_directive_whitespaces_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("default-content-type")) { + default_content_type_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("buffer")) { + buffer_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("error-on-undeclared-namespace")) { + error_on_undeclared_namespace_ = node.getTextContent().trim(); + continue; + } + } + + } + + @Override + public Collection getUrlPatterns() + { + return new ArrayList<>(url_patterns_); + } + + @Override + public String getElIgnored() + { + return el_ignored_; + } + + @Override + public String getPageEncoding() + { + return page_encoding_; + } + + @Override + public String getScriptingInvalid() + { + return scripting_invalid_; + } + + @Override + public String getIsXml() + { + return is_xml_; + } + + @Override + public Collection getIncludePreludes() + { + return new ArrayList<>(include_preludes_); + } + + @Override + public Collection getIncludeCodas() + { + return new ArrayList<>(include_codas_); + } + + @Override + public String getDeferredSyntaxAllowedAsLiteral() + { + return deffered_syntax_allowed_as_literal_; + } + + @Override + public String getTrimDirectiveWhitespaces() + { + return trim_directive_whitespaces_; + } + + @Override + public String getDefaultContentType() + { + return default_content_type_; + } + + @Override + public String getBuffer() + { + return buffer_; + } + + @Override + public String getErrorOnUndeclaredNamespace() + { + return error_on_undeclared_namespace_; + } +} + diff --git a/src/java/nginx/unit/OutputStream.java b/src/java/nginx/unit/OutputStream.java new file mode 100644 index 00000000..68af3423 --- /dev/null +++ b/src/java/nginx/unit/OutputStream.java @@ -0,0 +1,68 @@ +package nginx.unit; + +import java.io.IOException; + +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; + +public class OutputStream extends ServletOutputStream { + + private long req_info_ptr; + + public OutputStream(long ptr) { + req_info_ptr = ptr; + } + + @Override + public void write(int b) throws IOException + { + write(req_info_ptr, b); + } + + private static native void write(long req_info_ptr, int b); + + + @Override + public void write(byte b[], int off, int len) throws IOException + { + if (b == null) { + throw new NullPointerException(); + } else if ((off < 0) || (off > b.length) || (len < 0) || + ((off + len) > b.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + + write(req_info_ptr, b, off, len); + } + + private static native void write(long req_info_ptr, byte b[], int off, int len); + + @Override + public void flush() + { + flush(req_info_ptr); + } + + private static native void flush(long req_info_ptr); + + @Override + public void close() + { + close(req_info_ptr); + } + + private static native void close(long req_info_ptr); + + @Override + public boolean isReady() + { + return true; + } + + @Override + public void setWriteListener(WriteListener listener) + { + } +} diff --git a/src/java/nginx/unit/Request.java b/src/java/nginx/unit/Request.java new file mode 100644 index 00000000..dc73c656 --- /dev/null +++ b/src/java/nginx/unit/Request.java @@ -0,0 +1,1123 @@ +package nginx.unit; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import java.lang.IllegalArgumentException; +import java.lang.IllegalStateException; +import java.lang.Object; +import java.lang.String; +import java.lang.StringBuffer; + +import java.net.URI; +import java.net.URISyntaxException; + +import java.text.ParseException; +import java.text.SimpleDateFormat; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import java.security.Principal; + +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.ServletResponse; +import javax.servlet.SessionTrackingMode; +import javax.servlet.SessionCookieConfig; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpUpgradeHandler; +import javax.servlet.http.Part; + +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.UrlEncoded; +import org.eclipse.jetty.util.StringUtil; + +import org.eclipse.jetty.server.CookieCutter; +import org.eclipse.jetty.http.MimeTypes; + +public class Request implements HttpServletRequest, DynamicPathRequest +{ + private final Context context; + private final long req_info_ptr; + private final long req_ptr; + + protected String authType = null; + + protected boolean cookiesParsed = false; + + protected CookieCutter cookies = null; + + private final Map attributes = new HashMap<>(); + + private MultiMap parameters = null; + + private final String context_path; + private String filter_path = null; + private String servlet_path = null; + private String path_info = null; + private String request_uri = null; + private String query_string = null; + private boolean query_string_valid = false; + + private DispatcherType dispatcher_type = DispatcherType.REQUEST; + + private String characterEncoding = null; + + /** + * The only date format permitted when generating HTTP headers. + */ + public static final String RFC1123_DATE = + "EEE, dd MMM yyyy HH:mm:ss zzz"; + + private static final SimpleDateFormat formats[] = { + new SimpleDateFormat(RFC1123_DATE, Locale.US), + new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), + new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) + }; + + private InputStream inputStream = null; + private BufferedReader reader = null; + + private boolean request_session_id_parsed = false; + private String request_session_id = null; + private boolean request_session_id_from_cookie = false; + private boolean request_session_id_from_url = false; + private Session session = null; + + private final ServletRequestAttributeListener attr_listener; + + public static final String BARE = "nginx.unit.request.bare"; + + public Request(Context ctx, long req_info, long req) { + context = ctx; + req_info_ptr = req_info; + req_ptr = req; + + attr_listener = context.getRequestAttributeListener(); + context_path = context.getContextPath(); + } + + @Override + public boolean authenticate(HttpServletResponse response) + throws IOException, ServletException + { + log("authenticate"); + + if (response.isCommitted()) { + throw new IllegalStateException(); + } + + return false; + } + + @Override + public String getAuthType() + { + log("getAuthType"); + + return authType; + } + + @Override + public String getContextPath() + { + trace("getContextPath: " + context_path); + + return context_path; + } + + @Override + public Cookie[] getCookies() + { + trace("getCookies"); + + if (!cookiesParsed) { + parseCookies(); + } + + //Javadoc for Request.getCookies() stipulates null for no cookies + if (cookies == null || cookies.getCookies().length == 0) { + return null; + } + + return cookies.getCookies(); + } + + protected void parseCookies() + { + cookiesParsed = true; + + cookies = new CookieCutter(); + + Enumeration cookie_headers = getHeaders("Cookie"); + + while (cookie_headers.hasMoreElements()) { + cookies.addCookieField(cookie_headers.nextElement()); + } + } + + @Override + public long getDateHeader(String name) + { + trace("getDateHeader: " + name); + + String value = getHeader(name); + if (value == null) { + return -1L; + } + + long res = parseDate(value); + if (res == -1L) { + throw new IllegalArgumentException(value); + } + + return res; + } + + protected long parseDate(String value) + { + Date date = null; + for (int i = 0; (date == null) && (i < formats.length); i++) { + try { + date = formats[i].parse(value); + } catch (ParseException e) { + // Ignore + } + } + if (date == null) { + return -1L; + } + return date.getTime(); + } + + @Override + public String getHeader(String name) + { + String res = getHeader(req_ptr, name, name.length()); + + trace("getHeader: " + name + " = '" + res + "'"); + + return res; + } + + private static native String getHeader(long req_ptr, String name, int name_len); + + + @Override + public Enumeration getHeaderNames() + { + trace("getHeaderNames"); + + return getHeaderNames(req_ptr); + } + + private static native Enumeration getHeaderNames(long req_ptr); + + + @Override + public Enumeration getHeaders(String name) + { + trace("getHeaders: " + name); + + return getHeaders(req_ptr, name, name.length()); + } + + private static native Enumeration getHeaders(long req_ptr, String name, int name_len); + + + @Override + public int getIntHeader(String name) + { + trace("getIntHeader: " + name); + + return getIntHeader(req_ptr, name, name.length()); + } + + private static native int getIntHeader(long req_ptr, String name, int name_len); + + + @Override + public String getMethod() + { + trace("getMethod"); + + return getMethod(req_ptr); + } + + private static native String getMethod(long req_ptr); + + + @Override + public Part getPart(String name) throws IOException, ServletException + { + log("getPart: " + name); + + return null; + } + + @Override + public Collection getParts() throws IOException, ServletException + { + log("getParts"); + + return Collections.emptyList(); + } + + @Override + public String getPathInfo() + { + trace("getPathInfo: " + path_info); + + return path_info; + } + + @Override + public String getPathTranslated() + { + trace("getPathTranslated"); + + if (path_info == null) { + return null; + } + + return context.getRealPath(path_info); + } + + @Override + public String getQueryString() + { + if (!query_string_valid) { + query_string = getQueryString(req_ptr); + query_string_valid = true; + } + + trace("getQueryString: " + query_string); + + return query_string; + } + + private static native String getQueryString(long req_ptr); + + @Override + public void setQueryString(String query) + { + trace("setQueryString: " + query); + + query_string = query; + query_string_valid = true; + } + + @Override + public String getRemoteUser() + { + log("getRemoteUser"); + + /* TODO */ + return null; + } + + @Override + public String getRequestedSessionId() + { + trace("getRequestedSessionId"); + + if (!request_session_id_parsed) { + parseRequestSessionId(); + } + + return request_session_id; + } + + private void parseRequestSessionId() + { + request_session_id_parsed = true; + + Cookie[] cookies = getCookies(); + if (cookies == null) { + return; + } + + if (context.getEffectiveSessionTrackingModes().contains( + SessionTrackingMode.COOKIE)) + { + final String name = context.getSessionCookieConfig().getName(); + + for (Cookie c : cookies) { + if (c.getName().equals(name)) { + request_session_id = c.getValue(); + request_session_id_from_cookie = true; + + return; + } + } + } + } + + @Override + public String getRequestURI() + { + if (request_uri == null) { + request_uri = getRequestURI(req_ptr); + } + + trace("getRequestURI: " + request_uri); + + return request_uri; + } + + private static native String getRequestURI(long req_ptr); + + + @Override + public void setRequestURI(String uri) + { + trace("setRequestURI: " + uri); + + request_uri = uri; + } + + + @Override + public StringBuffer getRequestURL() + { + String host = getHeader("Host"); + String uri = getRequestURI(); + StringBuffer res = new StringBuffer("http://" + host + uri); + + trace("getRequestURL: " + res); + + return res; + } + + @Override + public String getServletPath() + { + trace("getServletPath: " + servlet_path); + + return servlet_path; + } + + @Override + public void setServletPath(String servlet_path, String path_info) + { + trace("setServletPath: " + servlet_path); + + this.filter_path = servlet_path; + this.servlet_path = servlet_path; + this.path_info = path_info; + } + + @Override + public void setServletPath(String filter_path, String servlet_path, String path_info) + { + trace("setServletPath: " + filter_path + ", " + servlet_path); + + this.filter_path = filter_path; + this.servlet_path = servlet_path; + this.path_info = path_info; + } + + @Override + public String getFilterPath() + { + return filter_path; + } + + @Override + public HttpSession getSession() + { + return getSession(true); + } + + @Override + public HttpSession getSession(boolean create) + { + if (session != null) { + if (context.isSessionIdValid(session.getId())) { + trace("getSession(" + create + "): " + session.getId()); + + return session; + } + + session = null; + } + + if (!request_session_id_parsed) { + parseRequestSessionId(); + + session = context.getSession(request_session_id); + } + + if (session != null || !create) { + trace("getSession(" + create + "): " + (session != null ? session.getId() : "null")); + + return session; + } + + session = context.createSession(); + + if (context.getEffectiveSessionTrackingModes().contains( + SessionTrackingMode.COOKIE)) + { + setSessionIdCookie(); + } + + trace("getSession(" + create + "): " + session.getId()); + + return session; + } + + private void setSessionIdCookie() + { + SessionCookieConfig config = context.getSessionCookieConfig(); + + Cookie c = new Cookie(config.getName(), session.getId()); + + c.setComment(config.getComment()); + if (!StringUtil.isBlank(config.getDomain())) { + c.setDomain(config.getDomain()); + } + + c.setHttpOnly(config.isHttpOnly()); + if (!StringUtil.isBlank(config.getPath())) { + c.setPath(config.getPath()); + } + + c.setMaxAge(config.getMaxAge()); + + getResponse(req_info_ptr).addSessionIdCookie(c); + } + + @Override + public Principal getUserPrincipal() + { + log("getUserPrincipal"); + + return null; + } + + @Override + public boolean isRequestedSessionIdFromCookie() + { + trace("isRequestedSessionIdFromCookie"); + + if (!request_session_id_parsed) { + parseRequestSessionId(); + } + + return request_session_id_from_cookie; + } + + @Override + @Deprecated + public boolean isRequestedSessionIdFromUrl() + { + trace("isRequestedSessionIdFromUrl"); + + if (!request_session_id_parsed) { + parseRequestSessionId(); + } + + return request_session_id_from_url; + } + + @Override + public boolean isRequestedSessionIdFromURL() + { + trace("isRequestedSessionIdFromURL"); + + if (!request_session_id_parsed) { + parseRequestSessionId(); + } + + return request_session_id_from_url; + } + + @Override + public boolean isRequestedSessionIdValid() + { + trace("isRequestedSessionIdValid"); + + if (!request_session_id_parsed) { + parseRequestSessionId(); + } + + return context.isSessionIdValid(request_session_id); + } + + @Override + public boolean isUserInRole(String role) + { + log("isUserInRole: " + role); + + return false; + } + + @Override + public void login(String username, String password) throws ServletException + { + log("login: " + username + "," + password); + } + + @Override + public void logout() throws ServletException + { + log("logout"); + } + + + @Override + public AsyncContext getAsyncContext() + { + log("getAsyncContext"); + + return null; + } + + @Override + public Object getAttribute(String name) + { + if (BARE.equals(name)) { + return this; + } + + Object o = attributes.get(name); + + trace("getAttribute: " + name + " = " + o); + + return o; + } + + @Override + public Enumeration getAttributeNames() + { + trace("getAttributeNames"); + + Set names = attributes.keySet(); + return Collections.enumeration(names); + } + + @Override + public String getCharacterEncoding() + { + trace("getCharacterEncoding"); + + if (characterEncoding != null) { + return characterEncoding; + } + + getContentType(); + + return characterEncoding; + } + + @Override + public int getContentLength() + { + trace("getContentLength"); + + return (int) getContentLength(req_ptr); + } + + private static native long getContentLength(long req_ptr); + + @Override + public long getContentLengthLong() + { + trace("getContentLengthLong"); + + return getContentLength(req_ptr); + } + + @Override + public String getContentType() + { + trace("getContentType"); + + String content_type = getContentType(req_ptr); + + if (characterEncoding == null && content_type != null) { + MimeTypes.Type mime = MimeTypes.CACHE.get(content_type); + String charset = (mime == null || mime.getCharset() == null) ? MimeTypes.getCharsetFromContentType(content_type) : mime.getCharset().toString(); + if (charset != null) { + characterEncoding = charset; + } + } + + return content_type; + } + + private static native String getContentType(long req_ptr); + + + @Override + public DispatcherType getDispatcherType() + { + trace("getDispatcherType: " + dispatcher_type); + + return dispatcher_type; + } + + @Override + public void setDispatcherType(DispatcherType type) + { + trace("setDispatcherType: " + type); + + dispatcher_type = type; + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + trace("getInputStream"); + + if (reader != null) { + throw new IllegalStateException("getInputStream: getReader() already used"); + } + + if (inputStream == null) { + inputStream = new InputStream(req_info_ptr); + } + + return inputStream; + } + + @Override + public String getLocalAddr() + { + trace("getLocalAddr"); + + return getLocalAddr(req_ptr); + } + + private static native String getLocalAddr(long req_ptr); + + + @Override + public Locale getLocale() + { + log("getLocale"); + + return Locale.getDefault(); + } + + @Override + public Enumeration getLocales() + { + log("getLocales"); + + return Collections.emptyEnumeration(); + } + + @Override + public String getLocalName() + { + trace("getLocalName"); + + return getLocalName(req_ptr); + } + + private static native String getLocalName(long req_ptr); + + + @Override + public int getLocalPort() + { + trace("getLocalPort"); + + return getLocalPort(req_ptr); + } + + private static native int getLocalPort(long req_ptr); + + + public MultiMap getParameters() + { + if (parameters != null) { + return parameters; + } + + parameters = new MultiMap<>(); + + String query = getQueryString(); + + if (query != null) { + UrlEncoded.decodeUtf8To(query, parameters); + } + + if (getContentLength() > 0 && + getMethod().equals("POST") && + getContentType().startsWith("application/x-www-form-urlencoded")) + { + try { + UrlEncoded.decodeUtf8To(new InputStream(req_info_ptr), + parameters, getContentLength(), -1); + } catch (IOException e) { + log("Unhandled IOException: " + e); + } + } + + return parameters; + } + + public void setParameters(MultiMap p) + { + parameters = p; + } + + @Override + public String getParameter(String name) + { + trace("getParameter: " + name); + + return getParameters().getValue(name, 0); + } + + @Override + public Map getParameterMap() + { + trace("getParameterMap"); + + return Collections.unmodifiableMap(getParameters().toStringArrayMap()); + } + + @Override + public Enumeration getParameterNames() + { + trace("getParameterNames"); + + return Collections.enumeration(getParameters().keySet()); + } + + @Override + public String[] getParameterValues(String name) + { + trace("getParameterValues: " + name); + + List vals = getParameters().getValues(name); + if (vals == null) + return null; + return vals.toArray(new String[vals.size()]); + } + + @Override + public String getProtocol() + { + trace("getProtocol"); + + return getProtocol(req_ptr); + } + + private static native String getProtocol(long req_ptr); + + @Override + public BufferedReader getReader() throws IOException + { + trace("getReader"); + + if (inputStream != null) { + throw new IllegalStateException("getReader: getInputStream() already used"); + } + + if (reader == null) { + reader = new BufferedReader(new InputStreamReader(new InputStream(req_info_ptr))); + } + + return reader; + } + + @Override + @Deprecated + public String getRealPath(String path) + { + trace("getRealPath: " + path); + + return context.getRealPath(path); + } + + @Override + public String getRemoteAddr() + { + String res = getRemoteAddr(req_ptr); + + trace("getRemoteAddr: " + res); + + return res; + } + + private static native String getRemoteAddr(long req_ptr); + + + @Override + public String getRemoteHost() + { + String res = getRemoteHost(req_ptr); + + trace("getRemoteHost: " + res); + + return res; + } + + private static native String getRemoteHost(long req_ptr); + + + @Override + public int getRemotePort() + { + int res = getRemotePort(req_ptr); + + trace("getRemotePort: " + res); + + return res; + } + + private static native int getRemotePort(long req_ptr); + + + @Override + public RequestDispatcher getRequestDispatcher(String path) + { + trace("getRequestDispatcher: " + path); + + if (path.startsWith("/")) { + return context.getRequestDispatcher(path); + } + + try { + URI uri = new URI(getRequestURI()); + uri = uri.resolve(path); + + return context.getRequestDispatcher(uri); + } catch (URISyntaxException e) { + log("getRequestDispatcher: failed to create dispatcher: " + e); + } + + return null; + } + + + @Override + public String getScheme() + { + log("getScheme"); + + return getScheme(req_ptr); + } + + private static native String getScheme(long req_ptr); + + + @Override + public String getServerName() + { + String res = getServerName(req_ptr); + + trace("getServerName: " + res); + + return res; + } + + private static native String getServerName(long req_ptr); + + + @Override + public int getServerPort() + { + int res = getServerPort(req_ptr); + + trace("getServerPort: " + res); + + return res; + } + + private static native int getServerPort(long req_ptr); + + @Override + public ServletContext getServletContext() + { + trace("getServletContext"); + + return context; + } + + @Override + public boolean isAsyncStarted() + { + log("isAsyncStarted"); + + return false; + } + + @Override + public boolean isAsyncSupported() + { + log("isAsyncSupported"); + + return false; + } + + @Override + public boolean isSecure() + { + log("isSecure"); + + return false; + } + + @Override + public void removeAttribute(String name) + { + trace("removeAttribute: " + name); + + Object prev = attributes.remove(name); + + if (attr_listener == null || prev == null) { + return; + } + + attr_listener.attributeRemoved( + new ServletRequestAttributeEvent(context, this, name, prev)); + } + + @Override + public void setAttribute(String name, Object o) + { + trace("setAttribute: " + name + ", " + o); + + Object prev; + + if (o != null) { + prev = attributes.put(name, o); + } else { + prev = attributes.remove(name); + } + + if (attr_listener == null) { + return; + } + + if (prev == null) { + if (o == null) { + return; + } + + attr_listener.attributeAdded(new ServletRequestAttributeEvent( + context, this, name, o)); + } else { + if (o != null) { + attr_listener.attributeReplaced( + new ServletRequestAttributeEvent(context, this, name, prev)); + } else { + attr_listener.attributeRemoved( + new ServletRequestAttributeEvent(context, this, name, prev)); + } + } + } + + public void setAttribute_(String name, Object o) + { + trace("setAttribute_: " + name + ", " + o); + + if (o != null) { + attributes.put(name, o); + } else { + attributes.remove(name); + } + } + + @Override + public void setCharacterEncoding(String env) throws UnsupportedEncodingException + { + trace("setCharacterEncoding: " + env); + + characterEncoding = env; + } + + @Override + public AsyncContext startAsync() throws IllegalStateException + { + log("startAsync"); + + return null; + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException + { + log("startAsync(Req, resp)"); + + return null; + } + + @Override + public T upgrade( + Class httpUpgradeHandlerClass) throws java.io.IOException, ServletException + { + log("upgrade: " + httpUpgradeHandlerClass.getName()); + + return null; + } + + @Override + public String changeSessionId() + { + trace("changeSessionId"); + + getSession(false); + + if (session == null) { + return null; + } + + context.changeSessionId(session); + + if (context.getEffectiveSessionTrackingModes().contains( + SessionTrackingMode.COOKIE)) + { + setSessionIdCookie(); + } + + return session.getId(); + } + + private void log(String msg) + { + msg = "Request." + msg; + log(req_info_ptr, msg, msg.length()); + } + + public static native void log(long req_info_ptr, String msg, int msg_len); + + + private void trace(String msg) + { + msg = "Request." + msg; + trace(req_info_ptr, msg, msg.length()); + } + + public static native void trace(long req_info_ptr, String msg, int msg_len); + + private static native Response getResponse(long req_info_ptr); +} + diff --git a/src/java/nginx/unit/RequestAttrProxy.java b/src/java/nginx/unit/RequestAttrProxy.java new file mode 100644 index 00000000..daedd01a --- /dev/null +++ b/src/java/nginx/unit/RequestAttrProxy.java @@ -0,0 +1,40 @@ +package nginx.unit; + +import java.util.List; + +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; + +public class RequestAttrProxy implements ServletRequestAttributeListener +{ + private final List listeners_; + + public RequestAttrProxy(List listeners) + { + listeners_ = listeners; + } + + @Override + public void attributeAdded(ServletRequestAttributeEvent srae) + { + for (ServletRequestAttributeListener l : listeners_) { + l.attributeAdded(srae); + } + } + + @Override + public void attributeReplaced(ServletRequestAttributeEvent srae) + { + for (ServletRequestAttributeListener l : listeners_) { + l.attributeReplaced(srae); + } + } + + @Override + public void attributeRemoved(ServletRequestAttributeEvent srae) + { + for (ServletRequestAttributeListener l : listeners_) { + l.attributeRemoved(srae); + } + } +} diff --git a/src/java/nginx/unit/Response.java b/src/java/nginx/unit/Response.java new file mode 100644 index 00000000..099d7f15 --- /dev/null +++ b/src/java/nginx/unit/Response.java @@ -0,0 +1,817 @@ +package nginx.unit; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; + +import java.lang.IllegalArgumentException; +import java.lang.String; + +import java.net.URI; +import java.net.URISyntaxException; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import java.text.SimpleDateFormat; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.Locale; +import java.util.TimeZone; +import java.util.Vector; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.util.StringUtil; + +public class Response implements HttpServletResponse { + + private long req_info_ptr; + + private static final String defaultCharacterEncoding = "iso-8859-1"; + private String characterEncoding = defaultCharacterEncoding; + private String contentType = null; + private String contentTypeHeader = null; + + private static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1; + private static final Charset UTF_8 = StandardCharsets.UTF_8; + + private static final String CONTENT_TYPE = "Content-Type"; + private static final byte[] SET_COOKIE_BYTES = "Set-Cookie".getBytes(ISO_8859_1); + private static final byte[] EXPIRES_BYTES = "Expires".getBytes(ISO_8859_1); + + /** + * The only date format permitted when generating HTTP headers. + */ + public static final String RFC1123_DATE = + "EEE, dd MMM yyyy HH:mm:ss zzz"; + + private static final SimpleDateFormat format = + new SimpleDateFormat(RFC1123_DATE, Locale.US); + + private static final String ZERO_DATE_STRING = dateToString(0); + private static final byte[] ZERO_DATE_BYTES = ZERO_DATE_STRING.getBytes(ISO_8859_1); + + /** + * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie + * will be set as HTTP ONLY. + */ + public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__"; + + private OutputStream outputStream = null; + + private PrintWriter writer = null; + + + public Response(long ptr) { + req_info_ptr = ptr; + } + + /** + * Format a set cookie value by RFC6265 + * + * @param name the name + * @param value the value + * @param domain the domain + * @param path the path + * @param maxAge the maximum age + * @param isSecure true if secure cookie + * @param isHttpOnly true if for http only + */ + public void addSetRFC6265Cookie( + final String name, + final String value, + final String domain, + final String path, + final long maxAge, + final boolean isSecure, + final boolean isHttpOnly) + { + // Check arguments + if (name == null || name.length() == 0) { + throw new IllegalArgumentException("Bad cookie name"); + } + + // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting + // Per RFC6265, Cookie.name follows RFC2616 Section 2.2 token rules + //Syntax.requireValidRFC2616Token(name, "RFC6265 Cookie name"); + // Ensure that Per RFC6265, Cookie.value follows syntax rules + //Syntax.requireValidRFC6265CookieValue(value); + + // Format value and params + StringBuilder buf = new StringBuilder(); + buf.append(name).append('=').append(value == null ? "" : value); + + // Append path + if (path != null && path.length() > 0) { + buf.append(";Path=").append(path); + } + + // Append domain + if (domain != null && domain.length() > 0) { + buf.append(";Domain=").append(domain); + } + + // Handle max-age and/or expires + if (maxAge >= 0) { + // Always use expires + // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies + buf.append(";Expires="); + if (maxAge == 0) + buf.append(ZERO_DATE_STRING); + else + buf.append(dateToString(System.currentTimeMillis() + 1000L * maxAge)); + + buf.append(";Max-Age="); + buf.append(maxAge); + } + + // add the other fields + if (isSecure) + buf.append(";Secure"); + if (isHttpOnly) + buf.append(";HttpOnly"); + + // add the set cookie + addHeader(req_info_ptr, SET_COOKIE_BYTES, + buf.toString().getBytes(ISO_8859_1)); + + // Expire responses with set-cookie headers so they do not get cached. + setHeader(req_info_ptr, EXPIRES_BYTES, ZERO_DATE_BYTES); + } + + @Override + public void addCookie(Cookie cookie) + { + trace("addCookie: " + cookie.getName() + "=" + cookie.getValue()); + + if (StringUtil.isBlank(cookie.getName())) { + throw new IllegalArgumentException("Cookie.name cannot be blank/null"); + } + + if (isCommitted()) { + return; + } + + addCookie_(cookie); + } + + private void addCookie_(Cookie cookie) + { + String comment = cookie.getComment(); + boolean httpOnly = false; + + if (comment != null && comment.contains(HTTP_ONLY_COMMENT)) { + httpOnly = true; + } + + addSetRFC6265Cookie(cookie.getName(), + cookie.getValue(), + cookie.getDomain(), + cookie.getPath(), + cookie.getMaxAge(), + cookie.getSecure(), + httpOnly || cookie.isHttpOnly()); + } + + public void addSessionIdCookie(Cookie cookie) + { + trace("addSessionIdCookie: " + cookie.getName() + "=" + cookie.getValue()); + + if (isCommitted()) { + /* + 9.3 The Include Method + + ... any call to HttpServletRequest.getSession() or + HttpServletRequest.getSession(boolean) that would require + adding a Cookie response header must throw an + IllegalStateException if the response has been committed. + */ + throw new IllegalStateException("Response already sent"); + } + + addCookie_(cookie); + } + + @Override + public void addDateHeader(String name, long date) + { + trace("addDateHeader: " + name + ": " + date); + + if (isCommitted()) { + return; + } + + String value = dateToString(date); + + addHeader(req_info_ptr, name.getBytes(ISO_8859_1), + value.getBytes(ISO_8859_1)); + } + + private static String dateToString(long date) + { + Date dateValue = new Date(date); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + return format.format(dateValue); + } + + + @Override + public void addHeader(String name, String value) + { + trace("addHeader: " + name + ": " + value); + + if (value == null) { + return; + } + + if (isCommitted()) { + return; + } + + if (CONTENT_TYPE.equalsIgnoreCase(name)) { + setContentType(value); + return; + } + + addHeader(req_info_ptr, name.getBytes(ISO_8859_1), + value.getBytes(ISO_8859_1)); + } + + private static native void addHeader(long req_info_ptr, byte[] name, byte[] value); + + + @Override + public void addIntHeader(String name, int value) + { + trace("addIntHeader: " + name + ": " + value); + + if (isCommitted()) { + return; + } + + addIntHeader(req_info_ptr, name.getBytes(ISO_8859_1), value); + } + + private static native void addIntHeader(long req_info_ptr, byte[] name, int value); + + + @Override + public boolean containsHeader(String name) + { + trace("containsHeader: " + name); + + return containsHeader(req_info_ptr, name.getBytes(ISO_8859_1)); + } + + private static native boolean containsHeader(long req_info_ptr, byte[] name); + + + @Override + @Deprecated + public String encodeRedirectUrl(String url) + { + return encodeRedirectURL(url); + } + + @Override + public String encodeRedirectURL(String url) + { + log("encodeRedirectURL: " + url); + + return url; + } + + @Override + @Deprecated + public String encodeUrl(String url) + { + return encodeURL(url); + } + + @Override + public String encodeURL(String url) + { + log("encodeURL: " + url); + + return url; + } + + @Override + public String getHeader(String name) + { + trace("getHeader: " + name); + + return getHeader(req_info_ptr, name.getBytes(ISO_8859_1)); + } + + private static native String getHeader(long req_info_ptr, byte[] name); + + + @Override + public Collection getHeaderNames() + { + trace("getHeaderNames"); + + Enumeration e = getHeaderNames(req_info_ptr); + if (e == null) { + return Collections.emptyList(); + } + + return Collections.list(e); + } + + private static native Enumeration getHeaderNames(long req_info_ptr); + + + @Override + public Collection getHeaders(String name) + { + trace("getHeaders: " + name); + + Enumeration e = getHeaders(req_info_ptr, name.getBytes(ISO_8859_1)); + if (e == null) { + return Collections.emptyList(); + } + + return Collections.list(e); + } + + private static native Enumeration getHeaders(long req_info_ptr, byte[] name); + + + @Override + public int getStatus() + { + trace("getStatus"); + + return getStatus(req_info_ptr); + } + + private static native int getStatus(long req_info_ptr); + + + @Override + public void sendError(int sc) throws IOException + { + sendError(sc, null); + } + + @Override + public void sendError(int sc, String msg) throws IOException + { + trace("sendError: " + sc + ", " + msg); + + if (isCommitted()) { + throw new IllegalStateException("Response already sent"); + } + + setStatus(sc); + + Request request = getRequest(req_info_ptr); + + // If we are allowed to have a body, then produce the error page. + if (sc != SC_NO_CONTENT && sc != SC_NOT_MODIFIED && + sc != SC_PARTIAL_CONTENT && sc >= SC_OK) + { + request.setAttribute_(RequestDispatcher.ERROR_STATUS_CODE, sc); + request.setAttribute_(RequestDispatcher.ERROR_MESSAGE, msg); + request.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI, + request.getRequestURI()); +/* + request.setAttribute_(RequestDispatcher.ERROR_SERVLET_NAME, + request.getServletName()); +*/ + } + +/* + Avoid commit and give chance for error handlers. + + if (!request.isAsyncStarted()) { + commit(); + } +*/ + } + + private static native Request getRequest(long req_info_ptr); + + private void commit() + { + if (writer != null) { + writer.close(); + + } else if (outputStream != null) { + outputStream.close(); + + } else { + commit(req_info_ptr); + } + } + + private static native void commit(long req_info_ptr); + + + @Override + public void sendRedirect(String location) throws IOException + { + trace("sendRedirect: " + location); + + if (isCommitted()) { + return; + } + + try { + URI uri = new URI(location); + + if (!uri.isAbsolute()) { + URI req_uri = new URI(getRequest(req_info_ptr).getRequestURL().toString()); + uri = req_uri.resolve(uri); + + location = uri.toString(); + } + } catch (URISyntaxException e) { + log("sendRedirect: failed to send redirect: " + e); + return; + } + + sendRedirect(req_info_ptr, location.getBytes(ISO_8859_1)); + } + + private static native void sendRedirect(long req_info_ptr, byte[] location); + + + @Override + public void setDateHeader(String name, long date) + { + trace("setDateHeader: " + name + ": " + date); + + if (isCommitted()) { + return; + } + + String value = dateToString(date); + + setHeader(req_info_ptr, name.getBytes(ISO_8859_1), + value.getBytes(ISO_8859_1)); + } + + + @Override + public void setHeader(String name, String value) + { + trace("setHeader: " + name + ": " + value); + + if (isCommitted()) { + return; + } + + if (CONTENT_TYPE.equalsIgnoreCase(name)) { + setContentType(value); + return; + } + + /* + * When value is null container behaviour is undefined. + * - Tomcat ignores setHeader call; + * - Jetty & Resin acts as removeHeader; + */ + if (value == null) { + removeHeader(req_info_ptr, name.getBytes(ISO_8859_1)); + return; + } + + setHeader(req_info_ptr, name.getBytes(ISO_8859_1), + value.getBytes(ISO_8859_1)); + } + + private static native void setHeader(long req_info_ptr, byte[] name, byte[] value); + + private static native void removeHeader(long req_info_ptr, byte[] name); + + @Override + public void setIntHeader(String name, int value) + { + trace("setIntHeader: " + name + ": " + value); + + if (isCommitted()) { + return; + } + + setIntHeader(req_info_ptr, name.getBytes(ISO_8859_1), value); + } + + private static native void setIntHeader(long req_info_ptr, byte[] name, int value); + + + @Override + public void setStatus(int sc) + { + trace("setStatus: " + sc); + + if (isCommitted()) { + return; + } + + setStatus(req_info_ptr, sc); + } + + private static native void setStatus(long req_info_ptr, int sc); + + + @Override + @Deprecated + public void setStatus(int sc, String sm) + { + trace("setStatus: " + sc + "; " + sm); + + if (isCommitted()) { + return; + } + + setStatus(req_info_ptr, sc); + } + + + @Override + public void flushBuffer() throws IOException + { + trace("flushBuffer"); + + if (writer != null) { + writer.flush(); + } + + if (outputStream != null) { + outputStream.flush(); + } + } + + @Override + public int getBufferSize() + { + trace("getBufferSize"); + + return getBufferSize(req_info_ptr); + } + + public static native int getBufferSize(long req_info_ptr); + + + @Override + public String getCharacterEncoding() + { + trace("getCharacterEncoding"); + + return characterEncoding; + } + + @Override + public String getContentType() + { + /* In JIRA decorator get content type called after commit. */ + + String res = contentTypeHeader; + + trace("getContentType: " + res); + + return res; + } + + private static native String getContentType(long req_info_ptr); + + @Override + public Locale getLocale() + { + log("getLocale"); + + return null; + } + + @Override + public ServletOutputStream getOutputStream() throws IOException + { + trace("getOutputStream"); + + if (writer != null) { + throw new IllegalStateException("Writer already created"); + } + + if (outputStream == null) { + outputStream = new OutputStream(req_info_ptr); + } + + return outputStream; + } + + @Override + public PrintWriter getWriter() throws IOException + { + trace("getWriter ( characterEncoding = '" + characterEncoding + "' )"); + + if (outputStream != null) { + throw new IllegalStateException("OutputStream already created"); + } + + if (writer == null) { + ServletOutputStream stream = new OutputStream(req_info_ptr); + + writer = new PrintWriter( + new OutputStreamWriter(stream, Charset.forName(characterEncoding)), + false); + } + + return writer; + } + + @Override + public boolean isCommitted() + { + trace("isCommitted"); + + return isCommitted(req_info_ptr); + } + + public static native boolean isCommitted(long req_info_ptr); + + @Override + public void reset() + { + trace("reset"); + + if (isCommitted()) { + return; + } + + reset(req_info_ptr); + + writer = null; + outputStream = null; + } + + public static native void reset(long req_info_ptr); + + @Override + public void resetBuffer() + { + trace("resetBuffer"); + + resetBuffer(req_info_ptr); + + writer = null; + outputStream = null; + } + + public static native void resetBuffer(long req_info_ptr); + + @Override + public void setBufferSize(int size) + { + trace("setBufferSize: " + size); + + setBufferSize(req_info_ptr, size); + } + + public static native void setBufferSize(long req_info_ptr, int size); + + @Override + public void setCharacterEncoding(String charset) + { + trace("setCharacterEncoding " + charset); + + if (isCommitted()) { + return; + } + + if (charset == null) { + if (writer != null + && !characterEncoding.equalsIgnoreCase(defaultCharacterEncoding)) + { + /* TODO throw */ + return; + } + + characterEncoding = defaultCharacterEncoding; + } else { + if (writer != null + && !characterEncoding.equalsIgnoreCase(charset)) + { + /* TODO throw */ + return; + } + + characterEncoding = charset; + } + + if (contentType != null) { + String type = contentType + ";charset=" + characterEncoding; + + contentTypeHeader = type; + + setContentType(req_info_ptr, type.getBytes(ISO_8859_1)); + } + } + + + @Override + public void setContentLength(int len) + { + trace("setContentLength: " + len); + + if (isCommitted()) { + return; + } + + setContentLength(req_info_ptr, len); + } + + @Override + public void setContentLengthLong(long len) + { + trace("setContentLengthLong: " + len); + + if (isCommitted()) { + return; + } + + setContentLength(req_info_ptr, len); + } + + private static native void setContentLength(long req_info_ptr, long len); + + + @Override + public void setContentType(String type) + { + trace("setContentType: " + type); + + if (isCommitted()) { + return; + } + + if (type == null) { + removeContentType(req_info_ptr); + contentType = null; + contentTypeHeader = null; + return; + } + + String charset = MimeTypes.getCharsetFromContentType(type); + String ctype = MimeTypes.getContentTypeWithoutCharset(type); + + if (writer != null + && charset != null + && !characterEncoding.equalsIgnoreCase(charset)) + { + /* To late to change character encoding */ + charset = characterEncoding; + type = ctype + ";charset=" + characterEncoding; + } + + if (charset == null) { + type = type + ";charset=" + characterEncoding; + } else { + characterEncoding = charset; + } + + contentType = ctype; + contentTypeHeader = type; + + setContentType(req_info_ptr, type.getBytes(ISO_8859_1)); + } + + private static native void setContentType(long req_info_ptr, byte[] type); + + private static native void removeContentType(long req_info_ptr); + + + @Override + public void setLocale(Locale loc) + { + log("setLocale: " + loc); + } + + private void log(String msg) + { + msg = "Response." + msg; + log(req_info_ptr, msg.getBytes(UTF_8)); + } + + public static native void log(long req_info_ptr, byte[] msg); + + + private void trace(String msg) + { + msg = "Response." + msg; + trace(req_info_ptr, msg.getBytes(UTF_8)); + } + + public static native void trace(long req_info_ptr, byte[] msg); +} diff --git a/src/java/nginx/unit/Session.java b/src/java/nginx/unit/Session.java new file mode 100644 index 00000000..6be74709 --- /dev/null +++ b/src/java/nginx/unit/Session.java @@ -0,0 +1,251 @@ +package nginx.unit; + +import java.io.Serializable; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Andrey Kazankov + */ +public class Session implements HttpSession, Serializable +{ + private final Map attributes = new HashMap<>(); + private final long creation_time = new Date().getTime(); + private long last_access_time = creation_time; + private long access_time = creation_time; + private int max_inactive_interval; + private String id; + private final Context context; + private boolean is_new = true; + private final HttpSessionAttributeListener attr_listener; + + public Session(Context context, String id, + HttpSessionAttributeListener al, int max_inactive_interval) + { + this.id = id; + this.context = context; + attr_listener = al; + this.max_inactive_interval = max_inactive_interval; + } + + public void setId(String id) + { + this.id = id; + } + + @Override + public long getCreationTime() + { + return creation_time; + } + + @Override + public String getId() + { + return id; + } + + @Override + public long getLastAccessedTime() + { + return last_access_time; + } + + @Override + public ServletContext getServletContext() + { + return context; + } + + @Override + public void setMaxInactiveInterval(int i) + { + max_inactive_interval = i; + } + + @Override + public int getMaxInactiveInterval() + { + return max_inactive_interval; + } + + @Deprecated + @Override + public javax.servlet.http.HttpSessionContext getSessionContext() + { + return null; + } + + @Override + public Object getAttribute(String s) + { + synchronized (attributes) { + return attributes.get(s); + } + } + + @Deprecated + @Override + public Object getValue(String s) + { + return getAttribute(s); + } + + @Override + public Enumeration getAttributeNames() + { + synchronized (attributes) { + return Collections.enumeration(attributes.keySet()); + } + } + + @Deprecated + @Override + public String[] getValueNames() + { + synchronized (attributes) { + return attributes.keySet().toArray(new String[attributes.keySet().size()]); + } + } + + @Override + public void setAttribute(String s, Object o) + { + Object old; + + if (o != null && o instanceof HttpSessionBindingListener) { + HttpSessionBindingListener l = (HttpSessionBindingListener) o; + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s); + + l.valueBound(e); + } + + synchronized (attributes) { + if (o != null) { + old = attributes.put(s, o); + } else { + old = attributes.remove(s); + } + } + + if (old != null && old instanceof HttpSessionBindingListener) { + HttpSessionBindingListener l = (HttpSessionBindingListener) old; + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s); + + l.valueUnbound(e); + } + + if (attr_listener == null) { + return; + } + + if (o == null) { + if (old != null) { + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, old); + attr_listener.attributeRemoved(e); + } + + return; + } + + if (old != null) { + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, old); + attr_listener.attributeReplaced(e); + } else { + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, o); + attr_listener.attributeAdded(e); + } + } + + @Deprecated + @Override + public void putValue(String s, Object o) + { + setAttribute(s,o); + } + + @Override + public void removeAttribute(String s) + { + Object o; + + synchronized (attributes) { + o = attributes.remove(s); + } + + if (o != null && o instanceof HttpSessionBindingListener) { + HttpSessionBindingListener l = (HttpSessionBindingListener) o; + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s); + + l.valueUnbound(e); + } + + if (attr_listener == null || o == null) { + return; + } + + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, o); + attr_listener.attributeRemoved(e); + } + + @Deprecated + @Override + public void removeValue(String s) + { + removeAttribute(s); + } + + @Override + public void invalidate() + { + context.invalidateSession(this); + + unboundAttributes(); + } + + private void unboundAttributes() + { + for (Map.Entry a : attributes.entrySet()) { + Object o = a.getValue(); + if (o != null && o instanceof HttpSessionBindingListener) { + HttpSessionBindingListener l = (HttpSessionBindingListener) o; + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, a.getKey()); + + l.valueUnbound(e); + } + } + + attributes.clear(); + } + + @Override + public boolean isNew() + { + return is_new; + } + + public void accessed() { + synchronized (this) { + is_new = false; + + last_access_time = access_time; + access_time = new Date().getTime(); + } + } + + public boolean checkTimeOut() + { + return (max_inactive_interval > 0) && + (access_time - last_access_time > max_inactive_interval * 1000); + } +} \ No newline at end of file diff --git a/src/java/nginx/unit/SessionAttrProxy.java b/src/java/nginx/unit/SessionAttrProxy.java new file mode 100644 index 00000000..bddeede9 --- /dev/null +++ b/src/java/nginx/unit/SessionAttrProxy.java @@ -0,0 +1,40 @@ +package nginx.unit; + +import java.util.List; + +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; + +public class SessionAttrProxy implements HttpSessionAttributeListener +{ + private final List listeners_; + + public SessionAttrProxy(List listeners) + { + listeners_ = listeners; + } + + @Override + public void attributeAdded(HttpSessionBindingEvent event) + { + for (HttpSessionAttributeListener l : listeners_) { + l.attributeAdded(event); + } + } + + @Override + public void attributeRemoved(HttpSessionBindingEvent event) + { + for (HttpSessionAttributeListener l : listeners_) { + l.attributeRemoved(event); + } + } + + @Override + public void attributeReplaced(HttpSessionBindingEvent event) + { + for (HttpSessionAttributeListener l : listeners_) { + l.attributeReplaced(event); + } + } +} diff --git a/src/java/nginx/unit/Taglib.java b/src/java/nginx/unit/Taglib.java new file mode 100644 index 00000000..f72033ba --- /dev/null +++ b/src/java/nginx/unit/Taglib.java @@ -0,0 +1,44 @@ +package nginx.unit; + +import javax.servlet.descriptor.TaglibDescriptor; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class Taglib implements TaglibDescriptor +{ + private String uri_ = null; + private String location_ = null; + + public Taglib(NodeList nodes) + { + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + String tag_name = node.getNodeName(); + + if (tag_name.equals("taglib-uri")) { + uri_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("taglib-location")) { + location_ = node.getTextContent().trim(); + continue; + } + } + + } + + @Override + public String getTaglibURI() + { + return uri_; + } + + @Override + public String getTaglibLocation() + { + return location_; + } +} + diff --git a/src/java/nginx/unit/UnitSessionCookieConfig.java b/src/java/nginx/unit/UnitSessionCookieConfig.java new file mode 100644 index 00000000..e1b2ae04 --- /dev/null +++ b/src/java/nginx/unit/UnitSessionCookieConfig.java @@ -0,0 +1,110 @@ +package nginx.unit; + +import javax.servlet.SessionCookieConfig; + +/* + + + 60 + + + + + + */ +public class UnitSessionCookieConfig implements SessionCookieConfig { + + private static final String default_name = "JSESSIONID"; + + private String name = default_name; + private String domain; + private String path; + private String comment; + private boolean httpOnly = true; + private boolean secure = false; + private int maxAge = -1; + + @Override + public void setName(String name) + { + this.name = name; + } + + @Override + public String getName() + { + return name; + } + + @Override + public void setDomain(String domain) + { + this.domain = domain; + } + + @Override + public String getDomain() + { + return domain; + } + + @Override + public void setPath(String path) + { + this.path = path; + } + + @Override + public String getPath() + { + return path; + } + + @Override + public void setComment(String comment) + { + this.comment = comment; + } + + @Override + public String getComment() + { + return comment; + } + + @Override + public void setHttpOnly(boolean httpOnly) + { + this.httpOnly = httpOnly; + } + + @Override + public boolean isHttpOnly() + { + return httpOnly; + } + + @Override + public void setSecure(boolean secure) + { + this.secure = secure; + } + + @Override + public boolean isSecure() + { + return secure; + } + + @Override + public void setMaxAge(int maxAge) + { + this.maxAge = maxAge; + } + + @Override + public int getMaxAge() + { + return maxAge; + } +} diff --git a/src/java/nxt_jni.c b/src/java/nxt_jni.c new file mode 100644 index 00000000..02ec1e37 --- /dev/null +++ b/src/java/nxt_jni.c @@ -0,0 +1,175 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include +#include + +#include "nxt_jni.h" + + +static jclass nxt_java_NoSuchElementException_class; +static jclass nxt_java_IOException_class; +static jclass nxt_java_IllegalStateException_class; +static jclass nxt_java_File_class; +static jmethodID nxt_java_File_ctor; + +static inline char nxt_java_lowcase(char c); + + +int +nxt_java_jni_init(JNIEnv *env) +{ + jclass cls; + + cls = (*env)->FindClass(env, "java/util/NoSuchElementException"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_NoSuchElementException_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + + cls = (*env)->FindClass(env, "java/io/IOException"); + if (cls == NULL) { + (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class); + return NXT_UNIT_ERROR; + } + + nxt_java_IOException_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + + cls = (*env)->FindClass(env, "java/lang/IllegalStateException"); + if (cls == NULL) { + (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class); + (*env)->DeleteGlobalRef(env, nxt_java_IOException_class); + return NXT_UNIT_ERROR; + } + + nxt_java_IllegalStateException_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + + cls = (*env)->FindClass(env, "java/io/File"); + if (cls == NULL) { + (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class); + (*env)->DeleteGlobalRef(env, nxt_java_IOException_class); + (*env)->DeleteGlobalRef(env, nxt_java_IllegalStateException_class); + return NXT_UNIT_ERROR; + } + + nxt_java_File_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + + nxt_java_File_ctor = (*env)->GetMethodID(env, nxt_java_File_class, "", + "(Ljava/lang/String;)V"); + if (nxt_java_File_ctor == NULL) { + (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class); + (*env)->DeleteGlobalRef(env, nxt_java_IOException_class); + (*env)->DeleteGlobalRef(env, nxt_java_IllegalStateException_class); + (*env)->DeleteGlobalRef(env, nxt_java_File_class); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +void +nxt_java_throw_NoSuchElementException(JNIEnv *env, const char *msg) +{ + (*env)->ThrowNew(env, nxt_java_NoSuchElementException_class, msg); +} + + +void +nxt_java_throw_IOException(JNIEnv *env, const char *msg) +{ + (*env)->ThrowNew(env, nxt_java_IOException_class, msg); +} + + +void +nxt_java_throw_IllegalStateException(JNIEnv *env, const char *msg) +{ + (*env)->ThrowNew(env, nxt_java_IllegalStateException_class, msg); +} + + +nxt_unit_field_t * +nxt_java_findHeader(nxt_unit_field_t *f, nxt_unit_field_t *end, + const char *name, uint8_t name_len) +{ + const char *field_name; + + for (/* void */ ; f < end; f++) { + if (f->skip != 0 || f->name_length != name_len) { + continue; + } + + field_name = nxt_unit_sptr_get(&f->name); + + if (nxt_java_strcaseeq(name, field_name, name_len)) { + return f; + } + } + + return NULL; +} + + +int +nxt_java_strcaseeq(const char *str1, const char *str2, int len) +{ + char c1, c2; + const char *end1; + + end1 = str1 + len; + + while (str1 < end1) { + c1 = nxt_java_lowcase(*str1++); + c2 = nxt_java_lowcase(*str2++); + + if (c1 != c2) { + return 0; + } + } + + return 1; +} + + +static inline char +nxt_java_lowcase(char c) +{ + return (c >= 'A' && c <= 'Z') ? c | 0x20 : c; +} + + +jstring +nxt_java_newString(JNIEnv *env, char *str, uint32_t len) +{ + char tmp; + jstring res; + + tmp = str[len]; + + if (tmp != '\0') { + str[len] = '\0'; + } + + res = (*env)->NewStringUTF(env, str); + + if (tmp != '\0') { + str[len] = tmp; + } + + return res; +} diff --git a/src/java/nxt_jni.h b/src/java/nxt_jni.h new file mode 100644 index 00000000..62acb752 --- /dev/null +++ b/src/java/nxt_jni.h @@ -0,0 +1,55 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_JNI_H_INCLUDED_ +#define _NXT_JAVA_JNI_H_INCLUDED_ + + +#include +#include + + +int nxt_java_jni_init(JNIEnv *env); + +void nxt_java_throw_NoSuchElementException(JNIEnv *env, const char *msg); + +void nxt_java_throw_IOException(JNIEnv *env, const char *msg); + +void nxt_java_throw_IllegalStateException(JNIEnv *env, const char *msg); + +nxt_unit_field_t *nxt_java_findHeader(nxt_unit_field_t *f, nxt_unit_field_t *e, + const char *name, uint8_t name_len); + +int nxt_java_strcaseeq(const char *str1, const char *str2, int len); + +jstring nxt_java_newString(JNIEnv *env, char *str, uint32_t len); + + +typedef struct { + uint32_t header_size; + uint32_t buf_size; + + jobject jreq; + jobject jresp; + + nxt_unit_buf_t *first; + nxt_unit_buf_t *buf; + +} nxt_java_request_data_t; + + +static inline jlong +nxt_ptr2jlong(void *ptr) +{ + return (jlong) (intptr_t) ptr; +} + +static inline void * +nxt_jlong2ptr(jlong l) +{ + return (void *) (intptr_t) l; +} + +#endif /* _NXT_JAVA_JNI_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_Context.c b/src/java/nxt_jni_Context.c new file mode 100644 index 00000000..8f7adddf --- /dev/null +++ b/src/java/nxt_jni_Context.c @@ -0,0 +1,164 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include + +#include "nxt_jni.h" +#include "nxt_jni_Context.h" +#include "nxt_jni_URLClassLoader.h" + + +static jclass nxt_java_Context_class; +static jmethodID nxt_java_Context_start; +static jmethodID nxt_java_Context_service; +static jmethodID nxt_java_Context_stop; + +static void JNICALL nxt_java_Context_log(JNIEnv *env, jclass cls, + jlong ctx_ptr, jstring msg, jint msg_len); +static void JNICALL nxt_java_Context_trace(JNIEnv *env, jclass cls, + jlong ctx_ptr, jstring msg, jint msg_len); + + +int +nxt_java_initContext(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.Context"); + if (cls == NULL) { + nxt_unit_warn(NULL, "nginx.unit.Context not found"); + return NXT_UNIT_ERROR; + } + + nxt_java_Context_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_Context_class; + + nxt_java_Context_start = (*env)->GetStaticMethodID(env, cls, "start", + "(Ljava/lang/String;[Ljava/net/URL;)Lnginx/unit/Context;"); + if (nxt_java_Context_start == NULL) { + nxt_unit_warn(NULL, "nginx.unit.Context.start() not found"); + goto failed; + } + + nxt_java_Context_service = (*env)->GetMethodID(env, cls, "service", + "(Lnginx/unit/Request;Lnginx/unit/Response;)V"); + if (nxt_java_Context_service == NULL) { + nxt_unit_warn(NULL, "nginx.unit.Context.service() not found"); + goto failed; + } + + nxt_java_Context_stop = (*env)->GetMethodID(env, cls, "stop", "()V"); + if (nxt_java_Context_service == NULL) { + nxt_unit_warn(NULL, "nginx.unit.Context.stop() not found"); + goto failed; + } + + JNINativeMethod context_methods[] = { + { (char *) "log", + (char *) "(JLjava/lang/String;I)V", + nxt_java_Context_log }, + + { (char *) "trace", + (char *) "(JLjava/lang/String;I)V", + nxt_java_Context_trace }, + + }; + + res = (*env)->RegisterNatives(env, nxt_java_Context_class, + context_methods, + sizeof(context_methods) + / sizeof(context_methods[0])); + + nxt_unit_debug(NULL, "registered Context methods: %d", res); + + if (res != 0) { + nxt_unit_warn(NULL, "registering natives for Context failed"); + goto failed; + } + + return NXT_UNIT_OK; + +failed: + + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; +} + + +jobject +nxt_java_startContext(JNIEnv *env, const char *webapp, jobject classpaths) +{ + jstring webapp_str; + + webapp_str = (*env)->NewStringUTF(env, webapp); + if (webapp_str == NULL) { + return NULL; + } + + return (*env)->CallStaticObjectMethod(env, nxt_java_Context_class, + nxt_java_Context_start, webapp_str, + classpaths); +} + + +void +nxt_java_service(JNIEnv *env, jobject ctx, jobject jreq, jobject jresp) +{ + (*env)->CallVoidMethod(env, ctx, nxt_java_Context_service, jreq, jresp); +} + + +void +nxt_java_stopContext(JNIEnv *env, jobject ctx) +{ + (*env)->CallVoidMethod(env, ctx, nxt_java_Context_stop); +} + + +static void JNICALL +nxt_java_Context_log(JNIEnv *env, jclass cls, jlong ctx_ptr, jstring msg, + jint msg_len) +{ + const char *msg_str; + nxt_unit_ctx_t *ctx; + + ctx = nxt_jlong2ptr(ctx_ptr); + + msg_str = (*env)->GetStringUTFChars(env, msg, NULL); + if (msg_str == NULL) { + return; + } + + nxt_unit_log(ctx, NXT_UNIT_LOG_INFO, "%.*s", msg_len, msg_str); + + (*env)->ReleaseStringUTFChars(env, msg, msg_str); +} + + +static void JNICALL +nxt_java_Context_trace(JNIEnv *env, jclass cls, jlong ctx_ptr, jstring msg, + jint msg_len) +{ +#if (NXT_DEBUG) + const char *msg_str; + nxt_unit_ctx_t *ctx; + + ctx = nxt_jlong2ptr(ctx_ptr); + + msg_str = (*env)->GetStringUTFChars(env, msg, NULL); + if (msg_str == NULL) { + return; + } + + nxt_unit_debug(ctx, "%.*s", msg_len, msg_str); + + (*env)->ReleaseStringUTFChars(env, msg, msg_str); +#endif +} diff --git a/src/java/nxt_jni_Context.h b/src/java/nxt_jni_Context.h new file mode 100644 index 00000000..976c36cf --- /dev/null +++ b/src/java/nxt_jni_Context.h @@ -0,0 +1,23 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_CONTEXT_H_INCLUDED_ +#define _NXT_JAVA_CONTEXT_H_INCLUDED_ + + +#include + + +int nxt_java_initContext(JNIEnv *env, jobject cl); + +jobject nxt_java_startContext(JNIEnv *env, const char *webapp, + jobject classpaths); + +void nxt_java_service(JNIEnv *env, jobject ctx, jobject jreq, jobject jresp); + +void nxt_java_stopContext(JNIEnv *env, jobject ctx); + +#endif /* _NXT_JAVA_CONTEXT_H_INCLUDED_ */ + diff --git a/src/java/nxt_jni_HeaderNamesEnumeration.c b/src/java/nxt_jni_HeaderNamesEnumeration.c new file mode 100644 index 00000000..eea0c387 --- /dev/null +++ b/src/java/nxt_jni_HeaderNamesEnumeration.c @@ -0,0 +1,153 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include +#include +#include + +#include "nxt_jni.h" +#include "nxt_jni_URLClassLoader.h" +#include "nxt_jni_HeaderNamesEnumeration.h" + + +static jlong JNICALL nxt_java_HeaderNamesEnumeration_nextElementPos(JNIEnv *env, + jclass cls, jlong headers_ptr, jlong size, jlong pos); +static jstring JNICALL nxt_java_HeaderNamesEnumeration_nextElement(JNIEnv *env, + jclass cls, jlong headers_ptr, jlong size, jlong pos); + + +static jclass nxt_java_HeaderNamesEnumeration_class; +static jmethodID nxt_java_HeaderNamesEnumeration_ctor; + + +int +nxt_java_initHeaderNamesEnumeration(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.HeaderNamesEnumeration"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_HeaderNamesEnumeration_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_HeaderNamesEnumeration_class; + + nxt_java_HeaderNamesEnumeration_ctor = (*env)->GetMethodID(env, cls, + "", "(JJ)V"); + if (nxt_java_HeaderNamesEnumeration_ctor == NULL) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + JNINativeMethod hnenum_methods[] = { + { (char *) "nextElementPos", + (char *) "(JJJ)J", + nxt_java_HeaderNamesEnumeration_nextElementPos }, + + { (char *) "nextElement", + (char *) "(JJJ)Ljava/lang/String;", + nxt_java_HeaderNamesEnumeration_nextElement }, + }; + + res = (*env)->RegisterNatives(env, nxt_java_HeaderNamesEnumeration_class, + hnenum_methods, + sizeof(hnenum_methods) + / sizeof(hnenum_methods[0])); + + nxt_unit_debug(NULL, "registered HeaderNamesEnumeration methods: %d", res); + + if (res != 0) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +jobject +nxt_java_newHeaderNamesEnumeration(JNIEnv *env, nxt_unit_field_t *f, + uint32_t fields_count) +{ + return (*env)->NewObject(env, + nxt_java_HeaderNamesEnumeration_class, + nxt_java_HeaderNamesEnumeration_ctor, nxt_ptr2jlong(f), + (jlong) fields_count); +} + + +static jlong JNICALL +nxt_java_HeaderNamesEnumeration_nextElementPos(JNIEnv *env, jclass cls, + jlong headers_ptr, jlong size, jlong pos) +{ + nxt_unit_field_t *f; + + f = nxt_jlong2ptr(headers_ptr); + + if (pos >= size) { + return size; + } + + if (pos > 0) { + while (pos < size + && f[pos].hash == f[pos - 1].hash + && f[pos].name_length == f[pos - 1].name_length) + { + pos++; + } + } + + return pos; +} + + +static jstring JNICALL +nxt_java_HeaderNamesEnumeration_nextElement(JNIEnv *env, jclass cls, + jlong headers_ptr, jlong size, jlong pos) +{ + char *name, tmp; + jstring res; + nxt_unit_field_t *f; + + f = nxt_jlong2ptr(headers_ptr); + + if (pos > 0) { + while (pos < size + && f[pos].hash == f[pos - 1].hash + && f[pos].name_length == f[pos - 1].name_length) + { + pos++; + } + } + + if (pos >= size) { + nxt_java_throw_NoSuchElementException(env, "pos >= size"); + + return NULL; + } + + f += pos; + + name = nxt_unit_sptr_get(&f->name); + tmp = name[f->name_length]; + + if (tmp != '\0') { + name[f->name_length] = '\0'; + } + + res = (*env)->NewStringUTF(env, name); + + if (tmp != '\0') { + name[f->name_length] = tmp; + } + + return res; +} diff --git a/src/java/nxt_jni_HeaderNamesEnumeration.h b/src/java/nxt_jni_HeaderNamesEnumeration.h new file mode 100644 index 00000000..90df8f54 --- /dev/null +++ b/src/java/nxt_jni_HeaderNamesEnumeration.h @@ -0,0 +1,19 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_HEADERNAMESENUMERATION_H_INCLUDED_ +#define _NXT_JAVA_HEADERNAMESENUMERATION_H_INCLUDED_ + + +#include +#include + + +int nxt_java_initHeaderNamesEnumeration(JNIEnv *env, jobject cl); + +jobject nxt_java_newHeaderNamesEnumeration(JNIEnv *env, nxt_unit_field_t *f, + uint32_t fields_count); + +#endif /* _NXT_JAVA_HEADERNAMESENUMERATION_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_HeadersEnumeration.c b/src/java/nxt_jni_HeadersEnumeration.c new file mode 100644 index 00000000..aaea710d --- /dev/null +++ b/src/java/nxt_jni_HeadersEnumeration.c @@ -0,0 +1,148 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include +#include +#include + +#include "nxt_jni.h" +#include "nxt_jni_URLClassLoader.h" +#include "nxt_jni_HeadersEnumeration.h" + + +static jclass nxt_java_HeadersEnumeration_class; +static jmethodID nxt_java_HeadersEnumeration_ctor; + + +static jlong JNICALL nxt_java_HeadersEnumeration_nextElementPos(JNIEnv *env, + jclass cls, jlong headers_ptr, jlong size, jlong ipos, jlong pos); + +static jstring JNICALL nxt_java_HeadersEnumeration_nextElement(JNIEnv *env, + jclass cls, jlong headers_ptr, jlong size, jlong ipos, jlong pos); + + +int +nxt_java_initHeadersEnumeration(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.HeadersEnumeration"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_HeadersEnumeration_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_HeadersEnumeration_class; + + nxt_java_HeadersEnumeration_ctor = (*env)->GetMethodID(env, cls, + "", "(JJJ)V"); + if (nxt_java_HeadersEnumeration_ctor == NULL) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + JNINativeMethod methods[] = { + { (char *) "nextElementPos", + (char *) "(JJJJ)J", + nxt_java_HeadersEnumeration_nextElementPos }, + + { (char *) "nextElement", + (char *) "(JJJJ)Ljava/lang/String;", + nxt_java_HeadersEnumeration_nextElement }, + }; + + res = (*env)->RegisterNatives(env, nxt_java_HeadersEnumeration_class, + methods, + sizeof(methods) / sizeof(methods[0])); + + nxt_unit_debug(NULL, "registered HeadersEnumeration methods: %d", res); + + if (res != 0) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +jobject +nxt_java_newHeadersEnumeration(JNIEnv *env, nxt_unit_field_t *f, + uint32_t fields_count, uint32_t pos) +{ + return (*env)->NewObject(env, + nxt_java_HeadersEnumeration_class, + nxt_java_HeadersEnumeration_ctor, nxt_ptr2jlong(f), + (jlong) fields_count, (jlong) pos); +} + + +static jlong JNICALL +nxt_java_HeadersEnumeration_nextElementPos(JNIEnv *env, jclass cls, + jlong headers_ptr, jlong size, jlong ipos, jlong pos) +{ + nxt_unit_field_t *f, *init_field; + + f = nxt_jlong2ptr(headers_ptr); + + init_field = f + ipos; + + if (pos >= size) { + return size; + } + + f += pos; + + if (f->hash != init_field->hash + || f->name_length != init_field->name_length) + { + return size; + } + + if (!nxt_java_strcaseeq(nxt_unit_sptr_get(&f->name), + nxt_unit_sptr_get(&init_field->name), + init_field->name_length)) + { + return size; + } + + return pos; +} + + +static jstring JNICALL +nxt_java_HeadersEnumeration_nextElement(JNIEnv *env, jclass cls, + jlong headers_ptr, jlong size, jlong ipos, jlong pos) +{ + nxt_unit_field_t *f, *init_field; + + f = nxt_jlong2ptr(headers_ptr); + + init_field = f + ipos; + + if (pos >= size) { + nxt_java_throw_IOException(env, "pos >= size"); + + return NULL; + } + + f += pos; + + if (f->hash != init_field->hash + || f->name_length != init_field->name_length) + { + nxt_java_throw_IOException(env, "f->hash != hash"); + + return NULL; + } + + return nxt_java_newString(env, nxt_unit_sptr_get(&f->value), + f->value_length); +} diff --git a/src/java/nxt_jni_HeadersEnumeration.h b/src/java/nxt_jni_HeadersEnumeration.h new file mode 100644 index 00000000..10f9393c --- /dev/null +++ b/src/java/nxt_jni_HeadersEnumeration.h @@ -0,0 +1,19 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_HEADERSENUMERATION_H_INCLUDED_ +#define _NXT_JAVA_HEADERSENUMERATION_H_INCLUDED_ + + +#include +#include + + +int nxt_java_initHeadersEnumeration(JNIEnv *env, jobject cl); + +jobject nxt_java_newHeadersEnumeration(JNIEnv *env, nxt_unit_field_t *f, + uint32_t fields_count, uint32_t pos); + +#endif /* _NXT_JAVA_HEADERSENUMERATION_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_InputStream.c b/src/java/nxt_jni_InputStream.c new file mode 100644 index 00000000..b96ff742 --- /dev/null +++ b/src/java/nxt_jni_InputStream.c @@ -0,0 +1,230 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include +#include + +#include "nxt_jni.h" +#include "nxt_jni_InputStream.h" +#include "nxt_jni_URLClassLoader.h" + + +static jint JNICALL nxt_java_InputStream_readLine(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray b, jint off, jint len); +static jboolean JNICALL nxt_java_InputStream_isFinished(JNIEnv *env, jclass cls, + jlong req_info_ptr); +static jint JNICALL nxt_java_InputStream_readByte(JNIEnv *env, jclass cls, + jlong req_info_ptr); +static jint JNICALL nxt_java_InputStream_read(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray b, jint off, jint len); +static jlong JNICALL nxt_java_InputStream_skip(JNIEnv *env, jclass cls, + jlong req_info_ptr, jlong n); +static jint JNICALL nxt_java_InputStream_available(JNIEnv *env, jclass cls, + jlong req_info_ptr); + + +static jclass nxt_java_InputStream_class; + + +int +nxt_java_initInputStream(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.InputStream"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_InputStream_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + JNINativeMethod is_methods[] = { + { (char *) "readLine", + (char *) "(J[BII)I", + nxt_java_InputStream_readLine }, + + { (char *) "isFinished", + (char *) "(J)Z", + nxt_java_InputStream_isFinished }, + + { (char *) "read", + (char *) "(J)I", + nxt_java_InputStream_readByte }, + + { (char *) "read", + (char *) "(J[BII)I", + nxt_java_InputStream_read }, + + { (char *) "skip", + (char *) "(JJ)J", + nxt_java_InputStream_skip }, + + { (char *) "available", + (char *) "(J)I", + nxt_java_InputStream_available }, + }; + + res = (*env)->RegisterNatives(env, nxt_java_InputStream_class, + is_methods, + sizeof(is_methods) / sizeof(is_methods[0])); + + nxt_unit_debug(NULL, "registered InputStream methods: %d", res); + + if (res != 0) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +static jint JNICALL +nxt_java_InputStream_readLine(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray out, jint off, jint len) +{ + char *p; + jint size, b_size; + uint8_t *data; + ssize_t res; + nxt_unit_buf_t *b; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + size = 0; + + for (b = req->content_buf; b; b = nxt_unit_buf_next(b)) { + b_size = b->end - b->free; + p = memchr(b->free, '\n', b_size); + + if (p != NULL) { + p++; + size += p - b->free; + break; + } + + size += b_size; + + if (size >= len) { + break; + } + } + + len = len < size ? len : size; + + data = (*env)->GetPrimitiveArrayCritical(env, out, NULL); + + res = nxt_unit_request_read(req, data + off, len); + + nxt_unit_req_debug(req, "readLine '%.*s'", res, (char *) data + off); + + (*env)->ReleasePrimitiveArrayCritical(env, out, data, 0); + + return res > 0 ? res : -1; +} + + +static jboolean JNICALL +nxt_java_InputStream_isFinished(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + return req->content_length == 0; +} + + +static jint JNICALL +nxt_java_InputStream_readByte(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + uint8_t b; + ssize_t size; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + size = nxt_unit_request_read(req, &b, 1); + + return size == 1 ? b : -1; +} + + +static jint JNICALL +nxt_java_InputStream_read(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray b, jint off, jint len) +{ + uint8_t *data; + ssize_t res; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + data = (*env)->GetPrimitiveArrayCritical(env, b, NULL); + + res = nxt_unit_request_read(req, data + off, len); + + nxt_unit_req_debug(req, "read '%.*s'", res, (char *) data + off); + + (*env)->ReleasePrimitiveArrayCritical(env, b, data, 0); + + return res > 0 ? res : -1; +} + + +static jlong JNICALL +nxt_java_InputStream_skip(JNIEnv *env, jclass cls, jlong req_info_ptr, jlong n) +{ + size_t rest, b_size; + nxt_unit_buf_t *buf; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + rest = n; + + buf = req->content_buf; + + while (buf != NULL) { + b_size = buf->end - buf->free; + b_size = rest < b_size ? rest : b_size; + + buf->free += b_size; + rest -= b_size; + + if (rest == 0) { + if (buf->end == buf->free) { + buf = nxt_unit_buf_next(buf); + } + + break; + } + + buf = nxt_unit_buf_next(buf); + } + + n = n < (jlong) req->content_length ? n : (jlong) req->content_length; + + req->content_length -= n; + + return n; +} + + +static jint JNICALL +nxt_java_InputStream_available(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + return req->content_length; +} diff --git a/src/java/nxt_jni_InputStream.h b/src/java/nxt_jni_InputStream.h new file mode 100644 index 00000000..b20b262a --- /dev/null +++ b/src/java/nxt_jni_InputStream.h @@ -0,0 +1,15 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_INPUTSTREAM_H_INCLUDED_ +#define _NXT_JAVA_INPUTSTREAM_H_INCLUDED_ + + +#include + + +int nxt_java_initInputStream(JNIEnv *env, jobject cl); + +#endif /* _NXT_JAVA_INPUTSTREAM_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_OutputStream.c b/src/java/nxt_jni_OutputStream.c new file mode 100644 index 00000000..170b33ba --- /dev/null +++ b/src/java/nxt_jni_OutputStream.c @@ -0,0 +1,236 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include + +#include "nxt_jni.h" +#include "nxt_jni_OutputStream.h" +#include "nxt_jni_URLClassLoader.h" + + +static void JNICALL nxt_java_OutputStream_writeByte(JNIEnv *env, jclass cls, + jlong req_info_ptr, jint b); +static nxt_unit_buf_t *nxt_java_OutputStream_req_buf(JNIEnv *env, + nxt_unit_request_info_t *req); +static void JNICALL nxt_java_OutputStream_write(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray b, jint off, jint len); +static void JNICALL nxt_java_OutputStream_flush(JNIEnv *env, jclass cls, + jlong req_info_ptr); +static void JNICALL nxt_java_OutputStream_close(JNIEnv *env, jclass cls, + jlong req_info_ptr); + + +static jclass nxt_java_OutputStream_class; + + +int +nxt_java_initOutputStream(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.OutputStream"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_OutputStream_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + cls = nxt_java_OutputStream_class; + + JNINativeMethod os_methods[] = { + { (char *) "write", + (char *) "(JI)V", + nxt_java_OutputStream_writeByte }, + + { (char *) "write", + (char *) "(J[BII)V", + nxt_java_OutputStream_write }, + + { (char *) "flush", + (char *) "(J)V", + nxt_java_OutputStream_flush }, + + { (char *) "close", + (char *) "(J)V", + nxt_java_OutputStream_close }, + }; + + res = (*env)->RegisterNatives(env, nxt_java_OutputStream_class, + os_methods, + sizeof(os_methods) / sizeof(os_methods[0])); + + nxt_unit_debug(NULL, "registered OutputStream methods: %d", res); + + if (res != 0) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +static void JNICALL +nxt_java_OutputStream_writeByte(JNIEnv *env, jclass cls, jlong req_info_ptr, + jint b) +{ + nxt_unit_buf_t *buf; + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + buf = nxt_java_OutputStream_req_buf(env, req); + if (buf == NULL) { + return; + } + + *buf->free++ = b; + + if ((uint32_t) (buf->free - buf->start) >= data->buf_size) { + nxt_java_OutputStream_flush_buf(env, req); + } +} + + +int +nxt_java_OutputStream_flush_buf(JNIEnv *env, nxt_unit_request_info_t *req) +{ + int rc; + nxt_java_request_data_t *data; + + data = req->data; + + if (!nxt_unit_response_is_init(req)) { + rc = nxt_unit_response_init(req, 200, 0, 0); + if (rc != NXT_UNIT_OK) { + nxt_java_throw_IOException(env, "Failed to allocate response"); + + return rc; + } + } + + if (!nxt_unit_response_is_sent(req)) { + rc = nxt_unit_response_send(req); + if (rc != NXT_UNIT_OK) { + nxt_java_throw_IOException(env, "Failed to send response headers"); + + return rc; + } + } + + if (data->buf != NULL) { + rc = nxt_unit_buf_send(data->buf); + if (rc != NXT_UNIT_OK) { + nxt_java_throw_IOException(env, "Failed to send buffer"); + + } else { + data->buf = NULL; + } + + } else { + rc = NXT_UNIT_OK; + } + + return rc; +} + + +static nxt_unit_buf_t * +nxt_java_OutputStream_req_buf(JNIEnv *env, nxt_unit_request_info_t *req) +{ + uint32_t size; + nxt_unit_buf_t *buf; + nxt_java_request_data_t *data; + + data = req->data; + buf = data->buf; + + if (buf == NULL || buf->free >= buf->end) { + size = data->buf_size == 0 ? nxt_unit_buf_min() : data->buf_size; + + buf = nxt_unit_response_buf_alloc(req, size); + if (buf == NULL) { + nxt_java_throw_IOException(env, "Failed to allocate buffer"); + + return NULL; + } + + data->buf = buf; + } + + return buf; +} + + +static void JNICALL +nxt_java_OutputStream_write(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray b, jint off, jint len) +{ + int rc; + jint copy; + uint8_t *ptr; + nxt_unit_buf_t *buf; + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + ptr = (*env)->GetPrimitiveArrayCritical(env, b, NULL); + + while (len > 0) { + buf = nxt_java_OutputStream_req_buf(env, req); + if (buf == NULL) { + return; + } + + copy = buf->end - buf->free; + copy = copy < len ? copy : len; + + memcpy(buf->free, ptr + off, copy); + buf->free += copy; + + len -= copy; + off += copy; + + if ((uint32_t) (buf->free - buf->start) >= data->buf_size) { + rc = nxt_java_OutputStream_flush_buf(env, req); + if (rc != NXT_UNIT_OK) { + break; + } + } + } + + (*env)->ReleasePrimitiveArrayCritical(env, b, ptr, 0); +} + + +static void JNICALL +nxt_java_OutputStream_flush(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + if (data->buf != NULL && data->buf->free > data->buf->start) { + nxt_java_OutputStream_flush_buf(env, req); + } +} + + +static void JNICALL +nxt_java_OutputStream_close(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_java_OutputStream_flush_buf(env, nxt_jlong2ptr(req_info_ptr)); +} diff --git a/src/java/nxt_jni_OutputStream.h b/src/java/nxt_jni_OutputStream.h new file mode 100644 index 00000000..0c3c9989 --- /dev/null +++ b/src/java/nxt_jni_OutputStream.h @@ -0,0 +1,17 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_OUTPUTSTREAM_H_INCLUDED_ +#define _NXT_JAVA_OUTPUTSTREAM_H_INCLUDED_ + + +#include + + +int nxt_java_initOutputStream(JNIEnv *env, jobject cl); + +int nxt_java_OutputStream_flush_buf(JNIEnv *env, nxt_unit_request_info_t *req); + +#endif /* _NXT_JAVA_OUTPUTSTREAM_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_Request.c b/src/java/nxt_jni_Request.c new file mode 100644 index 00000000..6fb9cb44 --- /dev/null +++ b/src/java/nxt_jni_Request.c @@ -0,0 +1,658 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include +#include +#include +#include + +#include "nxt_jni.h" +#include "nxt_jni_Request.h" +#include "nxt_jni_URLClassLoader.h" +#include "nxt_jni_HeadersEnumeration.h" +#include "nxt_jni_HeaderNamesEnumeration.h" + + +static jstring JNICALL nxt_java_Request_getHeader(JNIEnv *env, jclass cls, + jlong req_ptr, jstring name, jint name_len); +static jobject JNICALL nxt_java_Request_getHeaderNames(JNIEnv *env, jclass cls, + jlong req_ptr); +static jobject JNICALL nxt_java_Request_getHeaders(JNIEnv *env, jclass cls, + jlong req_ptr, jstring name, jint name_len); +static jint JNICALL nxt_java_Request_getIntHeader(JNIEnv *env, jclass cls, + jlong req_ptr, jstring name, jint name_len); +static jstring JNICALL nxt_java_Request_getMethod(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getQueryString(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getRequestURI(JNIEnv *env, jclass cls, + jlong req_ptr); +static jlong JNICALL nxt_java_Request_getContentLength(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getContentType(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getLocalAddr(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getLocalName(JNIEnv *env, jclass cls, + jlong req_ptr); +static jint JNICALL nxt_java_Request_getLocalPort(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getProtocol(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getRemoteAddr(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getRemoteHost(JNIEnv *env, jclass cls, + jlong req_ptr); +static jint JNICALL nxt_java_Request_getRemotePort(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getScheme(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getServerName(JNIEnv *env, jclass cls, + jlong req_ptr); +static jint JNICALL nxt_java_Request_getServerPort(JNIEnv *env, jclass cls, + jlong req_ptr); +static void JNICALL nxt_java_Request_log(JNIEnv *env, jclass cls, + jlong req_info_ptr, jstring msg, jint msg_len); +static void JNICALL nxt_java_Request_trace(JNIEnv *env, jclass cls, + jlong req_info_ptr, jstring msg, jint msg_len); +static jobject JNICALL nxt_java_Request_getResponse(JNIEnv *env, jclass cls, + jlong req_info_ptr); + + +static jclass nxt_java_Request_class; +static jmethodID nxt_java_Request_ctor; + + +int +nxt_java_initRequest(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.Request"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_Request_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_Request_class; + + nxt_java_Request_ctor = (*env)->GetMethodID(env, cls, "", "(Lnginx/unit/Context;JJ)V"); + if (nxt_java_Request_ctor == NULL) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + JNINativeMethod request_methods[] = { + { (char *) "getHeader", + (char *) "(JLjava/lang/String;I)Ljava/lang/String;", + nxt_java_Request_getHeader }, + + { (char *) "getHeaderNames", + (char *) "(J)Ljava/util/Enumeration;", + nxt_java_Request_getHeaderNames }, + + { (char *) "getHeaders", + (char *) "(JLjava/lang/String;I)Ljava/util/Enumeration;", + nxt_java_Request_getHeaders }, + + { (char *) "getIntHeader", + (char *) "(JLjava/lang/String;I)I", + nxt_java_Request_getIntHeader }, + + { (char *) "getMethod", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getMethod }, + + { (char *) "getQueryString", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getQueryString }, + + { (char *) "getRequestURI", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getRequestURI }, + + { (char *) "getContentLength", + (char *) "(J)J", + nxt_java_Request_getContentLength }, + + { (char *) "getContentType", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getContentType }, + + { (char *) "getLocalAddr", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getLocalAddr }, + + { (char *) "getLocalName", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getLocalName }, + + { (char *) "getLocalPort", + (char *) "(J)I", + nxt_java_Request_getLocalPort }, + + { (char *) "getProtocol", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getProtocol }, + + { (char *) "getRemoteAddr", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getRemoteAddr }, + + { (char *) "getRemoteHost", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getRemoteHost }, + + { (char *) "getRemotePort", + (char *) "(J)I", + nxt_java_Request_getRemotePort }, + + { (char *) "getScheme", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getScheme }, + + { (char *) "getServerName", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getServerName }, + + { (char *) "getServerPort", + (char *) "(J)I", + nxt_java_Request_getServerPort }, + + { (char *) "log", + (char *) "(JLjava/lang/String;I)V", + nxt_java_Request_log }, + + { (char *) "trace", + (char *) "(JLjava/lang/String;I)V", + nxt_java_Request_trace }, + + { (char *) "getResponse", + (char *) "(J)Lnginx/unit/Response;", + nxt_java_Request_getResponse }, + + }; + + res = (*env)->RegisterNatives(env, nxt_java_Request_class, + request_methods, + sizeof(request_methods) / sizeof(request_methods[0])); + + nxt_unit_debug(NULL, "registered Request methods: %d", res); + + if (res != 0) { + nxt_unit_warn(NULL, "registering natives for Request failed"); + goto failed; + } + + res = nxt_java_initHeadersEnumeration(env, cl); + if (res != NXT_UNIT_OK) { + goto failed; + } + + res = nxt_java_initHeaderNamesEnumeration(env, cl); + if (res != NXT_UNIT_OK) { + goto failed; + } + + return NXT_UNIT_OK; + +failed: + + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; +} + + +jobject +nxt_java_newRequest(JNIEnv *env, jobject ctx, nxt_unit_request_info_t *req) +{ + return (*env)->NewObject(env, nxt_java_Request_class, + nxt_java_Request_ctor, ctx, nxt_ptr2jlong(req), + nxt_ptr2jlong(req->request)); +} + + +static jstring JNICALL +nxt_java_Request_getHeader(JNIEnv *env, jclass cls, jlong req_ptr, + jstring name, jint name_len) +{ + const char *name_str; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + name_str = (*env)->GetStringUTFChars(env, name, NULL); + if (name_str == NULL) { + return NULL; + } + + r = nxt_jlong2ptr(req_ptr); + + f = nxt_java_findHeader(r->fields, r->fields + r->fields_count, + name_str, name_len); + + (*env)->ReleaseStringUTFChars(env, name, name_str); + + if (f == NULL) { + return NULL; + } + + return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&f->value)); +} + + +static jobject JNICALL +nxt_java_Request_getHeaderNames(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return nxt_java_newHeaderNamesEnumeration(env, r->fields, r->fields_count); +} + + +static jobject JNICALL +nxt_java_Request_getHeaders(JNIEnv *env, jclass cls, jlong req_ptr, + jstring name, jint name_len) +{ + const char *name_str; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + name_str = (*env)->GetStringUTFChars(env, name, NULL); + if (name_str == NULL) { + return NULL; + } + + r = nxt_jlong2ptr(req_ptr); + + f = nxt_java_findHeader(r->fields, r->fields + r->fields_count, + name_str, name_len); + + (*env)->ReleaseStringUTFChars(env, name, name_str); + + if (f == NULL) { + f = r->fields + r->fields_count; + } + + return nxt_java_newHeadersEnumeration(env, r->fields, r->fields_count, + f - r->fields); +} + + +static jint JNICALL +nxt_java_Request_getIntHeader(JNIEnv *env, jclass cls, jlong req_ptr, + jstring name, jint name_len) +{ + jint res; + char *value, *end; + const char *name_str; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + res = -1; + + name_str = (*env)->GetStringUTFChars(env, name, NULL); + if (name_str == NULL) { + return res; + } + + r = nxt_jlong2ptr(req_ptr); + + f = nxt_java_findHeader(r->fields, r->fields + r->fields_count, + name_str, name_len); + + (*env)->ReleaseStringUTFChars(env, name, name_str); + + if (f == NULL) { + return res; + } + + value = nxt_unit_sptr_get(&f->value); + end = value + f->value_length; + + res = strtol(value, &end, 10); + + if (end < value + f->value_length) { + // TODO throw NumberFormatException.forInputString(value) + } + + return res; +} + + +static jstring JNICALL +nxt_java_Request_getMethod(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&r->method)); +} + + +static jstring JNICALL +nxt_java_Request_getQueryString(JNIEnv *env, jclass cls, jlong req_ptr) +{ + char *query; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + if (r->query.offset != 0) { + query = nxt_unit_sptr_get(&r->query); + return (*env)->NewStringUTF(env, query); + } + + return NULL; +} + + +static jstring JNICALL +nxt_java_Request_getRequestURI(JNIEnv *env, jclass cls, jlong req_ptr) +{ + char *target, *query; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + target = nxt_unit_sptr_get(&r->target); + + if (r->query.offset != 0) { + query = nxt_unit_sptr_get(&r->query); + return nxt_java_newString(env, target, query - target - 1); + } + + return (*env)->NewStringUTF(env, target); +} + + +static jlong JNICALL +nxt_java_Request_getContentLength(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return r->content_length; +} + + +static jstring JNICALL +nxt_java_Request_getContentType(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + if (r->content_type_field != NXT_UNIT_NONE_FIELD) { + f = r->fields + r->content_type_field; + + return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&f->value)); + } + + return NULL; +} + + +static jstring JNICALL +nxt_java_Request_getLocalAddr(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return nxt_java_newString(env, nxt_unit_sptr_get(&r->local), + r->local_length); +} + + +static jstring JNICALL +nxt_java_Request_getLocalName(JNIEnv *env, jclass cls, jlong req_ptr) +{ + char *local, *colon; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + local = nxt_unit_sptr_get(&r->local); + colon = memchr(local, ':', r->local_length); + + if (colon == NULL) { + colon = local + r->local_length; + } + + return nxt_java_newString(env, local, colon - local); +} + + +static jint JNICALL +nxt_java_Request_getLocalPort(JNIEnv *env, jclass cls, jlong req_ptr) +{ + jint res; + char *local, *colon, tmp; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + local = nxt_unit_sptr_get(&r->local); + colon = memchr(local, ':', r->local_length); + + if (colon == NULL) { + return 80; + } + + tmp = local[r->local_length]; + + local[r->local_length] = '\0'; + + res = strtol(colon + 1, NULL, 10); + + local[r->local_length] = tmp; + + return res; +} + + +static jstring JNICALL +nxt_java_Request_getProtocol(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&r->version)); +} + + +static jstring JNICALL +nxt_java_Request_getRemoteAddr(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return nxt_java_newString(env, nxt_unit_sptr_get(&r->remote), + r->remote_length); +} + + +static jstring JNICALL +nxt_java_Request_getRemoteHost(JNIEnv *env, jclass cls, jlong req_ptr) +{ + char *remote, *colon; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + remote = nxt_unit_sptr_get(&r->remote); + colon = memchr(remote, ':', r->remote_length); + + if (colon == NULL) { + colon = remote + r->remote_length; + } + + return nxt_java_newString(env, remote, colon - remote); +} + + +static jint JNICALL +nxt_java_Request_getRemotePort(JNIEnv *env, jclass cls, jlong req_ptr) +{ + jint res; + char *remote, *colon, tmp; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + remote = nxt_unit_sptr_get(&r->remote); + colon = memchr(remote, ':', r->remote_length); + + if (colon == NULL) { + return 80; + } + + tmp = remote[r->remote_length]; + + remote[r->remote_length] = '\0'; + + res = strtol(colon + 1, NULL, 10); + + remote[r->remote_length] = tmp; + + return res; +} + + +static jstring JNICALL +nxt_java_Request_getScheme(JNIEnv *env, jclass cls, jlong req_ptr) +{ + return (*env)->NewStringUTF(env, "http"); +} + + +static jstring JNICALL +nxt_java_Request_getServerName(JNIEnv *env, jclass cls, jlong req_ptr) +{ + char *host, *colon; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + f = nxt_java_findHeader(r->fields, r->fields + r->fields_count, + "Host", 4); + if (f != NULL) { + host = nxt_unit_sptr_get(&f->value); + + colon = memchr(host, ':', f->value_length); + + if (colon == NULL) { + colon = host + f->value_length; + } + + return nxt_java_newString(env, host, colon - host); + } + + return nxt_java_Request_getLocalName(env, cls, req_ptr); +} + + +static jint JNICALL +nxt_java_Request_getServerPort(JNIEnv *env, jclass cls, jlong req_ptr) +{ + jint res; + char *host, *colon, tmp; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + f = nxt_java_findHeader(r->fields, r->fields + r->fields_count, + "Host", 4); + if (f != NULL) { + host = nxt_unit_sptr_get(&f->value); + + colon = memchr(host, ':', f->value_length); + + if (colon == NULL) { + return 80; + } + + tmp = host[f->value_length]; + + host[f->value_length] = '\0'; + + res = strtol(colon + 1, NULL, 10); + + host[f->value_length] = tmp; + + return res; + } + + return nxt_java_Request_getLocalPort(env, cls, req_ptr); +} + + +static void JNICALL +nxt_java_Request_log(JNIEnv *env, jclass cls, jlong req_info_ptr, jstring msg, + jint msg_len) +{ + const char *msg_str; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + msg_str = (*env)->GetStringUTFChars(env, msg, NULL); + if (msg_str == NULL) { + return; + } + + nxt_unit_req_log(req, NXT_UNIT_LOG_INFO, "%.*s", msg_len, msg_str); + + (*env)->ReleaseStringUTFChars(env, msg, msg_str); +} + + +static void JNICALL +nxt_java_Request_trace(JNIEnv *env, jclass cls, jlong req_info_ptr, jstring msg, + jint msg_len) +{ +#if (NXT_DEBUG) + const char *msg_str; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + msg_str = (*env)->GetStringUTFChars(env, msg, NULL); + if (msg_str == NULL) { + return; + } + + nxt_unit_req_debug(req, "%.*s", msg_len, msg_str); + + (*env)->ReleaseStringUTFChars(env, msg, msg_str); +#endif +} + + +static jobject JNICALL +nxt_java_Request_getResponse(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + return data->jresp; +} diff --git a/src/java/nxt_jni_Request.h b/src/java/nxt_jni_Request.h new file mode 100644 index 00000000..1c9c1428 --- /dev/null +++ b/src/java/nxt_jni_Request.h @@ -0,0 +1,18 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_REQUEST_H_INCLUDED_ +#define _NXT_JAVA_REQUEST_H_INCLUDED_ + + +#include +#include + + +int nxt_java_initRequest(JNIEnv *env, jobject cl); + +jobject nxt_java_newRequest(JNIEnv *env, jobject ctx, nxt_unit_request_info_t *req); + +#endif /* _NXT_JAVA_REQUEST_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_Response.c b/src/java/nxt_jni_Response.c new file mode 100644 index 00000000..2ccfd854 --- /dev/null +++ b/src/java/nxt_jni_Response.c @@ -0,0 +1,1105 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include +#include +#include + +#include "nxt_jni.h" +#include "nxt_jni_Response.h" +#include "nxt_jni_HeadersEnumeration.h" +#include "nxt_jni_HeaderNamesEnumeration.h" +#include "nxt_jni_OutputStream.h" +#include "nxt_jni_URLClassLoader.h" + + +static jclass nxt_java_Response_class; +static jmethodID nxt_java_Response_ctor; + + +static void JNICALL nxt_java_Response_addHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jarray value); + +static nxt_unit_request_info_t *nxt_java_get_response_info( + jlong req_info_ptr, uint32_t extra_fields, uint32_t extra_data); + +static void JNICALL nxt_java_Response_addIntHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jint value); + +static void nxt_java_add_int_header(nxt_unit_request_info_t *req, + const char *name, uint8_t name_len, int value); + +static jboolean JNICALL nxt_java_Response_containsHeader(JNIEnv *env, + jclass cls, jlong req_info_ptr, jarray name); + +static jstring JNICALL nxt_java_Response_getHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name); + +static jobject JNICALL nxt_java_Response_getHeaderNames(JNIEnv *env, + jclass cls, jlong req_info_ptr); + +static jobject JNICALL nxt_java_Response_getHeaders(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name); + +static jint JNICALL nxt_java_Response_getStatus(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static jobject JNICALL nxt_java_Response_getRequest(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_commit(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_sendRedirect(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray loc); + +static int nxt_java_response_set_header(jlong req_info_ptr, + const char *name, jint name_len, const char *value, jint value_len); + +static void JNICALL nxt_java_Response_setHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jarray value); + +static void JNICALL nxt_java_Response_removeHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name); + +static int nxt_java_response_remove_header(jlong req_info_ptr, + const char *name, jint name_len); + +static void JNICALL nxt_java_Response_setIntHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jint value); + +static void JNICALL nxt_java_Response_setStatus(JNIEnv *env, jclass cls, + jlong req_info_ptr, jint sc); + +static jstring JNICALL nxt_java_Response_getContentType(JNIEnv *env, + jclass cls, jlong req_info_ptr); + +static jboolean JNICALL nxt_java_Response_isCommitted(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_reset(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_resetBuffer(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_setBufferSize(JNIEnv *env, jclass cls, + jlong req_info_ptr, jint size); + +static jint JNICALL nxt_java_Response_getBufferSize(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_setContentLength(JNIEnv *env, jclass cls, + jlong req_info_ptr, jlong len); + +static void JNICALL nxt_java_Response_setContentType(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray type); + +static void JNICALL nxt_java_Response_removeContentType(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_log(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray msg); + +static void JNICALL nxt_java_Response_trace(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray msg); + +int +nxt_java_initResponse(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.Response"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_Response_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_Response_class; + + nxt_java_Response_ctor = (*env)->GetMethodID(env, cls, "", "(J)V"); + if (nxt_java_Response_ctor == NULL) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + JNINativeMethod resp_methods[] = { + { (char *) "addHeader", + (char *) "(J[B[B)V", + nxt_java_Response_addHeader }, + + { (char *) "addIntHeader", + (char *) "(J[BI)V", + nxt_java_Response_addIntHeader }, + + { (char *) "containsHeader", + (char *) "(J[B)Z", + nxt_java_Response_containsHeader }, + + { (char *) "getHeader", + (char *) "(J[B)Ljava/lang/String;", + nxt_java_Response_getHeader }, + + { (char *) "getHeaderNames", + (char *) "(J)Ljava/util/Enumeration;", + nxt_java_Response_getHeaderNames }, + + { (char *) "getHeaders", + (char *) "(J[B)Ljava/util/Enumeration;", + nxt_java_Response_getHeaders }, + + { (char *) "getStatus", + (char *) "(J)I", + nxt_java_Response_getStatus }, + + { (char *) "getRequest", + (char *) "(J)Lnginx/unit/Request;", + nxt_java_Response_getRequest }, + + { (char *) "commit", + (char *) "(J)V", + nxt_java_Response_commit }, + + { (char *) "sendRedirect", + (char *) "(J[B)V", + nxt_java_Response_sendRedirect }, + + { (char *) "setHeader", + (char *) "(J[B[B)V", + nxt_java_Response_setHeader }, + + { (char *) "removeHeader", + (char *) "(J[B)V", + nxt_java_Response_removeHeader }, + + { (char *) "setIntHeader", + (char *) "(J[BI)V", + nxt_java_Response_setIntHeader }, + + { (char *) "setStatus", + (char *) "(JI)V", + nxt_java_Response_setStatus }, + + { (char *) "getContentType", + (char *) "(J)Ljava/lang/String;", + nxt_java_Response_getContentType }, + + { (char *) "isCommitted", + (char *) "(J)Z", + nxt_java_Response_isCommitted }, + + { (char *) "reset", + (char *) "(J)V", + nxt_java_Response_reset }, + + { (char *) "resetBuffer", + (char *) "(J)V", + nxt_java_Response_resetBuffer }, + + { (char *) "setBufferSize", + (char *) "(JI)V", + nxt_java_Response_setBufferSize }, + + { (char *) "getBufferSize", + (char *) "(J)I", + nxt_java_Response_getBufferSize }, + + { (char *) "setContentLength", + (char *) "(JJ)V", + nxt_java_Response_setContentLength }, + + { (char *) "setContentType", + (char *) "(J[B)V", + nxt_java_Response_setContentType }, + + { (char *) "removeContentType", + (char *) "(J)V", + nxt_java_Response_removeContentType }, + + { (char *) "log", + (char *) "(J[B)V", + nxt_java_Response_log }, + + { (char *) "trace", + (char *) "(J[B)V", + nxt_java_Response_trace }, + + }; + + res = (*env)->RegisterNatives(env, nxt_java_Response_class, + resp_methods, + sizeof(resp_methods) + / sizeof(resp_methods[0])); + + nxt_unit_debug(NULL, "registered Response methods: %d", res); + + if (res != 0) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +jobject +nxt_java_newResponse(JNIEnv *env, nxt_unit_request_info_t *req) +{ + return (*env)->NewObject(env, nxt_java_Response_class, + nxt_java_Response_ctor, nxt_ptr2jlong(req)); +} + + +static void JNICALL +nxt_java_Response_addHeader(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray name, jarray value) +{ + int rc; + char *name_str, *value_str; + jsize name_len, value_len; + nxt_unit_request_info_t *req; + + name_len = (*env)->GetArrayLength(env, name); + value_len = (*env)->GetArrayLength(env, value); + + req = nxt_java_get_response_info(req_info_ptr, 1, name_len + value_len + 2); + if (req == NULL) { + return; + } + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(req, "addHeader: failed to get name content"); + return; + } + + value_str = (*env)->GetPrimitiveArrayCritical(env, value, NULL); + if (value_str == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); + nxt_unit_req_warn(req, "addHeader: failed to get value content"); + + return; + } + + rc = nxt_unit_response_add_field(req, name_str, name_len, + value_str, value_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, value, value_str, 0); + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); +} + + +static nxt_unit_request_info_t * +nxt_java_get_response_info(jlong req_info_ptr, uint32_t extra_fields, + uint32_t extra_data) +{ + int rc; + char *p; + uint32_t max_size; + nxt_unit_buf_t *buf; + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + + if (nxt_unit_response_is_sent(req)) { + return NULL; + } + + data = req->data; + + if (!nxt_unit_response_is_init(req)) { + max_size = nxt_unit_buf_max(); + max_size = max_size < data->header_size ? max_size : data->header_size; + + rc = nxt_unit_response_init(req, 200, 16, max_size); + if (rc != NXT_UNIT_OK) { + return NULL; + } + } + + buf = req->response_buf; + + if (extra_fields > req->response_max_fields + - req->response->fields_count + || extra_data > (uint32_t) (buf->end - buf->free)) + { + p = buf->start + req->response_max_fields * sizeof(nxt_unit_field_t); + + max_size = 2 * (buf->end - p); + if (max_size > nxt_unit_buf_max()) { + nxt_unit_req_warn(req, "required max_size is too big: %"PRIu32, + max_size); + return NULL; + } + + rc = nxt_unit_response_realloc(req, 2 * req->response_max_fields, + max_size); + if (rc != NXT_UNIT_OK) { + nxt_unit_req_warn(req, "reallocation failed: %"PRIu32", %"PRIu32, + 2 * req->response_max_fields, max_size); + return NULL; + } + } + + return req; +} + + +static void JNICALL +nxt_java_Response_addIntHeader(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray name, jint value) +{ + char *name_str; + jsize name_len; + nxt_unit_request_info_t *req; + + name_len = (*env)->GetArrayLength(env, name); + + req = nxt_java_get_response_info(req_info_ptr, 1, name_len + 40); + if (req == NULL) { + return; + } + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(req, "addIntHeader: failed to get name content"); + return; + } + + nxt_java_add_int_header(req, name_str, name_len, value); + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); +} + + +static void +nxt_java_add_int_header(nxt_unit_request_info_t *req, const char *name, + uint8_t name_len, int value) +{ + char *p; + nxt_unit_field_t *f; + nxt_unit_response_t *resp; + + resp = req->response; + + f = resp->fields + resp->fields_count; + p = req->response_buf->free; + + f->hash = nxt_unit_field_hash(name, name_len); + f->skip = 0; + f->name_length = name_len; + + nxt_unit_sptr_set(&f->name, p); + memcpy(p, name, name_len); + p += name_len; + + nxt_unit_sptr_set(&f->value, p); + f->value_length = snprintf(p, 40, "%d", (int) value); + p += f->value_length + 1; + + resp->fields_count++; + req->response_buf->free = p; + +} + + +static jboolean JNICALL +nxt_java_Response_containsHeader(JNIEnv *env, + jclass cls, jlong req_info_ptr, jarray name) +{ + jboolean res; + char *name_str; + jsize name_len; + nxt_unit_response_t *resp; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "containsHeader: response is not initialized"); + return 0; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "containsHeader: response already sent"); + return 0; + } + + name_len = (*env)->GetArrayLength(env, name); + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(req, "containsHeader: failed to get name content"); + return 0; + } + + resp = req->response; + + res = nxt_java_findHeader(resp->fields, + resp->fields + resp->fields_count, + name_str, name_len) != NULL; + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); + + return res; +} + + +static jstring JNICALL +nxt_java_Response_getHeader(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray name) +{ + char *name_str; + jsize name_len; + nxt_unit_field_t *f; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "getHeader: response is not initialized"); + return NULL; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "getHeader: response already sent"); + return NULL; + } + + name_len = (*env)->GetArrayLength(env, name); + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(req, "getHeader: failed to get name content"); + return NULL; + } + + f = nxt_java_findHeader(req->response->fields, + req->response->fields + req->response->fields_count, + name_str, name_len); + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); + + if (f == NULL) { + return NULL; + } + + return nxt_java_newString(env, nxt_unit_sptr_get(&f->value), + f->value_length); +} + + +static jobject JNICALL +nxt_java_Response_getHeaderNames(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "getHeaderNames: response is not initialized"); + return NULL; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "getHeaderNames: response already sent"); + return NULL; + } + + return nxt_java_newHeaderNamesEnumeration(env, req->response->fields, + req->response->fields_count); +} + + +static jobject JNICALL +nxt_java_Response_getHeaders(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name) +{ + char *name_str; + jsize name_len; + nxt_unit_field_t *f; + nxt_unit_response_t *resp; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "getHeaders: response is not initialized"); + return NULL; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "getHeaders: response already sent"); + return NULL; + } + + resp = req->response; + + name_len = (*env)->GetArrayLength(env, name); + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(req, "getHeaders: failed to get name content"); + return NULL; + } + + f = nxt_java_findHeader(resp->fields, resp->fields + resp->fields_count, + name_str, name_len); + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); + + if (f == NULL) { + f = resp->fields + resp->fields_count; + } + + return nxt_java_newHeadersEnumeration(env, resp->fields, resp->fields_count, + f - resp->fields); +} + + +static jint JNICALL +nxt_java_Response_getStatus(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "getStatus: response is not initialized"); + return 200; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "getStatus: response already sent"); + return 200; + } + + return req->response->status; +} + + +static jobject JNICALL +nxt_java_Response_getRequest(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + return data->jreq; +} + + +static void JNICALL +nxt_java_Response_commit(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + nxt_java_OutputStream_flush_buf(env, req); +} + + +static void JNICALL +nxt_java_Response_sendRedirect(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray loc) +{ + int rc; + char *loc_str; + jsize loc_len; + nxt_unit_request_info_t *req; + + static const char location[] = "Location"; + static const uint32_t location_len = sizeof(location) - 1; + + req = nxt_jlong2ptr(req_info_ptr); + + if (nxt_unit_response_is_sent(req)) { + nxt_java_throw_IllegalStateException(env, "Response already sent"); + + return; + } + + loc_len = (*env)->GetArrayLength(env, loc); + + req = nxt_java_get_response_info(req_info_ptr, 1, + location_len + loc_len + 2); + if (req == NULL) { + return; + } + + loc_str = (*env)->GetPrimitiveArrayCritical(env, loc, NULL); + if (loc_str == NULL) { + nxt_unit_req_warn(req, "sendRedirect: failed to get loc content"); + return; + } + + req->response->status = 302; + + rc = nxt_java_response_set_header(req_info_ptr, location, location_len, + loc_str, loc_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, loc, loc_str, 0); + + nxt_unit_response_send(req); +} + + +static int +nxt_java_response_set_header(jlong req_info_ptr, + const char *name, jint name_len, const char *value, jint value_len) +{ + int add_field; + char *dst; + nxt_unit_field_t *f, *e; + nxt_unit_response_t *resp; + nxt_unit_request_info_t *req; + + req = nxt_java_get_response_info(req_info_ptr, 0, 0); + if (req == NULL) { + return NXT_UNIT_ERROR; + } + + resp = req->response; + + f = resp->fields; + e = f + resp->fields_count; + + add_field = 1; + + for ( ;; ) { + f = nxt_java_findHeader(f, e, name, name_len); + if (f == NULL) { + break; + } + + if (add_field && f->value_length >= (uint32_t) value_len) { + dst = nxt_unit_sptr_get(&f->value); + memcpy(dst, value, value_len); + dst[value_len] = '\0'; + f->value_length = value_len; + + add_field = 0; + f->skip = 0; + + } else { + f->skip = 1; + } + + ++f; + } + + if (!add_field) { + return NXT_UNIT_OK; + } + + req = nxt_java_get_response_info(req_info_ptr, 1, name_len + value_len + 2); + if (req == NULL) { + return NXT_UNIT_ERROR; + } + + return nxt_unit_response_add_field(req, name, name_len, value, value_len); +} + + +static void JNICALL +nxt_java_Response_setHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jarray value) +{ + int rc; + char *name_str, *value_str; + jsize name_len, value_len; + nxt_unit_request_info_t *req; + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + req = nxt_jlong2ptr(req_info_ptr); + nxt_unit_req_warn(req, "setHeader: failed to get name content"); + return; + } + + value_str = (*env)->GetPrimitiveArrayCritical(env, value, NULL); + if (value_str == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); + + req = nxt_jlong2ptr(req_info_ptr); + nxt_unit_req_warn(req, "setHeader: failed to get value content"); + + return; + } + + name_len = (*env)->GetArrayLength(env, name); + value_len = (*env)->GetArrayLength(env, value); + + rc = nxt_java_response_set_header(req_info_ptr, name_str, name_len, + value_str, value_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, value, value_str, 0); + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); +} + + +static void JNICALL +nxt_java_Response_removeHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name) +{ + int rc; + char *name_str; + jsize name_len; + nxt_unit_request_info_t *req; + + name_len = (*env)->GetArrayLength(env, name); + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + req = nxt_jlong2ptr(req_info_ptr); + nxt_unit_req_warn(req, "setHeader: failed to get name content"); + return; + } + + rc = nxt_java_response_remove_header(req_info_ptr, name_str, name_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); +} + + +static int +nxt_java_response_remove_header(jlong req_info_ptr, + const char *name, jint name_len) +{ + nxt_unit_field_t *f, *e; + nxt_unit_response_t *resp; + nxt_unit_request_info_t *req; + + req = nxt_java_get_response_info(req_info_ptr, 0, 0); + if (req == NULL) { + return NXT_UNIT_ERROR; + } + + resp = req->response; + + f = resp->fields; + e = f + resp->fields_count; + + for ( ;; ) { + f = nxt_java_findHeader(f, e, name, name_len); + if (f == NULL) { + break; + } + + f->skip = 1; + + ++f; + } + + return NXT_UNIT_OK; +} + + +static void JNICALL +nxt_java_Response_setIntHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jint value) +{ + int value_len, rc; + char value_str[40]; + char *name_str; + jsize name_len; + + value_len = snprintf(value_str, sizeof(value_str), "%d", (int) value); + + name_len = (*env)->GetArrayLength(env, name); + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(nxt_jlong2ptr(req_info_ptr), + "setIntHeader: failed to get name content"); + return; + } + + rc = nxt_java_response_set_header(req_info_ptr, name_str, name_len, + value_str, value_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); +} + + +static void JNICALL +nxt_java_Response_setStatus(JNIEnv *env, jclass cls, jlong req_info_ptr, + jint sc) +{ + nxt_unit_request_info_t *req; + + req = nxt_java_get_response_info(req_info_ptr, 0, 0); + if (req == NULL) { + return; + } + + req->response->status = sc; +} + + +static jstring JNICALL +nxt_java_Response_getContentType(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_field_t *f; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "getContentType: response is not initialized"); + return NULL; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "getContentType: response already sent"); + return NULL; + } + + f = nxt_java_findHeader(req->response->fields, + req->response->fields + req->response->fields_count, + "Content-Type", sizeof("Content-Type") - 1); + + if (f == NULL) { + return NULL; + } + + return nxt_java_newString(env, nxt_unit_sptr_get(&f->value), + f->value_length); +} + + +static jboolean JNICALL +nxt_java_Response_isCommitted(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (nxt_unit_response_is_sent(req)) { + return 1; + } + + return 0; +} + + +static void JNICALL +nxt_java_Response_reset(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_buf_t *buf; + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + + if (nxt_unit_response_is_sent(req)) { + nxt_java_throw_IllegalStateException(env, "Response already sent"); + + return; + } + + data = req->data; + + if (data->buf != NULL && data->buf->free > data->buf->start) { + data->buf->free = data->buf->start; + } + + if (nxt_unit_response_is_init(req)) { + req->response->status = 200; + req->response->fields_count = 0; + + buf = req->response_buf; + + buf->free = buf->start + req->response_max_fields + * sizeof(nxt_unit_field_t); + } +} + + +static void JNICALL +nxt_java_Response_resetBuffer(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + if (data->buf != NULL && data->buf->free > data->buf->start) { + data->buf->free = data->buf->start; + } +} + + +static void JNICALL +nxt_java_Response_setBufferSize(JNIEnv *env, jclass cls, jlong req_info_ptr, + jint size) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + if (data->buf_size == (uint32_t) size) { + return; + } + + if (data->buf != NULL && data->buf->free > data->buf->start) { + nxt_java_throw_IllegalStateException(env, "Buffer is not empty"); + + return; + } + + data->buf_size = size; + + if (data->buf_size > nxt_unit_buf_max()) { + data->buf_size = nxt_unit_buf_max(); + } + + if (data->buf != NULL + && (uint32_t) (data->buf->end - data->buf->start) < data->buf_size) + { + nxt_unit_buf_free(data->buf); + + data->buf = NULL; + } +} + + +static jint JNICALL +nxt_java_Response_getBufferSize(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + return data->buf_size; +} + + +static void JNICALL +nxt_java_Response_setContentLength(JNIEnv *env, jclass cls, jlong req_info_ptr, + jlong len) +{ + nxt_unit_request_info_t *req; + + req = nxt_java_get_response_info(req_info_ptr, 0, 0); + if (req == NULL) { + return; + } + + req->response->content_length = len; +} + + +static void JNICALL +nxt_java_Response_setContentType(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray type) +{ + int rc; + char *type_str; + jsize type_len; + + static const char content_type[] = "Content-Type"; + static const uint32_t content_type_len = sizeof(content_type) - 1; + + type_len = (*env)->GetArrayLength(env, type); + + type_str = (*env)->GetPrimitiveArrayCritical(env, type, NULL); + if (type_str == NULL) { + return; + } + + rc = nxt_java_response_set_header(req_info_ptr, + content_type, content_type_len, + type_str, type_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, type, type_str, 0); +} + + +static void JNICALL +nxt_java_Response_removeContentType(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_java_response_remove_header(req_info_ptr, "Content-Type", + sizeof("Content-Type") - 1); +} + + +static void JNICALL +nxt_java_Response_log(JNIEnv *env, jclass cls, jlong req_info_ptr, jarray msg) +{ + char *msg_str; + jsize msg_len; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + msg_len = (*env)->GetArrayLength(env, msg); + + msg_str = (*env)->GetPrimitiveArrayCritical(env, msg, NULL); + if (msg_str == NULL) { + nxt_unit_req_warn(req, "log: failed to get msg content"); + return; + } + + nxt_unit_req_log(req, NXT_UNIT_LOG_INFO, "%.*s", msg_len, msg_str); + + (*env)->ReleasePrimitiveArrayCritical(env, msg, msg_str, 0); +} + + +static void JNICALL +nxt_java_Response_trace(JNIEnv *env, jclass cls, jlong req_info_ptr, jarray msg) +{ +#if (NXT_DEBUG) + char *msg_str; + jsize msg_len; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + msg_len = (*env)->GetArrayLength(env, msg); + + msg_str = (*env)->GetPrimitiveArrayCritical(env, msg, NULL); + if (msg_str == NULL) { + nxt_unit_req_warn(req, "trace: failed to get msg content"); + return; + } + + nxt_unit_req_debug(req, "%.*s", msg_len, msg_str); + + (*env)->ReleasePrimitiveArrayCritical(env, msg, msg_str, 0); +#endif +} + diff --git a/src/java/nxt_jni_Response.h b/src/java/nxt_jni_Response.h new file mode 100644 index 00000000..d10dba58 --- /dev/null +++ b/src/java/nxt_jni_Response.h @@ -0,0 +1,18 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_RESPONSE_H_INCLUDED_ +#define _NXT_JAVA_RESPONSE_H_INCLUDED_ + + +#include +#include + + +int nxt_java_initResponse(JNIEnv *env, jobject cl); + +jobject nxt_java_newResponse(JNIEnv *env, nxt_unit_request_info_t *req); + +#endif /* _NXT_JAVA_RESPONSE_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_Thread.c b/src/java/nxt_jni_Thread.c new file mode 100644 index 00000000..43dd90bd --- /dev/null +++ b/src/java/nxt_jni_Thread.c @@ -0,0 +1,94 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include +#include + +#include "nxt_jni_Thread.h" + + +static jclass nxt_java_Thread_class; +static jmethodID nxt_java_Thread_currentThread; +static jmethodID nxt_java_Thread_getContextClassLoader; +static jmethodID nxt_java_Thread_setContextClassLoader; + + +int +nxt_java_initThread(JNIEnv *env) +{ + jclass cls; + + cls = (*env)->FindClass(env, "java/lang/Thread"); + if (cls == NULL) { + nxt_unit_warn(NULL, "java.lang.Thread not found"); + return NXT_UNIT_ERROR; + } + + nxt_java_Thread_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_Thread_class; + + nxt_java_Thread_currentThread = (*env)->GetStaticMethodID(env, cls, + "currentThread", "()Ljava/lang/Thread;"); + if (nxt_java_Thread_currentThread == NULL) { + nxt_unit_warn(NULL, "java.lang.Thread.currentThread() not found"); + goto failed; + } + + nxt_java_Thread_getContextClassLoader = (*env)->GetMethodID(env, cls, + "getContextClassLoader", "()Ljava/lang/ClassLoader;"); + if (nxt_java_Thread_getContextClassLoader == NULL) { + nxt_unit_warn(NULL, "java.lang.Thread.getContextClassLoader() " + "not found"); + goto failed; + } + + nxt_java_Thread_setContextClassLoader = (*env)->GetMethodID(env, cls, + "setContextClassLoader", "(Ljava/lang/ClassLoader;)V"); + if (nxt_java_Thread_setContextClassLoader == NULL) { + nxt_unit_warn(NULL, "java.lang.Thread.setContextClassLoader() " + "not found"); + goto failed; + } + + return NXT_UNIT_OK; + +failed: + + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; +} + +void +nxt_java_setContextClassLoader(JNIEnv *env, jobject cl) +{ + jobject thread; + + thread = (*env)->CallStaticObjectMethod(env, nxt_java_Thread_class, + nxt_java_Thread_currentThread); + + if (thread == NULL) { + return; + } + + (*env)->CallVoidMethod(env, thread, nxt_java_Thread_setContextClassLoader, + cl); +} + +jobject +nxt_java_getContextClassLoader(JNIEnv *env) +{ + jobject thread; + + thread = (*env)->CallStaticObjectMethod(env, nxt_java_Thread_class, + nxt_java_Thread_currentThread); + + if (thread == NULL) { + return NULL; + } + + return (*env)->CallObjectMethod(env, thread, + nxt_java_Thread_getContextClassLoader); +} diff --git a/src/java/nxt_jni_Thread.h b/src/java/nxt_jni_Thread.h new file mode 100644 index 00000000..4d0b650e --- /dev/null +++ b/src/java/nxt_jni_Thread.h @@ -0,0 +1,20 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_THREAD_H_INCLUDED_ +#define _NXT_JAVA_THREAD_H_INCLUDED_ + + +#include + + +int nxt_java_initThread(JNIEnv *env); + +void nxt_java_setContextClassLoader(JNIEnv *env, jobject cl); + +jobject nxt_java_getContextClassLoader(JNIEnv *env); + +#endif /* _NXT_JAVA_THREAD_H_INCLUDED_ */ + diff --git a/src/java/nxt_jni_URLClassLoader.c b/src/java/nxt_jni_URLClassLoader.c new file mode 100644 index 00000000..bf3ab0c3 --- /dev/null +++ b/src/java/nxt_jni_URLClassLoader.c @@ -0,0 +1,187 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include +#include + +#include "nxt_jni_URLClassLoader.h" + + +static jclass nxt_java_URLClassLoader_class; +static jmethodID nxt_java_URLClassLoader_ctor; +static jmethodID nxt_java_URLClassLoader_parent_ctor; +static jmethodID nxt_java_URLClassLoader_loadClass; +static jmethodID nxt_java_URLClassLoader_addURL; + +static jclass nxt_java_URL_class; +static jmethodID nxt_java_URL_ctor; + + +int +nxt_java_initURLClassLoader(JNIEnv *env) +{ + jclass cls; + + cls = (*env)->FindClass(env, "java/net/URLClassLoader"); + if (cls == NULL) { + nxt_unit_warn(NULL, "java.net.URLClassLoader not found"); + return NXT_UNIT_ERROR; + } + + nxt_java_URLClassLoader_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_URLClassLoader_class; + + nxt_java_URLClassLoader_ctor = (*env)->GetMethodID(env, cls, + "", "([Ljava/net/URL;)V"); + if (nxt_java_URLClassLoader_ctor == NULL) { + nxt_unit_warn(NULL, "java.net.URLClassLoader constructor not found"); + goto failed; + } + + nxt_java_URLClassLoader_parent_ctor = (*env)->GetMethodID(env, cls, + "", "([Ljava/net/URL;Ljava/lang/ClassLoader;)V"); + if (nxt_java_URLClassLoader_ctor == NULL) { + nxt_unit_warn(NULL, "java.net.URLClassLoader constructor not found"); + goto failed; + } + + nxt_java_URLClassLoader_loadClass = (*env)->GetMethodID(env, cls, + "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); + if (nxt_java_URLClassLoader_loadClass == NULL) { + nxt_unit_warn(NULL, "java.net.URLClassLoader.loadClass not found"); + goto failed; + } + + nxt_java_URLClassLoader_addURL = (*env)->GetMethodID(env, cls, + "addURL", "(Ljava/net/URL;)V"); + if (nxt_java_URLClassLoader_addURL == NULL) { + nxt_unit_warn(NULL, "java.net.URLClassLoader.addURL not found"); + goto failed; + } + + cls = (*env)->FindClass(env, "java/net/URL"); + if (cls == NULL) { + nxt_unit_warn(NULL, "java.net.URL not found"); + return NXT_UNIT_ERROR; + } + + nxt_java_URL_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_URL_class; + + nxt_java_URL_ctor = (*env)->GetMethodID(env, cls, + "", "(Ljava/lang/String;)V"); + if (nxt_java_URL_ctor == NULL) { + nxt_unit_warn(NULL, "java.net.URL constructor not found"); + goto failed; + } + + return NXT_UNIT_OK; + +failed: + + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; +} + + +jobject +nxt_java_newURLClassLoader(JNIEnv *env, int url_count, char **urls) +{ + jobjectArray jurls; + + jurls = nxt_java_newURLs(env, url_count, urls); + if (jurls == NULL) { + return NULL; + } + + return (*env)->NewObject(env, nxt_java_URLClassLoader_class, + nxt_java_URLClassLoader_ctor, jurls); +} + + +jobject +nxt_java_newURLClassLoader_parent(JNIEnv *env, int url_count, char **urls, + jobject parent) +{ + jobjectArray jurls; + + jurls = nxt_java_newURLs(env, url_count, urls); + if (jurls == NULL) { + return NULL; + } + + return (*env)->NewObject(env, nxt_java_URLClassLoader_class, + nxt_java_URLClassLoader_parent_ctor, jurls, + parent); +} + + +jobjectArray +nxt_java_newURLs(JNIEnv *env, int url_count, char **urls) +{ + int i; + jstring surl; + jobject jurl; + jobjectArray jurls; + + jurls = (*env)->NewObjectArray(env, url_count, nxt_java_URL_class, NULL); + if (jurls == NULL) { + return NULL; + } + + for (i = 0; i < url_count; i++) { + surl = (*env)->NewStringUTF(env, urls[i]); + if (surl == NULL) { + return NULL; + } + + jurl = (*env)->NewObject(env, nxt_java_URL_class, nxt_java_URL_ctor, + surl); + if (jurl == NULL) { + return NULL; + } + + (*env)->SetObjectArrayElement(env, jurls, i, jurl); + } + + return jurls; +} + + +jclass +nxt_java_loadClass(JNIEnv *env, jobject cl, const char *name) +{ + jstring jname; + + jname = (*env)->NewStringUTF(env, name); + if (jname == NULL) { + return NULL; + } + + return (*env)->CallObjectMethod(env, cl, nxt_java_URLClassLoader_loadClass, + jname); +} + + +void +nxt_java_addURL(JNIEnv *env, jobject cl, const char *url) +{ + jstring surl; + jobject jurl; + + surl = (*env)->NewStringUTF(env, url); + if (surl == NULL) { + return; + } + + jurl = (*env)->NewObject(env, nxt_java_URL_class, nxt_java_URL_ctor, surl); + if (jurl == NULL) { + return; + } + + (*env)->CallVoidMethod(env, cl, nxt_java_URLClassLoader_addURL, jurl); +} diff --git a/src/java/nxt_jni_URLClassLoader.h b/src/java/nxt_jni_URLClassLoader.h new file mode 100644 index 00000000..4cf2c0ec --- /dev/null +++ b/src/java/nxt_jni_URLClassLoader.h @@ -0,0 +1,27 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_URLCLASSLOADER_H_INCLUDED_ +#define _NXT_JAVA_URLCLASSLOADER_H_INCLUDED_ + + +#include + + +int nxt_java_initURLClassLoader(JNIEnv *env); + +jobject nxt_java_newURLClassLoader(JNIEnv *env, int url_count, char **urls); + +jobject nxt_java_newURLClassLoader_parent(JNIEnv *env, int url_count, + char **urls, jobject parent); + +jobjectArray nxt_java_newURLs(JNIEnv *env, int url_count, char **urls); + +jclass nxt_java_loadClass(JNIEnv *env, jobject cl, const char *name); + +void nxt_java_addURL(JNIEnv *env, jobject cl, const char *url); + +#endif /* _NXT_JAVA_URLCLASSLOADER_H_INCLUDED_ */ + diff --git a/src/nxt_application.c b/src/nxt_application.c index acdebe04..a2827b75 100644 --- a/src/nxt_application.c +++ b/src/nxt_application.c @@ -331,6 +331,17 @@ nxt_app_start(nxt_task_t *task, void *data) nxt_app = nxt_app_module_load(task, lang->file); } + if (nxt_app->pre_init != NULL) { + ret = nxt_app->pre_init(task, data); + + if (nxt_slow_path(ret != NXT_OK)) { + nxt_debug(task, "application pre_init failed"); + + } else { + nxt_debug(task, "application pre_init done"); + } + } + if (app_conf->working_directory != NULL && app_conf->working_directory[0] != 0) { @@ -521,6 +532,9 @@ nxt_app_parse_type(u_char *p, size_t length) } else if (nxt_str_eq(&str, "ruby", 4)) { return NXT_APP_RUBY; + + } else if (nxt_str_eq(&str, "java", 4)) { + return NXT_APP_JAVA; } return NXT_APP_UNKNOWN; diff --git a/src/nxt_application.h b/src/nxt_application.h index fe8113c6..781f05e0 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -20,6 +20,7 @@ typedef enum { NXT_APP_PHP, NXT_APP_PERL, NXT_APP_RUBY, + NXT_APP_JAVA, NXT_APP_UNKNOWN, } nxt_app_type_t; @@ -70,6 +71,14 @@ typedef struct { } nxt_ruby_app_conf_t; +typedef struct { + nxt_conf_value_t *classpath; + char *webapp; + nxt_conf_value_t *options; + char *unit_jars; +} nxt_java_app_conf_t; + + struct nxt_common_app_conf_s { nxt_str_t name; nxt_str_t type; @@ -85,6 +94,7 @@ struct nxt_common_app_conf_s { nxt_php_app_conf_t php; nxt_perl_app_conf_t perl; nxt_ruby_app_conf_t ruby; + nxt_java_app_conf_t java; } u; }; @@ -152,6 +162,8 @@ struct nxt_app_module_s { nxt_str_t type; const char *version; + nxt_int_t (*pre_init)(nxt_task_t *task, + nxt_common_app_conf_t *conf); nxt_int_t (*init)(nxt_task_t *task, nxt_common_app_conf_t *conf); }; diff --git a/src/nxt_conf.h b/src/nxt_conf.h index 052334fe..20ff3b1e 100644 --- a/src/nxt_conf.h +++ b/src/nxt_conf.h @@ -125,7 +125,7 @@ void nxt_conf_set_element(nxt_conf_value_t *array, nxt_uint_t index, nxt_conf_value_t *value); nxt_int_t nxt_conf_set_element_string_dup(nxt_conf_value_t *array, nxt_mp_t *mp, nxt_uint_t index, nxt_str_t *value); -nxt_uint_t nxt_conf_array_elements_count(nxt_conf_value_t *value); +NXT_EXPORT nxt_uint_t nxt_conf_array_elements_count(nxt_conf_value_t *value); void nxt_conf_array_qsort(nxt_conf_value_t *value, int (*compare)(const void *, const void *)); diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 655097fd..5653b9eb 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -88,6 +88,10 @@ static nxt_int_t nxt_conf_vldt_argument(nxt_conf_validation_t *vldt, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_java_classpath(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_java_option(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value); static nxt_conf_vldt_object_t nxt_conf_vldt_http_members[] = { @@ -423,6 +427,31 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_ruby_members[] = { }; +static nxt_conf_vldt_object_t nxt_conf_vldt_java_members[] = { + { nxt_string("classpath"), + NXT_CONF_VLDT_ARRAY, + &nxt_conf_vldt_array_iterator, + (void *) &nxt_conf_vldt_java_classpath}, + + { nxt_string("webapp"), + NXT_CONF_VLDT_STRING, + NULL, + NULL }, + + { nxt_string("options"), + NXT_CONF_VLDT_ARRAY, + &nxt_conf_vldt_array_iterator, + (void *) &nxt_conf_vldt_java_option}, + + { nxt_string("unit_jars"), + NXT_CONF_VLDT_STRING, + NULL, + NULL }, + + NXT_CONF_VLDT_NEXT(&nxt_conf_vldt_common_members) +}; + + nxt_int_t nxt_conf_validate(nxt_conf_validation_t *vldt) { @@ -818,6 +847,7 @@ nxt_conf_vldt_app(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_vldt_php_members, nxt_conf_vldt_perl_members, nxt_conf_vldt_ruby_members, + nxt_conf_vldt_java_members, }; ret = nxt_conf_vldt_type(vldt, name, value, NXT_CONF_VLDT_OBJECT); @@ -1216,3 +1246,44 @@ nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt, nxt_str_t *name, return NXT_OK; } + + +static nxt_int_t +nxt_conf_vldt_java_classpath(nxt_conf_validation_t *vldt, nxt_conf_value_t *value) +{ + nxt_str_t str; + + if (nxt_conf_type(value) != NXT_CONF_STRING) { + return nxt_conf_vldt_error(vldt, "The \"classpath\" array " + "must contain only string values."); + } + + nxt_conf_get_string(value, &str); + + if (nxt_memchr(str.start, '\0', str.length) != NULL) { + return nxt_conf_vldt_error(vldt, "The \"classpath\" array must not " + "contain strings with null character."); + } + + return NXT_OK; +} + +static nxt_int_t +nxt_conf_vldt_java_option(nxt_conf_validation_t *vldt, nxt_conf_value_t *value) +{ + nxt_str_t str; + + if (nxt_conf_type(value) != NXT_CONF_STRING) { + return nxt_conf_vldt_error(vldt, "The \"options\" array " + "must contain only string values."); + } + + nxt_conf_get_string(value, &str); + + if (nxt_memchr(str.start, '\0', str.length) != NULL) { + return nxt_conf_vldt_error(vldt, "The \"options\" array must not " + "contain strings with null character."); + } + + return NXT_OK; +} diff --git a/src/nxt_external.c b/src/nxt_external.c index c7aacffc..89fe08c8 100644 --- a/src/nxt_external.c +++ b/src/nxt_external.c @@ -18,6 +18,7 @@ nxt_app_module_t nxt_external_module = { NULL, nxt_string("external"), "*", + NULL, nxt_external_init, }; diff --git a/src/nxt_java.c b/src/nxt_java.c new file mode 100644 index 00000000..bf4931ab --- /dev/null +++ b/src/nxt_java.c @@ -0,0 +1,462 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "java/nxt_jni_Thread.h" +#include "java/nxt_jni_Context.h" +#include "java/nxt_jni_Request.h" +#include "java/nxt_jni_Response.h" +#include "java/nxt_jni_InputStream.h" +#include "java/nxt_jni_OutputStream.h" +#include "java/nxt_jni_URLClassLoader.h" + +#include "nxt_jars.h" + +static nxt_int_t nxt_java_pre_init(nxt_task_t *task, + nxt_common_app_conf_t *conf); +static nxt_int_t nxt_java_init(nxt_task_t *task, nxt_common_app_conf_t *conf); +static void nxt_java_request_handler(nxt_unit_request_info_t *req); + +static uint32_t compat[] = { + NXT_VERNUM, NXT_DEBUG, +}; + +char *nxt_java_modules; + + +#define NXT_STRING(x) _NXT_STRING(x) +#define _NXT_STRING(x) #x + +NXT_EXPORT nxt_app_module_t nxt_app_module = { + sizeof(compat), + compat, + nxt_string("java"), + NXT_STRING(NXT_JAVA_VERSION), + nxt_java_pre_init, + nxt_java_init, +}; + +typedef struct { + JNIEnv *env; + jobject ctx; +} nxt_java_data_t; + + +static nxt_int_t +nxt_java_pre_init(nxt_task_t *task, nxt_common_app_conf_t *conf) +{ + const char *unit_jars; + + unit_jars = conf->u.java.unit_jars; + if (unit_jars == NULL) { + unit_jars = NXT_JARS; + } + + nxt_java_modules = realpath(unit_jars, NULL); + if (nxt_java_modules == NULL) { + nxt_alert(task, "realpath(%s) failed: %E", NXT_JARS, nxt_errno); + return NXT_ERROR; + } + + return NXT_OK; +} + + +static char ** +nxt_java_module_jars(const char *jars[], int jar_count) +{ + char **res, *jurl; + nxt_int_t modules_len, jlen, i; + const char **jar; + + res = nxt_malloc(jar_count * sizeof(char*)); + if (res == NULL) { + return NULL; + } + + modules_len = nxt_strlen(nxt_java_modules); + + for (i = 0, jar = jars; *jar != NULL; jar++) { + jlen = nxt_length("file:") + modules_len + nxt_length("/") + + nxt_strlen(*jar) + 1; + jurl = nxt_malloc(jlen); + if (jurl == NULL) { + return NULL; + } + + res[i++] = jurl; + + jurl = nxt_cpymem(jurl, "file:", nxt_length("file:")); + jurl = nxt_cpymem(jurl, nxt_java_modules, modules_len); + *jurl++ = '/'; + jurl = nxt_cpymem(jurl, *jar, nxt_strlen(*jar)); + *jurl++ = '\0'; + } + + return res; +} + + +static nxt_int_t +nxt_java_init(nxt_task_t *task, nxt_common_app_conf_t *conf) +{ + jint rc; + char *opt, *real_path; + char **classpath_arr, **unit_jars, **system_jars; + JavaVM *jvm; + JNIEnv *env; + jobject cl, classpath; + nxt_str_t str; + nxt_int_t opt_len, real_path_len; + nxt_uint_t i, unit_jars_count, classpath_count, system_jars_count; + JavaVMOption *jvm_opt; + JavaVMInitArgs jvm_args; + nxt_unit_ctx_t *ctx; + nxt_unit_init_t java_init; + nxt_java_data_t data; + nxt_conf_value_t *value; + nxt_java_app_conf_t *c; + + //setenv("ASAN_OPTIONS", "handle_segv=0", 1); + + jvm_args.version = JNI_VERSION_1_6; + jvm_args.nOptions = 0; + jvm_args.ignoreUnrecognized = 0; + + c = &conf->u.java; + + if (c->options != NULL) { + jvm_args.nOptions += nxt_conf_array_elements_count(c->options); + } + + jvm_opt = nxt_malloc(jvm_args.nOptions * sizeof(JavaVMOption)); + if (jvm_opt == NULL) { + nxt_alert(task, "failed to allocate jvm_opt"); + return NXT_ERROR; + } + + jvm_args.options = jvm_opt; + + unit_jars_count = nxt_nitems(nxt_java_unit_jars) - 1; + + unit_jars = nxt_java_module_jars(nxt_java_unit_jars, unit_jars_count); + if (unit_jars == NULL) { + nxt_alert(task, "failed to allocate buffer for unit_jars array"); + + return NXT_ERROR; + } + + system_jars_count = nxt_nitems(nxt_java_system_jars) - 1; + + system_jars = nxt_java_module_jars(nxt_java_system_jars, system_jars_count); + if (system_jars == NULL) { + nxt_alert(task, "failed to allocate buffer for system_jars array"); + + return NXT_ERROR; + } + + if (c->options != NULL) { + + for (i = 0; /* void */ ; i++) { + value = nxt_conf_get_array_element(c->options, i); + if (value == NULL) { + break; + } + + nxt_conf_get_string(value, &str); + + opt = nxt_malloc(str.length + 1); + if (opt == NULL) { + nxt_alert(task, "failed to allocate jvm_opt"); + return NXT_ERROR; + } + + memcpy(opt, str.start, str.length); + opt[str.length] = '\0'; + + jvm_opt[i].optionString = opt; + } + } + + if (c->classpath != NULL) { + classpath_count = nxt_conf_array_elements_count(c->classpath); + classpath_arr = nxt_malloc(classpath_count * sizeof(char *)); + + for (i = 0; /* void */ ; i++) { + value = nxt_conf_get_array_element(c->classpath, i); + if (value == NULL) { + break; + } + + nxt_conf_get_string(value, &str); + + opt_len = str.length + 1; + + char *sc = memchr(str.start, ':', str.length); + if (sc == NULL && str.start[0] == '/') { + opt_len += nxt_length("file:"); + } + + opt = nxt_malloc(opt_len); + if (opt == NULL) { + nxt_alert(task, "failed to allocate classpath"); + return NXT_ERROR; + } + + if (sc == NULL && str.start[0] != '/') { + nxt_memcpy(opt, str.start, str.length); + opt[str.length] = '\0'; + + real_path = realpath(opt, NULL); + if (real_path == NULL) { + nxt_alert(task, "realpath(%s) failed: %E", opt, nxt_errno); + return NXT_ERROR; + } + + real_path_len = nxt_strlen(real_path); + + free(opt); + + opt_len = nxt_length("file:") + real_path_len + 1; + + opt = nxt_malloc(opt_len); + if (opt == NULL) { + nxt_alert(task, "failed to allocate classpath"); + return NXT_ERROR; + } + + } else { + real_path = (char *) str.start; /* I love this cast! */ + real_path_len = str.length; + } + + classpath_arr[i] = opt; + + if (sc == NULL) { + opt = nxt_cpymem(opt, "file:", nxt_length("file:")); + } + + opt = nxt_cpymem(opt, real_path, real_path_len); + *opt = '\0'; + } + + } else { + classpath_count = 0; + classpath_arr = NULL; + } + + rc = JNI_CreateJavaVM(&jvm, (void **) &env, &jvm_args); + if (rc != JNI_OK) { + nxt_alert(task, "failed to create Java VM: %d", (int) rc); + return NXT_ERROR; + } + + rc = nxt_java_initThread(env); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initThread() failed"); + goto env_failed; + } + + rc = nxt_java_initURLClassLoader(env); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initURLClassLoader() failed"); + goto env_failed; + } + + cl = nxt_java_newURLClassLoader(env, system_jars_count, system_jars); + if (cl == NULL) { + nxt_alert(task, "nxt_java_newURLClassLoader failed"); + goto env_failed; + } + + nxt_java_setContextClassLoader(env, cl); + + cl = nxt_java_newURLClassLoader_parent(env, unit_jars_count, unit_jars, cl); + if (cl == NULL) { + nxt_alert(task, "nxt_java_newURLClassLoader_parent failed"); + goto env_failed; + } + + nxt_java_setContextClassLoader(env, cl); + + rc = nxt_java_initContext(env, cl); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initContext() failed"); + goto env_failed; + } + + rc = nxt_java_initRequest(env, cl); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initRequest() failed"); + goto env_failed; + } + + rc = nxt_java_initResponse(env, cl); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initResponse() failed"); + goto env_failed; + } + + rc = nxt_java_initInputStream(env, cl); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initInputStream() failed"); + goto env_failed; + } + + rc = nxt_java_initOutputStream(env, cl); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initOutputStream() failed"); + goto env_failed; + } + + nxt_java_jni_init(env); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_jni_init() failed"); + goto env_failed; + } + + classpath = nxt_java_newURLs(env, classpath_count, classpath_arr); + if (classpath == NULL) { + nxt_alert(task, "nxt_java_newURLs failed"); + goto env_failed; + } + + data.env = env; + data.ctx = nxt_java_startContext(env, c->webapp, classpath); + + if ((*env)->ExceptionCheck(env)) { + nxt_alert(task, "Unhandled exception in application start"); + (*env)->ExceptionDescribe(env); + return NXT_ERROR; + } + + nxt_unit_default_init(task, &java_init); + + java_init.callbacks.request_handler = nxt_java_request_handler; + java_init.request_data_size = sizeof(nxt_java_request_data_t); + java_init.data = &data; + + ctx = nxt_unit_init(&java_init); + if (nxt_slow_path(ctx == NULL)) { + nxt_alert(task, "nxt_unit_init() failed"); + return NXT_ERROR; + } + + rc = nxt_unit_run(ctx); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + /* TODO report error */ + } + + nxt_unit_done(ctx); + + nxt_java_stopContext(env, data.ctx); + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + } + + (*jvm)->DestroyJavaVM(jvm); + + exit(0); + + return NXT_OK; + +env_failed: + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + } + + return NXT_ERROR; +} + + +static void +nxt_java_request_handler(nxt_unit_request_info_t *req) +{ + JNIEnv *env; + jobject jreq, jresp; + nxt_java_data_t *java_data; + nxt_java_request_data_t *data; + + java_data = req->unit->data; + env = java_data->env; + data = req->data; + + jreq = nxt_java_newRequest(env, java_data->ctx, req); + if (jreq == NULL) { + nxt_unit_req_alert(req, "failed to create Request instance"); + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } + + nxt_unit_request_done(req, NXT_UNIT_ERROR); + return; + } + + jresp = nxt_java_newResponse(env, req); + if (jresp == NULL) { + nxt_unit_req_alert(req, "failed to create Response instance"); + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } + + (*env)->DeleteLocalRef(env, jreq); + + nxt_unit_request_done(req, NXT_UNIT_ERROR); + return; + } + + data->header_size = 10 * 1024; + data->buf_size = 32 * 1024; /* from Jetty */ + data->jreq = jreq; + data->jresp = jresp; + data->buf = NULL; + + nxt_unit_request_group_dup_fields(req); + + nxt_java_service(env, java_data->ctx, jreq, jresp); + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_response_init(req, 200, 0, 0); + } + + if (!nxt_unit_response_is_sent(req)) { + nxt_unit_response_send(req); + } + + if (data->buf != NULL) { + nxt_unit_buf_send(data->buf); + + data->buf = NULL; + } + + (*env)->DeleteLocalRef(env, jresp); + (*env)->DeleteLocalRef(env, jreq); + + nxt_unit_request_done(req, NXT_UNIT_OK); +} + diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index a33117e2..f756bff7 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -219,12 +219,38 @@ static nxt_conf_map_t nxt_ruby_app_conf[] = { }; +static nxt_conf_map_t nxt_java_app_conf[] = { + { + nxt_string("classpath"), + NXT_CONF_MAP_PTR, + offsetof(nxt_common_app_conf_t, u.java.classpath), + }, + { + nxt_string("webapp"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_common_app_conf_t, u.java.webapp), + }, + { + nxt_string("options"), + NXT_CONF_MAP_PTR, + offsetof(nxt_common_app_conf_t, u.java.options), + }, + { + nxt_string("unit_jars"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_common_app_conf_t, u.java.unit_jars), + }, + +}; + + static nxt_conf_app_map_t nxt_app_maps[] = { { nxt_nitems(nxt_external_app_conf), nxt_external_app_conf }, { nxt_nitems(nxt_python_app_conf), nxt_python_app_conf }, { nxt_nitems(nxt_php_app_conf), nxt_php_app_conf }, { nxt_nitems(nxt_perl_app_conf), nxt_perl_app_conf }, { nxt_nitems(nxt_ruby_app_conf), nxt_ruby_app_conf }, + { nxt_nitems(nxt_java_app_conf), nxt_java_app_conf }, }; diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index 559a3e37..e36dd20a 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -164,6 +164,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { compat, nxt_string("php"), PHP_VERSION, + NULL, nxt_php_init, }; diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c index 871c8e91..80ffb0f7 100644 --- a/src/nxt_python_wsgi.c +++ b/src/nxt_python_wsgi.c @@ -105,6 +105,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { compat, nxt_string("python"), PY_VERSION, + NULL, nxt_python_init, }; diff --git a/src/nxt_router.c b/src/nxt_router.c index 51c84df9..e46e8f82 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -293,6 +293,7 @@ static const nxt_str_t *nxt_app_msg_prefix[] = { &http_prefix, &http_prefix, &http_prefix, + &empty_prefix, }; diff --git a/src/perl/nxt_perl_psgi.c b/src/perl/nxt_perl_psgi.c index 275ed913..efcff7e3 100644 --- a/src/perl/nxt_perl_psgi.c +++ b/src/perl/nxt_perl_psgi.c @@ -113,6 +113,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { nxt_perl_psgi_compat, nxt_string("perl"), PERL_VERSION_STRING, + NULL, nxt_perl_psgi_init, }; diff --git a/src/ruby/nxt_ruby.c b/src/ruby/nxt_ruby.c index d099a338..17831175 100644 --- a/src/ruby/nxt_ruby.c +++ b/src/ruby/nxt_ruby.c @@ -76,6 +76,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { compat, nxt_string("ruby"), ruby_version, + NULL, nxt_ruby_init, }; diff --git a/test/java/content_type/app.java b/test/java/content_type/app.java new file mode 100644 index 00000000..7d8a7418 --- /dev/null +++ b/test/java/content_type/app.java @@ -0,0 +1,89 @@ + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/") +public class app extends HttpServlet +{ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + if (request.getServletPath().equals("/1")) { + response.setContentType("text/plain;charset=utf-8"); + response.setHeader("X-Character-Encoding", response.getCharacterEncoding()); + response.setHeader("X-Content-Type", response.getContentType()); + return; + } + + if (request.getServletPath().equals("/2")) { + response.setContentType("text/plain"); + response.setHeader("X-Character-Encoding", response.getCharacterEncoding()); + response.setHeader("X-Content-Type", response.getContentType()); + return; + } + + if (request.getServletPath().equals("/3")) { + response.setContentType("text/plain;charset=utf-8"); + response.setCharacterEncoding("windows-1251"); + response.setHeader("X-Character-Encoding", response.getCharacterEncoding()); + response.setHeader("X-Content-Type", response.getContentType()); + return; + } + + if (request.getServletPath().equals("/4")) { + response.setCharacterEncoding("windows-1251"); + response.setContentType("text/plain"); + response.setHeader("X-Character-Encoding", response.getCharacterEncoding()); + response.setHeader("X-Content-Type", response.getContentType()); + return; + } + + if (request.getServletPath().equals("/5")) { + response.setContentType("text/plain;charset=utf-8"); + response.setCharacterEncoding(null); + response.setHeader("X-Character-Encoding", response.getCharacterEncoding()); + response.setHeader("X-Content-Type", response.getContentType()); + return; + } + + if (request.getServletPath().equals("/6")) { + response.setContentType("text/plain;charset=utf-8"); + response.setContentType(null); + response.setHeader("X-Character-Encoding", response.getCharacterEncoding()); + response.setHeader("X-Content-Type", response.getContentType()); + return; + } + + if (request.getServletPath().equals("/7")) { + response.setContentType("text/plain;charset=utf-8"); + + PrintWriter out = response.getWriter(); + + response.setCharacterEncoding("windows-1251"); + response.setHeader("X-Character-Encoding", response.getCharacterEncoding()); + response.setHeader("X-Content-Type", response.getContentType()); + return; + } + + if (request.getServletPath().equals("/8")) { + response.setContentType("text/plain;charset=utf-8"); + + PrintWriter out = response.getWriter(); + + response.setContentType("text/html;charset=windows-1251"); + response.setHeader("X-Character-Encoding", response.getCharacterEncoding()); + response.setHeader("X-Content-Type", response.getContentType()); + return; + } + + response.sendError(404); + } +} diff --git a/test/java/cookies/app.java b/test/java/cookies/app.java new file mode 100644 index 00000000..13cea6d1 --- /dev/null +++ b/test/java/cookies/app.java @@ -0,0 +1,30 @@ + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet(urlPatterns = "/") +public class app extends HttpServlet +{ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie c : cookies) { + if (c.getName().equals("var1")) { + response.addHeader("X-Cookie-1", c.getValue()); + } + if (c.getName().equals("var2")) { + response.addHeader("X-Cookie-2", c.getValue()); + } + } + } + } +} diff --git a/test/java/empty/app.java b/test/java/empty/app.java new file mode 100644 index 00000000..b0fca631 --- /dev/null +++ b/test/java/empty/app.java @@ -0,0 +1,18 @@ + +import java.io.IOException; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/") +public class app extends HttpServlet +{ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { } +} diff --git a/test/java/filter/app.java b/test/java/filter/app.java new file mode 100644 index 00000000..a5da3997 --- /dev/null +++ b/test/java/filter/app.java @@ -0,0 +1,54 @@ + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +@WebServlet(urlPatterns = "/") +public class app extends HttpServlet +{ + @WebFilter(urlPatterns = "") + public static class filter implements Filter + { + @Override + public void init(FilterConfig filterConfig) + { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + response.getOutputStream().println("Extra Info"); + response.setCharacterEncoding("utf-8"); + + ((HttpServletResponse) response).addHeader("X-Filter-Before", "1"); + + chain.doFilter(request, response); + + ((HttpServletResponse) response).setHeader("X-Filter-After", "1"); + } + + @Override + public void destroy() + { + } + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + response.getOutputStream().println("This is servlet response"); + response.setHeader("X-Filter-After", "0"); + } +} diff --git a/test/java/forward/app.java b/test/java/forward/app.java new file mode 100644 index 00000000..0dea17d6 --- /dev/null +++ b/test/java/forward/app.java @@ -0,0 +1,138 @@ + +import java.io.IOException; +import java.io.PrintWriter; + +import java.util.Map; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +public class app extends HttpServlet +{ + private String id; + + private class RequestWrapper extends HttpServletRequestWrapper + { + public RequestWrapper(HttpServletRequest r) + { + super(r); + } + } + + private class ResponseWrapper extends HttpServletResponseWrapper + { + public ResponseWrapper(HttpServletResponse r) + { + super(r); + } + } + + @Override + public void init(ServletConfig sc) + throws ServletException + { + id = sc.getInitParameter("id"); + } + + private RequestDispatcher getRequestDispatcher(HttpServletRequest request, String str) + { + String disp = request.getParameter("disp"); + + if (disp != null && disp.equals("ctx")) { + return request.getServletContext().getRequestDispatcher(str); + } + + if (disp != null && disp.equals("name")) { + return request.getServletContext().getNamedDispatcher(str); + } + + if (disp == null || disp.equals("req")) { + return request.getRequestDispatcher(str); + } + + return null; + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + String dtype = "" + request.getDispatcherType(); + + response.addHeader("X-" + dtype + "-Id", id); + response.addHeader("X-" + dtype + "-Request-URI", "" + request.getRequestURI()); + response.addHeader("X-" + dtype + "-Servlet-Path", "" + request.getServletPath()); + response.addHeader("X-" + dtype + "-Path-Info", "" + request.getPathInfo()); + response.addHeader("X-" + dtype + "-Query-String", "" + request.getQueryString()); + response.addHeader("X-" + dtype + "-Dispatcher-Type", "" + request.getDispatcherType()); + + response.setContentType("text/plain; charset=utf-8"); + + Map pmap = request.getParameterMap(); + + for (Map.Entry p : pmap.entrySet()) { + response.addHeader("X-" + dtype + "-Param-" + p.getKey(), "" + String.join(",", p.getValue())); + } + + PrintWriter out = response.getWriter(); + + if (id.equals("fwd")) { + String uri = request.getParameter("uri"); + + if (uri != null && request.getDispatcherType() != DispatcherType.FORWARD) { + response.addHeader("X-Forward-To", "" + uri); + + out.println("Before forwarding."); + + RequestDispatcher d = getRequestDispatcher(request, uri); + + if (d == null) { + out.println("Dispatcher is null"); + return; + } + + try { + d.forward(new RequestWrapper(request), new ResponseWrapper(response)); + } catch(Exception e) { + response.addHeader("X-Exception", "" + e); + } + + response.addHeader("X-After-Forwarding", "you-should-not-see-this"); + + out.println("After forwarding."); + + return; + } + } + + if (id.equals("data")) { + response.addHeader("X-" + RequestDispatcher.FORWARD_REQUEST_URI, "" + request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI)); + response.addHeader("X-" + RequestDispatcher.FORWARD_CONTEXT_PATH, "" + request.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH)); + response.addHeader("X-" + RequestDispatcher.FORWARD_SERVLET_PATH, "" + request.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH)); + response.addHeader("X-" + RequestDispatcher.FORWARD_PATH_INFO, "" + request.getAttribute(RequestDispatcher.FORWARD_PATH_INFO)); + response.addHeader("X-" + RequestDispatcher.FORWARD_QUERY_STRING, "" + request.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING)); + + out.println("app.doGet(): #" + this + ", " + id); + out.println("RequestURI: " + request.getRequestURI()); + out.println("ServletPath: " + request.getServletPath()); + out.println("PathInfo: " + request.getPathInfo()); + out.println("DispType: " + request.getDispatcherType()); + out.println("QueryString: " + request.getQueryString()); + + for (Map.Entry p : pmap.entrySet()) { + out.println("- " + p.getKey() + "=" + String.join(",", p.getValue())); + } + + return; + } + + response.sendError(404); + } +} diff --git a/test/java/forward/index.html b/test/java/forward/index.html new file mode 100644 index 00000000..4f5a6379 --- /dev/null +++ b/test/java/forward/index.html @@ -0,0 +1 @@ +This is index.html. diff --git a/test/java/forward/web.xml b/test/java/forward/web.xml new file mode 100644 index 00000000..994adb37 --- /dev/null +++ b/test/java/forward/web.xml @@ -0,0 +1,38 @@ + + + + + + fwd + app + idfwd + + + + fwd + /fwd/* + + + + + data + app + iddata + + + + data + /data/* + + + + data + /WEB-INF/index.html + /index.html + + + + diff --git a/test/java/get_header/app.java b/test/java/get_header/app.java new file mode 100644 index 00000000..c981835d --- /dev/null +++ b/test/java/get_header/app.java @@ -0,0 +1,21 @@ + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/") +public class app extends HttpServlet +{ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + response.addHeader("X-Reply", request.getHeader("X-Header")); + } +} diff --git a/test/java/get_header_names/app.java b/test/java/get_header_names/app.java new file mode 100644 index 00000000..cd2f3097 --- /dev/null +++ b/test/java/get_header_names/app.java @@ -0,0 +1,27 @@ + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Enumeration; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/") +public class app extends HttpServlet +{ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + Enumeration header_names = request.getHeaderNames(); + + for (int i = 0; header_names.hasMoreElements(); i++) { + response.addHeader("X-Reply-" + Integer.toString(i), + header_names.nextElement()); + } + } +} diff --git a/test/java/get_headers/app.java b/test/java/get_headers/app.java new file mode 100644 index 00000000..f2930a61 --- /dev/null +++ b/test/java/get_headers/app.java @@ -0,0 +1,27 @@ + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Enumeration; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/") +public class app extends HttpServlet +{ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + Enumeration headers = request.getHeaders("X-Header"); + + for (int i = 0; headers.hasMoreElements(); i++) { + response.addHeader("X-Reply-" + Integer.toString(i), + headers.nextElement()); + } + } +} diff --git a/test/java/get_params/app.java b/test/java/get_params/app.java new file mode 100644 index 00000000..1965ae2a --- /dev/null +++ b/test/java/get_params/app.java @@ -0,0 +1,50 @@ + +import java.io.IOException; + +import java.util.Enumeration; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet(urlPatterns = "/") +public class app extends HttpServlet +{ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + response.addHeader("X-Var-1", request.getParameter("var1")); + response.addHeader("X-Var-2", "" + (request.getParameter("var2") != null)); + response.addHeader("X-Var-3", "" + (request.getParameter("var3") != null)); + response.addHeader("X-Var-4", request.getParameter("var4")); + + Enumeration parameter_names = request.getParameterNames(); + + String names = ""; + for (int i = 0; parameter_names.hasMoreElements(); i++) { + names = names.concat(parameter_names.nextElement() + " "); + } + response.addHeader("X-Param-Names", names); + + String[] parameter_values = request.getParameterValues("var4"); + + String values = ""; + for (int i = 0; i < parameter_values.length; i++) { + values = values.concat(parameter_values[i] + " "); + } + response.addHeader("X-Param-Values", values); + + Map parameter_map = request.getParameterMap(); + + String map = ""; + for (Map.Entry p : parameter_map.entrySet()) { + map = map.concat(p.getKey() + "=" + String.join(",", p.getValue()) + " "); + } + response.addHeader("X-Param-Map", map); + } +} diff --git a/test/java/header/app.java b/test/java/header/app.java new file mode 100644 index 00000000..02d56f4d --- /dev/null +++ b/test/java/header/app.java @@ -0,0 +1,34 @@ + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/") +public class app extends HttpServlet +{ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + response.setHeader("X-Set-Utf8-Value", "тест"); + response.setHeader("X-Set-Utf8-Name-Имя", "x"); + + response.addHeader("X-Add-Utf8-Value", "тест"); + response.addHeader("X-Add-Utf8-Name-Имя", "y"); + + response.addHeader("X-Add-Test", "v1"); + response.addHeader("X-Add-Test", null); + + response.setHeader("X-Set-Test1", "v1"); + response.setHeader("X-Set-Test1", null); + + response.setHeader("X-Set-Test2", "v1"); + response.setHeader("X-Set-Test2", ""); + } +} diff --git a/test/java/header_date/app.java b/test/java/header_date/app.java new file mode 100644 index 00000000..cedd569c --- /dev/null +++ b/test/java/header_date/app.java @@ -0,0 +1,22 @@ + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/") +public class app extends HttpServlet +{ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + response.setDateHeader("X-Set-Date", 1000); + response.addDateHeader("X-Get-Date", request.getDateHeader("X-Header")); + } +} diff --git a/test/java/header_int/app.java b/test/java/header_int/app.java new file mode 100644 index 00000000..3ac5478e --- /dev/null +++ b/test/java/header_int/app.java @@ -0,0 +1,22 @@ + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/") +public class app extends HttpServlet +{ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + response.setIntHeader("X-Set-Int", 1); + response.addHeader("X-Get-Int", Integer.toString(request.getIntHeader("X-Header"))); + } +} diff --git a/test/java/include/app.java b/test/java/include/app.java new file mode 100644 index 00000000..d7e36fc6 --- /dev/null +++ b/test/java/include/app.java @@ -0,0 +1,136 @@ + +import java.io.IOException; +import java.io.PrintWriter; + +import java.util.Map; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +public class app extends HttpServlet +{ + private String id; + + private class RequestWrapper extends HttpServletRequestWrapper + { + public RequestWrapper(HttpServletRequest r) + { + super(r); + } + } + + private class ResponseWrapper extends HttpServletResponseWrapper + { + public ResponseWrapper(HttpServletResponse r) + { + super(r); + } + } + + @Override + public void init(ServletConfig sc) + throws ServletException + { + id = sc.getInitParameter("id"); + } + + private RequestDispatcher getRequestDispatcher(HttpServletRequest request, String str) + { + String disp = request.getParameter("disp"); + + if (disp != null && disp.equals("ctx")) { + return request.getServletContext().getRequestDispatcher(str); + } + + if (disp != null && disp.equals("name")) { + return request.getServletContext().getNamedDispatcher(str); + } + + if (disp == null || disp.equals("req")) { + return request.getRequestDispatcher(str); + } + + return null; + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + String dtype = "" + request.getDispatcherType(); + + response.addHeader("X-" + dtype + "-Id", id); + response.addHeader("X-" + dtype + "-Request-URI", "" + request.getRequestURI()); + response.addHeader("X-" + dtype + "-Servlet-Path", "" + request.getServletPath()); + response.addHeader("X-" + dtype + "-Path-Info", "" + request.getPathInfo()); + response.addHeader("X-" + dtype + "-Query-String", "" + request.getQueryString()); + response.addHeader("X-" + dtype + "-Dispatcher-Type", "" + request.getDispatcherType()); + + response.setContentType("text/plain; charset=utf-8"); + + PrintWriter out = response.getWriter(); + + if (id.equals("inc")) { + String uri = request.getParameter("uri"); + + if (uri != null && request.getDispatcherType() != DispatcherType.INCLUDE) { + response.addHeader("X-Include", "" + uri); + + out.println("Before include."); + + RequestDispatcher d = getRequestDispatcher(request, uri); + + if (d == null) { + out.println("Dispatcher is null"); + return; + } + + try { + d.include(new RequestWrapper(request), new ResponseWrapper(response)); + } catch(Exception e) { + response.addHeader("X-Exception", "" + e); + out.println("Exception: " + e); + } + + response.addHeader("X-After-Include", "you-should-see-this"); + + out.println("After include."); + + return; + } + } + + if (id.equals("data")) { + out.println("app.doGet(): #" + this + ", " + id); + out.println("RequestURI: " + request.getRequestURI()); + out.println("ServletPath: " + request.getServletPath()); + out.println("PathInfo: " + request.getPathInfo()); + out.println("DispType: " + request.getDispatcherType()); + out.println("QueryString: " + request.getQueryString()); + + Map pmap = request.getParameterMap(); + + for (Map.Entry p : pmap.entrySet()) { + out.println("- " + p.getKey() + "=" + String.join(",", p.getValue())); + } + + out.println(RequestDispatcher.INCLUDE_REQUEST_URI + ": " + request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI)); + out.println(RequestDispatcher.INCLUDE_CONTEXT_PATH + ": " + request.getAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH)); + out.println(RequestDispatcher.INCLUDE_SERVLET_PATH + ": " + request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH)); + out.println(RequestDispatcher.INCLUDE_PATH_INFO + ": " + request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO)); + out.println(RequestDispatcher.INCLUDE_QUERY_STRING + ": " + request.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING)); + + return; + } + + response.sendError(404); + } +} diff --git a/test/java/include/index.html b/test/java/include/index.html new file mode 100644 index 00000000..4f5a6379 --- /dev/null +++ b/test/java/include/index.html @@ -0,0 +1 @@ +This is index.html. diff --git a/test/java/include/web.xml b/test/java/include/web.xml new file mode 100644 index 00000000..2ed86f1d --- /dev/null +++ b/test/java/include/web.xml @@ -0,0 +1,37 @@ + + + + + + inc + app + idinc + + + + data + app + iddata + + + + inc + /inc/* + + + + data + /data/* + + + + data + /WEB-INF/index.html + /index.html + + + + diff --git a/test/java/jsp/index.jsp b/test/java/jsp/index.jsp new file mode 100644 index 00000000..0af00a46 --- /dev/null +++ b/test/java/jsp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/plain"%>This is plain text response for "<%= request.getMethod() %> <%= request.getRequestURI() %>". +<% response.addHeader("X-Unit-JSP", "ok"); %> diff --git a/test/java/mirror/app.java b/test/java/mirror/app.java new file mode 100644 index 00000000..45bc1d0d --- /dev/null +++ b/test/java/mirror/app.java @@ -0,0 +1,37 @@ + +import java.io.*; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/") +public class app extends HttpServlet +{ + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + StringBuilder buffer = new StringBuilder(); + BufferedReader reader = request.getReader(); + String line; + + while ((line = reader.readLine()) != null) { + buffer.append(line); + } + + String data = buffer.toString(); + + String dataLength = Integer.toString(data.length()); + response.setHeader("Content-Length", dataLength); + + response.setContentType("text/html"); + + PrintWriter out = response.getWriter(); + out.print(data); + out.flush(); + } +} diff --git a/test/java/path_translation/app.java b/test/java/path_translation/app.java new file mode 100644 index 00000000..ce0b9368 --- /dev/null +++ b/test/java/path_translation/app.java @@ -0,0 +1,56 @@ + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.InputStream; + +import java.util.Set; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet( urlPatterns = { "/", "/pt/*" } ) +public class app extends HttpServlet +{ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + response.addHeader("X-Request-URI", "" + request.getRequestURI()); + response.addHeader("X-Servlet-Path", "" + request.getServletPath()); + response.addHeader("X-Path-Info", "" + request.getPathInfo()); + response.addHeader("X-Query-String", "" + request.getQueryString()); + response.addHeader("X-Path-Translated", "" + request.getPathTranslated()); + + response.setContentType("text/plain; charset=utf-8"); + + PrintWriter out = response.getWriter(); + ServletContext ctx = request.getServletContext(); + + String path = request.getParameter("path"); + + if (path != null) { + response.addHeader("X-Real-Path", "" + ctx.getRealPath(path)); + response.addHeader("X-Resource", "" + ctx.getResource(path)); + + Set paths = ctx.getResourcePaths(path); + + response.addHeader("X-Resource-Paths", "" + paths); + + InputStream is = ctx.getResourceAsStream(path); + + response.addHeader("X-Resource-As-Stream", "" + is); + + if (is != null) { + final byte[] buf = new byte[1024]; + int r = is.read(buf); + + out.println(new String(buf, 0, r, "utf-8")); + } + } + } +} diff --git a/test/java/path_translation/index.html b/test/java/path_translation/index.html new file mode 100644 index 00000000..4f5a6379 --- /dev/null +++ b/test/java/path_translation/index.html @@ -0,0 +1 @@ +This is index.html. diff --git a/test/java/post_params/app.java b/test/java/post_params/app.java new file mode 100644 index 00000000..0ed73d42 --- /dev/null +++ b/test/java/post_params/app.java @@ -0,0 +1,22 @@ + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet(urlPatterns = "/") +public class app extends HttpServlet +{ + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + response.addHeader("X-Var-1", request.getParameter("var1")); + response.addHeader("X-Var-2", "" + (request.getParameter("var2") != null)); + response.addHeader("X-Var-3", "" + (request.getParameter("var3") != null)); + } +} diff --git a/test/java/query_string/app.java b/test/java/query_string/app.java new file mode 100644 index 00000000..7962336b --- /dev/null +++ b/test/java/query_string/app.java @@ -0,0 +1,20 @@ + +import java.io.IOException; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet( urlPatterns = { "/" } ) +public class app extends HttpServlet +{ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + response.addHeader("X-Query-String", "" + request.getQueryString()); + } +} diff --git a/test/java/request_listeners/app.java b/test/java/request_listeners/app.java new file mode 100644 index 00000000..6cbf7860 --- /dev/null +++ b/test/java/request_listeners/app.java @@ -0,0 +1,79 @@ + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.annotation.WebServlet; +import javax.servlet.annotation.WebListener; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebListener +@WebServlet(urlPatterns = "/") +public class app extends HttpServlet implements + ServletRequestListener, + ServletRequestAttributeListener +{ + private static String request_initialized = ""; + private static String request_destroyed = ""; + private static String attribute_added = ""; + private static String attribute_removed = ""; + private static String attribute_replaced = ""; + + @Override + public void requestInitialized(ServletRequestEvent sre) + { + HttpServletRequest r = (HttpServletRequest) sre.getServletRequest(); + + request_initialized = r.getRequestURI(); + } + + @Override + public void requestDestroyed(ServletRequestEvent sre) + { + HttpServletRequest r = (HttpServletRequest) sre.getServletRequest(); + + request_destroyed = r.getRequestURI(); + + attribute_added = ""; + attribute_removed = ""; + attribute_replaced = ""; + } + + @Override + public void attributeAdded(ServletRequestAttributeEvent event) + { + attribute_added += event.getName() + "=" + event.getValue() + ";"; + } + + @Override + public void attributeRemoved(ServletRequestAttributeEvent event) + { + attribute_removed += event.getName() + "=" + event.getValue() + ";"; + } + + @Override + public void attributeReplaced(ServletRequestAttributeEvent event) + { + attribute_replaced += event.getName() + "=" + event.getValue() + ";"; + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + request.setAttribute("var", request.getParameter("var1")); + request.setAttribute("var", request.getParameter("var2")); + request.setAttribute("var", request.getParameter("var3")); + + response.addHeader("X-Request-Initialized", request_initialized); + response.addHeader("X-Request-Destroyed", request_destroyed); + response.addHeader("X-Attr-Added", attribute_added); + response.addHeader("X-Attr-Removed", attribute_removed); + response.addHeader("X-Attr-Replaced", attribute_replaced); + } +} diff --git a/test/java/session/app.java b/test/java/session/app.java new file mode 100644 index 00000000..84d3fa55 --- /dev/null +++ b/test/java/session/app.java @@ -0,0 +1,30 @@ +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +@WebServlet(urlPatterns = "/") +public class app extends HttpServlet +{ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + HttpSession s = request.getSession(); + String old_var1 = (String) s.getAttribute("var1"); + s.setAttribute("var1", request.getParameter("var1")); + + if (old_var1 == null) { + response.addHeader("X-Var-1", "null"); + } else { + response.addHeader("X-Var-1", old_var1); + } + + response.addHeader("X-Session-Id", s.getId()); + response.addHeader("X-Session-New", "" + s.isNew()); + } +} diff --git a/test/java/session_inactive/app.java b/test/java/session_inactive/app.java new file mode 100644 index 00000000..f338fc89 --- /dev/null +++ b/test/java/session_inactive/app.java @@ -0,0 +1,27 @@ +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +@WebServlet(urlPatterns = "/") +public class app extends HttpServlet +{ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + HttpSession s = request.getSession(); + + if (s.isNew()) { + s.setMaxInactiveInterval(2); + } + + response.addHeader("X-Session-Id", s.getId()); + response.addDateHeader("X-Session-Last-Access-Time", s.getLastAccessedTime()); + response.addIntHeader("X-Session-Interval", s.getMaxInactiveInterval()); + } +} diff --git a/test/java/session_invalidate/app.java b/test/java/session_invalidate/app.java new file mode 100644 index 00000000..3f66290f --- /dev/null +++ b/test/java/session_invalidate/app.java @@ -0,0 +1,23 @@ +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +@WebServlet(urlPatterns = "/") +public class app extends HttpServlet +{ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + HttpSession s = request.getSession(); + + s.invalidate(); + + response.addHeader("X-Session-Id", s.getId()); + } +} diff --git a/test/java/session_listeners/app.java b/test/java/session_listeners/app.java new file mode 100644 index 00000000..603cc932 --- /dev/null +++ b/test/java/session_listeners/app.java @@ -0,0 +1,80 @@ + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionIdListener; +import javax.servlet.http.HttpSessionListener; + +@WebServlet(urlPatterns = "/") +public class app extends HttpServlet implements + HttpSessionListener, + HttpSessionIdListener, + HttpSessionAttributeListener +{ + private static String session_created = ""; + private static String session_destroyed = ""; + private static String session_id_changed = ""; + private static String attribute_added = ""; + private static String attribute_removed = ""; + private static String attribute_replaced = ""; + + @Override + public void sessionCreated(HttpSessionEvent se) + { + session_created += se.getSession().getId(); + } + + @Override + public void sessionDestroyed(HttpSessionEvent se) + { + session_destroyed += se.getSession().getId(); + } + + @Override + public void sessionIdChanged(HttpSessionEvent event, String oldId) + { + session_id_changed += " " + oldId + "->" + event.getSession().getId(); + } + + @Override + public void attributeAdded(HttpSessionBindingEvent event) + { + attribute_added += event.getName() + "=" + event.getValue(); + } + + @Override + public void attributeRemoved(HttpSessionBindingEvent event) + { + attribute_removed += event.getName() + "=" + event.getValue(); + } + + @Override + public void attributeReplaced(HttpSessionBindingEvent event) + { + attribute_replaced += event.getName() + "=" + event.getValue(); + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + HttpSession s = request.getSession(); + s.setAttribute("var1", request.getParameter("var1")); + + response.addHeader("X-Session-Id", s.getId()); + response.addHeader("X-Session-Created", session_created); + response.addHeader("X-Session-Destroyed", session_destroyed); + response.addHeader("X-Attr-Added", attribute_added); + response.addHeader("X-Attr-Removed", attribute_removed); + response.addHeader("X-Attr-Replaced", attribute_replaced); + } +} diff --git a/test/java/session_listeners/web.xml b/test/java/session_listeners/web.xml new file mode 100644 index 00000000..aedfe175 --- /dev/null +++ b/test/java/session_listeners/web.xml @@ -0,0 +1,14 @@ + + + + + app + + + app + + + diff --git a/test/java/url_pattern/app.java b/test/java/url_pattern/app.java new file mode 100644 index 00000000..88b071a2 --- /dev/null +++ b/test/java/url_pattern/app.java @@ -0,0 +1,39 @@ + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class app extends HttpServlet +{ + private String id; + + @Override + public void init(ServletConfig sc) + throws ServletException + { + id = sc.getInitParameter("id"); + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + response.addHeader("X-Id", id); + response.addHeader("X-Request-URI", "" + request.getRequestURI()); + response.addHeader("X-Servlet-Path", "" + request.getServletPath()); + response.setHeader("X-Path-Info", "" + request.getPathInfo()); + + response.setContentType("text/plain; charset=utf-8"); + + PrintWriter out = response.getWriter(); + out.println("app.doGet(): #" + this + ", " + id); + out.println("RequestURI: " + request.getRequestURI()); + out.println("ServletPath: " + request.getServletPath()); + out.println("PathInfo: " + request.getPathInfo()); + } +} diff --git a/test/java/url_pattern/web.xml b/test/java/url_pattern/web.xml new file mode 100644 index 00000000..048400a6 --- /dev/null +++ b/test/java/url_pattern/web.xml @@ -0,0 +1,75 @@ + + + + + + servlet0 + app + idservlet0 + + + + servlet1 + app + idservlet1 + + + + servlet2 + app + idservlet2 + + + + servlet3 + app + idservlet3 + + + + servlet4 + app + idservlet4 + + + + default + app + iddefault + + + + servlet0 + /foo/* + + + + servlet1 + /foo/bar/* + + + + servlet2 + /baz/* + + + + servlet3 + /catalog + + + + servlet4 + *.bop + + + + default + / + + + + diff --git a/test/java/welcome_files/app.java b/test/java/welcome_files/app.java new file mode 100644 index 00000000..ce922531 --- /dev/null +++ b/test/java/welcome_files/app.java @@ -0,0 +1,67 @@ + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import javax.servlet.annotation.WebFilter; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +public class app extends HttpServlet +{ + @WebFilter(urlPatterns = "*.jsp") + public static class jsp_filter implements Filter + { + @Override + public void init(FilterConfig filterConfig) { } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + ((HttpServletResponse) response).addHeader("X-JSP-Filter", "1"); + + chain.doFilter(request, response); + } + + @Override + public void destroy() { } + } + + @WebFilter(urlPatterns = "*.txt") + public static class txt_filter implements Filter + { + @Override + public void init(FilterConfig filterConfig) { } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + ((HttpServletResponse) response).addHeader("X-TXT-Filter", "1"); + + chain.doFilter(request, response); + } + + @Override + public void destroy() { } + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + response.addHeader("X-App-Servlet", "1"); + response.setContentType("text/plain; charset=utf-8"); + + PrintWriter out = response.getWriter(); + out.println("App Servlet"); + } +} diff --git a/test/java/welcome_files/dir1/index.txt b/test/java/welcome_files/dir1/index.txt new file mode 100644 index 00000000..e7784d20 --- /dev/null +++ b/test/java/welcome_files/dir1/index.txt @@ -0,0 +1 @@ +This is index.txt. diff --git a/test/java/welcome_files/dir2/default.jsp b/test/java/welcome_files/dir2/default.jsp new file mode 100644 index 00000000..48627641 --- /dev/null +++ b/test/java/welcome_files/dir2/default.jsp @@ -0,0 +1,3 @@ +<%@ page contentType="text/html"%> +

You should see this on /dir2/ URL.

+<% response.addHeader("X-Unit-JSP", "ok"); %> diff --git a/test/java/welcome_files/dir2/index.html b/test/java/welcome_files/dir2/index.html new file mode 100644 index 00000000..5b111825 --- /dev/null +++ b/test/java/welcome_files/dir2/index.html @@ -0,0 +1 @@ +

You should see this on /dir2/ URL.

diff --git a/test/java/welcome_files/dir3/index.txt b/test/java/welcome_files/dir3/index.txt new file mode 100644 index 00000000..8a2b7dea --- /dev/null +++ b/test/java/welcome_files/dir3/index.txt @@ -0,0 +1 @@ +You should never see this. diff --git a/test/java/welcome_files/dir4/index.html b/test/java/welcome_files/dir4/index.html new file mode 100644 index 00000000..2cef75e2 --- /dev/null +++ b/test/java/welcome_files/dir4/index.html @@ -0,0 +1 @@ +

You should see this for /dir4/index.html or /dir4/ url. diff --git a/test/java/welcome_files/index.htm b/test/java/welcome_files/index.htm new file mode 100644 index 00000000..97e34cf5 --- /dev/null +++ b/test/java/welcome_files/index.htm @@ -0,0 +1 @@ +

You should see this ONLY for /index.htm url. diff --git a/test/java/welcome_files/web.xml b/test/java/welcome_files/web.xml new file mode 100644 index 00000000..6bbc7c8e --- /dev/null +++ b/test/java/welcome_files/web.xml @@ -0,0 +1,27 @@ + + + + + + index.txt + default.jsp + index.html + + + + app + app + + + + app + /dir3/index.txt + /dir4/index.txt + /dir5/index.html + + + + diff --git a/test/test_java_application.py b/test/test_java_application.py new file mode 100644 index 00000000..d603ed0f --- /dev/null +++ b/test/test_java_application.py @@ -0,0 +1,753 @@ +import time +import unittest +import unit + +class TestUnitJavaApplication(unit.TestUnitApplicationJava): + + def setUpClass(): + unit.TestUnit().check_modules('java') + + def test_java_application_cookies(self): + self.load('cookies') + + headers = self.get(headers={ + 'Cookie': 'var1=val1; var2=val2', + 'Host': 'localhost', + 'Connection': 'close' + })['headers'] + + self.assertEqual(headers['X-Cookie-1'], 'val1', 'cookie 1') + self.assertEqual(headers['X-Cookie-2'], 'val2', 'cookie 2') + + def test_java_application_filter(self): + self.load('filter') + + headers = self.get()['headers'] + + self.assertEqual(headers['X-Filter-Before'], '1', 'filter before') + self.assertEqual(headers['X-Filter-After'], '1', 'filter after') + + self.assertEqual(self.get(url='/test')['headers']['X-Filter-After'], + '0', 'filter after 2') + + def test_java_application_get_variables(self): + self.load('get_params') + + headers = self.get(url='/?var1=val1&var2=&var4=val4&var4=foo')['headers'] + + self.assertEqual(headers['X-Var-1'], 'val1', 'GET variables') + self.assertEqual(headers['X-Var-2'], 'true', 'GET variables 2') + self.assertEqual(headers['X-Var-3'], 'false', 'GET variables 3') + + self.assertEqual(headers['X-Param-Names'], 'var4 var2 var1 ', + 'getParameterNames') + self.assertEqual(headers['X-Param-Values'], 'val4 foo ', + 'getParameterValues') + self.assertEqual(headers['X-Param-Map'], + 'var2= var1=val1 var4=val4,foo ', 'getParameterMap') + + def test_java_application_post_variables(self): + self.load('post_params') + + headers = self.post(headers={ + 'Content-Type': 'application/x-www-form-urlencoded', + 'Host': 'localhost', + 'Connection': 'close' + }, body='var1=val1&var2=')['headers'] + + self.assertEqual(headers['X-Var-1'], 'val1', 'POST variables') + self.assertEqual(headers['X-Var-2'], 'true', 'POST variables 2') + self.assertEqual(headers['X-Var-3'], 'false', 'POST variables 3') + + def test_java_application_session(self): + self.load('session') + + headers = self.get(url='/?var1=val1')['headers'] + session_id = headers['X-Session-Id'] + + self.assertEqual(headers['X-Var-1'], 'null', 'variable empty') + self.assertEqual(headers['X-Session-New'], 'true', 'session create') + + headers = self.get(headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close' + }, url='/?var1=val2')['headers'] + + self.assertEqual(headers['X-Var-1'], 'val1', 'variable') + self.assertEqual(headers['X-Session-New'], 'false', 'session resume') + self.assertEqual(session_id, headers['X-Session-Id'], 'session same id') + + def test_java_application_session_active(self): + self.load('session_inactive') + + resp = self.get() + session_id = resp['headers']['X-Session-Id'] + + self.assertEqual(resp['status'], 200, 'session init') + self.assertEqual(resp['headers']['X-Session-Interval'], '2', + 'session interval') + self.assertLess(abs(self.date_to_sec_epoch( + resp['headers']['X-Session-Last-Access-Time']) - self.sec_epoch()), + 5, 'session last access time') + + time.sleep(1) + + resp = self.get(headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close' + }) + + self.assertEqual(resp['headers']['X-Session-Id'], session_id, + 'session active') + + session_id = resp['headers']['X-Session-Id'] + + time.sleep(1) + + resp = self.get(headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close' + }) + + self.assertEqual(resp['headers']['X-Session-Id'], session_id, + 'session active 2') + + time.sleep(1) + + resp = self.get(headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close' + }) + + self.assertEqual(resp['headers']['X-Session-Id'], session_id, + 'session active 3') + + def test_java_application_session_inactive(self): + self.load('session_inactive') + + resp = self.get() + session_id = resp['headers']['X-Session-Id'] + + time.sleep(3) + + resp = self.get(headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close' + }) + + self.assertNotEqual(resp['headers']['X-Session-Id'], session_id, + 'session inactive') + + def test_java_application_session_invalidate(self): + self.load('session_invalidate') + + resp = self.get() + session_id = resp['headers']['X-Session-Id'] + + resp = self.get(headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close' + }) + + self.assertNotEqual(resp['headers']['X-Session-Id'], session_id, + 'session invalidate') + + def test_java_application_session_listeners(self): + self.load('session_listeners') + + headers = self.get(url='/test?var1=val1')['headers'] + session_id = headers['X-Session-Id'] + + self.assertEqual(headers['X-Session-Created'], session_id, + 'session create') + self.assertEqual(headers['X-Attr-Added'], 'var1=val1', + 'attribute add') + + headers = self.get(headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close' + }, url='/?var1=val2')['headers'] + + self.assertEqual(session_id, headers['X-Session-Id'], 'session same id') + self.assertEqual(headers['X-Attr-Replaced'], 'var1=val1', + 'attribute replace') + + headers = self.get(headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close' + }, url='/')['headers'] + + self.assertEqual(session_id, headers['X-Session-Id'], 'session same id') + self.assertEqual(headers['X-Attr-Removed'], 'var1=val2', + 'attribute remove') + + def test_java_application_jsp(self): + self.load('jsp') + + headers = self.get(url='/index.jsp')['headers'] + + self.assertEqual(headers['X-Unit-JSP'], 'ok', 'JSP Ok header') + + def test_java_application_url_pattern(self): + self.load('url_pattern') + + headers = self.get(url='/foo/bar/index.html')['headers'] + + self.assertEqual(headers['X-Id'], 'servlet1', '#1 Servlet1 request') + self.assertEqual(headers['X-Request-URI'], '/foo/bar/index.html', '#1 request URI') + self.assertEqual(headers['X-Servlet-Path'], '/foo/bar', '#1 servlet path') + self.assertEqual(headers['X-Path-Info'], '/index.html', '#1 path info') + + headers = self.get(url='/foo/bar/index.bop')['headers'] + + self.assertEqual(headers['X-Id'], 'servlet1', '#2 Servlet1 request') + self.assertEqual(headers['X-Request-URI'], '/foo/bar/index.bop', '#2 request URI') + self.assertEqual(headers['X-Servlet-Path'], '/foo/bar', '#2 servlet path') + self.assertEqual(headers['X-Path-Info'], '/index.bop', '#2 path info') + + headers = self.get(url='/baz')['headers'] + + self.assertEqual(headers['X-Id'], 'servlet2', '#3 Servlet2 request') + self.assertEqual(headers['X-Request-URI'], '/baz', '#3 request URI') + self.assertEqual(headers['X-Servlet-Path'], '/baz', '#3 servlet path') + self.assertEqual(headers['X-Path-Info'], 'null', '#3 path info') + + headers = self.get(url='/baz/index.html')['headers'] + + self.assertEqual(headers['X-Id'], 'servlet2', '#4 Servlet2 request') + self.assertEqual(headers['X-Request-URI'], '/baz/index.html', '#4 request URI') + self.assertEqual(headers['X-Servlet-Path'], '/baz', '#4 servlet path') + self.assertEqual(headers['X-Path-Info'], '/index.html', '#4 path info') + + headers = self.get(url='/catalog')['headers'] + + self.assertEqual(headers['X-Id'], 'servlet3', '#5 Servlet3 request') + self.assertEqual(headers['X-Request-URI'], '/catalog', '#5 request URI') + self.assertEqual(headers['X-Servlet-Path'], '/catalog', '#5 servlet path') + self.assertEqual(headers['X-Path-Info'], 'null', '#5 path info') + + headers = self.get(url='/catalog/index.html')['headers'] + + self.assertEqual(headers['X-Id'], 'default', '#6 default request') + self.assertEqual(headers['X-Request-URI'], '/catalog/index.html', '#6 request URI') + self.assertEqual(headers['X-Servlet-Path'], '/catalog/index.html', '#6 servlet path') + self.assertEqual(headers['X-Path-Info'], 'null', '#6 path info') + + headers = self.get(url='/catalog/racecar.bop')['headers'] + + self.assertEqual(headers['X-Id'], 'servlet4', '#7 servlet4 request') + self.assertEqual(headers['X-Request-URI'], '/catalog/racecar.bop', '#7 request URI') + self.assertEqual(headers['X-Servlet-Path'], '/catalog/racecar.bop', '#7 servlet path') + self.assertEqual(headers['X-Path-Info'], 'null', '#7 path info') + + headers = self.get( url='/index.bop')['headers'] + + self.assertEqual(headers['X-Id'], 'servlet4', '#8 servlet4 request') + self.assertEqual(headers['X-Request-URI'], '/index.bop', '#8 request URI') + self.assertEqual(headers['X-Servlet-Path'], '/index.bop', '#8 servlet path') + self.assertEqual(headers['X-Path-Info'], 'null', '#8 path info') + + headers = self.get(url='/foo/baz')['headers'] + + self.assertEqual(headers['X-Id'], 'servlet0', '#9 servlet0 request') + self.assertEqual(headers['X-Request-URI'], '/foo/baz', '#9 request URI') + self.assertEqual(headers['X-Servlet-Path'], '/foo', '#9 servlet path') + self.assertEqual(headers['X-Path-Info'], '/baz', '#9 path info') + + headers = self.get()['headers'] + + self.assertEqual(headers['X-Id'], 'default', '#10 default request') + self.assertEqual(headers['X-Request-URI'], '/', '#10 request URI') + self.assertEqual(headers['X-Servlet-Path'], '/', '#10 servlet path') + self.assertEqual(headers['X-Path-Info'], 'null', '#10 path info') + + headers = self.get(url='/index.bop/')['headers'] + + self.assertEqual(headers['X-Id'], 'default', '#11 default request') + self.assertEqual(headers['X-Request-URI'], '/index.bop/', '#11 request URI') + self.assertEqual(headers['X-Servlet-Path'], '/index.bop/', '#11 servlet path') + self.assertEqual(headers['X-Path-Info'], 'null', '#11 path info') + + def test_java_application_header(self): + self.load('header') + + headers = self.get()['headers'] + + self.assertEqual(headers['X-Set-Utf8-Value'], '????', 'set Utf8 header value') + self.assertEqual(headers['X-Set-Utf8-Name-???'], 'x', 'set Utf8 header name') + self.assertEqual(headers['X-Add-Utf8-Value'], '????', 'add Utf8 header value') + self.assertEqual(headers['X-Add-Utf8-Name-???'], 'y', 'add Utf8 header name') + self.assertEqual(headers['X-Add-Test'], 'v1', 'add null header') + self.assertEqual('X-Set-Test1' in headers, False, 'set null header') + self.assertEqual(headers['X-Set-Test2'], '', 'set empty header') + + def test_java_application_content_type(self): + self.load('content_type') + + headers = self.get(url='/1')['headers'] + + self.assertEqual(headers['Content-Type'], 'text/plain;charset=utf-8', '#1 Content-Type header') + self.assertEqual(headers['X-Content-Type'], 'text/plain;charset=utf-8', '#1 response Content-Type') + self.assertEqual(headers['X-Character-Encoding'], 'utf-8', '#1 response charset') + + headers = self.get(url='/2')['headers'] + + self.assertEqual(headers['Content-Type'], 'text/plain;charset=iso-8859-1', '#2 Content-Type header') + self.assertEqual(headers['X-Content-Type'], 'text/plain;charset=iso-8859-1', '#2 response Content-Type') + self.assertEqual(headers['X-Character-Encoding'], 'iso-8859-1', '#2 response charset') + + headers = self.get(url='/3')['headers'] + + self.assertEqual(headers['Content-Type'], 'text/plain;charset=windows-1251', '#3 Content-Type header') + self.assertEqual(headers['X-Content-Type'], 'text/plain;charset=windows-1251', '#3 response Content-Type') + self.assertEqual(headers['X-Character-Encoding'], 'windows-1251', '#3 response charset') + + headers = self.get(url='/4')['headers'] + + self.assertEqual(headers['Content-Type'], 'text/plain;charset=windows-1251', '#4 Content-Type header') + self.assertEqual(headers['X-Content-Type'], 'text/plain;charset=windows-1251', '#4 response Content-Type') + self.assertEqual(headers['X-Character-Encoding'], 'windows-1251', '#4 response charset') + + headers = self.get(url='/5')['headers'] + + self.assertEqual(headers['Content-Type'], 'text/plain;charset=iso-8859-1', '#5 Content-Type header') + self.assertEqual(headers['X-Content-Type'], 'text/plain;charset=iso-8859-1', '#5 response Content-Type') + self.assertEqual(headers['X-Character-Encoding'], 'iso-8859-1', '#5 response charset') + + headers = self.get(url='/6')['headers'] + + self.assertEqual('Content-Type' in headers, False, '#6 no Content-Type header') + self.assertEqual('X-Content-Type' in headers, False, '#6 no response Content-Type') + self.assertEqual(headers['X-Character-Encoding'], 'utf-8', '#6 response charset') + + + headers = self.get(url='/7')['headers'] + + self.assertEqual(headers['Content-Type'], 'text/plain;charset=utf-8', '#7 Content-Type header') + self.assertEqual(headers['X-Content-Type'], 'text/plain;charset=utf-8', '#7 response Content-Type') + self.assertEqual(headers['X-Character-Encoding'], 'utf-8', '#7 response charset') + + headers = self.get(url='/8')['headers'] + + self.assertEqual(headers['Content-Type'], 'text/html;charset=utf-8', '#8 Content-Type header') + self.assertEqual(headers['X-Content-Type'], 'text/html;charset=utf-8', '#8 response Content-Type') + self.assertEqual(headers['X-Character-Encoding'], 'utf-8', '#8 response charset') + + def test_java_application_welcome_files(self): + self.load('welcome_files') + + headers = self.get()['headers'] + + resp = self.get(url='/dir1') + + self.assertEqual(resp['status'], 302, 'dir redirect expected') + + resp = self.get(url='/dir1/') + + self.assertEqual('This is index.txt.' in resp['body'], True, 'dir1 index body') + self.assertEqual(resp['headers']['X-TXT-Filter'], '1', 'TXT Filter header') + + headers = self.get(url='/dir2/')['headers'] + + self.assertEqual(headers['X-Unit-JSP'], 'ok', 'JSP Ok header') + self.assertEqual(headers['X-JSP-Filter'], '1', 'JSP Filter header') + + headers = self.get(url='/dir3/')['headers'] + + self.assertEqual(headers['X-App-Servlet'], '1', 'URL pattern overrides welcome file') + + headers = self.get(url='/dir4/')['headers'] + + self.assertEqual('X-App-Servlet' in headers, False, 'Static welcome file served first') + + headers = self.get(url='/dir5/')['headers'] + + self.assertEqual(headers['X-App-Servlet'], '1', 'Servlet for welcome file served when no static file found') + + def test_java_application_request_listeners(self): + self.load('request_listeners') + + headers = self.get(url='/test1')['headers'] + + self.assertEqual(headers['X-Request-Initialized'], '/test1', + 'request initialized event') + self.assertEqual(headers['X-Request-Destroyed'], '', + 'request destroyed event') + self.assertEqual(headers['X-Attr-Added'], '', + 'attribute added event') + self.assertEqual(headers['X-Attr-Removed'], '', + 'attribute removed event') + self.assertEqual(headers['X-Attr-Replaced'], '', + 'attribute replaced event') + + headers = self.get(url='/test2?var1=1')['headers'] + + self.assertEqual(headers['X-Request-Initialized'], '/test2', + 'request initialized event') + self.assertEqual(headers['X-Request-Destroyed'], '/test1', + 'request destroyed event') + self.assertEqual(headers['X-Attr-Added'], 'var=1;', + 'attribute added event') + self.assertEqual(headers['X-Attr-Removed'], 'var=1;', + 'attribute removed event') + self.assertEqual(headers['X-Attr-Replaced'], '', + 'attribute replaced event') + + headers = self.get(url='/test3?var1=1&var2=2')['headers'] + + self.assertEqual(headers['X-Request-Initialized'], '/test3', + 'request initialized event') + self.assertEqual(headers['X-Request-Destroyed'], '/test2', + 'request destroyed event') + self.assertEqual(headers['X-Attr-Added'], 'var=1;', + 'attribute added event') + self.assertEqual(headers['X-Attr-Removed'], 'var=2;', + 'attribute removed event') + self.assertEqual(headers['X-Attr-Replaced'], 'var=1;', + 'attribute replaced event') + + headers = self.get(url='/test4?var1=1&var2=2&var3=3')['headers'] + + self.assertEqual(headers['X-Request-Initialized'], '/test4', + 'request initialized event') + self.assertEqual(headers['X-Request-Destroyed'], '/test3', + 'request destroyed event') + self.assertEqual(headers['X-Attr-Added'], 'var=1;', + 'attribute added event') + self.assertEqual(headers['X-Attr-Removed'], '', + 'attribute removed event') + self.assertEqual(headers['X-Attr-Replaced'], 'var=1;var=2;', + 'attribute replaced event') + + def test_java_application_request_uri_forward(self): + self.load('forward') + + resp = self.get(url='/fwd?uri=%2Fdata%2Ftest%3Furi%3Dnew_uri%26a%3D2%26b%3D3&a=1&c=4') + headers = resp['headers'] + + self.assertEqual(headers['X-REQUEST-Id'], 'fwd', + 'initial request servlet mapping') + self.assertEqual(headers['X-Forward-To'], '/data/test?uri=new_uri&a=2&b=3', + 'forwarding triggered') + self.assertEqual(headers['X-REQUEST-Param-uri'], '/data/test?uri=new_uri&a=2&b=3', + 'original uri parameter') + self.assertEqual(headers['X-REQUEST-Param-a'], '1', + 'original a parameter') + self.assertEqual(headers['X-REQUEST-Param-c'], '4', + 'original c parameter') + + self.assertEqual(headers['X-FORWARD-Id'], 'data', + 'forward request servlet mapping') + self.assertEqual(headers['X-FORWARD-Request-URI'], '/data/test', + 'forward request uri') + self.assertEqual(headers['X-FORWARD-Servlet-Path'], '/data', + 'forward request servlet path') + self.assertEqual(headers['X-FORWARD-Path-Info'], '/test', + 'forward request path info') + self.assertEqual(headers['X-FORWARD-Query-String'], 'uri=new_uri&a=2&b=3', + 'forward request query string') + self.assertEqual(headers['X-FORWARD-Param-uri'], 'new_uri,/data/test?uri=new_uri&a=2&b=3', + 'forward uri parameter') + self.assertEqual(headers['X-FORWARD-Param-a'], '2,1', + 'forward a parameter') + self.assertEqual(headers['X-FORWARD-Param-b'], '3', + 'forward b parameter') + self.assertEqual(headers['X-FORWARD-Param-c'], '4', + 'forward c parameter') + + self.assertEqual(headers['X-javax.servlet.forward.request_uri'], '/fwd', + 'original request uri') + self.assertEqual(headers['X-javax.servlet.forward.context_path'], '', + 'original request context path') + self.assertEqual(headers['X-javax.servlet.forward.servlet_path'], '/fwd', + 'original request servlet path') + self.assertEqual(headers['X-javax.servlet.forward.path_info'], 'null', + 'original request path info') + self.assertEqual(headers['X-javax.servlet.forward.query_string'], 'uri=%2Fdata%2Ftest%3Furi%3Dnew_uri%26a%3D2%26b%3D3&a=1&c=4', + 'original request query') + + self.assertEqual('Before forwarding' in resp['body'], False, + 'discarded data added before forward() call') + self.assertEqual('X-After-Forwarding' in headers, False, + 'cannot add headers after forward() call') + self.assertEqual('After forwarding' in resp['body'], False, + 'cannot add data after forward() call') + + def test_java_application_named_dispatcher_forward(self): + self.load('forward') + + resp = self.get(url='/fwd?disp=name&uri=data') + headers = resp['headers'] + + self.assertEqual(headers['X-REQUEST-Id'], 'fwd', + 'initial request servlet mapping') + self.assertEqual(headers['X-Forward-To'], 'data', + 'forwarding triggered') + + self.assertEqual(headers['X-FORWARD-Id'], 'data', + 'forward request servlet mapping') + self.assertEqual(headers['X-FORWARD-Request-URI'], '/fwd', + 'forward request uri') + self.assertEqual(headers['X-FORWARD-Servlet-Path'], '/fwd', + 'forward request servlet path') + self.assertEqual(headers['X-FORWARD-Path-Info'], 'null', + 'forward request path info') + self.assertEqual(headers['X-FORWARD-Query-String'], 'disp=name&uri=data', + 'forward request query string') + + self.assertEqual(headers['X-javax.servlet.forward.request_uri'], 'null', + 'original request uri') + self.assertEqual(headers['X-javax.servlet.forward.context_path'], 'null', + 'original request context path') + self.assertEqual(headers['X-javax.servlet.forward.servlet_path'], 'null', + 'original request servlet path') + self.assertEqual(headers['X-javax.servlet.forward.path_info'], 'null', + 'original request path info') + self.assertEqual(headers['X-javax.servlet.forward.query_string'], 'null', + 'original request query') + + self.assertEqual('Before forwarding' in resp['body'], False, + 'discarded data added before forward() call') + self.assertEqual('X-After-Forwarding' in headers, False, + 'cannot add headers after forward() call') + self.assertEqual('After forwarding' in resp['body'], False, + 'cannot add data after forward() call') + + def test_java_application_request_uri_include(self): + self.load('include') + + resp = self.get(url='/inc?uri=/data/test') + headers = resp['headers'] + body = resp['body'] + + self.assertEqual(headers['X-REQUEST-Id'], 'inc', + 'initial request servlet mapping') + self.assertEqual(headers['X-Include'], '/data/test', + 'including triggered') + + self.assertEqual('X-INCLUDE-Id' in headers, False, + 'unable to add headers in include request') + + self.assertEqual('javax.servlet.include.request_uri: /data/test' in body, + True, 'include request uri') +# self.assertEqual('javax.servlet.include.context_path: ' in body, +# 'include request context path') + self.assertEqual('javax.servlet.include.servlet_path: /data' in body, + True, 'include request servlet path') + self.assertEqual('javax.servlet.include.path_info: /test' in body, + True, 'include request path info') + self.assertEqual('javax.servlet.include.query_string: null' in body, + True, 'include request query') + + self.assertEqual('Before include' in body, True, + 'preserve data added before include() call') + self.assertEqual(headers['X-After-Include'], 'you-should-see-this', + 'add headers after include() call') + self.assertEqual('After include' in body, True, + 'add data after include() call') + + def test_java_application_named_dispatcher_include(self): + self.load('include') + + resp = self.get(url='/inc?disp=name&uri=data') + headers = resp['headers'] + body = resp['body'] + + self.assertEqual(headers['X-REQUEST-Id'], 'inc', + 'initial request servlet mapping') + self.assertEqual(headers['X-Include'], 'data', + 'including triggered') + + self.assertEqual('X-INCLUDE-Id' in headers, False, + 'unable to add headers in include request') + + self.assertEqual('javax.servlet.include.request_uri: null' in body, + True, 'include request uri') +# self.assertEqual('javax.servlet.include.context_path: null' in body, +# 'include request context path') + self.assertEqual('javax.servlet.include.servlet_path: null' in body, + True, 'include request servlet path') + self.assertEqual('javax.servlet.include.path_info: null' in body, + True, 'include request path info') + self.assertEqual('javax.servlet.include.query_string: null' in body, + True, 'include request query') + + self.assertEqual('Before include' in body, True, + 'preserve data added before include() call') + self.assertEqual(headers['X-After-Include'], 'you-should-see-this', + 'add headers after include() call') + self.assertEqual('After include' in body, True, + 'add data after include() call') + + def test_java_application_path_translation(self): + self.load('path_translation') + + headers = self.get(url='/pt/test?path=/')['headers'] + + self.assertEqual(headers['X-Servlet-Path'], '/pt', + 'matched servlet path') + self.assertEqual(headers['X-Path-Info'], '/test', + 'the rest of the path') + self.assertEqual(headers['X-Path-Translated'], + headers['X-Real-Path'] + headers['X-Path-Info'], + 'translated path is the app root + path info') + self.assertEqual( + headers['X-Resource-Paths'].endswith('/WEB-INF/, /index.html]'), + True, 'app root directory content') + self.assertEqual(headers['X-Resource-As-Stream'], 'null', + 'no resource stream for root path') + + headers = self.get(url='/test?path=/none')['headers'] + + self.assertEqual(headers['X-Servlet-Path'], '/test', + 'matched whole path') + self.assertEqual(headers['X-Path-Info'], 'null', + 'the rest of the path is null, whole path matched') + self.assertEqual(headers['X-Path-Translated'], 'null', + 'translated path is null because path info is null') + self.assertEqual(headers['X-Real-Path'].endswith('/none'), True, + 'read path is not null') + self.assertEqual(headers['X-Resource-Paths'], 'null', + 'no resource found') + self.assertEqual(headers['X-Resource-As-Stream'], 'null', + 'no resource stream') + + def test_java_application_query_string(self): + self.load('query_string') + + self.assertEqual(self.get(url='/?a=b')['headers']['X-Query-String'], + 'a=b', 'query string') + + def test_java_application_query_empty(self): + self.load('query_string') + + self.assertEqual(self.get(url='/?')['headers']['X-Query-String'], '', + 'query string empty') + + def test_java_application_query_absent(self): + self.load('query_string') + + self.assertEqual(self.get()['headers']['X-Query-String'], 'null', + 'query string absent') + + def test_java_application_empty(self): + self.load('empty') + + self.assertEqual(self.get()['status'], 200, 'empty') + + def test_java_application_keepalive_body(self): + self.load('mirror') + + (resp, sock) = self.post(headers={ + 'Connection': 'keep-alive', + 'Content-Type': 'text/html', + 'Host': 'localhost' + }, start=True, body='0123456789' * 500) + + self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') + + resp = self.post(headers={ + 'Connection': 'close', + 'Content-Type': 'text/html', + 'Host': 'localhost' + }, sock=sock, body='0123456789') + + self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') + + def test_java_application_http_10(self): + self.load('empty') + + self.assertEqual(self.get(http_10=True)['status'], 200, 'HTTP 1.0') + + def test_java_application_no_method(self): + self.load('empty') + + self.assertEqual(self.post()['status'], 405, 'no method') + + def test_java_application_get_header(self): + self.load('get_header') + + self.assertEqual(self.get(headers={ + 'X-Header': 'blah', + 'Content-Type': 'text/html', + 'Host': 'localhost' + })['headers']['X-Reply'], 'blah', 'get header') + + def test_java_application_get_header_empty(self): + self.load('get_header') + + self.assertNotIn('X-Reply', self.get()['headers'], 'get header empty') + + def test_java_application_get_headers(self): + self.load('get_headers') + + headers = self.get(headers={ + 'X-Header': ['blah', 'blah'], + 'Content-Type': 'text/html', + 'Host': 'localhost' + })['headers'] + + self.assertEqual(headers['X-Reply-0'], 'blah', 'get headers') + self.assertEqual(headers['X-Reply-1'], 'blah', 'get headers 2') + + def test_java_application_get_headers_empty(self): + self.load('get_headers') + + self.assertNotIn('X-Reply-0', self.get()['headers'], + 'get headers empty') + + def test_java_application_get_header_names(self): + self.load('get_header_names') + + headers = self.get()['headers'] + + self.assertRegex(headers['X-Reply-0'], r'(?:Host|Connection)', + 'get header names') + self.assertRegex(headers['X-Reply-1'], r'(?:Host|Connection)', + 'get header names 2') + self.assertNotEqual(headers['X-Reply-0'], headers['X-Reply-1'], + 'get header names not equal') + + def test_java_application_get_header_names_empty(self): + self.load('get_header_names') + + self.assertNotIn('X-Reply-0', self.get(headers={})['headers'], + 'get header names empty') + + def test_java_application_header_int(self): + self.load('header_int') + + headers = self.get(headers={ + 'X-Header': '2', + 'Content-Type': 'text/html', + 'Host': 'localhost' + })['headers'] + + self.assertEqual(headers['X-Set-Int'], '1', 'set int header') + self.assertEqual(headers['X-Get-Int'], '2', 'get int header') + + def test_java_application_header_date(self): + self.load('header_date') + + date = 'Fri, 15 Mar 2019 14:45:34 GMT' + + headers = self.get(headers={ + 'X-Header': date, + 'Content-Type': 'text/html', + 'Host': 'localhost' + })['headers'] + + self.assertEqual(headers['X-Set-Date'], 'Thu, 01 Jan 1970 00:00:01 GMT', + 'set date header') + self.assertEqual(headers['X-Get-Date'], date, 'get date header') + +if __name__ == '__main__': + TestUnitJavaApplication.main() diff --git a/test/unit.py b/test/unit.py index 7a51eb20..a0de82e7 100644 --- a/test/unit.py +++ b/test/unit.py @@ -600,6 +600,72 @@ class TestUnitApplicationNode(TestUnitApplicationProto): } }) +class TestUnitApplicationJava(TestUnitApplicationProto): + def load(self, script, name='app'): + + app_path = self.testdir + '/java' + web_inf_path = app_path + '/WEB-INF/' + classes_path = web_inf_path + 'classes/' + + script_path = self.current_dir + '/java/' + script + '/' + + if not os.path.isdir(app_path): + os.makedirs(app_path) + + src = [] + + for f in os.listdir(script_path): + if f.endswith('.java'): + src.append(script_path + f) + continue + + if f.startswith('.') or f == 'Makefile': + continue + + if os.path.isdir(script_path + f): + if f == 'WEB-INF': + continue + + shutil.copytree(script_path + f, app_path + '/' + f) + continue + + if f == 'web.xml': + if not os.path.isdir(web_inf_path): + os.makedirs(web_inf_path) + + shutil.copy2(script_path + f, web_inf_path) + else: + shutil.copy2(script_path + f, app_path) + + if src: + if not os.path.isdir(classes_path): + os.makedirs(classes_path) + + javac = ['javac', '-encoding', 'utf-8', '-d', classes_path, + '-classpath', + self.pardir + '/build/tomcat-servlet-api-9.0.13.jar'] + javac.extend(src) + + process = subprocess.Popen(javac) + process.communicate() + + self.conf({ + "listeners": { + "*:7080": { + "application": script + } + }, + "applications": { + script: { + "unit_jars": self.pardir + '/build', + "type": "java", + "processes": { "spare": 0 }, + "working_directory": script_path, + "webapp": app_path + } + } + }) + class TestUnitApplicationPerl(TestUnitApplicationProto): def load(self, script, name='psgi.pl'): self.conf({ -- cgit From afda14d1f23cc49eee5939c25278eec3501d8631 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 28 Feb 2019 18:03:21 +0300 Subject: Preserving message 'share' field when pushing to queue. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As far as I understand, this field is important to control the number of buffers send in a single write attempt. Furthermore, having uninitialized field is always bad. This closes #204 issue on GitHub. Thanks to 洪志道 (Hong Zhi Dao). --- src/nxt_port_socket.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nxt_port_socket.c b/src/nxt_port_socket.c index aed3a292..01fe2dab 100644 --- a/src/nxt_port_socket.c +++ b/src/nxt_port_socket.c @@ -179,6 +179,7 @@ nxt_port_msg_create(nxt_task_t *task, nxt_port_send_msg_t *m) msg->link.prev = NULL; msg->buf = m->buf; + msg->share = m->share; msg->fd = m->fd; msg->close_fd = m->close_fd; msg->port_msg = m->port_msg; -- cgit From 834e8ca576868c5ecd104bd5ebec9968c7f80a0b Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Thu, 28 Feb 2019 18:04:11 +0300 Subject: Fixed timer and event race condition. When idle timeout occurs at the same time as a request comes in, the timer handler closes connection while the read event triggers request processing, and this eventually leads to segmentation fault. --- src/nxt_conn.h | 3 ++- src/nxt_conn_read.c | 7 ++++--- src/nxt_conn_write.c | 5 +++-- src/nxt_event_conn_job_sendfile.c | 6 ++++-- src/nxt_h1proto.c | 4 ++++ 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/nxt_conn.h b/src/nxt_conn.h index d8b48694..7284808b 100644 --- a/src/nxt_conn.h +++ b/src/nxt_conn.h @@ -157,7 +157,8 @@ struct nxt_conn_s { nxt_sockaddr_t *local; const char *action; - uint8_t blocked; /* 1 bit */ + uint8_t block_read; /* 1 bit */ + uint8_t block_write; /* 1 bit */ uint8_t delayed; /* 1 bit */ #define NXT_CONN_SENDFILE_OFF 0 diff --git a/src/nxt_conn_read.c b/src/nxt_conn_read.c index 8228326b..83969b31 100644 --- a/src/nxt_conn_read.c +++ b/src/nxt_conn_read.c @@ -45,10 +45,11 @@ nxt_conn_io_read(nxt_task_t *task, void *obj, void *data) c = obj; - nxt_debug(task, "conn read fd:%d rdy:%d cl:%d", - c->socket.fd, c->socket.read_ready, c->socket.closed); + nxt_debug(task, "conn read fd:%d rdy:%d cl:%d er:%d bl:%d", + c->socket.fd, c->socket.read_ready, c->socket.closed, + c->socket.error, c->block_read); - if (c->socket.error != 0) { + if (c->socket.error != 0 || c->block_read) { return; } diff --git a/src/nxt_conn_write.c b/src/nxt_conn_write.c index 80d6f5cf..298d8f75 100644 --- a/src/nxt_conn_write.c +++ b/src/nxt_conn_write.c @@ -22,9 +22,10 @@ nxt_conn_io_write(nxt_task_t *task, void *obj, void *data) c = obj; - nxt_debug(task, "conn write fd:%d", c->socket.fd); + nxt_debug(task, "conn write fd:%d er:%d bl:%d", + c->socket.fd, c->socket.error, c->block_write); - if (c->socket.error != 0) { + if (c->socket.error != 0 || c->block_write) { goto error; } diff --git a/src/nxt_event_conn_job_sendfile.c b/src/nxt_event_conn_job_sendfile.c index 2ca6e421..0f6f9353 100644 --- a/src/nxt_event_conn_job_sendfile.c +++ b/src/nxt_event_conn_job_sendfile.c @@ -80,7 +80,8 @@ nxt_event_conn_job_sendfile_start(nxt_task_t *task, void *obj, void *data) c->write = NULL; jbs->ready_handler = nxt_event_conn_job_sendfile_return; - c->blocked = 1; + c->block_read = 1; + c->block_write = 1; nxt_job_start(task, &jbs->job, nxt_event_conn_job_sendfile_handler); return; @@ -170,7 +171,8 @@ nxt_event_conn_job_sendfile_return(nxt_task_t *task, void *obj, void *data) jbs = obj; c = data; - c->blocked = 0; + c->block_read = 0; + c->block_write = 0; sent = jbs->sent; c->sent += sent; diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c index b5b5d6e6..07e3c7bc 100644 --- a/src/nxt_h1proto.c +++ b/src/nxt_h1proto.c @@ -1220,6 +1220,7 @@ nxt_h1p_conn_request_timeout(nxt_task_t *task, void *obj, void *data) nxt_debug(task, "h1p conn request timeout"); c = nxt_read_timer_conn(timer); + c->block_read = 1; /* * Disable SO_LINGER off during socket closing * to send "408 Request Timeout" error response. @@ -1250,6 +1251,7 @@ nxt_h1p_conn_request_send_timeout(nxt_task_t *task, void *obj, void *data) nxt_debug(task, "h1p conn request send timeout"); c = nxt_write_timer_conn(timer); + c->block_write = 1; h1p = c->socket.data; nxt_h1p_request_error(task, h1p, h1p->request); @@ -1464,6 +1466,7 @@ nxt_h1p_idle_timeout(nxt_task_t *task, void *obj, void *data) nxt_debug(task, "h1p idle timeout"); c = nxt_read_timer_conn(timer); + c->block_read = 1; nxt_h1p_idle_response(task, c); } @@ -1559,6 +1562,7 @@ nxt_h1p_idle_response_timeout(nxt_task_t *task, void *obj, void *data) nxt_debug(task, "h1p idle timeout response timeout"); c = nxt_read_timer_conn(timer); + c->block_write = 1; nxt_h1p_shutdown(task, c); } -- cgit From 7ce6f0597543baee4275e8d66567d08f2ddaf48b Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Thu, 28 Feb 2019 18:22:15 +0300 Subject: Packages: added unit-jsc (Java Servlet Container) family. --- pkg/deb/Makefile | 28 +- pkg/deb/Makefile.jsc-common | 28 + pkg/deb/Makefile.jsc10 | 53 ++ pkg/deb/Makefile.jsc11 | 53 ++ pkg/deb/Makefile.jsc8 | 53 ++ pkg/deb/debian.module/control-noarch.in | 23 + pkg/deb/debian.module/copyright.unit-jsc-common | 868 +++++++++++++++++++++ pkg/deb/debian.module/rules-noarch.in | 107 +++ pkg/deb/debian.module/rules.in | 7 +- pkg/deb/debian.module/unit.example-jsc-app | 12 + pkg/deb/debian.module/unit.example-jsc10-config | 15 + pkg/deb/debian.module/unit.example-jsc11-config | 15 + pkg/deb/debian.module/unit.example-jsc8-config | 15 + pkg/deb/debian.module/unit.example-jsc9-config | 15 + pkg/deb/debian/rules.in | 4 + pkg/rpm/Makefile | 39 +- pkg/rpm/Makefile.jsc-common | 41 + pkg/rpm/Makefile.jsc11 | 65 ++ pkg/rpm/Makefile.jsc8 | 65 ++ pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc-common | 868 +++++++++++++++++++++ pkg/rpm/rpmbuild/SOURCES/unit.example-jsc-app | 12 + pkg/rpm/rpmbuild/SOURCES/unit.example-jsc11-config | 15 + pkg/rpm/rpmbuild/SOURCES/unit.example-jsc8-config | 15 + pkg/rpm/unit.module.spec.in | 6 + 24 files changed, 2408 insertions(+), 14 deletions(-) create mode 100644 pkg/deb/Makefile.jsc-common create mode 100644 pkg/deb/Makefile.jsc10 create mode 100644 pkg/deb/Makefile.jsc11 create mode 100644 pkg/deb/Makefile.jsc8 create mode 100644 pkg/deb/debian.module/control-noarch.in create mode 100644 pkg/deb/debian.module/copyright.unit-jsc-common create mode 100644 pkg/deb/debian.module/rules-noarch.in create mode 100644 pkg/deb/debian.module/unit.example-jsc-app create mode 100644 pkg/deb/debian.module/unit.example-jsc10-config create mode 100644 pkg/deb/debian.module/unit.example-jsc11-config create mode 100644 pkg/deb/debian.module/unit.example-jsc8-config create mode 100644 pkg/deb/debian.module/unit.example-jsc9-config create mode 100644 pkg/rpm/Makefile.jsc-common create mode 100644 pkg/rpm/Makefile.jsc11 create mode 100644 pkg/rpm/Makefile.jsc8 create mode 100644 pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc-common create mode 100644 pkg/rpm/rpmbuild/SOURCES/unit.example-jsc-app create mode 100644 pkg/rpm/rpmbuild/SOURCES/unit.example-jsc11-config create mode 100644 pkg/rpm/rpmbuild/SOURCES/unit.example-jsc8-config diff --git a/pkg/deb/Makefile b/pkg/deb/Makefile index ffd9aa2e..3d747402 100644 --- a/pkg/deb/Makefile +++ b/pkg/deb/Makefile @@ -26,6 +26,9 @@ include Makefile.go19 include Makefile.go110 include Makefile.perl include Makefile.ruby +include Makefile.jsc-common +include Makefile.jsc8 +include Makefile.jsc11 endif # Ubuntu 18.04 @@ -37,6 +40,9 @@ include Makefile.go19 include Makefile.go110 include Makefile.perl include Makefile.ruby +include Makefile.jsc-common +include Makefile.jsc8 +include Makefile.jsc10 endif # Ubuntu 17.10 @@ -69,6 +75,8 @@ include Makefile.python35 include Makefile.go include Makefile.perl include Makefile.ruby +include Makefile.jsc-common +include Makefile.jsc8 endif # Ubuntu 14.04 @@ -87,6 +95,8 @@ include Makefile.go17 include Makefile.go18 include Makefile.perl include Makefile.ruby +include Makefile.jsc-common +include Makefile.jsc8 endif # Debian 8 @@ -200,7 +210,11 @@ else -e "s#%%CODENAME%%#$(CODENAME)#g" \ > $@/$(SRCDIR)/debian/changelog endif - cp debian/copyright debuild-$*/$(SRCDIR)/debian/ + if [ -f debian.module/copyright.unit-$(MODULE_SUFFIX_$*) ]; then \ + cp debian.module/copyright.unit-$(MODULE_SUFFIX_$*) debuild-$*/$(SRCDIR)/debian/copyright ; \ + else \ + cp debian/copyright debuild-$*/$(SRCDIR)/debian/ ; \ + fi @{ \ set -e ; \ for src in $(MODULE_SOURCES_$*); do \ @@ -209,8 +223,9 @@ endif definitions=`echo "$$MODULE_DEFINITIONS_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \ prebuild=`echo "$$MODULE_PREBUILD_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \ preinstall=`echo "$$MODULE_PREINSTALL_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \ + postinstall=`echo "$$MODULE_POSTINSTALL_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \ post=`echo "$$MODULE_POST_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \ - cat debian.module/control.in | sed \ + cat debian.module/$(if $(MODULE_NOARCH_$*),control-noarch.in,control.in) | sed \ -e "s#%%NAME%%#unit-$(MODULE_SUFFIX_$*)#g" \ -e "s#%%SUMMARY%%#$(MODULE_SUMMARY_$*)#g" \ -e "s#%%CODENAME%%#$(CODENAME)#g" \ @@ -221,7 +236,7 @@ endif -e "s#%%MODULE_BUILD_DEPENDS%%#$(MODULE_BUILD_DEPENDS_$*)#g" \ -e "s#%%MODULE_DEPENDS%%#$(MODULE_DEPENDS_$*)#g" \ > $@/$(SRCDIR)/debian/control ; \ - cat debian.module/rules.in | sed \ + cat debian.module/$(if $(MODULE_NOARCH_$*),rules-noarch.in,rules.in) | sed \ -e "s#%%NAME%%#unit-$(MODULE_SUFFIX_$*)#g" \ -e "s#%%CODENAME%%#$(CODENAME)#g" \ -e "s#%%UNIT_VERSION%%#$(VERSION)#g" \ @@ -233,6 +248,7 @@ endif -e "s#%%MODULE_DEFINITIONS%%#$${definitions}#g" \ -e "s#%%MODULE_PREBUILD%%#$${prebuild}#g" \ -e "s#%%MODULE_PREINSTALL%%#$${preinstall}#g" \ + -e "s#%%MODULE_POSTINSTALL%%#$${postinstall}#g" \ > $@/$(SRCDIR)/debian/rules ; \ cat debian.module/preinst.in | sed \ -e "s#%%MODULE_POST%%#$$post#g" \ @@ -249,8 +265,9 @@ unit-%: check-build-depends-% | debuild-% test: unit modules @{ \ - for so in `find debuild-*/unit-$(VERSION)/debian/build-unit/ -type f -name "*.so"` ; do \ + for so in `find debuild-*/unit-$(VERSION)/debian/build-unit/ -type f \( -name "*.so" -o -name "*.jar" \)`; do \ soname=`basename $${so}` ; \ + test "$${soname}" = "java.unit.so" && continue ; \ test -h debuild/unit-$(VERSION)/debian/build-unit/build/$${soname} || \ ln -fs `pwd`/$${so} debuild/unit-$(VERSION)/debian/build-unit/build/$${soname} ; \ done ; \ @@ -259,8 +276,9 @@ test: unit modules test-debug: unit modules @{ \ - for so in `find debuild-*/unit-$(VERSION)/debian/build-unit-debug/ -type f -name "*.so"` ; do \ + for so in `find debuild-*/unit-$(VERSION)/debian/build-unit-debug/ -type f \( -name "*.so" -o -name "*.jar" \)`; do \ soname=`basename $${so}` ; \ + test "$${soname}" = "java.unit.so" && continue ; \ test -h debuild/unit-$(VERSION)/debian/build-unit-debug/build/$${soname} || \ ln -fs `pwd`/$${so} debuild/unit-$(VERSION)/debian/build-unit-debug/build/$${soname} ; \ done ; \ diff --git a/pkg/deb/Makefile.jsc-common b/pkg/deb/Makefile.jsc-common new file mode 100644 index 00000000..902d60df --- /dev/null +++ b/pkg/deb/Makefile.jsc-common @@ -0,0 +1,28 @@ +MODULES+= jsc_common +MODULE_SUFFIX_jsc_common= jsc-common + +MODULE_SUMMARY_jsc_common= Java shared packages for NGINX Unit + +MODULE_VERSION_jsc_common= $(VERSION) +MODULE_RELEASE_jsc_common= 1 + +MODULE_CONFARGS_jsc_common= java --home=/usr/lib/jvm/java-8-openjdk-$$\(DEB_HOST_ARCH\) --jars=/usr/share/unit-jsc-common/ +MODULE_MAKEARGS_jsc_common= java +MODULE_INSTARGS_jsc_common= java-shared-install + +BUILD_DEPENDS_jsc_common= openjdk-8-jdk-headless openjdk-8-jre-headless +BUILD_DEPENDS+= $(BUILD_DEPENDS_jsc_common) + +MODULE_NOARCH_jsc_common= true + +define MODULE_POST_jsc_common +cat < +Build-Depends: debhelper (>= 9), + linux-libc-dev%%MODULE_BUILD_DEPENDS%% +Standards-Version: 3.9.5 +Homepage: https://unit.nginx.org + +Package: %%NAME%% +Section: admin +Architecture: all +Depends: lsb-base, + ${misc:Depends}, + unit (= %%UNIT_VERSION%%-%%UNIT_RELEASE%%~%%CODENAME%%)%%MODULE_DEPENDS%% +Description: %%SUMMARY%% + NGINX Unit is a runtime and delivery environment for modern distributed + applications. It runs the application code in multiple languages + (PHP, Python, Go, etc.), and tightly couples it with traffic delivery + in and out of the application. Take this application server and proxy + directly in the cloud / container environments and fully control your app + dynamically via an API. + This package contains %%SUMMARY%%. diff --git a/pkg/deb/debian.module/copyright.unit-jsc-common b/pkg/deb/debian.module/copyright.unit-jsc-common new file mode 100644 index 00000000..08907713 --- /dev/null +++ b/pkg/deb/debian.module/copyright.unit-jsc-common @@ -0,0 +1,868 @@ + +This package includes the following software components distributed +under corresponding licenses: + + * classgraph-4.4.11.jar: MIT + * ecj-3.13.102.jar: EPL 1.0 + * jetty-http-9.4.12.v20180830.jar: Apache 2.0 + EPL 1.0 + * jetty-server-9.4.12.v20180830.jar: Apache 2.0 + EPL 1.0 + * jetty-util-9.4.12.v20180830.jar: Apache 2.0 + EPL 1.0 + * tomcat-api-9.0.13.jar: Apache 2.0 + * tomcat-el-api-9.0.13.jar: Apache 2.0 + * tomcat-jasper-9.0.13.jar: Apache 2.0 + * tomcat-jasper-el-9.0.13.jar: Apache 2.0 + * tomcat-jsp-api-9.0.13.jar: Apache 2.0 + * tomcat-juli-9.0.13.jar: Apache 2.0 + * tomcat-servlet-api-9.0.13.jar: Apache 2.0 + CDDL 1.0 + * tomcat-util-9.0.13.jar: Apache 2.0 + * tomcat-util-scan-9.0.13.jar: Apache 2.0 + +Licenses could be found by the following links and below in this file: + + - MIT (The MIT License): + http://www.opensource.org/licenses/MIT + + - EPL (Eclipse Public License) v1.0: + https://www.eclipse.org/legal/epl-v10.html + + - Apache 2.0: + http://www.apache.org/licenses/LICENSE-2.0.html + + - CDDL (Common Development and Distribution License) v1.0: + https://opensource.org/licenses/CDDL-1.0 + + +====[ MIT license - begin ]=================================================== + +The MIT License (MIT) + +Copyright (c) 2015 Luke Hutchison + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +====[ MIT license - end ]===================================================== + + +====[ EPL license - begin ]=================================================== + +Eclipse Public License - v1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation +distributed under this Agreement, and + +b) in the case of each subsequent Contributor: + +i) changes to the Program, and + +ii) additions to the Program; + +where such changes and/or additions to the Program originate from and are +distributed by that particular Contributor. A Contribution 'originates' from a +Contributor if it was added to the Program by such Contributor itself or +anyone acting on such Contributor's behalf. Contributions do not include +additions to the Program which: (i) are separate modules of software +distributed in conjunction with the Program under their own license agreement, +and (ii) are not derivative works of the Program. "Contributor" means any +person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + +a) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free copyright license to +reproduce, prepare derivative works of, publicly display, publicly perform, +distribute and sublicense the Contribution of such Contributor, if any, and +such derivative works, in source code and object code form. + +b) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free patent license under +Licensed Patents to make, use, sell, offer to sell, import and otherwise +transfer the Contribution of such Contributor, if any, in source code and +object code form. This patent license shall apply to the combination of the +Contribution and the Program if, at the time the Contribution is added by the +Contributor, such addition of the Contribution causes such combination to be +covered by the Licensed Patents. The patent license shall not apply to any +other combinations which include the Contribution. No hardware per se is +licensed hereunder. + +c) Recipient understands that although each Contributor grants the licenses to +its Contributions set forth herein, no assurances are provided by any +Contributor that the Program does not infringe the patent or other +intellectual property rights of any other entity. Each Contributor disclaims +any liability to Recipient for claims brought by any other entity based on +infringement of intellectual property rights or otherwise. As a condition to +exercising the rights and licenses granted hereunder, each Recipient hereby +assumes sole responsibility to secure any other intellectual property rights +needed, if any. For example, if a third party patent license is required to +allow Recipient to distribute the Program, it is Recipient's responsibility to +acquire that license before distributing the Program. + +d) Each Contributor represents that to its knowledge it has sufficient +copyright rights in its Contribution, if any, to grant the copyright license +set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + +a) it complies with the terms and conditions of this Agreement; and + +b) its license agreement: + +i) effectively disclaims on behalf of all Contributors all warranties and +conditions, express and implied, including warranties or conditions of title +and non-infringement, and implied warranties or conditions of merchantability +and fitness for a particular purpose; + +ii) effectively excludes on behalf of all Contributors all liability for +damages, including direct, indirect, special, incidental and consequential +damages, such as lost profits; + +iii) states that any provisions which differ from this Agreement are offered +by that Contributor alone and not by any other party; and + +iv) states that source code for the Program is available from such +Contributor, and informs licensees how to obtain it in a reasonable manner on +or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + +a) it must be made available under this Agreement; and + +b) a copy of this Agreement must be included with each copy of the Program. + +Contributors may not remove or alter any copyright notices contained within +the Program. + +Each Contributor must identify itself as the originator of its Contribution, +if any, in a manner that reasonably allows subsequent Recipients to identify +the originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, +if a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits and +other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such Commercial +Contributor in connection with its distribution of the Program in a commercial +product offering. The obligations in this section do not apply to any claims +or Losses relating to any actual or alleged intellectual property +infringement. In order to qualify, an Indemnified Contributor must: a) +promptly notify the Commercial Contributor in writing of such claim, and b) +allow the Commercial Contributor to control, and cooperate with the Commercial +Contributor in, the defense and any related settlement negotiations. The +Indemnified Contributor may participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If +that Commercial Contributor then makes performance claims, or offers +warranties related to Product X, those performance claims and warranties are +such Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using +and distributing the Program and assumes all risks associated with its +exercise of rights under this Agreement , including but not limited to the +risks and costs of program errors, compliance with applicable laws, damage to +or loss of data, programs or equipment, and unavailability or interruption of +operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION +LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of the +remainder of the terms of this Agreement, and without further action by the +parties hereto, such provision shall be reformed to the minimum extent +necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted +under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to +time. No one other than the Agreement Steward has the right to modify this +Agreement. The Eclipse Foundation is the initial Agreement Steward. The +Eclipse Foundation may assign the responsibility to serve as the Agreement +Steward to a suitable separate entity. Each new version of the Agreement will +be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new version of the +Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly +stated in Sections 2(a) and 2(b) above, Recipient receives no rights or +licenses to the intellectual property of any Contributor under this Agreement, +whether expressly, by implication, estoppel or otherwise. All rights in the +Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial +in any resulting litigation. + +====[ EPL license - begin ]=================================================== + + +====[ Apache 2.0 license - begin ]============================================ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +====[ Apache 2.0 license - end ]============================================== + + +====[ CDDL v1.0 license - start ]============================================= + +COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0 + +1. Definitions. + + 1.1. "Contributor" means each individual or entity that creates + or contributes to the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Software, prior Modifications used by a Contributor (if any), + and the Modifications made by that particular Contributor. + + 1.3. "Covered Software" means (a) the Original Software, or (b) + Modifications, or (c) the combination of files containing + Original Software with files containing Modifications, in + each case including portions thereof. + + 1.4. "Executable" means the Covered Software in any form other + than Source Code. + + 1.5. "Initial Developer" means the individual or entity that first + makes Original Software available under this License. + + 1.6. "Larger Work" means a work which combines Covered Software or + portions thereof with code not governed by the terms of this + License. + + 1.7. "License" means this document. + + 1.8. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed + herein. + + 1.9. "Modifications" means the Source Code and Executable form of + any of the following: + + A. Any file that results from an addition to, deletion from or + modification of the contents of a file containing Original + Software or previous Modifications; + + B. Any new file that contains any part of the Original + Software or previous Modifications; or + + C. Any new file that is contributed or otherwise made + available under the terms of this License. + + 1.10. "Original Software" means the Source Code and Executable + form of computer software code that is originally released + under this License. + + 1.11. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, + process, and apparatus claims, in any patent Licensable by + grantor. + + 1.12. "Source Code" means (a) the common form of computer software + code in which modifications are made and (b) associated + documentation included in or with such code. + + 1.13. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms + of, this License. For legal entities, "You" includes any + entity which controls, is controlled by, or is under common + control with You. For purposes of this definition, + "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty + percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants. + + 2.1. The Initial Developer Grant. + + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, the Initial + Developer hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer, to use, + reproduce, modify, display, perform, sublicense and + distribute the Original Software (or portions thereof), + with or without Modifications, and/or as part of a Larger + Work; and + + (b) under Patent Claims infringed by the making, using or + selling of Original Software, to make, have made, use, + practice, sell, and offer for sale, and/or otherwise + dispose of the Original Software (or portions thereof). + + (c) The licenses granted in Sections 2.1(a) and (b) are + effective on the date Initial Developer first distributes + or otherwise makes the Original Software available to a + third party under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: (1) for code that You delete from the Original + Software, or (2) for infringements caused by: (i) the + modification of the Original Software, or (ii) the + combination of the Original Software with other software + or devices. + + 2.2. Contributor Grant. + + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, each + Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor to use, reproduce, + modify, display, perform, sublicense and distribute the + Modifications created by such Contributor (or portions + thereof), either on an unmodified basis, with other + Modifications, as Covered Software and/or as part of a + Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either + alone and/or in combination with its Contributor Version + (or portions of such combination), to make, use, sell, + offer for sale, have made, and/or otherwise dispose of: + (1) Modifications made by that Contributor (or portions + thereof); and (2) the combination of Modifications made by + that Contributor with its Contributor Version (or portions + of such combination). + + (c) The licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first distributes or + otherwise makes the Modifications available to a third + party. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: (1) for any code that Contributor has deleted + from the Contributor Version; (2) for infringements caused + by: (i) third party modifications of Contributor Version, + or (ii) the combination of Modifications made by that + Contributor with other software (except as part of the + Contributor Version) or other devices; or (3) under Patent + Claims infringed by Covered Software in the absence of + Modifications made by that Contributor. + +3. Distribution Obligations. + + 3.1. Availability of Source Code. + + Any Covered Software that You distribute or otherwise make + available in Executable form must also be made available in Source + Code form and that Source Code form must be distributed only under + the terms of this License. You must include a copy of this + License with every copy of the Source Code form of the Covered + Software You distribute or otherwise make available. You must + inform recipients of any such Covered Software in Executable form + as to how they can obtain such Covered Software in Source Code + form in a reasonable manner on or through a medium customarily + used for software exchange. + + 3.2. Modifications. + + The Modifications that You create or to which You contribute are + governed by the terms of this License. You represent that You + believe Your Modifications are Your original creation(s) and/or + You have sufficient rights to grant the rights conveyed by this + License. + + 3.3. Required Notices. + + You must include a notice in each of Your Modifications that + identifies You as the Contributor of the Modification. You may + not remove or alter any copyright, patent or trademark notices + contained within the Covered Software, or any notices of licensing + or any descriptive text giving attribution to any Contributor or + the Initial Developer. + + 3.4. Application of Additional Terms. + + You may not offer or impose any terms on any Covered Software in + Source Code form that alters or restricts the applicable version + of this License or the recipients' rights hereunder. You may + choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of + Covered Software. However, you may do so only on Your own behalf, + and not on behalf of the Initial Developer or any Contributor. + You must make it absolutely clear that any such warranty, support, + indemnity or liability obligation is offered by You alone, and You + hereby agree to indemnify the Initial Developer and every + Contributor for any liability incurred by the Initial Developer or + such Contributor as a result of warranty, support, indemnity or + liability terms You offer. + + 3.5. Distribution of Executable Versions. + + You may distribute the Executable form of the Covered Software + under the terms of this License or under the terms of a license of + Your choice, which may contain terms different from this License, + provided that You are in compliance with the terms of this License + and that the license for the Executable form does not attempt to + limit or alter the recipient's rights in the Source Code form from + the rights set forth in this License. If You distribute the + Covered Software in Executable form under a different license, You + must make it absolutely clear that any terms which differ from + this License are offered by You alone, not by the Initial + Developer or Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred + by the Initial Developer or such Contributor as a result of any + such terms You offer. + + 3.6. Larger Works. + + You may create a Larger Work by combining Covered Software with + other code not governed by the terms of this License and + distribute the Larger Work as a single product. In such a case, + You must make sure the requirements of this License are fulfilled + for the Covered Software. + +4. Versions of the License. + + 4.1. New Versions. + + Sun Microsystems, Inc. is the initial license steward and may + publish revised and/or new versions of this License from time to + time. Each version will be given a distinguishing version number. + Except as provided in Section 4.3, no one other than the license + steward has the right to modify this License. + + 4.2. Effect of New Versions. + + You may always continue to use, distribute or otherwise make the + Covered Software available under the terms of the version of the + License under which You originally received the Covered Software. + If the Initial Developer includes a notice in the Original + Software prohibiting it from being distributed or otherwise made + available under any subsequent version of the License, You must + distribute and make the Covered Software available under the terms + of the version of the License under which You originally received + the Covered Software. Otherwise, You may also choose to use, + distribute or otherwise make the Covered Software available under + the terms of any subsequent version of the License published by + the license steward. + + 4.3. Modified Versions. + + When You are an Initial Developer and You want to create a new + license for Your Original Software, You may create and use a + modified version of this License if You: (a) rename the license + and remove any references to the name of the license steward + (except to note that the license differs from this License); and + (b) otherwise make it clear that the license contains terms which + differ from this License. + +5. DISCLAIMER OF WARRANTY. + + COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" + BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, + INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED + SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR + PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND + PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY + COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE + INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY + NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF + WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS + DISCLAIMER. + +6. TERMINATION. + + 6.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to + cure such breach within 30 days of becoming aware of the breach. + Provisions which, by their nature, must remain in effect beyond + the termination of this License shall survive. + + 6.2. If You assert a patent infringement claim (excluding + declaratory judgment actions) against Initial Developer or a + Contributor (the Initial Developer or Contributor against whom You + assert such claim is referred to as "Participant") alleging that + the Participant Software (meaning the Contributor Version where + the Participant is a Contributor or the Original Software where + the Participant is the Initial Developer) directly or indirectly + infringes any patent, then any and all rights granted directly or + indirectly to You by such Participant, the Initial Developer (if + the Initial Developer is not the Participant) and all Contributors + under Sections 2.1 and/or 2.2 of this License shall, upon 60 days + notice from Participant terminate prospectively and automatically + at the expiration of such 60 day notice period, unless if within + such 60 day period You withdraw Your claim with respect to the + Participant Software against such Participant either unilaterally + or pursuant to a written agreement with Participant. + + 6.3. In the event of termination under Sections 6.1 or 6.2 above, + all end user licenses that have been validly granted by You or any + distributor hereunder prior to termination (excluding licenses + granted to You by any distributor) shall survive termination. + +7. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE + INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF + COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE + LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR + CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT + LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK + STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL + INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT + APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO + NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR + CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT + APPLY TO YOU. + +8. U.S. GOVERNMENT END USERS. + + The Covered Software is a "commercial item," as that term is + defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial + computer software" (as that term is defined at 48 + C.F.R. 252.227-7014(a)(1)) and "commercial computer software + documentation" as such terms are used in 48 C.F.R. 12.212 + (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 + C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all + U.S. Government End Users acquire Covered Software with only those + rights set forth herein. This U.S. Government Rights clause is in + lieu of, and supersedes, any other FAR, DFAR, or other clause or + provision that addresses Government rights in computer software + under this License. + +9. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed + by the law of the jurisdiction specified in a notice contained + within the Original Software (except to the extent applicable law, + if any, provides otherwise), excluding such jurisdiction's + conflict-of-law provisions. Any litigation relating to this + License shall be subject to the jurisdiction of the courts located + in the jurisdiction and venue specified in a notice contained + within the Original Software, with the losing party responsible + for costs, including, without limitation, court costs and + reasonable attorneys' fees and expenses. The application of the + United Nations Convention on Contracts for the International Sale + of Goods is expressly excluded. Any law or regulation which + provides that the language of a contract shall be construed + against the drafter shall not apply to this License. You agree + that You alone are responsible for compliance with the United + States export administration regulations (and the export control + laws and regulation of any other countries) when You use, + distribute or otherwise make available any Covered Software. + +10. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or + indirectly, out of its utilization of rights under this License + and You agree to work with Initial Developer and Contributors to + distribute such responsibility on an equitable basis. Nothing + herein is intended or shall be deemed to constitute any admission + of liability. + +-------------------------------------------------------------------- + +NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND +DISTRIBUTION LICENSE (CDDL) + +For Covered Software in this distribution, this License shall +be governed by the laws of the State of California (excluding +conflict-of-law provisions). + +Any litigation relating to this License shall be subject to the +jurisdiction of the Federal Courts of the Northern District of +California and the state courts of the State of California, with +venue lying in Santa Clara County, California. + +====[ CDDL v1.0 license - end ]=============================================== diff --git a/pkg/deb/debian.module/rules-noarch.in b/pkg/deb/debian.module/rules-noarch.in new file mode 100644 index 00000000..823675ba --- /dev/null +++ b/pkg/deb/debian.module/rules-noarch.in @@ -0,0 +1,107 @@ +#!/usr/bin/make -f + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +export DEB_BUILD_MAINT_OPTIONS=hardening=+all,-pie +export DEB_CFLAGS_MAINT_APPEND=-Wp,-D_FORTIFY_SOURCE=2 -fPIC +DPKG_EXPORT_BUILDFLAGS = 1 +include /usr/share/dpkg/buildflags.mk + +BUILDDIR_unit = $(CURDIR)/debian/build-unit +BUILDDIR_unit_debug = $(CURDIR)/debian/build-unit-debug +INSTALLDIR = $(CURDIR)/debian/%%NAME%% +BASEDIR = $(CURDIR) + +%%MODULE_DEFINITIONS%% + +config.env.%: + dh_testdir + mkdir -p $(BUILDDIR_$*) + cp -Pa $(CURDIR)/auto $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/configure $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/src $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/test $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/version $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/CHANGES $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/LICENSE $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/NOTICE $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/README $(BUILDDIR_$*)/ + touch $@ + +configure.unit: config.env.unit + cd $(BUILDDIR_unit) && \ + CFLAGS= ./configure \ + %%CONFIGURE_ARGS%% \ + --modules=/usr/lib/unit/modules \ + --cc-opt="$(CFLAGS)" && \ + ./configure %%MODULE_CONFARGS%% + touch $@ + +configure.unit_debug: config.env.unit_debug + cd $(BUILDDIR_unit_debug) && \ + CFLAGS= ./configure \ + %%CONFIGURE_ARGS%% \ + --modules=/usr/lib/unit/debug-modules \ + --cc-opt="$(CFLAGS)" \ + --debug && \ + ./configure %%MODULE_CONFARGS%% + touch $@ + +build-arch.%: configure.% + dh_testdir + $(MAKE) -C $(BUILDDIR_$*) %%MODULE_MAKEARGS%% + touch $@ + +build-indep: + dh_testdir + touch $@ + +build-arch: build-arch.unit build-arch.unit_debug + dh_testdir + touch $@ + +build: build-arch build-indep + dh_testdir + touch $@ + +clean: + dh_testdir + dh_testroot + dh_clean + find $(CURDIR) -maxdepth 1 -size 0 -delete + rm -rf $(BUILDDIR_unit) $(BUILDDIR_unit_debug) + +install: build + dh_testdir + dh_testroot + dh_prep + dh_installdirs + dh_installinit + dh_installlogrotate +%%MODULE_PREINSTALL%% + cd $(BUILDDIR_unit) && \ + DESTDIR=$(INSTALLDIR) make %%MODULE_INSTARGS%% + cd $(BUILDDIR_unit_debug) && \ + DESTDIR=$(INSTALLDIR) make %%MODULE_INSTARGS%% +%%MODULE_POSTINSTALL%% + +binary-indep: build install + dh_testdir + dh_testroot + dh_installdocs + dh_installchangelogs + dh_link + dh_compress + dh_fixperms + dh_installdeb + dh_perl + dh_gencontrol + dh_md5sums + dh_builddeb + +binary-arch: install + +binary: binary-indep binary-arch + +.PHONY: clean binary-indep binary-arch binary install build diff --git a/pkg/deb/debian.module/rules.in b/pkg/deb/debian.module/rules.in index e41b05d4..1391e01a 100755 --- a/pkg/deb/debian.module/rules.in +++ b/pkg/deb/debian.module/rules.in @@ -23,6 +23,10 @@ config.env.%: cp -Pa $(CURDIR)/src $(BUILDDIR_$*)/ cp -Pa $(CURDIR)/test $(BUILDDIR_$*)/ cp -Pa $(CURDIR)/version $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/CHANGES $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/LICENSE $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/NOTICE $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/README $(BUILDDIR_$*)/ touch $@ configure.unit: config.env.unit @@ -80,12 +84,13 @@ install: build DESTDIR=$(INSTALLDIR) make %%MODULE_INSTARGS%% cd $(BUILDDIR_unit_debug) && \ DESTDIR=$(INSTALLDIR) make %%MODULE_INSTARGS%% +%%MODULE_POSTINSTALL%% binary-indep: build install dh_testdir dh_testroot dh_installdocs - dh_installchangelogs + dh_installchangelogs dh_link dh_strip --dbg-package=%%NAME%%-dbg dh_shlibdeps diff --git a/pkg/deb/debian.module/unit.example-jsc-app b/pkg/deb/debian.module/unit.example-jsc-app new file mode 100644 index 00000000..be01e123 --- /dev/null +++ b/pkg/deb/debian.module/unit.example-jsc-app @@ -0,0 +1,12 @@ +<%@ page contentType="text/plain"%><%@ page import="java.util.*" %>This is plain text response for "<%= request.getMethod() %> <%= request.getRequestURI() %>". +Here is the list of all system properties: +<% + Properties p = System.getProperties(); + Enumeration keys = p.keys(); + while (keys.hasMoreElements()) { + String key = (String) keys.nextElement(); + String value = (String) p.get(key); + out.println(" " + key + " : " + value); + } +%> +<% response.addHeader("X-Unit-JSP", "ok"); %> diff --git a/pkg/deb/debian.module/unit.example-jsc10-config b/pkg/deb/debian.module/unit.example-jsc10-config new file mode 100644 index 00000000..6929356d --- /dev/null +++ b/pkg/deb/debian.module/unit.example-jsc10-config @@ -0,0 +1,15 @@ +{ + "applications": { + "example_java10": { + "processes": 1, + "type": "java 10", + "webapp": "/usr/share/doc/unit-jsc10/examples/jsc-app" + } + }, + + "listeners": { + "*:8800": { + "application": "example_java10" + } + } +} diff --git a/pkg/deb/debian.module/unit.example-jsc11-config b/pkg/deb/debian.module/unit.example-jsc11-config new file mode 100644 index 00000000..6c1d9549 --- /dev/null +++ b/pkg/deb/debian.module/unit.example-jsc11-config @@ -0,0 +1,15 @@ +{ + "applications": { + "example_java11": { + "processes": 1, + "type": "java 11", + "webapp": "/usr/share/doc/unit-jsc11/examples/jsc-app" + } + }, + + "listeners": { + "*:8800": { + "application": "example_java11" + } + } +} diff --git a/pkg/deb/debian.module/unit.example-jsc8-config b/pkg/deb/debian.module/unit.example-jsc8-config new file mode 100644 index 00000000..0254677b --- /dev/null +++ b/pkg/deb/debian.module/unit.example-jsc8-config @@ -0,0 +1,15 @@ +{ + "applications": { + "example_java8": { + "processes": 1, + "type": "java 1.8.0", + "webapp": "/usr/share/doc/unit-jsc8/examples/jsc-app" + } + }, + + "listeners": { + "*:8800": { + "application": "example_java8" + } + } +} diff --git a/pkg/deb/debian.module/unit.example-jsc9-config b/pkg/deb/debian.module/unit.example-jsc9-config new file mode 100644 index 00000000..c64a1aff --- /dev/null +++ b/pkg/deb/debian.module/unit.example-jsc9-config @@ -0,0 +1,15 @@ +{ + "applications": { + "example_java9": { + "processes": 1, + "type": "java 9", + "webapp": "/usr/share/doc/unit-jsc9/examples/jsc-app" + } + }, + + "listeners": { + "*:8800": { + "application": "example_java9" + } + } +} diff --git a/pkg/deb/debian/rules.in b/pkg/deb/debian/rules.in index a8c4c948..bee9223f 100644 --- a/pkg/deb/debian/rules.in +++ b/pkg/deb/debian/rules.in @@ -27,6 +27,10 @@ config.env.%: cp -Pa $(CURDIR)/src $(BUILDDIR_$*)/ cp -Pa $(CURDIR)/test $(BUILDDIR_$*)/ cp -Pa $(CURDIR)/version $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/CHANGES $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/LICENSE $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/NOTICE $(BUILDDIR_$*)/ + cp -Pa $(CURDIR)/README $(BUILDDIR_$*)/ touch $@ configure.unit: config.env.unit diff --git a/pkg/rpm/Makefile b/pkg/rpm/Makefile index 56cf31b5..d94890f2 100644 --- a/pkg/rpm/Makefile +++ b/pkg/rpm/Makefile @@ -7,9 +7,9 @@ DEFAULT_RELEASE := 1 VERSION ?= $(NXT_VERSION) RELEASE ?= $(DEFAULT_RELEASE) -ifeq ($(shell rpm --eval "%{?rhel}"), 6) +ifeq ($(shell test `rpm --eval '0%{?rhel} -eq 6 -a 0%{?amzn} -eq 0'`; echo $$?), 0) OSVER = centos6 -else ifeq ($(shell rpm --eval "%{?rhel}"), 7) +else ifeq ($(shell test `rpm --eval '0%{?rhel} -eq 7 -a 0%{?amzn} -eq 0'`; echo $$?), 0) OSVER = centos7 else ifeq ($(shell rpm --eval "%{?amzn}"), 1) OSVER = amazonlinux1 @@ -49,13 +49,18 @@ ifeq ($(OSVER), centos6) include Makefile.php include Makefile.python include Makefile.go +include Makefile.jsc-common +include Makefile.jsc8 endif -ifneq (,$(findstring $(OSVER),centos7 amazonlinux2)) +ifeq ($(OSVER), centos7) include Makefile.php include Makefile.python include Makefile.go include Makefile.perl +include Makefile.jsc-common +include Makefile.jsc8 +include Makefile.jsc11 endif ifeq ($(OSVER), amazonlinux1) @@ -66,6 +71,17 @@ include Makefile.python35 include Makefile.python36 include Makefile.go include Makefile.perl +include Makefile.jsc-common +include Makefile.jsc8 +endif + +ifeq ($(OSVER), amazonlinux2) +include Makefile.php +include Makefile.python +include Makefile.go +include Makefile.perl +include Makefile.jsc-common +include Makefile.jsc8 endif ifeq ($(OSVER), opensuse-leap) @@ -102,6 +118,9 @@ endif include Makefile.go include Makefile.perl include Makefile.ruby +include Makefile.jsc-common +include Makefile.jsc8 +include Makefile.jsc11 endif CONFIGURE_ARGS=\ @@ -191,9 +210,10 @@ rpmbuild/SPECS/unit-%.spec: unit.module.spec.in ../../docs/changes.xml | rpmbuil sources="$${sources}\n$${s}" ; \ i=$$(($${i}+1)) ; \ done ; \ - pkgname=$(shell echo $@ | cut -d '/' -f 3 | cut -d '.' -f 1) ; \ + pkgname=$(shell echo $@ | cut -d '/' -f 3 | tr '_' '-' | cut -d '.' -f 1) ; \ definitions=`echo "$$MODULE_DEFINITIONS_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \ preinstall=`echo "$$MODULE_PREINSTALL_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \ + postinstall=`echo "$$MODULE_POSTINSTALL_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \ files=`echo "$$MODULE_FILES_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \ post=`echo "$$MODULE_POST_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \ cat unit.module.spec.in | sed \ @@ -210,6 +230,7 @@ rpmbuild/SPECS/unit-%.spec: unit.module.spec.in ../../docs/changes.xml | rpmbuil -e "s#%%MODULE_INSTARGS%%#$(MODULE_INSTARGS_$*)#g" \ -e "s#%%MODULE_DEFINITIONS%%#$${definitions}#g" \ -e "s#%%MODULE_PREINSTALL%%#$${preinstall}#g" \ + -e "s#%%MODULE_POSTINSTALL%%#$${postinstall}#g" \ -e "s#%%MODULE_FILES%%#$${files}#g" \ -e "s#%%MODULE_POST%%#$${post}#g" \ > $@.tmp ; \ @@ -221,14 +242,15 @@ rpmbuild/SPECS/unit-%.spec: unit.module.spec.in ../../docs/changes.xml | rpmbuil mv $@.tmp $@ unit-%: check-build-depends-% rpmbuild/SPECS/unit-%.spec rpmbuild/SOURCES/unit-$(VERSION).tar.gz - @echo "===> Building $@ package" ; \ + @echo "===> Building $(subst _,-,$@) package" ; \ rpmbuild -D "_topdir `pwd`/rpmbuild" -ba rpmbuild/SPECS/$@.spec && \ - ln -s rpmbuild/BUILD/$@-$(VERSION)/build $@ + ln -s rpmbuild/BUILD/$(subst _,-,$@)-$(VERSION)/build $@ test: unit modules @{ \ - for so in `find rpmbuild/BUILD/*/build-nodebug/ -type f -name "*.so"`; do \ + for so in `find rpmbuild/BUILD/*/build-nodebug/ -type f \( -name "*.so" -o -name "*.jar" \)`; do \ soname=`basename $${so}` ; \ + test "$${soname}" = "java.unit.so" && continue ; \ test -h rpmbuild/BUILD/unit-$(VERSION)/build-nodebug/$${soname} || \ ln -fs `pwd`/$${so} rpmbuild/BUILD/unit-$(VERSION)/build-nodebug/$${soname} ; \ done ; \ @@ -237,8 +259,9 @@ test: unit modules test-debug: unit modules @{ \ - for so in `find rpmbuild/BUILD/*/build-debug/ -type f -name "*.so"`; do \ + for so in `find rpmbuild/BUILD/*/build-debug/ -type f \( -name "*.so" -o -name "*.jar" \)`; do \ soname=`basename $${so}` ; \ + test "$${soname}" = "java.unit.so" && continue ; \ test -h rpmbuild/BUILD/unit-$(VERSION)/build-debug/$${soname} || \ ln -fs `pwd`/$${so} rpmbuild/BUILD/unit-$(VERSION)/build-debug/$${soname} ; \ done ; \ diff --git a/pkg/rpm/Makefile.jsc-common b/pkg/rpm/Makefile.jsc-common new file mode 100644 index 00000000..d73ed06c --- /dev/null +++ b/pkg/rpm/Makefile.jsc-common @@ -0,0 +1,41 @@ +MODULES+= jsc_common +MODULE_SUFFIX_jsc_common= jsc-common + +MODULE_SUMMARY_jsc_common= Java shared packages for NGINX Unit + +MODULE_VERSION_jsc_common= $(VERSION) +MODULE_RELEASE_jsc_common= 1 + +MODULE_CONFARGS_jsc_common= java --home=/usr/lib/jvm/java-1.8.0 --jars=/usr/share/unit-jsc-common/ +MODULE_MAKEARGS_jsc_common= java +MODULE_INSTARGS_jsc_common= java-shared-install + +MODULE_SOURCES_jsc_common= COPYRIGHT.unit-jsc-common + +BUILD_DEPENDS_jsc_common= java-1.8.0-openjdk-devel +BUILD_DEPENDS+= $(BUILD_DEPENDS_jsc_common) + +define MODULE_DEFINITIONS_jsc_common +BuildArch: noarch +endef +export MODULE_DEFINITIONS_jsc_common + +define MODULE_FILES_jsc_common +%dir %{_datadir}/unit-jsc-common +%{_datadir}/unit-jsc-common/* +endef +export MODULE_FILES_jsc_common + +define MODULE_POST_jsc_common +cat <<%@ page import="java.util.*" %>This is plain text response for "<%= request.getMethod() %> <%= request.getRequestURI() %>". +Here is the list of all system properties: +<% + Properties p = System.getProperties(); + Enumeration keys = p.keys(); + while (keys.hasMoreElements()) { + String key = (String) keys.nextElement(); + String value = (String) p.get(key); + out.println(" " + key + " : " + value); + } +%> +<% response.addHeader("X-Unit-JSP", "ok"); %> diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc11-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc11-config new file mode 100644 index 00000000..6c1d9549 --- /dev/null +++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc11-config @@ -0,0 +1,15 @@ +{ + "applications": { + "example_java11": { + "processes": 1, + "type": "java 11", + "webapp": "/usr/share/doc/unit-jsc11/examples/jsc-app" + } + }, + + "listeners": { + "*:8800": { + "application": "example_java11" + } + } +} diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc8-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc8-config new file mode 100644 index 00000000..0254677b --- /dev/null +++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc8-config @@ -0,0 +1,15 @@ +{ + "applications": { + "example_java8": { + "processes": 1, + "type": "java 1.8.0", + "webapp": "/usr/share/doc/unit-jsc8/examples/jsc-app" + } + }, + + "listeners": { + "*:8800": { + "application": "example_java8" + } + } +} diff --git a/pkg/rpm/unit.module.spec.in b/pkg/rpm/unit.module.spec.in index 8b8a3433..6a229c0f 100644 --- a/pkg/rpm/unit.module.spec.in +++ b/pkg/rpm/unit.module.spec.in @@ -72,14 +72,20 @@ make %%MODULE_MAKEARGS%% %install %{__rm} -rf %{buildroot} %{__mkdir} -p %{buildroot}%{_datadir}/doc/%%NAME%% +if [ `basename %{SOURCE100}` == COPYRIGHT.%{name} ]; then +%{__install} -m 644 -p %{SOURCE100} \ + %{buildroot}%{_datadir}/doc/%%NAME%%/COPYRIGHT +else %{__install} -m 644 -p NOTICE \ %{buildroot}%{_datadir}/doc/%%NAME%%/COPYRIGHT +fi %%MODULE_PREINSTALL%% %{__ln_s} build-debug build DESTDIR=%{buildroot} make %%MODULE_INSTARGS%% %{__rm} -f build %{__ln_s} build-nodebug build DESTDIR=%{buildroot} make %%MODULE_INSTARGS%% +%%MODULE_POSTINSTALL%% %check %{__rm} -rf %{buildroot}/usr/src -- cgit From a5dd0f8aa9b81921ff28c486a39fd46607dbdbd9 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 28 Feb 2019 20:20:41 +0300 Subject: Made QUERY_STRING mandatory. According to CGI/1.1 RFC 3875: The server MUST set this variable; if the Script-URI does not include a query component, the QUERY_STRING MUST be defined as an empty string (""). Python's PEP 333(3) allows omitting it in WSGI interface; PHP docs force no requirements; PSGI and Rack specifications require it even if empty. When nginx proxies requests over FastCGI, it always provides QUERY_STRING. and some PHP apps have been observed to fail if it is missing (see issue #201 on GitHub). A drawback of this change (besides a small overhead) is that there will be no easy way to tell a missing query string from an empty one (i.e. requests with or without the "?" character); yet, it's negligible compared to the possible benefits of wider application compatibility. This closes #226 issue on GitHub. --- src/nxt_php_sapi.c | 6 ++---- src/nxt_python_wsgi.c | 6 +----- src/perl/nxt_perl_psgi.c | 6 ++---- src/ruby/nxt_ruby.c | 6 ++---- test/test_python_application.py | 1 - 5 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index e36dd20a..80321a85 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -917,10 +917,8 @@ nxt_php_register_variables(zval *track_vars_array TSRMLS_DC) track_vars_array TSRMLS_CC); nxt_php_set_sptr(req, "REQUEST_URI", &r->target, r->target_length, track_vars_array TSRMLS_CC); - if (r->query.offset) { - nxt_php_set_sptr(req, "QUERY_STRING", &r->query, r->query_length, - track_vars_array TSRMLS_CC); - } + nxt_php_set_sptr(req, "QUERY_STRING", &r->query, r->query_length, + track_vars_array TSRMLS_CC); nxt_php_set_sptr(req, "REMOTE_ADDR", &r->remote, r->remote_length, track_vars_array TSRMLS_CC); diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c index 80ffb0f7..6478f38c 100644 --- a/src/nxt_python_wsgi.c +++ b/src/nxt_python_wsgi.c @@ -720,11 +720,7 @@ nxt_python_get_environ(nxt_python_run_ctx_t *ctx) RC(nxt_python_add_sptr(ctx, "REQUEST_METHOD", &r->method, r->method_length)); RC(nxt_python_add_sptr(ctx, "REQUEST_URI", &r->target, r->target_length)); - - if (r->query.offset) { - RC(nxt_python_add_sptr(ctx, "QUERY_STRING", &r->query, - r->query_length)); - } + RC(nxt_python_add_sptr(ctx, "QUERY_STRING", &r->query, r->query_length)); RC(nxt_python_add_sptr(ctx, "PATH_INFO", &r->path, r->path_length)); RC(nxt_python_add_sptr(ctx, "REMOTE_ADDR", &r->remote, r->remote_length)); diff --git a/src/perl/nxt_perl_psgi.c b/src/perl/nxt_perl_psgi.c index efcff7e3..3e865d46 100644 --- a/src/perl/nxt_perl_psgi.c +++ b/src/perl/nxt_perl_psgi.c @@ -548,10 +548,8 @@ nxt_perl_psgi_env_create(PerlInterpreter *my_perl, RC(nxt_perl_psgi_add_value(my_perl, hash_env, NL("psgi.streaming"), &PL_sv_no)); - if (r->query.offset) { - RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("QUERY_STRING"), - &r->query, r->query_length)); - } + RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("QUERY_STRING"), + &r->query, r->query_length)); RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("SERVER_PROTOCOL"), &r->version, r->version_length)); RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("REMOTE_ADDR"), diff --git a/src/ruby/nxt_ruby.c b/src/ruby/nxt_ruby.c index 17831175..b2398abe 100644 --- a/src/ruby/nxt_ruby.c +++ b/src/ruby/nxt_ruby.c @@ -442,10 +442,8 @@ nxt_ruby_read_request(VALUE hash_env) nxt_ruby_add_sptr(hash_env, NL("REQUEST_URI"), &r->target, r->target_length); nxt_ruby_add_sptr(hash_env, NL("PATH_INFO"), &r->path, r->path_length); - if (r->query.offset) { - nxt_ruby_add_sptr(hash_env, NL("QUERY_STRING"), &r->query, - r->query_length); - } + nxt_ruby_add_sptr(hash_env, NL("QUERY_STRING"), &r->query, + r->query_length); nxt_ruby_add_sptr(hash_env, NL("SERVER_PROTOCOL"), &r->version, r->version_length); nxt_ruby_add_sptr(hash_env, NL("REMOTE_ADDR"), &r->remote, diff --git a/test/test_python_application.py b/test/test_python_application.py index da5d3ba2..a8631085 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -65,7 +65,6 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): self.assertEqual(resp['headers']['Query-String'], '', 'query string empty') - @unittest.expectedFailure def test_python_application_query_string_absent(self): self.load('query_string') -- cgit From 38ea191fbb91db000b1e68185359e612e7e47c54 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Thu, 28 Feb 2019 21:18:33 +0300 Subject: Tests: fixed PHP "disable_functions" and "disable_classes" tests. --- test/php/date_time/index.php | 3 +- test/php/highlight_file_exec/index.php | 4 -- test/php/time_exec/index.php | 4 ++ test/test_php_application.py | 118 +++++++++++++++------------------ 4 files changed, 60 insertions(+), 69 deletions(-) delete mode 100644 test/php/highlight_file_exec/index.php create mode 100644 test/php/time_exec/index.php diff --git a/test/php/date_time/index.php b/test/php/date_time/index.php index 4e06fdf9..42992c3f 100644 --- a/test/php/date_time/index.php +++ b/test/php/date_time/index.php @@ -1,4 +1,5 @@ format('u'); ?> diff --git a/test/php/highlight_file_exec/index.php b/test/php/highlight_file_exec/index.php deleted file mode 100644 index adcd5ed8..00000000 --- a/test/php/highlight_file_exec/index.php +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/test/php/time_exec/index.php b/test/php/time_exec/index.php new file mode 100644 index 00000000..05d59d28 --- /dev/null +++ b/test/php/time_exec/index.php @@ -0,0 +1,4 @@ + diff --git a/test/test_php_application.py b/test/test_php_application.py index f077cd3b..ac74359d 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -7,9 +7,11 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP): def setUpClass(): unit.TestUnit().check_modules('php') - def search_disabled(self, name): - p = re.compile(name + '\(\) has been disabled') - return self.search_in_log(p) + def before_disable_functions(self): + body = self.get()['body'] + + self.assertRegex(body, r'time: \d+', 'disable_functions before time') + self.assertRegex(body, r'exec: \/\w+', 'disable_functions before exec') def test_php_application_variables(self): self.load('variables') @@ -239,109 +241,97 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP): 'ini value repeat') def test_php_application_disable_functions_exec(self): - self.load('highlight_file_exec') - - self.conf({"admin": { "disable_functions": "exec" }}, - 'applications/highlight_file_exec/options') - - self.get() - - self.assertIsNotNone(self.search_disabled('exec'), - 'disable_functions exec') - self.assertIsNone(self.search_disabled('highlight_file'), - 'disable_functions highlight_file') + self.load('time_exec') - def test_php_application_disable_functions_highlight_file(self): - self.load('highlight_file_exec') + self.before_disable_functions() - self.conf({"admin": { "disable_functions": "highlight_file" }}, - 'applications/highlight_file_exec/options') + self.conf({"admin": { "disable_functions": "exec" }}, + 'applications/time_exec/options') - self.get() + body = self.get()['body'] - self.assertIsNone(self.search_disabled('exec'), - 'disable_functions exec') - self.assertIsNotNone(self.search_disabled('highlight_file'), - 'disable_functions highlight_file') + self.assertRegex(body, r'time: \d+', 'disable_functions time') + self.assertNotRegex(body, r'exec: \/\w+', 'disable_functions exec') def test_php_application_disable_functions_comma(self): - self.load('highlight_file_exec') + self.load('time_exec') + + self.before_disable_functions() - self.conf({"admin": { "disable_functions": "exec,highlight_file" }}, - 'applications/highlight_file_exec/options') + self.conf({"admin": { "disable_functions": "exec,time" }}, + 'applications/time_exec/options') - self.get() + body = self.get()['body'] - self.assertIsNotNone(self.search_disabled('exec'), - 'disable_functions exec') - self.assertIsNotNone(self.search_disabled('highlight_file'), - 'disable_functions highlight_file') + self.assertNotRegex(body, r'time: \d+', 'disable_functions comma time') + self.assertNotRegex(body, r'exec: \/\w+', + 'disable_functions comma exec') def test_php_application_disable_functions_space(self): - self.load('highlight_file_exec') + self.load('time_exec') - self.conf({"admin": { "disable_functions": "exec highlight_file" }}, - 'applications/highlight_file_exec/options') + self.before_disable_functions() - self.get() + self.conf({"admin": { "disable_functions": "exec time" }}, + 'applications/time_exec/options') - self.assertIsNotNone(self.search_disabled('exec'), - 'disable_functions exec') - self.assertIsNotNone(self.search_disabled('highlight_file'), - 'disable_functions highlight_file') + body = self.get()['body'] + + self.assertNotRegex(body, r'time: \d+', 'disable_functions space time') + self.assertNotRegex(body, r'exec: \/\w+', + 'disable_functions space exec') def test_php_application_disable_functions_user(self): - self.load('highlight_file_exec') + self.load('time_exec') + + self.before_disable_functions() self.conf({"user": { "disable_functions": "exec" }}, - 'applications/highlight_file_exec/options') + 'applications/time_exec/options') - self.get() + body = self.get()['body'] - self.assertIsNotNone(self.search_disabled('exec'), - 'disable_functions exec') - self.assertIsNone(self.search_disabled('highlight_file'), - 'disable_functions highlight_file') + self.assertRegex(body, r'time: \d+', 'disable_functions user time') + self.assertNotRegex(body, r'exec: \/\w+', 'disable_functions user exec') def test_php_application_disable_functions_nonexistent(self): - self.load('highlight_file_exec') + self.load('time_exec') + + self.before_disable_functions() self.conf({"admin": { "disable_functions": "blah" }}, - 'applications/highlight_file_exec/options') + 'applications/time_exec/options') - self.get() + body = self.get()['body'] - self.assertIsNone(self.search_disabled('exec'), - 'disable_functions exec') - self.assertIsNone(self.search_disabled('highlight_file'), - 'disable_functions highlight_file') + self.assertRegex(body, r'time: \d+', + 'disable_functions nonexistent time') + self.assertRegex(body, r'exec: \/\w+', + 'disable_functions nonexistent exec') def test_php_application_disable_classes(self): self.load('date_time') - self.get() - - self.assertIsNone(self.search_disabled('DateTime'), + self.assertRegex(self.get()['body'], r'012345', 'disable_classes before') self.conf({"admin": { "disable_classes": "DateTime" }}, 'applications/date_time/options') - self.get() - - self.assertIsNotNone(self.search_disabled('DateTime'), - 'disable_classes') + self.assertNotRegex(self.get()['body'], r'012345', + 'disable_classes before') def test_php_application_disable_classes_user(self): self.load('date_time') + self.assertRegex(self.get()['body'], r'012345', + 'disable_classes before') + self.conf({"user": { "disable_classes": "DateTime" }}, 'applications/date_time/options') - self.get() - - self.assertIsNotNone(self.search_disabled('DateTime'), - 'disable_classes user') + self.assertNotRegex(self.get()['body'], r'012345', + 'disable_classes before') if __name__ == '__main__': TestUnitPHPApplication.main() -- cgit From c1751f9de69638da89fda2ec17a693b7ec2de5c5 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Thu, 28 Feb 2019 21:19:32 +0300 Subject: Tests: specify ssl_version option. TLS is explicitly selected to prevent using of SSL protocol in ssl.get_server_certificate() call for Python 3.4 and older. --- test/unit.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/unit.py b/test/unit.py index a0de82e7..6cca7f48 100644 --- a/test/unit.py +++ b/test/unit.py @@ -718,7 +718,19 @@ class TestUnitApplicationTLS(TestUnitApplicationProto): **kwargs) def get_server_certificate(self, addr=('127.0.0.1', 7080)): - return ssl.get_server_certificate(addr) + + ssl_list = dir(ssl) + + if 'PROTOCOL_TLS' in ssl_list: + ssl_version = ssl.PROTOCOL_TLS + + elif 'PROTOCOL_TLSv1_2' in ssl_list: + ssl_version = ssl.PROTOCOL_TLSv1_2 + + else: + ssl_version = ssl.PROTOCOL_TLSv1_1 + + return ssl.get_server_certificate(addr, ssl_version=ssl_version) def load(self, script, name=None): if name is None: -- cgit From 5b98f0a55204c65b42fc5606e0ad2ba9bd01568c Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Fri, 1 Mar 2019 10:46:46 +0300 Subject: Packages: removed Ubuntu 17.04 and 17.10 references due to EOL. --- pkg/deb/Makefile | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/pkg/deb/Makefile b/pkg/deb/Makefile index 3d747402..f481ff02 100644 --- a/pkg/deb/Makefile +++ b/pkg/deb/Makefile @@ -45,28 +45,6 @@ include Makefile.jsc8 include Makefile.jsc10 endif -# Ubuntu 17.10 -ifeq ($(CODENAME),artful) -include Makefile.php -include Makefile.python27 -include Makefile.python36 -include Makefile.go18 -include Makefile.go19 -include Makefile.perl -include Makefile.ruby -endif - -# Ubuntu 17.04 -ifeq ($(CODENAME),zesty) -include Makefile.php -include Makefile.python27 -include Makefile.python35 -include Makefile.go17 -include Makefile.go18 -include Makefile.perl -include Makefile.ruby -endif - # Ubuntu 16.04 ifeq ($(CODENAME),xenial) include Makefile.php -- cgit From 2e4853f4d7b4a1e49bc4dedd3c9cba61134fb511 Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Fri, 1 Mar 2019 16:04:41 +0300 Subject: Packages: added Java trademark notice to corresponding packages. --- pkg/deb/debian.module/copyright.unit-jsc-common | 8 +++++-- pkg/deb/debian.module/copyright.unit-jsc10 | 26 ++++++++++++++++++++++ pkg/deb/debian.module/copyright.unit-jsc11 | 26 ++++++++++++++++++++++ pkg/deb/debian.module/copyright.unit-jsc8 | 26 ++++++++++++++++++++++ pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc-common | 8 +++++-- pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc10 | 25 +++++++++++++++++++++ pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc11 | 25 +++++++++++++++++++++ pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc8 | 25 +++++++++++++++++++++ 8 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 pkg/deb/debian.module/copyright.unit-jsc10 create mode 100644 pkg/deb/debian.module/copyright.unit-jsc11 create mode 100644 pkg/deb/debian.module/copyright.unit-jsc8 create mode 100644 pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc10 create mode 100644 pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc11 create mode 100644 pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc8 diff --git a/pkg/deb/debian.module/copyright.unit-jsc-common b/pkg/deb/debian.module/copyright.unit-jsc-common index 08907713..accb1834 100644 --- a/pkg/deb/debian.module/copyright.unit-jsc-common +++ b/pkg/deb/debian.module/copyright.unit-jsc-common @@ -1,6 +1,10 @@ -This package includes the following software components distributed -under corresponding licenses: +The unit-jsc-common package includes supplementary Java archives (JARs) +required by Java servlet container module for NGINX Unit. + +Java is a registered trademark of Oracle and/or its affiliates. + +The following software components distributed under corresponding licenses: * classgraph-4.4.11.jar: MIT * ecj-3.13.102.jar: EPL 1.0 diff --git a/pkg/deb/debian.module/copyright.unit-jsc10 b/pkg/deb/debian.module/copyright.unit-jsc10 new file mode 100644 index 00000000..dbad728b --- /dev/null +++ b/pkg/deb/debian.module/copyright.unit-jsc10 @@ -0,0 +1,26 @@ + + NGINX Unit. + + Copyright 2017-2019 NGINX, Inc. + Copyright 2017-2019 Igor Sysoev + Copyright 2017-2019 Valentin V. Bartenev + Copyright 2017-2019 Max Romanov + Copyright 2018-2019 Alexander Borisov + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + /usr/share/common-licenses/Apache-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + The unit-jsc10 package provides Java servlet container module + for NGINX Unit. + + Java is a registered trademark of Oracle and/or its affiliates. diff --git a/pkg/deb/debian.module/copyright.unit-jsc11 b/pkg/deb/debian.module/copyright.unit-jsc11 new file mode 100644 index 00000000..413c0094 --- /dev/null +++ b/pkg/deb/debian.module/copyright.unit-jsc11 @@ -0,0 +1,26 @@ + + NGINX Unit. + + Copyright 2017-2019 NGINX, Inc. + Copyright 2017-2019 Igor Sysoev + Copyright 2017-2019 Valentin V. Bartenev + Copyright 2017-2019 Max Romanov + Copyright 2018-2019 Alexander Borisov + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + /usr/share/common-licenses/Apache-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + The unit-jsc11 package provides Java servlet container module + for NGINX Unit. + + Java is a registered trademark of Oracle and/or its affiliates. diff --git a/pkg/deb/debian.module/copyright.unit-jsc8 b/pkg/deb/debian.module/copyright.unit-jsc8 new file mode 100644 index 00000000..96b62102 --- /dev/null +++ b/pkg/deb/debian.module/copyright.unit-jsc8 @@ -0,0 +1,26 @@ + + NGINX Unit. + + Copyright 2017-2019 NGINX, Inc. + Copyright 2017-2019 Igor Sysoev + Copyright 2017-2019 Valentin V. Bartenev + Copyright 2017-2019 Max Romanov + Copyright 2018-2019 Alexander Borisov + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + /usr/share/common-licenses/Apache-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + The unit-jsc8 package provides Java servlet container module + for NGINX Unit. + + Java is a registered trademark of Oracle and/or its affiliates. diff --git a/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc-common b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc-common index 08907713..accb1834 100644 --- a/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc-common +++ b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc-common @@ -1,6 +1,10 @@ -This package includes the following software components distributed -under corresponding licenses: +The unit-jsc-common package includes supplementary Java archives (JARs) +required by Java servlet container module for NGINX Unit. + +Java is a registered trademark of Oracle and/or its affiliates. + +The following software components distributed under corresponding licenses: * classgraph-4.4.11.jar: MIT * ecj-3.13.102.jar: EPL 1.0 diff --git a/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc10 b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc10 new file mode 100644 index 00000000..665785b2 --- /dev/null +++ b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc10 @@ -0,0 +1,25 @@ + + NGINX Unit. + + Copyright 2017-2019 NGINX, Inc. + Copyright 2017-2019 Igor Sysoev + Copyright 2017-2019 Valentin V. Bartenev + Copyright 2017-2019 Max Romanov + Copyright 2018-2019 Alexander Borisov + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + The unit-jsc10 package provides Java servlet container module + for NGINX Unit. + + Java is a registered trademark of Oracle and/or its affiliates. diff --git a/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc11 b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc11 new file mode 100644 index 00000000..e92a8245 --- /dev/null +++ b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc11 @@ -0,0 +1,25 @@ + + NGINX Unit. + + Copyright 2017-2019 NGINX, Inc. + Copyright 2017-2019 Igor Sysoev + Copyright 2017-2019 Valentin V. Bartenev + Copyright 2017-2019 Max Romanov + Copyright 2018-2019 Alexander Borisov + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + The unit-jsc11 package provides Java servlet container module + for NGINX Unit. + + Java is a registered trademark of Oracle and/or its affiliates. diff --git a/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc8 b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc8 new file mode 100644 index 00000000..1e7dbff6 --- /dev/null +++ b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc8 @@ -0,0 +1,25 @@ + + NGINX Unit. + + Copyright 2017-2019 NGINX, Inc. + Copyright 2017-2019 Igor Sysoev + Copyright 2017-2019 Valentin V. Bartenev + Copyright 2017-2019 Max Romanov + Copyright 2018-2019 Alexander Borisov + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + The unit-jsc8 package provides Java servlet container module + for NGINX Unit. + + Java is a registered trademark of Oracle and/or its affiliates. -- cgit From 3b2c1d0eaaf8252242d2a164ad768a985e8ded5c Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Fri, 1 Mar 2019 16:50:25 +0300 Subject: Perl: added implementation delayed response and streaming body. --- src/perl/nxt_perl_psgi.c | 192 ++++++++++++++++++++++++++++++++++++++---- test/test_perl_application.py | 2 +- 2 files changed, 177 insertions(+), 17 deletions(-) diff --git a/src/perl/nxt_perl_psgi.c b/src/perl/nxt_perl_psgi.c index 3e865d46..0b4b31d7 100644 --- a/src/perl/nxt_perl_psgi.c +++ b/src/perl/nxt_perl_psgi.c @@ -92,6 +92,8 @@ static ssize_t nxt_perl_psgi_io_read(nxt_unit_read_info_t *read_info, void *dst, size_t size); static int nxt_perl_psgi_result_array(PerlInterpreter *my_perl, SV *result, nxt_unit_request_info_t *req); +static void nxt_perl_psgi_result_cb(PerlInterpreter *my_perl, SV *result, + nxt_unit_request_info_t *req); static nxt_int_t nxt_perl_psgi_init(nxt_task_t *task, nxt_common_app_conf_t *conf); @@ -101,8 +103,11 @@ static void nxt_perl_psgi_atexit(void); typedef SV *(*nxt_perl_psgi_callback_f)(PerlInterpreter *my_perl, SV *env, nxt_task_t *task); -static PerlInterpreter *nxt_perl_psgi; -static nxt_perl_psgi_io_arg_t nxt_perl_psgi_arg_input, nxt_perl_psgi_arg_error; +static CV *nxt_perl_psgi_cb; +static PerlInterpreter *nxt_perl_psgi; +static nxt_perl_psgi_io_arg_t nxt_perl_psgi_arg_input; +static nxt_perl_psgi_io_arg_t nxt_perl_psgi_arg_error; +static nxt_unit_request_info_t *nxt_perl_psgi_request; static uint32_t nxt_perl_psgi_compat[] = { NXT_VERNUM, NXT_DEBUG, @@ -206,6 +211,115 @@ XS(XS_NGINX__Unit__PSGI_exit) } +XS(XS_NGINX__Unit__Sandbox_write); +XS(XS_NGINX__Unit__Sandbox_write) +{ + int rc; + char *body; + size_t len; + + dXSARGS; + + if (nxt_slow_path(items != 2)) { + Perl_croak(aTHX_ "Wrong number of arguments. Need one string"); + + XSRETURN_EMPTY; + } + + body = SvPV(ST(1), len); + + rc = nxt_unit_response_write(nxt_perl_psgi_request, body, len); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + Perl_croak(aTHX_ "Failed to write response body"); + + XSRETURN_EMPTY; + } + + XSRETURN_IV(len); +} + + +nxt_inline void +nxt_perl_psgi_cb_request_done(nxt_int_t status) +{ + nxt_unit_request_info_t *req; + + req = nxt_perl_psgi_request; + + if (req != NULL) { + nxt_unit_request_done(req, status); + nxt_perl_psgi_request = NULL; + } +} + + +XS(XS_NGINX__Unit__Sandbox_close); +XS(XS_NGINX__Unit__Sandbox_close) +{ + I32 ax; + + ax = POPMARK; + + nxt_perl_psgi_cb_request_done(NXT_UNIT_OK); + + XSRETURN_NO; +} + + +XS(XS_NGINX__Unit__Sandbox_cb); +XS(XS_NGINX__Unit__Sandbox_cb) +{ + SV *obj; + int rc; + long array_len; + + dXSARGS; + + if (nxt_slow_path(items != 1)) { + nxt_perl_psgi_cb_request_done(NXT_UNIT_ERROR); + + Perl_croak(aTHX_ "Wrong number of arguments"); + + XSRETURN_EMPTY; + } + + if (nxt_slow_path(SvOK(ST(0)) == 0 || SvROK(ST(0)) == 0 + || SvTYPE(SvRV(ST(0))) != SVt_PVAV)) + { + nxt_perl_psgi_cb_request_done(NXT_UNIT_ERROR); + + Perl_croak(aTHX_ "PSGI: An unexpected response was received " + "from Perl Application"); + + XSRETURN_EMPTY; + } + + rc = nxt_perl_psgi_result_array(PERL_GET_CONTEXT, ST(0), + nxt_perl_psgi_request); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + nxt_perl_psgi_cb_request_done(NXT_UNIT_ERROR); + + Perl_croak(aTHX_ (char *) NULL); + + XSRETURN_EMPTY; + } + + array_len = av_len((AV *) SvRV(ST(0))); + + if (array_len < 2) { + obj = sv_bless(newRV_noinc((SV *) newHV()), + gv_stashpv("NGINX::Unit::Sandbox", GV_ADD)); + ST(0) = obj; + + XSRETURN(1); + } + + nxt_perl_psgi_cb_request_done(NXT_UNIT_OK); + + XSRETURN_EMPTY; +} + + static void nxt_perl_psgi_xs_init(pTHX) { @@ -218,6 +332,14 @@ nxt_perl_psgi_xs_init(pTHX) /* DynaLoader for Perl modules who use XS */ newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, __FILE__); + + newXS("NGINX::Unit::Sandbox::write", XS_NGINX__Unit__Sandbox_write, + __FILE__); + newXS("NGINX::Unit::Sandbox::close", XS_NGINX__Unit__Sandbox_close, + __FILE__); + + nxt_perl_psgi_cb = newXS("NGINX::Unit::Sandbox::cb", + XS_NGINX__Unit__Sandbox_cb, __FILE__); } @@ -300,6 +422,9 @@ nxt_perl_psgi_module_create(nxt_task_t *task, const char *script) static nxt_str_t prefix = nxt_string( "package NGINX::Unit::Sandbox;" + "sub new {" + " return bless {}, $_[0];" + "}" "{my $app = do \"" ); @@ -546,7 +671,7 @@ nxt_perl_psgi_env_create(PerlInterpreter *my_perl, RC(nxt_perl_psgi_add_value(my_perl, hash_env, NL("psgi.nonblocking"), &PL_sv_no)); RC(nxt_perl_psgi_add_value(my_perl, hash_env, NL("psgi.streaming"), - &PL_sv_no)); + &PL_sv_yes)); RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("QUERY_STRING"), &r->query, r->query_length)); @@ -975,6 +1100,36 @@ nxt_perl_psgi_result_array(PerlInterpreter *my_perl, SV *result, } +static void +nxt_perl_psgi_result_cb(PerlInterpreter *my_perl, SV *result, + nxt_unit_request_info_t *req) +{ + dSP; + + ENTER; + SAVETMPS; + + PUSHMARK(sp); + XPUSHs(newRV_noinc((SV*) nxt_perl_psgi_cb)); + PUTBACK; + + call_sv(result, G_EVAL|G_SCALAR); + + SPAGAIN; + + if (SvTRUE(ERRSV)) { + nxt_unit_error(NULL, "PSGI: Failed to execute result callback: \n%s", + SvPV_nolen(ERRSV)); + + nxt_perl_psgi_cb_request_done(NXT_UNIT_ERROR); + } + + PUTBACK; + FREETMPS; + LEAVE; +} + + static nxt_int_t nxt_perl_psgi_init(nxt_task_t *task, nxt_common_app_conf_t *conf) { @@ -1031,6 +1186,8 @@ nxt_perl_psgi_request_handler(nxt_unit_request_info_t *req) input.my_perl = my_perl; input.req = req; + nxt_perl_psgi_request = req; + /* * Create environ variable for perl sub "application". * > sub application { @@ -1051,23 +1208,26 @@ nxt_perl_psgi_request_handler(nxt_unit_request_info_t *req) /* Call perl sub and get result as SV*. */ result = nxt_perl_psgi_call_var_application(my_perl, env, module->app, req); - /* - * We expect ARRAY ref like a - * ['200', ['Content-Type' => "text/plain"], ["body"]] - */ - if (nxt_slow_path(SvOK(result) == 0 || SvROK(result) == 0 - || SvTYPE(SvRV(result)) != SVt_PVAV)) - { - nxt_unit_req_error(req, "PSGI: An unexpected response was received " - "from Perl Application"); + if (nxt_fast_path(SvOK(result) != 0 && SvROK(result) != 0)) { - rc = NXT_UNIT_ERROR; + if (SvTYPE(SvRV(result)) == SVt_PVAV) { + rc = nxt_perl_psgi_result_array(my_perl, result, req); + nxt_unit_request_done(req, rc); + goto release; + } - } else { - rc = nxt_perl_psgi_result_array(my_perl, result, req); + if (SvTYPE(SvRV(result)) == SVt_PVCV) { + nxt_perl_psgi_result_cb(my_perl, result, req); + goto release; + } } - nxt_unit_request_done(req, rc); + nxt_unit_req_error(req, "PSGI: An unexpected response was received " + "from Perl Application"); + + nxt_unit_request_done(req, NXT_UNIT_ERROR); + +release: SvREFCNT_dec(result); SvREFCNT_dec(env); diff --git a/test/test_perl_application.py b/test/test_perl_application.py index 6fd0f78e..4609c2bd 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -45,7 +45,7 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): 'Psgi-Multiprocess': '1', 'Psgi-Run-Once': '', 'Psgi-Nonblocking': '', - 'Psgi-Streaming': '' + 'Psgi-Streaming': '1' }, 'headers') self.assertEqual(resp['body'], body, 'body') -- cgit From d92feef571696c4e0ec837fd7f7e237aea038af4 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Fri, 1 Mar 2019 17:12:40 +0300 Subject: README.JSR-340 legal notice to save our ughm.. bottoms of bodies. --- src/java/README.JSR-340 | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/java/README.JSR-340 diff --git a/src/java/README.JSR-340 b/src/java/README.JSR-340 new file mode 100644 index 00000000..0eb189a7 --- /dev/null +++ b/src/java/README.JSR-340 @@ -0,0 +1,16 @@ +NOTICE: + +This version of Unit code is made available in support of the open source +development process. This is an intermediate build made available for +testing purposes only. This Unit code is untested and presumed incompatible +with the JSR 340 Java Servlet 3.1 specification. You should not deploy or +write to this code. You should instead deploy and write production +applications on pre-built binaries that have been tested and certified +to meet the JSR-340 compatibility requirements such as certified binaries +published for the JSR-340 reference implementation available at +https://javaee.github.io/glassfish/. + +Redistribution of any Intermediate Build must retain this notice. + +Oracle and Java are registered trademarks of Oracle and/or its affiliates. +Other names may be trademarks of their respective owners. -- cgit From 754b85c3eedc6717b9bb94fe82a80f36f25a3e9f Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Fri, 1 Mar 2019 17:13:51 +0300 Subject: Tests: Perl streaming body and delayed response simple tests. --- test/perl/delayed_response/psgi.pl | 10 ++++++++++ test/perl/streaming_body/psgi.pl | 13 +++++++++++++ test/test_perl_application.py | 16 ++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 test/perl/delayed_response/psgi.pl create mode 100644 test/perl/streaming_body/psgi.pl diff --git a/test/perl/delayed_response/psgi.pl b/test/perl/delayed_response/psgi.pl new file mode 100644 index 00000000..f934c3a7 --- /dev/null +++ b/test/perl/delayed_response/psgi.pl @@ -0,0 +1,10 @@ +my $app = sub { + my ($environ) = @_; + + return sub { + (my $responder = shift)->([200, [ + 'Content-Type' => 'text/plain', + 'Content-Length' => '12' + ], ["Hello World!"]]); + } +}; diff --git a/test/perl/streaming_body/psgi.pl b/test/perl/streaming_body/psgi.pl new file mode 100644 index 00000000..a3e54ee0 --- /dev/null +++ b/test/perl/streaming_body/psgi.pl @@ -0,0 +1,13 @@ +my $app = sub { + my ($environ) = @_; + + return sub { + my $writer = (my $responder = shift)->([200, [ + 'Content-Type' => 'text/plain', + 'Content-Length' => '12' + ]]); + + $writer->write("Hello World!"); + $writer->close; + }; +}; diff --git a/test/test_perl_application.py b/test/test_perl_application.py index 4609c2bd..b169baab 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -200,5 +200,21 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): self.search_in_log(r'\[error\].+IOFake close\(\) called'), 'body io fake close') + def test_perl_delayed_response(self): + self.load('delayed_response') + + resp = self.get() + + self.assertEqual(resp['status'], 200, 'status') + self.assertEqual(resp['body'], 'Hello World!', 'body') + + def test_perl_streaming_body(self): + self.load('streaming_body') + + resp = self.get() + + self.assertEqual(resp['status'], 200, 'status') + self.assertEqual(resp['body'], 'Hello World!', 'body') + if __name__ == '__main__': TestUnitPerlApplication.main() -- cgit From 1b0595c74c62a73741f96794651fe284e4c1ec86 Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Fri, 1 Mar 2019 18:16:57 +0300 Subject: Packages: added JSR-340 compatibility notice to unit-jsc packages. --- pkg/deb/Makefile.jsc-common | 4 +++- pkg/deb/Makefile.jsc10 | 18 ++++++++++++++++++ pkg/deb/Makefile.jsc11 | 18 ++++++++++++++++++ pkg/deb/Makefile.jsc8 | 18 ++++++++++++++++++ pkg/rpm/Makefile.jsc11 | 4 ++++ pkg/rpm/Makefile.jsc8 | 4 ++++ 6 files changed, 65 insertions(+), 1 deletion(-) diff --git a/pkg/deb/Makefile.jsc-common b/pkg/deb/Makefile.jsc-common index 902d60df..42fdb12f 100644 --- a/pkg/deb/Makefile.jsc-common +++ b/pkg/deb/Makefile.jsc-common @@ -20,7 +20,9 @@ cat < Date: Fri, 1 Mar 2019 18:20:05 +0300 Subject: Fixed TLS connections hanging. After event is delivered from the kernel its further processing is blocked. Non-ready TSL I/O operation should mark connection I/O state as not ready to unblock events and to allow their further processing. Otherwise the connection hangs. --- src/nxt_openssl.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/nxt_openssl.c b/src/nxt_openssl.c index 99dd2077..c01f92c8 100644 --- a/src/nxt_openssl.c +++ b/src/nxt_openssl.c @@ -855,12 +855,11 @@ nxt_openssl_conn_test_error(nxt_task_t *task, nxt_conn_t *c, int ret, switch (tls->ssl_error) { case SSL_ERROR_WANT_READ: + c->socket.read_ready = 0; if (io != NXT_OPENSSL_READ) { nxt_fd_event_block_write(task->thread->engine, &c->socket); - c->socket.read_ready = 0; - if (nxt_fd_event_is_disabled(c->socket.read)) { nxt_fd_event_enable_read(task->thread->engine, &c->socket); } @@ -869,12 +868,11 @@ nxt_openssl_conn_test_error(nxt_task_t *task, nxt_conn_t *c, int ret, return NXT_AGAIN; case SSL_ERROR_WANT_WRITE: + c->socket.write_ready = 0; if (io != NXT_OPENSSL_WRITE) { nxt_fd_event_block_read(task->thread->engine, &c->socket); - c->socket.write_ready = 0; - if (nxt_fd_event_is_disabled(c->socket.write)) { nxt_fd_event_enable_write(task->thread->engine, &c->socket); } -- cgit From 050cfb6d5bc755189b6e95e54320cf4c2a8b7441 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Fri, 1 Mar 2019 18:23:38 +0300 Subject: Added version 1.8.0 CHANGES. --- CHANGES | 28 +++++++++++ docs/Makefile | 3 +- docs/changes.xml | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 72a21c3f..5398910e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,32 @@ +Changes with Unit 1.8.0 01 Mar 2019 + + *) Change: now three numbers are always used for versioning: major, + minor, and patch versions. + + *) Change: now QUERY_STRING is always defined even if the request does + not include the query component. + + *) Feature: basic internal request routing by Host, URI, and method. + + *) Feature: experimental support for Java Servlet Containers. + + *) Bugfix: segmentation fault might have occurred in the router process. + + *) Bugfix: various potential memory leaks. + + *) Bugfix: TLS connections might have stalled. + + *) Bugfix: some Perl applications might have failed to send the response + body. + + *) Bugfix: some compilers with specific flags might have produced + non-functioning builds; the bug had appeared in 1.5. + + *) Bugfix: Node.js package had wrong version number when installed from + sources. + + Changes with Unit 1.7.1 07 Feb 2019 *) Security: a heap memory buffer overflow might have been caused in the diff --git a/docs/Makefile b/docs/Makefile index 1f0ba451..ef9e596e 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -11,7 +11,8 @@ PACKAGES= unit \ unit-python3.5 unit-python3.6 unit-python3.7 \ unit-go unit-go1.7 unit-go1.8 unit-go1.9 unit-go1.10 \ unit-perl \ - unit-ruby + unit-ruby \ + unit-jsc-common unit-jsc8 unit-jsc10 unit-jsc11 all: changes changelogs diff --git a/docs/changes.xml b/docs/changes.xml index ea4298ba..e4b8158d 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -5,6 +5,147 @@ + + + + +Initial release of Java common packages for NGINX Unit. + + + + + + + + + + +Initial release of Java 8 module for NGINX Unit. + + + + + + + + + + +Initial release of Java 10 module for NGINX Unit. + + + + + + + + + + +Initial release of Java 11 module for NGINX Unit. + + + + + + + + + + +NGINX Unit updated to 1.8.0. + + + + + + + + + + +now three numbers are always used for versioning: major, minor, +and patch versions. + + + + + +now QUERY_STRING is always defined even if the request does not include +the query component. + + + + + +basic internal request routing by Host, URI, and method. + + + + + +experimental support for Java Servlet Containers. + + + + + +segmentation fault might have occurred in the router process. + + + + + +various potential memory leaks. + + + + + +TLS connections might have stalled. + + + + + +some Perl applications might have failed to send the response body. + + + + + +some compilers with specific flags might have produced non-functioning builds; +the bug had appeared in 1.5. + + + + + +Node.js package had wrong version number when installed from sources. + + + + + + " -ENV UNIT_VERSION 1.7.1-1~stretch +ENV UNIT_VERSION 1.8.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.go1.7-dev b/pkg/docker/Dockerfile.go1.7-dev index eb75e7b0..c0245ea7 100644 --- a/pkg/docker/Dockerfile.go1.7-dev +++ b/pkg/docker/Dockerfile.go1.7-dev @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.7.1-1~stretch +ENV UNIT_VERSION 1.8.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.go1.8-dev b/pkg/docker/Dockerfile.go1.8-dev index c60853ac..3c13c018 100644 --- a/pkg/docker/Dockerfile.go1.8-dev +++ b/pkg/docker/Dockerfile.go1.8-dev @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.7.1-1~stretch +ENV UNIT_VERSION 1.8.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.minimal b/pkg/docker/Dockerfile.minimal index f944da7f..b48f8410 100644 --- a/pkg/docker/Dockerfile.minimal +++ b/pkg/docker/Dockerfile.minimal @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.7.1-1~stretch +ENV UNIT_VERSION 1.8.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.perl5.24 b/pkg/docker/Dockerfile.perl5.24 index ee3ba4bf..4d5f502e 100644 --- a/pkg/docker/Dockerfile.perl5.24 +++ b/pkg/docker/Dockerfile.perl5.24 @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.7.1-1~stretch +ENV UNIT_VERSION 1.8.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.php7.0 b/pkg/docker/Dockerfile.php7.0 index 4468dafd..0afcf28a 100644 --- a/pkg/docker/Dockerfile.php7.0 +++ b/pkg/docker/Dockerfile.php7.0 @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.7.1-1~stretch +ENV UNIT_VERSION 1.8.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.python2.7 b/pkg/docker/Dockerfile.python2.7 index dec5d3cf..57619e26 100644 --- a/pkg/docker/Dockerfile.python2.7 +++ b/pkg/docker/Dockerfile.python2.7 @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.7.1-1~stretch +ENV UNIT_VERSION 1.8.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.python3.5 b/pkg/docker/Dockerfile.python3.5 index 0d93fb07..410f395c 100644 --- a/pkg/docker/Dockerfile.python3.5 +++ b/pkg/docker/Dockerfile.python3.5 @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.7.1-1~stretch +ENV UNIT_VERSION 1.8.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.ruby2.3 b/pkg/docker/Dockerfile.ruby2.3 index ff9474cc..7b674178 100644 --- a/pkg/docker/Dockerfile.ruby2.3 +++ b/pkg/docker/Dockerfile.ruby2.3 @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.7.1-1~stretch +ENV UNIT_VERSION 1.8.0-1~stretch RUN set -x \ && apt-get update \ -- cgit From df02b03824065389c73213b19736140442cf63bc Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Fri, 1 Mar 2019 18:26:10 +0300 Subject: Added tag 1.8.0 for changeset 0a18a14d169f --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 6420ce82..6f87491e 100644 --- a/.hgtags +++ b/.hgtags @@ -14,3 +14,4 @@ b3dee0cc5a4edd046345511769b5cfec49044f1c 1.5 d411e7fdee9e03036adb652f8d9f4c45a420bdd5 1.6 784b45adb0fe8bdd707510f59ed18309087e5c21 1.7 0f04ef991fbc1dadbc590ab7fb229d4f3d6357bc 1.7.1 +0a18a14d169f156f8e2daca35aa86d5a6dd9b1ae 1.8.0 -- cgit