Add ProposedRestApiPlugin example. (#101)
* Add ProposedRestApiPlugin example. This plugin is an example to demonstrate how `proxy.py` can help you test and debug client applications without actually having a need of backend server. Simply modify ProposedRestApiPlugin as per need to return json responses for your API specs. * Add README instructions * Correct username
This commit is contained in:
parent
3a9c8bc8a2
commit
c302537572
32
README.md
32
README.md
|
@ -36,6 +36,7 @@ Table of Contents
|
|||
* [Command Line](#command-line)
|
||||
* [Docker Image](#docker-image)
|
||||
* [Plugin Examples](#plugin-examples)
|
||||
* [ProposedRestApiPlugin](#proposedrestapiplugin)
|
||||
* [RedirectToCustomServerPlugin](#redirecttocustomserverplugin)
|
||||
* [FilterByUpstreamHostPlugin](#filterbyupstreamhostplugin)
|
||||
* [CacheResponsesPlugin](#cacheresponsesplugin)
|
||||
|
@ -167,6 +168,37 @@ See [plugin_examples.py](https://github.com/abhinavsingh/proxy.py/blob/develop/p
|
|||
All the examples below also works with `https` traffic but require additional flags and certificate generation.
|
||||
See [TLS Interception](#tls-interception).
|
||||
|
||||
## ProposedRestApiPlugin
|
||||
|
||||
Mock responses for your server REST API.
|
||||
Use to test and develop client side applications
|
||||
without need of an actual upstream REST API server.
|
||||
|
||||
Start `proxy.py` as:
|
||||
|
||||
```
|
||||
$ proxy.py \
|
||||
--plugins plugin_examples.ProposedRestApiPlugin
|
||||
```
|
||||
|
||||
Verify mock API response using `curl -x localhost:8899 http://api.example.com/v1/users/`
|
||||
|
||||
```
|
||||
{"count": 2, "next": null, "previous": null, "results": [{"email": "you@example.com", "groups": [], "url": "api.example.com/v1/users/1/", "username": "admin"}, {"email": "someone@example.com", "groups": [], "url": "api.example.com/v1/users/2/", "username": "admin"}]}
|
||||
```
|
||||
|
||||
Verify the same by inspecting `proxy.py` logs:
|
||||
|
||||
```
|
||||
2019-09-27 12:44:02,212 - INFO - pid:7077 - access_log:1210 - ::1:64792 - GET None:None/v1/users/ - None None - 0 byte
|
||||
```
|
||||
|
||||
Access log shows `None:None` as server `ip:port`. `None` simply means that
|
||||
the server connection was never made, since response was returned by our plugin.
|
||||
|
||||
Now modify `ProposedRestApiPlugin` to returns REST API mock
|
||||
responses as expected by your clients.
|
||||
|
||||
## RedirectToCustomServerPlugin
|
||||
|
||||
Redirects all incoming `http` requests to custom web server.
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
:copyright: (c) 2013-present by Abhinav Singh.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
|
@ -17,19 +18,80 @@ import proxy
|
|||
|
||||
@proxy.route(b'/hello-world')
|
||||
def hello_world(_request: proxy.HttpParser) -> bytes:
|
||||
"""A HttpWebServerRoutePlugin plugin for inbuilt web server."""
|
||||
return proxy.HttpParser.build_response(200, body=b'Hello World')
|
||||
|
||||
|
||||
class ProposedRestApiPlugin(proxy.HttpProxyBasePlugin):
|
||||
"""Mock responses for your upstream REST API.
|
||||
|
||||
Used to test and develop client side applications
|
||||
without need of an actual upstream REST API server.
|
||||
|
||||
Returns proposed REST API mock responses to the client."""
|
||||
|
||||
API_SERVER = b'api.example.com'
|
||||
|
||||
REST_API_SPEC = {
|
||||
b'/v1/users/': {
|
||||
'count': 2,
|
||||
'next': None,
|
||||
'previous': None,
|
||||
'results': [
|
||||
{
|
||||
'email': 'you@example.com',
|
||||
'groups': [],
|
||||
'url': proxy.text_(API_SERVER) + '/v1/users/1/',
|
||||
'username': 'admin',
|
||||
},
|
||||
{
|
||||
'email': 'someone@example.com',
|
||||
'groups': [],
|
||||
'url': proxy.text_(API_SERVER) + '/v1/users/2/',
|
||||
'username': 'someone',
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
def before_upstream_connection(self) -> bool:
|
||||
"""Called after client request is received and
|
||||
before connecting to upstream server."""
|
||||
if self.request.host == self.API_SERVER and self.request.url:
|
||||
if self.request.url.path in self.REST_API_SPEC:
|
||||
self.client.send(proxy.HttpParser.build_response(
|
||||
200, reason=b'OK',
|
||||
headers={b'Content-Type': b'application/json'},
|
||||
body=proxy.bytes_(json.dumps(self.REST_API_SPEC[self.request.url.path]))
|
||||
))
|
||||
else:
|
||||
self.client.send(proxy.HttpParser.build_response(
|
||||
404, reason=b'NOT FOUND', body=b'Not Found'
|
||||
))
|
||||
return True
|
||||
return False
|
||||
|
||||
def on_upstream_connection(self) -> None:
|
||||
pass
|
||||
|
||||
def handle_upstream_response(self, raw: bytes) -> bytes:
|
||||
return raw
|
||||
|
||||
def on_upstream_connection_close(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class RedirectToCustomServerPlugin(proxy.HttpProxyBasePlugin):
|
||||
"""Modifies client request to redirect all incoming requests to a fixed server address."""
|
||||
|
||||
UPSTREAM_SERVER = b'http://localhost:8899'
|
||||
|
||||
def before_upstream_connection(self) -> None:
|
||||
def before_upstream_connection(self) -> bool:
|
||||
# Redirect all non-https requests to inbuilt WebServer.
|
||||
if self.request.method != b'CONNECT':
|
||||
self.request.url = urlparse.urlsplit(self.UPSTREAM_SERVER)
|
||||
self.request.set_host_port()
|
||||
return False
|
||||
|
||||
def on_upstream_connection(self) -> None:
|
||||
pass
|
||||
|
@ -46,10 +108,11 @@ class FilterByUpstreamHostPlugin(proxy.HttpProxyBasePlugin):
|
|||
|
||||
FILTERED_DOMAINS = [b'google.com', b'www.google.com']
|
||||
|
||||
def before_upstream_connection(self) -> None:
|
||||
def before_upstream_connection(self) -> bool:
|
||||
if self.request.host in self.FILTERED_DOMAINS:
|
||||
raise proxy.HttpRequestRejected(
|
||||
status_code=418, reason=b'I\'m a tea pot')
|
||||
return False
|
||||
|
||||
def on_upstream_connection(self) -> None:
|
||||
pass
|
||||
|
@ -72,8 +135,8 @@ class CacheResponsesPlugin(proxy.HttpProxyBasePlugin):
|
|||
self.cache_file_path: Optional[str] = None
|
||||
self.cache_file: Optional[BinaryIO] = None
|
||||
|
||||
def before_upstream_connection(self) -> None:
|
||||
pass
|
||||
def before_upstream_connection(self) -> bool:
|
||||
return False
|
||||
|
||||
def on_upstream_connection(self) -> None:
|
||||
self.cache_file_path = os.path.join(
|
||||
|
@ -95,8 +158,8 @@ class CacheResponsesPlugin(proxy.HttpProxyBasePlugin):
|
|||
class ManInTheMiddlePlugin(proxy.HttpProxyBasePlugin):
|
||||
"""Modifies upstream server responses."""
|
||||
|
||||
def before_upstream_connection(self) -> None:
|
||||
pass
|
||||
def before_upstream_connection(self) -> bool:
|
||||
return False
|
||||
|
||||
def on_upstream_connection(self) -> None:
|
||||
pass
|
||||
|
|
21
proxy.py
21
proxy.py
|
@ -958,7 +958,7 @@ class HttpProxyBasePlugin(ABC):
|
|||
return self.__class__.__name__
|
||||
|
||||
@abstractmethod
|
||||
def before_upstream_connection(self) -> None:
|
||||
def before_upstream_connection(self) -> bool:
|
||||
"""Handler called just before Proxy upstream connection is established.
|
||||
|
||||
Raise HttpRequestRejected to drop the connection."""
|
||||
|
@ -1117,7 +1117,9 @@ class HttpProxyPlugin(ProtocolHandlerPlugin):
|
|||
|
||||
# Note: can raise HttpRequestRejected exception
|
||||
for plugin in self.plugins.values():
|
||||
plugin.before_upstream_connection()
|
||||
teardown = plugin.before_upstream_connection()
|
||||
if teardown:
|
||||
return teardown
|
||||
|
||||
self.authenticate()
|
||||
self.connect_upstream()
|
||||
|
@ -1186,7 +1188,7 @@ class HttpProxyPlugin(ProtocolHandlerPlugin):
|
|||
if not self.request.has_upstream_server():
|
||||
return
|
||||
|
||||
host, port = self.server.addr if self.server else (None, None)
|
||||
server_host, server_port = self.server.addr if self.server else (None, None)
|
||||
if self.request.method == b'CONNECT':
|
||||
logger.info(
|
||||
'%s:%s - %s %s:%s - %s bytes' %
|
||||
|
@ -1194,17 +1196,18 @@ class HttpProxyPlugin(ProtocolHandlerPlugin):
|
|||
self.client.addr[1],
|
||||
text_(
|
||||
self.request.method),
|
||||
text_(host),
|
||||
text_(port),
|
||||
text_(server_host),
|
||||
text_(server_port),
|
||||
self.response.total_size))
|
||||
elif self.request.method:
|
||||
logger.info(
|
||||
'%s:%s - %s %s:%s%s - %s %s - %s bytes' %
|
||||
(self.client.addr[0], self.client.addr[1], text_(
|
||||
self.request.method), text_(host), port, text_(
|
||||
self.request.build_url()), text_(
|
||||
self.response.code), text_(
|
||||
self.response.reason), self.response.total_size))
|
||||
self.request.method), text_(server_host), server_port,
|
||||
text_(self.request.build_url()),
|
||||
text_(self.response.code),
|
||||
text_(self.response.reason),
|
||||
self.response.total_size))
|
||||
|
||||
def authenticate(self) -> None:
|
||||
if self.config.auth_code:
|
||||
|
|
Loading…
Reference in New Issue