-
Notifications
You must be signed in to change notification settings - Fork 2
Modules
Module is useful to send custom response:
- Custom headers
- Custom dynamic page
- Custom URL
- Custom request (POST form, custom JSON content...)
- Custom authentication and permissions
- ...
Module is a python module or a package imported in WebScripts Server.
Signatures:
from typing import TypeVar, List, Dict, Tuple, Union
from collections.abc import Iterator
from os import _Environ
Json = TypeVar("Json", dict, list, str, int, None)
Server = TypeVar("Server")
User = TypeVar("User")
def example1(
environ: _Environ,
user: User,
server: Server,
filename: str,
arguments: List[str], # Arguments is a list of str, if you send a "WebScripts request" (a JSON object with "arguments" as attribute)
inputs: List[str], # Value of inputs
csrf_token: str = None,
) -> Tuple[str, Dict[str, str], Union[str, bytes, Iterator[bytes]]]:
return (
"200 OK",
{"Content-Security-Policy": "default-src 'self'"},
"Response text."
)
def example2(
environ: _Environ,
user: User,
server: Server,
filename: str,
arguments: Json, # Arguments is a loaded JSON, if you send a JSON content without attribute named "arguments"
inputs: List[str], # Inputs will be a empty list
csrf_token: str = None,
) -> Tuple[str, Dict[str, str], Union[str, bytes, Iterator[bytes]]]:
return (
"200 OK",
{"Content-Security-Policy": "default-src 'self'"},
[b"Response text."]
)
def example3(
environ: _Environ,
user: User,
server: Server,
filename: str,
arguments: bytes, # Arguments is bytes, if you send a non JSON request
inputs: List[str], # Inputs will be a empty list
csrf_token: str = None,
) -> Tuple[str, Dict[str, str], Union[str, bytes, Iterator[bytes]]]:
return (
"200 OK",
{"Content-Security-Policy": "default-src 'self'"},
(x for x in [b"Response text."])
)
-
environ
(no default value): WSGI environment variables for this request -
user
(no default value): User object (attributes:["id", "name", "groups", "csrf", "ip", "check_csrf"]
, optional: your custom user configurations) -
server
(no default value): it's the WebScripts Server object and contains configurations, useful functions, you can change the WebScripts behaviour from this object. Be careful, it's an advanced usages and you can break the server or security features. -
filename
(no default value): element after the last/
-
arguments
(no default value): list of command line arguments (to launch a script) or a loaded JSON (for JSON content without "arguments" attribute) or bytes (for non-JSON content) -
inputs
(no default value): list of inputs (for stdin of the script) or empty list (if the content is a non WebScripts request: non-JSON content or JSON without "arguments" attribute) -
csrf_token
(optional: default value isNone
)
The arguments
and inputs
lists are built if you respect the default JSON body of WebScripts Server. If the body of the request is JSON without arguments
key/attribute, arguments
will be a dict or list. If the body of the request is not JSON, arguments
will be bytes.
- WebScripts request:
POST /ModuleName/ClassName/method_name/little_string_argument HTTP/1.1
Host: webscripts.local
User-Agent: WebScripts client
Origin: http://127.0.0.1:8000
Content-Type: application/json
{"arguments": {"arg1": {"value": "my argument", "input": false}, "--optional-arg1": {"value": "WebScripts is good !", "input": false}, "arg1": {"value": "my input", "input": true}}}
-
filename
will belittle_string_argument
-
arguments
will be a list withmy argument
,--optional-arg1
andWebScripts is good !
as values (arg1
don't start with-
: is not in the list, but--optional-arg1
start with-
: is in the arguments list) -
inputs
will be a list withmy input
as values
- JSON request:
POST /ModuleName/ClassName/method_name/ HTTP/1.1
Host: webscripts.local
User-Agent: WebScripts client
Origin: http://127.0.0.1:8000
Content-Type: application/json
{"my dict": {"my list": [1, 2.5], "my string": "WebScripts is good !", "my int": 678, "my float": 45.6, "null": null, "true": true, "false": false}}
-
filename
will be empty string -
arguments
will be a dict:{"my dict": {"my list": [1, 2.5], "my string": "WebScripts is good !", "my int": 678, "my float": 45.6, "null": None, "true": True, "false": False}}
-
inputs
will be a empty list
- Others request:
POST /ModuleName/ClassName/method_name/xml_is_bad HTTP/1.1
Host: webscripts.local
User-Agent: WebScripts client
Origin: http://127.0.0.1:8000
Content-Type: application/xml
<ilove>JSON</ilove>
-
filename
will be a string withxml_is_bad
as value -
arguments
will be bytes with<ilove>JSON</ilove>
as value (you can send binary data, like archives, images, office documents, executables, ect...) -
inputs
will be a empty list
- Response
HTTP code
(str
): the HTTP status of the response, the first three digits are required (example:200 OK
) -
Headers
(dict
): dictionary of HTTP headers (pairs of names and header values) - Response body (
bytes
,str
,Iterator[bytes]
): the HTTP body of the response
In the PATH_INFO
the character /
is like .
(object attribute) in python code, the last /
is a call (function()
).
URLs to call a function named hello
in a hello
module:
/hello/hello/ # python code equivalent: hello.hello(..., filename='', ...)
/hello/hello/abc # python code equivalent: hello.hello(..., filename='abc', ...)
URLs to call a function named test
in a class named Test
in a module named Tests
in a package named Example
:
/Example/Tests/Test/test/ # python code equivalent: Example.Tests.Test.test(..., filename='', ...)
/Example/Tests/Test/test/abc # python code equivalent: Example.Tests.Test.test(..., filename='abc', ...)
Some default security headers are sended for all response, you can override the value but you can't delete these headers.
To build your custom error pages (HTTP errors: 500, 403, 404...) create a module (the name does not matter) with functions named: page_<error>
, for example on error 500 the function used will be page_500
.
Look at /path/of/WebScripts/scripts/py/hello.py
this is a demonstration.
To try a module you can comment/uncomment lines (16-19) in server.ini
, to get the following configuration:
# modules # Add custom modules (names) to the server
# modules_path # Add directory to import custom modules
modules=hello
modules_path=./scripts/py
Start the WebScripts Server and open these URL in your web broswer:
- Hello function.
-
Custom error 500 page (only if the
debug
configuration isfalse
). -
Custom error 404 page (only if the
debug
configuration isfalse
). - Custom error 403 page
Get the code in /path/of/WebScripts/project/scripts/py/hello.py
.
-
cgi
, make your own web pages and responses with any executable files and scripts (it's like modules for non python syntax, but you can't access to WebScripts server and configurations) -
Configuration
, activated with the debug mode, read and change your configurations in the web page without stop and restart the WebScripts server. -
csp
, activated with the debug mode, debug the CSP errors and get the CSP report. -
error_pages
, default error pages with requests to WebScripts administrators. -
JsonRpc
, a simple json rpc module to add simple API for some of your automatised tasks -
notification
, add a notifcation on the WebScripts Web Page -
rss
, a RSS to notify, read and add news for teams -
share
, uploads files and generates links to download shared files
URL: http://127.0.0.1:8000/cgi/bin/test.py, http://127.0.0.1:8000/cgi/test.py, http://127.0.0.1:8000/bin/test.py, http://127.0.0.1:8000/cgi-bin/test.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
print("Content-Type: text/plain")
print()
print("Hello world !")
URL: http://127.0.0.1:8000/cgi/bin/hello.py, http://127.0.0.1:8000/cgi/hello.py, http://127.0.0.1:8000/bin/hello.py, http://127.0.0.1:8000/cgi-bin/hello.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from cgi import FieldStorage, parse, MiniFieldStorage
from urllib.parse import unquote, parse_qs
# from cgitb import enable
from os import environ
from sys import argv
# enable() # debug mode
def parse_args(*args, **kwargs) -> None:
"""
This function parses arguments/body with
differents functions/tools.
"""
print("\t\t - Simple parsing:")
if len(argv) == 2:
arguments = unquote(argv[1])
print(f"\t\t\t - Arguments: {argv[0]!r} {argv[1]!r}")
else:
arguments = parse_qs(
environ["QUERY_STRING"], *args, **kwargs
) or parse(*args, **kwargs)
for key, values in arguments.items():
print(
"\t\t\t - ",
repr(key),
"=",
*[(repr(v) + ", ") for v in values],
)
print("\t\t - Complex parsing:")
arguments = FieldStorage(*args, **kwargs)
for argument_name in arguments.keys():
value = arguments[argument_name]
if isinstance(value, MiniFieldStorage):
print(
"\t\t\t - ",
repr(argument_name),
"=",
repr(value.value) + ",",
value,
)
elif isinstance(value, list):
print(
"\t\t\t - ",
repr(argument_name),
"=",
[(repr(v.value) + ",") for v in value],
*value,
)
print("Content-Type: text/plain")
print()
print("Hello world !")
print("\t 1. Don't keep blank values: ")
parse_args()
print("\t 2. Keep blank values: ")
parse_args(keep_blank_values=True)
print("- WebScripts -")
Only with insecure mode activated, don't use it in production.
URL: http://127.0.0.1:8000/csp/debug/
Only with debug mode activated, don't use it in production.
URL: http://127.0.0.1:8000/Configurations/Reload/server/, http://127.0.0.1:8000/Configurations/Reload/scripts/[script_name] (http://127.0.0.1:8000/Configurations/Reload/scripts/test_config.py), http://127.0.0.1:8000/Configurations/Reload/arguments/[script_name]|[argument_name] (http://127.0.0.1:8000/Configurations/Reload/arguments/test_config.py|test_input), http://127.0.0.1:8000/Configurations/Reload/modules/, http://127.0.0.1:8000/Configurations/Reload/web/, http://127.0.0.1:8000/Configurations/Reload/module/[module_name] (http://127.0.0.1:8000/Configurations/Reload/module/cgi)
URL: http://127.0.0.1:8000/error_pages/Report/new/[HTTP_error_code], http://127.0.0.1:8000/error_pages/Request/send/[HTTP_error_code]
Error pages are used when the body is empty or None
:
# create your default error page for error 404
def page_404(error: str) -> Tuple[str, Dict[str, str], List[bytes]]:
"""
This function returns the default HTTP response for error 404.
"""
return "404 Not Found" or error, {"Header1": "Value1"}, [b'<html><body>Error 404, Page Not Found, are you lost ?</body></html>']
def not_found_default(environ, user, server_configuration, filename, arguments, inputs, csrf_token = None):
return "404", {}, None # call page_404 because data is None
def not_found_default(environ, user, server_configuration, filename, arguments, inputs, csrf_token = None):
return "404", {}, b'' # call page_404 because data is empty
def not_found_custom(environ, user, server_configuration, filename, arguments, inputs, csrf_token = None):
return "404", {"Header1": "Value1"}, [b'html><body>This is not my default 404 error page !</body></html>'] # don't call page_404 because there are data
from WebScripts.modules.JsonRpc import JsonRpc
def test_call() -> int:
return 0
def test_argument_list(*args) -> str:
return str([repr(a) for a in args])
def test_argument_dict(a:int = 1, b:int = 2) -> int:
return a + b
JsonRpc.register_function(test_call, "call")
JsonRpc.register_function(test_argument_list)
JsonRpc.register_function(test_argument_dict, "test_args_dict")
# start your WebScripts server here
URL: http://127.0.0.1:8000/JsonRpc/JsonRpc/call, http://127.0.0.1:8000/JsonRpc/JsonRpc/test_argument_list, http://127.0.0.1:8000/JsonRpc/JsonRpc/test_args_dict
from urllib.request import urlopen, Request, HTTPError, URLError
from pprint import pprint
from json import load
try:
response = urlopen(
Request(
"http://127.0.0.1:8000/JsonRpc/JsonRpc/call",
method="POST",
headers={"Origin": "http://127.0.0.1:8000", "Authorization": "Basic QWRtaW46QWRtaW4=", "Content-Type": "application/json"},
data=b'{"jsonrpc": "2.0", "id": 1, "method": "call"}',
)
)
except (HTTPError, URLError) as e:
response = e
print("Status", response.code, response.reason)
pprint(load(response))
response = urlopen(
Request(
"http://127.0.0.1:8000/JsonRpc/JsonRpc/test_argument_list",
method="POST",
headers={"Origin": "http://127.0.0.1:8000", "Authorization": "Basic QWRtaW46QWRtaW4=", "Content-Type": "application/json"},
data=b'{"jsonrpc": "2.0", "id": 2, "method": "test_argument_list", "params": ["abc", 1, null, true]}',
)
)
pprint(load(response))
response = urlopen(
Request(
"http://127.0.0.1:8000/JsonRpc/JsonRpc/test_args_dict",
method="POST",
headers={"Origin": "http://127.0.0.1:8000", "Authorization": "Basic QWRtaW46QWRtaW4=", "Content-Type": "application/json"},
data=b'{"jsonrpc": "2.0", "id": 3, "method": "test_args_dict", "params": {"a": 2, "b": 3}}',
)
)
pprint(load(response))
URL: http://127.0.0.1:8000/notification/add/
URL: http://127.0.0.1:8000/rss/Feed/csv/[news_category], http://127.0.0.1:8000/rss/Feed/json/[news_category], http://127.0.0.1:8000/rss/Feed/[news_category]
URL: http://127.0.0.1:8000/share/Download/filename/LICENSE.txt, http://127.0.0.1:8000/share/Download/id/0, http://127.0.0.1:8000/share/upload/