Scripting

Package initialization

<modules>
    load MY.PACKAGE
    load MY.MODULE
    load ..
</modules>

<routers>
    <router ROUTER_NAME>
        pattern REGEX
        <host HOST_NAME>
            pattern REGEX
            <path PATH_NAME>
                handler MY.PACKAGE_OR_MODULE
            </path>
        </host>
    </router>
</routers>

In fact, modules registered with <path>handler MODULE</path> have the same import machanism as modules registered with <modules> section and are stored in the modules attribute of the Application object.

When the server starts, it executes the existing initialize(application) function of the registered modules/packages. The initialize(application) function accepts an Application object as an argument.

Example:

# file: entrypoint.py           -- sample module
#  or
# file: entrypoint/__init__.py  -- sample package

import datetime

def initialize(application):
    global start_time
    start_time = f'Start time: {datetime.datetime.now()}'

def handler(rw):
    rw.send_html_and_close(
        content=f'<html>{start_time}</html>'
    )

Mapfs architecture

If you create a package that doesn’t contain a handler, the default handler that is a Mapfs object is automatically generated by the server at startup.

The automatically generated handler uses the __www__ dir under the package dir as the folder of static files, and the __cgi__ dir under the package dir as the folder of script files.

myproj/
    pkgs/
        mysite/
            __init__.py
            __www__/
            __cgi__/

Static files in the __www__ folder shall be sent to the browser. And scripts in the __cgi__ folder will be executed when requested.

The static file will be sent if the file and script are matched by the same URL. If no files or scripts are matched, the existing index.html file or index.html.py script will be the choice.

Swap files and hidden files whose names start with dot ( . ), end with tilde ( ~ ), and have .swp , .swx suffixes are ignored.

script samples:

# file: pkgs/__cgi__/a/b/c/test1.py
# test: http(s)://ROUTER/a/b/c/test1/d/e/f

import slowdown.cgi

def GET(rw):  # only GET requests are processed
    path1       = rw.environ['PATH_INFO']  # -> the original path
    path2       = rw.environ['locals.path_info']    # -> /d/e/f/
    script_name = rw.environ['locals.script_name']  # -> /a/b/c/test1
    return \
        rw.send_html_and_close(
            content='<html>It works!</html>'
        )

def POST(rw):  # only POST requests are processed
    form = slowdown.cgi.Form(rw)
    return \
        rw.send_html_and_close(
            content=f'<html>{form}</html>'
        )
# file: pkgs/__cgi__/a/b/c/d/test2.py
# test: http(s)://ROUTER/a/b/c/d/test2/e/f/g

import slowdown.cgi

# You can define a handler called HTTP to handle all
# request methods just in one place.
#
# Be ware, don't define HTTP and GET/POST at the same time.
def HTTP(rw):
    path_info   = rw.environ['locals.path_info'  ]  # -> /e/f/g
    script_name = rw.environ['locals.script_name']  # -> /a/b/c/d/test2
    if 'GET' == rw.environ['REQUEST_METHOD']:
        return \
            rw.send_html_and_close(
                content='<html>It works!</html>'
            )
    elif 'POST' == rw.environ['REQUEST_METHOD']:
        form = slowdown.cgi.Form(rw)
        return \
            rw.send_html_and_close(
                content=f'<html>{form}</html>'
            )
    else:
        return rw.method_not_allowed()

Script initialization

The script can be loaded by multiple slowdown.mapfs.Mapfs handlers, and initialize(mapfs) will be called several times independently.

# The first time the script is loaded, the "initialize(mapfs)" function
# of the script is executed.
def initialize(mapfs_):
    global application
    global mapfs
    application = mapfs_.application
    mapfs       = mapfs_

def GET(rw):
    # Call `__cgi__/a.html.py`
    return mapfs.load_script('a.html').GET(rw)

Calling another cgi script

The Application ‘s modules attribute holds the module specified in the <modules> section of the configuration. And you can get the script module under the __cgi__ folder through Mapfs ‘s load_script(PATH) method.

def GET(rw):
    # Call `MY/PACKAGE/__cgi__/a.html.py:GET`
    mapfsobj = rw.application.modules['MY.PACKAGE'].handler
    mapfsobj.load_script('a.html').GET(rw)
def GET(rw):
    # Transfer to another package
    rw.application.modules['MY.PACKAGE'].handler(rw)

Access the matching configuration

If you want to know which configuration match this request, you can access the HTTPRWPair object’s match attribute.

def GET(rw):
    match = rw.match
    host_section = match.host_section  # HostSection object
    host_section.section  # Original ZConfig section object
    path_section = match.path_section  # PathSection object
    path_section.section  # Original ZConfig section object

Error log

def GET(rw):
    rw.errorlog.debug(msg)
    rw.errorlog.info(msg)
    rw.errorlog.warning(msg)
    rw.errorlog.error(msg)
    rw.errorlog.critical(msg)

The HTTPRWPair object

The script accepts HTTPRWPair object (inherited from slowdown.http.File ) as the only argument. In general, the HTTPRWPair object is sometimes called rw for short.

HTTP Headers

The script can access http headers by reading the rw.environ dict.

locals.path_info

The router sets the path matched by the named group to the envirment variable locals.path_info .

In most cases, when the rw object comes from a Mapfs dispatcher, the path after the script name is set to the envirment variable locals.path_info .

locals.script_name

The name of the script that is in use.

REMOTE_ADDR

The IP address of the client.

REMOTE_PORT

The port of the remote client.

CONTENT_TYPE

The Content-Type header.

REQUEST_URI

Full URI of the request.

REQUEST_METHOD

The method of the request, usually GET and POST , etc.

PATH_INFO

The original path.

SCRIPT_NAME

The originall script name. Always empty.

QUERY_STRING

The query string contained by the URL.

HTTP_*

Other HTTP headers. See RFC 3875

Note

Always use locals.path_info instead of PATH_INFO unless you need access to the original path.

Reading from the POST content

  • slowdown.__main__.HTTPRWPair.read()

  • slowdown.__main__.HTTPRWPair.readline()

Example:

def POST(rw):
    size = rw.readline()
    data = rw.read(size)
    rw.send_response_and_close(
         status='200 OK',
        headers=[('Content-Type', 'text/html')],
        content='<html>OK</html>'
    )

Note

The POST content must be read completely in order to respond further.

Streaming responses

Stream interfaces of HTTPRWPair:

Example:

def GET(rw):
    rw.start_response(
         status='200 OK',
        header=[('Content-Type', 'text/html')]
    )
    rw.write('<html>')
    rw.write('Hello, World!')
    rw.write('</html>')
    rw.close()

Response Status

Method

/

send_response_and_close()

/

send_html_and_close()

304

not_modified()

400

bad_request()

403

forbidden()

404

not_found()

405

method_not_allowed()

413

request_entity_too_large()

414

request_uri_too_large()

500

internal_server_error()

300

multiple_choices()

301

moved_permanently()

302

found()

303

see_other()

307

temporary_redirect()

Example:

def GET(rw):
    return rw.not_found()

Cookies

Cookies can be readed by accessing the cookie attribute of the slowdown.__main__.HTTPRWPair object .

# using cookies

import http.cookies

def GET(rw):
    # get cookies
    # `None` will be returned if there are no cookies exists.
    cookie = rw.cookie  # `http.cookies.SimpleCookie` object

    # set cookies
    new_cookie = http.cookies.SimpleCookie()
    new_cookie['key'] = 'value'
        rw.send_html_and_close(
            content='<html>OK</html>',
            cookie=new_cookie
        )

CGI

The slowdown.cgi module provides CGI protocol support.

Form

slowdown.cgi.Form is a CGI form parser.

>>> form = \
...     slowdown.cgi.Form(
...         rw,             # the incoming `HTTPRWPair` object.
...
...         max_size=10240  # the length of the http content containing
...                         # the CGI form data should be less than
...                         # `max_size` (bytes).
... )
>>> form['checkboxA']
'a'
>>> # If more than one form variable comes with the same name,
>>> # a list is returned.
>>> form['checkboxB']
['a', 'b', 'c']

Upload files

slowdown.cgi.multipart() can be used to handle file uploads.

import slowdown.cgi

def POST(rw):
    # The CGI message must be read completely in order to
    # respond further, so use 'for .. in' to ensure that
    # no parts are unprocessed.
    for part in \
        slowdown.cgi.multipart(
            rw,  # the incoming `slowdown.__main__.HTTPRWPair` object

            # Uploaded files always store their binary filenames in
            # multi-parts heads. Those filenames require an encoding
            # to convert to strings.
            filename_encoding='utf-8'  # the default is 'iso8859-1'
        ):
        # The reading of the current part must be completed
        # before the next part.
        if part.filename is None:  # ordinary form variable
            print (f'key  : {part.name  }')
            print (f'value: {part.read()}')
        else:  # file upload
            with open(part.filename, 'w') as file_out:
                while True:
                    data = part.read(8192)
                    file_out.write(data)
                    if not data:
                        break