myutil_first_not_empty $fne_data $arg_a $arg_b default;указывает, что следует найти первое непустое значение среди списка переменных $arg_a и $arg_b (которые соответствуют аргументам a и b строки запроса) и записать его в новую переменную $fne_data; в случае, если переменные $arg_a и $arg_b отсутствуют, либо не содержат непустых значений, в переменную $fne_data следует записать значение default. Будем предполагать, что литералы типа default являются равноправными членами наряду с переменными типа $arg_a и $arg_b и могут записываться где-то в середине списка аргументов (хотя очевидно, что из смысла директивы myutil_first_not_empty следует, что литерал должен быть только один и он должен находиться в конце списка). Сам список аргументов может быть произвольной длины.
Какие типы данных нам понадобятся? Ну, во-первых, как и в случае директивы myutil_var_alias, это массив типа ngx_array_t, соответствующий множеству переменных $fne_... (не забываем совет, приведенный в конце прошлой статьи и добавляем узнаваемый префикс, в данном случае $fne_, ко всем переменным, объявленным нашей новой директивой). Добавляем его в ngx_http_myutil_loc_conf_t:
typedef struct { ngx_array_t varalias_data; ngx_array_t first_not_empty_var_data; } ngx_http_myutil_loc_conf_t;В директиве myutil_var_alias в качестве элементов массива varalias_data мы использовали простую структуру
typedef struct { ngx_int_t self; ngx_int_t index; } ngx_http_myutil_var_elem_t;При этом значение self соответствовало индексу новой переменной, созданной директивой myutil_var_alias (ее первому аргументу), а значение index - исходной переменной, т.е. той, значение которой копировалось в новую переменную (это второй аргумент myutil_var_alias).
На этот раз мы имеем дело не с двумя аргументами, а с аргументом - новой переменной, создаваемой директивой myutil_first_not_empty, и списком неопределенной длины. Очевидно, что одним из элементов новой структуры должен быть массив:
typedef struct { ngx_array_t data; ngx_int_t index; } ngx_http_myutil_fne_elem_t;Здесь data - указанный массив, а index - индекс новой переменной. Какой тип элементов следует выбрать для массива data? Вспомните, что мы хотели записывать в него как переменные, так и литералы. Переменная легко идентифицируется индексом, а литерал - строкой, при этом будем считать, что литерал тоже имеет индекс, равный ( ngx_uint_t )NGX_ERROR: это позволит понимать, с чем мы имеем дело (переменной или литералом) в хендлере ngx_http_myutil_get_first_not_empty_value(), в котором происходит присваивание нового значения переменной $fne_.... Итак, тип элемента из списка аргументов директивы myutil_first_not_empty имеет вид
typedef struct { ngx_str_t key; ngx_int_t index; } ngx_http_myutil_var_handle_t;Добавляем директиву myutil_first_not_empty в список ngx_http_myutil_commands[], теперь он выглядит так:
static ngx_command_t ngx_http_myutil_commands[] = { { ngx_string( "myutil_var_alias" ), NGX_HTTP_LOC_CONF | NGX_HTTP_LIF_CONF | NGX_CONF_TAKE2, ngx_http_myutil_var_alias, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string( "myutil_first_not_empty" ), NGX_HTTP_LOC_CONF | NGX_HTTP_LIF_CONF | NGX_CONF_2MORE, ngx_http_myutil_first_not_empty, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, ngx_null_command };Также обновляем функции создания и слияния конфигураций уровня location:
static void * ngx_http_myutil_create_loc_conf( ngx_conf_t * cf ) { ngx_http_myutil_loc_conf_t * lcf = NULL; lcf = ngx_pcalloc( cf->pool, sizeof( ngx_http_myutil_loc_conf_t ) ); if ( ! lcf ) return NULL; if ( ngx_array_init( &lcf->varalias_data, cf->pool, 1, sizeof( ngx_http_myutil_var_elem_t ) ) != NGX_OK ) return NULL; if ( ngx_array_init( &lcf->first_not_empty_var_data, cf->pool, 1, sizeof( ngx_http_myutil_fne_elem_t ) ) != NGX_OK ) return NULL; return lcf; } static char * ngx_http_myutil_merge_loc_conf( ngx_conf_t * cf, void * parent, void * child ) { ngx_http_myutil_loc_conf_t * prev = parent; ngx_http_myutil_loc_conf_t * conf = child; ngx_uint_t i; /* all data in next sections will be imported from parent location in * addition to already collected data in child location */ for ( i = 0; i < prev->varalias_data.nelts; ++i ) { ngx_http_myutil_var_elem_t * elem = NULL; elem = ngx_array_push( &conf->varalias_data ); if ( ! elem ) return NGX_CONF_ERROR; *elem = ( ( ngx_http_myutil_var_elem_t * ) prev->varalias_data.elts )[ i ]; } for ( i = 0; i < prev->first_not_empty_var_data.nelts; ++i ) { ngx_http_myutil_fne_elem_t * elem = NULL; elem = ngx_array_push( &conf->first_not_empty_var_data ); if ( ! elem ) return NGX_CONF_ERROR; *elem = ( ( ngx_http_myutil_fne_elem_t * ) prev->first_not_empty_var_data.elts )[ i ]; } return NGX_CONF_OK; }Хендлером конфигурации при регистрации директивы myutil_first_not_empty в списке ngx_http_myutil_commands[] была объявлена функция ngx_http_myutil_first_not_empty(), вот ее тело (строки пронумерованы для дальнейших комментариев):
367 static char * ngx_http_myutil_first_not_empty( ngx_conf_t * cf, 368 ngx_command_t * cmd, void * conf ) 369 { 370 ngx_str_t * value = cf->args->elts; 371 ngx_http_variable_t * v = NULL; 372 ngx_http_myutil_loc_conf_t * lcf = conf; 373 ngx_int_t v_idx = NGX_ERROR; 374 ngx_uint_t * v_idx_ptr = NULL; 375 ngx_http_myutil_fne_elem_t * resvar = NULL; 376 377 ngx_uint_t i; 378 379 if ( value[ 1 ].data[ 0 ] != '$' ) 380 { 381 ngx_conf_log_error( NGX_LOG_EMERG, cf, 0, 382 "invalid variable name '%V'", &value[ 1 ] ); 383 return NGX_CONF_ERROR; 384 } 385 386 value[ 1 ].len--; 387 value[ 1 ].data++; 388 389 /* It must never occur due to command arguments number restriction */ 390 if ( cf->args->nelts < 3 ) 391 return NGX_CONF_ERROR; 392 393 resvar = ngx_array_push( &lcf->first_not_empty_var_data ); 394 395 if ( ! resvar ) 396 return NGX_CONF_ERROR; 397 398 if ( ngx_array_init( &resvar->data, cf->pool, cf->args->nelts - 2, 399 sizeof( ngx_http_myutil_var_handle_t ) ) != NGX_OK ) 400 return NGX_CONF_ERROR; 401 402 for ( i = 2; i < cf->args->nelts; ++i ) 403 { 404 ngx_http_myutil_var_handle_t * res = NULL; 405 ngx_int_t index = NGX_ERROR; 406 407 ngx_uint_t isvar = value[ i ].data[ 0 ] == '$' ? 1 : 0; 408 409 res = ngx_array_push( &resvar->data ); 410 411 if ( ! res ) 412 return NGX_CONF_ERROR; 413 414 if ( ! isvar ) 415 { 416 res->index = NGX_ERROR; 417 res->key = value[ i ]; 418 continue; 419 } 420 421 value[ i ].len--; 422 value[ i ].data++; 423 424 index = ngx_http_get_variable_index( cf, &value[ i ] ); 425 426 if ( index == NGX_ERROR ) 427 return NGX_CONF_ERROR; 428 429 res->index = index; 430 res->key = value[ i ]; 431 } 432 433 v = ngx_http_add_variable( cf, &value[ 1 ], NGX_HTTP_VAR_CHANGEABLE ); 434 435 if ( v == NULL ) 436 return NGX_CONF_ERROR; 437 438 v_idx = ngx_http_get_variable_index( cf, &value[ 1 ] ); 439 440 if ( v_idx == NGX_ERROR ) 441 return NGX_CONF_ERROR; 442 443 v_idx_ptr = ngx_pnalloc( cf->pool, sizeof( ngx_uint_t ) ); 444 445 if ( ! v_idx_ptr ) 446 return NGX_CONF_ERROR; 447 448 resvar->index = v_idx; 449 450 *v_idx_ptr = v_idx; 451 v->data = ( uintptr_t )v_idx_ptr; 452 v->get_handler = ngx_http_myutil_get_first_not_empty_value; 453 454 return NGX_CONF_OK; 455 }В строках 370-377 объявлены переменные, используемые внутри функции. В строках 379-384 проверяется, что первый аргумент директивы (переменная $fne_...) начинается с символа $. В строках 386-387 первый символ $ убирается из названия переменной (мы теперь и так знаем, что это переменная, и это нас устраивает). В строках 390-391 убеждаемся, что в директиву передано более одного аргумента (хотя эта проверка излишняя: флаг NGX_CONF_2MORE, указанный при объявлении директивы, не позволит запустить nginx, если в какой-либо из директив myutil_first_not_empty в конфигурации будет менее двух аргументов). В строках 393-400 мы создаем новый элемент массива first_not_empty_var_data из структуры ngx_http_myutil_loc_conf. На этом этапе мы знаем количество элементов внутри его подмассива data (это количество элементов директивы минус два), поэтому сразу же инициализируем data указанным значением элементов.
В строках 402-431 мы проходим по оставшимся аргументам директивы и заполняем инициализированные элементы массива data index и key значениями index и value[ i ] соответственно (строки 429-430). При этом, если текущий аргумент начинается с символа $ (т.е. является переменной), значение index получаем из возвращаемого значения функции ngx_http_get_variable_index(), а если это литерал - то записываем в качестве index значение ( ngx_uint_t )NGX_ERROR. В обоих случаях значением key будет имя текущего аргумента value[ i ] (хотя для аргумента-переменной это значение использоваться не будет).
В строках 433-441 создаем новую переменную v, соответствующую нашей переменной $fne_... и получаем ее индекс v_idx. Далее выделяем память под элемент типа ngx_uint_t (переменная v_idx_ptr): здесь будет храниться значение v_idx, а сам этот участок памяти будет передаваться в функцию ngx_http_myutul_get_first_not_empty_value() в качестве третьего аргумента для поиска нужного элемента в массиве first_not_empty_var_data (благодаря присваиванию resvar->index значения v_idx, а v->data - адреса v_idx_ptr в строках 448-451). В строке 452 присваиваем v->get_handler() адрес функции ngx_http_myutul_get_first_not_empty_value(), а затем возвращаем значение NGX_CONF_OK.
Хендлер ngx_http_myutil_get_first_not_empty_value() выглядит так:
225 static ngx_int_t ngx_http_myutil_get_first_not_empty_value( 226 ngx_http_request_t * r, ngx_http_variable_value_t * v, 227 uintptr_t data ) 228 { 229 ngx_http_myutil_loc_conf_t * lcf = NULL; 230 ngx_http_variable_value_t * var = NULL; 231 ngx_array_t * fne_data = NULL; 232 ngx_http_myutil_fne_elem_t * fne_elts = NULL; 233 ngx_http_myutil_fne_elem_t * fne_elem = NULL; 234 ngx_array_t * exprarray = NULL; 235 ngx_http_myutil_var_handle_t * expr = NULL; 236 ngx_int_t * index = NULL; 237 ngx_uint_t found_index = ( ngx_uint_t )NGX_ERROR; 238 239 ngx_uint_t i; 240 241 if ( ! data ) 242 return NGX_ERROR; 243 244 lcf = ngx_http_get_module_loc_conf( r, ngx_http_myutil_module ); 245 246 fne_data = &lcf->first_not_empty_var_data; 247 fne_elts = fne_data->elts; 248 index = ( ngx_int_t * )data; 249 250 for ( i = 0; i < fne_data->nelts; ++i ) 251 { 252 if ( *index != fne_elts[ i ].index ) 253 continue; 254 255 found_index = i; 256 break; 257 } 258 259 if ( found_index == ( ngx_uint_t )NGX_ERROR ) 260 return NGX_ERROR; 261 262 fne_elem = &fne_elts[ found_index ]; 263 264 exprarray = &fne_elem->data; 265 expr = exprarray->elts; 266 267 for ( i = 0; i < exprarray->nelts; ++i ) 268 { 269 ngx_http_variable_value_t * found = NULL; 270 271 if ( expr[ i ].index == NGX_ERROR ) 272 { 273 v->len = expr[ i ].key.len; 274 v->data = expr[ i ].key.data; 275 v->valid = 1; 276 v->no_cacheable = 0; 277 v->not_found = 0; 278 279 return NGX_OK; 280 } 281 282 found = ngx_http_get_indexed_variable( r, expr[ i ].index ); 283 284 if ( found && found->len > 0 ) 285 { 286 var = found; 287 break; 288 } 289 } 290 291 if ( ! var ) 292 return NGX_ERROR; 293 294 v->len = var->len; 295 v->data = var->data; 296 v->valid = 1; 297 v->no_cacheable = 0; 298 v->not_found = 0; 299 300 return NGX_OK; 301 }Здесь все относительно просто. В переменную index записываем значение третьего аргумента функции (значение, которое мы предусмотрительно сохранили в ngx_http_myutil_first_not_empty()) - строка 248. В строках 250-257 ищем соответствующий элемент в массиве first_not_empty_var_data. На найденный элемент будет указывать переменная fne_elem (строка 262). Получаем элементы массива data из fne_elem (строки 264-265). Далее проходим по всем элементам. Если текущий элемент переменная, то получаем ее значение по ее индексу (строка 282). В строках 284-288 проверяем, пусто ли это значение: если пусто, то продолжаем поиск, иначе выходим из цикла и присваиваем переменной v найденное значение (строки 294-298). Если же текущий элемент - литерал, то просто берем его значение key и присваиваем переменной v (строки 271-280; заметьте, что пустой литерал '' тоже сработает, так как проверка на expr[ i ].key.len не производится!).
Ну вот и все. Осталось запустить элементарную проверку. Будем проверять на следующей конфигурации:
events { worker_connections 1024; } http { server { listen 8010; server_name router; location /test_first_not_empty.html { myutil_first_not_empty $fne_1 $arg_a $arg_b default; myutil_first_not_empty $fne_2 $arg_aa $arg_bb 'no data'; if ($arg_c) { myutil_first_not_empty $fne_1 $arg_a $arg_c; echo $fne_1; echo $fne_2; break; } echo $fne_1; echo $fne_2; } } }Как видим, внутри блока if в локейшне /test_first_not_empty.html переменная $fne_1 переопределена. Нам нужно проверить, действительно ли она будет переопределена. Мы в праве этого ожидать, поскольку элементы массива first_not_empty_var_data родительской конфигурации добавляются после элементов дочерней конфигурации в функции ngx_http_myutil_merge_loc_conf(). Проверяем:
$ curl 'http://localhost:8010/test_first_not_empty.html?b=HiB' HiB no data
$ curl 'http://localhost:8010/test_first_not_empty.html?b=HiB&c=HiC' HiC no dataДействительно, присутствие аргумента c в строке запроса привело к тому, что сработала директива myutil_first_not_empty $fne_1 ... внутри if. Еще пара проверок:
$ curl 'http://localhost:8010/test_first_not_empty.html?bb=HiBB&a=HiA&b=HiB' HiA HiBB
$ curl 'http://localhost:8010/test_first_not_empty.html?aa=907' default 907Исходники модуля вместе с тестовой конфигурацией можно взять здесь.
Комментариев нет:
Отправить комментарий