Skip to content

Commit 8533e81

Browse files
Merge pull request #10 from FireTail-io/feat-log-format-updated
Added support for custom content type handling
2 parents 3192e85 + da9842f commit 8533e81

File tree

173 files changed

+22635
-18844
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

173 files changed

+22635
-18844
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
========================
2+
Custom Validator Example
3+
========================
4+
5+
In this example we fill-in non-provided properties with their defaults.
6+
Validator code is based on example from `python-jsonschema docs`_.
7+
8+
Running:
9+
10+
.. code-block:: bash
11+
12+
$ ./enforcedefaults.py
13+
14+
Now open your browser and go to http://localhost:8080/v1/ui/ to see the Swagger
15+
UI. If you send a ``POST`` request with empty body ``{}``, you should receive
16+
echo with defaults filled-in.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
openapi: '3.0.0'
2+
info:
3+
version: '1'
4+
title: Custom Validator Example
5+
servers:
6+
- url: http://localhost:8080/{basePath}
7+
variables:
8+
basePath:
9+
default: api
10+
paths:
11+
/echo:
12+
post:
13+
description: Echo passed data
14+
operationId: enforcedefaults.echo
15+
requestBody:
16+
required: true
17+
content:
18+
application/json:
19+
schema:
20+
$ref: '#/components/schemas/Data'
21+
responses:
22+
'200':
23+
description: Data with defaults filled in by validator
24+
default:
25+
description: Unexpected error
26+
content:
27+
application/json:
28+
schema:
29+
$ref: '#/components/schemas/Error'
30+
components:
31+
schemas:
32+
Data:
33+
type: object
34+
properties:
35+
foo:
36+
type: string
37+
default: foo
38+
Error:
39+
type: string
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env python3
2+
3+
import firetail
4+
import jsonschema
5+
import six
6+
from firetail.decorators.validation import RequestBodyValidator
7+
from firetail.json_schema import Draft4RequestValidator
8+
9+
10+
async def echo(body):
11+
return body
12+
13+
14+
# via https://python-jsonschema.readthedocs.io/
15+
def extend_with_set_default(validator_class):
16+
validate_properties = validator_class.VALIDATORS['properties']
17+
18+
def set_defaults(validator, properties, instance, schema):
19+
for property, subschema in six.iteritems(properties):
20+
if 'default' in subschema:
21+
instance.setdefault(property, subschema['default'])
22+
23+
for error in validate_properties(
24+
validator, properties, instance, schema):
25+
yield error
26+
27+
return jsonschema.validators.extend(
28+
validator_class, {'properties': set_defaults})
29+
30+
DefaultsEnforcingDraft4Validator = extend_with_set_default(Draft4RequestValidator)
31+
32+
33+
class DefaultsEnforcingRequestBodyValidator(RequestBodyValidator):
34+
def __init__(self, *args, **kwargs):
35+
super(DefaultsEnforcingRequestBodyValidator, self).__init__(
36+
*args, validator=DefaultsEnforcingDraft4Validator, **kwargs)
37+
38+
39+
validator_map = {
40+
'body': DefaultsEnforcingRequestBodyValidator
41+
}
42+
43+
44+
if __name__ == '__main__':
45+
app = firetail.AioHttpApp(
46+
__name__,
47+
port=8080,
48+
specification_dir='.',
49+
options={'swagger_ui': True}
50+
)
51+
app.add_api(
52+
'enforcedefaults-api.yaml',
53+
arguments={'title': 'Hello World Example'},
54+
validator_map=validator_map,
55+
)
56+
app.run()
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
===================
2+
Hello World Example
3+
===================
4+
5+
Running:
6+
7+
.. code-block:: bash
8+
9+
$ ./hello.py
10+
11+
Now open your browser and go to http://localhost:9090/v1.0/ui/ to see the Swagger UI.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env python3
2+
3+
import firetail
4+
from aiohttp import web
5+
6+
7+
async def post_greeting(name):
8+
return web.Response(text=f'Hello {name}')
9+
10+
11+
if __name__ == '__main__':
12+
app = firetail.AioHttpApp(__name__, port=9090, specification_dir='openapi/')
13+
app.add_api('helloworld-api.yaml', arguments={'title': 'Hello World Example'})
14+
app.run()
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
openapi: "3.0.0"
2+
3+
info:
4+
title: Hello World
5+
version: "1.0"
6+
servers:
7+
- url: http://localhost:9090/v1.0
8+
9+
paths:
10+
/greeting/{name}:
11+
post:
12+
summary: Generate greeting
13+
description: Generates a greeting message.
14+
operationId: hello.post_greeting
15+
responses:
16+
200:
17+
description: greeting response
18+
content:
19+
text/plain:
20+
schema:
21+
type: string
22+
example: "hello dave!"
23+
parameters:
24+
- name: name
25+
in: path
26+
description: Name of the person to greet.
27+
required: true
28+
schema:
29+
type: string
30+
example: "dave"
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
=====================
2+
Reverse Proxy Example
3+
=====================
4+
5+
This example demonstrates how to run a firetail application behind a path-altering reverse proxy.
6+
7+
You can either set the path in your app, or set the ``X-Forwarded-Path`` header.
8+
9+
Running:
10+
11+
.. code-block:: bash
12+
13+
$ sudo pip3 install --upgrade firetail[swagger-ui] aiohttp-remotes
14+
$ ./app.py
15+
16+
Now open your browser and go to http://localhost:8080/reverse_proxied/ui/ to see the Swagger UI.
17+
18+
19+
You can also use the ``X-Forwarded-Path`` header to modify the reverse proxy path.
20+
For example:
21+
22+
.. code-block:: bash
23+
24+
curl -H "X-Forwarded-Path: /banana/" http://localhost:8080/openapi.json
25+
26+
{
27+
"servers" : [
28+
{
29+
"url" : "banana"
30+
}
31+
],
32+
"paths" : {
33+
"/hello" : {
34+
"get" : {
35+
"responses" : {
36+
"200" : {
37+
"description" : "hello",
38+
"content" : {
39+
"text/plain" : {
40+
"schema" : {
41+
"type" : "string"
42+
}
43+
}
44+
}
45+
}
46+
},
47+
"operationId" : "app.hello",
48+
"summary" : "say hi"
49+
}
50+
}
51+
},
52+
"openapi" : "3.0.0",
53+
"info" : {
54+
"version" : "1.0",
55+
"title" : "Path-Altering Reverse Proxy Example"
56+
}
57+
}
58+
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/usr/bin/env python3
2+
'''
3+
example of aiohttp firetail running behind a path-altering reverse-proxy
4+
'''
5+
6+
import json
7+
import logging
8+
9+
import firetail
10+
from aiohttp import web
11+
from aiohttp_remotes.exceptions import RemoteError, TooManyHeaders
12+
from aiohttp_remotes.x_forwarded import XForwardedBase
13+
from yarl import URL
14+
15+
X_FORWARDED_PATH = "X-Forwarded-Path"
16+
17+
18+
class XPathForwarded(XForwardedBase):
19+
20+
def __init__(self, num=1):
21+
self._num = num
22+
23+
def get_forwarded_path(self, headers):
24+
forwarded_host = headers.getall(X_FORWARDED_PATH, [])
25+
if len(forwarded_host) > 1:
26+
raise TooManyHeaders(X_FORWARDED_PATH)
27+
return forwarded_host[0] if forwarded_host else None
28+
29+
@web.middleware
30+
async def middleware(self, request, handler):
31+
logging.warning(
32+
"this demo is not secure by default!! "
33+
"You'll want to make sure these headers are coming from your proxy, "
34+
"and not directly from users on the web!"
35+
)
36+
try:
37+
overrides = {}
38+
headers = request.headers
39+
40+
forwarded_for = self.get_forwarded_for(headers)
41+
if forwarded_for:
42+
overrides['remote'] = str(forwarded_for[-self._num])
43+
44+
proto = self.get_forwarded_proto(headers)
45+
if proto:
46+
overrides['scheme'] = proto[-self._num]
47+
48+
host = self.get_forwarded_host(headers)
49+
if host is not None:
50+
overrides['host'] = host
51+
52+
prefix = self.get_forwarded_path(headers)
53+
if prefix is not None:
54+
prefix = '/' + prefix.strip('/') + '/'
55+
request_path = URL(request.path.lstrip('/'))
56+
overrides['rel_url'] = URL(prefix).join(request_path)
57+
58+
request = request.clone(**overrides)
59+
60+
return await handler(request)
61+
except RemoteError as exc:
62+
exc.log(request)
63+
await self.raise_error(request)
64+
65+
66+
def hello(request):
67+
ret = {
68+
"host": request.host,
69+
"scheme": request.scheme,
70+
"path": request.path,
71+
"_href": str(request.url)
72+
}
73+
return web.Response(text=json.dumps(ret), status=200)
74+
75+
76+
if __name__ == '__main__':
77+
app = firetail.AioHttpApp(__name__)
78+
app.add_api('openapi.yaml', pass_context_arg_name='request')
79+
aio = app.app
80+
reverse_proxied = XPathForwarded()
81+
aio.middlewares.append(reverse_proxied.middleware)
82+
app.run(port=8080)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
worker_processes 1;
2+
error_log stderr;
3+
daemon off;
4+
pid nginx.pid;
5+
6+
events {
7+
worker_connections 1024;
8+
}
9+
10+
http {
11+
include /etc/nginx/mime.types;
12+
default_type application/octet-stream;
13+
14+
sendfile on;
15+
16+
keepalive_timeout 65;
17+
18+
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
19+
ssl_prefer_server_ciphers on;
20+
access_log access.log;
21+
server {
22+
23+
listen localhost:9000;
24+
25+
location /reverse_proxied/ {
26+
# Define the location of the proxy server to send the request to
27+
proxy_pass http://localhost:8080/;
28+
# Add prefix header
29+
proxy_set_header X-Forwarded-Path /reverse_proxied/;
30+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
31+
proxy_set_header X-Forwarded-Host $host:$server_port;
32+
proxy_set_header X-Forwarded-Server $host;
33+
proxy_set_header X-Forwarded-Port $server_port;
34+
proxy_set_header X-Forwarded-Proto http;
35+
}
36+
37+
}
38+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
openapi: 3.0.0
2+
info:
3+
title: Path-Altering Reverse Proxy Example
4+
version: '1.0'
5+
servers:
6+
- url: /api
7+
paths:
8+
/hello:
9+
get:
10+
summary: say hi
11+
operationId: app.hello
12+
responses:
13+
'200':
14+
description: hello
15+
content:
16+
text/plain:
17+
schema:
18+
type: string

0 commit comments

Comments
 (0)