Python: Added "prefix" to configuration.
This patch gives users the option to set a `"prefix"` attribute for Python applications, either at the top level or for specific `"target"`s. If the attribute is present, the value of `"prefix"` must be a string beginning with `"/"`. If the value of the `"prefix"` attribute is longer than 1 character and ends in `"/"`, the trailing `"/"` is stripped. The purpose of the `"prefix"` attribute is to set the `SCRIPT_NAME` context value for WSGI applications and the `root_path` context value for ASGI applications, allowing applications to properly route requests regardless of the path that the server uses to expose the application. The context value is only set if the request's URL path begins with the value of the `"prefix"` attribute. In all other cases, the `SCRIPT_NAME` or `root_path` values are not set. In addition, for WSGI applications, the value of `"prefix"` will be stripped from the beginning of the request's URL path before it is sent to the application. Reviewed-by: Andrei Zeliankou <zelenkov@nginx.com> Reviewed-by: Artem Konev <artem.konev@nginx.com> Signed-off-by: Alejandro Colomar <alx@nginx.com>
This commit is contained in:
committed by
Alejandro Colomar
parent
7a81d9d61d
commit
6dae517ebd
@@ -111,6 +111,13 @@ report the regex status in configure summary.
|
|||||||
</para>
|
</para>
|
||||||
</change>
|
</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">
|
<change type="bugfix">
|
||||||
<para>
|
<para>
|
||||||
fix HTTP cookie parsing when the value contains an equals sign.
|
fix HTTP cookie parsing when the value contains an equals sign.
|
||||||
|
|||||||
@@ -128,6 +128,8 @@ static nxt_int_t nxt_conf_vldt_python_path_element(nxt_conf_validation_t *vldt,
|
|||||||
nxt_conf_value_t *value);
|
nxt_conf_value_t *value);
|
||||||
static nxt_int_t nxt_conf_vldt_python_protocol(nxt_conf_validation_t *vldt,
|
static nxt_int_t nxt_conf_vldt_python_protocol(nxt_conf_validation_t *vldt,
|
||||||
nxt_conf_value_t *value, void *data);
|
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,
|
static nxt_int_t nxt_conf_vldt_threads(nxt_conf_validation_t *vldt,
|
||||||
nxt_conf_value_t *value, void *data);
|
nxt_conf_value_t *value, void *data);
|
||||||
static nxt_int_t nxt_conf_vldt_thread_stack_size(nxt_conf_validation_t *vldt,
|
static nxt_int_t nxt_conf_vldt_thread_stack_size(nxt_conf_validation_t *vldt,
|
||||||
@@ -795,6 +797,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = {
|
|||||||
.type = NXT_CONF_VLDT_STRING,
|
.type = NXT_CONF_VLDT_STRING,
|
||||||
.validator = nxt_conf_vldt_targets_exclusive,
|
.validator = nxt_conf_vldt_targets_exclusive,
|
||||||
.u.string = "callable",
|
.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"),
|
.name = nxt_string("targets"),
|
||||||
.type = NXT_CONF_VLDT_OBJECT,
|
.type = NXT_CONF_VLDT_OBJECT,
|
||||||
@@ -814,6 +821,10 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_target_members[] = {
|
|||||||
}, {
|
}, {
|
||||||
.name = nxt_string("callable"),
|
.name = nxt_string("callable"),
|
||||||
.type = NXT_CONF_VLDT_STRING,
|
.type = NXT_CONF_VLDT_STRING,
|
||||||
|
}, {
|
||||||
|
.name = nxt_string("prefix"),
|
||||||
|
.type = NXT_CONF_VLDT_STRING,
|
||||||
|
.validator = nxt_conf_vldt_python_prefix,
|
||||||
},
|
},
|
||||||
|
|
||||||
NXT_CONF_VLDT_END
|
NXT_CONF_VLDT_END
|
||||||
@@ -828,6 +839,10 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_notargets_members[] = {
|
|||||||
}, {
|
}, {
|
||||||
.name = nxt_string("callable"),
|
.name = nxt_string("callable"),
|
||||||
.type = NXT_CONF_VLDT_STRING,
|
.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)
|
NXT_CONF_VLDT_NEXT(nxt_conf_vldt_python_common_members)
|
||||||
@@ -1870,6 +1885,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
|
static nxt_int_t
|
||||||
nxt_conf_vldt_threads(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
|
nxt_conf_vldt_threads(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
|
||||||
void *data)
|
void *data)
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ static nxt_int_t nxt_python_start(nxt_task_t *task,
|
|||||||
nxt_process_data_t *data);
|
nxt_process_data_t *data);
|
||||||
static nxt_int_t nxt_python_set_target(nxt_task_t *task,
|
static nxt_int_t nxt_python_set_target(nxt_task_t *task,
|
||||||
nxt_python_target_t *target, nxt_conf_value_t *conf);
|
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 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_init_threads(nxt_python_app_conf_t *c);
|
||||||
static int nxt_python_ready_handler(nxt_unit_ctx_t *ctx);
|
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 module_str = nxt_string("module");
|
||||||
static nxt_str_t callable_str = nxt_string("callable");
|
static nxt_str_t callable_str = nxt_string("callable");
|
||||||
|
static nxt_str_t prefix_str = nxt_string("prefix");
|
||||||
|
|
||||||
module = obj = NULL;
|
module = obj = NULL;
|
||||||
|
|
||||||
@@ -436,6 +439,11 @@ nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target,
|
|||||||
goto fail;
|
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;
|
target->application = obj;
|
||||||
obj = NULL;
|
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
|
static nxt_int_t
|
||||||
nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value)
|
nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value)
|
||||||
{
|
{
|
||||||
@@ -731,6 +781,7 @@ static void
|
|||||||
nxt_python_atexit(void)
|
nxt_python_atexit(void)
|
||||||
{
|
{
|
||||||
nxt_int_t i;
|
nxt_int_t i;
|
||||||
|
nxt_python_target_t *target;
|
||||||
|
|
||||||
if (nxt_py_proto.done != NULL) {
|
if (nxt_py_proto.done != NULL) {
|
||||||
nxt_py_proto.done();
|
nxt_py_proto.done();
|
||||||
@@ -740,7 +791,12 @@ nxt_python_atexit(void)
|
|||||||
|
|
||||||
if (nxt_py_targets != NULL) {
|
if (nxt_py_targets != NULL) {
|
||||||
for (i = 0; i < nxt_py_targets->count; i++) {
|
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);
|
nxt_unit_free(NULL, nxt_py_targets);
|
||||||
|
|||||||
@@ -40,6 +40,8 @@
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject *application;
|
PyObject *application;
|
||||||
|
PyObject *py_prefix;
|
||||||
|
nxt_str_t prefix;
|
||||||
nxt_bool_t asgi_legacy;
|
nxt_bool_t asgi_legacy;
|
||||||
} nxt_python_target_t;
|
} nxt_python_target_t;
|
||||||
|
|
||||||
|
|||||||
@@ -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_request_handler(nxt_unit_request_info_t *req);
|
||||||
static void nxt_py_asgi_close_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,
|
static PyObject *nxt_py_asgi_create_address(nxt_unit_sptr_t *sptr, uint8_t len,
|
||||||
uint16_t port);
|
uint16_t port);
|
||||||
static PyObject *nxt_py_asgi_create_ip_address(nxt_unit_sptr_t *sptr,
|
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;
|
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)) {
|
if (nxt_slow_path(scope == NULL)) {
|
||||||
nxt_unit_request_done(req, NXT_UNIT_ERROR);
|
nxt_unit_request_done(req, NXT_UNIT_ERROR);
|
||||||
|
|
||||||
goto release_done;
|
goto release_done;
|
||||||
}
|
}
|
||||||
|
|
||||||
req->data = asgi;
|
|
||||||
target = &nxt_py_targets->target[req->request->app_target];
|
|
||||||
|
|
||||||
if (!target->asgi_legacy) {
|
if (!target->asgi_legacy) {
|
||||||
nxt_unit_req_debug(req, "Python call ASGI 3.0 application");
|
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 *
|
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;
|
char *p, *target, *query;
|
||||||
uint32_t target_length, i;
|
uint32_t target_length, i, path_length;
|
||||||
PyObject *scope, *v, *type, *scheme;
|
PyObject *scope, *v, *type, *scheme;
|
||||||
PyObject *headers, *header;
|
PyObject *headers, *header;
|
||||||
|
nxt_str_t prefix;
|
||||||
nxt_unit_field_t *f;
|
nxt_unit_field_t *f;
|
||||||
nxt_unit_request_t *r;
|
nxt_unit_request_t *r;
|
||||||
|
|
||||||
@@ -612,6 +615,17 @@ nxt_py_asgi_create_http_scope(nxt_unit_request_info_t *req)
|
|||||||
return NULL;
|
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);
|
p = nxt_unit_sptr_get(&r->version);
|
||||||
SET_ITEM(scope, http_version, p[7] == '1' ? nxt_py_1_1_str
|
SET_ITEM(scope, http_version, p[7] == '1' ? nxt_py_1_1_str
|
||||||
: nxt_py_1_0_str)
|
: nxt_py_1_0_str)
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ static nxt_python_string_t nxt_py_asgi_strings[] = {
|
|||||||
{ nxt_string("query_string"), &nxt_py_query_string_str },
|
{ nxt_string("query_string"), &nxt_py_query_string_str },
|
||||||
{ nxt_string("raw_path"), &nxt_py_raw_path_str },
|
{ nxt_string("raw_path"), &nxt_py_raw_path_str },
|
||||||
{ nxt_string("result"), &nxt_py_result_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("scheme"), &nxt_py_scheme_str },
|
||||||
{ nxt_string("server"), &nxt_py_server_str },
|
{ nxt_string("server"), &nxt_py_server_str },
|
||||||
{ nxt_string("set_exception"), &nxt_py_set_exception_str },
|
{ nxt_string("set_exception"), &nxt_py_set_exception_str },
|
||||||
|
|||||||
@@ -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_create_environ(nxt_python_app_conf_t *c);
|
||||||
static PyObject *nxt_python_copy_environ(nxt_unit_request_info_t *req);
|
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,
|
static int nxt_python_add_sptr(nxt_python_ctx_t *pctx, PyObject *name,
|
||||||
nxt_unit_sptr_t *sptr, uint32_t size);
|
nxt_unit_sptr_t *sptr, uint32_t size);
|
||||||
static int nxt_python_add_char(nxt_python_ctx_t *pctx, PyObject *name,
|
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_remote_addr_str;
|
||||||
static PyObject *nxt_py_request_method_str;
|
static PyObject *nxt_py_request_method_str;
|
||||||
static PyObject *nxt_py_request_uri_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_addr_str;
|
||||||
static PyObject *nxt_py_server_name_str;
|
static PyObject *nxt_py_server_name_str;
|
||||||
static PyObject *nxt_py_server_port_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("REMOTE_ADDR"), &nxt_py_remote_addr_str },
|
||||||
{ nxt_string("REQUEST_METHOD"), &nxt_py_request_method_str },
|
{ nxt_string("REQUEST_METHOD"), &nxt_py_request_method_str },
|
||||||
{ nxt_string("REQUEST_URI"), &nxt_py_request_uri_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_ADDR"), &nxt_py_server_addr_str },
|
||||||
{ nxt_string("SERVER_NAME"), &nxt_py_server_name_str },
|
{ nxt_string("SERVER_NAME"), &nxt_py_server_name_str },
|
||||||
{ nxt_string("SERVER_PORT"), &nxt_py_server_port_str },
|
{ nxt_string("SERVER_PORT"), &nxt_py_server_port_str },
|
||||||
@@ -306,9 +309,10 @@ nxt_python_request_handler(nxt_unit_request_info_t *req)
|
|||||||
{
|
{
|
||||||
int rc;
|
int rc;
|
||||||
PyObject *environ, *args, *response, *iterator, *item;
|
PyObject *environ, *args, *response, *iterator, *item;
|
||||||
PyObject *close, *result, *application;
|
PyObject *close, *result;
|
||||||
nxt_bool_t prepare_environ;
|
nxt_bool_t prepare_environ;
|
||||||
nxt_python_ctx_t *pctx;
|
nxt_python_ctx_t *pctx;
|
||||||
|
nxt_python_target_t *target;
|
||||||
|
|
||||||
pctx = req->ctx->data;
|
pctx = req->ctx->data;
|
||||||
|
|
||||||
@@ -331,7 +335,9 @@ nxt_python_request_handler(nxt_unit_request_info_t *req)
|
|||||||
|
|
||||||
prepare_environ = 1;
|
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)) {
|
if (nxt_slow_path(environ == NULL)) {
|
||||||
rc = NXT_UNIT_ERROR;
|
rc = NXT_UNIT_ERROR;
|
||||||
goto done;
|
goto done;
|
||||||
@@ -352,8 +358,7 @@ nxt_python_request_handler(nxt_unit_request_info_t *req)
|
|||||||
Py_INCREF(pctx->start_resp);
|
Py_INCREF(pctx->start_resp);
|
||||||
PyTuple_SET_ITEM(args, 1, pctx->start_resp);
|
PyTuple_SET_ITEM(args, 1, pctx->start_resp);
|
||||||
|
|
||||||
application = nxt_py_targets->target[req->request->app_target].application;
|
response = PyObject_CallObject(target->application, args);
|
||||||
response = PyObject_CallObject(application, args);
|
|
||||||
|
|
||||||
Py_DECREF(args);
|
Py_DECREF(args);
|
||||||
|
|
||||||
@@ -584,11 +589,14 @@ nxt_python_copy_environ(nxt_unit_request_info_t *req)
|
|||||||
|
|
||||||
|
|
||||||
static PyObject *
|
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;
|
int rc;
|
||||||
uint32_t i, j, vl;
|
char *path;
|
||||||
|
uint32_t i, j, vl, path_length;
|
||||||
PyObject *environ;
|
PyObject *environ;
|
||||||
|
nxt_str_t prefix;
|
||||||
nxt_unit_field_t *f, *f2;
|
nxt_unit_field_t *f, *f2;
|
||||||
nxt_unit_request_t *r;
|
nxt_unit_request_t *r;
|
||||||
|
|
||||||
@@ -608,8 +616,23 @@ nxt_python_get_environ(nxt_python_ctx_t *pctx)
|
|||||||
r->target_length));
|
r->target_length));
|
||||||
RC(nxt_python_add_sptr(pctx, nxt_py_query_string_str, &r->query,
|
RC(nxt_python_add_sptr(pctx, nxt_py_query_string_str, &r->query,
|
||||||
r->query_length));
|
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,
|
RC(nxt_python_add_sptr(pctx, nxt_py_remote_addr_str, &r->remote,
|
||||||
r->remote_length));
|
r->remote_length));
|
||||||
|
|||||||
15
test/python/prefix/asgi.py
Normal file
15
test/python/prefix/asgi.py
Normal file
@@ -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''})
|
||||||
10
test/python/prefix/wsgi.py
Normal file
10
test/python/prefix/wsgi.py
Normal file
@@ -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 []
|
||||||
@@ -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):
|
def legacy_application_200(scope):
|
||||||
assert scope['type'] == 'http'
|
assert scope['type'] == 'http'
|
||||||
|
|
||||||
|
|||||||
@@ -6,3 +6,12 @@ def wsgi_target_a(env, start_response):
|
|||||||
def wsgi_target_b(env, start_response):
|
def wsgi_target_b(env, start_response):
|
||||||
start_response('200', [('Content-Length', '1')])
|
start_response('200', [('Content-Length', '1')])
|
||||||
return [b'2']
|
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')]
|
||||||
|
|||||||
@@ -79,6 +79,43 @@ custom-header: BLAH
|
|||||||
resp['headers']['query-string'] == 'var1=val1&var2=val2'
|
resp['headers']['query-string'] == 'var1=val1&var2=val2'
|
||||||
), 'query-string header'
|
), '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):
|
def test_asgi_application_query_string_space(self):
|
||||||
self.load('query_string')
|
self.load('query_string')
|
||||||
|
|
||||||
|
|||||||
@@ -90,3 +90,48 @@ class TestASGITargets(TestApplicationPython):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert self.get(url='/1')['status'] != 200
|
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')
|
||||||
|
|||||||
@@ -318,6 +318,92 @@ class TestConfiguration(TestControl):
|
|||||||
|
|
||||||
assert 'success' in self.conf(conf)
|
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):
|
def test_json_application_many2(self):
|
||||||
conf = {
|
conf = {
|
||||||
"applications": {
|
"applications": {
|
||||||
|
|||||||
@@ -94,6 +94,44 @@ custom-header: BLAH
|
|||||||
resp['headers']['Query-String'] == ' var1= val1 & var2=val2'
|
resp['headers']['Query-String'] == ' var1= val1 & var2=val2'
|
||||||
), 'Query-String space 4'
|
), '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):
|
def test_python_application_query_string_empty(self):
|
||||||
self.load('query_string')
|
self.load('query_string')
|
||||||
|
|
||||||
|
|||||||
@@ -47,3 +47,55 @@ class TestPythonTargets(TestApplicationPython):
|
|||||||
resp = self.get(url='/2')
|
resp = self.get(url='/2')
|
||||||
assert resp['status'] == 200
|
assert resp['status'] == 200
|
||||||
assert resp['body'] == '2'
|
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/')
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class TestApplicationPython(TestApplicationProto):
|
|||||||
'protocol',
|
'protocol',
|
||||||
'targets',
|
'targets',
|
||||||
'threads',
|
'threads',
|
||||||
|
'prefix',
|
||||||
):
|
):
|
||||||
if attr in kwargs:
|
if attr in kwargs:
|
||||||
app[attr] = kwargs.pop(attr)
|
app[attr] = kwargs.pop(attr)
|
||||||
|
|||||||
Reference in New Issue
Block a user