Giter VIP home page Giter VIP logo

nanohttp's Introduction

nanohttp

Requirements Status https://travis-ci.org/pylover/nanohttp.svg?branch=master https://coveralls.io/repos/github/pylover/nanohttp/badge.svg?branch=master

A very micro HTTP framework.

Features

  • Very simple, less-code & fast
  • Using object dispatcher instead of regex route dispatcher.
  • Url-Encoded, Multipart and JSON form parsing.
  • No request and or response objects is available, everything is combined in nanohttp.context.
  • A very flexible configuration system: pymlconf
  • Dispatching arguments using the obj.__annonations__
  • Method(verb) dispatcher.

Roadmap

The road map is to keep it simple with 100% code coverage. no built-in template engine and or ORM will be included.

Install

PyPI

$ pip install nanohttp

From Source

$ cd path/to/nanohttp
$ pip install -e .

Quick Start

demo.py

from nanohttp import Controller, RestController, context, html, json, HttpFound


class TipsControllers(RestController):
    @json
    def get(self, tip_id: int = None):
        if tip_id is None:
            return [dict(id=i, title="Tip %s" % i) for i in range(1, 4)]
        else:
            return dict(
                id=tip_id,
                title="Tip %s" % tip_id
            )

    @json
    def post(self, tip_id: int = None):
        tip_title = context.form.get('title')
        print(tip_id, tip_title)

        # Updating the tips title
        # TipStore.get(tip_id).update(tip_title)
        raise HttpFound('/tips/')


class Root(Controller):
    tips = TipsControllers()

    @html
    def index(self):
        yield """
        <html><head><title>nanohttp Demo</title></head><body>
        <form method="POST" action="/tips/2">
            <input type="text" name="title" />
            <input type="submit" value="Update" />
        </form>
        </body></html>
        """
$ nanohttp demo

Or

from nanohttp import quickstart

quickstart(Root())

WSGI

Do you need a WSGI application?

wsgi.py

from nanohttp import configure

configure(config='<yaml config string>', files=['path/to/config.file', '...'], dirs=['path/to/config/directory', '...'])
app = Root().load_app()
# Pass the ``app`` to any ``WSGI`` server you want.

Serve it by gunicorn:

gunicorn --reload wsgi:app

Config File

Create a demo.yml file. The file below is same as the default configuration.

debug: true

domain:

cookie:
  http_only: false
  secure: false

You may use nanohttp.settings anywhere to access the config values.

from nanohttp import Controller, html, settings

class Root(Controller):

    @html
    def index(self):
        yield '<html><head><title>nanohttp demo</title></head><body>'
        yield '<h2>debug flag is: %s</h2>' % settings.debug
        yield '</body></html>'

Passing the config file(s) using command line:

$ nanohttp -c demo.yml [-c another.yml] demo

Passing the config file(s) Using python:

from nanohttp import quickstart

quickstart(Root(), config='<YAML config string>')

Command Line Interface

$ nanohttp -h

usage: nanohttp [-h] [-c CONFIG_FILE] [-d CONFIG_DIRECTORY] [-b {HOST:}PORT]
                [-C DIRECTORY] [-V]
                [{MODULE{.py}}{:CLASS}]

positional arguments:
  {MODULE{.py}}{:CLASS}
                        The python module and controller class to launch.
                        default is python built-in's : `demo_app`, And the
                        default value for `:CLASS` is `:Root` if omitted.

optional arguments:
  -h, --help            show this help message and exit
  -c CONFIG_FILE, --config-file CONFIG_FILE
                        This option may be passed multiple times.
  -d CONFIG_DIRECTORY, --config-directory CONFIG_DIRECTORY
                        This option may be passed multiple times.
  -b {HOST:}PORT, --bind {HOST:}PORT
                        Bind Address. default: 8080
  -C DIRECTORY, --directory DIRECTORY
                        Change to this path before starting the server default
                        is: `.`
  -V, --version         Show the version.

Cookies

Accessing the request cookies:

from nanohttp import context

counter = context.cookies.get('counter', 0)

Setting cookie:

from nanohttp import context, HttpCookie

context.response_cookies.append(HttpCookie('dummy-cookie1', value='dummy', http_only=True))

Trailing slashes

If the Controller.__remove_trailing_slash__ is True, then all trailing slashes are ignored.

def test_trailing_slash(self):
    self.assert_get('/users/10/jobs/', expected_response='User: 10\nAttr: jobs\n')

Decorators

Available decorators are: action, html, text, json, xml, binary

Those decorators are useful to encapsulate response preparation such as setting Content-Type HTTP header.

Take a look at the code of the action decorator, all other decorators are derived from this:

def action(*verbs, encoding='utf-8', content_type=None, inner_decorator=None):
    def _decorator(func):

        if inner_decorator is not None:
            func = inner_decorator(func)

        func.__http_methods__ = verbs if verbs else 'any'

        func.__response_encoding__ = encoding

        if content_type:
            func.__content_type__ = content_type

        return func

    if verbs and callable(verbs[0]):
        f = verbs[0]
        verbs = tuple()
        return _decorator(f)
    else:
        return _decorator

Other decorators are defined using functools.partial:

html = functools.partial(action, content_type='text/html')
text = functools.partial(action, content_type='text/plain')
json = functools.partial(action, content_type='application/json', inner_decorator=jsonify)
xml = functools.partial(action, content_type='application/xml')
binary = functools.partial(action, content_type='application/octet-stream', encoding=None)

Of-course, you can set the response content type using:

context.response_content_type = 'application/pdf'

Of-course, you can define your very own decorator to make your code DRY:

import functools
from nanohttp import action, RestController

pdf = functools.partial(action, content_type='application/pdf')

class MyController(RestController)

    @pdf
    def get(index):
        .......

Serving Static file(s)

The nanohttp.Static class is responsible to serve static files:

from nanohttp import Controller, Static

class Root(Controller):
    static = Static('path/to/static/directory', default_document='index.html')

Then you can access static files on /static/filename.ext

A simple way to run server and only serve static files is:

cd path/to/static/directory
nanohttp :Static

Accessing request payload

The context.form is a dictionary representing the request payload, supported request formats are query-string, multipart/form-data, application/x-www-form-urlencoded and json.

from nanohttp import context, RestController

class TipsControllers(RestController):

    @json
    def post(self, tip_id: int = None):
        tip_title = context.form.get('title')

Dispatcher

The requested path will be split-ed by / and python's getattr will be used on the Root controller recursively to find specific callable to handle request.

from nanohttp import RestController

class Nested(RestController):
    pass

class Root()
    children = Nested()

Then you can access methods on nested controller using: http://host:port/children

On the RestController dispatcher tries to dispatch request using HTTP method(verb) at first.

Context

The context object is a proxy to an instance of nanohttp.Context which is unique per request.

Hooks

A few hooks are available in Controller class: app_load, begin_request, begin_response, end_response, request_error.

For example this how I detect JWT token and refresh it if possible:

class JwtController(Controller):
    token_key = 'HTTP_AUTHORIZATION'
    refresh_token_cookie_key = 'refresh-token'

    def begin_request(self):
        if self.token_key in context.environ:
            encoded_token = context.environ[self.token_key]
            try:
                context.identity = JwtPrincipal.decode(encoded_token)
            except itsdangerous.SignatureExpired as ex:
                refresh_token_encoded = context.cookies.get(self.refresh_token_cookie_key)
                if refresh_token_encoded:
                    # Extracting session_id
                    session_id = ex.payload.get('sessionId')
                    if session_id:
                        context.identity = new_token = self.refresh_jwt_token(refresh_token_encoded, session_id)
                        if new_token:
                            context.response_headers.add_header('X-New-JWT-Token', new_token.encode().decode())

            except itsdangerous.BadData:
                pass

        if not hasattr(context, 'identity'):
            context.identity = None

nanohttp's People

Contributors

pylover avatar meyt avatar

Watchers

James Cloos avatar Salman Amintabar avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.