Merge pull request #15 from DrMarcII/master
Change to allow multiplex code to be used as a library.
diff --git a/bin/multiplex.dart b/bin/multiplex.dart
index 2ea011c..5c3dcc5 100644
--- a/bin/multiplex.dart
+++ b/bin/multiplex.dart
@@ -3,20 +3,14 @@
library wip.multiplex;
-import 'dart:async' show Future;
-import 'dart:convert' show JSON;
-import 'dart:io' show HttpClientResponse, HttpServer, InternetAddress, stderr;
+import 'dart:io' show stderr;
import 'package:args/args.dart' show ArgParser;
import 'package:logging/logging.dart'
show hierarchicalLoggingEnabled, Level, Logger, LogRecord;
-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/multiplex.dart' show Server;
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
- show ChromeConnection, ChromeTab, WipConnection;
+ show ChromeConnection;
main(List<String> argv) async {
var args = (new ArgParser()
@@ -42,164 +36,3 @@
new ChromeConnection(args['chrome_host'], int.parse(args['chrome_port']));
new Server(int.parse(args['listen_port']), cr, modelDom: args['model_dom']);
}
-
-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.ANY_IP_V4, 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/devtools.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 = JSON.encode(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, 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/devtools.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 {
- throw new ArgumentError('Cannot encode $obj');
- }
- }
-}
diff --git a/lib/dom_model.dart b/lib/dom_model.dart
index 0ddea83..bf34846 100644
--- a/lib/dom_model.dart
+++ b/lib/dom_model.dart
@@ -324,9 +324,7 @@
var result = <String>[];
attributes.forEach((k, v) {
if (k != null) {
- result
- ..add(k)
- ..add(v);
+ result..add(k)..add(v);
}
});
return result;
diff --git a/lib/multiplex.dart b/lib/multiplex.dart
new file mode 100644
index 0000000..dfb797c
--- /dev/null
+++ b/lib/multiplex.dart
@@ -0,0 +1,175 @@
+// 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 JSON;
+import 'dart:io' show HttpClientResponse, 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.ANY_IP_V4, 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/devtools.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 = JSON.encode(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, 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/devtools.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 {
+ throw new ArgumentError('Cannot encode $obj');
+ }
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 657b976..3c42c9e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: webkit_inspection_protocol
-version: 0.1.0
+version: 0.1.1
description: A client for the Webkit Inspection Protocol (WIP).
homepage: https://github.com/google/webkit_inspection_protocol.dart
@@ -8,10 +8,10 @@
environment:
sdk: '>=1.10.0 <2.0.0'
dependencies:
- args: '^0.13.1'
+ args: '^0.13.0'
logging: '^0.11.0'
shelf: '^0.6.1+2'
shelf_web_socket: '^0.0.1+2'
dev_dependencies:
shelf_static: '^0.2.2'
- test: '^0.12.0'
+ test: '^0.12.3+2'