Skip to content

Commit

Permalink
Create proxy mapper to extract the mapping logic to its own class
Browse files Browse the repository at this point in the history
Adding watchdog to watch the folder and rebuild the mapping index
  • Loading branch information
Omar Abdelhafith committed Jun 28, 2015
1 parent b1e6f70 commit c894e07
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 116 deletions.
20 changes: 6 additions & 14 deletions mockpy/core/cherrypy_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from mockpy.utils import log
from mockpy.utils.config import *
from mockpy.status.status import Status
import mockpy.utils.cherrypy_extensions


class CherryPyMapper(object):
Expand All @@ -15,14 +16,15 @@ def __init__(self, mapping_handler=None, cherrypy=None):

def handle_request(self):

if Status.is_status(self.cherrypy.url()):
log.log_url(self.cherrypy.url())

if self.status.is_status(self.cherrypy.url()):
info("Accessing Satus")
self.print_seperator()
log.print_seperator()
return self.status.html_response()

request = MappingRequest(self.cherry_request_dict())
request = self.cherrypy.to_mapper_request()
items = self.mapping_handler.mapping_item_for_mapping_request(request)
log.log_url(self.cherrypy.url())

if len(items) == 0:
self.cherrypy.response.status = 500
Expand All @@ -43,16 +45,6 @@ def handle_request(self):

return response.body_response()

def cherry_request_dict(self):
dic = {"method": self.cherrypy.request.method,
"url": self.cherrypy.url(),
"headers": self.cherrypy.request.headers}

if self.cherrypy.request.process_request_body:
dic["body"] = self.cherrypy.request.body.read()

return dic

def fill_headers(self, headers):
if type({}) is not type(headers):
return
Expand Down
90 changes: 90 additions & 0 deletions mockpy/core/proxy_mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-

import httplib
from threading import Thread

from libmproxy.protocol.http import HTTPRequest, HTTPResponse
from netlib.odict import ODictCaseless

import mockpy.utils.proxy_extensions
from ..utils.config import *
from mockpy.status.status import Status
from ..utils import log
from ..models.mapping_items_manager import *

class ProxyMapper(object):

def __init__(self, mapping_handler, http_proxy):
self.http_proxy = http_proxy
self.mapping_handler = mapping_handler
self.status = Status(self.mapping_handler)

success("Proxy server started")

def handle_request(self, flow):
log.log_url(flow.request.url)

if self.status.is_status(flow.request.url):
info("Accessing Satus")
flow.reply(HTTPResponse.with_html(self.status.html_response()))
log.print_seperator()
return

request = flow.request.to_mapper_request()
mapping_items = self.mapping_handler.mapping_item_for_mapping_request(request)

if len(mapping_items) > 1:
log.log_multiple_matches(mapping_items)

if len(mapping_items) == 0:
self.perform_http_request(flow)
else:
self.perform_mapping_request(flow, mapping_items[0])

log.print_seperator()

def perform_mapping_request(self, flow, mapping_item):
response, request = mapping_item.response, mapping_item.request

log.log_request(request)
log.log_response(response)

response = HTTPResponse.from_intercepted_response(response)
flow.reply(response)

def perform_http_request(self, flow):
if self.http_proxy is None:
flow.reply()
else:
thread = Thread(target=self.threaded_perform_http_request,
args=(flow, self.http_proxy))
thread.start()

def threaded_perform_http_request(self, flow, proxy_settings):
response = self.perform_http_connection(flow.request, proxy_settings[0], proxy_settings[1])
flow.reply(response)

@staticmethod
def perform_http_connection(request, url, port):
try:
conn = httplib.HTTPConnection(url, port)
headers = dict(request.headers.items())

conn.request(request.method, request.url,
body=request.content, headers=headers)
httplib_response = conn.getresponse()

headers = ODictCaseless.from_httplib_headers(httplib_response.getheaders())
response = HTTPResponse(code=httplib_response.status,
content=httplib_response.read(),
msg="",
httpversion=(1, 1),
headers=headers)
return response
except Exception as ex:
error("Error Happened")
error(ex)
error("method: %s\nurl: %s\nbody: --\nheaders: --" %
(request.method, request.url))
return None
89 changes: 9 additions & 80 deletions mockpy/core/proxy_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,99 +8,28 @@

from libmproxy import controller, proxy
from libmproxy.proxy.server import ProxyServer
from libmproxy.protocol.http import HTTPRequest, HTTPResponse
from netlib.odict import ODictCaseless

import mockpy.utils.extensions
from ..utils.config import *
from mockpy.status.status import Status
from ..utils import log
from ..models.mapping_items_manager import *

from .proxy_mapper import *

class MITMProxy(controller.Master):

def __init__(self, server, inout_path, res_path, http_proxy):
def __init__(self, server, proxy_mapper):
controller.Master.__init__(self, server)

self.http_proxy = http_proxy
self.handler = MappingItemsManager(inout_path, res_path)
self.status = Status(self.handler)

success("Proxy server started")
self.proxy_mapper = proxy_mapper

def handle_request(self, flow):
request = flow.request.to_mapper_request()
mapping_items = self.handler.mapping_item_for_mapping_request(request)

log.log_url(flow.request.url)

if Status.is_status(flow.request.url):
info("Accessing Satus")
flow.reply(HTTPResponse.with_html(self.status.html_response()))
log.print_seperator()
return

if len(mapping_items) > 1:
log.log_multiple_matches(mapping_items)

if len(mapping_items) == 0:
self.perform_http_request(flow)
else:
self.perform_mapping_request(flow, mapping_items[0])

log.print_seperator()

def perform_mapping_request(self, flow, mapping_item):
response, request = mapping_item.response, mapping_item.request

log.log_request(request)
log.log_response(response)

response = HTTPResponse.from_intercepted_response(response)
flow.reply(response)

def perform_http_request(self, flow):
if self.http_proxy is None:
flow.reply()
else:
thread = Thread(target=self.threaded_perform_http_request,
args=(flow, self.http_proxy))
thread.start()

def threaded_perform_http_request(self, flow, proxy_settings):
response = self.perform_request(flow.request, proxy_settings[0], proxy_settings[1])
flow.reply(response)

@staticmethod
def perform_request(request, url, port):
try:
conn = httplib.HTTPConnection(url, port)
headers = dict(request.headers.items())

conn.request(request.method, request.url,
body=request.content, headers=headers)
httplib_response = conn.getresponse()

headers = ODictCaseless.from_httplib_headers(httplib_response.getheaders())
response = HTTPResponse(code=httplib_response.status,
content=httplib_response.read(),
msg="",
httpversion=(1, 1),
headers=headers)
return response
except Exception as ex:
error("Error Happened")
error(ex)
error("method: %s\nurl: %s\nbody: --\nheaders: --" %
(request.method, request.url))
return None
self.proxy_mapper.handle_request(flow)


def start_proxy_server(port, inout_path, res_path, http_proxy):
config = proxy.ProxyConfig(port=port)
server = ProxyServer(config)
m = MITMProxy(server, inout_path, res_path, http_proxy)

mapping_handler = MappingItemsManager(inout_path, res_path)

proxy_mapper = ProxyMapper(mapping_handler, http_proxy)
m = MITMProxy(server, proxy_mapper)

def signal_handler(signal, frame):
info("\nShutting down proxy server")
Expand Down
50 changes: 42 additions & 8 deletions mockpy/models/mapping_items_manager.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,65 @@
__author__ = 'omarsubhiabdelhafith'

from .mapping_item import *
from mockpy.utils import log
from functools import partial
import re
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

class MappingItemsManager(object):

def __init__(self, inout_path, res_path):
self.yaml_files = get_yaml_files(inout_path)
create_mapping_item_with_yaml = partial(create_mapping_item, inout_path, res_path)
self.mappings = list(map(create_mapping_item_with_yaml, self.yaml_files))
self.inout_path = inout_path
self.res_path = res_path
self.parse_inout_and_res()
self.install_watchers()

def parse_inout_and_res(self):
self.yaml_files = get_yaml_files(self.inout_path)
self.mappings = list(map(self.create_mapping_item, self.yaml_files))

def create_mapping_item(self, yml_file):
full_path = self.inout_path + "/" + yml_file

with open(full_path, "r") as file:
return MappingItem(yaml.load(file), full_path, self.res_path)

def response_for_mapping_request(self, request):
return [item.response for item in self.mappings if item.handles_mapping_request(request)]

def mapping_item_for_mapping_request(self, request):
return [item for item in self.mappings if item.handles_mapping_request(request)]

def install_watchers(self):
self.event_handler = MapperDirectoryListener(self)
self.install_watcher(self.inout_path)
self.install_watcher(self.res_path)

def install_watcher(self, path):
observer = Observer()
observer.schedule(self.event_handler, path, recursive=True)
observer.start()


class MapperDirectoryListener(FileSystemEventHandler):

def __init__(self, mapping_manager):
self.mapping_manager = mapping_manager

def on_any_event(self, event):
log.info("Inout or Res directory changed, rebuilding mapping settings\n"
"File path: %s" % event.src_path +
"\nEvent type: %s" % event.event_type)

self.mapping_manager.parse_inout_and_res()
log.success("Mapping settings rebuilt successfully")
log.print_seperator()


def get_yaml_files(path):
files = os.listdir(path)
return filter(lambda file: re.match(".*\.yml$", file), files)


def create_mapping_item(inout_path, res_path, yml_file):
file_to_open = inout_path + "/" + yml_file
with open(file_to_open, "r") as file:
return MappingItem(yaml.load(file), file_to_open, res_path)


2 changes: 1 addition & 1 deletion mockpy/status/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def html_response(self):
string += "Parsed interceptors:<br/>"
string += "_" * 80
string += "<br/>"

for item in self.mapping_handler.mappings:
string += " - " + item.file_name + "<br/>"

Expand All @@ -27,7 +28,6 @@ def html_response(self):
string += "_" * 80
string += "<br/>"


string += "</body>"
string += "</html>"
return string
Expand Down
18 changes: 18 additions & 0 deletions mockpy/utils/cherrypy_extensions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import cherrypy
from mockpy.models.mapping_request import *


"""
Extensions
"""
def to_mapper_request():
dic = {"method": cherrypy.request.method,
"url": cherrypy.url(),
"headers": cherrypy.request.headers}

if cherrypy.request.process_request_body:
dic["body"] = cherrypy.request.body.read()

return MappingRequest(dic)

setattr(cherrypy, "to_mapper_request", to_mapper_request)
2 changes: 1 addition & 1 deletion mockpy/utils/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def log_multiple_matches(items):


def log_url(url):
info("\nRequest with url '%s'" % url)
info("Request with url '%s'" % url)


def log_request(request):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ def to_mapper_request(self):
flow_request = MappingRequest(params)
return flow_request

HTTPRequest.to_mapper_request = to_mapper_request


def from_httplib_headers(cls, headers):
odict = cls()
Expand All @@ -31,6 +33,9 @@ def from_httplib_headers(cls, headers):

return odict

ODictCaseless.from_httplib_headers = classmethod(from_httplib_headers)


def from_intercepted_response(cls, response):
headers = ODictCaseless.from_httplib_headers(response.headers)
response = cls(code=response.status,
Expand All @@ -48,8 +53,7 @@ def with_html(cls, html):
httpversion=(1, 1))
return response

HTTPRequest.to_mapper_request = to_mapper_request

ODictCaseless.from_httplib_headers = classmethod(from_httplib_headers)
HTTPResponse.from_intercepted_response = classmethod(from_intercepted_response)
HTTPResponse.with_html = classmethod(with_html)

3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
netlib
wheel==0.23.0
watchdog
wheel==0.23.0
Loading

0 comments on commit c894e07

Please sign in to comment.