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.

pyutils.c 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. #include "pyutils.h"
  2. void pyinit()
  3. {
  4. char *venv_path;
  5. PyObject *sitemod, *mainfun, *wsgi;
  6. if( (venv_path = getenv("VIRTUAL_ENV")) )
  7. {
  8. setenv("PYTHONHOME", venv_path, 1);
  9. swprintf(PyFCGI_conf.context.venv_path, PATH_MAX, L"%s",
  10. venv_path);
  11. Py_SetPythonHome(PyFCGI_conf.context.venv_path);
  12. pyfcgi_log(LOG_INFO, "Virtualenv found : '%s'",
  13. venv_path);
  14. swprintf(PyFCGI_conf.context.python_path, PATH_MAX,
  15. L"%s/bin/python3", venv_path);
  16. Py_SetProgramName(PyFCGI_conf.context.python_path);
  17. }
  18. Py_Initialize();
  19. sitemod = PyImport_ImportModule("site");
  20. mainfun = PyObject_GetAttrString(sitemod, "main");
  21. PyObject_CallObject(mainfun, NULL);
  22. //Append "." to sys.path
  23. sitemod = PySys_GetObject("path");
  24. PyList_Append(sitemod, PyUnicode_FromString("."));
  25. //Initialize the wsgi PEP333 dict
  26. wsgi = PyFCGI_conf.context.wsgi_dict = PyDict_New();
  27. PyDict_SetItemString(wsgi, "wsgi.version", Py_BuildValue("ii", 1,0));
  28. PyDict_SetItemString(wsgi, "wsgi.multithread", Py_False);
  29. PyDict_SetItemString(wsgi, "wsgi.multiprocess", Py_True);
  30. PyDict_SetItemString(wsgi, "wsgi.run_once", Py_False);
  31. }
  32. void update_python_path()
  33. {
  34. wchar_t *ppath, *wcwd, *wtmp;
  35. char *cwd, *ncwd, *err_fmt;
  36. size_t wcwd_sz, ppath_sz;
  37. int err;
  38. // Add a ':' in front of CWD and convert to wchar_t*
  39. cwd = get_current_dir_name();
  40. if(!cwd)
  41. {
  42. err = errno;
  43. err_fmt = "Unable to fecth CWD : %s";
  44. goto update_python_path_err;
  45. }
  46. ncwd = malloc(sizeof(char) * (strlen(cwd) + 2));
  47. if(!ncwd)
  48. {
  49. err = errno;
  50. err_fmt = "Unable to allocate memory for new CWD : %s";
  51. goto update_python_path_err_cwd;
  52. }
  53. ncwd[0] = ':';
  54. strcpy(ncwd+1, cwd);
  55. free(cwd);
  56. cwd = ncwd;
  57. wcwd_sz = mbstowcs(NULL, cwd, 0);
  58. if(wcwd_sz == (size_t)-1)
  59. {
  60. err = errno;
  61. err_fmt = "Unable to convert CWD to wchar : %s";
  62. goto update_python_path_err_cwd;
  63. }
  64. wcwd_sz++;
  65. wcwd = malloc(sizeof(wchar_t) * wcwd_sz);
  66. if(!wcwd)
  67. {
  68. err = errno;
  69. err_fmt = "Unable to allocate wchar for CWD : %s";
  70. goto update_python_path_err_cwd;
  71. }
  72. if(mbstowcs(wcwd, cwd, wcwd_sz) == -1)
  73. {
  74. err = errno;
  75. err_fmt = "Unable to convert CWD to wchar : %s";
  76. goto update_python_path_err_wcwd;
  77. }
  78. // Fetch, dup & update the python path
  79. ppath = Py_GetPath();
  80. if(!ppath)
  81. {
  82. if(PyErr_Occurred())
  83. {
  84. log_expt(LOG_ALERT);
  85. }
  86. else
  87. {
  88. pyfcgi_log(LOG_ALERT, "Unable to fetch python path, got NULL.");
  89. }
  90. err_fmt = NULL;
  91. err = 0;
  92. goto update_python_path_err_wcwd;
  93. }
  94. ppath = wcsdup(ppath);
  95. dprintf(2, "%ls\n", ppath);
  96. if(!wcwd)
  97. {
  98. err = errno;
  99. err_fmt = "Unable to dup the python PATH : %s";
  100. goto update_python_path_err_wcwd;
  101. }
  102. ppath_sz = wcslen(ppath);
  103. wtmp = realloc(ppath, sizeof(wchar_t) * (ppath_sz + wcwd_sz + 1));
  104. if(!wtmp)
  105. {
  106. err = errno;
  107. err_fmt = "Unable reallocate new python path : %s";
  108. goto update_python_path_err_ppath;
  109. }
  110. ppath = wtmp;
  111. wcsncpy(ppath+ppath_sz, wcwd, wcwd_sz);
  112. dprintf(2, "%ls\n", ppath);
  113. Py_SetPath(ppath);
  114. free(ppath);
  115. free(wcwd);
  116. free(cwd);
  117. return;
  118. update_python_path_err_ppath:
  119. free(ppath);
  120. update_python_path_err_wcwd:
  121. free(wcwd);
  122. update_python_path_err_cwd:
  123. free(cwd);
  124. update_python_path_err:
  125. if(err_fmt)
  126. {
  127. pyfcgi_log( LOG_ALERT, err_fmt, strerror(err));
  128. }
  129. exit(err);
  130. }
  131. static PyObject* _fetch_pyflush(const char *fdname)
  132. {
  133. PyObject *pyfd, *pyflush;
  134. pyfd = PySys_GetObject(fdname);
  135. if(!pyfd)
  136. {
  137. pyfcgi_log(LOG_ALERT, "Unable to fetch sys.%s", fdname);
  138. log_expt(LOG_ALERT);
  139. Py_Exit(EXIT_PYERR);
  140. }
  141. pyflush = PyObject_GetAttrString(pyfd, "flush");
  142. Py_DECREF(pyfd);
  143. if(!pyflush)
  144. {
  145. pyfcgi_log(LOG_ALERT, "Unable to fetch sys.%s.flush", fdname);
  146. log_expt(LOG_ALERT);
  147. Py_Exit(EXIT_PYERR);
  148. }
  149. if(!PyCallable_Check(pyflush))
  150. {
  151. pyfcgi_log(LOG_ALERT, "sys.%s.flush is not callable !",
  152. fdname);
  153. Py_Exit(EXIT_PYERR);
  154. }
  155. return pyflush;
  156. }
  157. void fetch_pyflush(PyObject** pystdout_flush, PyObject** pystderr_flush)
  158. {
  159. *pystdout_flush = _fetch_pyflush("stdout");
  160. *pystderr_flush = _fetch_pyflush("stderr");
  161. }
  162. void update_python_fd(int pipe_out[2], int pipe_err[2])
  163. {
  164. int pri, err;
  165. char *err_fmt;
  166. PyObject *os_mod, *pyfdopen, *args, *new_fd;
  167. pri = LOG_ALERT;
  168. if(pipe2(pipe_out, O_DIRECT) == -1)
  169. {
  170. err = errno;
  171. err_fmt = "Unable to create pipe for python stdout : %s";
  172. goto update_python_fd_err;
  173. }
  174. if(pipe2(pipe_err, O_DIRECT) == -1)
  175. {
  176. err = errno;
  177. err_fmt = "Unable to create pipe for python stderr : %s";
  178. goto update_python_fd_err_pipeout;
  179. }
  180. os_mod = PyImport_ImportModule("os");
  181. if(!os_mod)
  182. {
  183. if(PyErr_Occurred())
  184. {
  185. log_expt(LOG_ALERT);
  186. }
  187. else
  188. {
  189. pyfcgi_log( LOG_ALERT,
  190. "Unable to import python os module, got NULL.");
  191. }
  192. err_fmt = NULL;
  193. goto update_python_fd_err_pipes;
  194. }
  195. pyfdopen = PyObject_GetAttrString(os_mod, "fdopen");
  196. Py_DECREF(os_mod);
  197. if(!pyfdopen)
  198. {
  199. if(PyErr_Occurred())
  200. {
  201. log_expt(LOG_ALERT);
  202. }
  203. else
  204. {
  205. pyfcgi_log( LOG_ALERT,
  206. "Unable to fetch os.fdopen() , got NULL.");
  207. }
  208. err_fmt = NULL;
  209. goto update_python_fd_err_pipes;
  210. }
  211. args = Py_BuildValue("is", pipe_out[1], "w");
  212. if(!args)
  213. {
  214. pyfcgi_log( LOG_ERR, "Error building values with '%d', '%s' for stdout",
  215. pipe_out[1], "w");
  216. log_expt(LOG_ALERT);
  217. err_fmt = NULL;
  218. goto update_python_fd_err_fdopen;
  219. }
  220. new_fd = PyObject_CallObject(pyfdopen, args);
  221. if(!new_fd || PyErr_Occurred())
  222. {
  223. pyfcgi_log( LOG_ERR, "Error calling fdopen(%d, '%s')",
  224. pipe_out[1], "w");
  225. log_expt(LOG_ALERT);
  226. err_fmt = NULL;
  227. goto update_python_fd_err_args;
  228. }
  229. Py_DECREF(args);
  230. if(PySys_SetObject("stdout", new_fd))
  231. {
  232. pyfcgi_log(LOG_ERR, "Unable to set sys.stdout");
  233. log_expt(LOG_ALERT);
  234. goto update_python_fd_err_newfd;
  235. }
  236. Py_DECREF(new_fd);
  237. args = Py_BuildValue("is", pipe_err[1], "w");
  238. if(!args)
  239. {
  240. pyfcgi_log( LOG_ERR, "Error building values with '%d', '%s' for stderr",
  241. pipe_out[1], "w");
  242. log_expt(LOG_ALERT);
  243. err_fmt = NULL;
  244. goto update_python_fd_err_fdopen;
  245. }
  246. new_fd = PyObject_CallObject(pyfdopen, args);
  247. if(!new_fd || PyErr_Occurred())
  248. {
  249. pyfcgi_log( LOG_ERR, "Error calling fdopen(%d, '%s')",
  250. pipe_out[1], "w");
  251. log_expt(LOG_ALERT);
  252. err_fmt = NULL;
  253. goto update_python_fd_err_args;
  254. }
  255. Py_DECREF(args);
  256. PyDict_SetItemString(PyFCGI_conf.context.wsgi_dict, "wsgi.errors", new_fd);
  257. if(PySys_SetObject("stderr", new_fd))
  258. {
  259. pyfcgi_log(LOG_ERR, "Unable to set sys.stderr");
  260. log_expt(LOG_ALERT);
  261. goto update_python_fd_err_newfd;
  262. }
  263. Py_DECREF(new_fd);
  264. return;
  265. update_python_fd_err_newfd:
  266. Py_DECREF(new_fd);
  267. update_python_fd_err_args:
  268. Py_DECREF(args);
  269. update_python_fd_err_fdopen:
  270. Py_DECREF(fdopen);
  271. update_python_fd_err_pipes:
  272. close(pipe_err[0]);
  273. close(pipe_err[1]);
  274. update_python_fd_err_pipeout:
  275. close(pipe_out[0]);
  276. close(pipe_out[1]);
  277. update_python_fd_err:
  278. if(err_fmt)
  279. {
  280. pyfcgi_log(pri, err_fmt, strerror(err));
  281. }
  282. exit(1);
  283. }
  284. PyObject* update_pyenv(PyObject *py_osmod, char **environ)
  285. {
  286. PyObject *pyenv, *pykey, *pyval;
  287. char *key, *value, **cur;
  288. cur = environ;
  289. pyenv = PyObject_GetAttrString(py_osmod, "environ");
  290. if(!pyenv)
  291. {
  292. pyfcgi_log(LOG_WARNING, "Unable to get os.environ");
  293. log_expt(LOG_ALERT);
  294. }
  295. else
  296. {
  297. Py_DECREF(pyenv);
  298. }
  299. pyenv = PyDict_New();
  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. /**@todo set given headers */
  346. PyDict_SetItemString(PyFCGI_conf.context.wsgi_dict, "wsgi.url_scheme",
  347. Py_BuildValue("U", "http"));
  348. PyDict_Update(pyenv, PyFCGI_conf.context.wsgi_dict);
  349. PyObject_SetAttrString(py_osmod, "environ", pyenv);
  350. /*//Debug print env on stderr
  351. PyObject* repr = PyObject_ASCII(pyenv);
  352. Py_INCREF(repr);
  353. dprintf(2, "%s\n", PyUnicode_AsUTF8(repr));
  354. Py_DECREF(repr);
  355. */
  356. return pyenv;
  357. }
  358. PyObject* import_entrypoint()
  359. {
  360. PyObject *entry_fname, *entry_module, *entry_fun;
  361. entry_fname = PyUnicode_DecodeFSDefault(PyFCGI_conf.py_entrymod);
  362. entry_module = PyImport_Import(entry_fname);
  363. Py_DECREF(entry_fname);
  364. if(!entry_module)
  365. {
  366. //TODO syslog python error / traceback
  367. pyfcgi_log(LOG_CRIT, "Unable to import python file '%s'",
  368. PyFCGI_conf.py_entrymod);
  369. //pyfcgi_log( LOG_INFO, "%s", getcwd(NULL, 256));
  370. log_expt(LOG_ERR);
  371. return NULL;
  372. }
  373. // getting entrypoint function
  374. entry_fun = PyObject_GetAttrString(entry_module, PyFCGI_conf.py_entryfun);
  375. Py_DECREF(entry_module);
  376. if(!entry_fun)
  377. {
  378. //TODO syslog python error / traceback
  379. pyfcgi_log(LOG_CRIT,
  380. "Unable to import object '%s' from '%s'",
  381. PyFCGI_conf.py_entryfun, PyFCGI_conf.py_entrymod);
  382. log_expt(LOG_ERR);
  383. return NULL;
  384. }
  385. if(!PyCallable_Check(entry_fun))
  386. {
  387. pyfcgi_log( LOG_CRIT,
  388. "When imported from '%s', '%s' was not a callable",
  389. PyFCGI_conf.py_entrymod, PyFCGI_conf.py_entryfun);
  390. return NULL;
  391. }
  392. return entry_fun;
  393. }
  394. PyObject* pyinit_libpyfcgi()
  395. {
  396. PyObject *module;
  397. if(libpyfcgi.self) { return libpyfcgi.self; }
  398. module = PyInit_libpyfcgi();
  399. if(module == NULL)
  400. {
  401. pyfcgi_log(LOG_ERR, "Unable to create libpyfcgi python module");
  402. return NULL;
  403. }
  404. libpyfcgi.self = module;
  405. if(PySys_SetObject("stdout", (PyObject*)libpyfcgi.stdio[0]))
  406. {
  407. pyfcgi_log(LOG_ERR, "Unable to set sys.stdout");
  408. log_expt(LOG_ALERT);
  409. return NULL;
  410. }
  411. if(PySys_SetObject("stderr", (PyObject*)libpyfcgi.stdio[1]))
  412. {
  413. pyfcgi_log(LOG_ERR, "Unable to set sys.stderr");
  414. log_expt(LOG_ALERT);
  415. return NULL;
  416. }
  417. return module;
  418. }
  419. PyObject* get_start_response()
  420. {
  421. pyinit_libpyfcgi();
  422. return PyObject_GetAttrString(libpyfcgi.self, "start_response");
  423. }
  424. void log_expt(int priority)
  425. {
  426. if(!PyErr_Occurred())
  427. {
  428. pyfcgi_log(priority, "No exception");
  429. return;
  430. }
  431. PyObject *expt, *expt_bytes, *expt_cls,
  432. *expt_val, *expt_type, *traceback,
  433. *tbmod, *tbfmt, *tbfmt_args, *tbstr, **lines;
  434. Py_ssize_t i, nlines;
  435. char *msg, *type, *val;
  436. int msg_sz;
  437. char msg_fmt[] = "%s: %s";
  438. PyErr_Fetch(&expt_cls, &expt, &traceback);
  439. // Fetching exception message using __str__()
  440. expt_val = PyObject_ASCII(expt);
  441. expt_bytes = PyUnicode_AsUTF8String(expt_val);
  442. msg = PyBytes_AsString(expt_bytes);
  443. val = strndup(msg, 1024); // TODO check out of mem if !val
  444. // Fetching exception class name
  445. expt_type = PyObject_GetAttrString(expt_cls, "__name__");
  446. expt_bytes = PyUnicode_AsUTF8String(expt_type);
  447. msg = PyBytes_AsString(expt_bytes);
  448. type = strndup(msg, 1024); // TODO check out of mem if !type
  449. msg_sz = snprintf(NULL, 0, msg_fmt, type, val);
  450. msg_sz++;
  451. msg = malloc(sizeof(char) * msg_sz);
  452. snprintf(msg, msg_sz, msg_fmt, type, val);
  453. pyfcgi_log(priority, msg);
  454. //Fetching & logging traceback
  455. tbmod = PyImport_ImportModule("traceback");
  456. tbfmt = PyObject_GetAttrString(tbmod, "format_tb");
  457. tbfmt_args = Py_BuildValue("(O)", traceback);
  458. tbstr = PyObject_CallObject(tbfmt, tbfmt_args);
  459. if(!tbstr)
  460. {
  461. pyfcgi_log(LOG_ALERT,"FAILS TO FOMAT");
  462. PyErr_Fetch(&expt_cls, &expt, &traceback);
  463. pyfcgi_log(LOG_ALERT, "%s", PyUnicode_AsUTF8(PyObject_ASCII(expt)));
  464. return;
  465. }
  466. nlines = PySequence_Fast_GET_SIZE(tbstr);
  467. lines = PySequence_Fast_ITEMS(tbstr);
  468. for(i=0; i<nlines; i++)
  469. {
  470. pyfcgi_log(priority, PyUnicode_AsUTF8(lines[i]));
  471. }
  472. Py_DECREF(tbmod);
  473. Py_DECREF(tbfmt);
  474. Py_DECREF(tbfmt_args);
  475. Py_DECREF(tbstr);
  476. }
  477. void pyfcgi_python_version(char version[16])
  478. {
  479. snprintf(version, 16, "%d.%d.%d", PY_MAJOR_VERSION, PY_MINOR_VERSION,
  480. PY_MICRO_VERSION);
  481. }
  482. PyObject* python_osmod()
  483. {
  484. PyObject *ret;
  485. ret = PyImport_ImportModule("os");
  486. if(!ret)
  487. {
  488. pyfcgi_log(LOG_ALERT, "Unable to import os module");
  489. log_expt(LOG_ALERT);
  490. Py_Exit(EXIT_PYERR);
  491. }
  492. return ret;
  493. }