Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ core/dot_h.c
/uWSGI.egg-info/
check/check_*
!check/*.c

.vscode
/plugins/cffi/_*
/plugins/cffi/cffi_plugin.c

.uwsgi_plugins_builder
3 changes: 3 additions & 0 deletions buildconf/cffi.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[uwsgi]
main_plugin = cffi
inherit = base
13 changes: 13 additions & 0 deletions plugins/cffi/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
PYTHON := python

cffi_plugin.c: cffi_plugin.py cffi_init.py uwsgiplugin.py _constants.h _uwsgi.h types.h module_bundle.py uwsgi.py
$(PYTHON) cffi_plugin.py

_constants.h: ../../uwsgi.h constants.py
$(PYTHON) constants.py

_uwsgi.h: _uwsgi_preprocessed.h filtercdefs.py
$(PYTHON) filtercdefs.py _uwsgi_preprocessed.h > _uwsgi.h

clean:
rm _uwsgi.h _uwsgi_preprocessed.h _constants.h cffi_plugin.c
33 changes: 33 additions & 0 deletions plugins/cffi/build_bundle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python
"""
Create bundle to be used as cffi init code.
"""

import os.path
import gzip
import base64
import sys
import pprint

BUNDLED = {"_init": "cffi_init.py", "uwsgi": "uwsgi.py"}
OUTPUT = {}


def bundler(out):
for key, filename in BUNDLED.items():
with open(filename, "rb") as file:
data = file.read()
data = gzip.compress(data)
data = base64.b64encode(data).decode("latin1")
OUTPUT[key] = data

with open("module_bundle.py", "r") as loader:
for line in loader:
out.write(line)
if line.startswith("# MODULES"):
out.write("\nMODULES =")
pprint.pprint(OUTPUT, out)


if __name__ == "__main__":
bundler(sys.stdout)
173 changes: 173 additions & 0 deletions plugins/cffi/cffi_asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
"""
event-loop independent ASGI functions
"""

from _uwsgi import ffi, lib


def asgi_start_response(wsgi_req, status, headers):
status = b"%d" % status
lib.uwsgi_response_prepare_headers(wsgi_req, ffi.new("char[]", status), len(status))
for (header, value) in headers:
lib.uwsgi_response_add_header(
wsgi_req,
ffi.new("char[]", header),
len(header),
ffi.new("char[]", value),
len(value),
)


def asgi_scope_http(wsgi_req):
"""
Create the ASGI scope for a http or websockets connection.
"""
environ = {}
headers = []
iov = wsgi_req.hvec
for i in range(0, wsgi_req.var_cnt, 2):
key, value = (
ffi.string(ffi.cast("char*", iov[i].iov_base), iov[i].iov_len),
ffi.string(ffi.cast("char*", iov[i + 1].iov_base), iov[i + 1].iov_len),
)
if key.startswith(b"HTTP_"):
# replace cgi-style _ with http-style -
headers.append((key[5:].lower().replace(b"_", b"-"), value))
else:
environ[key.decode("ascii")] = value

REMOTE_PORT = 0
if "REMOTE_PORT" in environ:
REMOTE_PORT = int(environ["REMOTE_PORT"])
SERVER_PORT = 0
if "SERVER_PORT" in environ:
SERVER_PORT = int(environ["SERVER_PORT"])

scope = {
"type": "http",
"asgi": {"spec_version": "2.1"},
"http_version": environ["SERVER_PROTOCOL"][len("HTTP/") :].decode("utf-8"),
"method": environ["REQUEST_METHOD"].decode("utf-8"),
"scheme": "http",
"path": environ["PATH_INFO"].decode("utf-8"),
"raw_path": environ["REQUEST_URI"],
"query_string": environ["QUERY_STRING"],
"root_path": environ["SCRIPT_NAME"].decode("utf-8"),
"headers": headers,
# some references to REMOTE_PORT but not always in environ
"client": (environ["REMOTE_ADDR"].decode("utf-8"), REMOTE_PORT),
"server": (environ["SERVER_NAME"].decode("utf-8"), SERVER_PORT),
"extensions": {"environ": environ},
}

if environ.get("HTTPS") in (b"on",):
scope["scheme"] = "https"

if wsgi_req.http_sec_websocket_key != ffi.NULL:
scope["type"] = "websocket"
if scope["scheme"] == "https":
scope["scheme"] = "wss"
else:
scope["scheme"] = "ws"

return scope


def websocket_recv_nb(wsgi_req):
"""
uwsgi.websocket_recv_nb()
"""
ub = lib.uwsgi_websocket_recv_nb(wsgi_req)
if ub == ffi.NULL:
raise IOError("unable to receive websocket message")
ret = ffi.buffer(ub.buf, ub.pos)[:]
lib.uwsgi_buffer_destroy(ub)
return ret


def websocket_handler(wsgi_req, _send, _ready):
"""
Return (send, receive) for ASGI websocket.

wsgi_req: the request
_send: await _send(event)
_ready: await next message
"""

closed = False

async def receiver():
nonlocal closed

yield {"type": "websocket.connect"}

msg = None
while True:
try:
print("rx, closed=", closed)
msg = websocket_recv_nb(wsgi_req)
except IOError:
closed = True
await _send({"type": "websocket.close"})
yield {
"type": "websocket.disconnect",
"code": 1000,
} # todo lookup code
# don't raise, keep receivin' ?
continue # or return?
if msg:
# check wsgi_req->websocket_opcode for text / binary
value = {"type": "websocket.receive"}
# * %x0 denotes a continuation frame
# * %x1 denotes a text frame
# * %x2 denotes a binary frame
opcode = wsgi_req.websocket_opcode
if opcode == 1:
value["text"] = msg.decode("utf-8")
elif opcode == 2:
value["bytes"] = msg
else:
print("surprise opcode", opcode)
yield value
else:
print("no msg", wsgi_req.websocket_opcode)
await _ready()

receive = receiver().__anext__

async def send(event):
nonlocal closed
print("ws send", event)
if closed:
print("ignore send on closed ws")

elif event["type"] == "websocket.accept":
if (
lib.uwsgi_websocket_handshake(
wsgi_req, ffi.NULL, 0, ffi.NULL, 0, ffi.NULL, 0
)
< 0
):
closed = True
await _send({"type": "websocket.close"})
raise IOError("unable to send websocket handshake")

elif event["type"] == "websocket.send":
# ok to call during any part of app?
if "bytes" in event:
msg = event["bytes"]
websocket_send = lib.uwsgi_websocket_send_binary
else:
msg = event["text"].encode("utf-8")
websocket_send = lib.uwsgi_websocket_send
if websocket_send(wsgi_req, ffi.new("char[]", msg), len(msg)) < 0:
closed = True
await _send({"type": "websocket.close"})
raise IOError("unable to send websocket message")

elif event["type"] == "websocket.close":
print("asked to close in send")
closed = True
await _send(event)

return (send, receive)
Loading