Skip to content

Instantly share code, notes, and snippets.

@ochafik
Last active June 18, 2016 14:22
Show Gist options
  • Save ochafik/9929ec51d3c4d3d613b71b5f5b45130b to your computer and use it in GitHub Desktop.
Save ochafik/9929ec51d3c4d3d613b71b5f5b45130b to your computer and use it in GitHub Desktop.
Dart Development Server
#!/usr/bin/env python
# https://gist.github.com/ochafik/9929ec51d3c4d3d613b71b5f5b45130b
# For SPDY / HTTP2 push: https://github.com/eigengo/opensourcejournal/blob/master/2014.1/spdynetty/spdynetty.md
import getopt
import os
import re
import sys
import threading
import urllib
from SimpleHTTPServer import SimpleHTTPRequestHandler
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from SocketServer import ThreadingMixIn
# Example usage:
#
# ./serve \
# ads/adsense/fe/dashboard/web@/adsense/apps/dashboard/_/resources \
# default_app/web@/
#
# List of (served_path, served_path_with_slash, directory) tuples.
served_directories = []
# List of directories from which packages should be resolved.
root_directories = []
verbose = False
push_imported_files = False
def main():
global served_directories
global root_directories
global verbose
global push_imported_files
port = 8080
hostname = '0.0.0.0'
# The bazel root directory.
bazel_root = os.getcwd()
try:
opts, args = getopt.getopt(
sys.argv[1:],
"p:r:v",
[
"verbose",
"port=",
"root=",
"add-relative-root=",
"hostname",
"push"
])
except getopt.GetoptError as err:
print str(err)
sys.exit(2)
# See http://www.bazel.io/docs/output_directories.html
relative_roots = [
"blaze-genfiles",
"blaze-bin",
"blaze-out",
"bazel-genfiles",
"bazel-bin",
"bazel-out",
"../READONLY",
]
for o, a in opts:
if o in ("-v", "--verbose"):
verbose = True
elif o in ("-p", "--port"):
port = int(a)
elif o == "--push":
push_imported_files = True
elif o == "--hostname":
hostname = a
elif o in ("-r", "--root"):
bazel_root = a
elif o in ("--add-relative-root"):
relative_roots.append(a)
else:
assert False, "unhandled option"
for arg in args:
parts = arg.split("@")
if len(parts) == 2:
[directory, served_path] = parts
else:
[directory] = parts
served_path = "/"
if served_path == "/":
served_path_with_slash = served_path
elif served_path.endswith("/"):
served_path_with_slash = served_path
served_path = served_path[:-1]
else:
served_path_with_slash = served_path + "/"
directory = os.path.relpath(directory, bazel_root)
served_directories.append((served_path, served_path_with_slash, directory))
root_directories.append(bazel_root)
for sub in relative_roots:
dir = os.path.join(bazel_root, sub)
if os.path.isdir(dir):
root_directories.append(dir)
if verbose:
print "Served dirs:", served_directories
print " Root dirs: ", root_directories
print 'Starting server, use <Ctrl-C> to stop'
ThreadedHTTPServer((hostname, port), DartRequestHandler).serve_forever()
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
"""Handle requests in a separate thread."""
dart_package_re = re.compile('^(.*?/package(?:s/|:))([^/]+)/(.*)$')
# TODO(ochafik): Handle DDC ES6 modules preloading.
# dart_package_uri_re = re.compile('package:([^/]+)/(.*?)')
dart_import_re = re.compile('import\s*[\'"]([^:]+|package:([^\'"]+?))[\'"]', re.MULTILINE)
class DartRequestHandler(SimpleHTTPRequestHandler):
resolved_paths = {}
def resolve_path(self):
global served_directories
global verbose
if self.path in self.resolved_paths:
return self.resolved_paths[self.path]
path = urllib.unquote(self.path)
candidates = []
m = dart_package_re.match(path)
if m:
file = os.path.join(m.group(2).replace(".", "/"), "lib", m.group(3))
candidates += [
file,
os.path.join("third_party", "dart", file),
]
else:
for served_path, served_path_with_slash, directory in served_directories:
if path == served_path:
candidates.append(os.path.join(directory, "index.html"))
break
elif path.startswith(served_path_with_slash):
candidates.append(os.path.join(directory, path[len(served_path_with_slash):]))
break
# if verbose:
# print "Candidates: " + ", ".join(candidates)
file = self.find_file(candidates)
self.resolved_paths[self.path] = file
return file
def do_GET(self):
global served_directories
global verbose
googleCommand = self.headers.get('X-Google-Command', None)
if googleCommand:
print "X-Google-Command = ", googleCommand
file = self.resolve_path()
if file:
etag = "\"%s\"" % int(os.path.getmtime(file))
if self.headers.get('If-None-Match', None) == etag:
self.send_response(304)
self.end_headers()
return
content_type = 'application/dart' if file.endswith(".dart") else self.guess_type(file)
# Don't open as text as file size wouldn't match if newlines are modified
mode = 'rb'
try:
# Try to open the file 2 times
# TODO(ochafik): Print / understand errors (fuse-specific?).
try:
f = open(file, mode)
except IOError:
try:
f = open(file, mode)
except IOError:
f = open(file, mode)
if push_imported_files:
# Server push for Dart sources.
content = f.read()
f.close()
links = []
for dart_import_m in dart_import_re.finditer(content):
dart_import = dart_import_m.group(1)
packages_base = self.get_packages_base()
link = None
if dart_import.startswith("package:"):
link = os.path.join(packages_base, dart_import[len("package:"):])
else:
link = os.path.join(os.path.dirname(self.path), dart_import)
links.append("<%s>; rel=preload; as=script" % link)
self.send_response(200)
self.send_header("Content-Length", len(content))
self.send_header("Content-Type", content_type)
self.send_header("ETag", etag)
if len(links) > 0:
self.send_header("Link", ", ".join(links))
if verbose:
print "Links for ", self.path, ":\n\t", "\n\t".join(links)
self.end_headers()
self.wfile.write(content)
self.wfile.close();
f.close()
else:
self.send_response(200)
self.send_header("Content-Length", os.fstat(f.fileno()).st_size)
self.send_header("Content-Type", content_type)
self.send_header("ETag", etag)
self.end_headers()
self.copyfile(f, self.wfile)
f.close()
return
except IOError:
self.send_error(404, "File not found: " + file)
self.send_error(404, "File not found")
def get_packages_base(self):
path = self.path
m = dart_package_re.match(path)
return m.group(1) if m else os.path.dirname(path) + "packages/"
def find_file(self, candidates):
global root_directories
for root_directory in root_directories:
for candidate in candidates:
file = os.path.join(root_directory, candidate)
if os.path.exists(file):
return file
return None
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment