summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--docs/changes.xml7
-rw-r--r--src/nxt_conf_validation.c37
-rw-r--r--src/python/nxt_python.c60
-rw-r--r--src/python/nxt_python.h2
-rw-r--r--src/python/nxt_python_asgi.c28
-rw-r--r--src/python/nxt_python_asgi_str.c2
-rw-r--r--src/python/nxt_python_wsgi.c49
-rw-r--r--test/python/prefix/asgi.py15
-rw-r--r--test/python/prefix/wsgi.py10
-rw-r--r--test/python/targets/asgi.py17
-rw-r--r--test/python/targets/wsgi.py9
-rw-r--r--test/test_asgi_application.py37
-rw-r--r--test/test_asgi_targets.py45
-rw-r--r--test/test_configuration.py86
-rw-r--r--test/test_python_application.py38
-rw-r--r--test/test_python_targets.py52
-rw-r--r--test/unit/applications/lang/python.py1
17 files changed, 472 insertions, 23 deletions
diff --git a/docs/changes.xml b/docs/changes.xml
index 3a24f9ca..80febc54 100644
--- a/docs/changes.xml
+++ b/docs/changes.xml
@@ -111,6 +111,13 @@ report the regex status in configure summary.
</para>
</change>
+<change type="feature">
+<para>
+new "prefix" attribute in Python configurations to set WSGI SCRIPT_NAME
+and ASGI root-path variables.
+</para>
+</change>
+
<change type="bugfix">
<para>
fix HTTP cookie parsing when the value contains an equals sign.
diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c
index c6e63c25..bf8aa760 100644
--- a/src/nxt_conf_validation.c
+++ b/src/nxt_conf_validation.c
@@ -128,6 +128,8 @@ static nxt_int_t nxt_conf_vldt_python_path_element(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value);
static nxt_int_t nxt_conf_vldt_python_protocol(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data);
+static nxt_int_t nxt_conf_vldt_python_prefix(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_threads(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_thread_stack_size(nxt_conf_validation_t *vldt,
@@ -796,6 +798,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = {
.validator = nxt_conf_vldt_targets_exclusive,
.u.string = "callable",
}, {
+ .name = nxt_string("prefix"),
+ .type = NXT_CONF_VLDT_STRING,
+ .validator = nxt_conf_vldt_targets_exclusive,
+ .u.string = "prefix",
+ }, {
.name = nxt_string("targets"),
.type = NXT_CONF_VLDT_OBJECT,
.validator = nxt_conf_vldt_targets,
@@ -814,6 +821,10 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_target_members[] = {
}, {
.name = nxt_string("callable"),
.type = NXT_CONF_VLDT_STRING,
+ }, {
+ .name = nxt_string("prefix"),
+ .type = NXT_CONF_VLDT_STRING,
+ .validator = nxt_conf_vldt_python_prefix,
},
NXT_CONF_VLDT_END
@@ -828,6 +839,10 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_notargets_members[] = {
}, {
.name = nxt_string("callable"),
.type = NXT_CONF_VLDT_STRING,
+ }, {
+ .name = nxt_string("prefix"),
+ .type = NXT_CONF_VLDT_STRING,
+ .validator = nxt_conf_vldt_python_prefix,
},
NXT_CONF_VLDT_NEXT(nxt_conf_vldt_python_common_members)
@@ -1871,6 +1886,28 @@ nxt_conf_vldt_python_protocol(nxt_conf_validation_t *vldt,
static nxt_int_t
+nxt_conf_vldt_python_prefix(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value, void *data)
+{
+ nxt_str_t prefix;
+
+ if (nxt_conf_type(value) != NXT_CONF_STRING) {
+ return nxt_conf_vldt_error(vldt, "The \"prefix\" must be a string "
+ "beginning with \"/\".");
+ }
+
+ nxt_conf_get_string(value, &prefix);
+
+ if (!nxt_strchr_start(&prefix, '/')) {
+ return nxt_conf_vldt_error(vldt, "The \"prefix\" must be a string "
+ "beginning with \"/\".");
+ }
+
+ return NXT_OK;
+}
+
+
+static nxt_int_t
nxt_conf_vldt_threads(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
void *data)
{
diff --git a/src/python/nxt_python.c b/src/python/nxt_python.c
index 8983815c..bdb04579 100644
--- a/src/python/nxt_python.c
+++ b/src/python/nxt_python.c
@@ -30,6 +30,8 @@ static nxt_int_t nxt_python_start(nxt_task_t *task,
nxt_process_data_t *data);
static nxt_int_t nxt_python_set_target(nxt_task_t *task,
nxt_python_target_t *target, nxt_conf_value_t *conf);
+nxt_inline nxt_int_t nxt_python_set_prefix(nxt_task_t *task,
+ nxt_python_target_t *target, nxt_conf_value_t *value);
static nxt_int_t nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value);
static int nxt_python_init_threads(nxt_python_app_conf_t *c);
static int nxt_python_ready_handler(nxt_unit_ctx_t *ctx);
@@ -389,6 +391,7 @@ nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target,
static nxt_str_t module_str = nxt_string("module");
static nxt_str_t callable_str = nxt_string("callable");
+ static nxt_str_t prefix_str = nxt_string("prefix");
module = obj = NULL;
@@ -436,6 +439,11 @@ nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target,
goto fail;
}
+ value = nxt_conf_get_object_member(conf, &prefix_str, NULL);
+ if (nxt_slow_path(nxt_python_set_prefix(task, target, value) != NXT_OK)) {
+ goto fail;
+ }
+
target->application = obj;
obj = NULL;
@@ -453,6 +461,48 @@ fail:
}
+nxt_inline nxt_int_t
+nxt_python_set_prefix(nxt_task_t *task, nxt_python_target_t *target,
+ nxt_conf_value_t *value)
+{
+ u_char *prefix;
+ nxt_str_t str;
+
+ if (value == NULL) {
+ return NXT_OK;
+ }
+
+ nxt_conf_get_string(value, &str);
+
+ if (str.length == 0) {
+ return NXT_OK;
+ }
+
+ if (str.start[str.length - 1] == '/') {
+ str.length--;
+ }
+ target->prefix.length = str.length;
+ prefix = nxt_malloc(str.length);
+ if (nxt_slow_path(prefix == NULL)) {
+ nxt_alert(task, "Failed to allocate target prefix string");
+ return NXT_ERROR;
+ }
+
+ target->py_prefix = PyString_FromStringAndSize((char *)str.start,
+ str.length);
+ if (nxt_slow_path(target->py_prefix == NULL)) {
+ nxt_free(prefix);
+ nxt_alert(task, "Python failed to allocate target prefix "
+ "string");
+ return NXT_ERROR;
+ }
+ nxt_memcpy(prefix, str.start, str.length);
+ target->prefix.start = prefix;
+
+ return NXT_OK;
+}
+
+
static nxt_int_t
nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value)
{
@@ -730,7 +780,8 @@ nxt_python_done_strings(nxt_python_string_t *pstr)
static void
nxt_python_atexit(void)
{
- nxt_int_t i;
+ nxt_int_t i;
+ nxt_python_target_t *target;
if (nxt_py_proto.done != NULL) {
nxt_py_proto.done();
@@ -740,7 +791,12 @@ nxt_python_atexit(void)
if (nxt_py_targets != NULL) {
for (i = 0; i < nxt_py_targets->count; i++) {
- Py_XDECREF(nxt_py_targets->target[i].application);
+ target = &nxt_py_targets->target[i];
+
+ Py_XDECREF(target->application);
+ Py_XDECREF(target->py_prefix);
+
+ nxt_free(target->prefix.start);
}
nxt_unit_free(NULL, nxt_py_targets);
diff --git a/src/python/nxt_python.h b/src/python/nxt_python.h
index eddb1cfc..37e6265e 100644
--- a/src/python/nxt_python.h
+++ b/src/python/nxt_python.h
@@ -40,6 +40,8 @@
typedef struct {
PyObject *application;
+ PyObject *py_prefix;
+ nxt_str_t prefix;
nxt_bool_t asgi_legacy;
} nxt_python_target_t;
diff --git a/src/python/nxt_python_asgi.c b/src/python/nxt_python_asgi.c
index 256bcdcc..587a17cf 100644
--- a/src/python/nxt_python_asgi.c
+++ b/src/python/nxt_python_asgi.c
@@ -27,7 +27,8 @@ static void nxt_py_asgi_remove_reader(nxt_unit_ctx_t *ctx,
static void nxt_py_asgi_request_handler(nxt_unit_request_info_t *req);
static void nxt_py_asgi_close_handler(nxt_unit_request_info_t *req);
-static PyObject *nxt_py_asgi_create_http_scope(nxt_unit_request_info_t *req);
+static PyObject *nxt_py_asgi_create_http_scope(nxt_unit_request_info_t *req,
+ nxt_python_target_t *app_target);
static PyObject *nxt_py_asgi_create_address(nxt_unit_sptr_t *sptr, uint8_t len,
uint16_t port);
static PyObject *nxt_py_asgi_create_ip_address(nxt_unit_sptr_t *sptr,
@@ -455,16 +456,16 @@ nxt_py_asgi_request_handler(nxt_unit_request_info_t *req)
goto release_send;
}
- scope = nxt_py_asgi_create_http_scope(req);
+ req->data = asgi;
+ target = &nxt_py_targets->target[req->request->app_target];
+
+ scope = nxt_py_asgi_create_http_scope(req, target);
if (nxt_slow_path(scope == NULL)) {
nxt_unit_request_done(req, NXT_UNIT_ERROR);
goto release_done;
}
- req->data = asgi;
- target = &nxt_py_targets->target[req->request->app_target];
-
if (!target->asgi_legacy) {
nxt_unit_req_debug(req, "Python call ASGI 3.0 application");
@@ -573,12 +574,14 @@ nxt_py_asgi_close_handler(nxt_unit_request_info_t *req)
static PyObject *
-nxt_py_asgi_create_http_scope(nxt_unit_request_info_t *req)
+nxt_py_asgi_create_http_scope(nxt_unit_request_info_t *req,
+ nxt_python_target_t *app_target)
{
char *p, *target, *query;
- uint32_t target_length, i;
+ uint32_t target_length, i, path_length;
PyObject *scope, *v, *type, *scheme;
PyObject *headers, *header;
+ nxt_str_t prefix;
nxt_unit_field_t *f;
nxt_unit_request_t *r;
@@ -612,6 +615,17 @@ nxt_py_asgi_create_http_scope(nxt_unit_request_info_t *req)
return NULL;
}
+ prefix = app_target->prefix;
+ path_length = r->path_length;
+ p = nxt_unit_sptr_get(&r->path);
+ if (prefix.length > 0
+ && ((path_length > prefix.length && p[prefix.length] == '/')
+ || path_length == prefix.length)
+ && memcmp(prefix.start, p, prefix.length) == 0)
+ {
+ SET_ITEM(scope, root_path, app_target->py_prefix);
+ }
+
p = nxt_unit_sptr_get(&r->version);
SET_ITEM(scope, http_version, p[7] == '1' ? nxt_py_1_1_str
: nxt_py_1_0_str)
diff --git a/src/python/nxt_python_asgi_str.c b/src/python/nxt_python_asgi_str.c
index 34422973..7171d52b 100644
--- a/src/python/nxt_python_asgi_str.c
+++ b/src/python/nxt_python_asgi_str.c
@@ -99,7 +99,7 @@ static nxt_python_string_t nxt_py_asgi_strings[] = {
{ nxt_string("query_string"), &nxt_py_query_string_str },
{ nxt_string("raw_path"), &nxt_py_raw_path_str },
{ nxt_string("result"), &nxt_py_result_str },
- { nxt_string("root_path"), &nxt_py_root_path_str }, // not used
+ { nxt_string("root_path"), &nxt_py_root_path_str },
{ nxt_string("scheme"), &nxt_py_scheme_str },
{ nxt_string("server"), &nxt_py_server_str },
{ nxt_string("set_exception"), &nxt_py_set_exception_str },
diff --git a/src/python/nxt_python_wsgi.c b/src/python/nxt_python_wsgi.c
index 34afd9a9..dfb31509 100644
--- a/src/python/nxt_python_wsgi.c
+++ b/src/python/nxt_python_wsgi.c
@@ -60,7 +60,8 @@ static void nxt_python_request_handler(nxt_unit_request_info_t *req);
static PyObject *nxt_python_create_environ(nxt_python_app_conf_t *c);
static PyObject *nxt_python_copy_environ(nxt_unit_request_info_t *req);
-static PyObject *nxt_python_get_environ(nxt_python_ctx_t *pctx);
+static PyObject *nxt_python_get_environ(nxt_python_ctx_t *pctx,
+ nxt_python_target_t *app_target);
static int nxt_python_add_sptr(nxt_python_ctx_t *pctx, PyObject *name,
nxt_unit_sptr_t *sptr, uint32_t size);
static int nxt_python_add_char(nxt_python_ctx_t *pctx, PyObject *name,
@@ -141,6 +142,7 @@ static PyObject *nxt_py_query_string_str;
static PyObject *nxt_py_remote_addr_str;
static PyObject *nxt_py_request_method_str;
static PyObject *nxt_py_request_uri_str;
+static PyObject *nxt_py_script_name_str;
static PyObject *nxt_py_server_addr_str;
static PyObject *nxt_py_server_name_str;
static PyObject *nxt_py_server_port_str;
@@ -160,6 +162,7 @@ static nxt_python_string_t nxt_python_strings[] = {
{ nxt_string("REMOTE_ADDR"), &nxt_py_remote_addr_str },
{ nxt_string("REQUEST_METHOD"), &nxt_py_request_method_str },
{ nxt_string("REQUEST_URI"), &nxt_py_request_uri_str },
+ { nxt_string("SCRIPT_NAME"), &nxt_py_script_name_str },
{ nxt_string("SERVER_ADDR"), &nxt_py_server_addr_str },
{ nxt_string("SERVER_NAME"), &nxt_py_server_name_str },
{ nxt_string("SERVER_PORT"), &nxt_py_server_port_str },
@@ -304,11 +307,12 @@ nxt_python_wsgi_done(void)
static void
nxt_python_request_handler(nxt_unit_request_info_t *req)
{
- int rc;
- PyObject *environ, *args, *response, *iterator, *item;
- PyObject *close, *result, *application;
- nxt_bool_t prepare_environ;
- nxt_python_ctx_t *pctx;
+ int rc;
+ PyObject *environ, *args, *response, *iterator, *item;
+ PyObject *close, *result;
+ nxt_bool_t prepare_environ;
+ nxt_python_ctx_t *pctx;
+ nxt_python_target_t *target;
pctx = req->ctx->data;
@@ -331,7 +335,9 @@ nxt_python_request_handler(nxt_unit_request_info_t *req)
prepare_environ = 1;
- environ = nxt_python_get_environ(pctx);
+ target = &nxt_py_targets->target[req->request->app_target];
+
+ environ = nxt_python_get_environ(pctx, target);
if (nxt_slow_path(environ == NULL)) {
rc = NXT_UNIT_ERROR;
goto done;
@@ -352,8 +358,7 @@ nxt_python_request_handler(nxt_unit_request_info_t *req)
Py_INCREF(pctx->start_resp);
PyTuple_SET_ITEM(args, 1, pctx->start_resp);
- application = nxt_py_targets->target[req->request->app_target].application;
- response = PyObject_CallObject(application, args);
+ response = PyObject_CallObject(target->application, args);
Py_DECREF(args);
@@ -584,11 +589,14 @@ nxt_python_copy_environ(nxt_unit_request_info_t *req)
static PyObject *
-nxt_python_get_environ(nxt_python_ctx_t *pctx)
+nxt_python_get_environ(nxt_python_ctx_t *pctx,
+ nxt_python_target_t *app_target)
{
int rc;
- uint32_t i, j, vl;
+ char *path;
+ uint32_t i, j, vl, path_length;
PyObject *environ;
+ nxt_str_t prefix;
nxt_unit_field_t *f, *f2;
nxt_unit_request_t *r;
@@ -608,8 +616,23 @@ nxt_python_get_environ(nxt_python_ctx_t *pctx)
r->target_length));
RC(nxt_python_add_sptr(pctx, nxt_py_query_string_str, &r->query,
r->query_length));
- RC(nxt_python_add_sptr(pctx, nxt_py_path_info_str, &r->path,
- r->path_length));
+
+ prefix = app_target->prefix;
+ path_length = r->path_length;
+ path = nxt_unit_sptr_get(&r->path);
+ if (prefix.length > 0
+ && ((path_length > prefix.length && path[prefix.length] == '/')
+ || path_length == prefix.length)
+ && memcmp(prefix.start, path, prefix.length) == 0)
+ {
+ RC(nxt_python_add_py_string(pctx, nxt_py_script_name_str,
+ app_target->py_prefix));
+
+ path += prefix.length;
+ path_length -= prefix.length;
+ }
+
+ RC(nxt_python_add_char(pctx, nxt_py_path_info_str, path, path_length));
RC(nxt_python_add_sptr(pctx, nxt_py_remote_addr_str, &r->remote,
r->remote_length));
diff --git a/test/python/prefix/asgi.py b/test/python/prefix/asgi.py
new file mode 100644
index 00000000..234f084f
--- /dev/null
+++ b/test/python/prefix/asgi.py
@@ -0,0 +1,15 @@
+async def application(scope, receive, send):
+ assert scope['type'] == 'http'
+
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 200,
+ 'headers': [
+ (b'content-length', b'0'),
+ (b'prefix', scope.get('root_path', 'NULL').encode()),
+ ],
+ }
+ )
+
+ await send({'type': 'http.response.body', 'body': b''})
diff --git a/test/python/prefix/wsgi.py b/test/python/prefix/wsgi.py
new file mode 100644
index 00000000..83b58c9a
--- /dev/null
+++ b/test/python/prefix/wsgi.py
@@ -0,0 +1,10 @@
+def application(environ, start_response):
+ start_response(
+ '200',
+ [
+ ('Content-Length', '0'),
+ ('Script-Name', environ.get('SCRIPT_NAME', 'NULL')),
+ ('Path-Info', environ['PATH_INFO']),
+ ],
+ )
+ return []
diff --git a/test/python/targets/asgi.py b/test/python/targets/asgi.py
index b51f3964..749ec5b1 100644
--- a/test/python/targets/asgi.py
+++ b/test/python/targets/asgi.py
@@ -22,6 +22,23 @@ async def application_200(scope, receive, send):
)
+async def application_prefix(scope, receive, send):
+ assert scope['type'] == 'http'
+
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 200,
+ 'headers': [
+ (b'content-length', b'0'),
+ (b'prefix', scope.get('root_path', 'NULL').encode()),
+ ],
+ }
+ )
+
+ await send({'type': 'http.response.body', 'body': b''})
+
+
def legacy_application_200(scope):
assert scope['type'] == 'http'
diff --git a/test/python/targets/wsgi.py b/test/python/targets/wsgi.py
index fa17ab87..3f3d4b27 100644
--- a/test/python/targets/wsgi.py
+++ b/test/python/targets/wsgi.py
@@ -6,3 +6,12 @@ def wsgi_target_a(env, start_response):
def wsgi_target_b(env, start_response):
start_response('200', [('Content-Length', '1')])
return [b'2']
+
+
+def wsgi_target_prefix(env, start_response):
+ data = u'%s %s' % (
+ env.get('SCRIPT_NAME', 'No Script Name'),
+ env['PATH_INFO'],
+ )
+ start_response('200', [('Content-Length', '%d' % len(data))])
+ return [data.encode('utf-8')]
diff --git a/test/test_asgi_application.py b/test/test_asgi_application.py
index 7f5f1578..121a2fbc 100644
--- a/test/test_asgi_application.py
+++ b/test/test_asgi_application.py
@@ -79,6 +79,43 @@ custom-header: BLAH
resp['headers']['query-string'] == 'var1=val1&var2=val2'
), 'query-string header'
+ def test_asgi_application_prefix(self):
+ self.load('prefix', prefix='/api/rest')
+
+ def set_prefix(prefix):
+ self.conf('"' + prefix + '"', 'applications/prefix/prefix')
+
+ def check_prefix(url, prefix):
+ resp = self.get(url=url)
+ assert resp['status'] == 200
+ assert resp['headers']['prefix'] == prefix
+
+ check_prefix('/ap', 'NULL')
+ check_prefix('/api', 'NULL')
+ check_prefix('/api/', 'NULL')
+ check_prefix('/api/res', 'NULL')
+ check_prefix('/api/restful', 'NULL')
+ check_prefix('/api/rest', '/api/rest')
+ check_prefix('/api/rest/', '/api/rest')
+ check_prefix('/api/rest/get', '/api/rest')
+ check_prefix('/api/rest/get/blah', '/api/rest')
+
+ set_prefix('/api/rest/')
+ check_prefix('/api/rest', '/api/rest')
+ check_prefix('/api/restful', 'NULL')
+ check_prefix('/api/rest/', '/api/rest')
+ check_prefix('/api/rest/blah', '/api/rest')
+
+ set_prefix('/app')
+ check_prefix('/ap', 'NULL')
+ check_prefix('/app', '/app')
+ check_prefix('/app/', '/app')
+ check_prefix('/application/', 'NULL')
+
+ set_prefix('/')
+ check_prefix('/', 'NULL')
+ check_prefix('/app', 'NULL')
+
def test_asgi_application_query_string_space(self):
self.load('query_string')
diff --git a/test/test_asgi_targets.py b/test/test_asgi_targets.py
index c1e345ef..84d7b3b0 100644
--- a/test/test_asgi_targets.py
+++ b/test/test_asgi_targets.py
@@ -90,3 +90,48 @@ class TestASGITargets(TestApplicationPython):
)
assert self.get(url='/1')['status'] != 200
+
+ def test_asgi_targets_prefix(self):
+ self.conf_targets(
+ {
+ "1": {
+ "module": "asgi",
+ "callable": "application_prefix",
+ "prefix": "/1/",
+ },
+ "2": {
+ "module": "asgi",
+ "callable": "application_prefix",
+ "prefix": "/api",
+ },
+ }
+ )
+ self.conf(
+ [
+ {
+ "match": {"uri": "/1*"},
+ "action": {"pass": "applications/targets/1"},
+ },
+ {
+ "match": {"uri": "*"},
+ "action": {"pass": "applications/targets/2"},
+ },
+ ],
+ "routes",
+ )
+
+ def check_prefix(url, prefix):
+ resp = self.get(url=url)
+ assert resp['status'] == 200
+ assert resp['headers']['prefix'] == prefix
+
+ check_prefix('/1', '/1')
+ check_prefix('/11', 'NULL')
+ check_prefix('/1/', '/1')
+ check_prefix('/', 'NULL')
+ check_prefix('/ap', 'NULL')
+ check_prefix('/api', '/api')
+ check_prefix('/api/', '/api')
+ check_prefix('/api/test/', '/api')
+ check_prefix('/apis', 'NULL')
+ check_prefix('/apis/', 'NULL')
diff --git a/test/test_configuration.py b/test/test_configuration.py
index 7c612db0..9c27222c 100644
--- a/test/test_configuration.py
+++ b/test/test_configuration.py
@@ -318,6 +318,92 @@ class TestConfiguration(TestControl):
assert 'success' in self.conf(conf)
+ def test_json_application_python_prefix(self):
+ conf = {
+ "applications": {
+ "sub-app": {
+ "type": "python",
+ "processes": {"spare": 0},
+ "path": "/app",
+ "module": "wsgi",
+ "prefix": "/app",
+ }
+ },
+ "listeners": {"*:7080": {"pass": "routes"}},
+ "routes": [
+ {
+ "match": {"uri": "/app/*"},
+ "action": {"pass": "applications/sub-app"},
+ }
+ ],
+ }
+
+ assert 'success' in self.conf(conf)
+
+ def test_json_application_prefix_target(self):
+ conf = {
+ "applications": {
+ "sub-app": {
+ "type": "python",
+ "processes": {"spare": 0},
+ "path": "/app",
+ "targets": {
+ "foo": {"module": "foo.wsgi", "prefix": "/app"},
+ "bar": {
+ "module": "bar.wsgi",
+ "callable": "bar",
+ "prefix": "/api",
+ },
+ },
+ }
+ },
+ "listeners": {"*:7080": {"pass": "routes"}},
+ "routes": [
+ {
+ "match": {"uri": "/app/*"},
+ "action": {"pass": "applications/sub-app/foo"},
+ },
+ {
+ "match": {"uri": "/api/*"},
+ "action": {"pass": "applications/sub-app/bar"},
+ },
+ ],
+ }
+
+ assert 'success' in self.conf(conf)
+
+ def test_json_application_invalid_python_prefix(self):
+ conf = {
+ "applications": {
+ "sub-app": {
+ "type": "python",
+ "processes": {"spare": 0},
+ "path": "/app",
+ "module": "wsgi",
+ "prefix": "app",
+ }
+ },
+ "listeners": {"*:7080": {"pass": "applications/sub-app"}},
+ }
+
+ assert 'error' in self.conf(conf)
+
+ def test_json_application_empty_python_prefix(self):
+ conf = {
+ "applications": {
+ "sub-app": {
+ "type": "python",
+ "processes": {"spare": 0},
+ "path": "/app",
+ "module": "wsgi",
+ "prefix": "",
+ }
+ },
+ "listeners": {"*:7080": {"pass": "applications/sub-app"}},
+ }
+
+ assert 'error' in self.conf(conf)
+
def test_json_application_many2(self):
conf = {
"applications": {
diff --git a/test/test_python_application.py b/test/test_python_application.py
index 946d2118..c9483b6a 100644
--- a/test/test_python_application.py
+++ b/test/test_python_application.py
@@ -94,6 +94,44 @@ custom-header: BLAH
resp['headers']['Query-String'] == ' var1= val1 & var2=val2'
), 'Query-String space 4'
+ def test_python_application_prefix(self):
+ self.load('prefix', prefix='/api/rest')
+
+ def set_prefix(prefix):
+ self.conf('"' + prefix + '"', 'applications/prefix/prefix')
+
+ def check_prefix(url, script_name, path_info):
+ resp = self.get(url=url)
+ assert resp['status'] == 200
+ assert resp['headers']['Script-Name'] == script_name
+ assert resp['headers']['Path-Info'] == path_info
+
+ check_prefix('/ap', 'NULL', '/ap')
+ check_prefix('/api', 'NULL', '/api')
+ check_prefix('/api/', 'NULL', '/api/')
+ check_prefix('/api/res', 'NULL', '/api/res')
+ check_prefix('/api/restful', 'NULL', '/api/restful')
+ check_prefix('/api/rest', '/api/rest', '')
+ check_prefix('/api/rest/', '/api/rest', '/')
+ check_prefix('/api/rest/get', '/api/rest', '/get')
+ check_prefix('/api/rest/get/blah', '/api/rest', '/get/blah')
+
+ set_prefix('/api/rest/')
+ check_prefix('/api/rest', '/api/rest', '')
+ check_prefix('/api/restful', 'NULL', '/api/restful')
+ check_prefix('/api/rest/', '/api/rest', '/')
+ check_prefix('/api/rest/blah', '/api/rest', '/blah')
+
+ set_prefix('/app')
+ check_prefix('/ap', 'NULL', '/ap')
+ check_prefix('/app', '/app', '')
+ check_prefix('/app/', '/app', '/')
+ check_prefix('/application/', 'NULL', '/application/')
+
+ set_prefix('/')
+ check_prefix('/', 'NULL', '/')
+ check_prefix('/app', 'NULL', '/app')
+
def test_python_application_query_string_empty(self):
self.load('query_string')
diff --git a/test/test_python_targets.py b/test/test_python_targets.py
index 8e9ecb87..ae271b5f 100644
--- a/test/test_python_targets.py
+++ b/test/test_python_targets.py
@@ -47,3 +47,55 @@ class TestPythonTargets(TestApplicationPython):
resp = self.get(url='/2')
assert resp['status'] == 200
assert resp['body'] == '2'
+
+ def test_python_targets_prefix(self):
+ assert 'success' in self.conf(
+ {
+ "listeners": {"*:7080": {"pass": "routes"}},
+ "routes": [
+ {
+ "match": {"uri": ["/app*"]},
+ "action": {"pass": "applications/targets/app"},
+ },
+ {
+ "match": {"uri": "*"},
+ "action": {"pass": "applications/targets/catchall"},
+ },
+ ],
+ "applications": {
+ "targets": {
+ "type": "python",
+ "working_directory": option.test_dir
+ + "/python/targets/",
+ "path": option.test_dir + '/python/targets/',
+ "protocol": "wsgi",
+ "targets": {
+ "app": {
+ "module": "wsgi",
+ "callable": "wsgi_target_prefix",
+ "prefix": "/app/",
+ },
+ "catchall": {
+ "module": "wsgi",
+ "callable": "wsgi_target_prefix",
+ "prefix": "/api",
+ },
+ },
+ }
+ },
+ }
+ )
+
+ def check_prefix(url, body):
+ resp = self.get(url=url)
+ assert resp['status'] == 200
+ assert resp['body'] == body
+
+ check_prefix('/app', '/app ')
+ check_prefix('/app/', '/app /')
+ check_prefix('/app/rest/user/', '/app /rest/user/')
+ check_prefix('/catchall', 'No Script Name /catchall')
+ check_prefix('/api', '/api ')
+ check_prefix('/api/', '/api /')
+ check_prefix('/apis', 'No Script Name /apis')
+ check_prefix('/api/users/', '/api /users/')
diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py
index 1e38f3fa..3768cf07 100644
--- a/test/unit/applications/lang/python.py
+++ b/test/unit/applications/lang/python.py
@@ -50,6 +50,7 @@ class TestApplicationPython(TestApplicationProto):
'protocol',
'targets',
'threads',
+ 'prefix',
):
if attr in kwargs:
app[attr] = kwargs.pop(attr)