Browse Source

1st fonctionnal implementation of both python<->C IPC

Yann Weber 4 years ago
parent
commit
0c527fc2dc
9 changed files with 681 additions and 44 deletions
  1. 2
    2
      foo.py
  2. 8
    0
      foo_pep333.py
  3. 140
    0
      include/python_pyfcgi.h
  4. 3
    7
      include/pyutils.h
  5. 14
    0
      include/pyworker.h
  6. 434
    11
      src/python_pyfcgi.c
  7. 8
    18
      src/pyutils.c
  8. 49
    5
      src/pyworker.c
  9. 23
    1
      src/responder.c

+ 2
- 2
foo.py View File

@@ -4,8 +4,8 @@ import time
4 4
 
5 5
 def entrypoint():
6 6
 	import os
7
-	sys.stderr.write('Called ! req by %s' % os.getenv('REMOTE_ADDR'))
7
+	#sys.stderr.write('Called ! req by %s' % os.getenv('REMOTE_ADDR'))
8 8
 	env = "foo"
9 9
 	env = '<br/>'.join(["'%s'='%s'" % (k, os.environ[k]) for k in os.environ.keys()])
10
-	msg = "Content-Type: text/html\r\n\r\nHello world !(%0.2f)\nenv : %s" % (time.time(), env)
10
+	msg = "Content-Type: text/html\r\nStatus: 404 Not Found\r\n\r\nHello world !(%0.2f)\nenv : %s" % (time.time(), env)
11 11
 	sys.stdout.write(msg)

+ 8
- 0
foo_pep333.py View File

@@ -0,0 +1,8 @@
1
+import sys
2
+import os
3
+import time
4
+
5
+def entrypoint(env, start_response):
6
+	write_body = start_response("200 OK", [('Content-type', 'text/plain')])
7
+	write_body('Hello world !')
8
+	return ['Hello world !']

+ 140
- 0
include/python_pyfcgi.h View File

@@ -0,0 +1,140 @@
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
+/**@defgroup libpyfcgi libpyfcgi python module
21
+ * @brief The libpyfcgi python module
22
+ *
23
+ * PyFCGI defines a python module : libpyfcgi for PEP333 implementation.
24
+ * This module contains 2 methods : start_response() and write_body()
25
+ *
26
+ * The first one is given as argument to the application function, and
27
+ * the second one is returned when start_method() is called.
28
+ */
29
+
30
+/**@file python_pyfcgi.h
31
+ * @ingroup libpyfcgi
32
+ */
33
+#ifndef _PYTHON_PYFCGI__H__
34
+#define _PYTHON_PYFCGI__H__
35
+
36
+#include "config.h"
37
+
38
+#include <fcgiapp.h>
39
+#include <fcgi_stdio.h> /* fcgi library; put it first*/
40
+
41
+#define PY_SSIZE_T_CLEAN
42
+#include <Python.h>
43
+#include "structmember.h"
44
+
45
+#include "logger.h"
46
+
47
+#define LIBPYFCGI_DEFAULT_HEADERS "Content-Type: text/html\r\nStatus: %s\r\n\r\n"
48
+#define LIBPYFCGI_DEFAULT_STATUS "200 OK"
49
+#define LIBPYFCGI_DEFAULT_CTYPE "Content-Type: text/html\r\n"
50
+#define LIBPYFCGI_STATUS_SZ 128
51
+
52
+struct libpyfcgi_context_s
53
+{
54
+	/**@brief PEP333 handling python module reference
55
+	 * @ingroup libpyfcgi */
56
+	PyObject *self;
57
+	/**@brief PEP333 status ref
58
+	 * @ingroup libpyfcgi */
59
+	PyObject *status;
60
+	/**@brief PEP333 headers ref
61
+	 * @ingroup libpyfcgi */
62
+	PyObject *headers;
63
+	/**@brief Indicate if headers was sent in a PEP333 application
64
+	 * @ingroup libpyfcgi */
65
+	short headers_sent;
66
+	/**@brief Stores the libFCGI stream for PEP333 application
67
+	 * @ingroup libpyfcgi */
68
+	FCGX_Stream *out;
69
+	/**@brief Persistent buffer (avoid malloc) for PEP333 headers */
70
+	char *heads_buf;
71
+	/**@brief Buffer size */
72
+	size_t heads_buf_sz;
73
+	/**@brief Persistent buffer for PEP333 status */
74
+	char status_buf[LIBPYFCGI_STATUS_SZ];
75
+};
76
+
77
+typedef struct libpyfcgi_context_s libpyfcgi_context_t;
78
+
79
+/**@brief Stores python module context */
80
+extern libpyfcgi_context_t libpyfcgi;
81
+/**@brief libpyfcgi methods */
82
+extern PyMethodDef pyfcgimodule_methods[];
83
+/**@brief libpyfcgi module structure */
84
+extern PyModuleDef pyfcgimodule;
85
+
86
+/**@brief Clean response_status & response_headers globals */
87
+inline void libpyfcgi_clean_response()
88
+{
89
+	if(libpyfcgi.status) { Py_DECREF(libpyfcgi.status); }
90
+	libpyfcgi.status = NULL;
91
+	if(libpyfcgi.headers) { Py_DECREF(libpyfcgi.headers); }
92
+	libpyfcgi.headers = NULL;
93
+	libpyfcgi.headers_sent = 0;
94
+}
95
+
96
+/**@brief Send headers stored in @ref libpyfcgi context
97
+ * @note Set python error if called from outside a valid context
98
+ */
99
+void libpyfcgi_send_headers();
100
+
101
+/**@brief Send body to fcgi
102
+ * @param PyObject* the body data object (returned by PEP333 app)
103
+ * @return Python None
104
+ */
105
+PyObject* _pyfcgi_write_body(PyObject *body_data);
106
+
107
+/* Defining Python module */
108
+
109
+/**@brief Public module initialisation function
110
+ * @ingroup libpyfcgi
111
+ * @return A python module (PyObject*)
112
+ */
113
+PyMODINIT_FUNC PyInit_libpyfcgi(void);
114
+
115
+/**@brief libpyfcgi.start_response() python callable
116
+ * @ingroup libpyfcgi
117
+ * @note This python callable is a fastcall C method of libpyfcgi module.
118
+ * 
119
+ * The python function header is : start_response(status, response_headers, exc_info = None)
120
+ * @param PyObject* self
121
+ * @param PyObject* argv
122
+ * @param Py_ssize_t argc
123
+ * @return A PyObject* referencing a callable allowing to write data without
124
+ * cache : libpyfcgi.write_body()
125
+ */
126
+PyObject* pyfcgi_start_response(PyObject*, PyObject**, Py_ssize_t);
127
+
128
+/**@brief libpyfcgi.write_body() python callable
129
+ * @ingroup libpyfcgi
130
+ * @note This python callable is a fastcall C method of libpyfcgi module.
131
+ *
132
+ * The python function header is : write_body(body_data)
133
+ * @param PyObject* self
134
+ * @param PyObject* argv
135
+ * @param Py_ssize_t argc
136
+ * @return ???
137
+ */
138
+PyObject* pyfcgi_write_body(PyObject*, PyObject**, Py_ssize_t);
139
+
140
+#endif

+ 3
- 7
include/pyutils.h View File

@@ -30,8 +30,11 @@
30 30
 #include "logger.h"
31 31
 #include "python_pyfcgi.h"
32 32
 
33
+/* Imports from libpyfcgi python module header */
33 34
 extern PyObject* response_status;
34 35
 extern PyObject* response_headers;
36
+extern PyObject* libpyfcgi_self;
37
+
35 38
 /**@brief Call Py_Initialize & update_python_path
36 39
  */
37 40
 void pyinit();
@@ -71,13 +74,6 @@ PyObject* import_entrypoint();
71 74
 /**@brief Return the start_response() python function for pep333 worker */
72 75
 PyObject* get_start_response();
73 76
 
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 77
 
82 78
 /**@brief Logs an exception */
83 79
 void log_expt(int priority);

+ 14
- 0
include/pyworker.h View File

@@ -80,6 +80,7 @@
80 80
 
81 81
 #include "logger.h"
82 82
 #include "pyutils.h"
83
+#include "python_pyfcgi.h"
83 84
 
84 85
 #define WPIPER_SIG 30
85 86
 #define PIPER_STACK_SZ (1024 * 1024 * 4)
@@ -103,6 +104,19 @@ extern PyObject* response_headers;
103 104
 /**@todo TODO on all request (when updating env ?) update stdin so
104 105
  * python will be able to read the request content */
105 106
 
107
+/**@brief The function that initialize the alternate python worker
108
+ * @ingroup worker_process
109
+ *
110
+ * This function handles python worker by running a PEP333 application function.
111
+ * The start_response() function set @ref response_status and
112
+ * @ref response_headers globals.
113
+ * @param char* python_entrypoint a path to a python entrypoint
114
+ * @param int worker uid
115
+ * @param int semid for FCGI access
116
+ * @return 0 if exit avec max requests
117
+ */
118
+int work333(int, int);
119
+
106 120
 /**@brief the function that initialize the alternate python worker
107 121
  * @ingroup worker_process
108 122
  *

+ 434
- 11
src/python_pyfcgi.c View File

@@ -19,30 +19,453 @@
19 19
 
20 20
 #include "python_pyfcgi.h"
21 21
 
22
+/* Globals definitions */
23
+/* libpyfcgi context */
24
+libpyfcgi_context_t libpyfcgi = { NULL, NULL, NULL, 0, NULL, NULL, 0 };
25
+/* Python module methods specs */
26
+PyMethodDef pyfcgimodule_methods[] = {
27
+	{"start_response", (PyCFunction)pyfcgi_start_response, METH_FASTCALL, NULL},
28
+	{"write_body", (PyCFunction)pyfcgi_write_body, METH_FASTCALL, NULL},
29
+	{NULL} // Sentinel
30
+};
31
+/* Python module specs */
32
+PyModuleDef pyfcgimodule = {
33
+	PyModuleDef_HEAD_INIT,
34
+	"libpyfcgi",
35
+	"Python librarie for PyFCGi",
36
+	-1,
37
+	pyfcgimodule_methods,
38
+	NULL, NULL, NULL, NULL
39
+};
40
+
41
+/* Function definition */
42
+
43
+/**@brief Forge & send default headers */
44
+static void _default_headers()
45
+{
46
+	size_t sz;
47
+	void *tmp;
48
+	while(1)
49
+	{
50
+		sz = snprintf(libpyfcgi.heads_buf, libpyfcgi.heads_buf_sz,
51
+			LIBPYFCGI_DEFAULT_HEADERS, libpyfcgi.status_buf);
52
+
53
+		if(sz < libpyfcgi.heads_buf_sz) { break; }
54
+
55
+		sz = ((sz>>11)+1)<<11; // 2048 alloc blocks
56
+		tmp = realloc(libpyfcgi.heads_buf, sz);
57
+		if(!tmp)
58
+		{
59
+			pyfcgi_log(LOG_ALERT,
60
+				"Unable to realloc headers buffer : %s",
61
+				strerror(errno));
62
+			exit(PYFCGI_FATAL);
63
+		}
64
+		libpyfcgi.heads_buf = tmp;
65
+		libpyfcgi.heads_buf_sz = sz;
66
+	}
67
+}
68
+
69
+static void _set_status_buf()
70
+{
71
+	PyObject *bytes;
72
+
73
+	short decref = 1;
74
+	if(!libpyfcgi.status)
75
+	{
76
+		libpyfcgi.status = Py_BuildValue(LIBPYFCGI_DEFAULT_STATUS);
77
+		return;
78
+	}
79
+	if(PyBytes_Check(libpyfcgi.status))
80
+	{
81
+		bytes = libpyfcgi.status;
82
+		decref = 0;
83
+	}
84
+	else if(PyUnicode_Check(libpyfcgi.status))
85
+	{
86
+		
87
+		bytes = PyUnicode_AsUTF8String(libpyfcgi.status);
88
+		if(!bytes)
89
+		{
90
+			pyfcgi_log(LOG_ERR, "Unable to encode status using UTF8 codec");
91
+			log_expt(LOG_ERR);
92
+		}
93
+	}
94
+	else
95
+	{
96
+		bytes = PyObject_Bytes(libpyfcgi.status);
97
+		if(!bytes)
98
+		{
99
+			pyfcgi_log(LOG_ERR, "Unable to convert status to bytes");
100
+			log_expt(LOG_ERR);
101
+		}
102
+	}
103
+	strncpy(libpyfcgi.status_buf, PyBytes_AsString(bytes), LIBPYFCGI_STATUS_SZ);
104
+	libpyfcgi.status_buf[LIBPYFCGI_STATUS_SZ-1] = '\0';
105
+	if(decref) { Py_DECREF(bytes); }
106
+}
107
+
108
+void libpyfcgi_set_headers_buf()
109
+{
110
+	PyObject *heads_iter, *head_tuple, *head_seq, **head, *bytes[2], *repr;
111
+	Py_ssize_t tsz, n_head, sz, buf_sz, buf_left, buf_off;
112
+	char errstr[256];
113
+	char *head_str[2], *buf_ptr;
114
+	int i;
115
+	void *tmp;
116
+	short content_type; // indicate if headers contains content-type field
117
+
118
+	_set_status_buf();
119
+
120
+	if(!libpyfcgi.headers)
121
+	{
122
+		_default_headers();
123
+		return;
124
+	}
125
+
126
+repr = PyObject_ASCII(libpyfcgi.headers);
127
+pyfcgi_log(LOG_DEBUG, "Sending headers : '%s'", PyUnicode_AsUTF8(repr));
128
+Py_DECREF(repr);
129
+
130
+
131
+	heads_iter = PyObject_GetIter(libpyfcgi.headers);
132
+	if(!heads_iter)
133
+	{
134
+		pyfcgi_log(LOG_ERR, "Unable to get iterator from given headers");
135
+		return;
136
+	}
137
+
138
+	n_head = 0;
139
+	buf_ptr = libpyfcgi.heads_buf; //buffer pointer
140
+	buf_sz = libpyfcgi.heads_buf_sz; //buffer size
141
+	buf_left = buf_sz; //buffer size left
142
+	buf_off = 0; //buffer pointer offset
143
+	while((head_tuple = PyIter_Next(heads_iter)))
144
+	{
145
+		head_seq = PySequence_Fast(head_tuple, "Headers element is not a sequence");
146
+		if((tsz = PySequence_Fast_GET_SIZE(head_seq)) != 2)
147
+		{
148
+			repr = PyObject_ASCII(head_tuple);
149
+			snprintf(errstr, 256,
150
+				"Headers element expected to have len == 2, but got len %ld for element %ld : %s", tsz, n_head, PyUnicode_AsUTF8(repr));
151
+			Py_DECREF(repr);
152
+			PyErr_SetString(PyExc_TypeError, errstr);
153
+			return;
154
+		}
155
+		head = PySequence_Fast_ITEMS(head_seq);
156
+		for(i=0; i<2; i++)
157
+		{
158
+			if(PyUnicode_Check(libpyfcgi.status))
159
+			{
160
+				bytes[i] = PyUnicode_AsUTF8String(head[i]);
161
+				if(!bytes[i])
162
+				{
163
+					//Error encoding header field
164
+					pyfcgi_log(LOG_ERR, "Unable to encode header field using UTF8 codec");
165
+					goto head_decode_err;
166
+				}
167
+			}
168
+			else
169
+			{
170
+				bytes[i] = PyObject_Bytes(head[i]);
171
+				if(!bytes[i])
172
+				{
173
+					pyfcgi_log(LOG_ERR, "Unable to convert header field to bytes");
174
+					goto head_decode_err;
175
+				}
176
+			}
177
+			head_str[i] = PyBytes_AsString(bytes[i]);
178
+			
179
+		}
180
+		if(!strcmp(head_str[0], "Content-Type"))
181
+		{
182
+			content_type = 1;
183
+		}
184
+		// Writing current headers field & value in header buffer
185
+		while(1)
186
+		{
187
+			sz = snprintf(buf_ptr, buf_left,
188
+				"%s: %s\r\n", head_str[0], head_str[1]);
189
+			if(sz < buf_left)
190
+			{
191
+				buf_off += sz+1;
192
+				buf_ptr += sz;
193
+				break;
194
+			}
195
+
196
+			// 2048 alloc bloc
197
+			sz = (((buf_sz+sz)>>11)+1)<<11;
198
+			tmp = realloc(libpyfcgi.heads_buf, sz);
199
+			if(!tmp)
200
+			{
201
+				pyfcgi_log(LOG_ALERT,
202
+					"Unable to realloc headers buffer from iterator : %s",
203
+					strerror(errno));
204
+				exit(PYFCGI_FATAL);
205
+			}
206
+			buf_ptr = tmp + buf_off;
207
+			buf_left = sz - buf_off;
208
+			libpyfcgi.heads_buf = tmp;
209
+			libpyfcgi.heads_buf_sz = sz;
210
+		}
211
+
212
+		n_head++;
213
+		Py_DECREF(head_seq);
214
+		Py_DECREF(head_tuple);
215
+	}
216
+	Py_DECREF(heads_iter);
217
+	while(!content_type)
218
+	{
219
+		//Append text/html default content-type
220
+		/**@todo Autodetect content type ?? **/
221
+		sz = sizeof(LIBPYFCGI_DEFAULT_CTYPE)-1;
222
+		if(sz < buf_left)
223
+		{
224
+			buf_off += sz +1;
225
+			strncpy(buf_ptr, LIBPYFCGI_DEFAULT_CTYPE, buf_left);
226
+			buf_ptr += sz;
227
+			break;
228
+		}
229
+		sz = (((buf_sz+sz)>>11)+1)<<11;
230
+		tmp = realloc(libpyfcgi.heads_buf, sz);
231
+		if(!tmp)
232
+		{
233
+				pyfcgi_log(LOG_ALERT,
234
+					"Unable to realloc headers buffer from default Content-Type",
235
+					strerror(errno));
236
+				exit(PYFCGI_FATAL);
237
+		}
238
+		buf_ptr = tmp+buf_off;
239
+		buf_left = sz - buf_off;
240
+		libpyfcgi.heads_buf = tmp;
241
+		libpyfcgi.heads_buf_sz = sz;
242
+	}
243
+	
244
+	return;
245
+head_decode_err:
246
+	Py_DECREF(head_seq);
247
+	Py_DECREF(head_tuple);
248
+	Py_DECREF(heads_iter);
249
+}
250
+
251
+void libpyfcgi_send_headers()
252
+{
253
+	if(!libpyfcgi.out)
254
+	{
255
+		// invalid context
256
+		PyErr_SetString(PyExc_RuntimeError,
257
+			"Trying to send headers but not in FCGI context");
258
+		return;
259
+	}
260
+
261
+	if(libpyfcgi.headers_sent)
262
+	{
263
+		PyErr_SetString(PyExc_RuntimeError,
264
+			"Headers allready sent...");
265
+		return;
266
+	}
267
+	
268
+	libpyfcgi_set_headers_buf();
269
+	if(PyErr_Occurred())
270
+	{
271
+		/**@todo Better exception/error handling ? */
272
+		log_expt(LOG_ALERT);
273
+		exit(PYFCGI_FATAL);
274
+	}
275
+pyfcgi_log(LOG_DEBUG, "Headers ready to be sent : '%s'", libpyfcgi.heads_buf);
276
+	FCGX_PutStr(libpyfcgi.heads_buf, strlen(libpyfcgi.heads_buf),
277
+		libpyfcgi.out);
278
+pyfcgi_log(LOG_DEBUG, "Headers sent...");
279
+	FCGX_PutStr("\r\n", 2, libpyfcgi.out);
280
+	libpyfcgi.headers_sent = 1;
281
+}
282
+
22 283
 PyMODINIT_FUNC
23 284
 PyInit_libpyfcgi(void)
24 285
 {
25
-	PyObject *module;
26
-	module = PyModule_Create(&pyfcgimodule);
27
-	if(module == NULL) { return NULL; }
28
-	return module;
286
+	if(libpyfcgi.self != NULL)
287
+	{
288
+		return libpyfcgi.self;
289
+	}
290
+	// init module & globals
291
+	libpyfcgi.status = NULL;
292
+	libpyfcgi.headers = NULL;
293
+	libpyfcgi.self = PyModule_Create(&pyfcgimodule);
294
+	if(libpyfcgi.self == NULL) { return NULL; }
295
+	return libpyfcgi.self;
29 296
 }
30 297
 
31 298
 PyObject* pyfcgi_start_response(PyObject* self, PyObject** argv, Py_ssize_t argc)
32 299
 {
33
-	char err[64];
34
-	if(argc != 2)
300
+	char err[128];
301
+	PyObject *exc_info;
302
+	if(argc != 2 && argc != 3)
35 303
 	{
36
-		snprintf(err, 64,
37
-			"libpyfcgi.start_response() expected 2 arguments but %d given",
304
+		snprintf(err, 128,
305
+			"libpyfcgi.start_response() expected 2 to 3 arguments but %ld given",
38 306
 			argc);
39 307
 		PyErr_SetString(PyExc_ValueError, err);
40 308
 		Py_RETURN_NONE;
41 309
 	}
42 310
 
43
-	libpyfcgi_clean_response();
311
+	if(libpyfcgi.status)
312
+	{
313
+		Py_DECREF(libpyfcgi.status);
314
+	}
315
+	if(libpyfcgi.headers)
316
+	{
317
+		Py_DECREF(libpyfcgi.headers);
318
+	}
319
+
320
+	libpyfcgi.status = argv[0];
321
+	libpyfcgi.headers = argv[1];
322
+	Py_INCREF(argv[0]);
323
+	Py_INCREF(argv[1]);
324
+	exc_info = argc>2?argv[2]:NULL;
325
+	if(exc_info)
326
+	{
327
+		Py_INCREF(argv[2]);
328
+		/* check if headers are sent, if yes re-raise the exception using
329
+		   exc_info */
330
+	}
331
+
332
+PyObject *repr = PyObject_ASCII(libpyfcgi.headers);
333
+pyfcgi_log(LOG_DEBUG, "start_response called, headers : '%s'", PyUnicode_AsUTF8(repr));
334
+Py_DECREF(repr);
335
+
336
+	return PyObject_GetAttrString(self, "write_body");
337
+}
338
+
339
+PyObject* pyfcgi_write_body(PyObject* self, PyObject** argv, Py_ssize_t argc)
340
+{
341
+	char err[128];
342
+
343
+pyfcgi_log(LOG_DEBUG, "Write body called...");
344
+	if(argc != 1)
345
+	{
346
+		snprintf(err, 128,
347
+			"libpyfcgi.write_body() excpeted 1 argument but %ld given",
348
+			argc);
349
+		PyErr_SetString(PyExc_ValueError, err);
350
+		Py_RETURN_NONE;
351
+	}
352
+
353
+	return _pyfcgi_write_body(argv[0]);
354
+}
355
+
356
+PyObject* _pyfcgi_write_body(PyObject *body_data)
357
+{
358
+	char err[128];
359
+	const char *dat;
360
+	PyObject *bytes, *cur, *iter, *repr;
361
+	Py_ssize_t sz;
362
+
363
+
364
+	if(!libpyfcgi.out)
365
+	{
366
+		// invalid context
367
+		PyErr_SetString(PyExc_RuntimeError,
368
+			"Trying to send body data but not in FCGI context");
369
+		Py_RETURN_NONE;
370
+	}
371
+
372
+	cur = NULL;
373
+	iter = NULL;
374
+	if(PyUnicode_Check(body_data) || PyBytes_Check(body_data))
375
+	{
376
+		cur = body_data;
377
+	}
378
+	else if (!(iter = PyObject_GetIter(body_data)))
379
+	{
380
+		log_expt(LOG_ERR);
381
+		repr = PyObject_ASCII(body_data);
382
+		snprintf(err, 128, 
383
+			"libpyfcgi.write_body() expect argument to be a str a bytes or an iterator, but %s given",
384
+			PyUnicode_AsUTF8(repr));
385
+		Py_DECREF(repr);
386
+		PyErr_SetString(PyExc_ValueError, err);
387
+		Py_RETURN_NONE;
388
+	}
389
+	else
390
+	{
391
+		Py_INCREF(iter);
392
+		cur = PyIter_Next(iter);
393
+pyfcgi_log(LOG_DEBUG, "Got iter for writing body...");
394
+	}
395
+
396
+	if(!cur)
397
+	{
398
+		/**@todo trigger pyhon warning : empty body ? **/
399
+		Py_RETURN_NONE;
400
+	}
401
+
402
+	// if headers not sent yet, send them....
403
+	if(!libpyfcgi.headers_sent)
404
+	{
405
+pyfcgi_log(LOG_DEBUG, "Headers not sent... sending them...");
406
+		libpyfcgi_send_headers();
407
+	}
408
+
409
+	while(cur)
410
+	{
411
+		Py_INCREF(cur);
412
+		bytes = NULL;
413
+		if(PyUnicode_Check(cur))
414
+		{
415
+			dat = PyUnicode_AsUTF8AndSize(cur, &sz);
416
+			if(!dat)
417
+			{
418
+				repr = PyObject_ASCII(cur);
419
+				snprintf(err, 128,
420
+					"libpyfcgi.__write_body unable to encode string as UTF-8 : %s",
421
+					PyUnicode_AsUTF8(repr));
422
+				Py_DECREF(repr);
423
+				PyErr_SetString(PyExc_ValueError, err);
424
+				Py_RETURN_NONE;
425
+			}
426
+		}
427
+		else if(PyBytes_Check(cur))
428
+		{
429
+			dat = PyBytes_AsString(cur);
430
+			if(!dat)
431
+			{
432
+				/**@todo if no expt set, set error str */
433
+				Py_RETURN_NONE;
434
+			}
435
+			sz = PyBytes_GET_SIZE(cur);
436
+		}
437
+		else
438
+		{
439
+			bytes = PyObject_Bytes(cur);
440
+			if(!bytes)
441
+			{
442
+				repr = PyObject_ASCII(cur);
443
+				snprintf(err, 128,
444
+					"libpyfcgi.__write_body expected str or bytes but %s given",
445
+					PyUnicode_AsUTF8(repr));
446
+				Py_DECREF(repr);
447
+				PyErr_SetString(PyExc_ValueError, err);
448
+				Py_RETURN_NONE;
449
+			}
450
+			dat = PyBytes_AsString(cur);
451
+			if(!dat)
452
+			{
453
+				/**@todo if no expt set, set error str */
454
+				Py_RETURN_NONE;
455
+			}
456
+			sz = PyBytes_GET_SIZE(cur);
457
+		}
458
+		
459
+pyfcgi_log(LOG_DEBUG, "Sending '%s'", dat);
460
+		FCGX_PutStr(dat, sz, libpyfcgi.out);
44 461
 
45
-	response_status = argv[0];
46
-	response_headers = argv[1];
462
+		if(bytes) { Py_DECREF(bytes); }
463
+		Py_DECREF(cur);
464
+		cur = iter?PyIter_Next(iter):NULL;
465
+	}
466
+	if(iter)
467
+	{
468
+		Py_DECREF(iter);
469
+	}
47 470
 	Py_RETURN_NONE;
48 471
 }

+ 8
- 18
src/pyutils.c View File

@@ -57,7 +57,9 @@ void update_python_path()
57 57
 	}
58 58
 
59 59
 	// Fetch, dup & update the python path
60
+pyfcgi_log(LOG_DEBUG, "DEBUG 0");
60 61
 	ppath = Py_GetPath();
62
+pyfcgi_log(LOG_DEBUG, "DEBUG 1");
61 63
 	if(!ppath)
62 64
 	{
63 65
 		if(PyErr_Occurred())
@@ -386,27 +388,20 @@ PyObject* import_entrypoint()
386 388
 	return entry_fun;
387 389
 }
388 390
 
389
-static PyObject* libpyfcgi_self = NULL;
390 391
 PyObject* get_start_response()
391 392
 {
392 393
 	PyObject *module;
393
-	response_status = NULL;
394
-	response_headers = NULL;
395
-	if(!libpyfcgi_self)
394
+	if(!libpyfcgi.self)
396 395
 	{
397
-		module = PyModule_Create(&pyfcgimodule);
396
+		module = PyInit_libpyfcgi();
398 397
 		if(module == NULL)
399 398
 		{
400 399
 			pyfcgi_log(LOG_ERR, "Unable to create libpyfcgi python module");
401 400
 			return NULL;
402 401
 		}
403
-		libpyfcgi_self = module;
402
+		libpyfcgi.self = module;
404 403
 	}
405
-	else
406
-	{
407
-		module = libpyfcgi_self;
408
-	}
409
-	return PyObject_GetAttrString(libpyfcgi_self, "start_response");
404
+	return PyObject_GetAttrString(libpyfcgi.self, "start_response");
410 405
 }
411 406
 
412 407
 void log_expt(int priority)
@@ -417,7 +412,7 @@ void log_expt(int priority)
417 412
 		return;
418 413
 	}
419 414
 
420
-	PyObject *expt, *expt_str, *expt_bytes, *expt_cls,
415
+	PyObject *expt, *expt_bytes, *expt_cls,
421 416
 		*expt_val, *expt_type, *traceback;
422 417
 	char *msg, *type, *val;
423 418
 	int msg_sz;
@@ -426,12 +421,7 @@ void log_expt(int priority)
426 421
 	PyErr_Fetch(&expt_cls, &expt, &traceback);
427 422
 
428 423
 	// Fetching exception message using __str__()
429
-	expt_str = PyObject_GetAttrString(expt, "__str__");
430
-	if(!expt_str)
431
-	{
432
-		pyfcgi_log(LOG_ERR, "Unable to fetch __str__ from exception");
433
-	}
434
-	expt_val = PyObject_CallObject(expt_str, NULL);
424
+	expt_val = PyObject_ASCII(expt);
435 425
 	expt_bytes = PyUnicode_AsUTF8String(expt_val);
436 426
 
437 427
 	msg = PyBytes_AsString(expt_bytes);

+ 49
- 5
src/pyworker.c View File

@@ -27,6 +27,13 @@ static inline void worker_set_idle(int);
27 27
  * @param int semid */
28 28
 static inline void worker_set_busy(int);
29 29
 
30
+/**@brief Process results from a pep333 worker
31
+ * @param FCGX_Stream* out stream from libFCGI
32
+ * @param PyObject* application function returned value
33
+ */
34
+static inline int work333_send_result(FCGX_Stream*, PyObject* ret);
35
+
36
+
30 37
 static int worker_piper_sigrcv = 0;
31 38
 
32 39
 int work333(int wrk_id, int semid)
@@ -47,11 +54,10 @@ int work333(int wrk_id, int semid)
47 54
 
48 55
 	pyfcgi_log(LOG_INFO, "Worker started with PEP333 App");
49 56
 
50
-	update_python_path(); // add cwd to python path
51
-	Py_Initialize();
57
+	pyinit();
58
+	pyfcgi_log(LOG_DEBUG, "Python started");
52 59
 	update_python_fd(pipe_out, pipe_err);
53 60
 	fetch_pyflush(&(pyflush[0]), &(pyflush[1]));
54
-	pyfcgi_log(LOG_INFO, "Python started");
55 61
 
56 62
 	//importing os
57 63
 	py_osmod = python_osmod();
@@ -79,28 +85,63 @@ int work333(int wrk_id, int semid)
79 85
 	{
80 86
 		gettimeofday(&start, NULL);
81 87
 		worker_set_busy(semid);
88
+pyfcgi_log(LOG_DEBUG, "Working !!");
82 89
 		count++;
83 90
 		environ = update_pyenv(py_osmod, envp);
84 91
 
85 92
 		libpyfcgi_clean_response();
93
+		libpyfcgi.out = out_stream;
86 94
 		args = Py_BuildValue("OO", environ, start_response);
95
+pyfcgi_log(LOG_DEBUG, "Calling entrypoint :D");
87 96
 		entry_ret = PyObject_CallObject(entry_fun, args);
97
+		Py_INCREF(entry_ret);
98
+
88 99
 		if(PyErr_Occurred())
89 100
 		{
90 101
 			log_expt(LOG_ERR);
91 102
 		}
92 103
 		// able to process returned value
104
+		// Simulate python call of libpyfcgi.write_body()
105
+pyfcgi_log(LOG_DEBUG, "Writing body !");
106
+		_pyfcgi_write_body(entry_ret);
107
+		if(PyErr_Occurred())
108
+		{
109
+			log_expt(LOG_ERR);
110
+		}
111
+pyfcgi_log(LOG_DEBUG, "Cleaning...");
93 112
 
94 113
 		// clean stuffs
95 114
 		Py_DECREF(args);
96 115
 		Py_DECREF(environ);
116
+		Py_DECREF(entry_ret);
97 117
 		// flush & logs pystdout & pystderr
98 118
 		PyObject_CallObject(pyflush[0], NULL);
99 119
 		PyObject_CallObject(pyflush[1], NULL);
120
+
121
+		FCGX_FClose(out_stream);
122
+		FCGX_FClose(in_stream);
123
+		FCGX_FClose(err_stream);
124
+		FCGI_Finish();
125
+		worker_set_idle(semid);
126
+		gettimeofday(&stop, NULL);
127
+		stop.tv_sec = stop.tv_sec - start.tv_sec;
128
+		stop.tv_usec = stop.tv_usec - start.tv_usec;
129
+		if(stop.tv_usec < 0)
130
+		{
131
+			stop.tv_usec += 1000000;
132
+			stop.tv_sec -= 1;
133
+		}
134
+		pyfcgi_log(LOG_DEBUG, "Worker[%d] request %d END [OK] in %ld.%06lds",
135
+			wrk_id, count, stop.tv_sec, stop.tv_usec);
100 136
 	}
101 137
 	return 0;
102 138
 }
103 139
 
140
+static inline int work333_send_result(FCGX_Stream *out, PyObject* ret)
141
+{
142
+	return 0;
143
+}
144
+
104 145
 int work(int wrk_id, int semid)
105 146
 {
106 147
 	PyObject *entry_fun, *pystdout_flush, *pystderr_flush, *py_osmod,
@@ -117,6 +158,10 @@ int work(int wrk_id, int semid)
117 158
 	piper_args_t piper_args;
118 159
 	char *piper_stack;
119 160
 
161
+	char ident[128];
162
+	snprintf(ident, 128, "Worker[%d]", wrk_id);
163
+	pyfcgi_logger_set_ident(ident);
164
+
120 165
 	max_reqs = PyFCGI_conf.max_reqs;
121 166
 	piper_args.wrk_id = wrk_id;
122 167
 	piper_args.act = &act;
@@ -144,8 +189,7 @@ int work(int wrk_id, int semid)
144 189
 		exit(err);
145 190
 	}
146 191
 
147
-	pyfcgi_log(	LOG_INFO,
148
-		"Worker %d started", wrk_id);
192
+	pyfcgi_log(LOG_INFO, "Worker %d started", wrk_id);
149 193
 
150 194
 	update_python_path(); // add cwd to python path
151 195
 	Py_Initialize(); // "start" python

+ 23
- 1
src/responder.c View File

@@ -112,6 +112,21 @@ int responder_loop()
112 112
 			}
113 113
 			if(status)
114 114
 			{
115
+				if(WIFSIGNALED(status))
116
+				{
117
+					if(WTERMSIG(status) == 11)
118
+					{
119
+						pyfcgi_log(LOG_ALERT,
120
+							"Worker[%d] segfault !",
121
+							n);
122
+					}
123
+					else
124
+					{
125
+						pyfcgi_log(LOG_ALERT,
126
+							"Worker[%d] terminated by signal %d",
127
+							n, WTERMSIG(status));
128
+					}
129
+				}
115 130
 				if(WEXITSTATUS(status) & PYFCGI_FATAL)
116 131
 				{
117 132
 					pyfcgi_log(LOG_ALERT,
@@ -213,7 +228,14 @@ pid_t spawn(int wrk_id, int semid)
213 228
 	else if(!res)
214 229
 	{
215 230
 		// Child process
216
-		exit(work(wrk_id, semid));
231
+		if(PyFCGI_conf.pep333)
232
+		{
233
+			exit(work333(wrk_id, semid));
234
+		}
235
+		else
236
+		{
237
+			exit(work(wrk_id, semid));
238
+		}
217 239
 	}
218 240
 	// Sleep to avoid spawning like hell thinking all workers are
219 241
 	// busy. Let some time to this one to go up...

Loading…
Cancel
Save