Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 601 lines (532 sloc) 16.026 kB
f61f188 @spiritloose Initial commit
authored
1 /*
2 * Copyright 2009 Jiro Nishiguchi <jiro@cpan.org>
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include "httpd.h"
17 #include "http_log.h"
18 #include "http_config.h"
19 #include "http_protocol.h"
20 #include "util_script.h"
21 #include "ap_config.h"
22 #include "ap_mpm.h"
23 #include "apr_strings.h"
c115693 @spiritloose 'do $psgi_app' at post_config
authored
24 #include "apr_hash.h"
f61f188 @spiritloose Initial commit
authored
25
26 #include "EXTERN.h"
27 #include "perl.h"
28 #include "XSUB.h"
29 #define NEED_eval_pv
30 #define NEED_newRV_noinc
31 #define NEED_sv_2pv_flags
32 #include "ppport.h"
33
34 #ifdef DEBUG
35 #define TRACE(...) ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, NULL, __VA_ARGS__)
36 #endif
37
38 module AP_MODULE_DECLARE_DATA psgi_module;
39
40 typedef struct {
ace1272 @spiritloose Make PerlInterpreter persistent
authored
41 char *file;
42 SV *app;
f61f188 @spiritloose Initial commit
authored
43 } psgi_dir_config;
44
c115693 @spiritloose 'do $psgi_app' at post_config
authored
45 static PerlInterpreter *perlinterp = NULL;
46
47 static apr_hash_t *app_mapping = NULL;
48
49 static apr_array_header_t *psgi_apps = NULL;
e284234 @spiritloose Call PERL_SYS_INIT3,PERL_SYS_TERM once per process
authored
50
f61f188 @spiritloose Initial commit
authored
51 static void server_error(request_rec *r, const char *fmt, ...)
52 {
53 va_list argp;
54 const char *msg;
55 va_start(argp, fmt);
56 msg = apr_pvsprintf(r->pool, fmt, argp);
57 va_end(argp);
58 ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r->server, "%s", msg);
59 }
60
61 EXTERN_C void xs_init (pTHX);
62
63 EXTERN_C void boot_DynaLoader (pTHX_ CV* cv);
64
65 XS(ModPSGI_exit);
66 XS(ModPSGI_exit)
67 {
68 dXSARGS;
69 croak("exit");
70 XSRETURN(0);
71 }
72
73 XS(ModPSGI_Input_read);
74 XS(ModPSGI_Input_read)
75 {
76 dXSARGS;
77 SV *self = ST(0);
78 SV *buf = ST(1);
79 request_rec *r = (request_rec *) mg_find(SvRV(self), PERL_MAGIC_ext)->mg_obj;
80 apr_size_t len = SvIV(ST(2));
81 apr_size_t offset = items >= 4 ? SvIV(ST(3)) : 0;
82 apr_status_t rv;
83 apr_bucket_brigade *bb;
84 apr_bucket *bucket;
85 int eos = 0;
86 SV *ret;
87 dXSTARG;
88
89 ret = newSVpv("", 0);
90 bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
91 rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES, APR_BLOCK_READ, len);
92 if (rv != APR_SUCCESS) {
93 ST(0) = &PL_sv_undef;
94 XSRETURN(1);
95 }
96
97 for (bucket = APR_BRIGADE_FIRST(bb);
98 bucket != APR_BRIGADE_SENTINEL(bb);
99 bucket = APR_BUCKET_NEXT(bucket)) {
100 const char *bbuf;
101 apr_size_t blen;
102 if (APR_BUCKET_IS_EOS(bucket)) {
103 eos = 1;
104 break;
105 }
106 if (APR_BUCKET_IS_METADATA(bucket)) {
107 continue;
108 }
109 apr_bucket_read(bucket, &bbuf, &blen, APR_BLOCK_READ);
110 sv_catpvn(ret, bbuf, blen);
111 }
112
113 sv_setsv(buf, ret);
114 ST(0) = sv_2mortal(newSViv(SvCUR(buf)));
115 XSRETURN(1);
116 }
117
118 XS(ModPSGI_Errors_print);
119 XS(ModPSGI_Errors_print)
120 {
121 dXSARGS;
122 SV *self = ST(0);
123 SV *msg = ST(1);
124 dXSTARG;
125 request_rec *r = (request_rec *) mg_find(SvRV(self), PERL_MAGIC_ext)->mg_obj;
126 ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r->server, "%s", SvPV_nolen(msg));
127 ST(0) = newSViv(1);
128 XSRETURN(1);
129 }
130
131 EXTERN_C void
132 xs_init(pTHX)
133 {
134 char *file = __FILE__;
135 dXSUB_SYS;
136
137 newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
138 newXS("ModPSGI::exit", ModPSGI_exit, file);
139 newXSproto("ModPSGI::Input::read", ModPSGI_Input_read, file, "$$$;$");
140 newXSproto("ModPSGI::Errors::print", ModPSGI_Errors_print, file, "$$");
141 }
142
143 static int copy_env(void *rec, const char *key, const char *val)
144 {
fe98cae @spiritloose use dTHX
authored
145 dTHX;
f61f188 @spiritloose Initial commit
authored
146 HV *env = (HV *) rec;
147 hv_store(env, key, strlen(key), newSVpv(val, 0), 0);
148 return 1;
149 }
150
151 static SV *make_env(request_rec *r)
152 {
fe98cae @spiritloose use dTHX
authored
153 dTHX;
f61f188 @spiritloose Initial commit
authored
154 HV *env;
155 AV *version;
156 char *url_scheme;
157 SV *input, *errors;
158
159 env = newHV();
160
161 ap_add_cgi_vars(r);
162 ap_add_common_vars(r);
163 if (apr_table_get(r->subprocess_env, "PATH_INFO") == NULL) {
164 apr_table_set(r->subprocess_env, "PATH_INFO", "");
165 }
166 if (strcmp(apr_table_get(r->subprocess_env, "SCRIPT_NAME"), "/") == 0
167 && strcmp(apr_table_get(r->subprocess_env, "PATH_INFO"), "") == 0) {
168 apr_table_set(r->subprocess_env, "PATH_INFO", "/");
169 apr_table_set(r->subprocess_env, "SCRIPT_NAME", "");
170 }
171 apr_table_do(copy_env, env, r->subprocess_env, NULL);
172
173 version = newAV();
174 av_push(version, newSViv(1));
175 av_push(version, newSViv(0));
176 hv_store(env, "psgi.version", 12, newRV_inc((SV *) version), 0);
177
178 url_scheme = apr_table_get(r->subprocess_env, "HTTPS") == NULL ? "http" : "https";
179 hv_store(env, "psgi.url_scheme", 15, newSVpv(url_scheme, 0), 0);
180
181 input = newRV_noinc(newSV(0));
182 sv_magic(SvRV(input), NULL, PERL_MAGIC_ext, NULL, 0);
183 mg_find(SvRV(input), PERL_MAGIC_ext)->mg_obj = (void *) r;
184 sv_bless(input, gv_stashpv("ModPSGI::Input", 1));
185 hv_store(env, "psgi.input", 10, input, 0);
186
187 errors = newRV_noinc(newSV(0));
188 sv_magic(SvRV(errors), NULL, PERL_MAGIC_ext, NULL, 0);
189 mg_find(SvRV(errors), PERL_MAGIC_ext)->mg_obj = (void *) r;
190 sv_bless(errors, gv_stashpv("ModPSGI::Errors", 1));
191 hv_store(env, "psgi.errors", 11, errors, 0);
192
193 hv_store(env, "psgi.multithread", 16, newSViv(0), 0);
194 hv_store(env, "psgi.multiprocess", 17, newSViv(1), 0);
195 hv_store(env, "psgi.run_once", 13, newSViv(1), 0);
196 hv_store(env, "psgi.async", 10, newSViv(0), 0);
197
198 return newRV_inc((SV *) env);
199 }
200
201 static SV *run_app(request_rec *r, SV *app, SV *env)
202 {
fe98cae @spiritloose use dTHX
authored
203 dTHX;
f61f188 @spiritloose Initial commit
authored
204 int count;
205 SV *res;
206 dSP;
207 ENTER;
208 SAVETMPS;
209 PUSHMARK(SP) ;
210 XPUSHs(sv_2mortal(env));
211 PUTBACK;
212
213 count = call_sv(app, G_EVAL|G_KEEPERR|G_SCALAR);
214 SPAGAIN;
215 if (SvTRUE(ERRSV)) {
216 res = NULL;
217 server_error(r, "%s", SvPV_nolen(ERRSV));
218 POPs;
219 } else if (count > 0) {
220 res = POPs;
221 SvREFCNT_inc(res);
222 } else {
223 res = NULL;
224 }
225 PUTBACK;
226 FREETMPS;
227 LEAVE;
228 return res;
229 }
230
231 static int output_status(request_rec *r, SV *status)
232 {
fe98cae @spiritloose use dTHX
authored
233 dTHX;
f61f188 @spiritloose Initial commit
authored
234 int s = SvIV(status);
235 if (s < 100) {
236 server_error(r, "invalid response status %d", s);
237 return HTTP_INTERNAL_SERVER_ERROR;
238 }
239 r->status = s;
240 return OK;
241 }
242
243 static int output_headers(request_rec *r, AV *headers)
244 {
fe98cae @spiritloose use dTHX
authored
245 dTHX;
f61f188 @spiritloose Initial commit
authored
246 SV *key_sv, *val_sv;
247 char *key, *val;
248 while (av_len(headers) > -1) {
249 key_sv = av_shift(headers);
250 val_sv = av_shift(headers);
251 if (key_sv == NULL || val_sv == NULL) break;
252 key = SvPV_nolen(key_sv);
253 val = SvPV_nolen(val_sv);
aab7f6a @spiritloose Do not check header value
authored
254 if (strcmp(key, "Content-Type") == 0) {
f61f188 @spiritloose Initial commit
authored
255 r->content_type = apr_pstrdup(r->pool, val);
256 } else if (strcmp(key, "Status") == 0) {
257 server_error(r, "headers must not contain a Status");
258 return HTTP_INTERNAL_SERVER_ERROR;
259 } else {
260 apr_table_add(r->headers_out, key, val);
261 }
262 }
263 return OK;
264 }
265
266 static int respond_to(SV *obj, const char *method)
267 {
fe98cae @spiritloose use dTHX
authored
268 dTHX;
f61f188 @spiritloose Initial commit
authored
269 int res;
270 dSP;
271 ENTER;
272 SAVETMPS;
273 PUSHMARK(SP);
274 XPUSHs(obj);
275 XPUSHs(sv_2mortal(newSVpv(method, 0)));
276 PUTBACK;
277
278 call_method("can", G_SCALAR);
279 SPAGAIN;
280 res = SvROK(POPs);
281 PUTBACK;
282 FREETMPS;
283 LEAVE;
284 return res;
285 }
286
287 static void set_content_length(request_rec *r, apr_off_t length)
288 {
289 if (apr_table_get(r->headers_out, "Content-Length") == NULL) {
290 apr_table_add(r->headers_out, "Content-Length", apr_off_t_toa(r->pool, length));
291 }
292 }
293
294 static int output_body_ary(request_rec *r, AV *bodys)
295 {
fe98cae @spiritloose use dTHX
authored
296 dTHX;
f61f188 @spiritloose Initial commit
authored
297 SV **body;
298 I32 i;
299 I32 lastidx;
300 char *buf;
301 STRLEN len;
302 apr_off_t clen;
303
304 lastidx = av_len(bodys);
305 for (i = 0; i <= lastidx; i++) {
306 body = av_fetch(bodys, i, 0);
307 if (SvOK(*body)) {
308 buf = SvPV(*body, len);
309 ap_rwrite(buf, len, r);
310 clen += len;
311 }
312 }
313 set_content_length(r, clen);
314 return OK;
315 }
316
317 static int output_body_obj(request_rec *r, SV *obj, int type)
318 {
fe98cae @spiritloose use dTHX
authored
319 dTHX;
f61f188 @spiritloose Initial commit
authored
320 SV *buf_sv, *rs;
321 apr_off_t clen = 0;
322 STRLEN len;
323 char *buf;
324 int count;
325
326 if (type == SVt_PVMG && !respond_to(obj, "getline")) {
327 server_error(r, "response body object must be able to getline");
328 return HTTP_INTERNAL_SERVER_ERROR;
329 }
330
331 dSP;
332 ENTER;
333 SAVETMPS;
334 SAVESPTR(PL_rs);
335 PL_rs = newRV_inc(newSViv(AP_IOBUFSIZE));
336 while (1) {
337 PUSHMARK(SP);
338 XPUSHs(obj);
339 PUTBACK;
340 count = call_method("getline", G_SCALAR);
341 if (count != 1) croak("Big trouble\n");
342 SPAGAIN;
343 buf_sv = POPs;
344 if (SvOK(buf_sv)) {
345 buf = SvPV(buf_sv, len);
346 clen += len;
347 ap_rwrite(buf, len, r);
348 } else {
349 break;
350 }
351 }
352 set_content_length(r, len);
353 PUSHMARK(SP);
354 XPUSHs(obj);
355 PUTBACK;
356 call_method("close", G_DISCARD);
357 SPAGAIN;
358 PUTBACK;
359 FREETMPS;
360 LEAVE;
361 return OK;
362 }
363
364 static int output_body(request_rec *r, SV *body)
365 {
fe98cae @spiritloose use dTHX
authored
366 dTHX;
f61f188 @spiritloose Initial commit
authored
367 int rc, type;
368 switch (type = SvTYPE(SvRV(body))) {
369 case SVt_PVAV:
370 rc = output_body_ary(r, (AV *) SvRV(body));
371 break;
372 case SVt_PVGV:
373 require_pv("IO/Handle.pm");
374 case SVt_PVMG:
375 rc = output_body_obj(r, body, type);
376 break;
377 default:
378 server_error(r, "response body must be an array reference or object");
379 rc = HTTP_INTERNAL_SERVER_ERROR;
380 break;
381 }
382 return rc;
383 }
384
385 static int output_response(request_rec *r, SV *res)
386 {
fe98cae @spiritloose use dTHX
authored
387 dTHX;
f61f188 @spiritloose Initial commit
authored
388 AV *res_av;
389 SV **status;
390 SV **headers;
391 AV *headers_av;
392 SV **body;
393 int rc;
394
395 if (!SvROK(res) || SvTYPE(SvRV(res)) != SVt_PVAV) {
396 server_error(r, "response must be an array reference");
397 return HTTP_INTERNAL_SERVER_ERROR;
398 }
399 res_av = (AV *) SvRV(res);
400 if (av_len(res_av) != 2) {
401 server_error(r, "response must have 3 elements");
402 return HTTP_INTERNAL_SERVER_ERROR;
403 }
404
405 status = av_fetch(res_av, 0, 0);
406 if (!SvOK(*status)) {
407 server_error(r, "response status must be a scalar value");
408 return HTTP_INTERNAL_SERVER_ERROR;
409 }
410 rc = output_status(r, *status);
411 if (rc != OK) return rc;
412
413 headers = av_fetch(res_av, 1, 0);
414 if (!SvROK(*headers) || SvTYPE(SvRV(*headers)) != SVt_PVAV) {
415 server_error(r, "response headers must be an array reference");
416 return HTTP_INTERNAL_SERVER_ERROR;
417 }
418 headers_av = (AV *) SvRV(*headers);
419 if ((av_len(headers_av) + 1) % 2 != 0) {
420 server_error(r, "num of response headers must be even");
421 return HTTP_INTERNAL_SERVER_ERROR;
422 }
423 rc = output_headers(r, headers_av);
424 if (rc != OK) return rc;
425
426 body = av_fetch(res_av, 2, 0);
427 if (!SvROK(*body)) {
428 server_error(r, "response body must be a reference");
429 return HTTP_INTERNAL_SERVER_ERROR;
430 }
431 rc = output_body(r, *body);
432
433 return rc;
434 }
435
ace1272 @spiritloose Make PerlInterpreter persistent
authored
436 static void init_perl_variables()
f61f188 @spiritloose Initial commit
authored
437 {
fe98cae @spiritloose use dTHX
authored
438 dTHX;
f61f188 @spiritloose Initial commit
authored
439 GV *exit_gv = gv_fetchpv("CORE::GLOBAL::exit", TRUE, SVt_PVCV);
440 GvCV(exit_gv) = get_cv("ModPSGI::exit", TRUE);
441 GvIMPORTED_CV_on(exit_gv);
442 hv_store(GvHV(PL_envgv), "MOD_PSGI", 8, newSVpv(MOD_PSGI_VERSION, 0), 0);
443 }
444
4e00d63 @spiritloose init and destroy in handler
authored
445 static int psgi_handler(request_rec *r)
f61f188 @spiritloose Initial commit
authored
446 {
447 SV *app, *env, *res;
448 psgi_dir_config *c;
449
450 if (strcmp(r->handler, "psgi")) {
451 return DECLINED;
452 }
453 c = (psgi_dir_config *) ap_get_module_config(r->per_dir_config, &psgi_module);
ace1272 @spiritloose Make PerlInterpreter persistent
authored
454 if (c->file == NULL) {
f61f188 @spiritloose Initial commit
authored
455 ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r->server,
456 "PSGIApp not configured");
457 return DECLINED;
458 }
c115693 @spiritloose 'do $psgi_app' at post_config
authored
459 app = apr_hash_get(app_mapping, c->file, APR_HASH_KEY_STRING);
f61f188 @spiritloose Initial commit
authored
460 env = make_env(r);
c115693 @spiritloose 'do $psgi_app' at post_config
authored
461 res = run_app(r, app, env);
f61f188 @spiritloose Initial commit
authored
462 if (res == NULL) {
463 server_error(r, "invalid response");
c115693 @spiritloose 'do $psgi_app' at post_config
authored
464 return HTTP_INTERNAL_SERVER_ERROR;
f61f188 @spiritloose Initial commit
authored
465 }
c115693 @spiritloose 'do $psgi_app' at post_config
authored
466 return output_response(r, res);
f61f188 @spiritloose Initial commit
authored
467 }
468
469 static int supported_mpm()
470 {
471 int result;
472 ap_mpm_query(AP_MPMQ_IS_FORKED, &result);
473 return result;
474 }
475
e284234 @spiritloose Call PERL_SYS_INIT3,PERL_SYS_TERM once per process
authored
476 static apr_status_t psgi_child_exit(void *p)
477 {
ace1272 @spiritloose Make PerlInterpreter persistent
authored
478 PerlInterpreter *my_perl = perlinterp;
479 PL_perl_destruct_level = 1;
480 perl_destruct(my_perl);
481 perl_free(my_perl);
e284234 @spiritloose Call PERL_SYS_INIT3,PERL_SYS_TERM once per process
authored
482 PERL_SYS_TERM();
483 return OK;
484 }
485
486 static void psgi_child_init(apr_pool_t *p, server_rec *s)
487 {
488 apr_pool_cleanup_register(p, NULL, psgi_child_exit, psgi_child_exit);
489 }
490
ace1272 @spiritloose Make PerlInterpreter persistent
authored
491 static SV *load_psgi(apr_pool_t *pool, const char *file)
492 {
493 dTHX;
494 SV *app;
495 char *code;
496
497 code = apr_psprintf(pool, "do q\"%s\" or die $@",
498 ap_escape_quotes(pool, file));
499 app = eval_pv(code, FALSE);
500
501 if (SvTRUE(ERRSV)) {
502 ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, NULL, "%s", SvPV_nolen(ERRSV));
503 return NULL;
504 }
505 if (!SvOK(app) || !SvROK(app) || SvTYPE(SvRV(app)) != SVt_PVCV) {
506 ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, NULL,
507 "%s does not return an application code reference", file);
508 return NULL;
509 }
510 return app;
511 }
512
c115693 @spiritloose 'do $psgi_app' at post_config
authored
513 static apr_status_t
514 psgi_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
ace1272 @spiritloose Make PerlInterpreter persistent
authored
515 {
516 int argc = 2;
517 char *argv[] = { "perl", "-e;0", NULL };
518 char **envp = NULL;
519
520 PERL_SYS_INIT3(&argc, (char ***) argv, &envp);
521 perlinterp = perl_alloc();
522 PL_perl_destruct_level = 1;
523 perl_construct(perlinterp);
524 perl_parse(perlinterp, xs_init, argc, argv, envp);
525 PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
526 perl_run(perlinterp);
527 init_perl_variables();
c115693 @spiritloose 'do $psgi_app' at post_config
authored
528
529 psgi_apps = apr_array_make(pconf, 10, sizeof(char *));
530 app_mapping = apr_hash_make(pconf);
531
532 return OK;
533 }
534
535 static int
536 psgi_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
537 {
538 dTHX;
539 int i;
540 char *file, **elts;
541 SV *app;
542
543 elts = (char **) psgi_apps->elts;
544 for (i = 0; i < psgi_apps->nelts; i++) {
545 file = elts[i];
546 app = load_psgi(pconf, file);
547 if (app == NULL) {
548 ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, NULL,
549 "%s had compilation errors.", file);
550 return DONE;
551 }
552 apr_hash_set(app_mapping, file, APR_HASH_KEY_STRING, app);
553 }
ace1272 @spiritloose Make PerlInterpreter persistent
authored
554 return OK;
555 }
556
f61f188 @spiritloose Initial commit
authored
557 static void psgi_register_hooks(apr_pool_t *p)
558 {
559 if (supported_mpm()) {
ace1272 @spiritloose Make PerlInterpreter persistent
authored
560 ap_hook_pre_config(psgi_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
c115693 @spiritloose 'do $psgi_app' at post_config
authored
561 ap_hook_post_config(psgi_post_config, NULL, NULL, APR_HOOK_MIDDLE);
e284234 @spiritloose Call PERL_SYS_INIT3,PERL_SYS_TERM once per process
authored
562 ap_hook_child_init(psgi_child_init, NULL, NULL, APR_HOOK_MIDDLE);
ace1272 @spiritloose Make PerlInterpreter persistent
authored
563 ap_hook_handler(psgi_handler, NULL, NULL, APR_HOOK_MIDDLE);
f61f188 @spiritloose Initial commit
authored
564 } else {
ace1272 @spiritloose Make PerlInterpreter persistent
authored
565 ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, NULL,
566 "mod_psgi only supports prefork mpm");
f61f188 @spiritloose Initial commit
authored
567 }
568 }
569
570 static void *create_dir_config(apr_pool_t *p, char *path)
571 {
572 psgi_dir_config *c = apr_pcalloc(p, sizeof(psgi_dir_config));
ace1272 @spiritloose Make PerlInterpreter persistent
authored
573 c->file = NULL;
f61f188 @spiritloose Initial commit
authored
574 return (void *) c;
575 }
576
577 static const char *cmd_psgi_app(cmd_parms *cmd, void *conf, const char *v)
578 {
579 psgi_dir_config *c = (psgi_dir_config *) conf;
ace1272 @spiritloose Make PerlInterpreter persistent
authored
580 c->file = (char *) apr_pstrdup(cmd->pool, v);
c115693 @spiritloose 'do $psgi_app' at post_config
authored
581 *(const char **) apr_array_push(psgi_apps) = c->file;
f61f188 @spiritloose Initial commit
authored
582 return NULL;
583 }
584
585 static const command_rec command_table[] = {
586 AP_INIT_TAKE1("PSGIApp", cmd_psgi_app, NULL,
b991f34 @spiritloose Allow PSGIApp in .htaccess
authored
587 OR_LIMIT, "set PSGI application"),
f61f188 @spiritloose Initial commit
authored
588 { NULL }
589 };
590
591 module AP_MODULE_DECLARE_DATA psgi_module = {
592 STANDARD20_MODULE_STUFF,
593 create_dir_config, /* create per-dir config structures */
594 NULL, /* merge per-dir config structures */
595 NULL, /* create per-server config structures */
596 NULL, /* merge per-server config structures */
597 command_table, /* table of config file commands */
598 psgi_register_hooks /* register hooks */
599 };
600
Something went wrong with that request. Please try again.