├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile ├── authors.rst ├── conf.py ├── contributing.rst ├── history.rst ├── index.rst ├── installation.rst ├── make.bat ├── readme.rst └── usage.rst ├── nginx ├── __init__.py ├── asyncio │ ├── __init__.py │ ├── loop.pyx │ └── module.py ├── config ├── cycle.pyx ├── hooks.py ├── http │ ├── __init__.py │ ├── http.pyx │ └── module.py ├── log.pyx ├── module.py ├── nginx.pyx ├── nginx_config.pxd ├── nginx_core.pxd ├── nginx_event.pxd ├── ngx_http.pxd ├── ngx_python_module.c ├── ngx_python_module.h └── ngx_python_module_modules.c ├── pyproject.toml ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── tests ├── __init__.py └── test_nginxpy.py └── tox.ini /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * NGINXpy version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | nginx/nginx.c 9 | nginx/nginx.h 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # dotenv 86 | .env 87 | 88 | # virtualenv 89 | .venv 90 | venv/ 91 | ENV/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # PyCharm 107 | .idea/ 108 | 109 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 3.7 4 | install: pip install -U tox-travis 5 | script: tox 6 | dist: xenial 7 | sudo: required 8 | deploy: 9 | provider: pypi 10 | distributions: sdist 11 | user: fantix 12 | password: 13 | secure: PBnaPG9IrXOh1wxPNSJYSIz7eI2Ei3v1w2a8Sy8wWkJ/CIjIqnj6ovB+mlH2nz89Ni4pHmxQMb1rZHNEdoM4lfFcvq5/MiYAcsub+t+sa9nGD1X1sHtOqN8VMk+IumLEVTmQzjbiUcGyfSQbgTloMH4OXpyi8c4aIEMqV5gh711ytM7r1ONy118tXkqfvby6THuFarUuSC40VSJA2xFtPuTdaMKJNZdrbhh0hdFX1c4wxo/twhbiXOTAEoOTisMrJZS/ZtDFCa9+O/PSX6c0hoiQHZJQcTukPfcICpG6IsLaqt7swijCN9pmx3bbj7Ej6IwRcVvslyHzoBMo0j/LdV9x27Xp89a8bm9IUcx1DOBi//46juuC6r79/rnuyU4Mb/mKECXxmQfRKlquqIS9nYbQ1pbSSxccTesrmnfozznHDZ3zecEYeF8HqoSaxaJ4EllundQLIDlBeDC0BSs84mfBzXCoXi0N28Ln2k9vepQWryQq1U8V4GVTacLv9x9BQAKpg811vRfNLqah220Yh/Qw5OXEOUcD+9D8NrP3nTMbPRqwiMGlPUXt9IHrr8qmAgmc/d5v3lJzYEPCgD5vJeZXtz1p2pRnOXp2D9kOyB74VauFfveyNcf3+3LwN0bopMD5gmPYUxg5JuD9FPWsd3I7+74v4vP0kMgHcD3B5Xs= 14 | on: 15 | tags: true 16 | repo: decentfox/nginxpy 17 | python: 3.7 18 | addons: 19 | apt: 20 | packages: 21 | - nginx 22 | - libperl-dev 23 | - libgeoip-dev 24 | - libgd-dev 25 | notifications: 26 | email: 27 | - foss@decentfox.com 28 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * DecentFoX Studio 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every little bit 8 | helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/decentfox/nginxpy/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 30 | wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | NGINXpy could always use more documentation, whether as part of the 42 | official NGINXpy docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/decentfox/nginxpy/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `nginxpy` for local development. 61 | 62 | 1. Fork the `nginxpy` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/nginxpy.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv nginxpy 70 | $ cd nginxpy/ 71 | $ python setup.py develop 72 | 73 | 4. Create a branch for local development:: 74 | 75 | $ git checkout -b name-of-your-bugfix-or-feature 76 | 77 | Now you can make your changes locally. 78 | 79 | 5. When you're done making changes, check that your changes pass flake8 and the 80 | tests, including testing other Python versions with tox:: 81 | 82 | $ flake8 nginxpy tests 83 | $ python setup.py test or py.test 84 | $ tox 85 | 86 | To get flake8 and tox, just pip install them into your virtualenv. 87 | 88 | 6. Commit your changes and push your branch to GitHub:: 89 | 90 | $ git add . 91 | $ git commit -m "Your detailed description of your changes." 92 | $ git push origin name-of-your-bugfix-or-feature 93 | 94 | 7. Submit a pull request through the GitHub website. 95 | 96 | Pull Request Guidelines 97 | ----------------------- 98 | 99 | Before you submit a pull request, check that it meets these guidelines: 100 | 101 | 1. The pull request should include tests. 102 | 2. If the pull request adds functionality, the docs should be updated. Put 103 | your new functionality into a function with a docstring, and add the 104 | feature to the list in README.rst. 105 | 3. The pull request should work for Python 2.7, 3.4, 3.5 and 3.6, and for PyPy. Check 106 | https://travis-ci.org/decentfox/nginxpy/pull_requests 107 | and make sure that the tests pass for all supported Python versions. 108 | 109 | Tips 110 | ---- 111 | 112 | To run a subset of tests:: 113 | 114 | 115 | $ python -m unittest tests.test_nginxpy 116 | 117 | Deploying 118 | --------- 119 | 120 | A reminder for the maintainers on how to deploy. 121 | Make sure all your changes are committed (including an entry in HISTORY.rst). 122 | Then run:: 123 | 124 | $ bumpversion patch # possible: major / minor / patch 125 | $ git push 126 | $ git push --tags 127 | 128 | Travis will then deploy to PyPI if tests pass. 129 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 0.1.0 (2018-11-30) 6 | ------------------ 7 | 8 | * First release on PyPI with asyncio sleep demo. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache Software License 2.0 2 | 3 | Copyright (c) 2018, DecentFoX Studio 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | include nginx/config 7 | include nginx/ngx_python_module.h 8 | 9 | recursive-include nginx *.pyx 10 | recursive-include nginx *.pxd 11 | recursive-include tests * 12 | recursive-exclude * __pycache__ 13 | recursive-exclude * *.py[co] 14 | 15 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | try: 8 | from urllib import pathname2url 9 | except: 10 | from urllib.request import pathname2url 11 | 12 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 13 | endef 14 | export BROWSER_PYSCRIPT 15 | 16 | define PRINT_HELP_PYSCRIPT 17 | import re, sys 18 | 19 | for line in sys.stdin: 20 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 21 | if match: 22 | target, help = match.groups() 23 | print("%-20s %s" % (target, help)) 24 | endef 25 | export PRINT_HELP_PYSCRIPT 26 | 27 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 28 | 29 | help: 30 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 31 | 32 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 33 | 34 | clean-build: ## remove build artifacts 35 | rm -fr build/ 36 | rm -fr dist/ 37 | rm -fr .eggs/ 38 | find . -name '*.egg-info' -exec rm -fr {} + 39 | find . -name '*.egg' -exec rm -f {} + 40 | 41 | clean-pyc: ## remove Python file artifacts 42 | find . -name '*.pyc' -exec rm -f {} + 43 | find . -name '*.pyo' -exec rm -f {} + 44 | find . -name '*~' -exec rm -f {} + 45 | find . -name '__pycache__' -exec rm -fr {} + 46 | 47 | clean-test: ## remove test and coverage artifacts 48 | rm -fr .tox/ 49 | rm -f .coverage 50 | rm -fr htmlcov/ 51 | rm -fr .pytest_cache 52 | 53 | lint: ## check style with flake8 54 | flake8 nginxpy tests 55 | 56 | test: ## run tests quickly with the default Python 57 | python setup.py test 58 | 59 | test-all: ## run tests on every Python version with tox 60 | tox 61 | 62 | coverage: ## check code coverage quickly with the default Python 63 | coverage run --source nginxpy setup.py test 64 | coverage report -m 65 | coverage html 66 | $(BROWSER) htmlcov/index.html 67 | 68 | docs: ## generate Sphinx HTML documentation, including API docs 69 | rm -f docs/nginxpy.rst 70 | rm -f docs/modules.rst 71 | sphinx-apidoc -o docs/ nginxpy 72 | $(MAKE) -C docs clean 73 | $(MAKE) -C docs html 74 | $(BROWSER) docs/_build/html/index.html 75 | 76 | servedocs: docs ## compile the docs watching for changes 77 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 78 | 79 | release: dist ## package and upload a release 80 | twine upload dist/* 81 | 82 | dist: clean ## builds source and wheel package 83 | python setup.py sdist 84 | python setup.py bdist_wheel 85 | ls -l dist 86 | 87 | install: clean ## install the package to the active Python's site-packages 88 | python setup.py install 89 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | NGINXpy 3 | ======= 4 | 5 | 6 | .. image:: https://img.shields.io/pypi/v/nginxpy.svg 7 | :target: https://pypi.python.org/pypi/nginxpy 8 | 9 | .. image:: https://img.shields.io/travis/decentfox/nginxpy.svg 10 | :target: https://travis-ci.org/decentfox/nginxpy 11 | 12 | .. image:: https://readthedocs.org/projects/nginxpy/badge/?version=latest 13 | :target: https://nginxpy.readthedocs.io/en/latest/?badge=latest 14 | :alt: Documentation Status 15 | 16 | 17 | .. image:: https://pyup.io/repos/github/decentfox/nginxpy/shield.svg 18 | :target: https://pyup.io/repos/github/decentfox/nginxpy/ 19 | :alt: Updates 20 | 21 | 22 | 23 | Embed Python in NGINX. 24 | 25 | 26 | * Free software: Apache Software License 2.0 27 | * Documentation: https://nginxpy.readthedocs.io. 28 | 29 | 30 | Features 31 | -------- 32 | 33 | * Standard Python package with Cython extension 34 | * Automatically build into NGINX dynamic module for current NGINX install 35 | * Run embeded Python in NGINX worker processes 36 | * Write NGINX modules in Python or Cython 37 | * Python ``logging`` module redirected to NGINX ``error.log`` 38 | * (ongoing) NGINX event loop wrapped as Python ``asyncio`` interface 39 | * (TBD) Python and Cython interface to most NGINX code 40 | * (TBD) Adapt NGINX web server to WSGI, ASGI and aiohttp interfaces 41 | 42 | 43 | Installation 44 | ------------ 45 | 46 | 1. Install NGINX in whatever way, make sure ``nginx`` command is available. 47 | 2. ``pip install nginxpy``, or get the source and run ``pip install .``. You 48 | may want to add the ``-v`` option, because the process is a bit slow 49 | downloading Cython, NGINX source code and configuring it. The usual ``python 50 | setup.py install`` currently doesn't work separately - you should run 51 | ``python setup.py build`` first. 52 | 3. Run ``python -c 'import nginx'`` to get NGINX configuration hint. 53 | 4. Update NGINX configuration accordingly and reload NGINX. 54 | 5. Visit your NGINX site, see NGINX ``error.log`` for now. 55 | 56 | 57 | Development 58 | ----------- 59 | 60 | 1. Install NGINX in whatever way, make sure ``nginx`` command is available. 61 | 2. Checkout source code. 62 | 3. Run ``python setup.py build && python setup.py develop``. 63 | 4. Run ``python -c 'import nginx'`` to get NGINX configuration hint. 64 | 5. Update NGINX configuration accordingly and reload NGINX. 65 | 6. Visit your NGINX site, see NGINX ``error.log`` for now. 66 | 7. Change code if result is not satisfying, or else go for pull request. 67 | 8. Goto 3 if Cython code was changed, or else goto 5. 68 | 69 | Surprisingly NGINX has a very simple but powerful architecture, learn about it 70 | here: http://nginx.org/en/docs/dev/development_guide.html 71 | 72 | 73 | Credits 74 | ------- 75 | 76 | This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. 77 | 78 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 79 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage 80 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = nginxpy 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # nginxpy documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another 17 | # directory, add these directories to sys.path here. If the directory is 18 | # relative to the documentation root, use os.path.abspath to make it 19 | # absolute, like shown here. 20 | # 21 | import os 22 | import sys 23 | sys.path.insert(0, os.path.abspath('..')) 24 | 25 | import nginx 26 | 27 | # -- General configuration --------------------------------------------- 28 | 29 | # If your documentation needs a minimal Sphinx version, state it here. 30 | # 31 | # needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 35 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The suffix(es) of source filenames. 41 | # You can specify multiple suffix as a list of string: 42 | # 43 | # source_suffix = ['.rst', '.md'] 44 | source_suffix = '.rst' 45 | 46 | # The master toctree document. 47 | master_doc = 'index' 48 | 49 | # General information about the project. 50 | project = u'NGINXpy' 51 | copyright = u"2018, DecentFoX Studio" 52 | author = u"DecentFoX Studio" 53 | 54 | # The version info for the project you're documenting, acts as replacement 55 | # for |version| and |release|, also used in various other places throughout 56 | # the built documents. 57 | # 58 | # The short X.Y version. 59 | version = nginx.__version__ 60 | # The full version, including alpha/beta/rc tags. 61 | release = nginx.__version__ 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | # 66 | # This is also used if you do content translation via gettext catalogs. 67 | # Usually you set "language" from the command line for these cases. 68 | language = None 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | # This patterns also effect to html_static_path and html_extra_path 73 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 74 | 75 | # The name of the Pygments (syntax highlighting) style to use. 76 | pygments_style = 'sphinx' 77 | 78 | # If true, `todo` and `todoList` produce output, else they produce nothing. 79 | todo_include_todos = False 80 | 81 | 82 | # -- Options for HTML output ------------------------------------------- 83 | 84 | # The theme to use for HTML and HTML Help pages. See the documentation for 85 | # a list of builtin themes. 86 | # 87 | html_theme = 'alabaster' 88 | 89 | # Theme options are theme-specific and customize the look and feel of a 90 | # theme further. For a list of options available for each theme, see the 91 | # documentation. 92 | # 93 | # html_theme_options = {} 94 | 95 | # Add any paths that contain custom static files (such as style sheets) here, 96 | # relative to this directory. They are copied after the builtin static files, 97 | # so a file named "default.css" will overwrite the builtin "default.css". 98 | html_static_path = ['_static'] 99 | 100 | 101 | # -- Options for HTMLHelp output --------------------------------------- 102 | 103 | # Output file base name for HTML help builder. 104 | htmlhelp_basename = 'nginxpydoc' 105 | 106 | 107 | # -- Options for LaTeX output ------------------------------------------ 108 | 109 | latex_elements = { 110 | # The paper size ('letterpaper' or 'a4paper'). 111 | # 112 | # 'papersize': 'letterpaper', 113 | 114 | # The font size ('10pt', '11pt' or '12pt'). 115 | # 116 | # 'pointsize': '10pt', 117 | 118 | # Additional stuff for the LaTeX preamble. 119 | # 120 | # 'preamble': '', 121 | 122 | # Latex figure (float) alignment 123 | # 124 | # 'figure_align': 'htbp', 125 | } 126 | 127 | # Grouping the document tree into LaTeX files. List of tuples 128 | # (source start file, target name, title, author, documentclass 129 | # [howto, manual, or own class]). 130 | latex_documents = [ 131 | (master_doc, 'nginxpy.tex', 132 | u'NGINXpy Documentation', 133 | u'DecentFoX Studio', 'manual'), 134 | ] 135 | 136 | 137 | # -- Options for manual page output ------------------------------------ 138 | 139 | # One entry per manual page. List of tuples 140 | # (source start file, name, description, authors, manual section). 141 | man_pages = [ 142 | (master_doc, 'nginxpy', 143 | u'NGINXpy Documentation', 144 | [author], 1) 145 | ] 146 | 147 | 148 | # -- Options for Texinfo output ---------------------------------------- 149 | 150 | # Grouping the document tree into Texinfo files. List of tuples 151 | # (source start file, target name, title, author, 152 | # dir menu entry, description, category) 153 | texinfo_documents = [ 154 | (master_doc, 'nginxpy', 155 | u'NGINXpy Documentation', 156 | author, 157 | 'nginxpy', 158 | 'One line description of project.', 159 | 'Miscellaneous'), 160 | ] 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to NGINXpy's documentation! 2 | ====================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | readme 9 | installation 10 | usage 11 | modules 12 | contributing 13 | authors 14 | history 15 | 16 | Indices and tables 17 | ================== 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | 8 | Stable release 9 | -------------- 10 | 11 | To install NGINXpy, run this command in your terminal: 12 | 13 | .. code-block:: console 14 | 15 | $ pip install nginxpy 16 | 17 | This is the preferred method to install NGINXpy, as it will always install the most recent stable release. 18 | 19 | If you don't have `pip`_ installed, this `Python installation guide`_ can guide 20 | you through the process. 21 | 22 | .. _pip: https://pip.pypa.io 23 | .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ 24 | 25 | 26 | From sources 27 | ------------ 28 | 29 | The sources for NGINXpy can be downloaded from the `Github repo`_. 30 | 31 | You can either clone the public repository: 32 | 33 | .. code-block:: console 34 | 35 | $ git clone git://github.com/decentfox/nginxpy 36 | 37 | Or download the `tarball`_: 38 | 39 | .. code-block:: console 40 | 41 | $ curl -OL https://github.com/decentfox/nginxpy/tarball/master 42 | 43 | Once you have a copy of the source, you can install it with: 44 | 45 | .. code-block:: console 46 | 47 | $ python setup.py install 48 | 49 | 50 | .. _Github repo: https://github.com/decentfox/nginxpy 51 | .. _tarball: https://github.com/decentfox/nginxpy/tarball/master 52 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=nginxpy 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | To use NGINXpy in a project:: 6 | 7 | import nginxpy 8 | -------------------------------------------------------------------------------- /nginx/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Top-level package for NGINXpy.""" 4 | 5 | __author__ = """DecentFoX Studio""" 6 | __email__ = 'foss@decentfox.com' 7 | __version__ = '0.1.0' 8 | 9 | try: 10 | from ._nginx import ( 11 | Cycle, Log, get_current_cycle, ReturnCode, 12 | Request, 13 | NginxEventLoop, NginxEventLoopPolicy, Event, 14 | ) 15 | except ImportError: 16 | import sys 17 | from os.path import abspath, dirname 18 | from importlib.util import find_spec 19 | spec = find_spec('nginx._nginx') 20 | 21 | if spec and ('' not in sys.path or 22 | abspath(dirname(spec.origin)) != abspath(dirname(__file__))): 23 | print('Cannot import nginx, should load in nginx.conf first:') 24 | print() 25 | print(f' load_module {spec.origin};') 26 | print() 27 | elif '' in sys.path: 28 | sys.modules.pop('nginx') 29 | sys.path.remove('') 30 | __import__('nginx') 31 | else: 32 | print('Cannot import nginx, please check installation.') 33 | -------------------------------------------------------------------------------- /nginx/asyncio/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.Logger(__name__) 4 | 5 | from .module import AsyncioModule 6 | -------------------------------------------------------------------------------- /nginx/asyncio/loop.pyx: -------------------------------------------------------------------------------- 1 | from cpython cimport Py_INCREF, Py_DECREF 2 | 3 | from .nginx_core cimport ngx_cycle_t, ngx_calloc, ngx_free 4 | from .nginx_event cimport ngx_event_t, ngx_post_event, ngx_add_timer 5 | from .nginx_event cimport ngx_posted_events 6 | 7 | import contextvars 8 | import logging 9 | import time 10 | import traceback 11 | from asyncio import Task, AbstractEventLoopPolicy, Future 12 | 13 | log = logging.Logger(__name__) 14 | 15 | 16 | cdef class Event: 17 | cdef: 18 | ngx_event_t *event 19 | object _callback 20 | object _args 21 | object _context 22 | 23 | def __cinit__(self, callback, args, context): 24 | self.event = ngx_calloc(sizeof(ngx_event_t), 25 | current_cycle.log.log) 26 | self.event.log = current_cycle.log.log 27 | self.event.data = self 28 | self.event.handler = self._run 29 | self._callback = callback 30 | self._args = args 31 | if context is None: 32 | context = contextvars.copy_context() 33 | self._context = context 34 | 35 | def __dealloc__(self): 36 | ngx_free(self.event) 37 | self.event = NULL 38 | 39 | @staticmethod 40 | cdef void _run(ngx_event_t *ev): 41 | cdef Event self = ev.data 42 | try: 43 | self._context.run(self._callback, *self._args) 44 | except Exception as exc: 45 | traceback.print_exc() 46 | finally: 47 | Py_DECREF(self) 48 | 49 | def cancel(self): 50 | # TODO: XXX 51 | pass 52 | 53 | cdef call_later(self, float delay): 54 | ngx_add_timer(self.event, int(delay * 1000)) 55 | Py_INCREF(self) 56 | return self 57 | 58 | cdef post(self): 59 | ngx_post_event(self.event, &ngx_posted_events) 60 | Py_INCREF(self) 61 | return self 62 | 63 | 64 | cdef class NginxEventLoop: 65 | def create_task(self, coro): 66 | return Task(coro, loop=self) 67 | 68 | def create_future(self): 69 | return Future(loop=self) 70 | 71 | def time(self): 72 | return time.monotonic() 73 | 74 | def call_later(self, delay, callback, *args, context=None): 75 | return Event(callback, args, context).call_later(delay) 76 | 77 | def call_at(self, when, callback, *args, context=None): 78 | return self.call_later(when - self.time(), callback, *args, 79 | context=context) 80 | 81 | def call_soon(self, callback, *args, context=None): 82 | return Event(callback, args, context).post() 83 | 84 | def get_debug(self): 85 | return False 86 | 87 | 88 | class NginxEventLoopPolicy(AbstractEventLoopPolicy): 89 | def __init__(self): 90 | self._loop = NginxEventLoop() 91 | 92 | def get_event_loop(self): 93 | return self._loop 94 | 95 | def set_event_loop(self, loop) -> None: 96 | pass 97 | 98 | def new_event_loop(self): 99 | return self._loop 100 | -------------------------------------------------------------------------------- /nginx/asyncio/module.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from . import log 4 | from .. import NginxEventLoopPolicy 5 | from ..module import BaseModule 6 | from ..hooks import after_init_process 7 | 8 | 9 | class AsyncioModule(BaseModule): 10 | def init_process(self): 11 | log.debug('init_process') 12 | asyncio.set_event_loop_policy(NginxEventLoopPolicy()) 13 | loop = asyncio.get_event_loop() 14 | # noinspection PyProtectedMember 15 | asyncio.events._set_running_loop(loop) 16 | log.debug('created event loop: %r', loop) 17 | loop.call_later(0, loop.create_task, after_init_process()) 18 | 19 | async def after_init_process(self): 20 | log.info('Hello, asyncio!') 21 | await asyncio.sleep(1) 22 | log.info('Hello, timer!') 23 | 24 | def exit_process(self): 25 | log.debug('exit_process') 26 | # noinspection PyProtectedMember 27 | asyncio.events._set_running_loop(None) 28 | asyncio.set_event_loop_policy(None) 29 | -------------------------------------------------------------------------------- /nginx/config: -------------------------------------------------------------------------------- 1 | ngx_module_type=HTTP 2 | ngx_module_name=ngx_python_module 3 | ngx_module_srcs="$ngx_addon_dir/ngx_python_module.c" 4 | 5 | . auto/module 6 | 7 | ngx_addon_name=$ngx_module_name 8 | -------------------------------------------------------------------------------- /nginx/cycle.pyx: -------------------------------------------------------------------------------- 1 | from .nginx_core cimport ngx_cycle_t 2 | 3 | cdef class Cycle: 4 | cdef: 5 | ngx_cycle_t *cycle 6 | public Log log 7 | 8 | def __init__(self, *args): 9 | raise NotImplementedError 10 | 11 | @staticmethod 12 | cdef Cycle from_ptr(ngx_cycle_t *cycle): 13 | cdef Cycle rv = Cycle.__new__(Cycle) 14 | rv.cycle = cycle 15 | rv.log = Log.from_ptr(cycle.log) 16 | return rv 17 | 18 | cdef Cycle current_cycle = None 19 | 20 | def get_current_cycle(): 21 | return current_cycle 22 | -------------------------------------------------------------------------------- /nginx/hooks.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | 4 | from . import ReturnCode 5 | from .module import load_modules, _modules 6 | 7 | log = logging.Logger(__name__) 8 | 9 | 10 | def init_process(): 11 | log.debug('init_process') 12 | 13 | load_modules() 14 | 15 | for mod in _modules: 16 | log.debug('init_process: %r', mod) 17 | mod.init_process() 18 | 19 | 20 | async def after_init_process(): 21 | log.debug('after_init_process') 22 | loop = asyncio.get_event_loop() 23 | tasks = [] 24 | for mod in _modules: 25 | log.debug('after_init_process: %r', mod) 26 | tasks.append(loop.create_task(mod.after_init_process())) 27 | await asyncio.wait(tasks) 28 | 29 | 30 | def exit_process(): 31 | log.debug('exit_process') 32 | for mod in _modules[::-1]: 33 | log.debug('exit_process: %r', mod) 34 | mod.exit_process() 35 | _modules.clear() 36 | 37 | 38 | def post_read(request): 39 | rv = ReturnCode.declined 40 | for mod in _modules: 41 | rv = mod.post_read(request) 42 | if rv != ReturnCode.declined: 43 | return rv 44 | return rv 45 | 46 | 47 | async def post_read_async(request): 48 | rv = ReturnCode.declined 49 | for mod in _modules: 50 | rv = await mod.post_read_async(request) 51 | if rv in (ReturnCode.again,): 52 | raise ValueError('post_read_async cannot return NGX_AGAIN') 53 | if rv != ReturnCode.declined: 54 | return rv 55 | return rv 56 | -------------------------------------------------------------------------------- /nginx/http/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | from .module import HTTPModule 6 | -------------------------------------------------------------------------------- /nginx/http/http.pyx: -------------------------------------------------------------------------------- 1 | from cpython cimport Py_INCREF, Py_DECREF 2 | from .nginx_core cimport ngx_log_error, NGX_LOG_CRIT, NGX_AGAIN, from_nginx_str 3 | from .ngx_http cimport ngx_http_request_t, ngx_http_core_run_phases 4 | from .ngx_http cimport ngx_http_get_module_ctx, ngx_http_set_ctx 5 | 6 | import traceback 7 | 8 | 9 | cdef class Request: 10 | cdef: 11 | ngx_http_request_t *request 12 | public Log log 13 | object future 14 | public str request_line 15 | public str uri 16 | public str args 17 | public str extension 18 | public str unparsed_uri 19 | public str method_name 20 | public str http_protocol 21 | 22 | def __init__(self, *args): 23 | raise NotImplementedError 24 | 25 | def _started(self): 26 | return self.future is not None 27 | 28 | def _start(self, fut): 29 | self.future = fut 30 | Py_INCREF(self) 31 | return NGX_AGAIN 32 | 33 | def _result(self): 34 | if self.future.done(): 35 | Py_DECREF(self) 36 | ngx_http_set_ctx(self.request, NULL, ngx_python_module) 37 | return self.future.result() 38 | return NGX_AGAIN 39 | 40 | def __repr__(self): 41 | return f'Request({self.method_name} {self.uri})' 42 | 43 | def __str__(self): 44 | return f''' request_line: {self.request_line} 45 | uri: {self.uri} 46 | args: {self.args} 47 | extension: {self.extension} 48 | unparsed_uri: {self.unparsed_uri} 49 | method_name: {self.method_name} 50 | http_protocol: {self.http_protocol}''' 51 | 52 | @staticmethod 53 | cdef Request from_ptr(ngx_http_request_t *request): 54 | cdef: 55 | void *rv 56 | Request new_req 57 | rv = ngx_http_get_module_ctx(request, ngx_python_module) 58 | if rv == NULL: 59 | new_req = Request.__new__(Request) 60 | new_req.request = request 61 | new_req.log = Log.from_ptr(request.connection.log) 62 | 63 | new_req.request_line = from_nginx_str(request.request_line) 64 | new_req.uri = from_nginx_str(request.uri) 65 | new_req.args = from_nginx_str(request.args) 66 | new_req.extension = from_nginx_str(request.exten) 67 | new_req.unparsed_uri = from_nginx_str(request.unparsed_uri) 68 | new_req.method_name = from_nginx_str(request.method_name) 69 | new_req.http_protocol = from_nginx_str(request.http_protocol) 70 | 71 | ngx_http_set_ctx(request, new_req, ngx_python_module) 72 | return new_req 73 | else: 74 | return rv 75 | 76 | 77 | cdef public ngx_int_t nginxpy_post_read(ngx_http_request_t *request): 78 | try: 79 | from . import hooks 80 | return hooks.post_read(Request.from_ptr(request)) 81 | except: 82 | ngx_log_error(NGX_LOG_CRIT, request.connection.log, 0, 83 | b'Error occured in post_read:\n' + 84 | traceback.format_exc().encode()) 85 | return 500 86 | 87 | 88 | def run_phases(Request request): 89 | ngx_http_core_run_phases(request.request) 90 | -------------------------------------------------------------------------------- /nginx/http/module.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from . import log 4 | from .._nginx import run_phases 5 | from ..module import BaseModule, ReturnCode 6 | from ..hooks import post_read_async 7 | 8 | 9 | class HTTPModule(BaseModule): 10 | def __init__(self): 11 | self.loop = None 12 | 13 | def init_process(self): 14 | self.loop = asyncio.get_event_loop() 15 | 16 | def post_read(self, request): 17 | if request._started(): 18 | log.debug('post_read end') 19 | return request._result() 20 | else: 21 | log.debug('post_read request:\n%s', request) 22 | return request._start(self.loop.create_task( 23 | self._post_read_async(request))) 24 | 25 | async def _post_read_async(self, request): 26 | try: 27 | return await post_read_async(request) 28 | finally: 29 | self.loop.call_soon(run_phases, request) 30 | 31 | async def post_read_async(self, request): 32 | log.info('Delaying request for 1 second...') 33 | await asyncio.sleep(1) 34 | log.info('Now continue with the request') 35 | return ReturnCode.declined 36 | -------------------------------------------------------------------------------- /nginx/log.pyx: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from .nginx_core cimport ngx_log_error 4 | from .nginx_core cimport ( 5 | NGX_LOG_ALERT, 6 | NGX_LOG_CRIT, 7 | NGX_LOG_ERR, 8 | NGX_LOG_WARN, 9 | NGX_LOG_INFO, 10 | NGX_LOG_DEBUG, 11 | ) 12 | from .nginx_core cimport ngx_log_t, ngx_uint_t 13 | 14 | cdef class Log: 15 | cdef ngx_log_t *log 16 | 17 | def __init__(self, *args): 18 | raise NotImplementedError 19 | 20 | @staticmethod 21 | cdef Log from_ptr(ngx_log_t *log): 22 | cdef Log rv = Log.__new__(Log) 23 | rv.log = log 24 | return rv 25 | 26 | 27 | class NginxLogHandler(logging.Handler): 28 | def __init__(self, log): 29 | super().__init__() 30 | self._log = log 31 | self.lock = None 32 | 33 | def emit(self, record): 34 | cdef ngx_uint_t level = NGX_LOG_ALERT 35 | if record.levelno == logging.DEBUG: 36 | level = NGX_LOG_DEBUG 37 | elif record.levelno == logging.INFO: 38 | level = NGX_LOG_INFO 39 | elif record.levelno == logging.WARN: 40 | level = NGX_LOG_WARN 41 | elif record.levelno == logging.ERROR: 42 | level = NGX_LOG_ERR 43 | elif record.levelno == logging.CRITICAL: 44 | level = NGX_LOG_CRIT 45 | ngx_log_error(level, ( self._log).log, 0, 46 | '[{}] {}'.format( 47 | record.name, 48 | record.getMessage()).encode()) 49 | 50 | def createLock(self): 51 | pass 52 | 53 | 54 | cdef set_last_resort(Log log): 55 | logging.lastResort = NginxLogHandler(log) 56 | logging.basicConfig(level=logging.NOTSET, 57 | handlers=[logging.lastResort]) 58 | 59 | cdef unset_last_resort(): 60 | logging.lastResort = logging._defaultLastResort 61 | logging.root.handlers.clear() 62 | -------------------------------------------------------------------------------- /nginx/module.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pkg_resources 3 | 4 | from . import ReturnCode 5 | 6 | log = logging.Logger(__name__) 7 | _modules = [] 8 | 9 | 10 | class BaseModule: 11 | def init_process(self): 12 | pass 13 | 14 | async def after_init_process(self): 15 | pass 16 | 17 | def exit_process(self): 18 | pass 19 | 20 | def post_read(self, request): 21 | return ReturnCode.declined 22 | 23 | async def post_read_async(self, request): 24 | return ReturnCode.declined 25 | 26 | 27 | def load_modules(): 28 | log.debug('loading modules') 29 | endpoints = [ep for d in pkg_resources.working_set for ep in 30 | d.get_entry_map('nginx.modules').values()] 31 | endpoints.sort(key=lambda ep: ep.name) 32 | for ep in endpoints: 33 | log.info('loading %s', ep.module_name) 34 | mod = ep.load() 35 | log.info('installing %s', mod) 36 | _modules.append(mod()) 37 | -------------------------------------------------------------------------------- /nginx/nginx.pyx: -------------------------------------------------------------------------------- 1 | # cython: language_level=3 2 | 3 | import traceback 4 | from enum import IntEnum 5 | 6 | from .nginx_config cimport ngx_int_t 7 | from .nginx_core cimport ngx_module_t, ngx_cycle_t 8 | from .nginx_core cimport NGX_OK, NGX_ERROR, NGX_DECLINED, NGX_AGAIN 9 | from .nginx_core cimport NGX_LOG_DEBUG, NGX_LOG_CRIT 10 | from .nginx_core cimport ngx_log_error 11 | 12 | 13 | cdef extern from "ngx_python_module.h": 14 | ngx_module_t ngx_python_module 15 | 16 | 17 | class ReturnCode(IntEnum): 18 | ok = NGX_OK 19 | error = NGX_ERROR 20 | declined = NGX_DECLINED 21 | again = NGX_AGAIN 22 | 23 | 24 | cdef public ngx_int_t nginxpy_init_process(ngx_cycle_t *cycle): 25 | ngx_log_error(NGX_LOG_DEBUG, cycle.log, 0, 26 | b'Starting init_process.') 27 | # noinspection PyBroadException 28 | try: 29 | from . import hooks 30 | global current_cycle 31 | current_cycle = Cycle.from_ptr(cycle) 32 | set_last_resort(current_cycle.log) 33 | hooks.init_process() 34 | except: 35 | ngx_log_error(NGX_LOG_CRIT, cycle.log, 0, 36 | b'Error occured in init_process:\n' + 37 | traceback.format_exc().encode()) 38 | return NGX_ERROR 39 | else: 40 | ngx_log_error(NGX_LOG_DEBUG, cycle.log, 0, 41 | b'Finished init_process.') 42 | return NGX_OK 43 | 44 | 45 | cdef public void nginxpy_exit_process(ngx_cycle_t *cycle): 46 | ngx_log_error(NGX_LOG_DEBUG, cycle.log, 0, 47 | b'Starting exit_process.') 48 | # noinspection PyBroadException 49 | try: 50 | from . import hooks 51 | global current_cycle 52 | hooks.exit_process() 53 | unset_last_resort() 54 | current_cycle = None 55 | except: 56 | ngx_log_error(NGX_LOG_CRIT, cycle.log, 0, 57 | b'Error occured in exit_process:\n' + 58 | traceback.format_exc().encode()) 59 | else: 60 | ngx_log_error(NGX_LOG_DEBUG, cycle.log, 0, 61 | b'Finished exit_process.') 62 | 63 | 64 | include "log.pyx" 65 | include "cycle.pyx" 66 | include "http/http.pyx" 67 | include "asyncio/loop.pyx" 68 | -------------------------------------------------------------------------------- /nginx/nginx_config.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "ngx_config.h": 2 | ctypedef int ngx_int_t 3 | ctypedef int ngx_uint_t 4 | -------------------------------------------------------------------------------- /nginx/nginx_core.pxd: -------------------------------------------------------------------------------- 1 | from cpython.bytes cimport PyBytes_FromStringAndSize 2 | 3 | from .nginx_config cimport ngx_int_t, ngx_uint_t 4 | 5 | 6 | cdef extern from "ngx_core.h": 7 | const ngx_int_t NGX_OK 8 | const ngx_int_t NGX_ERROR 9 | const ngx_int_t NGX_DECLINED 10 | const ngx_int_t NGX_AGAIN 11 | 12 | const int NGX_LOG_EMERG 13 | const int NGX_LOG_ALERT 14 | const int NGX_LOG_CRIT 15 | const int NGX_LOG_ERR 16 | const int NGX_LOG_WARN 17 | const int NGX_LOG_NOTICE 18 | const int NGX_LOG_INFO 19 | const int NGX_LOG_DEBUG 20 | 21 | ctypedef int ngx_err_t 22 | ctypedef int ngx_msec_t 23 | 24 | ctypedef struct ngx_str_t: 25 | size_t len 26 | char *data 27 | 28 | ctypedef struct ngx_module_t: 29 | pass 30 | 31 | ctypedef struct ngx_log_t: 32 | pass 33 | 34 | ctypedef struct ngx_cycle_t: 35 | ngx_log_t *log 36 | 37 | ctypedef struct ngx_queue_t: 38 | ngx_queue_t *prev 39 | ngx_queue_t *next 40 | 41 | void *ngx_calloc(size_t size, ngx_log_t *log) 42 | void ngx_free(void *p) 43 | void ngx_log_error(ngx_uint_t level, 44 | ngx_log_t *log, 45 | ngx_err_t err, 46 | const char *fmt) 47 | 48 | 49 | cdef inline str from_nginx_str(ngx_str_t str): 50 | return PyBytes_FromStringAndSize(str.data, 51 | str.len).decode('iso-8859-1') 52 | -------------------------------------------------------------------------------- /nginx/nginx_event.pxd: -------------------------------------------------------------------------------- 1 | from .nginx_core cimport ngx_queue_t, ngx_msec_t, ngx_log_t 2 | 3 | cdef extern from "ngx_event.h": 4 | ctypedef void (*ngx_event_handler_pt)(ngx_event_t *ev) 5 | 6 | ctypedef struct ngx_event_t: 7 | void *data 8 | ngx_event_handler_pt handler 9 | ngx_queue_t queue 10 | ngx_log_t *log 11 | ngx_queue_t ngx_posted_events 12 | 13 | void ngx_post_event(ngx_event_t *ev, ngx_queue_t *q) 14 | void ngx_add_timer(ngx_event_t *ev, ngx_msec_t timer) 15 | 16 | -------------------------------------------------------------------------------- /nginx/ngx_http.pxd: -------------------------------------------------------------------------------- 1 | from .nginx_core cimport ngx_str_t, ngx_module_t, ngx_log_t 2 | 3 | 4 | cdef extern from "ngx_http.h": 5 | ctypedef struct ngx_connection_t: 6 | ngx_log_t *log 7 | 8 | ctypedef struct ngx_http_request_t: 9 | ngx_connection_t *connection 10 | ngx_str_t request_line 11 | ngx_str_t uri 12 | ngx_str_t args 13 | ngx_str_t exten 14 | ngx_str_t unparsed_uri 15 | ngx_str_t method_name 16 | ngx_str_t http_protocol 17 | 18 | void ngx_http_core_run_phases(ngx_http_request_t *request) 19 | void *ngx_http_get_module_ctx(ngx_http_request_t *request, 20 | ngx_module_t module) 21 | void ngx_http_set_ctx(ngx_http_request_t *request, void *ctx, 22 | ngx_module_t module) 23 | -------------------------------------------------------------------------------- /nginx/ngx_python_module.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "nginx.h" 6 | 7 | 8 | static ngx_int_t ngx_python_init_process(ngx_cycle_t *cycle); 9 | static void ngx_python_exit_process(ngx_cycle_t *cycle); 10 | static ngx_int_t ngx_python_postconfiguration(ngx_conf_t *cf); 11 | 12 | static wchar_t *python_exec = NULL; 13 | 14 | 15 | static void *ngx_http_wsgi_create_loc_conf(ngx_conf_t *cf); 16 | static char *ngx_handle_app(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 17 | static ngx_conf_post_t ngx_wsgi_pass_post = { ngx_handle_app }; 18 | 19 | typedef struct { 20 | ngx_str_t wsgi_pass; 21 | } ngx_wsgi_pass_conf_t; 22 | 23 | static ngx_command_t ngx_wsgi_commands[] = { 24 | { ngx_string("wsgi_pass"), 25 | NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1, 26 | ngx_conf_set_str_slot, 27 | NGX_HTTP_LOC_CONF_OFFSET, 28 | offsetof(ngx_wsgi_pass_conf_t, wsgi_pass), 29 | &ngx_wsgi_pass_post, 30 | }, 31 | 32 | ngx_null_command 33 | }; 34 | 35 | 36 | static ngx_http_module_t ngx_python_module_ctx = { 37 | NULL, /* preconfiguration */ 38 | ngx_python_postconfiguration, /* postconfiguration */ 39 | 40 | NULL, /* create main configuration */ 41 | NULL, /* init main configuration */ 42 | 43 | NULL, /* create server configuration */ 44 | NULL, /* merge server configuration */ 45 | 46 | ngx_http_wsgi_create_loc_conf, /* create location configuration */ 47 | NULL /* merge location configuration */ 48 | }; 49 | 50 | 51 | ngx_module_t ngx_python_module = { 52 | NGX_MODULE_V1, 53 | &ngx_python_module_ctx, /* module context */ 54 | ngx_wsgi_commands, /* module directives */ 55 | NGX_HTTP_MODULE, /* module type */ 56 | NULL, /* init master */ 57 | NULL, /* init module */ 58 | ngx_python_init_process, /* init process */ 59 | NULL, /* init thread */ 60 | NULL, /* exit thread */ 61 | ngx_python_exit_process, /* exit process */ 62 | NULL, /* exit master */ 63 | NGX_MODULE_V1_PADDING 64 | }; 65 | 66 | 67 | static ngx_int_t 68 | ngx_python_init_process(ngx_cycle_t *cycle) { 69 | if (python_exec == NULL) { 70 | python_exec = Py_DecodeLocale(PYTHON_EXEC, NULL); 71 | if (python_exec == NULL) { 72 | ngx_log_error(NGX_LOG_CRIT, cycle->log, 0, 73 | "Could not decode Python executable path."); 74 | return NGX_ERROR; 75 | } 76 | } 77 | Py_SetProgramName(python_exec); 78 | if (PyImport_AppendInittab("nginx._nginx", PyInit__nginx) == -1) { 79 | ngx_log_error(NGX_LOG_CRIT, cycle->log, 0, 80 | "Could not initialize nginxpy extension."); 81 | return NGX_ERROR; 82 | } 83 | ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, 84 | "Initializing Python..."); 85 | Py_Initialize(); 86 | if (PyImport_ImportModule("nginx._nginx") == NULL) { 87 | ngx_log_error(NGX_LOG_CRIT, cycle->log, 0, 88 | "Could not import nginxpy extension."); 89 | return NGX_ERROR; 90 | } 91 | return nginxpy_init_process(cycle); 92 | } 93 | 94 | static void 95 | ngx_python_exit_process(ngx_cycle_t *cycle) { 96 | nginxpy_exit_process(cycle); 97 | ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, 98 | "Finalizing Python..."); 99 | if (Py_FinalizeEx() < 0) { 100 | ngx_log_error(NGX_LOG_CRIT, cycle->log, 0, 101 | "Failed to finalize Python!"); 102 | } 103 | } 104 | 105 | static ngx_int_t 106 | ngx_python_postconfiguration(ngx_conf_t *cf) { 107 | ngx_http_handler_pt *h; 108 | ngx_http_core_main_conf_t *cmcf; 109 | 110 | cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 111 | 112 | h = ngx_array_push(&cmcf->phases[NGX_HTTP_POST_READ_PHASE].handlers); 113 | if (h == NULL) { 114 | return NGX_ERROR; 115 | } 116 | *h = nginxpy_post_read; 117 | 118 | return NGX_OK; 119 | } 120 | 121 | 122 | static void * 123 | ngx_http_wsgi_create_loc_conf(ngx_conf_t *cf) 124 | { 125 | ngx_wsgi_pass_conf_t *conf; 126 | 127 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_wsgi_pass_conf_t)); 128 | if (conf == NULL) { 129 | return NULL; 130 | } 131 | 132 | return conf; 133 | } 134 | 135 | 136 | static char * 137 | ngx_handle_app(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 138 | { 139 | ngx_wsgi_pass_conf_t *wac = conf; 140 | if (wac->wsgi_pass.len > 0) { 141 | ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "get application %s", wac->wsgi_pass.data); 142 | } else { 143 | ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "no wsgi_pass found"); 144 | } 145 | 146 | return NGX_CONF_OK; 147 | } 148 | -------------------------------------------------------------------------------- /nginx/ngx_python_module.h: -------------------------------------------------------------------------------- 1 | extern ngx_module_t ngx_python_module; 2 | -------------------------------------------------------------------------------- /nginx/ngx_python_module_modules.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern ngx_module_t ngx_python_module; 5 | 6 | ngx_module_t *ngx_modules[] = { 7 | &ngx_python_module, 8 | NULL 9 | }; 10 | 11 | char *ngx_module_names[] = { 12 | "ngx_python_module", 13 | NULL 14 | }; 15 | 16 | char *ngx_module_order[] = { 17 | NULL 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires=['setuptools', 'wheel', 'cython'] 3 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pip==18.1 2 | bumpversion==0.5.3 3 | wheel==0.32.1 4 | watchdog==0.9.0 5 | flake8==3.5.0 6 | tox==3.5.2 7 | coverage==4.5.1 8 | Sphinx==1.8.1 9 | twine==1.12.1 10 | 11 | 12 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.0 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:nginxpy/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | exclude = docs 19 | 20 | [aliases] 21 | # Define setup.py command aliases here 22 | 23 | [options] 24 | setup_requires = 25 | setuptools 26 | wheel 27 | cython 28 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """The setup script.""" 5 | 6 | import os 7 | import re 8 | import subprocess 9 | import sys 10 | import sysconfig 11 | 12 | import setuptools 13 | from distutils import log 14 | from distutils.command.build import build 15 | from urllib.request import urlretrieve 16 | from setuptools import setup, find_packages, Extension 17 | from setuptools.command.develop import develop 18 | 19 | with open('README.rst') as readme_file: 20 | readme = readme_file.read() 21 | 22 | with open('HISTORY.rst') as history_file: 23 | history = history_file.read() 24 | 25 | requirements = [] 26 | 27 | test_requirements = [] 28 | 29 | 30 | class nginxpy_build(build): 31 | def run(self): 32 | print('checking current NGINX install') 33 | nginx_bin = subprocess.getoutput('which nginx') 34 | nginx_ver = subprocess.getoutput(nginx_bin + ' -v') 35 | nginx_ver = re.findall(r'nginx/([\d\.]+)', nginx_ver)[0] 36 | print(nginx_ver, '@', nginx_bin) 37 | 38 | print('checking NGINX configure parameters') 39 | nginx_params = subprocess.getoutput(nginx_bin + ' -V') 40 | nginx_params = re.findall(r'configure arguments: (.*)', 41 | nginx_params)[0] 42 | 43 | def filter_params(params): 44 | params = iter(params) 45 | try: 46 | while True: 47 | val = next(params) 48 | if val.startswith('--add-dynamic-module'): 49 | if val == '--add-dynamic-module': 50 | next(params) 51 | else: 52 | yield val 53 | except StopIteration: 54 | pass 55 | 56 | nginx_params = list(filter_params(nginx_params.split())) 57 | print(' '.join(nginx_params)) 58 | 59 | print('updating NGINX source') 60 | build_base = os.path.abspath(self.build_base) 61 | if not os.path.exists(build_base): 62 | os.mkdir(build_base) 63 | nginx_src = os.path.join(build_base, 'nginx-' + nginx_ver) 64 | nginx_tarball = os.path.join(build_base, 65 | 'nginx-' + nginx_ver + '.tar.gz') 66 | nginx_url = 'https://nginx.org/download/nginx-{}.tar.gz'.format( 67 | nginx_ver) 68 | if os.path.exists(nginx_src): 69 | print('reusing', nginx_src) 70 | else: 71 | if os.path.exists(nginx_tarball): 72 | print('reusing', nginx_tarball) 73 | else: 74 | print('downloading', nginx_url) 75 | urlretrieve(nginx_url, nginx_tarball) 76 | print('extracting tarball') 77 | subprocess.check_call(['tar', 'xvf', nginx_tarball, 78 | '-C', build_base]) 79 | 80 | print('retrieving NGINX build options') 81 | nginx_configure = os.path.join(nginx_src, 'configure') 82 | nginx_make = os.path.join(nginx_src, 'objs', 'Makefile') 83 | if os.path.exists(nginx_make): 84 | print('reusing', nginx_make) 85 | else: 86 | print('configuring NGINX') 87 | src_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'nginx') 88 | cmd = (' '.join([nginx_configure] + nginx_params + 89 | ['--add-dynamic-module=' + src_dir])) 90 | subprocess.check_call([cmd], 91 | cwd=nginx_src, shell=True) 92 | with open(nginx_make, 'a') as f: 93 | f.write('\n') 94 | f.write('print-% : ; @echo $* = $($*)') 95 | f.write('\n') 96 | nginx_cflags = subprocess.getoutput( 97 | 'make -f ' + nginx_make + ' print-CFLAGS') 98 | nginx_cflags = re.findall(r'CFLAGS = (.*)', nginx_cflags)[0].split() 99 | nginx_cflags = list(filter(lambda x: x != '-Werror', nginx_cflags)) 100 | nginx_all_incs = subprocess.getoutput( 101 | 'make -f ' + nginx_make + ' print-ALL_INCS') 102 | nginx_all_incs = re.findall(r'ALL_INCS = (.*)', nginx_all_incs)[0] 103 | nginx_all_incs = list(map(lambda x: os.path.join(nginx_src, x), 104 | filter(lambda x: x != '-I', 105 | nginx_all_incs.split()))) 106 | if subprocess.getoutput('grep fPIC ' + nginx_make): 107 | nginx_cflags.append('-fPIC') 108 | print('NGINX CFLAGS:', ' '.join(nginx_cflags)) 109 | print('NGINX ALL_INCS:', ' '.join(nginx_all_incs)) 110 | 111 | nginxpy.include_dirs.extend(nginx_all_incs) 112 | nginxpy.extra_compile_args.extend(nginx_cflags) 113 | 114 | print('retrieving Python link options') 115 | pyver = sysconfig.get_config_var('VERSION') 116 | getvar = sysconfig.get_config_var 117 | libs = ['-lpython' + pyver + sys.abiflags] 118 | libs += getvar('LIBS').split() 119 | libs += getvar('SYSLIBS').split() 120 | # add the prefix/lib/pythonX.Y/config dir, but only if there is no 121 | # shared library in prefix/lib/. 122 | if not getvar('Py_ENABLE_SHARED'): 123 | libs.insert(0, '-L' + getvar('LIBPL')) 124 | if not getvar('PYTHONFRAMEWORK'): 125 | libs.extend(getvar('LINKFORSHARED').split()) 126 | libs = list(filter(lambda x: 'stack_size' not in x, libs)) 127 | 128 | print('link options:', ' '.join(libs)) 129 | nginxpy.extra_link_args.extend(libs) 130 | 131 | super().run() 132 | 133 | 134 | class nginxpy_develop(develop): 135 | def install_for_development(self): 136 | self.reinitialize_command('build', inplace=1) 137 | self.run_command('build') 138 | 139 | self.install_site_py() # ensure that target dir is site-safe 140 | if setuptools.bootstrap_install_from: 141 | self.easy_install(setuptools.bootstrap_install_from) 142 | setuptools.bootstrap_install_from = None 143 | 144 | self.install_namespaces() 145 | 146 | # create an .egg-link in the installation dir, pointing to our egg 147 | log.info("Creating %s (link to %s)", self.egg_link, self.egg_base) 148 | if not self.dry_run: 149 | with open(self.egg_link, "w") as f: 150 | f.write(self.egg_path + "\n" + self.setup_path) 151 | # postprocess the installed distro, fixing up .pth, installing scripts, 152 | # and handling requirements 153 | self.process_distribution(None, self.dist, not self.no_deps) 154 | 155 | 156 | nginxpy = Extension( 157 | 'nginx._nginx', 158 | sources=[ 159 | 'nginx/ngx_python_module.c', 160 | 'nginx/ngx_python_module_modules.c', 161 | 'nginx/nginx.pyx', 162 | ], define_macros=[ 163 | ('PYTHON_EXEC', '"{}"'.format(os.path.abspath(sys.executable))), 164 | ], depends=[ 165 | 'nginx/cycle.pyx', 166 | 'nginx/log.pyx', 167 | 'nginx/asyncio/loop.pyx', 168 | 'nginx/http/http.pyx', 169 | ]) 170 | 171 | setup( 172 | author="DecentFoX Studio", 173 | author_email='foss@decentfox.com', 174 | classifiers=[ 175 | 'Development Status :: 2 - Pre-Alpha', 176 | 'Intended Audience :: Developers', 177 | 'License :: OSI Approved :: Apache Software License', 178 | 'Natural Language :: English', 179 | 'Programming Language :: Python :: 3', 180 | 'Programming Language :: Python :: 3.7', 181 | ], 182 | description="Embed Python in NGINX.", 183 | install_requires=requirements, 184 | license="Apache Software License 2.0", 185 | long_description=readme + '\n\n' + history, 186 | include_package_data=True, 187 | keywords='nginxpy', 188 | name='nginxpy', 189 | packages=find_packages(include=['nginx', 'nginx.asyncio', 'nginx.http']), 190 | ext_modules=[nginxpy], 191 | cmdclass=dict(build=nginxpy_build), 192 | entry_points='''\ 193 | [nginx.modules] 194 | 100 = nginx.asyncio:AsyncioModule 195 | 200 = nginx.http:HTTPModule 196 | ''', 197 | test_suite='tests', 198 | tests_require=test_requirements, 199 | url='https://github.com/decentfox/nginxpy', 200 | version='0.1.0', 201 | zip_safe=False, 202 | ) 203 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Unit test package for nginxpy.""" 4 | -------------------------------------------------------------------------------- /tests/test_nginxpy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Tests for `nginxpy` package.""" 5 | 6 | import re 7 | import subprocess 8 | import tempfile 9 | import unittest 10 | from os.path import exists, abspath 11 | 12 | import nginx 13 | 14 | CONF_OK = re.compile(r'configuration file ([^ ]+) test is successful') 15 | 16 | 17 | class TestNginxpy(unittest.TestCase): 18 | def test_extension(self): 19 | self.assertTrue(hasattr(nginx, 'spec')) 20 | self.assertTrue(exists(nginx.spec.origin)) 21 | 22 | with tempfile.NamedTemporaryFile('w') as f: 23 | f.write('error_log stderr;pid ') 24 | with tempfile.NamedTemporaryFile() as pid: 25 | f.write(abspath(pid.name)) 26 | f.write(';load_module ') 27 | f.write(nginx.spec.origin) 28 | f.write(';events {}') 29 | f.flush() 30 | subprocess.check_call(['nginx', '-c', abspath(f.name), '-t']) 31 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27, py34, py35, py36, flake8 3 | 4 | [travis] 5 | python = 6 | 3.6: py36 7 | 3.5: py35 8 | 3.4: py34 9 | 2.7: py27 10 | 11 | [testenv:flake8] 12 | basepython = python 13 | deps = flake8 14 | commands = flake8 nginxpy 15 | 16 | [testenv] 17 | setenv = 18 | PYTHONPATH = {toxinidir} 19 | 20 | commands = 21 | python setup.py build 22 | python setup.py test 23 | --------------------------------------------------------------------------------