From 6e4981e8b3ae57de825653a49bef4a3da8497aae Mon Sep 17 00:00:00 2001 From: YAO WEI Date: Mon, 27 Mar 2023 18:18:37 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E8=BD=AC=E5=8F=91=EF=BC=8C=E9=81=BF=E5=85=8D=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=B5=AE=E5=8A=A8=E7=AB=AF=E5=8F=A3=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/remotecontrol_android.html | 50 ++++++++++----- web/urls.py | 6 +- web/views/device.py | 95 ++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 16 deletions(-) diff --git a/templates/remotecontrol_android.html b/templates/remotecontrol_android.html index 48d1e42..2a2c4dd 100644 --- a/templates/remotecontrol_android.html +++ b/templates/remotecontrol_android.html @@ -399,14 +399,14 @@

增加快捷命令

- 下载截图 + 下载截图 刷新 @@ -546,9 +546,12 @@

增加快捷命令

this.app.finished = true }) }, + formatAtxAgentUrl(path) { + return `${this.deviceAtxAgentUrl}${path}?host=${this.deviceHost}&port=${this.devicePort}` + }, appLaunch(packageName) { $.ajax({ - url: `${this.deviceUrl}/session/${packageName}`, + url: this.formatAtxAgentUrl(`/session/${packageName}`), method: "post" }).then(ret => { console.log(ret) @@ -702,7 +705,7 @@

增加快捷命令

// }) $.ajax({ method: "get", - url: this.deviceUrl + "/info/rotation" + url: this.formatAtxAgentUrl("/info/rotation") }).done(ret => { this.$notify({ message: "Rotation updated", @@ -730,7 +733,7 @@

增加快捷命令

runShell(command) { return $.ajax({ method: "get", - url: this.deviceUrl + "/shell", + url: this.formatAtxAgentUrl("/shell"), data: { "command": command, }, @@ -761,7 +764,7 @@

增加快捷命令

}, loadTerminal() { let term; - let ws = new WebSocket("ws://" + this.address + "/term"); + let ws = new WebSocket(this.deviceAtxAgentTermWSUrl); ws.binaryType = "arraybuffer" function ab2str(buf) { @@ -858,7 +861,7 @@

增加快捷命令

versionName: "", versionCode: "", } - $.getJSON(`${this.deviceUrl}/packages/${packageName}/info`).then(ret => { + $.getJSON(this.formatAtxAgentUrl(`/packages/${packageName}/info`)).then(ret => { if (ret.success && ret.data) { item.label = ret.data.label item.versionName = ret.data.versionName @@ -892,7 +895,7 @@

增加快捷命令

this.closeSyncTouchpad() }, mirrorDisplay() { - let ws = new WebSocket(this.deviceUrl.replace(/^http/, "ws") + '/minicap'); + let ws = new WebSocket(`${this.deviceAtxAgentCapWSUrl}`); this.websockets.screen = ws; ws.onopen = (ev) => { @@ -941,7 +944,7 @@

增加快捷命令

bounds: {} } - let ws = new WebSocket("ws://" + this.address + "/minitouch") + let ws = new WebSocket(this.deviceAtxAgentTouchWSUrl) this.websockets.touchpad = ws ws.onopen = (ret) => { @@ -1445,14 +1448,31 @@

增加快捷命令

address() { return this.source.atxAgentAddress }, - deviceUrl() { - return "http://" + this.address + deviceHost() { + return this.address.split(':')[0] + }, + devicePort() { + return this.address.split(':')[1] + }, + deviceAtxAgentCapWSUrl() { + var host = window.location.host; + return `ws://${host}/websocket/atxagent/minicap?host=${this.deviceHost}&port=${this.devicePort}` + }, + deviceAtxAgentTouchWSUrl() { + var host = window.location.host; + return `ws://${host}/websocket/atxagent/minitouch?host=${this.deviceHost}&port=${this.devicePort}` + }, + deviceAtxAgentTermWSUrl() { + var host = window.location.host; + return `ws://${host}/websocket/atxagent/term?host=${this.deviceHost}&port=${this.devicePort}` }, - remoteTerminal() { - return "http://" + this.address + "/term" + deviceAtxAgentUrl() { + var host = window.location.host; + return "http://" + host + '/api/v1/atxagent' }, - screenshotUrl() { - return "http://" + this.address + "/screenshot/0" + screenshotAtxAgentUrl() { + var host = window.location.host; + return `http://${host}/api/v1/atxagent/screenshot/0?host=${this.deviceHost}&port=${this.devicePort}` }, remoteConnectAddr() { return "adb connect " + this.source.remoteConnectAddress diff --git a/web/urls.py b/web/urls.py index 2502384..61734f7 100644 --- a/web/urls.py +++ b/web/urls.py @@ -9,7 +9,8 @@ APIDeviceListHandler, APIDevicePropertiesHandler, APIUserDeviceActiveHandler, APIUserDeviceHandler, AppleDeviceListHandler, DeviceChangesWSHandler, - DeviceItemHandler, DeviceListHandler) + DeviceItemHandler, DeviceListHandler, + DeviceAtxAgentWSHandler, AndroidDeviceAtxAgentProxyHandler) from .views.group import (APIGroupUserListHandler, APIUserGroupListHandler, UserGroupCreateHandler) from .views.provider import ProviderHeartbeatWSHandler @@ -35,6 +36,8 @@ (r"/websocket/devicechanges", DeviceChangesWSHandler), (r"/websocket/heartbeat", ProviderHeartbeatWSHandler), + (r"/websocket/atxagent/(minicap|term|minitouch)", DeviceAtxAgentWSHandler), + # For compability of atx-server-1 (r"/list", make_redirect_handler("/api/v1/devices")), # RESP API @@ -47,6 +50,7 @@ (r"/api/v1/user/devices/([^/]+)/active", APIUserDeviceActiveHandler), # GET (r"/api/v1/user/settings", APIUserSettingsHandler), # GET, PUT (r"/api/v1/admins", APIAdminListHandler), # GET, POST + (r"/api/v1/atxagent/(.*)", AndroidDeviceAtxAgentProxyHandler), ## Group API # (r"/api/v1/user/groups/([^/]+)", APIUserGroupHandler), # GET, POST, DELETE TODO(ssx) (r"/api/v1/user/groups", APIUserGroupListHandler), # GET, POST diff --git a/web/views/device.py b/web/views/device.py index 9fed87a..346932a 100644 --- a/web/views/device.py +++ b/web/views/device.py @@ -6,12 +6,15 @@ import urllib from functools import wraps from typing import Union +from six.moves import http_cookies as Cookie +from urllib.parse import urlencode import rethinkdb as rdb import tornado.websocket from logzero import logger from rethinkdb import r from tornado import gen +from tornado.platform.asyncio import to_asyncio_future from tornado.httpclient import AsyncHTTPClient, HTTPRequest from tornado.ioloop import IOLoop from tornado.web import HTTPError, authenticated @@ -523,3 +526,95 @@ async def open(self, udid): return self.write_message("device is yours") + + +class CheckMixin(object): + def check_host_port(self): + host = self.get_argument('host', None) + port = self.get_argument('port', None) + if not host or not port: + hcookie = self.request.headers.get('cookie') + if hcookie: + cookie = Cookie.SimpleCookie() + for hcookie_part in hcookie.split(';'): + hcookie_part = hcookie_part.lstrip() + try: + cookie.load(hcookie_part) + except Cookie.CookieError: + logger.warning('Found malformed cookie') + else: + if 'host' in cookie: + host = cookie['host'].value + if 'port' in cookie: + port = cookie['port'].value + return host, port + + +class AndroidDeviceAtxAgentProxyHandler(CheckMixin, AuthRequestHandler): + """ device atx agent proxy """ + async def do_proxy(self, method, *args, **kwargs): + host, port = self.check_host_port() + if not host or not port: + self.set_status(400) # bad request + self.write_json({ + "success": False, + "description": "Missing host and port" + }) + arguments = self.request.arguments # 内部是List[bytes]格式 + arguments.pop('host', None) + arguments.pop('port', None) + params = {name: self.get_argument(name) for name in arguments} + try: + if params: + url = f'http://{host}:{port}/{args[0]}?{urlencode(params)}' + else: + url = f'http://{host}:{port}/{args[0]}' + logger.info(f'Proxy {self.request.uri} <--> {url}') + headers = self.request.headers + response = await tornado.httpclient.AsyncHTTPClient().fetch( + url, method=method, headers=headers, body=self.request.body, allow_nonstandard_methods=True, **kwargs) + self.set_cookie("host", host) + self.set_cookie("port", port) + self.write(response.body) + except Exception as e: + print(e) + self.write_error(500) + + async def get(self, *args, **kwargs): + await self.do_proxy('GET', *args, **kwargs) + + async def post(self, *args, **kwargs): + await self.do_proxy('POST', *args, **kwargs) + + +class DeviceAtxAgentWSHandler(CheckMixin, tornado.websocket.WebSocketHandler): + def __init__(self, *args, **kwargs): + super(DeviceAtxAgentWSHandler, self).__init__(*args, **kwargs) + self._client = None + + def check_origin(self, origin): + return True + + async def open(self, *args, **kwargs): + host, port = self.check_host_port() + if not host or not port: + self.write_message("Missing host and port") + self.close() + return + + schema = {"http":"ws", "https":"wss"}[self.request.protocol] + target_url = f'{schema}://{host}:{port}/{args[0]}' + self._client = await to_asyncio_future( + tornado.websocket.websocket_connect(target_url, on_message_callback=self.on_target_message)) + + def on_message(self, message): + if self._client is not None: + self._client.write_message(message, binary=isinstance(message, bytes)) + + def on_target_message(self, message): + if message: + self.write_message(message, binary=isinstance(message, bytes)) + + def on_close(self): + if self._client is not None: + self._client.close() From e9f4d1ef0f632f5ccc3883212cea28ade1571cc3 Mon Sep 17 00:00:00 2001 From: YAO WEI Date: Tue, 28 Mar 2023 10:30:27 +0800 Subject: [PATCH 2/3] =?UTF-8?q?whatsinput=E7=9A=84websocket=E8=BD=AC?= =?UTF-8?q?=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/remotecontrol_android.html | 11 +++++++---- web/urls.py | 5 +++-- web/views/device.py | 12 ++++++++---- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/templates/remotecontrol_android.html b/templates/remotecontrol_android.html index 2a2c4dd..6b1b994 100644 --- a/templates/remotecontrol_android.html +++ b/templates/remotecontrol_android.html @@ -596,9 +596,8 @@

增加快捷命令

}) }, loadWhatsinput(callback) { - console.log(this.whatsInputUrl) let defer = $.Deferred() - let ws = new WebSocket(this.whatsInputUrl) + let ws = new WebSocket(this.whatsInputWSUrl) this.websockets.winput = ws; ws.onopen = (ev) => { defer.resolve() @@ -1454,6 +1453,9 @@

增加快捷命令

devicePort() { return this.address.split(':')[1] }, + deviceWhatsInputPort() { + return this.source.whatsInputAddress.split(':')[1] + }, deviceAtxAgentCapWSUrl() { var host = window.location.host; return `ws://${host}/websocket/atxagent/minicap?host=${this.deviceHost}&port=${this.devicePort}` @@ -1477,8 +1479,9 @@

增加快捷命令

remoteConnectAddr() { return "adb connect " + this.source.remoteConnectAddress }, - whatsInputUrl() { - return "ws://" + this.source.whatsInputAddress + whatsInputWSUrl() { + var host = window.location.host; + return `ws://${host}/websocket/whatsinput?host=${this.deviceHost}&port=${this.deviceWhatsInputPort}` }, displayLinked() { return this.websockets.screen !== null; diff --git a/web/urls.py b/web/urls.py index 61734f7..8e29a06 100644 --- a/web/urls.py +++ b/web/urls.py @@ -10,7 +10,7 @@ APIUserDeviceActiveHandler, APIUserDeviceHandler, AppleDeviceListHandler, DeviceChangesWSHandler, DeviceItemHandler, DeviceListHandler, - DeviceAtxAgentWSHandler, AndroidDeviceAtxAgentProxyHandler) + AndroidDeviceWSProxyHandler, AndroidDeviceAtxAgentProxyHandler) from .views.group import (APIGroupUserListHandler, APIUserGroupListHandler, UserGroupCreateHandler) from .views.provider import ProviderHeartbeatWSHandler @@ -36,7 +36,8 @@ (r"/websocket/devicechanges", DeviceChangesWSHandler), (r"/websocket/heartbeat", ProviderHeartbeatWSHandler), - (r"/websocket/atxagent/(minicap|term|minitouch)", DeviceAtxAgentWSHandler), + (r"/websocket/atxagent/(minicap|term|minitouch)", AndroidDeviceWSProxyHandler), + (r"/websocket/whatsinput", AndroidDeviceWSProxyHandler), # For compability of atx-server-1 (r"/list", make_redirect_handler("/api/v1/devices")), diff --git a/web/views/device.py b/web/views/device.py index 346932a..1b73b06 100644 --- a/web/views/device.py +++ b/web/views/device.py @@ -569,7 +569,7 @@ async def do_proxy(self, method, *args, **kwargs): url = f'http://{host}:{port}/{args[0]}?{urlencode(params)}' else: url = f'http://{host}:{port}/{args[0]}' - logger.info(f'Proxy {self.request.uri} <--> {url}') + logger.info(f'Proxy http from {self.request.uri} <--> {url}') headers = self.request.headers response = await tornado.httpclient.AsyncHTTPClient().fetch( url, method=method, headers=headers, body=self.request.body, allow_nonstandard_methods=True, **kwargs) @@ -587,9 +587,9 @@ async def post(self, *args, **kwargs): await self.do_proxy('POST', *args, **kwargs) -class DeviceAtxAgentWSHandler(CheckMixin, tornado.websocket.WebSocketHandler): +class AndroidDeviceWSProxyHandler(CheckMixin, tornado.websocket.WebSocketHandler): def __init__(self, *args, **kwargs): - super(DeviceAtxAgentWSHandler, self).__init__(*args, **kwargs) + super(AndroidDeviceWSProxyHandler, self).__init__(*args, **kwargs) self._client = None def check_origin(self, origin): @@ -603,7 +603,11 @@ async def open(self, *args, **kwargs): return schema = {"http":"ws", "https":"wss"}[self.request.protocol] - target_url = f'{schema}://{host}:{port}/{args[0]}' + if args: + target_url = f'{schema}://{host}:{port}/{args[0]}' + else: + target_url = f'{schema}://{host}:{port}/' + logger.info(f'Proxy websocket from {self.request.uri} <--> {target_url}') self._client = await to_asyncio_future( tornado.websocket.websocket_connect(target_url, on_message_callback=self.on_target_message)) From 9b11cc0ddc764bff3ccac550579bcaf23c48ebba Mon Sep 17 00:00:00 2001 From: YAO WEI Date: Tue, 28 Mar 2023 15:14:34 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dapk=E5=AE=89=E8=A3=85?= =?UTF-8?q?=E7=9A=84url=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/remotecontrol_android.html | 15 ++++++++++++++- web/urls.py | 4 +++- web/views/device.py | 9 ++++++--- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/templates/remotecontrol_android.html b/templates/remotecontrol_android.html index 6b1b994..37eed41 100644 --- a/templates/remotecontrol_android.html +++ b/templates/remotecontrol_android.html @@ -527,7 +527,7 @@

增加快捷命令

$.ajax({ method: "post", - url: this.source.url + "/app/install?udid=" + this.udid, + url: this.formatProviderUrl('/app/install') + "&udid=" + this.udid, data: { url: this.app.installUrl, launch: this.app.launch, @@ -549,6 +549,9 @@

增加快捷命令

formatAtxAgentUrl(path) { return `${this.deviceAtxAgentUrl}${path}?host=${this.deviceHost}&port=${this.devicePort}` }, + formatProviderUrl(path) { + return `${this.deviceProviderUrl}${path}?host=${this.providerHost}&port=${this.providerPort}` + }, appLaunch(packageName) { $.ajax({ url: this.formatAtxAgentUrl(`/session/${packageName}`), @@ -1447,6 +1450,12 @@

增加快捷命令

address() { return this.source.atxAgentAddress }, + providerHost() { + return this.source.url.split('/')[2].split(':')[0] + }, + providerPort() { + return this.source.url.split(':')[2] + }, deviceHost() { return this.address.split(':')[0] }, @@ -1472,6 +1481,10 @@

增加快捷命令

var host = window.location.host; return "http://" + host + '/api/v1/atxagent' }, + deviceProviderUrl() { + var host = window.location.host; + return `http://${host}/api/v1/provider` + }, screenshotAtxAgentUrl() { var host = window.location.host; return `http://${host}/api/v1/atxagent/screenshot/0?host=${this.deviceHost}&port=${this.devicePort}` diff --git a/web/urls.py b/web/urls.py index 8e29a06..c0fb70b 100644 --- a/web/urls.py +++ b/web/urls.py @@ -9,7 +9,7 @@ APIDeviceListHandler, APIDevicePropertiesHandler, APIUserDeviceActiveHandler, APIUserDeviceHandler, AppleDeviceListHandler, DeviceChangesWSHandler, - DeviceItemHandler, DeviceListHandler, + DeviceItemHandler, DeviceListHandler, AndroidProviderProxyHandler, AndroidDeviceWSProxyHandler, AndroidDeviceAtxAgentProxyHandler) from .views.group import (APIGroupUserListHandler, APIUserGroupListHandler, UserGroupCreateHandler) @@ -52,6 +52,8 @@ (r"/api/v1/user/settings", APIUserSettingsHandler), # GET, PUT (r"/api/v1/admins", APIAdminListHandler), # GET, POST (r"/api/v1/atxagent/(.*)", AndroidDeviceAtxAgentProxyHandler), + (r"/api/v1/provider/(app/install|cold)", AndroidProviderProxyHandler), + ## Group API # (r"/api/v1/user/groups/([^/]+)", APIUserGroupHandler), # GET, POST, DELETE TODO(ssx) (r"/api/v1/user/groups", APIUserGroupListHandler), # GET, POST diff --git a/web/views/device.py b/web/views/device.py index 1b73b06..dd7c212 100644 --- a/web/views/device.py +++ b/web/views/device.py @@ -560,7 +560,7 @@ async def do_proxy(self, method, *args, **kwargs): "success": False, "description": "Missing host and port" }) - arguments = self.request.arguments # 内部是List[bytes]格式 + arguments = self.request.query_arguments # 内部是List[bytes]格式 arguments.pop('host', None) arguments.pop('port', None) params = {name: self.get_argument(name) for name in arguments} @@ -569,7 +569,7 @@ async def do_proxy(self, method, *args, **kwargs): url = f'http://{host}:{port}/{args[0]}?{urlencode(params)}' else: url = f'http://{host}:{port}/{args[0]}' - logger.info(f'Proxy http from {self.request.uri} <--> {url}') + logger.info(f'Forward http {method} from {self.request.uri} <--> {url}') headers = self.request.headers response = await tornado.httpclient.AsyncHTTPClient().fetch( url, method=method, headers=headers, body=self.request.body, allow_nonstandard_methods=True, **kwargs) @@ -587,6 +587,9 @@ async def post(self, *args, **kwargs): await self.do_proxy('POST', *args, **kwargs) +AndroidProviderProxyHandler = AndroidDeviceAtxAgentProxyHandler + + class AndroidDeviceWSProxyHandler(CheckMixin, tornado.websocket.WebSocketHandler): def __init__(self, *args, **kwargs): super(AndroidDeviceWSProxyHandler, self).__init__(*args, **kwargs) @@ -607,7 +610,7 @@ async def open(self, *args, **kwargs): target_url = f'{schema}://{host}:{port}/{args[0]}' else: target_url = f'{schema}://{host}:{port}/' - logger.info(f'Proxy websocket from {self.request.uri} <--> {target_url}') + logger.info(f'Forward websocket from {self.request.uri} <--> {target_url}') self._client = await to_asyncio_future( tornado.websocket.websocket_connect(target_url, on_message_callback=self.on_target_message))