Coder Social home page Coder Social logo

pylover / nanohttp Goto Github PK

View Code? Open in Web Editor NEW
47.0 14.0 12.0 9.11 MB

A very micro HTTP framework.

Home Page: http://nanohttp.org

License: Other

Python 96.25% Shell 1.22% Makefile 0.77% CSS 1.38% HTML 0.38%
python-3 rest http dispatcher micro-framework

nanohttp's Introduction

nanohttp

A very micro HTTP framework. documentation PyPI Gitter Build Status Coverage Status

Contribution

Branching

A new branching model is applied to this repository, which consists of a master branch and release branches.

Master branch

The master branch is an integration branch where bug fixes/features are gathered for compiling and functional testing.

Release branch

The release branch is where releases are maintained and hot fixes (with names like release/v2.x.x) are added. Please ensure that all your production-related work are tracked with the release branches.

With this new model, we can push out bug fixes more quickly and achieve simpler maintenance.

nanohttp's People

Contributors

eteamin avatar farzaneka avatar gitter-badger avatar irhonin avatar mahtabsarvmaili avatar masoodkamyab avatar mehrdad1373pedramfar avatar memlucky71 avatar meyt avatar mohadese-yousefi avatar mohammadsheikhian avatar pylover avatar shayan-7 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nanohttp's Issues

Application Object

Hooks, Authenticator and Exception handling will be moved into this object
The WSGI and hooks section in the readme should be updated

Misplace tests

tests module installed automatically on site-packages.

Stack nested contexts

Assume two contexts are nested within the same thread.

from nanohttp.contexts import context, Context

with Context() as ctx1:
    with Context() as ctx2:
        context['blahblah'] = 'something'

500, when no content-length in header

ERROR:main:Internal server error
Traceback (most recent call last):
  File "/home/vahid/.virtualenvs/restfulpy-client.js/lib/python3.5/site-packages/nanohttp/application.py", line 56, in __call__
    result = self.__root__(*remaining_paths)
  File "/home/vahid/workspace/restfulpy/restfulpy/controllers.py", line 21, in __call__
    return super().__call__(*remaining_paths)
  File "/home/vahid/.virtualenvs/restfulpy-client.js/lib/python3.5/site-packages/nanohttp/controllers.py", line 59, in __call__
    return self._serve_handler(handler, *remaining_paths)
  File "/home/vahid/.virtualenvs/restfulpy-client.js/lib/python3.5/site-packages/nanohttp/controllers.py", line 30, in _serve_handler
    return handler(*remaining_paths)
  File "/home/vahid/.virtualenvs/restfulpy-client.js/lib/python3.5/site-packages/nanohttp/decorators.py", line 35, in wrapper
    result = func(*args, **kwargs)
  File "./tests/mockup-server.py", line 56, in echo
    return context.form
  File "/home/vahid/.virtualenvs/restfulpy-client.js/lib/python3.5/site-packages/nanohttp/contexts.py", line 159, in __getattr__
    return getattr(Context.get_current(), key)
  File "/home/vahid/.virtualenvs/restfulpy-client.js/lib/python3.5/site-packages/nanohttp/helpers.py", line 37, in __get__
    val = f(obj)
  File "/home/vahid/.virtualenvs/restfulpy-client.js/lib/python3.5/site-packages/nanohttp/contexts.py", line 97, in form
    data = fp.read(self.request_content_length)
  File "/home/vahid/.virtualenvs/restfulpy-client.js/lib/python3.5/site-packages/nanohttp/helpers.py", line 37, in __get__
    val = f(obj)
  File "/home/vahid/.virtualenvs/restfulpy-client.js/lib/python3.5/site-packages/nanohttp/contexts.py", line 41, in request_content_length
    return None if v is None else int(v)
ValueError: invalid literal for int() with base 10: ''

New validation system

@validate(
    title=dict(required=(True, 704, 'Message'),
    pattern, min_length, max_length, min, max, type, .....),
    id=dict(required=lambda v: False, 400, 'message'), remaining=(400, 'message')
)
def index(self):
    pass


strict_validate = functools.partial(validate, remaining=400)

This is how to test this:

v = ActionValidator(firlds=dict(), )
form = dict(......)
with self.assertRaises(HttpBadRequest):
    v(form)

self.assertRaises(Error, func, params)

Error when Content-Type header repeated.

Request

POST /apiv1/sessions HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: 15302aa4-b157-c61c-720d-66ee6ab486cf

email=ha%example.com&password=123456

Response

Traceback (most recent call last):\n File "/home/dev/.virtualenvs/leo/lib/python3.5/site-packages/nanohttp.py", line 453, in _handle_request\n buffer = next(resp_generator)\n File "/home/dev/.virtualenvs/leo/lib/python3.5/site-packages/nanohttp.py", line 375, in wrapper\n result = func(*args, **kwargs)\n File "/home/dev/workspace/leo/leo/controllers/sessions.py", line 16, in post\n email = context.form.get('email')\n File "/home/dev/.virtualenvs/leo/lib/python3.5/site-packages/nanohttp.py", line 342, in getattr\n return getattr(Context.get_current(), key)\n File "/home/dev/.virtualenvs/leo/lib/python3.5/site-packages/nanohttp.py", line 141, in get\n val = f(obj)\n File "/home/dev/.virtualenvs/leo/lib/python3.5/site-packages/nanohttp.py", line 291, in form\n keep_blank_values=True\n File "/usr/lib/python3.5/cgi.py", line 561, in init\n self.read_single()\n File "/usr/lib/python3.5/cgi.py", line 740, in read_single\n self.read_binary()\n File "/usr/lib/python3.5/cgi.py", line 762, in read_binary\n self.file.write(data)\n\nTypeError: write() argument must be str, not bytes

Revise the documentation.

  • Review the sections and api documentation
  • Add some landing page and tutorials.
  • Uploading the documentation automatically into the nanohttp.org when a new tag is pushed.
  • Google analytics

Jsonilizing time objects fails with Segmentation fault: 11

from datetime import time

from nanohttp import Controller, RestController, html, json, quickstart

class ObjectControllers(RestController):
    @json
    def get(self, obj_id: int = None):
        return dict(some_key=time(15, 15, 15))


class Root(Controller):
    objects = ObjectControllers()

    @html
    def index(self):
        yield

Serving using nanohttp module_name and visiting http://localhost:8080/objects kills the server with a one line log:

Segmentation fault: 11

Are we dealing with resource constraints due to an infinite loop or something?

Cannot handle exception by WSGI middlewares

WSGI middlewares for debugging like werkzeug doesn't work currently, and exceptions handled by nanohttp.

Based this sample:

from werkzeug.debug import DebuggedApplication
from nanohttp import Controller, configure, html
class Root(Controller):
    @html
    def index(self):
        x = 1 / 0
        return 'test'
configure()
app = DebuggedApplication(Root().load_app(), evalex=True)

results show nanohttp exception handler instead of werkzeug debugger.

Simple quickstart app raises Config key was not found: "json" when using `quickstart`

Steps to reproduce:

from nanohttp import Controller, RestController, html, json, quickstart

class ObjectControllers(RestController):
    @json
    def get(self, obj_id: int = None):
        return dict(some_key='some_value')

class Root(Controller):
    objects = ObjectControllers()

    @html
    def index(self):
        yield

quickstart(Root())

visiting http://localhost:8080/objects raises:

File "/Users/etemin/workspace/nanohttp/nanohttp/application.py", line 86, in call
status, response_body = self._handle_exception(ex)
File "/Users/etemin/workspace/nanohttp/nanohttp/application.py", line 39, in _handle_exception
raise ex
File "/Users/etemin/workspace/nanohttp/nanohttp/application.py", line 65, in call
response_body = self.root(*remaining_paths)
File "/Users/etemin/workspace/nanohttp/nanohttp/controllers.py", line 59, in call
return self._serve_handler(handler, *remaining_paths)
File "/Users/etemin/workspace/nanohttp/nanohttp/controllers.py", line 30, in _serve_handler
return handler(*remaining_paths)
File "/Users/etemin/workspace/nanohttp/nanohttp/controllers.py", line 59, in call
return self._serve_handler(handler, *remaining_paths)
File "/Users/etemin/workspace/nanohttp/nanohttp/controllers.py", line 30, in _serve_handler
return handler(*remaining_paths)
File "/Users/etemin/workspace/nanohttp/nanohttp/decorators.py", line 42, in wrapper
return ujson.dumps(result, indent=settings.json.indent)
File "/Users/etemin/virtualenvs/blog/lib/python3.5/site-packages/pymlconf/proxy.py", line 44, in getattr
return getattr(object.getattribute(self, 'proxied_object'), key)
File "/Users/etemin/virtualenvs/blog/lib/python3.5/site-packages/pymlconf/config_nodes.py", line 138, in getattr
raise ConfigKeyError(key)
pymlconf.errors.ConfigKeyError: Config key was not found: "json"

What's the deal here? Should we specify the json manipulation library in the config?
Please note that it works well using nanohttp module_name

Environment:
python: 3.5.2
os: macOS Sierra 10.12.5

TypeError: __init__() got an unexpected keyword argument 'config_files'

Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/gunicorn/arbiter.py", line 578, in spawn_worker
    worker.init_process()
  File "/usr/local/lib/python3.6/site-packages/gunicorn/workers/base.py", line 126, in init_process
    self.load_wsgi()
  File "/usr/local/lib/python3.6/site-packages/gunicorn/workers/base.py", line 135, in load_wsgi
    self.wsgi = self.app.wsgi()
  File "/usr/local/lib/python3.6/site-packages/gunicorn/app/base.py", line 67, in wsgi
    self.callable = self.load()
  File "/usr/local/lib/python3.6/site-packages/gunicorn/app/wsgiapp.py", line 65, in load
    return self.load_wsgiapp()
  File "/usr/local/lib/python3.6/site-packages/gunicorn/app/wsgiapp.py", line 52, in load_wsgiapp
    return util.import_app(self.app_uri)
  File "/usr/local/lib/python3.6/site-packages/gunicorn/util.py", line 376, in import_app
    __import__(module)
  File "/Users/mohammad/Desktop/tehran/wsgi.py", line 5, in <module>
    configure(config_files='config.yml')
  File "/usr/local/lib/python3.6/site-packages/nanohttp.py", line 399, in configure
    settings.load(*args, builtin=BUILTIN_CONFIG, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/pymlconf/proxy.py", line 78, in load
    self._set_instance(ConfigManager(**kw))
TypeError: __init__() got an unexpected keyword argument 'config_files'

Merge status code and status text

Merge status_code and status_text to status field like below:

status_code = 400
status_text = 'Bad Request'

Should be

status = '400 Bad Request'

Take care of `quickstart` function

Reported by @meyt

quickstart(Root(), config='<YAML config string>')
pymlconf.errors.ConfigurationNotInitializedError: Configuration manager object is not initialized yet.

Feature Request: More customization for error handling

At current behavior, when using request_error hook it returns text/plain content type with prepended Internal Server Error message. But i want an HTML page without any extra things.
P.S: It's clear to us, Errors are important, specially if we have sensitive clients.

412 Precondition Failed, if `if-none-match` failed

The If-None-Match HTTP request header makes the request conditional. For GET and HEAD methods, the server will send back the requested resource, with a 200 status, only if it doesn't have an ETag matching the given ones. For other methods, the request will be processed only if the eventually existing resource's ETag doesn't match any of the values listed.

Validate parameters by type annotations

Nice idea!

@validate
def index(self, a: int, b: str, c: date = datetime.now):
    ...

This behaviour should be implemented inside the validation system, so the @validate decorator is mandatory to enable this feature.

ContextStack should be threadsafe

With each request in the web, the request is added to a request queue. Then, Thread pool chooses a thread to serve the request and send back the response.
Finally, this thread is added to the thread pool. In nanohttp, whole the concept is managed through the Context. In nanohttp, it is possible to make nested context. For each request, a thread stack is created.
Since two requests can use a single context, this can run into problems. So the context should be handled thread safe using thread_local.

Use bytes for start_response

It seems we must encode the start_response function's arguments too:

From PEP3333

Note also that strings passed to start_response() as a status or as response headers must follow RFC 2616 with respect to encoding. That is, they must either be ISO-8859-1 characters, or use RFC 2047 MIME encoding.

Improve coding style

  • I believe the codes meet the PEP8 requirements. However, the codes should be reviewed.
  • Maximum line width should be less than 80 characters.

rfc2047

From the cherrypy

            if '=?' in value:
                dict.__setitem__(headers, name, httputil.decode_TEXT(value))
            else:
                dict.__setitem__(headers, name, value)

Use `httputil.HeaderMap()`

Enhancement idea comes from cherrypy:

class HeaderMap(CaseInsensitiveDict):

    """A dict subclass for HTTP request and response headers.

    Each key is changed on entry to str(key).title(). This allows headers
    to be case-insensitive and avoid duplicates.

    Values are header values (decoded according to :rfc:`2047` if necessary).
    """

    protocol = (1, 1)
    encodings = ['ISO-8859-1']

    # Someday, when http-bis is done, this will probably get dropped
    # since few servers, clients, or intermediaries do it. But until then,
    # we're going to obey the spec as is.
    # "Words of *TEXT MAY contain characters from character sets other than
    # ISO-8859-1 only when encoded according to the rules of RFC 2047."
    use_rfc_2047 = True

    def elements(self, key):
        """Return a sorted list of HeaderElements for the given header."""
        key = str(key).title()
        value = self.get(key)
        return header_elements(key, value)

    def values(self, key):
        """Return a sorted list of HeaderElement.value for the given header."""
        return [e.value for e in self.elements(key)]

    def output(self):
        """Transform self into a list of (name, value) tuples."""
        return list(self.encode_header_items(self.items()))

    @classmethod
    def encode_header_items(cls, header_items):
        """
        Prepare the sequence of name, value tuples into a form suitable for
        transmitting on the wire for HTTP.
        """
        for k, v in header_items:
            if isinstance(k, six.text_type):
                k = cls.encode(k)

            if not isinstance(v, text_or_bytes):
                v = str(v)

            if isinstance(v, six.text_type):
                v = cls.encode(v)

            # See header_translate_* constants above.
            # Replace only if you really know what you're doing.
            k = k.translate(header_translate_table,
                            header_translate_deletechars)
            v = v.translate(header_translate_table,
                            header_translate_deletechars)

            yield (k, v)

    @classmethod
    def encode(cls, v):
        """Return the given header name or value, encoded for HTTP output."""
        for enc in cls.encodings:
            try:
                return v.encode(enc)
            except UnicodeEncodeError:
                continue

        if cls.protocol == (1, 1) and cls.use_rfc_2047:
            # Encode RFC-2047 TEXT
            # (e.g. u"\u8200" -> "=?utf-8?b?6IiA?=").
            # We do our own here instead of using the email module
            # because we never want to fold lines--folding has
            # been deprecated by the HTTP working group.
            v = b2a_base64(v.encode('utf-8'))
            return (ntob('=?utf-8?b?') + v.strip(ntob('\n')) + ntob('?='))

        raise ValueError('Could not encode header part %r using '
                         'any of the encodings %r.' %
                         (v, cls.encodings))

Review the response interation

What is going happens if the controller's action returns a string ?

def _response():
    try:
        if buffer is not None:
            yield ctx.encode_response(buffer)

        if resp_generator:
            # noinspection PyTypeChecker
            for chunk in resp_generator:
                yield ctx.encode_response(chunk)
        else:
            yield b''
    except Exception as ex_:  # pragma: no cover
        self.__logger__.exception('Exception while serving the response.')
        if settings.debug:
            # FIXME: Proper way to handle exceptions after start_response
            yield str(ex_).encode()
        raise ex_

    finally:
        self._hook('end_response')
        context.__exit__(*sys.exc_info())

Error when CONTENT_LENGTH is empty string

ERROR:main:Internal server error
Traceback (most recent call last):
  File "/home/mohammad/workspace/nanohttp/nanohttp/application.py", line 64, in __call__
    response_body = self.__root__(*remaining_paths)
  File "/home/mohammad/workspace/restfulpy/restfulpy/controllers.py", line 18, in __call__
    return super().__call__(*remaining_paths)
  File "/home/mohammad/workspace/nanohttp/nanohttp/controllers.py", line 83, in __call__
    return self._serve_handler(handler, remaining_paths)
  File "/home/mohammad/workspace/nanohttp/nanohttp/controllers.py", line 78, in _serve_handler
    return handler(*remaining_paths, **kwargs)
  File "/home/mohammad/workspace/nanohttp/nanohttp/decorators.py", line 52, in wrapper
    result = func(*args, **kwargs)
  File "/home/mohammad/workspace/restfulpy/restfulpy/mockupservers/simple.py", line 144, in echo
    return {k: v for i in (context.form, context.query_string) for k, v in i.items()}
  File "/home/mohammad/workspace/nanohttp/nanohttp/contexts.py", line 184, in __getattr__
    return getattr(Context.get_current(), key)
  File "/home/mohammad/workspace/nanohttp/nanohttp/helpers.py", line 40, in __get__
    val = f(obj)
  File "/home/mohammad/workspace/nanohttp/nanohttp/contexts.py", line 115, in form
    content_type=self.request_content_type
  File "/home/mohammad/workspace/nanohttp/nanohttp/helpers.py", line 133, in parse_any_form
    content_length = int(environ.get('CONTENT_LENGTH', 0))
ValueError: invalid literal for int() with base 10: ''

New exception structure

  • We shouldn't be able to get instance of HttpStatus class.
  • Ability to raise a custom HTTP exception by passing the code and message in initialize method.

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.