Skip to content

Commit

Permalink
resolve some proxy issues
Browse files Browse the repository at this point in the history
  • Loading branch information
Eduardo Rodrigues committed Feb 27, 2019
1 parent 66e83db commit c2e8e72
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 31 deletions.
52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# functionapprest

[![Build Status](http://travis-ci.org/trustpilot/python-functionapprest.svg?branch=master)](https://travis-ci.org/trustpilot/python-functionapprest) [![Latest Version](https://img.shields.io/pypi/v/functionapprest.svg)](https://pypi.python.org/pypi/functionapprest) [![Python Support](https://img.shields.io/pypi/pyversions/functionapprest.svg)](https://pypi.python.org/pypi/functionapprest)
[![Build Status](https://travis-ci.com/eduardomourar/python-functionapprest.svg?branch=master)](https://travis-ci.com/eduardomourar/python-functionapprest) [![Latest Version](https://img.shields.io/pypi/v/functionapprest.svg)](https://pypi.python.org/pypi/functionapprest) [![Python Support](https://img.shields.io/pypi/pyversions/functionapprest.svg)](https://pypi.python.org/pypi/functionapprest)

Python routing mini-framework for [MS Azure Functions](https://azure.microsoft.com/en-us/services/functions/) with optional JSON-schema validation.

Expand Down Expand Up @@ -112,6 +112,56 @@ def my_own_get(event, path):
return {'path': path}
```

## Using within Function App

**function.json**

```json
{
"scriptFile": "handler.py",
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get"
],
"route": "products/{product_id}"
},
{
"type": "http",
"direction": "out",
"name": "$return"
}
]
}
```

**handler.py**

```python
from functionapprest import functionapp_handler, Request

@functionapp_handler.handle('get', path='/products/<path:product_id>/')
def list_products(req: Request, product_id):
query = req.json.get('query', {})
body = req.json.get('body', {})
headers = req.get('headers', {})
params = req.get('params', {})
response = {
'method': req.method,
'url': req.url,
'headers': headers,
'params': params,
'query': query,
'body': body
}
return response

main = functionapp_handler
```

## Tests

Expand Down
58 changes: 29 additions & 29 deletions functionapprest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
__default_headers = {
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
'Accept': 'application/json'
'Content-Type': 'application/json'
}


Expand Down Expand Up @@ -90,24 +89,20 @@ def __init__(self,
**kwargs) -> None:
self.method = method
self.url = url
if request is not None:
kwargs = request.__dict__
class_name = type(request).__name__
self.headers = kwargs.get(f"_{class_name}__headers")
self.params = kwargs.get(f"_{class_name}__params")
self.route_params = kwargs.get(f"_{class_name}__route_params")
self.__body_bytes = kwargs.get(f"_{class_name}__body_bytes")
if request is not None and isinstance(request, HttpRequest):
self.headers = request.headers
self.params = request.params
self.route_params = request.route_params
body = request.get_body()
else:
self.headers = kwargs.get('headers')
self.params = kwargs.get('params')
self.route_params = kwargs.get('route_params')
if kwargs.get('body') is not None:
self.set_body(kwargs.get('body'))
else:
self.__body_bytes = b''
self.__json = kwargs.get('json') or {}
self.__context = kwargs.get('context') or {}
self.__proxy = kwargs.get('proxy') or False
body = kwargs.get('body', b'')
self.set_body(body or b'')
self.__json = kwargs.get('json', {})
self.__context = kwargs.get('context', {})
self.__proxy = kwargs.get('proxy', None)

self.__charset = 'utf-8'

Expand Down Expand Up @@ -178,17 +173,17 @@ def context(self, val: object = None):
self.__context = val

@property
def proxy(self) -> bool:
def proxy(self) -> str:
return self.__proxy

@proxy.setter
def proxy(self, val: bool):
def proxy(self, val: str):
self.__proxy = val

def get_body(self) -> bytes:
return self.__body_bytes

def get_json(self) -> object:
def get_json(self):
return json.loads(self.__body_bytes.decode())

def set_body(self, body):
Expand All @@ -197,7 +192,7 @@ def set_body(self, body):

if not isinstance(body, (bytes, bytearray)):
raise TypeError(
f"reponse is expected to be either of "
f"response is expected to be either of "
f"str, bytes, or bytearray, got {type(body).__name__}")

self.__body_bytes = bytes(body)
Expand All @@ -213,7 +208,7 @@ class Response(HttpResponse):
def __init__(self, body=None, status_code=None, headers=None, *,
mimetype='application/json', charset='utf-8'):
self.json = None
if isinstance(body, dict):
if isinstance(body, (dict, list)):
self.json = body
body = json.dumps(body, default=_json_serial)
super(Response, self).__init__(body, status_code=status_code, headers=headers, mimetype=mimetype, charset=charset)
Expand Down Expand Up @@ -276,11 +271,14 @@ def _json_load_query(query):
for key, value in query.items()}


def _options_response(req: Request, context: FunctionsContext = None):
# Have to send all possibilities until we can find all methods for a path in Azure Functions
# methods = context.bindings.get('methods') or ['options']
methods = ['options', 'get', 'post', 'put', 'delete']
allowed_methods = ','.join(methods)
def _options_response(req: Request, methods: list):
if not methods:
methods = req.context['bindings'].get('methods', [])
if 'OPTIONS' in methods:
methods.remove('OPTIONS')
if 'HEAD' in methods:
methods.remove('HEAD')
allowed_methods = ','.join(sorted(methods, key=str.upper))
allowed_methods = allowed_methods.upper()
body = {
'allow': allowed_methods
Expand Down Expand Up @@ -330,7 +328,9 @@ def inner_functionapp_handler(req: Request, context: FunctionsContext):
logging.error(message)
return Response(message, 500)

req = Request(req.method, req.url, request=req)
# Casting from Azure HttpRequest to our Request implementation
if isinstance(req, HttpRequest):
req = Request(req.method, req.url, request=req)

# Save context within req for easy access
context.bindings = _load_function_json(context)
Expand All @@ -350,15 +350,15 @@ def inner_functionapp_handler(req: Request, context: FunctionsContext):
req.proxy = None

method_name = req.method.lower()
if method_name == 'options':
return _options_response(req, context)
func = None
kwargs = {}
error_tuple = ('Internal server error', 500)
logging_message = "[%s][{status_code}]: {message}" % method_name
try:
# bind the mapping to an empty server name
mapping = url_maps.bind('')
if method_name == 'options':
return _options_response(req, mapping.allowed_methods(path))
rule, kwargs = mapping.match(path, method=method_name, return_rule=True)
func = rule.endpoint

Expand Down
2 changes: 1 addition & 1 deletion functionapprest/__version__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__author__ = 'eduardomourar'
__email__ = 'eduardo_demoura@yahoo.com.br'
__version__ = '0.1.0'
__version__ = '0.1.1'

0 comments on commit c2e8e72

Please sign in to comment.