xref: /unit/src/nxt_application.c (revision 673:9fa79c719a17)
1 
2 /*
3  * Copyright (C) Max Romanov
4  * Copyright (C) Igor Sysoev
5  * Copyright (C) Valentin V. Bartenev
6  * Copyright (C) NGINX, Inc.
7  */
8 
9 #include <nxt_main.h>
10 #include <nxt_runtime.h>
11 #include <nxt_main_process.h>
12 #include <nxt_router.h>
13 #include <nxt_http.h>
14 #include <nxt_application.h>
15 
16 #include <glob.h>
17 
18 
19 typedef struct {
20     nxt_app_type_t  type;
21     nxt_str_t       version;
22     nxt_str_t       file;
23 } nxt_module_t;
24 
25 
26 static nxt_buf_t *nxt_discovery_modules(nxt_task_t *task, const char *path);
27 static nxt_int_t nxt_discovery_module(nxt_task_t *task, nxt_mp_t *mp,
28     nxt_array_t *modules, const char *name);
29 static void nxt_discovery_completion_handler(nxt_task_t *task, void *obj,
30     void *data);
31 static void nxt_discovery_quit(nxt_task_t *task, nxt_port_recv_msg_t *msg,
32     void *data);
33 static nxt_app_module_t *nxt_app_module_load(nxt_task_t *task,
34     const char *name);
35 
36 static void nxt_app_http_release(nxt_task_t *task, void *obj, void *data);
37 
38 
39 static uint32_t  compat[] = {
40     NXT_VERNUM, NXT_DEBUG,
41 };
42 
43 
44 nxt_str_t  nxt_server = nxt_string(NXT_SERVER);
45 
46 
47 static nxt_thread_mutex_t        nxt_app_mutex;
48 static nxt_thread_cond_t         nxt_app_cond;
49 
50 static nxt_application_module_t  *nxt_app;
51 
52 
53 nxt_int_t
54 nxt_discovery_start(nxt_task_t *task, void *data)
55 {
56     uint32_t       stream;
57     nxt_buf_t      *b;
58     nxt_int_t      ret;
59     nxt_port_t     *main_port, *discovery_port;
60     nxt_runtime_t  *rt;
61 
62     nxt_debug(task, "DISCOVERY");
63 
64     rt = task->thread->runtime;
65 
66     b = nxt_discovery_modules(task, rt->modules);
67     if (nxt_slow_path(b == NULL)) {
68         return NXT_ERROR;
69     }
70 
71     main_port = rt->port_by_type[NXT_PROCESS_MAIN];
72     discovery_port = rt->port_by_type[NXT_PROCESS_DISCOVERY];
73 
74     stream = nxt_port_rpc_register_handler(task, discovery_port,
75                                            nxt_discovery_quit,
76                                            nxt_discovery_quit,
77                                            main_port->pid, NULL);
78 
79     if (nxt_slow_path(stream == 0)) {
80         return NXT_ERROR;
81     }
82 
83     ret = nxt_port_socket_write(task, main_port, NXT_PORT_MSG_MODULES, -1,
84                                 stream, discovery_port->id, b);
85 
86     if (nxt_slow_path(ret != NXT_OK)) {
87         nxt_port_rpc_cancel(task, discovery_port, stream);
88         return NXT_ERROR;
89     }
90 
91     return NXT_OK;
92 }
93 
94 
95 static nxt_buf_t *
96 nxt_discovery_modules(nxt_task_t *task, const char *path)
97 {
98     char          *name;
99     u_char        *p, *end;
100     size_t        size;
101     glob_t        glb;
102     nxt_mp_t      *mp;
103     nxt_buf_t     *b;
104     nxt_int_t     ret;
105     nxt_uint_t    i, n;
106     nxt_array_t   *modules;
107     nxt_module_t  *module;
108 
109     b = NULL;
110 
111     mp = nxt_mp_create(1024, 128, 256, 32);
112     if (mp == NULL) {
113         return b;
114     }
115 
116     ret = glob(path, 0, NULL, &glb);
117 
118     n = glb.gl_pathc;
119 
120     if (ret != 0) {
121         nxt_log(task, NXT_LOG_NOTICE,
122                 "no modules matching: \"%s\" found", path);
123         n = 0;
124     }
125 
126     modules = nxt_array_create(mp, n, sizeof(nxt_module_t));
127     if (modules == NULL) {
128         goto fail;
129     }
130 
131     for (i = 0; i < n; i++) {
132         name = glb.gl_pathv[i];
133 
134         ret = nxt_discovery_module(task, mp, modules, name);
135         if (ret != NXT_OK) {
136             goto fail;
137         }
138     }
139 
140     size = sizeof("[]") - 1;
141     module = modules->elts;
142     n = modules->nelts;
143 
144     for (i = 0; i < n; i++) {
145         nxt_debug(task, "module: %d %V %V",
146                   module[i].type, &module[i].version, &module[i].file);
147 
148         size += sizeof("{\"type\": ,") - 1;
149         size += sizeof(" \"version\": \"\",") - 1;
150         size += sizeof(" \"file\": \"\"},") - 1;
151 
152         size += NXT_INT_T_LEN
153                 + module[i].version.length
154                 + module[i].file.length;
155     }
156 
157     b = nxt_buf_mem_alloc(mp, size, 0);
158     if (b == NULL) {
159         goto fail;
160     }
161 
162     b->completion_handler = nxt_discovery_completion_handler;
163 
164     p = b->mem.free;
165     end = b->mem.end;
166     *p++ = '[';
167 
168     for (i = 0; i < n; i++) {
169         p = nxt_sprintf(p, end,
170                       "{\"type\": %d, \"version\": \"%V\", \"file\": \"%V\"},",
171                       module[i].type, &module[i].version, &module[i].file);
172     }
173 
174     *p++ = ']';
175     b->mem.free = p;
176 
177 fail:
178 
179     globfree(&glb);
180 
181     return b;
182 }
183 
184 
185 static nxt_int_t
186 nxt_discovery_module(nxt_task_t *task, nxt_mp_t *mp, nxt_array_t *modules,
187     const char *name)
188 {
189     void                      *dl;
190     nxt_str_t                 version;
191     nxt_int_t                 ret;
192     nxt_uint_t                i, n;
193     nxt_module_t              *module;
194     nxt_app_type_t            type;
195     nxt_application_module_t  *app;
196 
197     /*
198      * Only memory allocation failure should return NXT_ERROR.
199      * Any module processing errors are ignored.
200      */
201     ret = NXT_ERROR;
202 
203     dl = dlopen(name, RTLD_GLOBAL | RTLD_NOW);
204 
205     if (dl == NULL) {
206         nxt_alert(task, "dlopen(\"%s\"), failed: \"%s\"", name, dlerror());
207         return NXT_OK;
208     }
209 
210     app = dlsym(dl, "nxt_app_module");
211 
212     if (app != NULL) {
213         nxt_log(task, NXT_LOG_NOTICE, "module: %V %s \"%s\"",
214                 &app->type, app->version, name);
215 
216         if (app->compat_length != sizeof(compat)
217             || nxt_memcmp(app->compat, compat, sizeof(compat)) != 0)
218         {
219             nxt_log(task, NXT_LOG_NOTICE, "incompatible module %s", name);
220 
221             goto done;
222         }
223 
224         type = nxt_app_parse_type(app->type.start, app->type.length);
225 
226         if (type == NXT_APP_UNKNOWN) {
227             nxt_log(task, NXT_LOG_NOTICE, "unknown module type %V", &app->type);
228 
229             goto done;
230         }
231 
232         module = modules->elts;
233         n = modules->nelts;
234 
235         version.start = (u_char *) app->version;
236         version.length = nxt_strlen(app->version);
237 
238         for (i = 0; i < n; i++) {
239             if (type == module[i].type
240                 && nxt_strstr_eq(&module[i].version, &version))
241             {
242                 nxt_log(task, NXT_LOG_NOTICE,
243                         "ignoring %s module with the same "
244                         "application language version %V %V as in %V",
245                         name, &app->type, &version, &module[i].file);
246 
247                 goto done;
248             }
249         }
250 
251         module = nxt_array_add(modules);
252         if (module == NULL) {
253             goto fail;
254         }
255 
256         module->type = type;
257 
258         nxt_str_dup(mp, &module->version, &version);
259         if (module->version.start == NULL) {
260             goto fail;
261         }
262 
263         module->file.length = nxt_strlen(name);
264 
265         module->file.start = nxt_mp_alloc(mp, module->file.length);
266         if (module->file.start == NULL) {
267             goto fail;
268         }
269 
270         nxt_memcpy(module->file.start, name, module->file.length);
271 
272     } else {
273         nxt_alert(task, "dlsym(\"%s\"), failed: \"%s\"", name, dlerror());
274     }
275 
276 done:
277 
278     ret = NXT_OK;
279 
280 fail:
281 
282     if (dlclose(dl) != 0) {
283         nxt_alert(task, "dlclose(\"%s\"), failed: \"%s\"", name, dlerror());
284     }
285 
286     return ret;
287 }
288 
289 
290 static void
291 nxt_discovery_completion_handler(nxt_task_t *task, void *obj, void *data)
292 {
293     nxt_mp_t   *mp;
294     nxt_buf_t  *b;
295 
296     b = obj;
297     mp = b->data;
298 
299     nxt_mp_destroy(mp);
300 }
301 
302 
303 static void
304 nxt_discovery_quit(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data)
305 {
306     nxt_worker_process_quit_handler(task, msg);
307 }
308 
309 
310 nxt_int_t
311 nxt_app_start(nxt_task_t *task, void *data)
312 {
313     nxt_int_t              ret;
314     nxt_app_lang_module_t  *lang;
315     nxt_common_app_conf_t  *app_conf;
316 
317     app_conf = data;
318 
319     lang = nxt_app_lang_module(task->thread->runtime, &app_conf->type);
320     if (nxt_slow_path(lang == NULL)) {
321         nxt_alert(task, "unknown application type: \"%V\"", &app_conf->type);
322         return NXT_ERROR;
323     }
324 
325     nxt_app = lang->module;
326 
327     if (nxt_app == NULL) {
328         nxt_debug(task, "application language module: %s \"%s\"",
329                   lang->version, lang->file);
330 
331         nxt_app = nxt_app_module_load(task, lang->file);
332     }
333 
334     if (app_conf->working_directory != NULL
335         && app_conf->working_directory[0] != 0)
336     {
337         ret = chdir(app_conf->working_directory);
338 
339         if (nxt_slow_path(ret != 0)) {
340             nxt_log(task, NXT_LOG_WARN, "chdir(%s) failed %E",
341                     app_conf->working_directory, nxt_errno);
342 
343             return NXT_ERROR;
344         }
345     }
346 
347     if (nxt_slow_path(nxt_thread_mutex_create(&nxt_app_mutex) != NXT_OK)) {
348         return NXT_ERROR;
349     }
350 
351     if (nxt_slow_path(nxt_thread_cond_create(&nxt_app_cond) != NXT_OK)) {
352         return NXT_ERROR;
353     }
354 
355     ret = nxt_app->init(task, data);
356 
357     if (nxt_slow_path(ret != NXT_OK)) {
358         nxt_debug(task, "application init failed");
359 
360     } else {
361         nxt_debug(task, "application init done");
362     }
363 
364     return ret;
365 }
366 
367 
368 static nxt_app_module_t *
369 nxt_app_module_load(nxt_task_t *task, const char *name)
370 {
371     void  *dl;
372 
373     dl = dlopen(name, RTLD_GLOBAL | RTLD_LAZY);
374 
375     if (dl != NULL) {
376         return dlsym(dl, "nxt_app_module");
377     }
378 
379     nxt_alert(task, "dlopen(\"%s\"), failed: \"%s\"", name, dlerror());
380 
381     return NULL;
382 }
383 
384 
385 void
386 nxt_app_quit_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg)
387 {
388     if (nxt_app->atexit != NULL) {
389         nxt_app->atexit(task);
390     }
391 
392     nxt_worker_process_quit_handler(task, msg);
393 }
394 
395 
396 void
397 nxt_app_data_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg)
398 {
399     size_t          dump_size;
400     nxt_int_t       res;
401     nxt_buf_t       *b;
402     nxt_port_t      *port;
403     nxt_app_rmsg_t  rmsg = { msg->buf };
404     nxt_app_wmsg_t  wmsg;
405 
406     b = msg->buf;
407     dump_size = b->mem.free - b->mem.pos;
408 
409     if (dump_size > 300) {
410         dump_size = 300;
411     }
412 
413     nxt_debug(task, "app data: %*s ...", dump_size, b->mem.pos);
414 
415     port = nxt_runtime_port_find(task->thread->runtime, msg->port_msg.pid,
416                                  msg->port_msg.reply_port);
417     if (nxt_slow_path(port == NULL)) {
418         nxt_debug(task, "stream #%uD: reply port %d not found",
419                   msg->port_msg.stream, msg->port_msg.reply_port);
420         return;
421     }
422 
423     wmsg.port = port;
424     wmsg.write = NULL;
425     wmsg.buf = &wmsg.write;
426     wmsg.stream = msg->port_msg.stream;
427 
428     res = nxt_app->run(task, &rmsg, &wmsg);
429 
430     if (nxt_slow_path(res != NXT_OK)) {
431         nxt_port_socket_write(task, port, NXT_PORT_MSG_RPC_ERROR, -1,
432                               msg->port_msg.stream, 0, NULL);
433     }
434 }
435 
436 
437 u_char *
438 nxt_app_msg_write_get_buf(nxt_task_t *task, nxt_app_wmsg_t *msg, size_t size)
439 {
440     size_t      free_size;
441     u_char      *res;
442     nxt_buf_t   *b;
443 
444     res = NULL;
445 
446     do {
447         b = *msg->buf;
448 
449         if (b == NULL) {
450             b = nxt_port_mmap_get_buf(task, msg->port, size);
451             if (nxt_slow_path(b == NULL)) {
452                 return NULL;
453             }
454 
455             *msg->buf = b;
456 
457             free_size = nxt_buf_mem_free_size(&b->mem);
458 
459             if (nxt_slow_path(free_size < size)) {
460                 nxt_log(task, NXT_LOG_WARN, "requested buffer too big "
461                         "(%z < %z)", free_size, size);
462                 return NULL;
463             }
464 
465         }
466 
467         free_size = nxt_buf_mem_free_size(&b->mem);
468 
469         if (free_size >= size) {
470             res = b->mem.free;
471             b->mem.free += size;
472 
473             return res;
474         }
475 
476         if (nxt_port_mmap_increase_buf(task, b, size, size) == NXT_OK) {
477             res = b->mem.free;
478             b->mem.free += size;
479 
480             return res;
481         }
482 
483         msg->buf = &b->next;
484     } while(1);
485 }
486 
487 
488 nxt_int_t
489 nxt_app_msg_write(nxt_task_t *task, nxt_app_wmsg_t *msg, u_char *c, size_t size)
490 {
491     u_char  *dst;
492     size_t  dst_length;
493 
494     if (c != NULL) {
495         dst_length = size + (size < 128 ? 1 : 4) + 1;
496 
497         dst = nxt_app_msg_write_get_buf(task, msg, dst_length);
498         if (nxt_slow_path(dst == NULL)) {
499             nxt_debug(task, "nxt_app_msg_write: get_buf(%uz) failed",
500                       dst_length);
501             return NXT_ERROR;
502         }
503 
504         dst = nxt_app_msg_write_length(dst, size + 1); /* +1 for trailing 0 */
505 
506         nxt_memcpy(dst, c, size);
507         dst[size] = 0;
508 
509         nxt_debug(task, "nxt_app_msg_write: %uz %*s", size, size, c);
510 
511     } else {
512         dst_length = 1;
513 
514         dst = nxt_app_msg_write_get_buf(task, msg, dst_length);
515         if (nxt_slow_path(dst == NULL)) {
516             nxt_debug(task, "nxt_app_msg_write: get_buf(%uz) failed",
517                       dst_length);
518             return NXT_ERROR;
519         }
520 
521         dst = nxt_app_msg_write_length(dst, 0);
522 
523         nxt_debug(task, "nxt_app_msg_write: NULL");
524     }
525 
526     return NXT_OK;
527 }
528 
529 
530 nxt_int_t
531 nxt_app_msg_write_prefixed_upcase(nxt_task_t *task, nxt_app_wmsg_t *msg,
532     const nxt_str_t *prefix, u_char *c, size_t size)
533 {
534     u_char  *dst, *src;
535     size_t  i, length, dst_length;
536 
537     length = prefix->length + size;
538 
539     dst_length = length + (length < 128 ? 1 : 4) + 1;
540 
541     dst = nxt_app_msg_write_get_buf(task, msg, dst_length);
542     if (nxt_slow_path(dst == NULL)) {
543         return NXT_ERROR;
544     }
545 
546     dst = nxt_app_msg_write_length(dst, length + 1); /* +1 for trailing 0 */
547 
548     nxt_memcpy(dst, prefix->start, prefix->length);
549     dst += prefix->length;
550 
551     src = c;
552     for (i = 0; i < size; i++, dst++, src++) {
553 
554         if (*src >= 'a' && *src <= 'z') {
555             *dst = *src & ~0x20;
556             continue;
557         }
558 
559         if (*src == '-') {
560             *dst = '_';
561             continue;
562         }
563 
564         *dst = *src;
565     }
566 
567     *dst = 0;
568 
569     return NXT_OK;
570 }
571 
572 
573 nxt_inline nxt_int_t
574 nxt_app_msg_read_size_(nxt_task_t *task, nxt_app_rmsg_t *msg, size_t *size)
575 {
576     nxt_buf_t  *buf;
577 
578     do {
579         buf = msg->buf;
580 
581         if (nxt_slow_path(buf == NULL)) {
582             return NXT_DONE;
583         }
584 
585         if (nxt_slow_path(nxt_buf_mem_used_size(&buf->mem) < 1)) {
586             if (nxt_fast_path(nxt_buf_mem_used_size(&buf->mem) == 0)) {
587                 msg->buf = buf->next;
588                 continue;
589             }
590             return NXT_ERROR;
591         }
592 
593         if (buf->mem.pos[0] >= 128) {
594             if (nxt_slow_path(nxt_buf_mem_used_size(&buf->mem) < 4)) {
595                 return NXT_ERROR;
596             }
597         }
598 
599         break;
600     } while (1);
601 
602     buf->mem.pos = nxt_app_msg_read_length(buf->mem.pos, size);
603 
604     return NXT_OK;
605 }
606 
607 
608 nxt_int_t
609 nxt_app_msg_read_str(nxt_task_t *task, nxt_app_rmsg_t *msg, nxt_str_t *str)
610 {
611     size_t      length;
612     nxt_int_t   ret;
613     nxt_buf_t  *buf;
614 
615     ret = nxt_app_msg_read_size_(task, msg, &length);
616     if (ret != NXT_OK) {
617         return ret;
618     }
619 
620     buf = msg->buf;
621 
622     if (nxt_slow_path(nxt_buf_mem_used_size(&buf->mem) < (intptr_t) length)) {
623         return NXT_ERROR;
624     }
625 
626     if (length > 0) {
627         str->start = buf->mem.pos;
628         str->length = length - 1;
629 
630         buf->mem.pos += length;
631 
632         nxt_debug(task, "nxt_read_str: %uz %*s", length - 1,
633                         length - 1, str->start);
634 
635     } else {
636         str->start = NULL;
637         str->length = 0;
638 
639         nxt_debug(task, "nxt_read_str: NULL");
640     }
641 
642     return NXT_OK;
643 }
644 
645 
646 size_t
647 nxt_app_msg_read_raw(nxt_task_t *task, nxt_app_rmsg_t *msg, void *dst,
648     size_t size)
649 {
650     size_t     res, read_size;
651     nxt_buf_t  *buf;
652 
653     res = 0;
654 
655     while (size > 0) {
656         buf = msg->buf;
657 
658         if (nxt_slow_path(buf == NULL)) {
659             break;
660         }
661 
662         if (nxt_slow_path(nxt_buf_mem_used_size(&buf->mem) == 0)) {
663             msg->buf = buf->next;
664             continue;
665         }
666 
667         read_size = nxt_buf_mem_used_size(&buf->mem);
668         read_size = nxt_min(read_size, size);
669 
670         dst = nxt_cpymem(dst, buf->mem.pos, read_size);
671 
672         size -= read_size;
673         buf->mem.pos += read_size;
674         res += read_size;
675     }
676 
677     nxt_debug(task, "nxt_read_raw: %uz", res);
678 
679     return res;
680 }
681 
682 
683 nxt_int_t
684 nxt_app_msg_read_nvp(nxt_task_t *task, nxt_app_rmsg_t *rmsg, nxt_str_t *n,
685     nxt_str_t *v)
686 {
687     nxt_int_t rc;
688 
689     rc = nxt_app_msg_read_str(task, rmsg, n);
690     if (nxt_slow_path(rc != NXT_OK)) {
691         return rc;
692     }
693 
694     rc = nxt_app_msg_read_str(task, rmsg, v);
695     if (nxt_slow_path(rc != NXT_OK)) {
696         return rc;
697     }
698 
699     return rc;
700 }
701 
702 
703 nxt_int_t
704 nxt_app_msg_read_size(nxt_task_t *task, nxt_app_rmsg_t *msg, size_t *size)
705 {
706     nxt_int_t  ret;
707 
708     ret = nxt_app_msg_read_size_(task, msg, size);
709 
710     nxt_debug(task, "nxt_read_size: %d", (int) *size);
711 
712     return ret;
713 }
714 
715 
716 nxt_int_t
717 nxt_app_http_req_done(nxt_task_t *task, nxt_app_parse_ctx_t *ar)
718 {
719     ar->timer.handler = nxt_app_http_release;
720     nxt_timer_add(task->thread->engine, &ar->timer, 0);
721 
722     return NXT_OK;
723 }
724 
725 
726 static void
727 nxt_app_http_release(nxt_task_t *task, void *obj, void *data)
728 {
729     nxt_timer_t          *timer;
730     nxt_app_parse_ctx_t  *ar;
731 
732     timer = obj;
733 
734     nxt_debug(task, "http app release");
735 
736     ar = nxt_timer_data(timer, nxt_app_parse_ctx_t, timer);
737 
738     nxt_mp_release(ar->request->mem_pool);
739 }
740 
741 
742 nxt_int_t
743 nxt_app_msg_flush(nxt_task_t *task, nxt_app_wmsg_t *msg, nxt_bool_t last)
744 {
745     nxt_int_t   rc;
746     nxt_buf_t   *b;
747 
748     rc = NXT_OK;
749 
750     if (nxt_slow_path(last == 1)) {
751         do {
752             b = *msg->buf;
753 
754             if (b == NULL) {
755                 b = nxt_buf_sync_alloc(msg->port->mem_pool, NXT_BUF_SYNC_LAST);
756                 *msg->buf = b;
757                 break;
758             }
759 
760             msg->buf = &b->next;
761         } while(1);
762     }
763 
764     if (nxt_slow_path(msg->write != NULL)) {
765         rc = nxt_port_socket_write(task, msg->port, NXT_PORT_MSG_DATA,
766                                    -1, msg->stream, 0, msg->write);
767 
768         msg->write = NULL;
769         msg->buf = &msg->write;
770     }
771 
772     return rc;
773 }
774 
775 
776 nxt_int_t
777 nxt_app_msg_write_raw(nxt_task_t *task, nxt_app_wmsg_t *msg, const u_char *c,
778     size_t size)
779 {
780     size_t      free_size, copy_size;
781     nxt_buf_t   *b;
782 
783     nxt_debug(task, "nxt_app_msg_write_raw: %uz", size);
784 
785     while (size > 0) {
786         b = *msg->buf;
787 
788         if (b == NULL) {
789             b = nxt_port_mmap_get_buf(task, msg->port, size);
790             if (nxt_slow_path(b == NULL)) {
791                 return NXT_ERROR;
792             }
793 
794             *msg->buf = b;
795         }
796 
797         do {
798             free_size = nxt_buf_mem_free_size(&b->mem);
799 
800             if (free_size > 0) {
801                 copy_size = nxt_min(free_size, size);
802 
803                 b->mem.free = nxt_cpymem(b->mem.free, c, copy_size);
804 
805                 size -= copy_size;
806                 c += copy_size;
807 
808                 if (size == 0) {
809                     return NXT_OK;
810                 }
811             }
812         } while (nxt_port_mmap_increase_buf(task, b, size, 1) == NXT_OK);
813 
814         msg->buf = &b->next;
815     }
816 
817     return NXT_OK;
818 }
819 
820 
821 nxt_app_lang_module_t *
822 nxt_app_lang_module(nxt_runtime_t *rt, nxt_str_t *name)
823 {
824     u_char                 *p, *end, *version;
825     size_t                 version_length;
826     nxt_uint_t             i, n;
827     nxt_app_type_t         type;
828     nxt_app_lang_module_t  *lang;
829 
830     end = name->start + name->length;
831     version = end;
832 
833     for (p = name->start; p < end; p++) {
834         if (*p == ' ') {
835             version = p + 1;
836             break;
837         }
838 
839         if (*p >= '0' && *p <= '9') {
840             version = p;
841             break;
842         }
843     }
844 
845     type = nxt_app_parse_type(name->start, p - name->start);
846 
847     if (type == NXT_APP_UNKNOWN) {
848         return NULL;
849     }
850 
851     version_length = end - version;
852 
853     lang = rt->languages->elts;
854     n = rt->languages->nelts;
855 
856     for (i = 0; i < n; i++) {
857 
858         /*
859          * Versions are sorted in descending order
860          * so first match chooses the highest version.
861          */
862 
863         if (lang[i].type == type
864             && nxt_strvers_match(lang[i].version, version, version_length))
865         {
866             return &lang[i];
867         }
868     }
869 
870     return NULL;
871 }
872 
873 
874 nxt_app_type_t
875 nxt_app_parse_type(u_char *p, size_t length)
876 {
877     nxt_str_t str;
878 
879     str.length = length;
880     str.start = p;
881 
882     if (nxt_str_eq(&str, "python", 6)) {
883         return NXT_APP_PYTHON;
884 
885     } else if (nxt_str_eq(&str, "php", 3)) {
886         return NXT_APP_PHP;
887 
888     } else if (nxt_str_eq(&str, "go", 2)) {
889         return NXT_APP_GO;
890 
891     } else if (nxt_str_eq(&str, "perl", 4)) {
892         return NXT_APP_PERL;
893 
894     } else if (nxt_str_eq(&str, "ruby", 4)) {
895         return NXT_APP_RUBY;
896     }
897 
898     return NXT_APP_UNKNOWN;
899 }
900