TABLE OF CONTENTS

Usage

Preface

Before dick in details into Clink, you should know “What is Clink?”. After that, you get Clink’s advantage and disadvantage then use Clink in effective way. This section help you get it.

Clink is HTTP API library. HTTP is big, large, huge domain and Clink doesn’t try to work in all of concepts, it focuses to build HTTP APIs to get, process and save data. If you want something which help you build web front-end application, then go back, Clink is back-end side.

Although Clink focus on HTTP APIs, it also contains many concepts rather than HTTP API such as Application Architecture, Database Model, Authentication, Authorization, and so on. It provides best model to work immediately. That way reduces knowledge which developers must to know in details to help them focus on logic of application. In simple, you can write first logic code lines after you install Clink and don’t worry about Routing, Authentication, etc.

Clink likes extensibility than customizable. You can see that if you look into Clink’s built-in module, it limits option arguments, it is short, clean and do one thing as best as possible. In case you aren’t comfort with it’s behaviors, functions, etc, that time you write new module instead try to custom it.

It doesn’t meant that you can custom any things in Clink. For example, clink.service.auth.AuthConf defines 4 hours for token life, 30 days for refresh token life in default, but you still can use your values.

Clink written in Python and Python runs on cross platform but Clink run on Unix-like platform. Because Clink is server side library, it need to work as well as possible on server side, when Unix-like is best choice for server side. Most popular Unix-like platform for server side is operating system base on Linux kernel such as Debian, CentOS, etc...

Now, if you agreed with our terms: HTTP API, Backend Programming, Extensibility instead of Customizable, Unix-like Platform, then inspect Clink.

Quickstart

Installation

# python3 is use as interpreter for python language
# curl use to test api
$ apt-get install python3 wget curl

# pip is python package manager
$ wget https://bootstrap.pypa.io/get-pip.py
$ python get-pip.py

# install clink through pip
$ pip install clink

Writting

app.py
# STEP 1: get clink library
from clink import stamp, mapper, App, AppConf, Controller


# STEP 2: get an WSGI server
from wsgiref.simple_server import make_server


# STEP 3: create application
conf = AppConf('book-api')
app = App(conf)


# STEP 4: define controller
@stamp()
@mapper.path('/book')
class BookCtl(Controller):
    @mapper.get('/item')
    def get_item(self, req, res):
        res.body = {
            'name': 'How to Die',
            'author': 'Death'
        }


# STEP 5: add controller to application
app.add_ctl(BookCtl)


# STEP 6: load components
app.load()


# STEP 7: serve application on WSGI server
address = 'localhost'
port = 8080
print('Prepare API on http://%s:%i/book/item' % (address, port))
httpd = make_server(address, port, app)
httpd.serve_forever()

Testing

$ python app.py &> /dev/null &
[1] 10297

$ curl localhost:8080/book/item
{"name": "Linux Programming Interface", "author": "Michael Kerrisk"}

$ kill %1

Pipe Line

General Model

                    handler-0                        handler-n
                       |                                |
[o]---[req, res]---[req, res]---[req, res]---....---[req, res]------>[x]
 |                                   |                                |
in msg                            handler-i                      out msg

in msg is HTTP request message from client. out msg is HTTP response message to client. handler-i is handler i-th in pipe line. req and res store data during processing.

In starting of HTTP request message, two object are created req and res. req contains data of incoming message such as remote address, query string, body of messgae, etc. res contains data of response message such as header, body of message, etc.

Then application calls handlers from 0 to n to process request. Each handler do something with [req, res]. It can reads data from req, writes data to res.

Final, application send back res to client, finish request processing.

Detail Model

   [o]------- in msg
    |
[req, res]--- lv 0: receiving handling                              [1]
    |
[req, res]--- lv 1: pre-routing handling                            [n]
    |
[req, res]--- lv 2: routing                                         [1]
    |
[req, res]--- lv 3: pre-main handling                               [n]
    |
[req, res]--- lv 4: main handling                                   [1]
    |
[req, res]--- lv 5: responding handling                             [n]
    |
[req, res]--- lv 6: sending handling                                [1]
    |           |
   [x]          |--- error occurs
 out msg        |
            [req, res]--- lv 7: error handling                      [n]
                |
            [req, res]--- lv 8: error logging handling              [1]
                |
            [req, res]--- lv 9: error sending handling              [1]
                |           |
               [x]          |--- error occurs
             out msg        |--- server application handling
                            |
                           [x]
                        undefined

Right number is number of handlers. n mean that 0 to n. 1 mean that only one.

Level 0: Receiving Handling

This level MUST be perform quickly. First action is create req, set request data such as query string, header, body message in stream format and other default values to it. Second action is create req and set default values to it.

Level 1: Pre-routing Handling

This level MUST be perform quickly. Normal, this level contains one handler. it performs request logging.

Level 2: Routing

This level MUST be perform quickly. It finds main handler to perform in level 4 from request method, path and content type.

Level 3: Pre-main Handling

This level perform data converting such as convert JSON text to Python dictionary. Be careful, if this level contains too many handlers, it causes performance issue.

Level 4: Main Handling

This is main processing what developer MUST defines. For example, client send POST request with data in JSON format to application:

{“name”: “A book”, “author”: “Some body”}

It mean that application MUST create new book with above data. Other levels doesn’t do it. After all, it only finds main handling, maps text data into Python dictionary and call main handling with [req, res]. This level MUST validate data, put it to datbase and set response data. Then next levels will send data to client.

Level 5: Responding Handling

This level perform data converting such as convert dictionary to text format. Be careful, if this level contains too many handlers, it causes performance issues.

Level 6: Sending Handling

This level receive input as text stream, or string then send it to client.

Level 7: Error Handling

From level 0 to level 6 if error occurs, this level handles for it. It can changes [req, res] contents. Main target of this level is provides HTTP Status Code and Message to client.

Level 8: Error Logging Handling

Normal, this level contains one handler, it performs log error information.

Level 9: Error Sending Handling

Send res to client. Note that res was handled, set Status Code and Message correspond with error was occurs.

Exception

After all, if any error occurs during level 7 to level 9, it is handle by server program.

Components

For now, Clink is design follow Pipe Line Model, that mean handlers had format look like below:

def handle(req, res, ...):
    pass

However, handler need to shares, accesses shared resources such as database connection, configurations, etc. We avoid to do it though function’s arguments because each time invoke handler, we MUST detect what is resources handler required, where is it, etc. In simple, it take down performance and take up complicated.

Awesome solution is Components. Components is concept includes component, auto creation, depedency solving, and get instances. All of that concept be perform by Injector. For example:

components.py:
from clink.com import Component, Injector, stamp
from clink.type import Request, Response


# this is component
@stamp()
class AccountService(Component):
    def create(self, info):
        print('created:', info)


# this other component
@stamp(AccountService)
class AccountCtl(Component):
    def __init__(self, account_service):
        # this is dependency
        self._account_service = account_service

    # this is component's function
    def create(self, req, res):
        info = req.body
        self._account_service.create(info)


# this is injector
i = Injector()


# add component into injector
i.add_com(AccountCtl)


# this is auto creation, dependency solving
i.load()


# this is getting instances
acc_ctl = i.ref(AccountCtl)


# try call function
req = Request()
res = Response()
req.body = {'name': 'Micheal', 'email': 'michael@mars.com'}
acc_ctl.create(req, res)


# get other instace
acc_sv = i.ref(AccountService)
print(acc_sv)

Test it

$ python components.py
created: {'name': 'Micheal', 'email': 'michael@mars.com'}
<__main__.AccountService object at 0x7f4716069f28>

Component

Component is wrapped context. It puts all of references to dependencies and component’s functions together. So functions can accesses quickly to dependencies.

@com(AccountService) specify that AccountCtl depends on AccountService.

AccountCtl extend from Component to mark that AccountCtl is an component. However, Component doesn’t contains any things.

Auto Creation

Because Component is class, it MUST be create to run. It’s perform automatically by injector. With abow example, during auto creation, an instance of AccountService will pass into first param of AccountCtl.__init__() without handle from developers.

Depedency Solving

@com decorator specify dependencies. During auto creation, dependencies of component will be recursive analyst, create automatically. That is reason why we don’t add AccountService component but we still can get an instance of it.

Get Instances

Access instance of component by type.

Application

Application is where components combies together. First application define in clink.Application class. To create an HTTP APIs, you must create application.

Every application implement clink.IWsgi. It is an WSGI and allow application run on WSGI server.

Creation Steps

Step 2: Get WSGI server

Here, we use built-in WSGI server in Python. You can chose other servers.

Step 3: Create application

clink.AppConf is an component, however others components may depends on it, so it MUST provide as a argument of clink.App constructor.

Step 4: Define components

This step defines components, it MUST be marked by clink.com.

We define an controller, it MUST be extend from clink.Controller to help application get knowledge about it. mapper.path(‘book’) and mapper.get(‘item’) specifies that: BookCtl.get_item will be call if client perform request GET /book/item.

Step 5: Add components to application

Simply, add type of components to application, then application knows “How to create it?. How to bring them togeter?”, etc.

Step 6: Load components

Application creates instance of components, solve depedency of components, etc. It ensure that everythings are ready to runs.

Step 7: Serve application in WSGI server

We create an WSGI server, specify address and port to bind and make it available on network.

Example

app.py
# STEP 1: get clink library
from clink import stamp, mapper, App, AppConf, Controller


# STEP 2: get an WSGI server
from wsgiref.simple_server import make_server


# STEP 3: create application
conf = AppConf('book-api')
app = App(conf)


# STEP 4: define controller
@stamp()
@mapper.path('/book')
class BookCtl(Controller):
    @mapper.get('/item')
    def get_item(self, req, res):
        res.body = {
            'name': 'How to Die',
            'author': 'Death'
        }


# STEP 5: add controller to application
app.add_ctl(BookCtl)


# STEP 6: load components
app.load()


# STEP 7: serve application on WSGI server
address = 'localhost'
port = 8080
print('Prepare API on http://%s:%i/book/item' % (address, port))
httpd = make_server(address, port, app)
httpd.serve_forever()

Testing

$ python app.py &> /dev/null &
[1] 5946

$ curl localhost:8080/book/item
{"author": "Death", "name": "How to Die"}

$ kill %1

Routing

Clink’s routing is simple but efficiency. It not allow parameters in path for explicit and performance. Let use query arguments instead of path parameters. For examples:

Parameter form Argument form
/book/:id /book/item?id=1243
/book/:id/notes /book/item/notes?id=1243
/news/:year/:month/:date /news/archive?year=2017&month=5&date=3

However, arguments isn’t introduce here, it’s introduce in Controller section.

Clink’s routing provides routing methods by three factors: method, path, content-type. All of it can be done with clink.route.

  • clink.route.get(path)
  • clink.route.post(path, type=MIME_JSON)
  • clink.route.put(path, type=MIME_JSON)
  • clink.route.patch(path, type=MIME_JSON)
  • clink.route.delete(path)

As you see, GET and DELETE method ignores content-type because it’s no meaning. Other methods allow content-type with default value is MIME_JSON. You can access shortcut name for MIME type in clink.mime.type.

Example

routing.py
# STEP 1: get clink library
from clink import stamp, mapper, App, AppConf, Controller


# STEP 2: get an WSGI server
from wsgiref.simple_server import make_server


# STEP 3: create application configuration and application
conf = AppConf('book-api', 'Hell Corporation', '1st, Hell street')
app = App(conf)


# STEP 4: define component - controllers
@stamp()
@mapper.path('/book')
class BookCtl(Controller):
    @mapper.get('/item')
    def get_item(self, req, res):
        res.body = {
            'name': 'How to Die',
            'author': 'Death'
        }

# ===[BEGIN] ADD MORE ROUTES HERE ============================================
    @mapper.post('/item', 'text/plain')
    def create_item(self, req, res):
        res.body = {'msg': 'created'}

    @mapper.patch('/item')
    def patch_item(self, req, res):
        res.body = {'msg': 'patched'}

    @mapper.put('/item')
    def put_item(self, req, res):
        res.body = {'msg': 'putted'}

    @mapper.delete('/item')
    def delete_item(self, req, res):
        res.body = {'msg': 'deleted'}
# ===[END] ADD MORE ROUTES HERE ==============================================


# STEP 5: add components to application
app.add_ctl(BookCtl)


# STEP 6: load components
app.load()


# STEP 7: serve application on WSGI server
address = 'localhost'
port = 8080
print('Prepare API on http://%s:%i/book/item' % (address, port))
httpd = make_server(address, port, app)
httpd.serve_forever()

Testing

$ python routing.py &> /dev/null &
[1] 7071

$ curl -X GET localhost:8080/book/item; echo
{"name": "How to Die", "author": "Death"}

$ curl -X POST -H "Content-Type: text/plain" localhost:8080/book/item; \
  echo
{"msg": "created"}

$ curl -X POST -H "Content-Type: application/json" \
  localhost:8080/book/item; echo
{"status_name": "406 Not Accepted", "message": null, "status": 406}

$ curl localhost:8080/not-exist-path; echo
{"status_name": "404 Not Found", "message": null, "status": 404}

$ kill %1

Logging

clink.App provides built-in logging, it writes log data to two log files:

  • /var/tmp/<app-name>/request.log
  • /var/tmp/<app-name>/error.log

Other logging mechanisms are in developing.

Controller

You know Controller in before sections, now let access request data and modify response. Get back to app_creation, remove BookCtl and add RootCtl, we have example below explains how to do it:

Built-in controllers in clink.ctl

Example

controller.py
# STEP 1: get clink library
from clink import stamp, mapper, App, AppConf, Controller
from clink.error.http import Http401Error, Http404Error


# STEP 2: get an WSGI server
from wsgiref.simple_server import make_server


# STEP 3: create application configuration and application
conf = AppConf('book-api', 'Hell Corporation', '1st, Hell street')
app = App(conf)


# STEP 4: define component - controllers
# ===[BEGIN] REMOVE BOOKCTL AND ADD ROOTCTL ==================================
@stamp()
@mapper.path('/req')
class RootCtl(Controller):
    @mapper.get('/info')
    def get_info(self, req, res):
        res.body = {
            'path': req.path,
            'query_str': req.query_str,
            'content_type': req.content_type,
            'content_length': req.content_length,
            'server_name': req.server_name,
            'server_port': req.server_port,
            'server_protocol': req.server_protocol,
            'remote_addr': req.remote_addr,
            'header': req.header,
            'body': req.body,
        }

    @mapper.get('/no-content')
    def no_content(self, req, res):
        res.status = 204

    @mapper.get('/not-found')
    def not_found(self, req, res):
        raise Http404Error(req, 'Nothing here')

    @mapper.get('/no-auth')
    def no_auth(self, req, res):
        raise Http401Error(req, 'Go back. You are alien')
# ===[END] REMOVE BOOKCTL AND ADD ROOTCTL ====================================


# STEP 5: add components to application
app.add_ctl(RootCtl)


# STEP 6: load components
app.load()


# STEP 7: serve application on WSGI server
address = 'localhost'
port = 8080
print('Prepare API on http://%s:%i/req/info' % (address, port))
httpd = make_server(address, port, app)
httpd.serve_forever()

Testing

$ python controller.py &> /dev/null &
[1] 6556

$ curl -X GET localhost:8080/req/info; echo
{"query_str": null, "body": null, "header": {"HOST": "localhost:8080",
"ACCEPT": "*/*", "USER_AGENT": "curl/7.38.0"}, "content_type": null,
"remote_addr": "127.0.0.1", "server_name": "localhost",
"server_protocol": "HTTP/1.1", "server_port": 8080, "path": "/req/info",
"content_length": 0}

$ curl -X GET -D - localhost:8080/req/no-content; echo
HTTP/1.0 204 No Content
Date: Sat, 20 May 2017 14:56:20 GMT
Server: WSGIServer/0.2 CPython/3.4.2
Content-Type: application/json
Content-Length: 0

$ curl -X GET localhost:8080/req/not-found; echo
{"status_name": "404 Not Found", "message": "Nothing here", "status": 404}

$ curl -X GET localhost:8080/req/no-auth; echo
{"status_name": "401 Unauthorized", "message": "Go back. You are alien",
"status": 401}

$ kill %1

Service

Service is component. You should use service when you need:

  • Carry big logic processing. Entry of main logic proccessing is handlers of controllers, when it too long, you should move it into services.
  • Reuse logic processing. May be some handlers must do same of logic processing, that borrow to write again and again, best way is services. That mean code is more easy to read, debug and maintain.
  • Access to shared resources. Shared resources is configurations, connection to database, it’s some things only exist during runtime. Service is most simple way to to do it because serivce is component and Injector give it’s dependencies automatically.

Clink also provides built-in services, check it out in clink.service

Example

Now, let create an service, share way to public newsparer or magazine. It accepts type and content of newsparer, magazine then generate string includes information of application with type and content. Two controllers use this service though injector.

service.py
# STEP 1: get clink library
from clink import stamp, mapper, App, AppConf, Controller, Service, Version
from clink.mime.type import MIME_PLAINTEXT
from bson import ObjectId


# STEP 2: get an WSGI server
from wsgiref.simple_server import make_server


# STEP 3: create application configuration and application
conf = AppConf('book-api', 'MIT License', Version(0, 1, 0),
               'Hell Corporation', '1st, Hell street')
app = App(conf)


# STEP 4: define components
# ===[BEGIN] DEFINE SERVICES =================================================
@stamp(AppConf)
class PubService(Service):
    def __init__(self, app_conf):
        self._app_conf = app_conf

    def publish(self, type, content):
        id = ObjectId()
        parts = (
            'Id: ', str(id), '\n',
            'Type: ', type, '\n',
            'Content:\n\n', content, '\n\n',
            self._app_conf.name,
            ' v', str(self._app_conf.version), '\n',
            self._app_conf.org_name, '\n',
            self._app_conf.org_addr,
        )
        return ''.join(parts)


@stamp(PubService)
@mapper.path('/newsparer')
class NewsCtl(Controller):
    def __init__(self, pub_sv):
        self._pub_sv = pub_sv

    @mapper.post('/', MIME_PLAINTEXT)
    def publish(self, req, res):
        content = req.body.decode('utf-8')
        pub = self._pub_sv.publish('NEWSPAPER', content)
        res.body = pub.encode('utf-8')
        res.content_type = MIME_PLAINTEXT


@stamp(PubService)
@mapper.path('/magazine')
class MagazineCtl(Controller):
    def __init__(self, pub_sv):
        self._pub_sv = pub_sv

    @mapper.post('/', MIME_PLAINTEXT)
    def publish(self, req, res):
        content = req.body.decode('utf-8')
        pub = self._pub_sv.publish('MAGAZINE', content)
        res.body = pub.encode('utf-8')
        res.content_type = MIME_PLAINTEXT

# ===[END] DEFINE SERVICES ===================================================


# STEP 5: add components to application
app.add_ctl(NewsCtl)
app.add_ctl(MagazineCtl)

# STEP 6: load components
app.load()

# STEP 7: serve application on WSGI server
address = 'localhost'
port = 8080
print('Prepare API on http://%s:%i' % (address, port))
httpd = make_server(address, port, app)
httpd.serve_forever()

Testing

$ python service.py &> /dev/null &
[1] 5864

$ curl -X POST -H "Content-Type: text/plain" \
  -d "This is awesome newsparer" localhost:8080/newsparer; echo
Id: 5920522fe7798916e88e93fd
Type: NEWSPAPER
Content:

This is awesome newsparer

book-api
Hell Corporation
1st, Hell street

$ curl -X POST -H "Content-Type: text/plain" \
  -d "This is awesome magazine" localhost:8080/magazine; echo
Id: 59be3309e779894758b26f86
Type: NEWSPAPER
Content:

This is awesome newsparer

book-api v0.1.0
Hell Corporation
1st, Hell street

$ kill %1

Data Conversion

Data conversion divides into two type:

  • Request conversion: Depend on request content-type, map raw data into Python object. For example, map JSON string into Python dictionary. In default, Clink convert JSON string and URL Encode to dictionary or list, etc. This handlers must extend from Lv3Handler - Pre-Main Handling.
  • Response conversion: Depend on response content-type, convert Python object into string, because send handler accept string as body of response message. In default, Clink convert dictionary, list, etc to JSON string. This handlers must extend from Lv5Handler - Responding Handling

Now, let create data conversion handlers to know “How it work?”. We create two handlers:

  • ReqTextHandler converts ‘text/plain’ to list of words.
  • ResTextHandler joins list of words to string

And we create main handler to converts list of words to uppercase: TextCtl.process_text

Example

data_conversion.py
# STEP 1: get clink library
from clink import stamp, mapper, App, AppConf, Controller
from clink.iface import ILv3Handler, ILv5Handler
from clink.mime.type import MIME_PLAINTEXT


# STEP 2: get an WSGI server
from wsgiref.simple_server import make_server


# STEP 3: create application configuration and application
conf = AppConf('book-api', 'Hell Corporation', '1st, Hell street')
app = App(conf)


# STEP 4: define components
# ===[BEGIN] ADD DATA CONVERSION HANDLERS ====================================
@stamp()
class ReqTextHandler(ILv3Handler):
    def handle(self, req, res):
        if req.content_type != MIME_PLAINTEXT:
            return
        if req.body is None:
            req.body = []
        else:
            req.body = req.body.decode('utf-8').split(' ')


@stamp()
class ResTextHandler(ILv5Handler):
    def handle(self, req, res):
        if res.content_type != MIME_PLAINTEXT:
            return
        if res.body is None:
            res.body = ''
        else:
            res.body = ' '.join(res.body).encode('utf-8')


@stamp()
@mapper.path('/text')
class TextCtl(Controller):
    @mapper.post('/', MIME_PLAINTEXT)
    def process_text(self, req, res):
        res.body = [w.upper() for w in req.body]
        res.content_type = MIME_PLAINTEXT
# ===[END] ADD DATA CONVERSION HANDLERS ======================================


# STEP 5: add components to application
app.add_handler(ReqTextHandler)
app.add_handler(ResTextHandler)
app.add_ctl(TextCtl)

# STEP 6: load components
app.load()

# STEP 7: serve application on WSGI server
address = 'localhost'
port = 8080
print('Prepare API on http://%s:%i/text' % (address, port))
httpd = make_server(address, port, app)
httpd.serve_forever()

Testing

$ python data_conversion.py &> /dev/null &
[1] 6456

$ curl -X POST -H "Content-Type: text/plain" \
  -d "my name is kevin" localhost:8080/text; echo
MY NAME IS KEVIN

$ kill %1

Data Validation

Data validation use many CPU resource, any mistake on data validation will down system into hole. Clink provides a mechanism to controll all of this processing in clink.dflow. In simple, put one data validation in service.

Example

data_validation.py
# STEP 1: get clink library
from clink import stamp, mapper, App, AppConf, Controller, Service
from clink.dflow import verify


# STEP 2: get an WSGI server
from wsgiref.simple_server import make_server


# STEP 3: create application configuration and application
conf = AppConf('book-api', 'Hell Corporation', '1st, Hell street')
app = App(conf)


# STEP 4: define component - service, controllers
# ===[BEGIN] TRY DATA VALIDATION =============================================
POST_BOOK_SCHEMA = {
    'type': 'object',
    'additionalProperties': False,
    'required': ['name', 'author'],
    'properties': {
        'name': {'type': 'string', 'pattern': '^[a-zA-Z0-9 ]{2,32}$'},
        'author': {'type': 'string', 'pattern': '^[a-zA-Z0-9 ]{2,16}$'}
    }
}


@stamp()
class BookSv(Service):
    @verify(None, POST_BOOK_SCHEMA)
    def create_one(self, book):
        # if data is invalid, clink.dflow.FormatError will be raise
        # then application catch and handle it

        pass


@stamp(BookSv)
@mapper.path('/book')
class BookCtl(Controller):
    def __init__(self, book_sv):
        self._book_sv = book_sv

    @mapper.post('/item')
    def create_one_book(self, req, res):
        self._book_sv.create_one(req.body)
        res.body = {'message': 'created'}
# ===[END] TRY DATA VALIDATION ===============================================


# STEP 5: add components to application
app.add_ctl(BookCtl)


# STEP 6: load components
app.load()


# STEP 7: serve application on WSGI server
address = 'localhost'
port = 8080
print('Prepare API on http://%s:%i/book/item' % (address, port))
httpd = make_server(address, port, app)
httpd.serve_forever()

Testing

$ python data_validation.py &> /dev/null &
[1] 9977

$ curl -X POST -H "Content-Type: application/json" \
-d '{"name": "Dead Note"}' localhost:8080/book/item; echo
{"message": {"value": "{'name': 'Dead Note'}", "name": "book",
"schema": {"type": "object", "properties": {"name": {"type":
"string", "pattern": "^[a-zA-Z0-9 ]{2,32}$"}, "author": {"type":
"string", "pattern": "^[a-zA-Z0-9 ]{2,16}$"}}, "required":
["name", "author"], "additionalProperties": false}},
"status_name": "400 Bad Request", "status": 400}


$ curl -X POST -H "Content-Type: application/json" \
-d '{"name": "Dead Note", "author": "Death"}' \
localhost:8080/book/item; echo
{"message": "created"}

$ kill %1

APIs

clink.iface

class clink.iface.pipe.IPipeHandler[source]
class clink.iface.pipe.ILv0Handler[source]

Receive handling

handle(req, res, env)[source]
Parameters:
class clink.iface.pipe.ILv1Handler[source]

Pre-Routing handling

handle(req, res)[source]
Parameters:
class clink.iface.pipe.ILv2Handler[source]

Routing

handle(req)[source]
Parameters:req (Request) –
Return type:function
class clink.iface.pipe.ILv3Handler[source]

Pre-Main handling

handle(req, res)[source]
Parameters:
class clink.iface.pipe.ILv4Handler[source]

Main handling. It must be function, but we can’t define interface for functions. Here are symbolic interface.

class clink.iface.pipe.ILv5Handler[source]

Responding handling

handle(req, res)[source]
Parameters:
class clink.iface.pipe.ILv6Handler[source]

Sending handling

handle(req, res, wsgi_send)[source]
Parameters:
class clink.iface.pipe.ILv7Handler[source]

Error handling

handle(req, res, e)[source]
Parameters:
class clink.iface.pipe.ILv8Handler[source]

Error logging handling

handle(req, res, e)[source]
Parameters:
class clink.iface.pipe.ILv9Handler[source]

Sending error handling

handle(req, res, wsgi_send)
Parameters:
class clink.iface.wsgi.IWsgi[source]
__call__(wsgi_env, wsgi_send)[source]

WSGI inteface

Parameters:
  • wsgi_env (dict) –
  • wsgi_send (function) –

clink.handler

class clink.handler.err_http.ErrorHttpHandler[source]

Catch HTTP error and make response message correspond with error

handle(req, res, e)[source]
class clink.handler.err_log.ErrorLogHandler(app_conf)[source]

Catch error, write information to file at /var/tmp/<app-name>/error.log

handle(req, res, e)[source]
class clink.handler.recv.RecvHandler[source]

Receive HTTP message, construct an isinstance of Request

Parameters:
  • req (clink.Request) –
  • res (clink.Response) –
  • env (dict) –
handle(req, res, env)[source]
class clink.handler.req_json.ReqJsonHandler[source]

Map JSON string from body message to Python object

handle(req, res)[source]
class clink.handler.req_log.ReqLogHandler(app_conf)[source]

Catch HTTP request, write information of request to file in /var/tmp/<app-name>/request.log

handle(req, res)[source]
class clink.handler.req_url_encode.ReqUrlEncodeHandler[source]

Map URL Encode string to Python object

handle(req, res)[source]
class clink.handler.res_cors.ResCorsHandler[source]

Inform client to know that server allow CORS

handle(req, res)[source]
class clink.handler.res_json.ResJsonHandler[source]

Serialize Python object to JSON string

handle(req, res)[source]
class clink.handler.send.SendHandler[source]

Send response message to client

handle(req, res, wsgi_send)[source]
class clink.handler.dflow_err.DflowErrorHandler[source]

Catch Data Flow error and make response message correspond with error

handle(req, res, e)[source]

clink.mime

Development

Installation

Clink is not depends on mongodb server but it’s testing do. Mongodb server is not in standard package repository of Linux distros, install it by hand here https://docs.mongodb.com/manual/administration/install-on-linux/.

Then follow instructions:

# essential tools
$ apt-get install python3 git

# clone source code
$ git clone https://github.com/kevin-leptons/clink
$ cd clink

# enter virtual environment
$ ./env init
$ . venv/bin/active

# install dependency packages
$ ./env install

# create test configuration
export CLINK_TEST_ROOT_EMAIL='test-mail@gmail.com'
export CLINK_TEST_ROOT_EMAIL_PWD='test-mail-pwd'
export CLINK_TEST_ROOT_EMAIL_SERVER='smtp.gmail.com'
export CLINK_TEST_ROOT_EMAIL_SERVER_PORT='587'

Develop

# build document
$ ./ctl doc

# view document
$ ./ctl doc --view

# test
$ ./ctl test

# build and push pip package to pypi
# it required authentication
$ ./ctl release