Repository: bokeh/bokeh

Issue:
Allow for configuring the IP address of the Bokeh server
I have downloaded the bokeh developer build "conda install -c bokeh/channel/dev bokeh", which has a new Bokeh server "https://github.com/bokeh/bokeh/wiki/Porting-guide:-new-Bokeh-server".

For previous Bokeh server, this capability is added (see "add ability to use a different ip address and port for bokeh-server #383"). 

Please tell me how to use a different ip address for the new bokeh server "boken serve".
BTW, I know that I can change to a different port by using "bokeh serve --port PORT"

Thank you very much!



Discussion:
The new server is not yet complete, which is why it is not in an official release yet. We have a long TODO list of things (some small, like this, others more substantial) to finish before the 0.11 release next month. 

If you have the time and ability, you could add this feature and submit a Pull Request, which would be incredibly helpful and certainly appreciated. It looks like the command line argument parsing is here:

https://github.com/bokeh/bokeh/blob/master/bokeh/command/__init__.py#L75

ping @havocp I don't think there is another issue for this already? Just double checking 

I don't think there is. We have kind of a meta-issue that we need a "config file story" but I think a command line option would be a fine start here.


Resolving patch:
diff --git a/bokeh/command/__init__.py b/bokeh/command/__init__.py
--- a/bokeh/command/__init__.py
+++ b/bokeh/command/__init__.py
@@ -77,9 +77,10 @@ class Serve(ApplicationsSubcommand):
 
     def __init__(self, **kwargs):
         super(Serve, self).__init__(**kwargs)
-        self.parser.add_argument('--port', metavar='PORT', type=int, help="Port to listen on", default=-1)
         self.parser.add_argument('--develop', action='store_true', help="Enable develop-time features that should not be used in production")
         self.parser.add_argument('--show', action='store_true', help="Open server app(s) in a browser")
+        self.parser.add_argument('--port', metavar='PORT', type=int, help="Port to listen on", default=-1)
+        self.parser.add_argument('--address', metavar='ADDRESS', type=str, help="Address to listen on", default=None)
         self.port = 5006
         self.develop_mode = False
         self.server = None
@@ -95,7 +96,7 @@ def func(self, args):
         # TODO make log level a command line option
         logging.basicConfig(level=logging.DEBUG)
 
-        server = Server(applications, port=self.port)
+        server = Server(applications, port=self.port, address=args.address)
 
         if args.show:
             # we have to defer opening in browser until
@@ -107,7 +108,13 @@ def show_callback():
 
         if self.develop_mode:
             log.info("Using develop mode (do not enable --develop in production)")
-        log.info("Starting Bokeh server on port %d with apps at %r", server.port, sorted(applications.keys()))
+        address_string = ''
+        if server.address is not None and server.address != '':
+            address_string = ' address ' + server.address
+        log.info("Starting Bokeh server on port %d%s with applications at paths %r",
+                 server.port,
+                 address_string,
+                 sorted(applications.keys()))
         server.start()
 
 class Html(ApplicationsSubcommand):
diff --git a/bokeh/server/server.py b/bokeh/server/server.py
--- a/bokeh/server/server.py
+++ b/bokeh/server/server.py
@@ -39,13 +39,23 @@ def __init__(self, applications, **kwargs):
         self._port = DEFAULT_SERVER_PORT
         if 'port' in kwargs:
             self._port = kwargs['port']
+        self._address = None
+        if 'address' in kwargs:
+            self._address = kwargs['address']
         # these queue a callback on the ioloop rather than
         # doing the operation immediately (I think - havocp)
         try:
-            self._http.bind(self._port)
+            self._http.bind(self._port, address=self._address)
             self._http.start(1)
-        except OSError:
-            log.critical("Cannot start bokeh server, port %s already in use" % self._port)
+        except OSError as e:
+            import errno
+            if e.errno == errno.EADDRINUSE:
+                log.critical("Cannot start Bokeh server, port %s is already in use", self._port)
+            elif e.errno == errno.EADDRNOTAVAIL:
+                log.critical("Cannot start Bokeh server, address '%s' not available", self._address)
+            else:
+                codename = errno.errorcode[e.errno]
+                log.critical("Cannot start Bokeh server, %s %r", codename, e)
             sys.exit(1)
 
     # TODO this is broken, it's only used by test_client_server.py so fix that then remove this
@@ -57,6 +67,10 @@ def ws_url(self):
     def port(self):
         return self._port
 
+    @property
+    def address(self):
+        return self._address
+
     @property
     def io_loop(self):
         return self._tornado.io_loop


Test patch:
diff --git a/bokeh/tests/test_server.py b/bokeh/tests/test_server.py
new file mode 100644
--- /dev/null
+++ b/bokeh/tests/test_server.py
@@ -0,0 +1,25 @@
+from __future__ import absolute_import, print_function
+
+import unittest
+
+import logging
+from bokeh.server.server import Server
+from bokeh.application import Application
+from tornado.ioloop import IOLoop
+
+logging.basicConfig(level=logging.DEBUG)
+
+# test_client_server.py is the test for the main functionality
+# of the server, here we're testing some properties in isolation
+class TestServer(unittest.TestCase):
+    def test_port(self):
+        loop = IOLoop()
+        loop.make_current()
+        server = Server(Application(), port=1234)
+        assert server.port == 1234
+
+    def test_address(self):
+        loop = IOLoop()
+        loop.make_current()
+        server = Server(Application(), address='0.0.0.0')
+        assert server.address == '0.0.0.0'