Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Revert "Merging newproxy branch."

This reverts commit d466db7, reversing
changes made to 5773468.
  • Loading branch information...
commit 23e99cdc208f614db07170b68791788c61d54e31 1 parent b981834
@admc admc authored
Showing with 871 additions and 1,234 deletions.
  1. +1 −1  setup.py
  2. +5 −5 test/internet_tests/__init__.py
  3. +1 −1  test/internet_tests/domain_switcher.html
  4. +2 −1  test/internet_tests/test_open_foreign_domain.py
  5. +4 −5 test/local_tests/__init__.py
  6. +1 −5 windmill/__init__.py
  7. +1 −2  windmill/authoring/__init__.py
  8. +6 −5 windmill/authoring/transforms.py
  9. +2 −3 windmill/bin/admin_lib.py
  10. +16 −14 windmill/bin/shell_objects.py
  11. +3 −0  windmill/dep/__init__.py
  12. +147 −0 windmill/dep/_wsgi_fileserver/__init__.py
  13. +33 −19 windmill/{server/jsonrpc.py → dep/_wsgi_jsonrpc/__init__.py}
  14. +126 −0 windmill/dep/_wsgi_jsonrpc/json_tools.py
  15. +61 −0 windmill/dep/_wsgi_jsonrpc/test_jsonrpc.py
  16. +29 −11 windmill/{server/xmlrpc.py → dep/_wsgi_xmlrpc/__init__.py}
  17. +5 −135 windmill/server/__init__.py
  18. +0 −71 windmill/server/compressor.py
  19. +3 −3 windmill/server/convergence.py
  20. 0  windmill/server/{old_forwardmanager.py → forwardmanager.py}
  21. +108 −133 windmill/server/https.py
  22. +0 −342 windmill/server/old_proxy.py
  23. +313 −469 windmill/server/proxy.py
  24. 0  windmill/server/{old_wsgi.py → wsgi.py}
  25. +4 −9 windmill/tools/__init__.py
View
2  setup.py
@@ -31,7 +31,7 @@
Thanks for your interest and participation!
"""
-dependencies = ['webenv >= 0.6.1', 'httplib2']
+dependencies = []
two_four_dependencies = ['ctypes']
View
10 test/internet_tests/__init__.py
@@ -4,13 +4,13 @@
# hit a forward
from windmill.bin import admin_lib
-from windmill import server, authoring
+import windmill
import os, sys
-from webenv.applications.file_server import FileServerApplication
+from windmill.dep import wsgi_fileserver
def setup_module(module):
- authoring.setup_module(module)
- application = FileServerApplication(os.path.dirname(__file__))
- server.add_namespace('windmill-unittests', application)
+ windmill.authoring.setup_module(module)
+ application = wsgi_fileserver.WSGIFileServerApplication(root_path=os.path.dirname(__file__), mount_point='/windmill-unittests/')
+ windmill.server.wsgi.add_namespace('windmill-unittests', application)
from windmill.authoring import teardown_module
View
2  test/internet_tests/domain_switcher.html
@@ -11,7 +11,7 @@
<fieldset>
<p>Submit a POST search to Drupal.org</p>
<p><input type="text" name="search_theme_form"></p>
-<p><input type="submit" value="Search" name="op"></p>
+<p><input type="submit" value="Submit" name="op"></p>
<input type="hidden" value="search_theme_form" id="edit-search-theme-form" name="form_id"/>
</fieldset>
</form>
View
3  test/internet_tests/test_open_foreign_domain.py
@@ -1,8 +1,9 @@
# Generated by the windmill services transformer
from windmill.authoring import WindmillTestClient
-from time import sleep
+
def test_foreign_open():
client = WindmillTestClient(__name__)
+
client.open(url=u'http://www.asdf.com')
client.waits.forPageLoad(timeout=u'2000')
client.asserts.assertJS(js=u"windmill.testWin().document.title == 'asdf'")
View
9 test/local_tests/__init__.py
@@ -14,17 +14,16 @@
from windmill.bin import admin_lib
import windmill
-from windmill import authoring, server
import os, sys
from windmill.dep import functest
from time import sleep
-from webenv.applications.file_server import FileServerApplication
+from windmill.dep import wsgi_fileserver
def setup_module(module):
- authoring.setup_module(module)
+ windmill.authoring.setup_module(module)
- application = FileServerApplication(os.path.dirname(__file__))
- server.add_namespace('windmill-unittests', application)
+ application = wsgi_fileserver.WSGIFileServerApplication(root_path=os.path.abspath(os.path.dirname(__file__)), mount_point='/windmill-unittests/')
+ windmill.server.wsgi.add_namespace('windmill-unittests', application)
from windmill.authoring import teardown_module
View
6 windmill/__init__.py
@@ -14,8 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import sys
-
+import bin, browser, server, conf, tools, sys
if not sys.version.startswith('2.4'):
from urlparse import urlparse
else:
@@ -49,6 +48,3 @@ def get_test_url(url):
else:
test_url = url.geturl().replace(url.netloc, url.netloc+'/windmill-serv/start.html')
return test_url
-
-# Backwards compat, I hate this import!
-from windmill import authoring, server
View
3  windmill/authoring/__init__.py
@@ -21,7 +21,6 @@
from windmill.dep import json
import os, sys, re
from time import sleep
-from windmill import tools
logger = logging.getLogger(__name__)
@@ -140,7 +139,7 @@ def __init__(self, suite_name, assertions=None, browser_debugging=None, method_p
"""Assign all available attributes to instance so they are easily introspected"""
if method_proxy is None:
- method_proxy = tools.make_jsonrpc_client()
+ method_proxy = windmill.tools.make_jsonrpc_client()
self._method_proxy = method_proxy
View
11 windmill/authoring/transforms.py
@@ -17,7 +17,6 @@
import os
import sys
import windmill
-from windmill import server, tools
from windmill.dep import json
import tempfile
@@ -25,7 +24,7 @@
from urlparse import urlparse
else:
# python 2.4
- from tools.urlparse_25 import urlparse
+ from windmill.tools.urlparse_25 import urlparse
def get_save_url(suite_name, extension):
@@ -35,9 +34,11 @@ def get_save_url(suite_name, extension):
def create_saves_path():
directory = tempfile.mkdtemp(suffix='.windmill-saves')
# Mount the fileserver application for tests
- from webenv.applications.file_server import FileServerApplication
- application = FileServerApplication(os.path.dirname(__file__))
- server.add_namespace('windmill-unittests', application)
+ from windmill.dep import wsgi_fileserver
+ WSGIFileServerApplication = wsgi_fileserver.WSGIFileServerApplication
+ application = WSGIFileServerApplication(root_path=os.path.abspath(directory), mount_point='/windmill-saves/')
+ from windmill.server import wsgi
+ wsgi.add_namespace('windmill-saves', application)
windmill.settings['SAVES_PATH'] = directory
windmill.teardown_directories.append(directory)
View
5 windmill/bin/admin_lib.py
@@ -15,7 +15,6 @@
# limitations under the License.
import windmill
-from windmill import conf, server
import logging
from time import sleep
import os, sys
@@ -87,7 +86,7 @@ def setup_servers(console_level=logging.INFO):
if len(logging.getLogger().handlers) > 0:
console_handler = logging.getLogger().handlers[0]
console_handler.setLevel(console_level)
- httpd = server.make_server()
+ httpd = windmill.server.wsgi.make_windmill_server()
return httpd
def run_threaded(console_level=logging.INFO):
@@ -122,7 +121,7 @@ def configure_global_settings(logging_on=True):
else:
local_settings = None
- windmill.settings = conf.configure_settings(localSettings=local_settings)
+ windmill.settings = windmill.conf.configure_settings(localSettings=local_settings)
if 'controllers' not in windmill.settings:
windmill.settings['controllers'] = []
View
30 windmill/bin/shell_objects.py
@@ -24,10 +24,8 @@
logger = logging.getLogger(__name__)
-from windmill import tools, browser, server
-
-jsonrpc_client = tools.make_jsonrpc_client()
-xmlrpc_client = tools.make_xmlrpc_client()
+jsonrpc_client = windmill.tools.make_jsonrpc_client()
+xmlrpc_client = windmill.tools.make_xmlrpc_client()
from StringIO import StringIO
test_stream_object = StringIO()
@@ -43,7 +41,7 @@ def clear_queue():
def start_firefox():
"""Start the Firefox web browser configured for windmill"""
- controller = browser.get_firefox_controller()
+ controller = windmill.browser.get_firefox_controller()
controller.start()
#print 'Started '+str(controller.command)
logger.info(str(controller.command))
@@ -52,21 +50,21 @@ def start_firefox():
def start_ie():
"""Start the Internet Explorer web browser configured for windmill"""
- controller = browser.get_ie_controller()
+ controller = windmill.browser.get_ie_controller()
controller.start()
windmill.settings['controllers'].append(controller)
return controller
def start_safari():
"""Start the Safari web browser configured for windmill"""
- controller = browser.get_safari_controller()
+ controller = windmill.browser.get_safari_controller()
controller.start()
windmill.settings['controllers'].append(controller)
return controller
def start_chrome():
"""Start the Crhome web browser configured for windmill"""
- controller = browser.get_chrome_controller()
+ controller = windmill.browser.get_chrome_controller()
controller.start()
windmill.settings['controllers'].append(controller)
return controller
@@ -146,9 +144,11 @@ def run_js_tests(js_dir, test_filter=None, phase=None):
import windmill
windmill.js_framework_active = True
js_dir = os.path.abspath(os.path.expanduser(js_dir))
- from webenv.applications.file_server import FileServerApplication
- application = FileServerApplication(os.path.abspath(js_dir))
- server.add_namespace('windmill-jstests', application)
+ from windmill.dep import wsgi_fileserver
+ WSGIFileServerApplication = wsgi_fileserver.WSGIFileServerApplication
+ application = WSGIFileServerApplication(root_path=os.path.abspath(js_dir), mount_point='/windmill-jstest/')
+ from windmill.server import wsgi
+ wsgi.add_namespace('windmill-jstest', application)
# Build list of files and send to IDE
base_url = windmill.settings['TEST_URL']+'/windmill-jstest'
@@ -172,9 +172,11 @@ def parse_files(x, directory, files):
def load_extensions_dir(dirname):
"""Mount the directory and send all javascript file links to the IDE in order to execute those test urls under the jsUnit framework"""
# Mount the fileserver application for tests
- from webenv.applications.file_server import FileServerApplication
- application = FileServerApplication(os.path.abspath(dirname))
- server.add_namespace('windmill-extensions', application)
+ from windmill.dep import wsgi_fileserver
+ WSGIFileServerApplication = wsgi_fileserver.WSGIFileServerApplication
+ application = WSGIFileServerApplication(root_path=os.path.abspath(dirname), mount_point='/windmill-extentions/')
+ from windmill.server import wsgi
+ wsgi.add_namespace('windmill-extentions', application)
# Build list of files and send to IDE
base_url = windmill.settings['TEST_URL']+'/windmill-extentions'
View
3  windmill/dep/__init__.py
@@ -13,6 +13,9 @@
import _simplesettings as simplesettings
import _mozrunner as mozrunner
+import _wsgi_fileserver as wsgi_fileserver
+import _wsgi_jsonrpc as wsgi_jsonrpc
+import _wsgi_xmlrpc as wsgi_xmlrpc
import _functest as functest
import sys
View
147 windmill/dep/_wsgi_fileserver/__init__.py
@@ -0,0 +1,147 @@
+# Copyright (c) 2006-2007 Open Source Applications Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from urlparse import urlparse
+import os, sys
+import logging
+import urllib
+
+logger = logging.getLogger(__name__)
+
+# Content type sources taken from http://en.wikipedia.org/wiki/MIME_type
+content_type_table = {'js': 'application/x-javascript', 'html': 'text/html; charset=utf-8',
+ 'fallback':'text/plain; charset=utf-8', 'ogg': 'application/ogg',
+ 'xhtml':'text/html; charset=utf-8', 'rm':'audio/vnd.rn-realaudio',
+ 'swf':'application/x-shockwave-flash', 'mp3': 'audio/mpeg', 'wma':'audio/x-ms-wma',
+ 'ra':'audio/vnd.rn-realaudio', 'wav':'audio/x-wav', 'gif':'image/gif', 'jpeg':'image/jpeg',
+ 'jpg':'image/jpeg', 'png':'image/png', 'tiff':'image/tiff', 'css':'text/css; charset=utf-8',
+ 'mpeg':'video/mpeg', 'mp4':'video/mp4', 'qt':'video/quicktime', 'mov':'video/quicktime',
+ 'wmv':'video/x-ms-wmv', 'atom':'application/atom+xml; charset=utf-8',
+ 'xslt':'application/xslt+xml', 'svg':'image/svg+xml', 'mathml':'application/mathml+xml',
+ 'rss':'application/rss+xml; charset=utf-8',
+ 'ics':'text/calendar; charset=utf-8 '}
+
+def reconstruct_url(environ):
+ # From WSGI spec, PEP 333
+ from urllib import quote
+ url = environ['wsgi.url_scheme']+'://'
+ if environ.get('HTTP_HOST'): url += environ['HTTP_HOST']
+ else:
+ url += environ['SERVER_NAME']
+ if environ['wsgi.url_scheme'] == 'https':
+ if environ['SERVER_PORT'] != '443':
+ url += ':' + environ['SERVER_PORT']
+ else:
+ if environ['SERVER_PORT'] != '80':
+ url += ':' + environ['SERVER_PORT']
+ url += quote(environ.get('SCRIPT_NAME',''))
+ url += quote(environ.get('PATH_INFO','')).replace(url.replace(':', '%3A'), '')
+ if environ.get('QUERY_STRING'):
+ url += '?' + environ['QUERY_STRING']
+ environ['reconstructed_url'] = url
+ return url
+
+class FileResponse(object):
+ readsize = 1024
+ def __init__(self, f, filename):
+ self.size = os.path.getsize(filename)
+ self.f = f
+ def __iter__(self):
+ output = '\n'
+ while len(output) is not 0:
+ output = self.f.read(self.readsize)
+ yield output
+
+class WSGIFileServerApplication(object):
+ """Application to serve out windmill provided"""
+
+ def __init__(self, root_path, mount_point=None):
+ self.path = os.path.abspath(os.path.expanduser(root_path))
+ self.mount_point = mount_point
+
+ def handler(self, environ, start_response):
+ """Application to serve out windmill provided"""
+ url = urlparse(reconstruct_url(environ))
+
+ if self.mount_point is not None:
+ #split_url = url.path.split(self.mount_point, 1)
+ split_url = url[2].split(self.mount_point, 1)
+ serve_file = split_url[1]
+ else:
+ #serve_file = url.path
+ serve_file = url[2]
+
+ serve_file = urllib.unquote(serve_file).replace('/', os.path.sep)
+
+ def do_get():
+ if serve_file.endswith('/') or os.path.isdir(os.path.join(self.path, serve_file)):
+ if os.path.isdir(os.path.join(self.path, serve_file)):
+ start_response('200 OK', [('Cache-Control','no-cache'), ('Pragma','no-cache'),
+ ('Content-Type', 'text/html; charset=utf-8')])
+ return [ '<html>' +
+ '<br>'.join( ['<a href="%s/%s">%s</a>' % (serve_file.replace(filename, ''), filename, filename)
+ for filename in os.listdir(os.path.join(self.path, serve_file))])
+ + '</html>' ]
+ else:
+ logger.error('failed to list directory %s/%s' % (self.path, serve_file))
+ start_response('404 Not found', [('Content-Type', 'text/plain')])
+ return ['404 Not Found']
+
+ try:
+ if os.name == 'nt' or sys.platform == 'cygwin':
+ f = open(os.path.join(self.path, serve_file), 'rb')
+ else:
+ f = open(os.path.join(self.path, serve_file), 'r')
+ logger.debug('opened file %s' % serve_file)
+ except IOError:
+ logger.error('failed to open file %s/%s' % (self.path, serve_file))
+ start_response('404 Not found', [('Content-Type', 'text/plain')])
+ return ['404 Not Found']
+
+ response = FileResponse(f, os.path.join(self.path, serve_file))
+ start_response('200 OK', [('Cache-Control','no-cache'), ('Pragma','no-cache'),
+ ('Content-Length', str(response.size),),
+ ('Content-Type', self.guess_content_type(environ['PATH_INFO']))])
+ return response
+
+ def do_put():
+ #Write file
+ try:
+ f = open(os.path.join(self.path, serve_file), 'w')
+ logger.debug('opened file for writing %s' % serve_file)
+ except:
+ logger.error('failed to open file for writiing %s/%s' % (self.path, serve_file))
+ start_response('403 Forbidden', [('Content-Type', 'text/plain')])
+ return ['403 Forbidden']
+
+ f.write(environ['wsgi.input'].read())
+
+ def do_mkcollection():
+ pass
+
+ http_method_map = {'GET':do_get, 'PUT':do_put, 'MKCOLLECTION':do_mkcollection}
+ return http_method_map[environ['REQUEST_METHOD']]()
+
+
+ def guess_content_type(self, path_info):
+ """Make a best guess at the content type"""
+ extention_split = path_info.split('.')
+
+ if content_type_table.has_key(extention_split[-1]):
+ return content_type_table[extention_split[-1]]
+ else:
+ return content_type_table['fallback']
+
+ def __call__(self, environ, start_response):
+ return self.handler(environ, start_response)
View
52 windmill/server/jsonrpc.py → windmill/dep/_wsgi_jsonrpc/__init__.py
@@ -1,5 +1,4 @@
# Copyright (c) 2006-2007 Open Source Applications Foundation
-# Copyright (c) 2009 Mikeal Rogers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,11 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from datetime import datetime
+from StringIO import StringIO
+import sys, traceback
import logging
+import json_tools, test_jsonrpc
from windmill.dep import json
-from webenv import Response, Response500
-from webenv.rest import RestApplication
logger = logging.getLogger(__name__)
@@ -209,23 +210,36 @@ def _encode(self, result=None, error=None, jsonrpc_id=None):
def _decode(self, s):
"""Internal method for decoding json objects, uses simplejson"""
return json.loads(s)
-
-class JsonResponse(Response):
- content_type = 'application/json'
-class JSONRPCApplication(JSONRPCDispatcher, RestApplication):
+class WSGIJSONRPCApplication(JSONRPCDispatcher):
"""A WSGI Application for generic JSONRPC requests."""
- def __init__(self, *args, **kwargs):
- JSONRPCDispatcher.__init__(self, *args, **kwargs)
- RestApplication.__init__(self)
- def POST(self, request):
+ def handler(self, environ, start_response):
"""A WSGI handler for generic JSONRPC requests."""
- body = str(request.body)
- try:
- logger.debug('Sending %s to dispatcher' % body)
- response = self.dispatch(body) + '\n'
- return JsonResponse(response)
- except Exception, e:
- logger.exception('WSGIJSONRPCApplication Dispatcher excountered exception')
- return Response500(str(e))
+
+ if environ['REQUEST_METHOD'].endswith('POST'):
+ body = None
+ if environ.get('CONTENT_LENGTH'):
+ length = int(environ['CONTENT_LENGTH'])
+ body = environ['wsgi.input'].read(length)
+
+ try:
+ logger.debug('Sending %s to dispatcher' % body)
+ response = self.dispatch(body) + '\n'
+ start_response('200 OK', [('Cache-Control','no-cache'), ('Pragma','no-cache'),
+ ('Content-Length', str(len(response)),),
+ ('Content-Type', 'application/json')])
+ return [response]
+ except Exception, e:
+ logger.exception('WSGIJSONRPCApplication Dispatcher excountered exception')
+ start_response('500 Internal Server Error', [('Cache-Control','no-cache'), ('Content-Type', 'text/plain')])
+ return ['500 Internal Server Error']
+
+ else:
+ start_response('405 Method Not Allowed', [('Cache-Control','no-cache'), ('Content-Type', 'text/plain')])
+ return ['405 Method Not Allowed. This JSONRPC interface only supports POST. Method used was "'+str(environ['REQUEST_METHOD'])+'"']
+
+ def __call__(self, environ, start_response):
+ return self.handler(environ, start_response)
+
+
View
126 windmill/dep/_wsgi_jsonrpc/json_tools.py
@@ -0,0 +1,126 @@
+# Copyright (c) 2006-2007 Open Source Applications Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import time
+import httplib, urllib, copy
+from urlparse import urlparse
+from windmill.dep import json
+import logging
+
+__version__ = str(0.1)
+
+logger = logging.getLogger(__name__)
+
+class _Method(object):
+
+ def __init__(self, call, name):
+ self.call = call
+ self.name = name
+
+ def __call__(self, *args, **kwargs):
+
+ # Need to handle keyword arguments per 1.1 spec
+
+ request = {}
+ request['version'] = '1.1'
+ request['method'] = self.name
+ if len(kwargs) is not 0:
+ params = copy.copy(kwargs)
+ index = 0
+ for arg in args:
+ params[str(index)] = arg
+ index = index + 1
+ elif len(args) is not 0:
+ params = copy.copy(args)
+ else:
+ params = None
+ request['params'] = params
+ logger.debug('Created python request object %s' % str(request))
+ return self.call(json.dumps(request))
+
+ def __getattr__(self, name):
+ return _Method(self.call, "%s.%s" % (self.name, name))
+
+class JSONRPCTransport:
+
+ def __init__(self, uri, proxy_uri=None):
+
+ if proxy_uri is not None:
+ self.connection_url = urlparse(proxy_uri)
+ self.request_path = uri
+ else:
+ self.connection_url = urlparse(uri)
+ self.request_path = self.connection_url.path
+
+ headers = {'User-Agent':'jsonrpclib',
+ 'Content-Type':'application/json',
+ 'Accept':'application/json'}
+
+ def request(self, request_body):
+
+ if self.connection_url.scheme == 'http':
+ if self.connection_url == '':
+ port = 80
+ else:
+ port = self.connection_url.port
+ connection = httplib.HTTPConnection(self.connection_url.hostname+':'+str(port))
+ elif self.connection_url.scheme == 'https':
+ if self.connection_url == '':
+ port = 443
+ else:
+ port = self.connection_url.port
+ connection = httplib.HTTPSConnection(self.connection_url.hostname+':'+str(port))
+ else:
+ raise Exception, 'unsupported transport'
+
+ connection.request('POST', self.request_path, body=request_body, headers=self.headers)
+ self.response = connection.getresponse()
+ if self.response.status == 200:
+ return self.response.read()
+ else:
+ return self.response.status
+
+
+class ServerProxy(object):
+ def __init__(self, uri=None, transport=None):
+ """Initialization"""
+ if uri is None and transport is None:
+ raise Exception, 'either uri or transport needs to be specified'
+
+ if transport is None:
+ transport = JSONRPCTransport(uri)
+ self.__transport = transport
+
+ def __request(self, request):
+ # call a method on the remote server
+
+ response = self.__transport.request(request)
+ logger.debug('got response from __transport :: %s' % response)
+ if type(response) is not int:
+ return json.loads(response)
+ else:
+ logger.error('Recieved status code %s' % response)
+
+ def __repr__(self):
+ return (
+ "<ServerProxy for %s%s>" %
+ (self.__host, self.__handler)
+ )
+
+ __str__ = __repr__
+
+ def __getattr__(self, name):
+ # magic method dispatcher
+ return _Method(self.__request, name)
+
View
61 windmill/dep/_wsgi_jsonrpc/test_jsonrpc.py
@@ -0,0 +1,61 @@
+# Copyright (c) 2006-2007 Open Source Applications Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+def make_server(host='localhost', port=4325):
+ from wsgiref import simple_server
+ from windmill.dep import wsgi_jsonrpc
+
+ class Methods(object):
+ def test_1(self):
+ return u'test_1'
+ def test_2(self, value):
+ return value
+
+ methods = Methods()
+
+ def test_3():
+ return 'test3'
+
+ application = wsgi_jsonrpc.WSGIJSONRPCApplication(instance=methods, methods=[test_3])
+ return simple_server.make_server(host, port, application)
+
+def test_jsonrpc_server(uri='http://localhost:4325/'):
+ from windmill.dep import wsgi_jsonrpc
+ json_tools = wsgi_jsonrpc.json_tools
+
+ jsonrpc_client = json_tools.ServerProxy(uri=uri)
+ assert jsonrpc_client.test_1() == {u'result':u'test_1'}
+ assert jsonrpc_client.test_2({'test':4}) == {u'result':{'test':4}}
+ assert jsonrpc_client.test_3() == {u'result':u'test3'}
+
+
+if __name__ == "__main__":
+ import sys
+ from threading import Thread
+
+ run = True
+
+ try:
+ server = make_server()
+ def test_wrapper():
+ test_jsonrpc_server()
+ run = False
+ sys.exit()
+ thread = Thread(target=test_wrapper)
+ thread.start()
+ while run:
+ server.handle_request()
+ sys.exit()
+ except KeyboardInterrupt:
+ sys.exit()
View
40 windmill/server/xmlrpc.py → windmill/dep/_wsgi_xmlrpc/__init__.py
@@ -15,15 +15,9 @@
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
import logging
-from webenv import Response, Response500
-from webenv.rest import RestApplication
-
logger = logging.getLogger(__name__)
-class XMLResponse(Response):
- content_type = 'text/xml'
-
-class XMLRPCApplication(RestApplication):
+class WSGIXMLRPCApplication(object):
"""Application to handle requests to the XMLRPC service"""
def __init__(self, instance=None, methods=[]):
@@ -38,8 +32,17 @@ def __init__(self, instance=None, methods=[]):
for method in methods:
self.dispatcher.register_function(method)
self.dispatcher.register_introspection_functions()
+
+ def handler(self, environ, start_response):
+ """XMLRPC service for windmill browser core to communicate with"""
+
+ if environ['REQUEST_METHOD'] == 'POST':
+ return self.handle_POST(environ, start_response)
+ else:
+ start_response("400 Bad request", [('Content-Type','text/plain')])
+ return ['']
- def POST(self, request, *path):
+ def handle_POST(self, environ, start_response):
"""Handles the HTTP POST request.
Attempts to interpret all HTTP POST requests as XML-RPC calls,
@@ -49,8 +52,17 @@ def POST(self, request, *path):
"""
try:
- data = str(request.body)
+ # Get arguments by reading body of request.
+ # We read this in chunks to avoid straining
+ # socket.read(); around the 10 or 15Mb mark, some platforms
+ # begin to have problems (bug #792570).
+
+ length = int(environ['CONTENT_LENGTH'])
+ data = environ['wsgi.input'].read(length)
+ max_chunk_size = 10*1024*1024
+ size_remaining = length
+
# In previous versions of SimpleXMLRPCServer, _dispatch
# could be overridden in this class, instead of in
# SimpleXMLRPCDispatcher. To maintain backwards compatibility,
@@ -62,7 +74,13 @@ def POST(self, request, *path):
response += '\n'
except: # This should only happen if the module is buggy
# internal error, report as HTTP server error
- return Response500()
+ start_response("500 Server error", [('Content-Type', 'text/plain')])
+ return []
else:
# got a valid XML RPC response
- return XMLResponse(response)
+ start_response("200 OK", [('Content-Type','text/xml'), ('Content-Length', str(len(response)),)])
+ return [response]
+
+
+ def __call__(self, environ, start_response):
+ return self.handler(environ, start_response)
View
140 windmill/server/__init__.py
@@ -13,149 +13,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from urlparse import urlparse
-from webenv import Response500
+import wsgi, convergence
-initial_forwarding_conditions = [
+forwarding_conditions = [
lambda e : 'google.com/safebrowsing/downloads' not in e['reconstructed_url'],
lambda e : 'mozilla.org/en-US/firefox/livebookmarks.html' not in e['reconstructed_url'],
lambda e : e.get('CONTENT_TYPE') != 'application/x-shockwave-flash',
lambda e : not e['reconstructed_url'].endswith(".mozilla.com/firefox/headlines.xml")
]
-current_add_forward_condition = None
-current_remove_forward_condition = None
-
def add_forward_condition(condition):
- if not current_add_forward_condition:
- initial_forwarding_conditions.append(condition)
- else:
- current_add_forward_condition(condition)
+ forwarding_conditions.append(condition)
def remove_forward_condition(condition):
- if not current_remove_forward_condition:
- while condition in initial_forwarding_conditions:
- initial_forwarding_conditions.remove(condition)
- else:
- current_remove_forward_condition(condition)
-
-import os
-
-from webenv.rest import RestApplication
-from webenv.applications.file_server import FileServerApplication
-
-import windmill
-from proxy import ProxyApplication
-from jsonrpc import JSONRPCApplication
-from xmlrpc import XMLRPCApplication
-from convergence import XMLRPCMethods, JSONRPCMethods, TestResolutionSuite, CommandResolutionSuite, ControllerQueue
-from compressor import CompressorApplication
-
-class WindmillApplication(RestApplication):
-
- def __init__(self, js_path=None, compression_enabled=None):
- super(WindmillApplication, self).__init__()
-
- if js_path is None:
- js_path = windmill.settings['JS_PATH']
- if compression_enabled is None:
- compression_enabled = not windmill.settings['DISABLE_JS_COMPRESS']
-
- self.proxy_application = ProxyApplication()
- self.test_resolution_suite = TestResolutionSuite()
- self.command_resolution_suite = CommandResolutionSuite()
- self.queue = ControllerQueue(self.command_resolution_suite, self.test_resolution_suite)
- self.xmlrpc_methods_instance = XMLRPCMethods(self.queue, self.test_resolution_suite,
- self.command_resolution_suite,
- proxy=self.proxy_application)
- self.jsonrpc_methods_instance = JSONRPCMethods(self.queue, self.test_resolution_suite,
- self.command_resolution_suite,
- proxy=self.proxy_application)
-
- self.add_resource('windmill-jsonrpc', JSONRPCApplication(instance=self.jsonrpc_methods_instance))
- self.add_resource('windmill-serv', FileServerApplication(js_path))
- self.add_resource('windmill-xmlrpc', XMLRPCApplication(instance=self.xmlrpc_methods_instance))
- self.add_resource('windmill-compressor', CompressorApplication(os.path.join(js_path, 'js'),
- compression_enabled))
-
- def __call__(self, environ, start_response):
- """Special subclass __call__ method that finds windmill-serv anywhere in path"""
- request = self.request_class(environ, start_response)
-
- path = environ['SCRIPT_NAME'] + environ['PATH_INFO']
-
- if len(path) is 0:
- path = '/'
-
- if path.startswith('/'):
- path = [p for p in path.split('/') if len(p) is not 0]
- elif environ['PATH_INFO'].startswith('http'):
- path = [p for p in urlparse(path).path.split('/') if len(p) is not 0]
- else:
- raise Exception('Cannot read PATH_INFO '+request.full_uri+str(request.environ))
-
- if len(path) is 0:
- response = self.handler(request)
- if response is None:
- response = Response500(str(type(self))+".handler() did not return a response object")
- response.request = request
- response.start_response()
- return response
- elif 'windmill-serv' in path:
- path = path[path.index('windmill-serv'):]
- response = self.rest_handler(request, *path)
- if response is None:
- response = Response500(str(type(self))+".rest_handler() did not return a response object")
- response.request = request
- response.start_response()
- return response
- else:
- response = self.rest_handler(request, *path)
- if response is None:
- response = Response500(str(type(self))+".rest_handler() did not return a response object")
- response.request = request
- response.start_response()
- return response
-
- def handler(self, request, *path):
- return self.proxy_application.handler(request)
-
-def make_server(http_port=None, js_path=None, compression_enabled=None):
- if http_port is None:
- http_port = windmill.settings['SERVER_HTTP_PORT']
-
- if windmill.has_ssl:
- import certificate
- cc = certificate.CertificateCreator()
- else:
- cc = None
-
- application = WindmillApplication(js_path=js_path, compression_enabled=compression_enabled)
- import https
- httpd = https.WindmillHTTPServer(('0.0.0.0', http_port),
- https.WindmillHTTPRequestHandler, cc,
- application)
-
- # Attach some objects to httpd for convenience and reverse compatibility
- httpd.controller_queue = application.queue
- httpd.test_resolution_suite = application.test_resolution_suite
- httpd.command_resolution_suite = application.command_resolution_suite
- httpd.xmlrpc_methods_instance = application.xmlrpc_methods_instance
- httpd.jsonrpc_methods_instance = application.jsonrpc_methods_instance
-
- # Global declarations.
- # Eventually it would be great to get rid of these provided we have some way of passing
- # the current server instance to test modules.
- global add_namespace
- add_namespace = application.add_resource
-
- # These globals are renamed for reverse compatibility
- global current_add_forward_condition
- current_add_forward_condition = application.proxy_application.fm.add_environ_condition
- global current_remove_forward_condition
- current_remove_forward_condition = application.proxy_application.fm.remove_environ_condition
-
- for c in initial_forwarding_conditions:
- current_add_forward_condition(c)
+ while condition in forwarding_conditions:
+ forwarding_conditions.remove(condition)
- return httpd
View
71 windmill/server/compressor.py
@@ -1,71 +0,0 @@
-import os
-import threading
-from time import sleep
-
-from webenv import Response, Response404
-from webenv.rest import RestApplication
-
-import jsmin
-
-class JavascriptResponse(Response):
- content_type = 'application/x-javascript'
-
-class CompressorApplication(RestApplication):
- """Full JavaScript Compression Library"""
- js_file_list = [
- ('lib', 'firebug', 'pi.js',),
- ('lib', 'firebug', 'firebug-lite.js',),
- ('lib', 'json2.js',),
- ('lib', 'browserdetect.js',),
- ('wm', 'windmill.js',),
- ('lib', 'getXPath.js',),
- ('lib', 'elementslib.js',),
- ('lib', 'js-xpath.js',),
- ('controller', 'controller.js',),
- ('controller', 'commands.js',),
- ('controller', 'asserts.js',),
- ('controller', 'waits.js',),
- ('wm', 'registry.js',),
- ('extensions', 'extensions.js',),
- ('wm', 'utils.js',),
- ('wm', 'ide', 'ui.js',),
- ('wm', 'ide', 'recorder.js',),
- ('wm', 'ide', 'remote.js',),
- ('wm', 'ide', 'dx.js',),
- ('wm', 'ide', 'ax.js',),
- ('wm', 'ide', 'results.js',),
- ('wm', 'xhr.js',),
- ('wm', 'metrics.js',),
- ('wm', 'events.js',),
- ('wm', 'global.js',),
- ('wm', 'jstest.js',),
- ('wm', 'load.js',),
- ]
-
- def __init__(self, js_path, enabled=True):
- super(CompressorApplication, self).__init__()
- self.enabled = enabled
- self.js_path = js_path
- self.compressed_windmill = None
- if enabled:
- self._thread = threading.Thread(target=self.compress_file)
- self._thread.start()
-
- def compress_file(self):
- compressed_windmill = ''
- for filename in self.js_file_list:
- compressed_windmill += jsmin.jsmin(open(os.path.join(self.js_path, *filename), 'r').read())
- self.compressed_windmill = compressed_windmill
-
- def handler(self, request, *path):
- if not self.enabled:
- return Response404()
- # if self.compressed_windmill is None:
- # self.compressed_windmill = ''
- # for filename in self.js_file_list:
- # self.compressed_windmill += jsmin.jsmin(open(os.path.join(self.js_path, *filename), 'r').read())
-
- while not self.compressed_windmill:
- sleep(.15)
-
- return JavascriptResponse(self.compressed_windmill)
View
6 windmill/server/convergence.py
@@ -178,12 +178,11 @@ def __getattr__(self, key):
class RPCMethods(object):
- def __init__(self, queue, test_resolution_suite, command_resolution_suite, proxy):
+ def __init__(self, queue, test_resolution_suite, command_resolution_suite):
self._queue = queue
self._logger = logging.getLogger('jsonrpc_methods_instance')
self._test_resolution_suite = test_resolution_suite
self._command_resolution_suite = command_resolution_suite
- self.proxy = proxy
def start_suite(self, suite_name):
self._test_resolution_suite.start_suite(suite_name)
@@ -317,7 +316,8 @@ def status_change(self, status):
pass
def set_test_url(self, url):
- self.proxy.fm.set_test_url(url)
+ windmill.settings['FORWARDING_TEST_URL'] = url
+ windmill.server.proxy.clearForwardingRegistry()
return 200
def restart_test_run(self, tests):
View
0  windmill/server/old_forwardmanager.py → windmill/server/forwardmanager.py
File renamed without changes
View
241 windmill/server/https.py
@@ -21,11 +21,14 @@
and WindmillProxyApplication that are drop-in replacements for the standard
non-ssl-enabled ones.
"""
+import time
import socket
+import select
import urllib
import SocketServer
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from StringIO import StringIO
+from proxy import WindmillProxyApplication
from httplib import HTTPConnection, HTTPException
import traceback
import sys
@@ -115,49 +118,14 @@ def _socket_create_connection(address, timeout=None):
socket.setdefaulttimeout(oldtimeout)
return sock
-class StartResponse(object):
- def __init__(self, request_handler, environ):
- self.request_handler = request_handler
- self.environ = environ
- self.start_response_called = False
- def __call__(self, status, headers, exc_info=None):
- if not self.request_handler.ready:
- raise AssertionError("start_response is being called before the http handler is ready")
- if self.start_response_called:
- raise AssertionError("this start_response was already called")
- self.start_response_called = True
-
- if exc_info:
- try:
- if self.request_handler.headers_sent:
- raise exc_info[0], exc_info[1], exc_info[2]
- finally:
- exc_info = None
-
- self.request_handler.headers_set[:] = [status, headers]
- status, response_headers = self.request_handler.headers_sent[:] = self.request_handler.headers_set
- if ' ' in status:
- code, message = status.split(' ', 1)
- else:
- code, message = (status, '')
- self.request_handler.send_response(int(code), message)
- self.request_handler.send_headers(response_headers)
- return self.request_handler.write
-
class WindmillHTTPRequestHandler(SocketServer.ThreadingMixIn, BaseHTTPRequestHandler):
def __init__(self, request, client_address, server):
- self.reset()
- BaseHTTPRequestHandler.__init__(self, request, client_address, server)
-
- protocol_version = "HTTP/1.0"
-
- def reset(self):
self.headers_set = []
self.headers_sent = []
self.header_buffer = ''
- self.ready = True
- self.start_response = None
+ BaseHTTPRequestHandler.__init__(self, request, client_address,
+ server)
def _sock_connect_to(self, netloc, soc):
"""Parse netloc string and establish connection on socket."""
@@ -214,44 +182,61 @@ def do_CONNECT(self):
if request is not None:
request.close()
- def handle_one_request(self):
- self.raw_requestline = self.rfile.readline()
- if not self.raw_requestline:
- self.close_connection = 1
- self.connection.close()
- return
- if not self.parse_request(): # An error code has been sent, just exit
- return
-
- if self.command == 'CONNECT':
- return self.do_CONNECT()
-
- if not self.ready:
- raise AssertionError("This handler is not ready.")
- if self.start_response is not None:
- raise AssertionError("This handler is already waiting on a start_response instance to finish.")
- environ = self.get_environ()
- self.start_response = StartResponse(self, environ)
- result = self.server.application(environ, self.start_response)
- self.ready = False
+ def handle_ALL(self):
+ namespaces = self.server.namespaces
+ proxy = self.server.proxy
+ found = None
+ path = self.path.split('?', 1)[0]
+ for key in namespaces:
+ if path.find('/'+key+'/') is not -1:
+ found = key
+ environ = self.get_environ()
+ result = namespaces[found](environ, self.start_response)
+ break
+ else:
+ found = None
+ environ = self.get_environ()
+ result = proxy(environ, self.start_response)
+ # == Old blocking code ==
+ # out = list(result)
+ # # send data back to browser
+ # try:
+ # self.write(''.join(out))
+ # except socket.error, err:
+ # logger.debug("%s while serving (%s) %s" % (err,
+ # self.command, self.path))
+ # == New non-blocking code ==
try:
for out in result:
self.write(out)
except socket.error, err:
logger.debug("%s while serving (%s) %s" % (err,self.command, self.path))
-
+
self.wfile.flush()
- self.reset()
- if self.close_connection:
- self.connection.close()
-
-
- # do_GET = handle_ALL
- # do_POST = handle_ALL
- # do_PUT = handle_ALL
- # do_HEAD = handle_ALL
- # do_DELETE = handle_ALL
+ self.connection.close()
+
+ do_GET = handle_ALL
+ do_POST = handle_ALL
+
+ def start_response(self, status, headers, exc_info=None):
+ if exc_info:
+ try:
+ if self.headers_sent:
+ raise exc_info[0], exc_info[1], exc_info[2]
+ finally:
+ exc_info = None
+ elif self.headers_set:
+ raise AssertionError("Headers already set!")
+
+ self.headers_set[:] = [status, headers]
+ status, response_headers = self.headers_sent[:] = self.headers_set
+ code, message = status.split(' ', 1)
+ self.send_response(int(code), message)
+
+ self.send_headers(response_headers)
+
+ return self.write
def send_headers(self, response_headers):
for header in response_headers:
@@ -270,9 +255,9 @@ def send_header(self, keyword, value):
self.header_buffer += "%s: %s\r\n" % (keyword, value)
if keyword.lower() == 'connection':
if value.lower() == 'close':
- self.close_connection = True
- else:
- self.close_connection = False
+ self.close_connection = 1
+ elif value.lower() == 'keep-alive':
+ self.close_connection = 0
def send_response(self, code, message=None):
"""Send the response header and log the response code.
@@ -294,6 +279,7 @@ def write(self, data):
if not self.headers_set:
raise AssertionError("write() before start_response()")
+
# elif not self.headers_sent:
# # Before the first output, send the stored headers
# status, response_headers = self.headers_sent[:] = self.headers_set
@@ -307,35 +293,23 @@ def write(self, data):
def get_environ(self):
""" Put together a wsgi environment """
- env = self.server.base_environ.copy()
-
if hasattr(self, 'base_path'):
self.path = self.base_path + self.path
- env['RAW_PATH'] = self.path
+ env = self.server.base_environ.copy()
env['SERVER_PROTOCOL'] = self.request_version
env['REQUEST_METHOD'] = self.command
-
if '?' in self.path:
path, query = self.path.split('?', 1)
else:
path, query = self.path,''
-
- scheme = urlparse(self.path).scheme
- env['wsgi.url_scheme'] = scheme
-
- host = self.address_string()
- remote_addr = self.client_address[0]
- if host != remote_addr:
- env['REMOTE_HOST'] = host
- env['REMOTE_ADDR'] = remote_addr
-
- if path.startswith(scheme+'://'):
- path = path[len(scheme) + 3:]
- path = path[path.index('/'):]
-
env['PATH_INFO'] = urllib.unquote(path)
env['QUERY_STRING'] = query
+ host = self.address_string()
+ if host != self.client_address[0]:
+ env['REMOTE_HOST'] = host
+ env['REMOTE_ADDR'] = self.client_address[0]
+
if self.headers.typeheader is not None:
env['CONTENT_TYPE'] = self.headers.typeheader
@@ -353,7 +327,7 @@ def get_environ(self):
env['HTTP_' + key] += ',' + value
else:
env['HTTP_' + key] = value
-
+ env['wsgi.url_scheme'] = urlparse(self.path).scheme
clen = self.headers.getheader('content-length')
if clen is not None and int(clen) > 0:
@@ -361,48 +335,48 @@ def get_environ(self):
env['wsgi.input'] = StringIO(i)
else:
env['wsgi.input'] = self.rfile
- # self.reconstruct_url(env)
+ self.reconstruct_url(env)
return env
- # def reconstruct_url(self, environ):
- # """ This can be done much faster and in a tidier way."""
- # url = environ['wsgi.url_scheme']+'://'
- # if environ.get('HTTP_HOST'):
- # url += environ['HTTP_HOST']
- # else:
- # url += environ['SERVER_NAME']
- # if environ['wsgi.url_scheme'] == 'https':
- # if environ['SERVER_PORT'] != '443':
- # url += ':' + environ['SERVER_PORT']
- # else:
- # if environ['SERVER_PORT'] != '80':
- # url += ':' + environ['SERVER_PORT']
- # url += environ.get('SCRIPT_NAME','')
- # if '://' in self.path:
- # url = self.path
- # else:
- # url += self.path
- # environ['reconstructed_url'] = url
- # return url
+ def reconstruct_url(self, environ):
+ """ This can be done much faster and in a tidier way."""
+ url = environ['wsgi.url_scheme']+'://'
+ if environ.get('HTTP_HOST'):
+ url += environ['HTTP_HOST']
+ else:
+ url += environ['SERVER_NAME']
+ if environ['wsgi.url_scheme'] == 'https':
+ if environ['SERVER_PORT'] != '443':
+ url += ':' + environ['SERVER_PORT']
+ else:
+ if environ['SERVER_PORT'] != '80':
+ url += ':' + environ['SERVER_PORT']
+ url += environ.get('SCRIPT_NAME','')
+ if '://' in self.path:
+ url = self.path
+ else:
+ url += self.path
+ environ['reconstructed_url'] = url
+ return url
def log_message(self, format, *args):
logger.debug(format % args)
class WindmillHTTPServer(SocketServer.ThreadingMixIn, HTTPServer):
- def __init__(self, address, handler, cert_creator, application):
+ def __init__(self, address, handler, cert_creator, apps, proxy):
# all we want from this method is to register a pem file
# $ openssl req -x509 -nodes -days 365 -newkey rsa:1024
# -keyout mycert.pem -out mycert.pem
self.cert_creator = cert_creator
+ self.namespaces = dict([ (arg.ns, arg) for arg in apps ])
+ self.proxy = proxy
# the rest is the same
HTTPServer.__init__(self, address, handler)
self.setup_environ()
self.threads = []
- self.application = application
- # daemon_threads = True
- daemon_thread = False # For debugging
+ daemon_threads = True
def setup_environ(self):
# Set up base environment
@@ -468,23 +442,19 @@ def process_request(self, request, client_address):
def handle_error(self, request, client_address):
try:
- print 'Exception happened during processing of request from', client_address
- except: pass
- traceback.print_exc()
- # try:
- # args = sys.exec_info()
- # except:
- # return
- #
- # print '-' * 40
- # print 'Exception happened during processing of request from',
- # print client_address
- # # traceback doesn't appear to be always be thread safe
- # try:
- # traceback.print_exception(*args)
- # except TypeError:
- # print "Traceback cannot be printed, probably do to a thread safety issue."
- # print '-' * 40
+ args = sys.exec_info()
+ except:
+ return
+
+ print '-' * 40
+ print 'Exception happened during processing of request from',
+ print client_address
+ # traceback doesn't appear to be always be thread safe
+ try:
+ traceback.print_exception(*args)
+ except TypeError:
+ print "Traceback cannot be printed, probably do to a thread safety issue."
+ print '-' * 40
class WindmillConnection(HTTPConnection):
""" Decide on the run if we should do HTTP or HTTPS """
@@ -507,4 +477,9 @@ def connect(self):
sock = _socket_create_connection((self.host, self.port), self.timeout)
self.sock = _ssl_wrap_socket(sock, self.key_file, self.cert_file)
-
+
+class WindmillHTTPSProxyApplication(WindmillProxyApplication):
+ ConnectionClass = WindmillConnection
+
+ def get_connection(self, url):
+ return self.ConnectionClass(url.scheme, url.netloc)
View
342 windmill/server/old_proxy.py
@@ -1,342 +0,0 @@
- # Copyright (c) 2006-2007 Open Source Applications Foundation
-# Copyright (c) 2008-2009 Mikeal Rogers <mikeal.rogers@gmail.com>
-# Copyright (c) 2009 Canonical Ltd.
-# Copyright (c) 2009 Domen Kozar <domen@dev.si>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import windmill
-
-from httplib import HTTPConnection
-import copy
-import sys
-import logging
-import urllib
-logger = logging.getLogger(__name__)
-from forwardmanager import ForwardManager
-if not sys.version.startswith('2.4'):
- from urlparse import urlparse
-else:
- # python 2.4
- from windmill.tools.urlparse_25 import urlparse
-
-first_forward_domains = []
-exclude_from_retry = ['http://sb-ssl.google.com',
- 'https://sb-ssl.google.com',
- 'http://en-us.fxfeeds.mozilla.com',
- 'fxfeeds.mozilla.com',
- 'http://www.google-analytics.com',
- ]
-
-# Note that hoppish conntains proxy-connection, which is pre-HTTP-1.1 and
-# is somewhat nebulous
-_hoppish = {
- 'connection':1, 'keep-alive':1, 'proxy-authenticate':1,
- 'proxy-authorization':1, 'te':1, 'trailers':1, 'transfer-encoding':1,
- 'upgrade':1, 'proxy-connection':1,
- 'p3p':1 #Not actually a hop-by-hop header, just really annoying
- }
-
-# Cache stopping headers
-cache_headers = {'Pragma':'no-cache', 'Cache-Control': 'post-check=0, pre-check=0',
- 'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0',
- 'Expires': '-1'}
-
-cache_removal = [k.lower() for k in cache_headers.keys()]
-cache_additions = cache_headers.items()
-
-def is_hop_by_hop(header):
- """check if the given header is hop_by_hop"""
- return _hoppish.has_key(header.lower())
-
-class IterativeResponse(object):
- def __init__(self, response_instance):
- self.response_instance = response_instance
- self.read_size = response_instance.length / 100
-
- def __iter__(self):
- yield self.response_instance.read(self.read_size)
- while self.response_instance.chunk_left is not None:
- if self.response_instance.chunk_left < self.read_size:
- yield self.response_instance.read()
- self.response_instance.chunk_left = None
- else:
- yield self.response_instance.read(self.read_size)
-
-def get_wsgi_response(response):
-
- if type(response) is str:
- return [response]
- if response.length > 512000:
- return IterativeResponse(response)
- else:
- return [response.read()]
-
-def conditions_pass(e):
- for c in windmill.server.forwarding_conditions:
- if c(e) is False:
- return False
- return True
-
-def proxy_post_redirect_form(environ, action):
- body = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH']))
- parameters = body.split('&')
- inputs = []
- for parameter in parameters:
- parts = parameter.split('=', 1)
- if len(parts) == 1:
- continue
- parts = tuple(unicode(urllib.unquote(part), 'utf-8') for part in parts)
- inputs.append('<input type="hidden" name="%s" value="%s" />' % parts)
- form = """<html><head><title>There is no spoon.</title></head>
-<body onload="document.getElementById('redirect').submit();"
- style="text-align: center;">
- <form id="redirect" action="%s" method="POST">%s</form>
-</body></html>""" % (action, '\n'.join(inputs))
- return form.encode('utf-8')
-
-forward_forms = {}
-
-class WindmillProxyApplication(object):
- """Application to handle requests that need to be proxied"""
-
- def __init__(self):
- self.fmgr = None
- proxyInstances.append(self)
-
- ConnectionClass = HTTPConnection
-
- def handler(self, environ, start_response):
- """Proxy for requests to the actual http server"""
- url = urlparse(environ['reconstructed_url'])
- referer = environ.get('HTTP_REFERER', None)
- test_url = windmill.settings['FORWARDING_TEST_URL']
- if self.fmgr is None and windmill.settings['FORWARDING_TEST_URL'] is not None:
- # Be lazy at creating the forward manager to give
- # FORWARDING_TEST_URL a chance to be set
- self.fmgr = ForwardManager(test_url)
- # Once FORWARDING_TEST_URL is set we should check for cross-domain
- # forward but we must disable for 127.0.0.1 as redirects to 127.0.0.1
- # will cause the browser to error.
- if windmill.settings['FORWARDING_TEST_URL'] is not None and (
- not url.netloc.startswith('127.0.0.1') ) and (
- not url.netloc.startswith('127.0.0.1') ) and (
- conditions_pass(environ) ):
- # Do our domain change magic
- url = urlparse(environ['reconstructed_url'])
- test_target = urlparse(test_url)
- #test_netloc = urlparse(test_url).netloc
-
- if (self.fmgr.is_static_forwarded(url)):
- environ = self.fmgr.forward(url, environ)
- url = self.fmgr.forward_map(url)
-
- elif ( url.scheme+"://"+url.netloc != test_target.scheme+"://"+test_target.netloc ):
- # if the url's network address is not the test URL that has
- # been set we need to return a forward
- environ = self.fmgr.forward(url, environ)
- redirect_url = self.fmgr.forward_map(url).geturl()
- if environ['REQUEST_METHOD'] == 'POST':
- form = proxy_post_redirect_form(environ, redirect_url)
- forward_forms[redirect_url] = form
- start_response("302 Found", [('Location', redirect_url),
- ]+cache_additions)
- logger.debug('Domain change, forwarded to ' + redirect_url)
- return ['']
- elif url.geturl() in forward_forms:
- response = forward_forms[url.geturl()]
- length = str(len(response))
- start_response("200 Ok", [('Content-Type', 'text/html',),
- ('Content-Length', length,),
- ]+cache_additions)
- del forward_forms[url.geturl()]
- return [response]
- elif (self.fmgr.is_forward_mapped(url)):
- orig_url = self.fmgr.forward_unmap(url)
- environ = self.fmgr.change_environ_domain(url, orig_url,
- environ)
- url = orig_url
- elif (not self.fmgr.is_forward_mapped(url) and
- referer is not None and
- self.fmgr.is_forward_mapped(urlparse(referer))):
- # This handles the case that the referer is a url we've already
- # done a cross-domain request for
- orig_referer = self.fmgr.forward_unmap(urlparse(referer))
- orig_url = self.fmgr.forward_to(url, orig_referer)
- environ = self.fmgr.change_environ_domain(url, orig_url, environ)
- url = orig_url
- self.fmgr.forward(orig_url, {}) # Take note of the forwarding
- def make_remote_connection(url, environ):
- # Create connection object
- try:
- connection = self.get_connection(url)
- # Build path
- path = url.geturl().replace(url.scheme+'://'+url.netloc, '')
- except Exception, e:
- logger.exception('Could not Connect')
- return [("501 Gateway error", [('Content-Type', 'text/html')],),
- '<H1>Could not connect:</H1><pre>%s</pre>' % (str(e),)]
-
- # Read in request body if it exists
- body = None
- if environ.has_key('body'):
- body = environ['body']
- elif environ.get('CONTENT_LENGTH'):
- length = int(environ['CONTENT_LENGTH'])
- body = environ['wsgi.input'].read(length)
- environ['body'] = body
-
- # Build headers
- headers = {}
- logger.debug('Environ ; %s' % str(environ))
- for key in environ.keys():
- # Keys that start with HTTP_ are all headers
- if key.startswith('HTTP_'):
- # This is a hacky way of getting the header names right
- value = environ[key]
- key = key.replace('HTTP_', '', 1).swapcase().replace('_', '-')
- if is_hop_by_hop(key) is False:
- headers[key] = value
- if key.lower() == 'location':
- # There should never be a legitimate redirect
- # to /windmill-serv from a remote site
- if '/windmill-serv' in value:
- value = value.split('/windmill-serv')[0]
-
- # Handler headers that aren't HTTP_ in environ
- if environ.get('CONTENT_TYPE'):
- headers['content-type'] = environ['CONTENT_TYPE']
-
- # Add our host if one isn't defined
- if not headers.has_key('host'):
- headers['host'] = environ['SERVER_NAME']
-
- # Make the remote request
- try:
-
- logger.debug('%s %s %s' % (environ['REQUEST_METHOD'], path,
- str(headers)))
- connection.request(environ['REQUEST_METHOD'], path, body=body,
- headers=headers)
- connection.url = url
- return connection
- except Exception, e:
- # We need extra exception handling in the case the server
- # fails in mid connection, it's an edge case but I've seen it
- return [("501 Gateway error", [('Content-Type', 'text/html')],),
- '<H1>Could not make request:</H1><pre>%s</pre>' % (str(e),)]
-
- def retry_known_hosts(url, environ):
- # retry the given request against all the hosts the current session
- # has run against
- if self.fmgr is None:
- return
- for host in self.fmgr.known_hosts():
- orig_url = self.fmgr.forward_to(url, host)
- new_environ = self.fmgr.change_environ_domain(
- self.fmgr.forward_map(orig_url),
- orig_url, environ)
- connection = make_remote_connection(orig_url, new_environ)
- if isinstance(connection, HTTPConnection):
- try:
- new_response = connection.getresponse()
- except:
- return
- if new_response.status > 199 and new_response.status < 399:
- logger.info('Retry success, ' + url.geturl() + ' to ' +
- host.geturl())
- new_response.url = connection.url
- return new_response
- connection = make_remote_connection(url, environ)
- # This following code is ugly. It should be refactored in to some
- # elegant way to decide when to retry, and which URLs to retry.
- # Maybe hand that responsability to the ForwardManager?
- if isinstance(connection, HTTPConnection):
- response = connection.getresponse()
- response.url = connection.url
-
- if environ['REQUEST_METHOD'] == 'POST':
- threshold = 399
- else:
- threshold = 399
-
- if not isinstance(connection, HTTPConnection) or \
- response.status > threshold:
- # if it's not an HTTPConnection object then the request failed
- # so we should retry
- new_response = retry_known_hosts(url, environ)
- if new_response is not None:
- response = new_response
- elif not isinstance(connection, HTTPConnection):
- status = connection[0][0]
- headers = connection[0][1]
- body = connection[1]
- for header in copy.copy(headers):
- if header[0].lower() == 'content-length':
- body.length = int(header[1].strip())
- if header[0].lower() in cache_removal:
- headers.remove(header)
- start_response(status, headers+cache_additions)
- return get_wsgi_response(body)
-
- # Remove hop by hop headers
- headers = self.parse_headers(response)
- if response.status == 404:
- logger.info('Could not fullfill proxy request to ' + url.geturl())
-
- for header in copy.copy(headers):
- if header[0].lower() == 'content-length':
- response.length = int(header[1].strip())
- if header[0].lower() in cache_removal:
- headers.remove(header)
-
- start_response(response.status.__str__()+' '+response.reason,
- headers+cache_additions)
- return get_wsgi_response(response)
-
- def parse_headers(self, response):
- headers = [(x.lower(), y) for x, y in [z.split(':', 1) for z in
- str(response.msg).splitlines() if ':' in z]]
- #all this does is cookie management, which we currently turned off
- #if self.fmgr is not None:
- #self.fmgr.parse_headers(headers, response.url.netloc)
- for header in headers:
- if is_hop_by_hop(header[0]):
- headers.remove(header)
- elif header[0] == 'location':
- # There should never be a legitimate redirect to /windmill-serv
- # from a remote site
- if '/windmill-serv' in header[1]:
- i = headers.index(header)
- location = header[1]
- headers.remove(header)
- headers.insert(i, ('location',
- location.split('/windmill-serv')[0],))
- return headers
-
- def get_connection(self, url):
- """ Factory method for connections """
- connection = self.ConnectionClass(url.netloc)
- return connection
-
- def clearForwardingRegistry(self):
- if self.fmgr is not None:
- self.fmgr.clear()
-
- def __call__(self, environ, start_response):
- return self.handler(environ, start_response)
-
-proxyInstances = []
-def clearForwardingRegistry():
- for p in proxyInstances:
- p.clearForwardingRegistry()
View
782 windmill/server/proxy.py
@@ -1,498 +1,342 @@
-import socket
-import httplib
-import logging
-import urllib
-from time import sleep
-from copy import copy
-from urlparse import urlparse
+# Copyright (c) 2006-2007 Open Source Applications Foundation
+# Copyright (c) 2008-2009 Mikeal Rogers <mikeal.rogers@gmail.com>
+# Copyright (c) 2009 Canonical Ltd.
+# Copyright (c) 2009 Domen Kozar <domen@dev.si>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
-from webenv import Application, Response, Response302, HtmlResponse, Response404
-import httplib2
+import windmill
-logger = logging.getLogger()
+from httplib import HTTPConnection
+import copy
+import sys
+import logging
+import urllib
+logger = logging.getLogger(__name__)
+from forwardmanager import ForwardManager
+if not sys.version.startswith('2.4'):
+ from urlparse import urlparse
+else:
+ # python 2.4
+ from windmill.tools.urlparse_25 import urlparse
-global_exclude = ['http://sb-ssl.google.com',
- 'https://sb-ssl.google.com',
- 'http://en-us.fxfeeds.mozilla.com',
- 'http://fxfeeds.mozilla.com',
- 'https://fxfeeds.mozilla.com',
- 'http://www.google-analytics.com',
- ]
+first_forward_domains = []
+exclude_from_retry = ['http://sb-ssl.google.com',
+ 'https://sb-ssl.google.com',
+ 'http://en-us.fxfeeds.mozilla.com',
+ 'fxfeeds.mozilla.com',
+ 'http://www.google-analytics.com',
+ ]
# Note that hoppish conntains proxy-connection, which is pre-HTTP-1.1 and
# is somewhat nebulous
-hoppish_headers = {'connection':1, 'keep-alive':1, 'proxy-authenticate':1,
- 'proxy-authorization':1, 'te':1, 'trailers':1, 'transfer-encoding':1,
- 'upgrade':1, 'proxy-connection':1,
- 'p3p':1 #Not actually a hop-by-hop header, just really annoying
- }
-
+_hoppish = {
+ 'connection':1, 'keep-alive':1, 'proxy-authenticate':1,
+ 'proxy-authorization':1, 'te':1, 'trailers':1, 'transfer-encoding':1,
+ 'upgrade':1, 'proxy-connection':1,
+ 'p3p':1 #Not actually a hop-by-hop header, just really annoying
+ }
+
# Cache stopping headers
cache_headers = {'Pragma':'no-cache', 'Cache-Control': 'post-check=0, pre-check=0',
'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0',
'Expires': '-1'}
-
+
cache_removal = [k.lower() for k in cache_headers.keys()]
cache_additions = cache_headers.items()
-
-class ProxyResponse(Response):
- def __init__(self, resp):
- self.http2lib_response = resp
- self.status = resp['status']
- if 'reason' in resp:
- self.status += ' '+resp['reason']
- self.httplib_response = resp._response
- # Anything under .5 meg just return
- # Anything over .5 meg return in 100 chunked reads
- if 'content-length' in resp:
- self.httplib_response.length = int(resp['content-length'])
-
- if self.httplib_response.length > 512000:
- self.read_size = self.httplib_response.length / 100
- else:
- self.read_size = None
+def is_hop_by_hop(header):
+ """check if the given header is hop_by_hop"""
+ return _hoppish.has_key(header.lower())
+
+class IterativeResponse(object):
+ def __init__(self, response_instance):
+ self.response_instance = response_instance
+ self.read_size = response_instance.length / 100
+
def __iter__(self):
- if self.read_size is not None:
- yield self.httplib_response.read(self.read_size)
- while self.httplib_response.chunk_left is not None:
- if self.httplib_response.chunk_left < self.read_size:
- yield self.httplib_response.read()
- self.httplib_response.chunk_left = None
- else:
- yield self.httplib_response.read(self.read_size)
- else:
- if self.httplib_response.length:
- body = self.httplib_response.read(self.httplib_response.length)
- yield body
+ yield self.response_instance.read(self.read_size)
+ while self.response_instance.chunk_left is not None:
+ if self.response_instance.chunk_left < self.read_size:
+ yield self.response_instance.read()
+ self.response_instance.chunk_left = None
else:
- body = self.httplib_response.read()
- yield body
- self.httplib_response.conn.busy = False
+ yield self.response_instance.read(self.read_size)
+
+def get_wsgi_response(response):
-class WindmillHttp(httplib2.Http):
- def _conn_request(self, conn, request_uri, method, body, headers):
- """Customized response code for Windmill."""
- for i in range(2):
- try:
- conn.request(method, request_uri, body, headers)
- except socket.gaierror:
- conn.close()
- raise httplib2.ServerNotFoundError("Unable to find the server at %s" % conn.host)
- except (socket.error, httplib.HTTPException):
- # Just because the server closed the connection doesn't apparently mean
- # that the server didn't send a response.
- pass
- try:
- response = conn.getresponse()
- response.conn = conn
- except (socket.error, httplib.HTTPException):
- if i == 0:
- conn.close()
- conn.connect()
- continue
- else:
- raise
- else:
- # Decompression was removed from this section as was HEAD request checks.
- resp = httplib2.Response(response)
- resp._response = response
- break
- # Since content is never checked in the rest of the httplib code we can safely return
- # our own windmill response class here.
- proxy_response = ProxyResponse(resp)
- return (resp, proxy_response)
- def request(self, uri, method="GET", body=None, headers=None, redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None):
- """request handler with thread safety hacked in"""
- try:
- if headers is None:
- headers = {}
- else:
- headers = httplib2._normalize_headers(headers)
- if not headers.has_key('user-agent'):
- headers['user-agent'] = "Python-httplib2/%s" % httplib2.__version__
- uri = httplib2.iri2uri(uri)
- (scheme, authority, request_uri, defrag_uri) = httplib2.urlnorm(uri)
- domain_port = authority.split(":")[0:2]
- if len(domain_port) == 2 and domain_port[1] == '443' and scheme == 'http':
- scheme = 'https'
- authority = domain_port[0]
- conn_key = scheme+":"+authority
- def get_conn(conn_key):
- if conn_key in self.connections:
- conn = self.connections[conn_key]
- if type(conn) is list:
- for c in conn:
- if not getattr(c, 'busy', True):
- return c
- else: return c
- if type(conn) is list:
- return None
- conn = get_conn(conn_key)
- if conn is None:
- if not connection_type:
- connection_type = (scheme == 'https') and httplib2.HTTPSConnectionWithTimeout or httplib2.HTTPConnectionWithTimeout
- certs = list(self.certificates.iter(authority))
- if scheme == 'https' and certs:
- conn = connection_type(authority, key_file=certs[0][0],
- cert_file=certs[0][1], timeout=self.timeout, proxy_info=self.proxy_info)
- self.connections.setdefault(conn_key, []).append(conn)
- else:
- conn = connection_type(authority, timeout=self.timeout, proxy_info=self.proxy_info)
- self.connections.setdefault(conn_key, []).append(conn)
- conn.set_debuglevel(httplib2.debuglevel)
- conn.busy = True
- if method in ["GET", "HEAD"] and 'range' not in headers and 'accept-encoding' not in headers:
- headers['accept-encoding'] = 'deflate, gzip'
- info = httplib2.email.Message.Message()
- cached_value = None
- if self.cache:
- cachekey = defrag_uri
- cached_value = self.cache.get(cachekey)
- if cached_value:
- try:
- info, content = cached_value.split('\r\n\r\n', 1)
- feedparser = httplib2.email.FeedParser.FeedParser()
- feedparser.feed(info)
- info = feedparser.close()
- feedparser._parse = None
- except IndexError:
- self.cache.delete(cachekey)
- cachekey = None
- cached_value = None
- else: cachekey = None
- if method in self.optimistic_concurrency_methods and self.cache and info.has_key('etag') and not self.ignore_etag and 'if-match' not in headers:
- # http://www.w3.org/1999/04/Editing/
- headers['if-match'] = info['etag']
- if method not in ["GET", "HEAD"] and self.cache and cachekey:
- # RFC 2616 Section 13.10
- self.cache.delete(cachekey)
- if cached_value and method in ["GET", "HEAD"] and self.cache and 'range' not in headers:
- if info.has_key('-x-permanent-redirect-url'):
- (response, new_content) = self.request(info['-x-permanent-redirect-url'], "GET", headers = headers, redirections = redirections - 1)
- response.previous = Response(info)
- response.previous.fromcache = True
- else:
- entry_disposition = httplib2._entry_disposition(info, headers)
- if entry_disposition == "FRESH":
- if not cached_value:
- info['status'] = '504'
- content = ""
- response = Response(info)
- if cached_value:
- response.fromcache = True
- return (response, content)
- if entry_disposition == "STALE":
- if info.has_key('etag') and not self.ignore_etag and not 'if-none-match' in headers:
- headers['if-none-match'] = info['etag']
- if info.has_key('last-modified') and not 'last-modified' in headers:
- headers['if-modified-since'] = info['last-modified']
- elif entry_disposition == "TRANSPARENT": pass
- (response, new_content) = self._request(conn, authority, uri, request_uri, method, body, headers, redirections, cachekey)
- if response.status == 304 and method == "GET":
- # Rewrite the cache entry with the new end-to-end headers
- # Take all headers that are in response
- # and overwrite their values in info.
- # unless they are hop-by-hop, or are listed in the connection header.
+ if type(response) is str:
+ return [response]
+ if response.length > 512000:
+ return IterativeResponse(response)
+ else:
+ return [response.read()]
- for key in httplib2._get_end2end_headers(response):
- info[key] = response[key]
- merged_response = Response(info)
- if hasattr(response, "_stale_digest"):
- merged_response._stale_digest = response._stale_digest
- httplib2._updateCache(headers, merged_response, content, self.cache, cachekey)
- response = merged_response
- response.status = 200
- response.fromcache = True
+def conditions_pass(e):
+ for c in windmill.server.forwarding_conditions:
+ if c(e) is False:
+ return False
+ return True
- elif response.status == 200:
- content = new_content
- else:
- self.cache.delete(cachekey)
- content = new_content
- else:
- cc = httplib2._parse_cache_control(headers)
- if cc.has_key('only-if-cached'):
- info['status'] = '504'
- response = Response(info)
- content = ""
- else:
- (response, content) = self._request(conn, authority, uri, request_uri, method, body, headers, redirections, cachekey)
- except Exception, e:
- if self.force_exception_to_status_code:
- if isinstance(e, httplib2.HttpLib2ErrorWithResponse):
- response = e.response
- content = e.content
- response.status = 500
- response.reason = str(e)
- elif isinstance(e, socket.timeout):
- content = "Request Timeout"
- response = Response( {
- "content-type": "text/plain",
- "status": "408",
- "content-length": len(content)
- })
- response.reason = "Request Timeout"
- else:
- content = str(e)
- response = Response( {
- "content-type": "text/plain",
- "status": "400",
- "content-length": len(content)
- })
- response.reason = "Bad Request"
- else: raise
- return (response, content)
+def proxy_post_redirect_form(environ, action):
+ body = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH']))
+ parameters = body.split('&')
+ inputs = []
+ for parameter in parameters:
+ parts = parameter.split('=', 1)
+ if len(parts) == 1:
+ continue
+ parts = tuple(unicode(urllib.unquote(part), 'utf-8') for part in parts)
+ inputs.append('<input type="hidden" name="%s" value="%s" />' % parts)
+ form = """<html><head><title>There is no spoon.</title></head>
+<body onload="document.getElementById('redirect').submit();"
+ style="text-align: center;">
+ <form id="redirect" action="%s" method="POST">%s</form>
+</body></html>""" % (action, '\n'.join(inputs))
+ return form.encode('utf-8')
-class ProxyClient(object):
- def __init__(self, fm):
- self.http = WindmillHttp()
- self.http.follow_redirects = False
- self.fm = fm
+forward_forms = {}
- def is_hop_by_hop(self, header):
- """check if the given header is hop_by_hop"""
- return hoppish_headers.has_key(header.lower())
-
- def clean_request_headers(self, request, host):
- headers = {}
- for key, value in request.headers.items():
- if '/windmill-serv' in value:
- value = value.split('/windmill-serv')[-1]
- if not self.is_hop_by_hop(key):
- if request.host in value:
- headers[key] = value.replace(request.host, host)
- elif request.url.netloc in value:
- headers[key] = value.replace(request.url.netloc, host.split('://')[-1])
- elif request.url.scheme in value:
- headers[key] = value.replace(request.url.scheme, host.split('://')[0])
- else:
- headers[key] = value
- return headers
-
- def set_response_headers(self, resp, response, request_host, proxy_host):
- # TODO: Cookie handler on headers
- for k in ['status', 'reason']:
- if k in resp: del resp[k]
- #[(k,v.replace(proxy_host, request_host),) for k,v in resp.items()]
- response.headers = resp.items() + cache_additions
-
- def make_request(self, request, host):
- if request.proxy_uri in self.fm.form_headers:
- h = self.fm.form_headers.pop(request.proxy_uri)
- h.pop('content-length')
- request.headers.update(h)
- uri = request.proxy_uri.replace(request.host, host, 1)
- headers = self.clean_request_headers(request, host)
- headers['host'] = host[host.rindex('/')+1:]
- resp, response = self.http.request(uri, method=request.method, body=str(request.body),
- headers=headers)
- self.set_response_headers(resp, response, request.host, host)
- response.request = request
- # print resp.status, uri
- return response
-
+class WindmillProxyApplication(object):
+ """Application to handle requests that need to be proxied"""
-class ForwardMap(dict):
- def __init__(self, *args, **kwargs):
- dict.__init__(self, *args, **kwargs)
- self.ordered_hosts = []
- def __setitem__(self, key, value):
- dict.__setitem__(self, key, value)
- self.ordered_hosts.append(value)
- def known_hosts(self, first_forward_hosts, exclude_hosts):
- hosts = first_forward_hosts