Skip to content

Commit

Permalink
Merge pull request #352 from jessepollak/add-wsgi-flexibility-to-server
Browse files Browse the repository at this point in the history
Add wsgi flexibility to server
  • Loading branch information
mattbennett committed Sep 14, 2016
2 parents 96c91c6 + 944869c commit aa96e74
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 11 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Version 2.4.1

Released PENDING

* Enhanced :class: `~nameko.web.server.WebServer` with ``get_wsgi_app`` and ``get_wsgi_server``
to allow easy usage of WSGI middleware and modifications of the WSGI server.
* Enhanced :func:`~nameko.testing.services.replace_dependencies` to allow
specific replacement values to be provided with named arguments.

Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ Jakub Borys (@kooba)
Andriy Kogut (@andriykohut)
Fabrizio Romano (@gianchub)
Alexandre Héaumé (@aheaume)
Jesse Pollak (@jessepollak)
7 changes: 7 additions & 0 deletions docs/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ Pull requests are automatically built with `Travis-CI <https://travis-ci.org/one
* Documentation builds successfully (including spell-check)

See :ref:`getting in touch <getting_in_touch>` for more guidance on contributing.

Running the tests
--------------------

There is a Makefile with convenience commands for running the tests. To run them locally you must have RabbitMQ :ref:`installed <installation>` and running, then call::

$ make test
1 change: 1 addition & 0 deletions docs/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ lifealike
lifecycle
localhost
logstash
makefile
maximize
memcached
microservice
Expand Down
34 changes: 27 additions & 7 deletions nameko/web/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,18 @@ def finish(self):


class WebServer(ProviderCollector, SharedExtension):
"""A SharedExtension that wraps a WSGI interface for processing HTTP
requests.
WebServer can be subclassed to add additional WSGI functionality through
overriding the get_wsgi_server and get_wsgi_app methods.
"""

def __init__(self):
super(WebServer, self).__init__()
self._gt = None
self._sock = None
self._serv = None
self._wsgi_app = None
self._starting = False
self._is_accepting = True

Expand All @@ -77,15 +82,30 @@ def run(self):
def start(self):
if not self._starting:
self._starting = True
self._wsgi_app = WsgiApp(self)
self._sock = eventlet.listen(self.bind_addr)
self._serv = wsgi.Server(self._sock,
self._sock.getsockname(),
self._wsgi_app,
protocol=HttpOnlyProtocol,
debug=False)
self._serv = self.get_wsgi_server(self._sock, self.get_wsgi_app())
self._gt = self.container.spawn_managed_thread(self.run)

def get_wsgi_app(self):
"""Get the WSGI application used to process requests.
This method can be overriden to apply WSGI middleware or replace
the WSGI application all together.
"""
return WsgiApp(self)

def get_wsgi_server(
self, sock, wsgi_app, protocol=HttpOnlyProtocol, debug=False
):
"""Get the WSGI server used to process requests."""
return wsgi.Server(
sock,
sock.getsockname(),
wsgi_app,
protocol=protocol,
debug=debug
)

def stop(self):
self._is_accepting = False
self._gt.kill()
Expand Down
83 changes: 79 additions & 4 deletions test/web/test_server.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
from mock import patch
import pytest
import socket
from eventlet import wsgi
from werkzeug.contrib.fixers import ProxyFix

from nameko.exceptions import ConfigurationError
from nameko.web.handlers import http
from nameko.web.server import BaseHTTPServer, parse_address
from nameko.web.handlers import http, HttpRequestHandler
from nameko.web.server import (
BaseHTTPServer,
parse_address,
WebServer,
HttpOnlyProtocol
)


class ExampleService(object):
Expand All @@ -21,7 +28,8 @@ def do_large(self, request):


def test_broken_pipe(
container_factory, web_config, web_config_port, web_session):
container_factory, web_config, web_config_port, web_session
):
container = container_factory(ExampleService, web_config)
container.start()

Expand All @@ -36,7 +44,8 @@ def test_broken_pipe(


def test_other_error(
container_factory, web_config, web_config_port, web_session):
container_factory, web_config, web_config_port, web_session
):
container = container_factory(ExampleService, web_config)
container.start()

Expand Down Expand Up @@ -69,3 +78,69 @@ def test_parse_address(source, result):

else:
assert parse_address(source) == result


def test_adding_middleware_with_get_wsgi_app(container_factory, web_config):

class CustomWebServer(WebServer):
def get_wsgi_app(self):
# get the original WSGI app that processes http requests
app = super(CustomWebServer, self).get_wsgi_app()
# apply the ProxyFix middleware as an example
return ProxyFix(app, num_proxies=1)

class CustomHttpRequestHandler(HttpRequestHandler):
server = CustomWebServer()

http = CustomHttpRequestHandler.decorator

class CustomServerExampleService(object):
name = 'customserverservice'

@http('GET', '/')
def do_index(self, request):
return ''

container = container_factory(CustomServerExampleService, web_config)
with patch.object(CustomWebServer, 'get_wsgi_server') as get_wsgi_server:
container.start()

wsgi_app = get_wsgi_server.call_args[0][1]
assert isinstance(wsgi_app, ProxyFix)


def test_custom_wsgi_server_is_used(
container_factory, web_config, web_config_port, web_session
):
def custom_wsgi_app(environ, start_response):
start_response('200 OK', [])
return 'Override'

class CustomWebServer(WebServer):
def get_wsgi_server(
self, sock, wsgi_app, protocol=HttpOnlyProtocol, debug=False
):
return wsgi.Server(
sock,
sock.getsockname(),
custom_wsgi_app,
protocol=protocol,
debug=debug
)

class CustomHttpRequestHandler(HttpRequestHandler):
server = CustomWebServer()

http = CustomHttpRequestHandler.decorator

class CustomServerExampleService(object):
name = 'customserverservice'

@http('GET', '/')
def do_index(self, request):
return ''

container = container_factory(CustomServerExampleService, web_config)
container.start()

assert web_session.get('/').text == 'Override'

0 comments on commit aa96e74

Please sign in to comment.