Tests about a simple python3 fastcgi runner using libfcgi and the Python-C API.
python
c
wsgi
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

python_pyfcgi.c 13KB

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