Skip to content

Commit 324ffef

Browse files
CodexHerett2468
authored andcommitted
This fixes #16
* Slight modification to Dockerfile to include `requirements.txt`, as well as modify copy/`pip` locations to reduce reinstalling on every change/build. Speeds up dev cycle a bit. * Updated `requirements.txt` to include `aiohttp_cors` * Server port updated from 4445 -> 4456 to keep in vain of ws+1 * `config.ini` Changes: * Changed port 4444 -> 4455 to match obs-websocket v5 default * Added `cors_domains`, which is a comma-separated list * `docker-compose.yml` Changes: * Moved comments as they were getting long * Updated values to match new `config.ini` changes * Added `extra_hosts` section to properly route DNS to local Host IP, allowing easier connectivity * `entrypoint.sh` Changes: * Updated to add and sed replace `cors_domains` * `main.py` Changes: * Added Parser configs for `cors_domains`, defaulting to wildcard (`*`) * Added some logging to show CORS domains accepted * Added some logging to show bindAddress and port for user's sake * ***Re-added loop***... I know this was removed, but after looking at how aiohttp works, it will create it's own event loop and mess everything up if not included. Since the main event loop is being used to run `init`, it's necessary to inject into `web.run_app` to use the same event loop or all requests to obs will timeout. Maybe there's a better way to do this, but I am not a Python dev to know better. * Added method to iterate over supplied CORS Domains and add the routes as accepted from said domains. You can test this from a codepen example I built getting the OBS version: https://codepen.io/CodexHere/full/ZEqYLVX To validate, simply set `cors_domains` to any invalid value, such as "none" and restart the server.
1 parent 7f0b5cd commit 324ffef

File tree

7 files changed

+71
-20
lines changed

7 files changed

+71
-20
lines changed

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
From python:3
2-
COPY main.py entrypoint.sh ./
2+
COPY entrypoint.sh requirements.txt ./
33
RUN pip install -r requirements.txt
4+
COPY main.py ./
45
ENTRYPOINT [ "sh", "./entrypoint.sh" ]

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,14 @@ A request type is always required, however the request body is optional, and is
3434
For a list of request types, refer to the [obs-websocket protocol docs](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#requests)
3535

3636
## Example cURL commands:
37-
- `curl -XPOST -H 'Authorization: pp123' -H "Content-type: application/json" -d '{"sceneName": "Scene 5"}' 'http://127.0.0.1:4445/emit/SetCurrentProgramScene'`
38-
- `curl -XPOST -H "Content-type: application/json" 'http://127.0.0.1:4445/call/GetCurrentProgramScene'`
37+
- `curl -XPOST -H 'Authorization: pp123' -H "Content-type: application/json" -d '{"sceneName": "Scene 5"}' 'http://127.0.0.1:4456/emit/SetCurrentProgramScene'`
38+
- `curl -XPOST -H "Content-type: application/json" 'http://127.0.0.1:4456/call/GetCurrentProgramScene'`
39+
`
40+
## CORS Errors/Setup
41+
42+
If you receive errors about CORS and not having `Access-Control-Allow-Origin` header for the endpoint, be sure to modify `config.ini` or `docker-compose.yml` to properly set `cors_domains`.
43+
44+
> A domain of `*` will allow *ALL* domains privileges to make successful requests, so use it at your own risk. Either configure appropriately, or use an Auth key for the HTTP configuration.
3945
4046
## IRLTookit Links
4147

config.ini

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
[http]
2-
bind_to_address = 0.0.0.0
3-
bind_to_port = 4445
2+
bind_to_address=0.0.0.0
3+
bind_to_port=4456
4+
cors_domains=*
45
#Leave empty if no authentication is required.
5-
authentication_key =
6+
authentication_key=
67

78
[obsws]
8-
ws_url = ws://127.0.0.1:4444
9+
#obs-websocket v5 uses port 4455 by default
10+
ws_url=ws://127.0.0.1:4455
911
#Only necessary if "Enable authentication" is checked in the obs-websocket settings menu.
10-
ws_password =
12+
ws_password =

docker-compose.yml

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,22 @@ services:
55
build: .
66
restart: unless-stopped
77
environment:
8-
- API_ADDRESS=0.0.0.0 # Address of this machine
9-
- API_PORT=4445 # Port you wish to use for API
10-
- API_KEY= # Auth key you wish to set
11-
- OBS_URL=ws://127.0.0.1:4444 # WebSocket Connect URL
12-
- OBS_PASSWORD= # OBS password, if used
8+
# Address of this machine
9+
- API_ADDRESS=0.0.0.0
10+
# Port you wish to use for API
11+
- API_PORT=4456
12+
# Auth key you wish to set
13+
- API_KEY=
14+
# WebSocket Connect URL
15+
- OBS_URL=ws://host.docker.internal:4455
16+
# OBS password, if used
17+
- OBS_PASSWORD=
18+
# CORS Domains to accept requests from (comma separated, no spaces. *=all domains)
19+
- CORS_DOMAINS=*
1320
ports:
14-
- "4445:4445" # Set to same value as API_PORT
21+
# Set to same value as API_PORT
22+
- '4456:4456'
23+
extra_hosts:
24+
# Allows routing from docker container to Host OS (see: https://stackoverflow.com/a/43541681)
25+
- 'host.docker.internal:host-gateway'
26+

entrypoint.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ cat << EOF > ./config.ini
55
[http]
66
bind_to_address = @api_address@
77
bind_to_port = @api_port@
8+
cors_domains = @cors_domains@
89
authentication_key = @api_key@
910
1011
[obsws]
@@ -14,6 +15,7 @@ EOF
1415
sed -i "s|@api_address@|${API_ADDRESS}|" ./config.ini
1516
sed -i "s|@api_port@|${API_PORT}|" ./config.ini
1617
sed -i "s|@api_key@|${API_KEY}|" ./config.ini
18+
sed -i "s|@cors_domains@|${CORS_DOMAINS}|" ./config.ini
1719

1820
sed -i "s|@obs_url@|${OBS_URL}|" ./config.ini
1921
sed -i "s|@obs_password@|${OBS_PASSWORD}|" ./config.ini

main.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
import simpleobsws
88
import aiohttp
99
from aiohttp import web
10+
import aiohttp_cors
1011
from configparser import ConfigParser
1112

12-
loop = asyncio.get_event_loop()
1313
app = web.Application()
1414
ws = None
1515

@@ -79,7 +79,8 @@ async def emit_request_callback(request):
7979
return await request_callback(request, True)
8080

8181
async def init():
82-
logging.info('Connecting to obs-websocket...')
82+
logging.info('Connecting to obs-websocket: {}'.format(wsUrl))
83+
8384
try:
8485
await ws.connect()
8586
except ConnectionRefusedError:
@@ -100,22 +101,38 @@ async def shutdown(app):
100101
else:
101102
logging.info('Not connected to obs-websocket, not disconnecting.')
102103

104+
def setup_cors(corsDomains):
105+
cors_settings = {
106+
"allow_credentials": True,
107+
"expose_headers": "*",
108+
"allow_headers": "*"
109+
}
110+
111+
resource_options = aiohttp_cors.ResourceOptions(**cors_settings)
112+
defaults = {domain: resource_options for domain in corsDomains}
113+
cors = aiohttp_cors.setup(app, defaults=defaults)
114+
115+
for route in list(app.router.routes()):
116+
cors.add(route)
117+
103118
if __name__ == '__main__':
104119
config = ConfigParser()
105120
config.read('config.ini')
106121

107122
# Command line args take priority, with fallback to config.ini, and further fallback to defaults.
108123
parser = argparse.ArgumentParser(description='A Python-based program that provides HTTP endpoints for obs-websocket')
109124
parser.add_argument('--http_bind_addres', dest='http_bind_addres', default=config.get('http', 'bind_to_address', fallback='0.0.0.0'))
110-
parser.add_argument('--http_bind_port', dest='http_bind_port', type=int, default=config.getint('http', 'bind_to_port', fallback=4445))
125+
parser.add_argument('--http_bind_port', dest='http_bind_port', type=int, default=config.getint('http', 'bind_to_port', fallback=4456))
126+
parser.add_argument('--cors_domains', dest='cors_domains', default=config.get('http', 'cors_domains', fallback='*'))
111127
parser.add_argument('--http_auth_key', dest='http_auth_key', default=config.get('http', 'authentication_key', fallback=''))
112-
parser.add_argument('--ws_url', dest='ws_url', default=config.get('obsws', 'ws_url', fallback='ws://127.0.0.1:4444'))
128+
parser.add_argument('--ws_url', dest='ws_url', default=config.get('obsws', 'ws_url', fallback='ws://127.0.0.1:4455'))
113129
parser.add_argument('--ws_password', dest='ws_password', default=config.get('obsws', 'ws_password', fallback=''))
114130
args = parser.parse_args()
115131

116132
httpAddress = args.http_bind_addres
117133
httpPort = args.http_bind_port
118134
httpAuthKey = args.http_auth_key
135+
corsDomains = args.cors_domains.split(',')
119136
wsUrl = args.ws_url
120137
wsPassword = args.ws_password
121138

@@ -125,12 +142,22 @@ async def shutdown(app):
125142
logging.info('HTTP server will start without authentication.')
126143
httpAuthKey = None
127144

145+
logging.info('CORS Domains Accepted: {}'.format(", ".join(corsDomains)))
146+
logging.info('HTTP Server Running: {}:{}'.format(httpAddress, httpPort))
147+
128148
ws = simpleobsws.WebSocketClient(url=wsUrl, password=wsPassword)
129149

150+
loop = asyncio.get_event_loop()
130151
if not loop.run_until_complete(init()):
131152
os._exit(1)
132153

133-
app.add_routes([web.post('/call/{requestType}', call_request_callback), web.post('/emit/{requestType}', emit_request_callback)])
154+
app.add_routes([
155+
web.post('/call/{requestType}', call_request_callback),
156+
web.post('/emit/{requestType}', emit_request_callback)
157+
])
158+
134159
app.on_cleanup.append(shutdown)
135160

136-
web.run_app(app, host=httpAddress, port=httpPort)
161+
setup_cors(corsDomains)
162+
163+
web.run_app(app, host=httpAddress, port=httpPort, loop=loop)

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
simpleobsws>=1.3.1
22
aiohttp
33
configparser
4+
aiohttp_cors

0 commit comments

Comments
 (0)