Initial Commit

master
Harald Wolff 2019-01-08 17:58:53 +01:00
commit f77d40cc0f
10 changed files with 545 additions and 0 deletions

10
.gitignore vendored 100755
View File

@ -0,0 +1,10 @@
.AppleDouble
.DS_Store
*/.AppleDouble
*/.DS_Store
*.pyc
**/*.pyc
tmp

View File

@ -0,0 +1,9 @@
from lnwsgi.exceptions import *
import lnwsgi.status
import lnwsgi.walkable
import lnwsgi.authentication
from lnwsgi.request import WSGIRequest
from lnwsgi.application import WSGIApplication

View File

@ -0,0 +1,91 @@
import sys
import traceback
import io
import lnwsgi
from lnwsgi.exceptions import WebException
class WSGIApplication:
def __init__(self, appName = "WSGIApplication"):
self.__root = lnwsgi.walkable.Walkable()
self.url = None
self.appName = appName
def root(self):
return self.__root
def setRoot(self, r):
self.__root = r
def setApplicationURL(self, url):
self.url = url
def authenticate(self, request):
return lnwsgi.authentication.Identity("", [])
def handle(self, request):
# try:
path = list( filter( None, request.path.split("/") ) )
response = self.__root.walk( request, path )
if isinstance(response, str):
response = ( response.encode(), )
return response
# except:
# ei = sys.exc_info()
# request.setResponseHeader("content-type","text/html")
# return ("<html><body>{0}: {1}</body></html>".format(ei[0].__name__,ei[1]).encode(),)
def handleException(self, request, exc_info = None):
if exc_info is None:
exc_info = sys.exc_info()
m = io.StringIO()
m.write("Exception: {0}\n".format(exc_info[0].__name__))
m.write("Message: {0}\n".format(exc_info[1]))
m.write("Traceback: {0}\n".format(
"\n ".join( traceback.extract_tb(exc_info[2]).format() )
))
if isinstance(exc_info[1], WebException ):
status = exc_info[1].status
else:
status = 500
if status == 401:
request.setResponseHeader("WWW-Authenticate","Basic realm=\"{0}\", charset=\"UTF-8\"".format(self.appName))
request.setResponseHeader("content-type","text/plain")
request.status(status)
return (m.getvalue().encode(),)
def __call__(self, environ, start_response):
request = lnwsgi.WSGIRequest( self, environ )
print("R: {0:16} {1}".format( str(request.identity()), request ))
try:
request.identity().authorize( request )
response = self.handle( request )
except:
response = self.handleException( request, sys.exc_info() )
start_response(
lnwsgi.status.getStatusMessage(request.status()),
request.listResponseHeaders()
)
return response

View File

@ -0,0 +1,47 @@
import itertools
from lnwsgi.exceptions import Unauthorized,Unauthenticated
class Identity:
def __init__(self, name, rights):
self.__name = name
self.__rights = dict( rights )
def name(self):
return self.__name
def rights(self):
return dict(self.__rights)
def authorizePath(self, request, rpath):
print("authorizePath(): {0} => {1}".format(rpath, request.path))
p = itertools.zip_longest(rpath.split("/"),request.path.split("/"))
for pi in p:
if pi[1] is None:
return True
if pi[0] == pi[1]:
continue
if pi[0] == "*":
continue
if pi[0] == "**":
return True
return False
return True
def authorize(self, request):
for r in self.__rights:
if self.authorizePath( request, r ):
return self.__rights[r]
if self.__name == "":
raise Unauthenticated( request )
raise Unauthorized( request )
def __str__(self):
return self.__name

View File

@ -0,0 +1,25 @@
class WebException(Exception):
def __init__(self, message, status = 500):
Exception.__init__( self, message )
self.status = status
class UnsupportedMediaType(WebException):
pass
class ParameterException(WebException):
def __init__(self, name):
WebException.__init__(self, "value of parameter [{0}] is unsupported".format(name))
class Unauthenticated(WebException):
def __init__(self, request):
WebException.__init__(self, "unauthorized to access {0}".format(request.path), status = 401)
class Unauthorized(WebException):
def __init__(self, request):
WebException.__init__(self, "unauthorized to access {0}".format(request.path), status = 403)

143
lnwsgi/request.py 100755
View File

@ -0,0 +1,143 @@
import sys
import io
import json
from lnwsgi.reqvar import RequestParameters
class WSGIRequest:
def __init__(self,application,environ):
self.__application = application
self.__environ = environ
self.__scheme = self.__environ[ "wsgi.url_scheme" ].lower()
self.method = self.__environ["REQUEST_METHOD"].upper()
if "HTTP_HOST" in self.__environ:
self.__hostname = self.__environ[ "HTTP_HOST" ]
else:
self.__hostname = self.__environ[ "SERVER_NAME" ]
self.__port = int(self.__environ[ "SERVER_PORT" ])
self.__script = self.__environ[ "SCRIPT_NAME" ]
self.path = self.__environ[ "PATH_INFO" ]
if "QUERY_STRING" in self.__environ:
self.__query = self.__environ[ "QUERY_STRING" ]
else:
self.__query = ""
if "CONTENT_TYPE" in self.__environ:
self.content_type = self.__environ["CONTENT_TYPE"]
if "wsgi.input" in self.__environ:
self.__content = self.__environ["wsgi.input"].read()
else:
self.__content = bytes()
self.parameters = RequestParameters( self )
self.__status = 200
self.__response_headers = {}
self.setResponseHeader("content-type","text/html")
self.__walked = []
self.__interpretAccepts()
self.__identity = application.authenticate( self )
def identity(self):
return self.__identity
def __interpretAccepts(self):
self.__accepts = []
if "HTTP_ACCEPTS" in self.__environ:
al = self.__environ["HTTP_ACCEPTS"].split(",")
for a in al:
t = a.split(";")
mtype = t[0].strip()
self.__accepts.append( mtype.lower() )
def __contains__(self, name):
return name in self.__environ
def __getitem__(self, name):
return self.__environ[name]
def __iter__(self):
return self.__environ.__iter__()
def getOrNone(self, name):
if name in self:
return self[name]
return None
def accepts(self):
return list( self.__accepts )
def status(self, sc = None):
if not sc is None:
self.__status = sc
return self.__status
def content(self):
return self.__content
def json(self):
return json.loads(self.__content.decode())
def query(self):
return self.__query
def pushWalkedObject(self, wo):
self.__walked.append( wo )
def walkedObjects(self):
return list( self.__walked )
def self(self, path = None):
if self.__application.url is None:
url = "{0}://{1}".format(self.__scheme,self.__hostname)
else:
url = self.__application.url
# if not ((self.__scheme == "http" and self.__port == 80) or (self.__scheme == "https" and self.__port == 443)):
# url = "{0}:{1}".format(url, self.__port)
if path is None:
path = self.path
url = "{0}{1}{2}".format(url,self.__script,path)
return url;
def getResponseHeaders(self):
return dict( self.__response_headers )
def listResponseHeaders(self):
rh = []
for n in self.__response_headers:
rh.append( (n, self.__response_headers[n] ) )
return rh
def setResponseHeader(self, name, value):
if value is None:
del self.__response_headers[ name.lower() ]
else:
self.__response_headers[ name.lower() ] = value
def __str__(self):
s = io.StringIO()
s.write("{method:8} {path}".format(**self.__dict__))
return s.getvalue()

95
lnwsgi/reqvar.py 100755
View File

@ -0,0 +1,95 @@
from urllib.parse import unquote_plus
class RequestParameter:
def __init__(self,name,value = None,headers = {}):
self.__name = name
self.__value = value
self.__headers = headers
def name(self):
return self.__name
def value(self):
return self.__value
def headers(self):
return self.__headers
def filename(self):
pp = self.__headers.parameters("CONTENT-DISPOSITION")
if (not pp is None) and ("filename" in pp.keys()):
return pp["filename"]
return None
def save(self, filename):
f = open( filename, "wb" )
f.write( self.__value )
f.close()
class RequestParameters:
def __init__(self, request):
self.__request = request
self.__values = {}
self.__append_query()
self.__append_mime()
def __getitem__(self,name):
return self.__values[name][0]
def __contains__(self,name):
return name in self.__values
def get(self,name):
try:
return self[name].value()
except:
return None
def getOrDefault(self, name, default = None):
if name in self:
return self[name].value()
return default
def keys(self):
return self.__values.keys()
def list(self,name):
if name in self.__values:
return list(self.__values[name])
return []
def __append_var(self,var):
if not var.name() in self.__values.keys():
self.__values[var.name()] = []
self.__values[var.name()].append( var )
def __append_mime(self):
pass
# mime = self.__request.mime()
#
# if (mime.multipart()):
# for p in mime.parts():
# n = p.name()
# if not n is None:
# self.__append_var(FormValue(n,p.body(),p.headers()))
def __append_query(self):
qs = self.__request.query()
pairs = qs.split("&")
for pair in pairs:
sp = pair.split("=")
if (len(sp)>0):
name = sp[0]
if (len(sp)>1):
value = sp[1]
else:
value= ""
self.__append_var( RequestParameter( unquote_plus(name), unquote_plus(value)) )

14
lnwsgi/status.py 100755
View File

@ -0,0 +1,14 @@
status_messages = {
200: "OK",
401: "Unauthenticated",
403: "Unauthorized",
404: "NotFound",
500: "Exception was raised while handling your request"
}
def getStatusMessage(statuscode):
if statuscode in status_messages:
return "{0} {1}".format(statuscode,status_messages[statuscode])
return "{0} unknown status".format(statuscode)

View File

@ -0,0 +1,3 @@
from lnwsgi.walkable.walkable import Walkable,CachedWalkable,Callable,TypeAware

View File

@ -0,0 +1,108 @@
import sys
import time
from lnwsgi.exceptions import WebException
class TypeAware:
def media(self, mimeType, request):
raise UnsupportedMediaType( mimeType )
def __call__(self, request):
for accepted in request.accepts():
try:
return self.media( accepted )
except UnsupportedMediaType:
pass
raise UnsupportedMediaType(", ".join(request.accepts()))
class Walkable:
def __init__(self):
pass
def walk(self, request, path = []):
if len(path) == 0:
return self( request )
np = path.pop(0)
next = None
try:
print("___ = {0} : {1}".format(str(self),np))
next = getattr( self, np )
except AttributeError:
if len(path) == 0 and request.method == "PUT":
return self.PUT( request, np )
raise WebException("Not Found", status = 404)
if not isinstance( next, Walkable ):
raise TypeError("Walkable needed")
return next.walk( request, path )
def GET(self, request):
return ("No Implementation here.".encode(),)
def PUT(self, request, name):
raise WebException("Object already exists!")
POST = GET
PUT = GET
HEAD = GET
DELETE = None
def __call__(self, request):
method = getattr(self, request.method)
return method( request )
class CachedWalkable(Walkable):
def __init__(self):
Walkable.__init__(self)
self.__cache_size = 16 # Maximum number of entries within cache
self.__cache = {} # key -> value table
self.__cache_names = [] # List of all cached names (youngest accessed #0)
def _touchCache(self, name):
if name in self.__cache_names:
self.__cache_names.remove( name )
self.__cache_names.insert(0, name)
if len(self.__cache_names) > self.__cache_size:
self._rmCachedObject( self.__cache_names[-1] )
def _addCachedObject(self, name, value):
self.__cache[ name ] = value
self._touchCache( name )
def _rmCachedObject(self, name):
del self.__cache[ name ]
self.__cache_names.remove( name )
def lookup(self,name):
raise AttributeError(name)
def __getattr__(self,name):
if name in self.__cache_names:
self._touchCache( name )
return self.__cache[ name ]
o = self.lookup(name)
self._addCachedObject( name, o )
return o
class Callable(Walkable):
def __init__(self, m):
self.__method = m
def __call__(self, s, request):
return self.__method( s, request )