import aiofiles
+import aiohttp
import asyncio
import http
import json
else:
await self._bus.send(Message(self._name, message))
except ConnectionClosed:
- pass
- await self._bus.send(Message(self._name,
- {'event': 'connection closed'}))
- self._bus.unregister(self._name)
+ await self._bus.send(Message(self._name,
+ {'event': 'connection closed'}))
+ self._bus.unregister(self._name)
Response = Optional[Tuple[http.HTTPStatus, Headers, bytes]]
'port': {'type': 'integer'},
'web': {'type': 'object',
'patternProperties':
- {'^/([A-Z][A-Za-z]*)?$':
- {'type': 'object',
- 'properties': {'module': {'type': 'string'},
- 'location': {'type': 'string'}},
- 'required': ['location']}},
+ {'^/[A-Za-z0-9]*$':
+ {'anyOf':
+ [{'type': 'object',
+ 'properties': {'module': {'type': 'string'},
+ 'location': {'type': 'string'}},
+ 'required': ['location']},
+ {'type': 'object',
+ 'properties': {'url': {'type': 'string'}},
+ 'required': ['url']}]}},
'additionalProperties': False}}}
"""Schema for WSServer plugin configuration.
- 'port': port to connect to (default: 80)
- 'web': mapping of web paths to locations on disk (either as absolute
path, relative path from the working directory or path from the
- path containing a given module)
+ path containing a given module) or proxies to URLs
"""
- async def _handler(self, websocket: WebSocketServerProtocol,
- path: str) -> None:
- connection = Connection(self.bus, websocket)
- await connection.run()
+ def process_conf(self) -> None:
+ """Get host, port and path settings from configuration."""
+ self._host = None
+ if 'host' in self.conf:
+ self._host = self.conf['host']
+ else:
+ print(f"'host' not configured for WSServer '{self.name}'."
+ " Serving on all interfaces.")
+ self._port = 80
+ if 'port' in self.conf:
+ self._port = self.conf['port']
+ else:
+ print(f"'port' not configured for WSServer '{self.name}'."
+ " Using port 80.")
+ self._web_locations = {}
+ self._web_proxies = {}
+ if 'web' in self.conf:
+ for path in self.conf['web']:
+ path_conf = self.conf['web'][path]
+ if 'location' in path_conf:
+ location = path_conf['location']
+ if 'module' in path_conf:
+ # Determine location relative to module directory:
+ module_file = sys.modules[path_conf['module']].__file__
+ module_dir = os.path.dirname(module_file)
+ location = os.path.join(module_dir, 'web', location)
+ else:
+ # Determine location relative to working directory:
+ location = os.path.join(os.getcwd(), location)
+ self._web_locations[path] = os.path.realpath(location)
+ elif 'url' in path_conf:
+ base_url = path_conf['url']
+ if not base_url.endswith('/'):
+ base_url += '/'
+ self._web_proxies[path] = base_url
async def _process_request(self, path: str,
request_headers: Headers) -> Response:
response_headers['Server'] = 'controlpi-wsserver websocket server'
response_headers['Connection'] = 'close'
location = ''
+ url = ''
start_path_length = 0
for start_path in self._web_locations:
if (path.startswith(start_path) and
len(start_path) > start_path_length):
start_path_length = len(start_path)
- location_path = self._web_locations[start_path]
+ start_location = self._web_locations[start_path]
+ if not start_path.endswith('/'):
+ start_path += '/'
+ relative_path = path[len(start_path):]
+ location = os.path.join(start_location, relative_path)
+ for start_path in self._web_proxies:
+ if (path.startswith(start_path) and
+ len(start_path) > start_path_length):
+ start_path_length = len(start_path)
+ base_url = self._web_proxies[start_path]
if not start_path.endswith('/'):
start_path += '/'
relative_path = path[len(start_path):]
- location = os.path.join(location_path, relative_path)
+ url = base_url + relative_path
if location:
if os.path.isdir(location) and not path.endswith('/'):
status = http.HTTPStatus.MOVED_PERMANENTLY
async with aiofiles.open(location, 'rb') as f:
body = await f.read()
response_headers['Content-Length'] = str(len(body))
+ if url:
+ async with aiohttp.ClientSession() as session:
+ async with session.get(url) as resp:
+ status = http.HTTPStatus.OK
+ response_headers['Content-Type'] = \
+ resp.headers['Content-Type']
+ response_headers['Content-Length'] = \
+ resp.headers['Content-Length']
+ body = await resp.read()
if not status:
status = http.HTTPStatus.NOT_FOUND
body = f"'{path}' not found!".encode()
response_headers['Content-Length'] = str(len(body))
return status, response_headers, body
- def process_conf(self) -> None:
- """Get host, port and path settings from configuration."""
- self._host = None
- if 'host' in self.conf:
- self._host = self.conf['host']
- else:
- print(f"'host' not configured for WSServer '{self.name}'."
- " Serving on all interfaces.")
- self._port = 80
- if 'port' in self.conf:
- self._port = self.conf['port']
- else:
- print(f"'port' not configured for WSServer '{self.name}'."
- " Using port 80.")
- self._web_locations = {}
- if 'web' in self.conf:
- for path in self.conf['web']:
- path_conf = self.conf['web'][path]
- location = path_conf['location']
- if 'module' in path_conf:
- # Determine location relative to module directory:
- module_file = sys.modules[path_conf['module']].__file__
- module_dir = os.path.dirname(module_file)
- location = os.path.join(module_dir, 'web', location)
- else:
- # Determine location relative to current working directory:
- location = os.path.join(os.getcwd(), location)
- self._web_locations[path] = os.path.realpath(location)
+ async def _handler(self, websocket: WebSocketServerProtocol,
+ path: str) -> None:
+ connection = Connection(self.bus, websocket)
+ await connection.run()
async def run(self) -> None:
"""Set up websocket server."""