diff --git a/templates/remotecontrol_android.html b/templates/remotecontrol_android.html index 48d1e42..37eed41 100644 --- a/templates/remotecontrol_android.html +++ b/templates/remotecontrol_android.html @@ -399,14 +399,14 @@

增加快捷命令

- 下载截图 + 下载截图 刷新 @@ -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, @@ -546,9 +546,15 @@

增加快捷命令

this.app.finished = true }) }, + 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.deviceUrl}/session/${packageName}`, + url: this.formatAtxAgentUrl(`/session/${packageName}`), method: "post" }).then(ret => { console.log(ret) @@ -593,9 +599,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() @@ -702,7 +707,7 @@

增加快捷命令

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

增加快捷命令

runShell(command) { return $.ajax({ method: "get", - url: this.deviceUrl + "/shell", + url: this.formatAtxAgentUrl("/shell"), data: { "command": command, }, @@ -761,7 +766,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 +863,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 +897,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 +946,7 @@

增加快捷命令

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

增加快捷命令

address() { return this.source.atxAgentAddress }, - deviceUrl() { - return "http://" + this.address + providerHost() { + return this.source.url.split('/')[2].split(':')[0] + }, + providerPort() { + return this.source.url.split(':')[2] + }, + deviceHost() { + return this.address.split(':')[0] + }, + 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}` + }, + 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}` + }, + deviceAtxAgentUrl() { + var host = window.location.host; + return "http://" + host + '/api/v1/atxagent' }, - remoteTerminal() { - return "http://" + this.address + "/term" + deviceProviderUrl() { + var host = window.location.host; + return `http://${host}/api/v1/provider` }, - 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 }, - 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 2502384..c0fb70b 100644 --- a/web/urls.py +++ b/web/urls.py @@ -9,7 +9,8 @@ APIDeviceListHandler, APIDevicePropertiesHandler, APIUserDeviceActiveHandler, APIUserDeviceHandler, AppleDeviceListHandler, DeviceChangesWSHandler, - DeviceItemHandler, DeviceListHandler) + DeviceItemHandler, DeviceListHandler, AndroidProviderProxyHandler, + AndroidDeviceWSProxyHandler, AndroidDeviceAtxAgentProxyHandler) from .views.group import (APIGroupUserListHandler, APIUserGroupListHandler, UserGroupCreateHandler) from .views.provider import ProviderHeartbeatWSHandler @@ -35,6 +36,9 @@ (r"/websocket/devicechanges", DeviceChangesWSHandler), (r"/websocket/heartbeat", ProviderHeartbeatWSHandler), + (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")), # RESP API @@ -47,6 +51,9 @@ (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), + (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 9fed87a..dd7c212 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,102 @@ 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.query_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'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) + 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) + + +AndroidProviderProxyHandler = AndroidDeviceAtxAgentProxyHandler + + +class AndroidDeviceWSProxyHandler(CheckMixin, tornado.websocket.WebSocketHandler): + def __init__(self, *args, **kwargs): + super(AndroidDeviceWSProxyHandler, 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] + if args: + target_url = f'{schema}://{host}:{port}/{args[0]}' + else: + target_url = f'{schema}://{host}:{port}/' + 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)) + + 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()