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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  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* get_start_response()
  395. {
  396. PyObject *module;
  397. if(!libpyfcgi.self)
  398. {
  399. module = PyInit_libpyfcgi();
  400. if(module == NULL)
  401. {
  402. pyfcgi_log(LOG_ERR, "Unable to create libpyfcgi python module");
  403. return NULL;
  404. }
  405. libpyfcgi.self = module;
  406. }
  407. return PyObject_GetAttrString(libpyfcgi.self, "start_response");
  408. }
  409. void log_expt(int priority)
  410. {
  411. if(!PyErr_Occurred())
  412. {
  413. pyfcgi_log(priority, "No exception");
  414. return;
  415. }
  416. PyObject *expt, *expt_bytes, *expt_cls,
  417. *expt_val, *expt_type, *traceback,
  418. *tbmod, *tbfmt, *tbfmt_args, *tbstr, **lines;
  419. Py_ssize_t i, nlines;
  420. char *msg, *type, *val;
  421. int msg_sz;
  422. char msg_fmt[] = "%s: %s";
  423. PyErr_Fetch(&expt_cls, &expt, &traceback);
  424. // Fetching exception message using __str__()
  425. expt_val = PyObject_ASCII(expt);
  426. expt_bytes = PyUnicode_AsUTF8String(expt_val);
  427. msg = PyBytes_AsString(expt_bytes);
  428. val = strndup(msg, 1024); // TODO check out of mem if !val
  429. // Fetching exception class name
  430. expt_type = PyObject_GetAttrString(expt_cls, "__name__");
  431. expt_bytes = PyUnicode_AsUTF8String(expt_type);
  432. msg = PyBytes_AsString(expt_bytes);
  433. type = strndup(msg, 1024); // TODO check out of mem if !type
  434. msg_sz = snprintf(NULL, 0, msg_fmt, type, val);
  435. msg_sz++;
  436. msg = malloc(sizeof(char) * msg_sz);
  437. snprintf(msg, msg_sz, msg_fmt, type, val);
  438. pyfcgi_log(priority, msg);
  439. //Fetching & logging traceback
  440. tbmod = PyImport_ImportModule("traceback");
  441. tbfmt = PyObject_GetAttrString(tbmod, "format_tb");
  442. tbfmt_args = Py_BuildValue("(O)", traceback);
  443. tbstr = PyObject_CallObject(tbfmt, tbfmt_args);
  444. if(!tbstr)
  445. {
  446. pyfcgi_log(LOG_ALERT,"FAILS TO FOMAT");
  447. PyErr_Fetch(&expt_cls, &expt, &traceback);
  448. pyfcgi_log(LOG_ALERT, "%s", PyUnicode_AsUTF8(PyObject_ASCII(expt)));
  449. return;
  450. }
  451. nlines = PySequence_Fast_GET_SIZE(tbstr);
  452. lines = PySequence_Fast_ITEMS(tbstr);
  453. for(i=0; i<nlines; i++)
  454. {
  455. pyfcgi_log(priority, PyUnicode_AsUTF8(lines[i]));
  456. }
  457. Py_DECREF(tbmod);
  458. Py_DECREF(tbfmt);
  459. Py_DECREF(tbfmt_args);
  460. Py_DECREF(tbstr);
  461. }
  462. void pyfcgi_python_version(char version[16])
  463. {
  464. snprintf(version, 16, "%d.%d.%d", PY_MAJOR_VERSION, PY_MINOR_VERSION,
  465. PY_MICRO_VERSION);
  466. }
  467. PyObject* python_osmod()
  468. {
  469. PyObject *ret;
  470. ret = PyImport_ImportModule("os");
  471. if(!ret)
  472. {
  473. pyfcgi_log(LOG_ALERT, "Unable to import os module");
  474. log_expt(LOG_ALERT);
  475. Py_Exit(EXIT_PYERR);
  476. }
  477. return ret;
  478. }