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 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  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, 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 module & globals
  288. libpyfcgi.status = NULL;
  289. libpyfcgi.headers = NULL;
  290. libpyfcgi.rep_sz = 0;
  291. libpyfcgi.self = PyModule_Create(&pyfcgimodule);
  292. if(libpyfcgi.self == NULL) { return NULL; }
  293. // Add type to module (optionnal)
  294. PyModule_AddObject(libpyfcgi.self, "IoIn", (PyObject*)&IoInType);
  295. // Create a new instance of IoIn
  296. libpyfcgi.ioin = (IoIn*)PyObject_CallObject((PyObject*)&IoInType, NULL);
  297. Py_INCREF(libpyfcgi.ioin);
  298. if(!libpyfcgi.ioin)
  299. {
  300. return NULL;
  301. }
  302. libpyfcgi.ioin->in_stream = &libpyfcgi.in; // point on stream pointer
  303. libpyfcgi.ioin->bin = 1; // binary stream (bytes for python)
  304. // Add it to wsgi dict
  305. if(PyDict_SetItemString(PyFCGI_conf.context.wsgi_dict, "wsgi.input",
  306. (PyObject*)libpyfcgi.ioin))
  307. {
  308. return NULL;
  309. }
  310. return libpyfcgi.self;
  311. }
  312. PyObject* pyfcgi_start_response(PyObject* self, PyObject** argv, Py_ssize_t argc)
  313. {
  314. char err[128];
  315. PyObject *exc_info;
  316. if(argc != 2 && argc != 3)
  317. {
  318. snprintf(err, 128,
  319. "libpyfcgi.start_response() expected 2 to 3 arguments but %ld given",
  320. argc);
  321. PyErr_SetString(PyExc_ValueError, err);
  322. Py_RETURN_NONE;
  323. }
  324. if(libpyfcgi.status)
  325. {
  326. Py_DECREF(libpyfcgi.status);
  327. }
  328. if(libpyfcgi.headers)
  329. {
  330. Py_DECREF(libpyfcgi.headers);
  331. }
  332. libpyfcgi.status = argv[0];
  333. libpyfcgi.headers = argv[1];
  334. Py_INCREF(argv[0]);
  335. Py_INCREF(argv[1]);
  336. exc_info = argc>2?argv[2]:NULL;
  337. if(exc_info)
  338. {
  339. Py_INCREF(argv[2]);
  340. /* check if headers are sent, if yes re-raise the exception using
  341. exc_info */
  342. }
  343. return PyObject_GetAttrString(self, "write_body");
  344. }
  345. PyObject* pyfcgi_write_body(PyObject* self, PyObject** argv, Py_ssize_t argc)
  346. {
  347. char err[128];
  348. if(argc != 1)
  349. {
  350. snprintf(err, 128,
  351. "libpyfcgi.write_body() excpeted 1 argument but %ld given",
  352. argc);
  353. PyErr_SetString(PyExc_ValueError, err);
  354. Py_RETURN_NONE;
  355. }
  356. return _pyfcgi_write_body(argv[0]);
  357. }
  358. PyObject* _pyfcgi_write_body(PyObject *body_data)
  359. {
  360. char err[128];
  361. const char *dat;
  362. PyObject *bytes, *cur, *iter, *repr;
  363. Py_ssize_t sz;
  364. int ret;
  365. if(!libpyfcgi.out)
  366. {
  367. // invalid context
  368. PyErr_SetString(PyExc_RuntimeError,
  369. "Trying to send body data but not in FCGI context");
  370. Py_RETURN_NONE;
  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. }
  394. if(!cur)
  395. {
  396. /**@todo trigger pyhon warning : empty body ? **/
  397. Py_RETURN_NONE;
  398. }
  399. // if headers not sent yet, send them....
  400. if(!libpyfcgi.headers_sent)
  401. {
  402. //pyfcgi_log(LOG_DEBUG, "Headers not sent... sending them...");
  403. libpyfcgi_send_headers();
  404. }
  405. while(cur)
  406. {
  407. Py_INCREF(cur);
  408. bytes = NULL;
  409. if(PyUnicode_Check(cur))
  410. {
  411. dat = PyUnicode_AsUTF8AndSize(cur, &sz);
  412. if(!dat)
  413. {
  414. if(PyErr_Occurred()) { goto err_clean; }
  415. repr = PyObject_ASCII(cur);
  416. snprintf(err, 128,
  417. "libpyfcgi.__write_body unable to encode string as UTF-8 : %s",
  418. PyUnicode_AsUTF8(repr));
  419. Py_DECREF(repr);
  420. PyErr_SetString(PyExc_ValueError, err);
  421. goto err_clean;
  422. }
  423. }
  424. else if(PyBytes_Check(cur))
  425. {
  426. dat = PyBytes_AsString(cur);
  427. if(!dat)
  428. {
  429. if(PyErr_Occurred()) { goto err_clean; }
  430. snprintf(err, 128,
  431. "Unable to get bytes buffer when sending body data");
  432. PyErr_SetString(PyExc_ValueError, err);
  433. goto err_clean;
  434. }
  435. sz = PyBytes_GET_SIZE(cur);
  436. }
  437. else
  438. {
  439. bytes = PyObject_Bytes(cur);
  440. if(!bytes)
  441. {
  442. if(PyErr_Occurred()) { goto err_clean; }
  443. repr = PyObject_ASCII(cur);
  444. snprintf(err, 128,
  445. "libpyfcgi.__write_body expected str or bytes but %s given",
  446. PyUnicode_AsUTF8(repr));
  447. Py_DECREF(repr);
  448. PyErr_SetString(PyExc_ValueError, err);
  449. goto err_clean;
  450. }
  451. dat = PyBytes_AsString(cur);
  452. if(!dat)
  453. {
  454. if(PyErr_Occurred()) { goto err_clean; }
  455. snprintf(err, 128,
  456. "Unable to get bytes buffer when sending body data");
  457. PyErr_SetString(PyExc_ValueError, err);
  458. goto err_clean;
  459. }
  460. sz = PyBytes_GET_SIZE(cur);
  461. }
  462. ret = FCGX_PutStr(dat, sz, libpyfcgi.out);
  463. if(ret < 0)
  464. {
  465. pyfcgi_log(LOG_ALERT, "Unable to send reply");
  466. goto err_clean;
  467. }
  468. libpyfcgi.rep_sz += ret;
  469. if(bytes) { Py_DECREF(bytes); }
  470. Py_DECREF(cur);
  471. cur = iter?PyIter_Next(iter):NULL;
  472. }
  473. if(iter)
  474. {
  475. Py_DECREF(iter);
  476. }
  477. Py_RETURN_NONE;
  478. err_clean:
  479. if(bytes) { Py_DECREF(bytes); }
  480. Py_DECREF(cur);
  481. if(iter) { Py_DECREF(iter); }
  482. Py_RETURN_NONE;
  483. }
  484. void libpyfcgi_timeout()
  485. {
  486. if(!libpyfcgi.headers_sent)
  487. {
  488. FCGX_PutStr(LIBPYFCGI_TIMEOUT_HEADERS,
  489. sizeof(LIBPYFCGI_TIMEOUT_HEADERS),
  490. libpyfcgi.out);
  491. }
  492. }