| // Copyright 2015 Google. All rights reserved. Use of this source code is |
| // governed by a BSD-style license that can be found in the LICENSE file. |
| |
| library wip.multiplex; |
| |
| import 'dart:async' show Future; |
| import 'dart:convert' show jsonEncode; |
| import 'dart:io' show HttpServer, InternetAddress; |
| |
| import 'package:logging/logging.dart' show Logger; |
| import 'package:shelf/shelf.dart' as shelf; |
| import 'package:shelf/shelf_io.dart' as io; |
| import 'package:shelf_web_socket/shelf_web_socket.dart' as ws; |
| import 'package:webkit_inspection_protocol/dom_model.dart' show WipDomModel; |
| import 'package:webkit_inspection_protocol/forwarder.dart' show WipForwarder; |
| import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart' |
| show ChromeConnection, ChromeTab, WipConnection; |
| |
| class Server { |
| static final _log = new Logger('Server'); |
| |
| Future<HttpServer> _server; |
| final ChromeConnection chrome; |
| final int port; |
| final bool modelDom; |
| |
| final _connections = <String, Future<WipConnection>>{}; |
| final _modelDoms = <String, WipDomModel>{}; |
| |
| Server(this.port, this.chrome, {this.modelDom}) { |
| _server = io.serve(_handler, InternetAddress.anyIPv4, port); |
| } |
| |
| shelf.Handler get _handler => const shelf.Pipeline() |
| .addMiddleware(shelf.logRequests(logger: _shelfLogger)) |
| .addHandler(new shelf.Cascade() |
| .add(_webSocket) |
| .add(_mainPage) |
| .add(_json) |
| .add(_forward) |
| .handler); |
| |
| void _shelfLogger(String msg, bool isError) { |
| if (isError) { |
| _log.severe(msg); |
| } else { |
| _log.info(msg); |
| } |
| } |
| |
| Future<shelf.Response> _mainPage(shelf.Request request) async { |
| var path = request.url.pathSegments; |
| if (path.isEmpty) { |
| var resp = await _mainPageHtml(); |
| _log.info('mainPage: $resp'); |
| return new shelf.Response.ok(resp, |
| headers: {'Content-Type': 'text/html'}); |
| } |
| return new shelf.Response.notFound(null); |
| } |
| |
| Future<String> _mainPageHtml() async { |
| var html = new StringBuffer(r'''<!DOCTYPE html> |
| <html> |
| <head> |
| <title>Chrome Windows</title> |
| </head> |
| <body> |
| <table> |
| <thead> |
| <tr><td>Title</td><td>Description</td></tr> |
| </thead> |
| <tbody>'''); |
| |
| for (var tab in await chrome.getTabs()) { |
| html |
| ..write('<tr><td><a href="/devtools/inspector.html?ws=localhost:') |
| ..write(port) |
| ..write('/devtools/page/') |
| ..write(tab.id) |
| ..write('">'); |
| if (tab.title != null && tab.title.isNotEmpty) { |
| html.write(tab.title); |
| } else { |
| html.write(tab.url); |
| } |
| html..write('</a></td><td>')..write(tab.description)..write('</td></tr>'); |
| } |
| html.write(r'''</tbody> |
| </table> |
| </body> |
| </html>'''); |
| return html.toString(); |
| } |
| |
| Future<shelf.Response> _json(shelf.Request request) async { |
| var path = request.url.pathSegments; |
| if (path.length == 1 && path[0] == 'json') { |
| var resp = jsonEncode(await chrome.getTabs(), toEncodable: _jsonEncode); |
| _log.info('json: $resp'); |
| return new shelf.Response.ok(resp, |
| headers: {'Content-Type': 'application/json'}); |
| } |
| return new shelf.Response.notFound(null); |
| } |
| |
| Future<shelf.Response> _forward(shelf.Request request) async { |
| _log.info('forwarding: ${request.url}'); |
| var dtResp = await chrome.getUrl(request.url.path); |
| |
| if (dtResp.statusCode == 200) { |
| return new shelf.Response.ok(dtResp, |
| headers: {'Content-Type': dtResp.headers.contentType.toString()}); |
| } |
| _log.warning( |
| 'Forwarded ${request.url} returned statusCode: ${dtResp.statusCode}'); |
| return new shelf.Response.notFound(null); |
| } |
| |
| Future<shelf.Response> _webSocket(shelf.Request request) async { |
| var path = request.url.pathSegments; |
| if (path.length != 3 || path[0] != 'devtools' || path[1] != 'page') { |
| return new shelf.Response.notFound(null); |
| } |
| _log.info('connecting to websocket: ${request.url}'); |
| |
| return ws.webSocketHandler((ws) async { |
| var debugger = await _connections.putIfAbsent(path[2], () async { |
| var tab = await chrome.getTab((tab) => tab.id == path[2]); |
| return WipConnection.connect(tab.webSocketDebuggerUrl); |
| }); |
| var dom; |
| if (modelDom) { |
| dom = await _modelDoms.putIfAbsent(path[2], () { |
| return new WipDomModel(debugger.dom); |
| }); |
| } |
| var forwarder = |
| new WipForwarder(debugger, ws.stream, sink: ws.sink, domModel: dom); |
| debugger.onClose.listen((_) { |
| _connections.remove(path[2]); |
| _modelDoms.remove(path[2]); |
| forwarder.stop(); |
| }); |
| })(request); |
| } |
| |
| Future close() async { |
| if (_server != null) { |
| await (await _server).close(force: true); |
| _server = null; |
| } |
| } |
| |
| _jsonEncode(obj) { |
| if (obj is ChromeTab) { |
| var json = <String, dynamic>{ |
| 'description': obj.description, |
| 'devtoolsFrontendUrl': '/devtools/inspector.html' |
| '?ws=localhost:$port/devtools/page/${obj.id}', |
| 'id': obj.id, |
| 'title': obj.title, |
| 'type': obj.type, |
| 'url': obj.url, |
| 'webSocketDebuggerUrl': 'ws://localhost:$port/devtools/page/${obj.id}' |
| }; |
| if (obj.faviconUrl != null) { |
| json['faviconUrl'] = obj.faviconUrl; |
| } |
| return json; |
| } else if (obj is Uri) { |
| return obj.toString(); |
| } else if (obj is Iterable) { |
| return obj.toList(); |
| } else { |
| return obj; |
| } |
| } |
| } |