Skip to content

Commit

Permalink
Add support for regex based routing. Fixes #203
Browse files Browse the repository at this point in the history
  • Loading branch information
abhinavsingh committed Dec 2, 2019
1 parent 54d2a1c commit d88c36a
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 50 deletions.
16 changes: 8 additions & 8 deletions proxy/dashboard/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,21 @@ class ProxyDashboard(HttpWebServerBasePlugin):

# Redirects to /dashboard/
REDIRECT_ROUTES = [
(httpProtocolTypes.HTTP, b'/dashboard'),
(httpProtocolTypes.HTTPS, b'/dashboard'),
(httpProtocolTypes.HTTP, b'/dashboard/proxy.html'),
(httpProtocolTypes.HTTPS, b'/dashboard/proxy.html'),
(httpProtocolTypes.HTTP, r'/dashboard$'),
(httpProtocolTypes.HTTPS, r'/dashboard$'),
(httpProtocolTypes.HTTP, r'/dashboard/proxy.html$'),
(httpProtocolTypes.HTTPS, r'/dashboard/proxy.html$'),
]

# Index html route
INDEX_ROUTES = [
(httpProtocolTypes.HTTP, b'/dashboard/'),
(httpProtocolTypes.HTTPS, b'/dashboard/'),
(httpProtocolTypes.HTTP, r'/dashboard/$'),
(httpProtocolTypes.HTTPS, r'/dashboard/$'),
]

# Handles WebsocketAPI requests for dashboard
WS_ROUTES = [
(httpProtocolTypes.WEBSOCKET, b'/dashboard'),
(httpProtocolTypes.WEBSOCKET, r'/dashboard$'),
]

def __init__(self, *args: Any, **kwargs: Any) -> None:
Expand All @@ -54,7 +54,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
for method in p.methods():
self.plugins[method] = p

def routes(self) -> List[Tuple[int, bytes]]:
def routes(self) -> List[Tuple[int, str]]:
return ProxyDashboard.REDIRECT_ROUTES + \
ProxyDashboard.INDEX_ROUTES + \
ProxyDashboard.WS_ROUTES
Expand Down
4 changes: 2 additions & 2 deletions proxy/http/inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.subscriber = EventSubscriber(self.event_queue)

def routes(self) -> List[Tuple[int, bytes]]:
def routes(self) -> List[Tuple[int, str]]:
return [
(httpProtocolTypes.WEBSOCKET, self.flags.devtools_ws_path)
(httpProtocolTypes.WEBSOCKET, text_(self.flags.devtools_ws_path))
]

def handle_request(self, request: HttpParser) -> None:
Expand Down
73 changes: 39 additions & 34 deletions proxy/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
:license: BSD, see LICENSE for more details.
"""
import gzip
import re
import time
import logging
import os
import mimetypes
import socket
from abc import ABC, abstractmethod
from typing import List, Tuple, Optional, NamedTuple, Dict, Union, Any
from typing import List, Tuple, Optional, NamedTuple, Dict, Union, Any, Pattern

from .exception import HttpProtocolException
from .websocket import WebsocketFrame, websocketOpcodes
Expand Down Expand Up @@ -56,7 +57,7 @@ def __init__(
self.event_queue = event_queue

@abstractmethod
def routes(self) -> List[Tuple[int, bytes]]:
def routes(self) -> List[Tuple[int, str]]:
"""Return List(protocol, path) that this plugin handles."""
raise NotImplementedError() # pragma: no cover

Expand Down Expand Up @@ -88,11 +89,11 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
self.pac_file_response: Optional[memoryview] = None
self.cache_pac_file_response()

def routes(self) -> List[Tuple[int, bytes]]:
def routes(self) -> List[Tuple[int, str]]:
if self.flags.pac_file_url_path:
return [
(httpProtocolTypes.HTTP, bytes_(self.flags.pac_file_url_path)),
(httpProtocolTypes.HTTPS, bytes_(self.flags.pac_file_url_path)),
(httpProtocolTypes.HTTP, text_(self.flags.pac_file_url_path)),
(httpProtocolTypes.HTTPS, text_(self.flags.pac_file_url_path)),
]
return [] # pragma: no cover

Expand Down Expand Up @@ -148,7 +149,7 @@ def __init__(
self.start_time: float = time.time()
self.pipeline_request: Optional[HttpParser] = None
self.switched_protocol: Optional[int] = None
self.routes: Dict[int, Dict[bytes, HttpWebServerBasePlugin]] = {
self.routes: Dict[int, Dict[Pattern[str], HttpWebServerBasePlugin]] = {
httpProtocolTypes.HTTP: {},
httpProtocolTypes.HTTPS: {},
httpProtocolTypes.WEBSOCKET: {},
Expand All @@ -162,8 +163,8 @@ def __init__(
self.flags,
self.client,
self.event_queue)
for (protocol, path) in instance.routes():
self.routes[protocol][path] = instance
for (protocol, route) in instance.routes():
self.routes[protocol][re.compile(route)] = instance

@staticmethod
def read_and_build_static_file_response(path: str) -> memoryview:
Expand Down Expand Up @@ -215,28 +216,35 @@ def on_request_complete(self) -> Union[socket.socket, bool]:
if self.request.has_upstream_server():
return False

assert self.request.path

# If a websocket route exists for the path, try upgrade
if self.request.path in self.routes[httpProtocolTypes.WEBSOCKET]:
self.route = self.routes[httpProtocolTypes.WEBSOCKET][self.request.path]
for route in self.routes[httpProtocolTypes.WEBSOCKET]:
match = route.match(text_(self.request.path))
if match:
self.route = self.routes[httpProtocolTypes.WEBSOCKET][route]

# Connection upgrade
teardown = self.try_upgrade()
if teardown:
return True
# Connection upgrade
teardown = self.try_upgrade()
if teardown:
return True

# For upgraded connections, nothing more to do
if self.switched_protocol:
# Invoke plugin.on_websocket_open
self.route.on_websocket_open()
return False
# For upgraded connections, nothing more to do
if self.switched_protocol:
# Invoke plugin.on_websocket_open
self.route.on_websocket_open()
return False

break

# Routing for Http(s) requests
protocol = httpProtocolTypes.HTTPS \
if self.flags.encryption_enabled() else \
httpProtocolTypes.HTTP
for r in self.routes[protocol]:
if r == self.request.path:
self.route = self.routes[protocol][r]
for route in self.routes[protocol]:
match = route.match(text_(self.request.path))
if match:
self.route = self.routes[protocol][route]
self.route.handle_request(self.request)
return False

Expand Down Expand Up @@ -266,15 +274,13 @@ def on_client_data(self, raw: memoryview) -> Optional[memoryview]:
while remaining != b'':
# TODO: Teardown if invalid protocol exception
remaining = frame.parse(remaining)
for r in self.routes[httpProtocolTypes.WEBSOCKET]:
if r == self.request.path:
route = self.routes[httpProtocolTypes.WEBSOCKET][r]
if frame.opcode == websocketOpcodes.CONNECTION_CLOSE:
logger.warning(
'Client sent connection close packet')
raise HttpProtocolException()
else:
route.on_websocket_message(frame)
if frame.opcode == websocketOpcodes.CONNECTION_CLOSE:
logger.warning(
'Client sent connection close packet')
raise HttpProtocolException()
else:
assert self.route
self.route.on_websocket_message(frame)
frame.reset()
return None
# If 1st valid request was completed and it's a HTTP/1.1 keep-alive
Expand Down Expand Up @@ -305,9 +311,8 @@ def on_client_connection_close(self) -> None:
return
if self.switched_protocol:
# Invoke plugin.on_websocket_close
for r in self.routes[httpProtocolTypes.WEBSOCKET]:
if r == self.request.path:
self.routes[httpProtocolTypes.WEBSOCKET][r].on_websocket_close()
assert self.route
self.route.on_websocket_close()
self.access_log()

def access_log(self) -> None:
Expand Down
4 changes: 2 additions & 2 deletions proxy/plugin/reverse_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ class ReverseProxyPlugin(HttpWebServerBasePlugin):
}
"""

REVERSE_PROXY_LOCATION: bytes = b'/get'
REVERSE_PROXY_LOCATION: str = r'/get$'
REVERSE_PROXY_PASS = [
b'http://httpbin.org/get'
]

def routes(self) -> List[Tuple[int, bytes]]:
def routes(self) -> List[Tuple[int, str]]:
return [
(httpProtocolTypes.HTTP, ReverseProxyPlugin.REVERSE_PROXY_LOCATION),
(httpProtocolTypes.HTTPS, ReverseProxyPlugin.REVERSE_PROXY_LOCATION)
Expand Down
8 changes: 4 additions & 4 deletions proxy/plugin/web_server_route.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
class WebServerPlugin(HttpWebServerBasePlugin):
"""Demonstrates inbuilt web server routing using plugin."""

def routes(self) -> List[Tuple[int, bytes]]:
def routes(self) -> List[Tuple[int, str]]:
return [
(httpProtocolTypes.HTTP, b'/http-route-example'),
(httpProtocolTypes.HTTPS, b'/https-route-example'),
(httpProtocolTypes.WEBSOCKET, b'/ws-route-example'),
(httpProtocolTypes.HTTP, r'/http-route-example$'),
(httpProtocolTypes.HTTPS, r'/https-route-example$'),
(httpProtocolTypes.WEBSOCKET, r'/ws-route-example$'),
]

def handle_request(self, request: HttpParser) -> None:
Expand Down

0 comments on commit d88c36a

Please sign in to comment.