Browse Source

Start implementing pep333 application support

Yann Weber 4 years ago
parent
commit
6a93f8042f
14 changed files with 515 additions and 301 deletions
  1. 1
    1
      Doxyfile
  2. 1
    1
      Makefile.am
  3. 2
    1
      autogen.sh
  4. 12
    1
      configure.ac
  5. 8
    1
      include/conf.h
  6. 2
    0
      include/logger.h
  7. 46
    2
      include/pyutils.h
  8. 7
    26
      include/pyworker.h
  9. 4
    0
      lib/Makefile.am
  10. 48
    0
      lib/pyfcgi.c
  11. 4
    0
      src/conf.c
  12. 269
    0
      src/pyutils.c
  13. 109
    266
      src/pyworker.c
  14. 2
    2
      tests/Makefile.am

+ 1
- 1
Doxyfile View File

@@ -790,7 +790,7 @@ WARN_LOGFILE           =
790 790
 # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
791 791
 # Note: If this tag is empty the current directory is searched.
792 792
 
793
-INPUT                  = src
793
+INPUT                  = src include
794 794
 
795 795
 # This tag can be used to specify the character encoding of the source files
796 796
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses

+ 1
- 1
Makefile.am View File

@@ -1 +1 @@
1
-SUBDIRS = src tests
1
+SUBDIRS = src tests lib

+ 2
- 1
autogen.sh View File

@@ -6,6 +6,7 @@ rm -fR configure aclocal.m4 autom4te.cache src/Makefile.in tests/Makefile.in Mak
6 6
 
7 7
 [ "$1" = "clean" ] && exit 0
8 8
 
9
+libtoolize
9 10
 aclocal
10
-automake -a -c
11 11
 autoconf
12
+automake -a -c

+ 12
- 1
configure.ac View File

@@ -6,6 +6,7 @@ AC_INIT([PyFCGI], [0.0.1], [yann.weber@member.fsf.org])
6 6
 AC_CONFIG_SRCDIR([src/main.c])
7 7
 AC_CONFIG_HEADERS([include/config.h])
8 8
 
9
+
9 10
 AC_ARG_ENABLE([debug],
10 11
 	AS_HELP_STRING([--enable-debug], [Enable debug, disabling -Werror, etc.]))
11 12
 
@@ -21,6 +22,10 @@ if test x"${PYTHON_CONFIG}" == x"yes"; then
21 22
 	PYTHON_LDFLAGS=`python3-config --libs`
22 23
 	AC_SUBST([PYTHON_CFLAGS])
23 24
 	AC_SUBST([PYTHON_LDFLAGS])
25
+	PYTHON_SO_CFLAGS=`python3-config --cflags`
26
+	PYTHON_SO_LDFLAGS=`python3-config --cflags`
27
+	AC_SUBST([PYTHON_SO_CFLAGS])
28
+	AC_SUBST([PYTHON_SO_LDFLAGS])
24 29
 else
25 30
 	AC_MSG_ERROR([Unable to find python3-config])
26 31
 fi
@@ -31,6 +36,11 @@ else
31 36
 	AM_CFLAGS="-Wall -Werror -O2"
32 37
 fi
33 38
 
39
+AC_ENABLE_SHARED
40
+AC_DISABLE_STATIC
41
+AC_PROG_LIBTOOL
42
+LT_INIT
43
+
34 44
 AC_SUBST([AM_CFLAGS])
35 45
 
36 46
 AC_C_INLINE
@@ -55,5 +65,6 @@ AC_CHECK_FUNCS([bzero getcwd gettimeofday memmove strdup strerror strndup semop
55 65
 AM_INIT_AUTOMAKE
56 66
 AC_CONFIG_FILES([Makefile
57 67
                  src/Makefile
58
-		 tests/Makefile])
68
+		 tests/Makefile
69
+		 lib/Makefile])
59 70
 AC_OUTPUT

+ 8
- 1
include/conf.h View File

@@ -44,14 +44,17 @@
44 44
 /**@ingroup ret_status */
45 45
 #define PYFCGI_FATAL 128
46 46
 
47
+#define EXIT_PYERR 42
48
+
47 49
 #define PYFCGI_NAME "spawn-fcgi [OPTIONS] -- pyfcgi"
48 50
 
49
-#define PYFCGI_SHORT_OPT "Ce:E:w:W:m:L:Svh"
51
+#define PYFCGI_SHORT_OPT "Ce:E:Aw:W:m:L:Svh"
50 52
 
51 53
 #define PYFCGI_LONG_OPT { \
52 54
 	{"config", required_argument, 0, 'C'},\
53 55
 	{"pymodule", required_argument, 0, 'e'},\
54 56
 	{"pyapp", required_argument, 0, 'E'},\
57
+	{"alt-io", no_argument, 0, 'A'},\
55 58
 	{"min-worker", required_argument, 0, 'w'},\
56 59
 	{"max-worker", required_argument, 0, 'W'},\
57 60
 	{"max-request", required_argument, 0, 'm'},\
@@ -67,6 +70,7 @@
67 70
 	{"Load options from configuration file", "CONFIG"},\
68 71
 	{"Search application function in given python module", "MODULE_NAME"},\
69 72
 	{"Python application entrypoint function name", "FUNC_NAME"},\
73
+	{"Use stdout to communicate with web server instead of entrypoint return as specified in PEP 333", NULL},\
70 74
 	{"Minimum worker in the pool", "INT"},\
71 75
 	{"Maximum worker in the pool", "INT"},\
72 76
 	{"Request count after wich the worker is restarted (if 0 never restart)", "INT"},\
@@ -121,6 +125,9 @@ struct pyfcgi_conf_s
121 125
 	/**@brief Entrypoint function name
122 126
 	 * @ingroup conf_glob */
123 127
 	char *py_entryfun;
128
+	/**@brief If 0 use stdout to communicate with webserver, else
129
+	 * PyFCGI will expect PEP333 compliant entrypoint */
130
+	short pep333;
124 131
 	/**@brief Minimum count worker in pool
125 132
 	 * @ingroup conf_glob */
126 133
 	int min_wrk;

+ 2
- 0
include/logger.h View File

@@ -19,6 +19,7 @@
19 19
 #ifndef __LOGGER_H___
20 20
 #define __LOGGER_H___
21 21
 
22
+
22 23
 #include "config.h"
23 24
 #include <syslog.h>
24 25
 #include <stdlib.h>
@@ -38,6 +39,7 @@
38 39
  */
39 40
 /**@defgroup conf_logger Logging configuration
40 41
  * @ingroup conf_internal
42
+ * @ingroup logging
41 43
  */
42 44
 
43 45
 /**@defgroup log_facility Logger custom facilities */

+ 46
- 2
include/pyutils.h View File

@@ -28,7 +28,10 @@
28 28
 #include <Python.h>
29 29
 
30 30
 #include "logger.h"
31
+#include "python_pyfcgi.h"
31 32
 
33
+extern PyObject* response_status;
34
+extern PyObject* response_headers;
32 35
 /**@brief Call Py_Initialize & update_python_path
33 36
  */
34 37
 void pyinit();
@@ -37,14 +40,55 @@ void pyinit();
37 40
  */
38 41
 void update_python_path();
39 42
 
40
-/**@brief Import & return the python entrypoint callable
41
- * from PyFCGI_conf.py_entrymod & PyFCGI_conf.py_entryfun
43
+/**@brief Fetch stdout & stderr python flush() function
44
+ * @param PyObject* pystdout_flush
45
+ * @param PyObject* pystderr_flush
46
+ */
47
+void fetch_pyflush(PyObject**, PyObject**);
48
+
49
+/**@brief Create two pipes for stdout & stderr
50
+ * @ingroup worker_process
51
+ * @params int[2] pystdout
52
+ * @params int[2] pystderr
42 53
  */
54
+void update_python_fd(int[2], int[2]);
55
+
56
+/**@brief Clear then update python sys.environ using current FCI environ
57
+ * @ingroup worker_process
58
+ * @note The environ has to be set without a call to os.putenv, the problem
59
+ * is that the os.environ is a special mapping calling putenv on setitem...
60
+ * For these reason the os.environ will be replaced by a new dict instance for
61
+ * each request...
62
+ * @param PyObject* os module
63
+ * @return Python env dict
64
+ */
65
+PyObject* update_pyenv(PyObject*, char**);
66
+
67
+/**@brief Import & return the python entrypoint callable
68
+ * from PyFCGI_conf.py_entrymod & PyFCGI_conf.py_entryfun */
43 69
 PyObject* import_entrypoint();
44 70
 
71
+/**@brief Return the start_response() python function for pep333 worker */
72
+PyObject* get_start_response();
73
+
74
+/**@brief Clean response_status & response_headers globals */
75
+inline void libpyfcgi_clean_response()
76
+{
77
+	if(response_status) { Py_DECREF(response_status); }
78
+	if(response_headers) { Py_DECREF(response_headers); }
79
+}
80
+
81
+
82
+/**@brief Logs an exception */
45 83
 void log_expt(int priority);
46 84
 
85
+/**@brief Set python version
86
+ * @param char[16] version buffer */
47 87
 void pyfcgi_python_version(char[16]);
48 88
 
89
+/**@brief Import os module and exit on error
90
+ * @return Imported module */
91
+PyObject* python_osmod();
92
+
49 93
 #endif
50 94
 

+ 7
- 26
include/pyworker.h View File

@@ -81,7 +81,6 @@
81 81
 #include "logger.h"
82 82
 #include "pyutils.h"
83 83
 
84
-#define EXIT_PYERR 42
85 84
 #define WPIPER_SIG 30
86 85
 #define PIPER_STACK_SZ (1024 * 1024 * 4)
87 86
 
@@ -98,15 +97,20 @@ struct piper_args_s
98 97
 	struct sigaction *act;
99 98
 };
100 99
 
100
+extern PyObject* response_status;
101
+extern PyObject* response_headers;
102
+
101 103
 /**@todo TODO on all request (when updating env ?) update stdin so
102 104
  * python will be able to read the request content */
103 105
 
104
-/**@brief the function that initialize the python worker
106
+/**@brief the function that initialize the alternate python worker
105 107
  * @ingroup worker_process
108
+ *
109
+ * This function handles python worker using stdout to communicate with
110
+ * FCGI. This function clones for each request, running worker_piper()
106 111
  * @param char* python_entrypoint a path to a python entrypoint
107 112
  * @param int worker uid
108 113
  * @param int semid for FCGI access
109
- * @param int max request before worker restart
110 114
  * @return 0 if exit avec max requests
111 115
  */
112 116
 int work(int, int);
@@ -138,28 +142,5 @@ void worker_piper_sighandler(int);
138 142
  */
139 143
 int ctl_get_rep_sz(int, size_t*);
140 144
 
141
-/**@brief Fetch stdout & stderr python flush() function
142
- * @param PyObject* pystdout_flush
143
- * @param PyObject* pystderr_flush
144
- */
145
-void fetch_pyflush(PyObject**, PyObject**);
146
-
147
-/**@brief Create two pipes for stdout & stderr
148
- * @ingroup worker_process
149
- * @params int[2] pystdout
150
- * @params int[2] pystderr
151
- */
152
-void update_python_fd(int[2], int[2]);
153
-
154
-/**@brief Clear then update python sys.environ using current FCI environ
155
- * @ingroup worker_process
156
- * @note The environ has to be set without a call to os.putenv, the problem
157
- * is that the os.environ is a special mapping calling putenv on setitem...
158
- * For these reason the os.environ will be replaced by a new dict instance for
159
- * each request...
160
- * @param PyObject* os module
161
- */
162
-void update_pyenv(PyObject*, char**);
163
-
164 145
 #endif
165 146
 

+ 4
- 0
lib/Makefile.am View File

@@ -0,0 +1,4 @@
1
+lib_LTLIBRARIES = libpyfcgi.la
2
+libpyfcgi_la_SOURCES = pyfcgi.c
3
+libpyfcgi_la_CFLAGS = $(PYTHON_SO_CFLAGS)
4
+libpyfcgi_la_LDFLAGS = $(PYTHON_SO_LDFLAGS)

+ 48
- 0
lib/pyfcgi.c View File

@@ -0,0 +1,48 @@
1
+/*
2
+ * Copyright (C) 2019 Weber Yann
3
+ * 
4
+ * This file is part of PyFCGI.
5
+ * 
6
+ * PyFCGI is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU Affero General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * any later version.
10
+ * 
11
+ * PyFCGI is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU Affero General Public License for more details.
15
+ * 
16
+ * You should have received a copy of the GNU Affero General Public License
17
+ * along with PyFCGI.  If not, see <http://www.gnu.org/licenses/>.
18
+ */
19
+
20
+#include "python_pyfcgi.h"
21
+
22
+PyMODINIT_FUNC
23
+PyInit_libpyfcgi(void)
24
+{
25
+	PyObject *module;
26
+	module = PyModule_Create(&pyfcgimodule);
27
+	if(module == NULL) { return NULL; }
28
+	return module;
29
+}
30
+
31
+PyObject* pyfcgi_start_response(PyObject* self, PyObject** argv, Py_ssize_t argc)
32
+{
33
+	char err[64];
34
+	if(argc != 2)
35
+	{
36
+		snprintf(err, 64,
37
+			"libpyfcgi.start_response() expected 2 arguments but %d given",
38
+			argc);
39
+		PyErr_SetString(PyExc_ValueError, err);
40
+		Py_RETURN_NONE;
41
+	}
42
+
43
+	libpyfcgi_clean_response();
44
+
45
+	response_status = argv[0];
46
+	response_headers = argv[1];
47
+	Py_RETURN_NONE;
48
+}

+ 4
- 0
src/conf.c View File

@@ -44,6 +44,7 @@ void default_conf()
44 44
 	PyFCGI_conf.min_wrk = 1;
45 45
 	PyFCGI_conf.max_wrk = 5;
46 46
 	PyFCGI_conf.max_reqs = 1000;
47
+	PyFCGI_conf.pep333 = 1;
47 48
 }
48 49
 
49 50
 int parse_args(int argc, char *argv[])
@@ -71,6 +72,9 @@ int parse_args(int argc, char *argv[])
71 72
 			case 'E':
72 73
 				PyFCGI_conf.py_entryfun = strdup(optarg);
73 74
 				break;
75
+			case 'A':
76
+				PyFCGI_conf.pep333 = 0;
77
+				break;
74 78
 			case 'w':
75 79
 				PyFCGI_conf.min_wrk = atoi(optarg);
76 80
 				break;

+ 269
- 0
src/pyutils.c View File

@@ -112,6 +112,240 @@ update_python_path_err:
112 112
 	exit(err);
113 113
 }
114 114
 
115
+static PyObject* _fetch_pyflush(const char *fdname)
116
+{
117
+	PyObject *pyfd, *pyflush;
118
+	pyfd = PySys_GetObject(fdname);
119
+	if(!pyfd)
120
+	{
121
+		pyfcgi_log(LOG_ALERT, "Unable to fetch sys.%s", fdname);
122
+		log_expt(LOG_ALERT);
123
+		Py_Exit(EXIT_PYERR);
124
+	}
125
+	pyflush = PyObject_GetAttrString(pyfd, "flush");
126
+	Py_DECREF(pyfd);
127
+	if(!pyflush)
128
+	{
129
+		pyfcgi_log(LOG_ALERT, "Unable to fetch sys.%s.flush", fdname);
130
+		log_expt(LOG_ALERT);
131
+		Py_Exit(EXIT_PYERR);
132
+	}
133
+	if(!PyCallable_Check(pyflush))
134
+	{
135
+		pyfcgi_log(LOG_ALERT, "sys.%s.flush is not callable !",
136
+		       fdname);
137
+		Py_Exit(EXIT_PYERR);
138
+	}
139
+
140
+	return pyflush;
141
+}
142
+
143
+void fetch_pyflush(PyObject** pystdout_flush, PyObject** pystderr_flush)
144
+{
145
+	*pystdout_flush = _fetch_pyflush("stdout");
146
+	*pystderr_flush = _fetch_pyflush("stderr");
147
+}
148
+
149
+void update_python_fd(int pipe_out[2], int pipe_err[2])
150
+{
151
+	int pri, err;
152
+	char *err_fmt;
153
+	PyObject *os_mod, *pyfdopen, *args, *new_fd;
154
+
155
+	pri = LOG_ALERT;
156
+
157
+	if(pipe2(pipe_out, O_DIRECT) == -1)
158
+	{
159
+		err = errno;
160
+		err_fmt = "Unable to create pipe for python stdout : %s";
161
+		goto update_python_fd_err;
162
+	}
163
+	if(pipe2(pipe_err, O_DIRECT) == -1)
164
+	{
165
+		err = errno;
166
+		err_fmt = "Unable to create pipe for python stderr : %s";
167
+		goto update_python_fd_err_pipeout;
168
+	}
169
+
170
+	os_mod = PyImport_ImportModule("os");
171
+	if(!os_mod)
172
+	{
173
+		if(PyErr_Occurred())
174
+		{
175
+			log_expt(LOG_ALERT);
176
+		}
177
+		else
178
+		{
179
+			pyfcgi_log(	LOG_ALERT,
180
+				"Unable to import python os module, got NULL.");
181
+		}
182
+		err_fmt = NULL;
183
+		goto update_python_fd_err_pipes;
184
+	}
185
+	pyfdopen = PyObject_GetAttrString(os_mod, "fdopen");
186
+	Py_DECREF(os_mod);
187
+	if(!pyfdopen)
188
+	{
189
+		if(PyErr_Occurred())
190
+		{
191
+			log_expt(LOG_ALERT);
192
+		}
193
+		else
194
+		{
195
+			pyfcgi_log(	LOG_ALERT,
196
+				"Unable to fetch os.fdopen() , got NULL.");
197
+		}
198
+		err_fmt = NULL;
199
+		goto update_python_fd_err_pipes;
200
+	}
201
+
202
+	args = Py_BuildValue("is", pipe_out[1], "w");
203
+	if(!args)
204
+	{
205
+		pyfcgi_log(	LOG_ERR, "Error building values with '%d', '%s' for stdout",
206
+			pipe_out[1], "w");
207
+		log_expt(LOG_ALERT);
208
+		err_fmt = NULL;
209
+		goto update_python_fd_err_fdopen;
210
+	}
211
+	new_fd = PyObject_CallObject(pyfdopen, args);
212
+	if(!new_fd || PyErr_Occurred())
213
+	{
214
+		pyfcgi_log(	LOG_ERR, "Error calling fdopen(%d, '%s')",
215
+			pipe_out[1], "w");
216
+		log_expt(LOG_ALERT);
217
+		err_fmt = NULL;
218
+		goto update_python_fd_err_args;
219
+	}
220
+	Py_DECREF(args);
221
+	if(PySys_SetObject("stdout", new_fd))
222
+	{
223
+		pyfcgi_log(LOG_ERR, "Unable to set sys.stdout");
224
+		log_expt(LOG_ALERT);
225
+		goto update_python_fd_err_newfd;
226
+	}
227
+	Py_DECREF(new_fd);
228
+
229
+	args = Py_BuildValue("is", pipe_err[1], "w");
230
+	if(!args)
231
+	{
232
+		pyfcgi_log(	LOG_ERR, "Error building values with '%d', '%s' for stderr",
233
+			pipe_out[1], "w");
234
+		log_expt(LOG_ALERT);
235
+		err_fmt = NULL;
236
+		goto update_python_fd_err_fdopen;
237
+	}
238
+	new_fd = PyObject_CallObject(pyfdopen, args);
239
+	if(!new_fd || PyErr_Occurred())
240
+	{
241
+		pyfcgi_log(	LOG_ERR, "Error calling fdopen(%d, '%s')",
242
+			pipe_out[1], "w");
243
+		log_expt(LOG_ALERT);
244
+		err_fmt = NULL;
245
+		goto update_python_fd_err_args;
246
+	}
247
+	Py_DECREF(args);
248
+	if(PySys_SetObject("stderr", new_fd))
249
+	{
250
+		pyfcgi_log(LOG_ERR, "Unable to set sys.stderr");
251
+		log_expt(LOG_ALERT);
252
+		goto update_python_fd_err_newfd;
253
+	}
254
+	Py_DECREF(new_fd);
255
+
256
+	return;
257
+
258
+update_python_fd_err_newfd:
259
+	Py_DECREF(new_fd);
260
+update_python_fd_err_args:
261
+	Py_DECREF(args);
262
+update_python_fd_err_fdopen:
263
+	Py_DECREF(fdopen);
264
+update_python_fd_err_pipes:
265
+	close(pipe_err[0]);
266
+	close(pipe_err[1]);
267
+update_python_fd_err_pipeout:
268
+	close(pipe_out[0]);
269
+	close(pipe_out[1]);
270
+update_python_fd_err:
271
+	if(err_fmt)
272
+	{
273
+		pyfcgi_log(pri, err_fmt, strerror(err));
274
+	}
275
+	exit(1);
276
+}
277
+
278
+
279
+PyObject* update_pyenv(PyObject *py_osmod, char **environ)
280
+{
281
+	PyObject *pyenv, *pykey, *pyval;
282
+	char *key, *value, **cur;
283
+
284
+	cur = environ;
285
+
286
+	pyenv = PyObject_GetAttrString(py_osmod, "environ");
287
+	if(!pyenv)
288
+	{
289
+		pyfcgi_log(LOG_WARNING, "Unable to get os.environ");
290
+		log_expt(LOG_ALERT);
291
+	}
292
+	else
293
+	{
294
+		Py_DECREF(pyenv);
295
+	}
296
+	pyenv = PyDict_New();
297
+
298
+
299
+
300
+	while(*cur)
301
+	{
302
+		key = value = *cur;
303
+		while(*value && *value != '=')
304
+		{
305
+			value++;
306
+		}
307
+		if(!*value)
308
+		{
309
+			pyfcgi_log(LOG_WARNING, "Droping environment value without value : '%s'",
310
+			       key);
311
+			cur++;
312
+			continue;
313
+		}
314
+		value++;
315
+		*(value-1) = '\0'; // dirty modification of **environ
316
+//pyfcgi_log(LOG_DEBUG, "PySetEnv '%s'='%s'", key, value);
317
+		pykey = PyUnicode_DecodeLocale(key, "surrogateescape");
318
+		if(!pykey)
319
+		{
320
+			*(value-1) = '='; // **environ restore
321
+			pyfcgi_log(LOG_ALERT, "Unable to parse environ key string '%s'",
322
+			       key);
323
+			log_expt(LOG_ALERT);
324
+			Py_Exit(EXIT_PYERR);
325
+		}
326
+		*(value-1) = '='; // **environ restore
327
+		pyval = PyUnicode_DecodeFSDefault(value);
328
+		if(!pyval)
329
+		{
330
+			pyfcgi_log(LOG_ALERT, "Unable to parse environ val string '%s'",
331
+			       value);
332
+			log_expt(LOG_ALERT);
333
+			Py_Exit(EXIT_PYERR);
334
+		}
335
+		if(PyDict_SetItem(pyenv, pykey, pyval) == -1)
336
+		{
337
+			pyfcgi_log(LOG_ERR, "Unable to set environ '%s'='%s'",
338
+			       key, value);
339
+			log_expt(LOG_ERR);
340
+		}
341
+		Py_DECREF(pyval);
342
+		Py_DECREF(pykey);
343
+		cur++;
344
+	}
345
+	PyObject_SetAttrString(py_osmod, "environ", pyenv);
346
+	return pyenv;
347
+}
348
+
115 349
 PyObject* import_entrypoint()
116 350
 {
117 351
 	PyObject *entry_fname, *entry_module, *entry_fun;
@@ -152,6 +386,29 @@ PyObject* import_entrypoint()
152 386
 	return entry_fun;
153 387
 }
154 388
 
389
+static PyObject* libpyfcgi_self = NULL;
390
+PyObject* get_start_response()
391
+{
392
+	PyObject *module;
393
+	response_status = NULL;
394
+	response_headers = NULL;
395
+	if(!libpyfcgi_self)
396
+	{
397
+		module = PyModule_Create(&pyfcgimodule);
398
+		if(module == NULL)
399
+		{
400
+			pyfcgi_log(LOG_ERR, "Unable to create libpyfcgi python module");
401
+			return NULL;
402
+		}
403
+		libpyfcgi_self = module;
404
+	}
405
+	else
406
+	{
407
+		module = libpyfcgi_self;
408
+	}
409
+	return PyObject_GetAttrString(libpyfcgi_self, "start_response");
410
+}
411
+
155 412
 void log_expt(int priority)
156 413
 {
157 414
 	if(!PyErr_Occurred())
@@ -200,3 +457,15 @@ void pyfcgi_python_version(char version[16])
200 457
 		PY_MICRO_VERSION);
201 458
 }
202 459
 
460
+PyObject* python_osmod()
461
+{
462
+	PyObject *ret;
463
+	ret = PyImport_ImportModule("os");
464
+	if(!ret)
465
+	{
466
+		pyfcgi_log(LOG_ALERT, "Unable to import os module");
467
+		log_expt(LOG_ALERT);
468
+		Py_Exit(EXIT_PYERR);
469
+	}
470
+	return ret;
471
+}

+ 109
- 266
src/pyworker.c View File

@@ -19,17 +19,96 @@
19 19
 
20 20
 #include "pyworker.h"
21 21
 
22
+
23
+/**@brief Indicate that a worker is idle
24
+ * @param int semid */
25
+static inline void worker_set_idle(int);
26
+/**@brief Indicate that a worker is busy
27
+ * @param int semid */
28
+static inline void worker_set_busy(int);
29
+
22 30
 static int worker_piper_sigrcv = 0;
23 31
 
32
+int work333(int wrk_id, int semid)
33
+{
34
+	PyObject *entry_fun, *pyflush[2], *py_osmod, *entry_ret, *environ,
35
+		*start_response, *args;
36
+	FCGX_Stream *in_stream, *out_stream, *err_stream;
37
+	char **envp;
38
+	int count, pipe_out[2], pipe_err[2], err;
39
+	int max_reqs;
40
+	char ident[128];
41
+	struct timeval start, stop;
42
+
43
+	max_reqs = PyFCGI_conf.max_reqs;
44
+
45
+	snprintf(ident, 128, "Worker[%d]", wrk_id);
46
+	pyfcgi_logger_set_ident(ident);
47
+
48
+	pyfcgi_log(LOG_INFO, "Worker started with PEP333 App");
49
+
50
+	update_python_path(); // add cwd to python path
51
+	Py_Initialize();
52
+	update_python_fd(pipe_out, pipe_err);
53
+	fetch_pyflush(&(pyflush[0]), &(pyflush[1]));
54
+	pyfcgi_log(LOG_INFO, "Python started");
55
+
56
+	//importing os
57
+	py_osmod = python_osmod();
58
+	// loading module
59
+	entry_fun = import_entrypoint();
60
+
61
+	pyfcgi_log(LOG_INFO, "Waiting request with %s.%s()",
62
+		PyFCGI_conf.py_entrymod, PyFCGI_conf.py_entryfun);
63
+
64
+	worker_set_idle(semid); //before failing on import
65
+
66
+	if(!entry_fun) //but exit if import failed
67
+	{
68
+		pyfcgi_log(LOG_ALERT, "Unable to import entrypoint");
69
+		exit(PYFCGI_FATAL);
70
+
71
+	}
72
+
73
+	start_response = get_start_response();
74
+
75
+	// requests accepting loop
76
+	count = 0;
77
+	while ((!count || count != max_reqs) &&
78
+		FCGX_Accept(&in_stream, &out_stream, &err_stream, &envp) >= 0)
79
+	{
80
+		gettimeofday(&start, NULL);
81
+		worker_set_busy(semid);
82
+		count++;
83
+		environ = update_pyenv(py_osmod, envp);
84
+
85
+		libpyfcgi_clean_response();
86
+		args = Py_BuildValue("OO", environ, start_response);
87
+		entry_ret = PyObject_CallObject(entry_fun, args);
88
+		if(PyErr_Occurred())
89
+		{
90
+			log_expt(LOG_ERR);
91
+		}
92
+		// able to process returned value
93
+
94
+		// clean stuffs
95
+		Py_DECREF(args);
96
+		Py_DECREF(environ);
97
+		// flush & logs pystdout & pystderr
98
+		PyObject_CallObject(pyflush[0], NULL);
99
+		PyObject_CallObject(pyflush[1], NULL);
100
+	}
101
+	return 0;
102
+}
103
+
24 104
 int work(int wrk_id, int semid)
25 105
 {
26
-	PyObject *entry_fun, *pystdout_flush, *pystderr_flush,
27
-	         *py_osmod;
106
+	PyObject *entry_fun, *pystdout_flush, *pystderr_flush, *py_osmod,
107
+		*environ;
28 108
 	FCGX_Stream *in_stream, *out_stream, *err_stream;
29 109
 	char **envp;
30 110
 	int count, pipe_out[2], pipe_err[2], pipe_ctl[2], err, piper_status;
31 111
 	int max_reqs;
32
-	struct sembuf sop;
33 112
 	struct sigaction act;
34 113
 	struct timeval start, stop;
35 114
 	sigset_t emptyset;
@@ -41,8 +120,6 @@ int work(int wrk_id, int semid)
41 120
 	max_reqs = PyFCGI_conf.max_reqs;
42 121
 	piper_args.wrk_id = wrk_id;
43 122
 	piper_args.act = &act;
44
-	sop.sem_num = 0;
45
-	sop.sem_flg = 0;
46 123
 
47 124
 	// preparing sigaction for piper
48 125
 	if(sigemptyset(&emptyset))
@@ -81,14 +158,7 @@ int work(int wrk_id, int semid)
81 158
 		"Worker[%d] Python started", wrk_id);
82 159
 	
83 160
 	//importing os
84
-	py_osmod = PyImport_ImportModule("os");
85
-	if(!py_osmod)
86
-	{
87
-		pyfcgi_log(LOG_ALERT, "Unable to import os module");
88
-		log_expt(LOG_ALERT);
89
-		Py_Exit(EXIT_PYERR);
90
-	}
91
-
161
+	py_osmod = python_osmod();
92 162
 	// loading module
93 163
 	entry_fun = import_entrypoint();
94 164
 
@@ -97,14 +167,7 @@ int work(int wrk_id, int semid)
97 167
 		PyFCGI_conf.py_entrymod, PyFCGI_conf.py_entryfun);
98 168
 
99 169
 
100
-	sop.sem_op = 1; //indicate worker as ready & idle
101
-	if(semop(semid, &sop, 1) < 0)
102
-	{
103
-		err = errno;
104
-		pyfcgi_log(LOG_ERR,
105
-		       "Worker[%d] error incrementing the semaphore : %s",
106
-		       wrk_id, strerror(err));
107
-	}
170
+	worker_set_idle(semid); //before failing on import
108 171
 
109 172
 	if(!entry_fun) //but exit if import failed
110 173
 	{
@@ -127,20 +190,14 @@ int work(int wrk_id, int semid)
127 190
 	while ((!count || count != max_reqs) && FCGX_Accept(&in_stream, &out_stream, &err_stream, &envp) >= 0)
128 191
 	{
129 192
 		gettimeofday(&start, NULL);
130
-		sop.sem_op = -1; // decrementing sem to show worker busy
131
-		if(semop(semid, &sop, 1) < 0)
132
-		{
133
-			err = errno;
134
-			pyfcgi_log(LOG_ERR,
135
-			       "Worker[%d] error decrementing the semaphore : %s",
136
-			       wrk_id, strerror(err));
137
-		}
193
+		worker_set_busy(semid);
138 194
 
139 195
 		count++;
140 196
 		piper_args.out = out_stream;
141 197
 		//piper_args.req_id = count;
142 198
 		worker_piper_sigrcv = 0;
143
-
199
+//		if(!PyFCGI_conf.pep333)
200
+		/**@todo avoid clone on each request... (using named pipes ?) */
144 201
 		pid_t pid = clone(worker_piper, piper_stack + PIPER_STACK_SZ - 1,
145 202
 		                  CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID | \
146 203
 				  SIGCHLD | \
@@ -154,7 +211,8 @@ int work(int wrk_id, int semid)
154 211
 			       wrk_id, count, strerror(err));
155 212
 			exit(err);
156 213
 		}
157
-		update_pyenv(py_osmod, envp);
214
+		environ = update_pyenv(py_osmod, envp);
215
+		Py_DECREF(environ);
158 216
 		//close(pipe_ctl[1]);
159 217
 		PyObject_CallObject(entry_fun, NULL);
160 218
 		if(PyErr_Occurred())
@@ -176,15 +234,7 @@ int work(int wrk_id, int semid)
176 234
 		//close(pipe_ctl[0]);
177 235
 
178 236
 		kill(pid, WPIPER_SIG); //indicate child python call ended
179
-/*
180
-gettimeofday(&stop, NULL);
181
-pyfcgi_log(LOG_DEBUG, "W%dR%dtimer KIL %ld.%06ld us", wrk_id, count, stop.tv_sec - start.tv_sec, stop.tv_usec - start.tv_usec);
182
-*/
183 237
 		waitpid(pid, &piper_status, 0);
184
-/*
185
-gettimeofday(&stop, NULL);
186
-pyfcgi_log(LOG_DEBUG, "W%dR%dtimer W8  %ld.%06ld us", wrk_id, count, stop.tv_sec - start.tv_sec, stop.tv_usec - start.tv_usec);
187
-*/
188 238
 		if(WIFSIGNALED(piper_status))
189 239
 		{
190 240
 			pyfcgi_log(LOG_ERR,
@@ -206,14 +256,7 @@ pyfcgi_log(LOG_DEBUG, "W%dR%dtimer W8  %ld.%06ld us", wrk_id, count, stop.tv_sec
206 256
 			rep_sz = 0;
207 257
 		}
208 258
 		//Increase sem showing the worker is idle
209
-		sop.sem_op = 1;
210
-		if(semop(semid, &sop, 1) < 0)
211
-		{
212
-			err = errno;
213
-			pyfcgi_log(LOG_ERR,
214
-			       "Worker[%d] error incrementing the semaphore : %s",
215
-			       wrk_id, strerror(err));
216
-		}
259
+		worker_set_idle(semid);
217 260
 		gettimeofday(&stop, NULL);
218 261
 		stop.tv_sec = stop.tv_sec - start.tv_sec;
219 262
 		stop.tv_usec = stop.tv_usec - start.tv_usec;
@@ -434,237 +477,37 @@ int ctl_get_rep_sz(int ctl_pipe, size_t* rep_sz)
434 477
 	return 0;
435 478
 }
436 479
 
437
-static PyObject* _fetch_pyflush(const char *fdname)
438
-{
439
-	PyObject *pyfd, *pyflush;
440
-	pyfd = PySys_GetObject(fdname);
441
-	if(!pyfd)
442
-	{
443
-		pyfcgi_log(LOG_ALERT, "Unable to fetch sys.%s", fdname);
444
-		log_expt(LOG_ALERT);
445
-		Py_Exit(EXIT_PYERR);
446
-	}
447
-	pyflush = PyObject_GetAttrString(pyfd, "flush");
448
-	Py_DECREF(pyfd);
449
-	if(!pyflush)
450
-	{
451
-		pyfcgi_log(LOG_ALERT, "Unable to fetch sys.%s.flush", fdname);
452
-		log_expt(LOG_ALERT);
453
-		Py_Exit(EXIT_PYERR);
454
-	}
455
-	if(!PyCallable_Check(pyflush))
456
-	{
457
-		pyfcgi_log(LOG_ALERT, "sys.%s.flush is not callable !",
458
-		       fdname);
459
-		Py_Exit(EXIT_PYERR);
460
-	}
461
-
462
-	return pyflush;
463
-}
464
-
465
-void fetch_pyflush(PyObject** pystdout_flush, PyObject** pystderr_flush)
466
-{
467
-	*pystdout_flush = _fetch_pyflush("stdout");
468
-	*pystderr_flush = _fetch_pyflush("stderr");
469
-}
470
-
471
-void update_python_fd(int pipe_out[2], int pipe_err[2])
480
+static void worker_set_idle(int semid)
472 481
 {
473
-	int pri, err;
474
-	char *err_fmt;
475
-	PyObject *os_mod, *pyfdopen, *args, *new_fd;
482
+	int err;
483
+	struct sembuf sop;
476 484
 
477
-	pri = LOG_ALERT;
485
+	sop.sem_num = 0;
486
+	sop.sem_flg = 0;
487
+	sop.sem_op = 1;
478 488
 
479
-	if(pipe2(pipe_out, O_DIRECT) == -1)
480
-	{
481
-		err = errno;
482
-		err_fmt = "Unable to create pipe for python stdout : %s";
483
-		goto update_python_fd_err;
484
-	}
485
-	if(pipe2(pipe_err, O_DIRECT) == -1)
489
+	if(semop(semid, &sop, 1) < 0)
486 490
 	{
487 491
 		err = errno;
488
-		err_fmt = "Unable to create pipe for python stderr : %s";
489
-		goto update_python_fd_err_pipeout;
490
-	}
491
-
492
-	os_mod = PyImport_ImportModule("os");
493
-	if(!os_mod)
494
-	{
495
-		if(PyErr_Occurred())
496
-		{
497
-			log_expt(LOG_ALERT);
498
-		}
499
-		else
500
-		{
501
-			pyfcgi_log(	LOG_ALERT,
502
-				"Unable to import python os module, got NULL.");
503
-		}
504
-		err_fmt = NULL;
505
-		goto update_python_fd_err_pipes;
506
-	}
507
-	pyfdopen = PyObject_GetAttrString(os_mod, "fdopen");
508
-	Py_DECREF(os_mod);
509
-	if(!pyfdopen)
510
-	{
511
-		if(PyErr_Occurred())
512
-		{
513
-			log_expt(LOG_ALERT);
514
-		}
515
-		else
516
-		{
517
-			pyfcgi_log(	LOG_ALERT,
518
-				"Unable to fetch os.fdopen() , got NULL.");
519
-		}
520
-		err_fmt = NULL;
521
-		goto update_python_fd_err_pipes;
522
-	}
523
-
524
-	args = Py_BuildValue("is", pipe_out[1], "w");
525
-	if(!args)
526
-	{
527
-		pyfcgi_log(	LOG_ERR, "Error building values with '%d', '%s' for stdout",
528
-			pipe_out[1], "w");
529
-		log_expt(LOG_ALERT);
530
-		err_fmt = NULL;
531
-		goto update_python_fd_err_fdopen;
532
-	}
533
-	new_fd = PyObject_CallObject(pyfdopen, args);
534
-	if(!new_fd || PyErr_Occurred())
535
-	{
536
-		pyfcgi_log(	LOG_ERR, "Error calling fdopen(%d, '%s')",
537
-			pipe_out[1], "w");
538
-		log_expt(LOG_ALERT);
539
-		err_fmt = NULL;
540
-		goto update_python_fd_err_args;
541
-	}
542
-	Py_DECREF(args);
543
-	if(PySys_SetObject("stdout", new_fd))
544
-	{
545
-		pyfcgi_log(LOG_ERR, "Unable to set sys.stdout");
546
-		log_expt(LOG_ALERT);
547
-		goto update_python_fd_err_newfd;
548
-	}
549
-	Py_DECREF(new_fd);
550
-
551
-	args = Py_BuildValue("is", pipe_err[1], "w");
552
-	if(!args)
553
-	{
554
-		pyfcgi_log(	LOG_ERR, "Error building values with '%d', '%s' for stderr",
555
-			pipe_out[1], "w");
556
-		log_expt(LOG_ALERT);
557
-		err_fmt = NULL;
558
-		goto update_python_fd_err_fdopen;
559
-	}
560
-	new_fd = PyObject_CallObject(pyfdopen, args);
561
-	if(!new_fd || PyErr_Occurred())
562
-	{
563
-		pyfcgi_log(	LOG_ERR, "Error calling fdopen(%d, '%s')",
564
-			pipe_out[1], "w");
565
-		log_expt(LOG_ALERT);
566
-		err_fmt = NULL;
567
-		goto update_python_fd_err_args;
568
-	}
569
-	Py_DECREF(args);
570
-	if(PySys_SetObject("stderr", new_fd))
571
-	{
572
-		pyfcgi_log(LOG_ERR, "Unable to set sys.stderr");
573
-		log_expt(LOG_ALERT);
574
-		goto update_python_fd_err_newfd;
575
-	}
576
-	Py_DECREF(new_fd);
577
-
578
-	return;
579
-
580
-update_python_fd_err_newfd:
581
-	Py_DECREF(new_fd);
582
-update_python_fd_err_args:
583
-	Py_DECREF(args);
584
-update_python_fd_err_fdopen:
585
-	Py_DECREF(fdopen);
586
-update_python_fd_err_pipes:
587
-	close(pipe_err[0]);
588
-	close(pipe_err[1]);
589
-update_python_fd_err_pipeout:
590
-	close(pipe_out[0]);
591
-	close(pipe_out[1]);
592
-update_python_fd_err:
593
-	if(err_fmt)
594
-	{
595
-		pyfcgi_log(pri, err_fmt, strerror(err));
492
+		pyfcgi_log(LOG_ERR, "error incrementing the semaphore : %s",
493
+			strerror(err));
596 494
 	}
597
-	exit(1);
598 495
 }
599 496
 
600
-
601
-void update_pyenv(PyObject *py_osmod, char **environ)
497
+static void worker_set_busy(int semid)
602 498
 {
603
-	PyObject *pyenv, *pykey, *pyval;
604
-	char *key, *value, **cur;
605
-
606
-	cur = environ;
607
-
608
-	pyenv = PyObject_GetAttrString(py_osmod, "environ");
609
-	if(!pyenv)
610
-	{
611
-		pyfcgi_log(LOG_WARNING, "Unable to get os.environ");
612
-		log_expt(LOG_ALERT);
613
-	}
614
-	else
615
-	{
616
-		Py_DECREF(pyenv);
617
-	}
618
-	pyenv = PyDict_New();
619
-
499
+	int err;
500
+	struct sembuf sop;
620 501
 
502
+	sop.sem_num = 0;
503
+	sop.sem_flg = 0;
504
+	sop.sem_op = -1;
621 505
 
622
-	while(*cur)
506
+	if(semop(semid, &sop, 1) < 0)
623 507
 	{
624
-		key = value = *cur;
625
-		while(*value && *value != '=')
626
-		{
627
-			value++;
628
-		}
629
-		if(!*value)
630
-		{
631
-			pyfcgi_log(LOG_WARNING, "Droping environment value without value : '%s'",
632
-			       key);
633
-			cur++;
634
-			continue;
635
-		}
636
-		value++;
637
-		*(value-1) = '\0'; // dirty modification of **environ
638
-//pyfcgi_log(LOG_DEBUG, "PySetEnv '%s'='%s'", key, value);
639
-		pykey = PyUnicode_DecodeLocale(key, "surrogateescape");
640
-		if(!pykey)
641
-		{
642
-			*(value-1) = '='; // **environ restore
643
-			pyfcgi_log(LOG_ALERT, "Unable to parse environ key string '%s'",
644
-			       key);
645
-			log_expt(LOG_ALERT);
646
-			Py_Exit(EXIT_PYERR);
647
-		}
648
-		*(value-1) = '='; // **environ restore
649
-		pyval = PyUnicode_DecodeFSDefault(value);
650
-		if(!pyval)
651
-		{
652
-			pyfcgi_log(LOG_ALERT, "Unable to parse environ val string '%s'",
653
-			       value);
654
-			log_expt(LOG_ALERT);
655
-			Py_Exit(EXIT_PYERR);
656
-		}
657
-		if(PyDict_SetItem(pyenv, pykey, pyval) == -1)
658
-		{
659
-			pyfcgi_log(LOG_ERR, "Unable to set environ '%s'='%s'",
660
-			       key, value);
661
-			log_expt(LOG_ERR);
662
-		}
663
-		Py_DECREF(pyval);
664
-		Py_DECREF(pykey);
665
-		cur++;
508
+		err = errno;
509
+		pyfcgi_log(LOG_ERR, "error incrementing the semaphore : %s",
510
+			strerror(err));
666 511
 	}
667
-	PyObject_SetAttrString(py_osmod, "environ", pyenv);
668
-
669 512
 }
670 513
 

+ 2
- 2
tests/Makefile.am View File

@@ -1,8 +1,8 @@
1 1
 TESTS = check_logger
2 2
 check_PROGRAMS = check_logger
3 3
 check_logger_SOURCES = check_logger.c $(top_builddir)/include/logger.h
4
-check_logger_CFLAGS = @CHECK_CFLAGS@
5
-check_logger_LDADD = $(top_builddir)/src/libpyfcgi.a @CHECK_LIBS@
4
+check_logger_CFLAGS = @CHECK_CFLAGS@ $(PYTHON_CFLAGS)
5
+check_logger_LDADD = $(top_builddir)/src/libpyfcgi.a @CHECK_LIBS@ $(PYTHON_LDFLAGS)
6 6
 
7 7
 clean-local:
8 8
 	-rm -rf /tmp/tmp_PyFCGI_checks*

Loading…
Cancel
Save