/
http_server.py
164 lines (142 loc) · 6.46 KB
/
http_server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# -*- coding: utf-8 -*-
"""
Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix)
Copyright (C) 2021 Stefano Gottardo (original implementation module)
HTTP Server for Netflix session, cache, proxy for InputStream Adaptive
SPDX-License-Identifier: MIT
See LICENSES/MIT.md for more information.
"""
import pickle
from http.server import BaseHTTPRequestHandler
from socketserver import TCPServer, ThreadingMixIn
from urllib.parse import urlparse, parse_qs, unquote
from resources.lib.common import IPC_ENDPOINT_CACHE, IPC_ENDPOINT_NFSESSION, IPC_ENDPOINT_MSL, IPC_ENDPOINT_NFSESSION_TEST
from resources.lib.common.exceptions import InvalidPathError, CacheMiss, MetadataNotAvailable, SlotNotImplemented
from resources.lib.globals import G
from resources.lib.services.nfsession.nfsession import NetflixSession
from resources.lib.utils.logging import LOG
class NetflixHttpRequestHandler(BaseHTTPRequestHandler):
"""Handles and translates requests from IPC via HTTP"""
# pylint: disable=invalid-name
def do_HEAD(self):
"""Answers head requests with a success code"""
self.send_response(200)
def do_GET(self):
LOG.debug('HTTP Server: received GET request {}', self.path)
parsed_url = urlparse(self.path)
params = parse_qs(parsed_url.query)
endpoint, func_name = parsed_url.path.rsplit('/', 1)
if endpoint == IPC_ENDPOINT_MSL:
handle_msl_request(self, func_name, None, params)
else:
self.send_error(404, 'Not found')
self.end_headers()
def do_POST(self):
LOG.debug('HTTP Server: received POST request {}', self.path)
parsed_url = urlparse(self.path)
endpoint, func_name = parsed_url.path.rsplit('/', 1)
length = int(self.headers.get('content-length', 0))
data = self.rfile.read(length) or None
if endpoint == IPC_ENDPOINT_MSL:
handle_msl_request(self, func_name, data)
elif endpoint == IPC_ENDPOINT_CACHE:
handle_cache_request(self, func_name, data)
elif endpoint == IPC_ENDPOINT_NFSESSION:
handle_request(self, self.server.netflix_session, func_name, data)
elif endpoint == IPC_ENDPOINT_NFSESSION_TEST and LOG.is_enabled:
handle_request_test(self, self.server.netflix_session, func_name, data)
else:
self.send_error(404, 'Not found')
self.end_headers()
def log_message(self, *args): # pylint: disable=arguments-differ
"""Disable the BaseHTTPServer Log"""
class NFThreadedTCPServer(ThreadingMixIn, TCPServer):
"""Handle each request in a separate thread"""
def __init__(self, server_address):
ThreadingMixIn.__init__(self)
TCPServer.__init__(self, server_address, NetflixHttpRequestHandler)
# Define shared members
self.netflix_session = NetflixSession()
def __del__(self):
if self.netflix_session.nfsession.session:
# Close the connection pool of the session
self.netflix_session.nfsession.session.close()
def handle_msl_request(server, func_name, data, params=None):
if func_name == 'get_license':
# Proxy for InputStream Adaptive to get the licence for the requested video
license_data = server.server.netflix_session.msl_handler.get_license(data)
server.send_response(200)
server.end_headers()
server.wfile.write(license_data)
elif func_name == 'get_manifest':
# Proxy for InputStream Adaptive to get the XML manifest for the requested video
videoid = int(params['videoid'][0])
challenge = server.headers.get('challengeB64', '')
sid = server.headers.get('sessionId', '')
manifest_data = server.server.netflix_session.msl_handler.get_manifest(videoid, unquote(challenge), sid)
server.send_response(200)
server.send_header('Content-type', 'application/dash+xml')
server.end_headers()
server.wfile.write(manifest_data)
else:
handle_request(server, server.server.netflix_session, func_name, data)
def handle_request(server, handler, func_name, data):
server.send_response(200)
server.end_headers()
try:
try:
func = handler.http_ipc_slots[func_name]
except KeyError as exc:
raise SlotNotImplemented(f'The specified IPC slot {func_name} does not exist') from exc
ret_data = _call_func(func, pickle.loads(data))
except Exception as exc: # pylint: disable=broad-except
if not isinstance(exc, (CacheMiss, MetadataNotAvailable)):
LOG.error('IPC callback raised exception: {exc}', exc=exc)
import traceback
LOG.error(traceback.format_exc())
ret_data = exc
if ret_data is not None:
server.wfile.write(pickle.dumps(ret_data, protocol=pickle.HIGHEST_PROTOCOL))
def handle_cache_request(server, func_name, data):
server.send_response(200)
server.end_headers()
try:
ret_data = _call_instance_func(G.CACHE_MANAGEMENT, func_name, pickle.loads(data))
except Exception as exc: # pylint: disable=broad-except
if not isinstance(exc, (CacheMiss, MetadataNotAvailable)):
LOG.error('IPC callback raised exception: {exc}', exc=exc)
import traceback
LOG.error(traceback.format_exc())
ret_data = exc
if ret_data is not None:
server.wfile.write(pickle.dumps(ret_data, protocol=pickle.HIGHEST_PROTOCOL))
def handle_request_test(server, handler, func_name, data):
server.send_response(200)
server.end_headers()
import json
try:
try:
func = handler.http_ipc_slots[func_name]
except KeyError as exc:
raise SlotNotImplemented(f'The specified IPC slot {func_name} does not exist') from exc
ret_data = _call_func(func, json.loads(data))
except Exception as exc: # pylint: disable=broad-except
ret_data = f'The request has failed, error: {exc}'
if ret_data:
server.wfile.write(json.dumps(ret_data).encode('utf-8'))
def _call_instance_func(instance, func_name, data):
try:
func = getattr(instance, func_name)
except AttributeError as exc:
raise InvalidPathError(f'Function {func_name} not found') from exc
if isinstance(data, dict):
return func(**data)
if data is not None:
return func(data)
return func()
def _call_func(func, data):
if isinstance(data, dict):
return func(**data)
if data is not None:
return func(data)
return func()