We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Improve Title functionality of panel.serve to be specific for each app served
THE BELOW IS COPIED FROM https://discourse.holoviz.org/t/how-do-i-set-the-app-title-with-panel-serve/550

IN THAT POST I HAVE TRIED DIFFERENT APPROACHES WITHOUT LUCK.

#### SOLUTION

Provide functionality to support use case

-----------

I'm using `panel.serve` to serve a list of Panel applications.

I would like the *index* page to show a custom title in the browser tab like `Corp Analytics`

I would like each app to show a custom title in the browser tab like `App Name`.

**How do I achieve that?**

What I can do and would like is shown below

#### Index Page

![image](https://user-images.githubusercontent.com/42288570/79939878-80bfc600-8460-11ea-9ef6-0bafdf907667.png)

#### App 1 Page

![image](https://user-images.githubusercontent.com/42288570/79939884-85847a00-8460-11ea-8a0f-bcc5f639a792.png)

#### App 2 Page

![image](https://user-images.githubusercontent.com/42288570/79939898-8cab8800-8460-11ea-98c6-3f04e77a1a09.png)

#### Code

```python
import panel as pn

def create_app():

    return pn.Spacer(background="green", sizing_mode="stretch_both")

APP_ROUTES = {

    "app1": create_app,

    "app2": create_app

}

pn.serve(APP_ROUTES, port=14033, title="My App")
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `panel/io/server.py`
Content:
```
1 """
2 Utilities for creating bokeh Server instances.
3 """
4 from __future__ import absolute_import, division, unicode_literals
5 
6 import os
7 import signal
8 import sys
9 import threading
10 import uuid
11 
12 from contextlib import contextmanager
13 from functools import partial
14 from types import FunctionType
15 
16 from bokeh.document.events import ModelChangedEvent
17 from bokeh.server.server import Server
18 from tornado.websocket import WebSocketHandler
19 from tornado.web import RequestHandler
20 from tornado.wsgi import WSGIContainer
21 
22 from .state import state
23 
24 
25 #---------------------------------------------------------------------
26 # Private API
27 #---------------------------------------------------------------------
28 
29 INDEX_HTML = os.path.join(os.path.dirname(__file__), '..', '_templates', "index.html")
30 
31 def _origin_url(url):
32     if url.startswith("http"):
33         url = url.split("//")[1]
34     return url
35 
36 
37 def _server_url(url, port):
38     if url.startswith("http"):
39         return '%s:%d%s' % (url.rsplit(':', 1)[0], port, "/")
40     else:
41         return 'http://%s:%d%s' % (url.split(':')[0], port, "/")
42 
43 def _eval_panel(panel, server_id, title, location, doc):
44     from ..template import BaseTemplate
45     from ..pane import panel as as_panel
46 
47     if isinstance(panel, FunctionType):
48         panel = panel()
49     if isinstance(panel, BaseTemplate):
50         return panel._modify_doc(server_id, title, doc, location)
51     return as_panel(panel)._modify_doc(server_id, title, doc, location)
52 
53 #---------------------------------------------------------------------
54 # Public API
55 #---------------------------------------------------------------------
56 
57 
58 @contextmanager
59 def unlocked():
60     """
61     Context manager which unlocks a Document and dispatches
62     ModelChangedEvents triggered in the context body to all sockets
63     on current sessions.
64     """
65     curdoc = state.curdoc
66     if curdoc is None or curdoc.session_context is None:
67         yield
68         return
69     connections = curdoc.session_context.session._subscribed_connections
70 
71     hold = curdoc._hold
72     if hold:
73         old_events = list(curdoc._held_events)
74     else:
75         old_events = []
76         curdoc.hold()
77     try:
78         yield
79         events = []
80         for conn in connections:
81             socket = conn._socket
82             if hasattr(socket, 'write_lock') and socket.write_lock._block._value == 0:
83                 state._locks.add(socket)
84             locked = socket in state._locks
85             for event in curdoc._held_events:
86                 if (isinstance(event, ModelChangedEvent) and event not in old_events
87                     and hasattr(socket, 'write_message') and not locked):
88                     msg = conn.protocol.create('PATCH-DOC', [event])
89                     WebSocketHandler.write_message(socket, msg.header_json)
90                     WebSocketHandler.write_message(socket, msg.metadata_json)
91                     WebSocketHandler.write_message(socket, msg.content_json)
92                     for header, payload in msg._buffers:
93                         WebSocketHandler.write_message(socket, header)
94                         WebSocketHandler.write_message(socket, payload, binary=True)
95                 elif event not in events:
96                     events.append(event)
97         curdoc._held_events = events
98     finally:
99         if not hold:
100             curdoc.unhold()
101 
102 
103 def serve(panels, port=0, websocket_origin=None, loop=None, show=True,
104           start=True, title=None, verbose=True, location=True, **kwargs):
105     """
106     Allows serving one or more panel objects on a single server.
107     The panels argument should be either a Panel object or a function
108     returning a Panel object or a dictionary of these two. If a 
109     dictionary is supplied the keys represent the slugs at which
110     each app is served, e.g. `serve({'app': panel1, 'app2': panel2})`
111     will serve apps at /app and /app2 on the server.
112 
113     Arguments
114     ---------
115     panel: Viewable, function or {str: Viewable}
116       A Panel object, a function returning a Panel object or a
117       dictionary mapping from the URL slug to either.
118     port: int (optional, default=0)
119       Allows specifying a specific port
120     websocket_origin: str or list(str) (optional)
121       A list of hosts that can connect to the websocket.
122 
123       This is typically required when embedding a server app in
124       an external web site.
125 
126       If None, "localhost" is used.
127     loop : tornado.ioloop.IOLoop (optional, default=IOLoop.current())
128       The tornado IOLoop to run the Server on
129     show : boolean (optional, default=False)
130       Whether to open the server in a new browser tab on start
131     start : boolean(optional, default=False)
132       Whether to start the Server
133     title: str (optional, default=None)
134       An HTML title for the application
135     verbose: boolean (optional, default=True)
136       Whether to print the address and port
137     location : boolean or panel.io.location.Location
138       Whether to create a Location component to observe and
139       set the URL location.
140     kwargs: dict
141       Additional keyword arguments to pass to Server instance
142     """
143     return get_server(panels, port, websocket_origin, loop, show, start,
144                       title, verbose, location, **kwargs)
145 
146 
147 class ProxyFallbackHandler(RequestHandler):
148     """A `RequestHandler` that wraps another HTTP server callback and
149     proxies the subpath.
150     """
151 
152     def initialize(self, fallback, proxy=None):
153         self.fallback = fallback
154         self.proxy = proxy
155 
156     def prepare(self):
157         if self.proxy:
158             self.request.path = self.request.path.replace(self.proxy, '')
159         self.fallback(self.request)
160         self._finished = True
161         self.on_finish()
162 
163 
164 def get_server(panel, port=0, websocket_origin=None, loop=None,
165                show=False, start=False, title=None, verbose=False,
166                location=True, **kwargs):
167     """
168     Returns a Server instance with this panel attached as the root
169     app.
170 
171     Arguments
172     ---------
173     panel: Viewable, function or {str: Viewable}
174       A Panel object, a function returning a Panel object or a
175       dictionary mapping from the URL slug to either.
176     port: int (optional, default=0)
177       Allows specifying a specific port
178     websocket_origin: str or list(str) (optional)
179       A list of hosts that can connect to the websocket.
180 
181       This is typically required when embedding a server app in
182       an external web site.
183 
184       If None, "localhost" is used.
185     loop : tornado.ioloop.IOLoop (optional, default=IOLoop.current())
186       The tornado IOLoop to run the Server on
187     show : boolean (optional, default=False)
188       Whether to open the server in a new browser tab on start
189     start : boolean(optional, default=False)
190       Whether to start the Server
191     title: str (optional, default=None)
192       An HTML title for the application
193     verbose: boolean (optional, default=False)
194       Whether to report the address and port
195     location : boolean or panel.io.location.Location
196       Whether to create a Location component to observe and
197       set the URL location.
198     kwargs: dict
199       Additional keyword arguments to pass to Server instance
200 
201     Returns
202     -------
203     server : bokeh.server.server.Server
204       Bokeh Server instance running this panel
205     """
206     from tornado.ioloop import IOLoop
207 
208     server_id = kwargs.pop('server_id', uuid.uuid4().hex)
209     kwargs['extra_patterns'] = extra_patterns = kwargs.get('extra_patterns', [])
210     if isinstance(panel, dict):
211         apps = {}
212         for slug, app in panel.items():
213             slug = slug if slug.startswith('/') else '/'+slug
214             if 'flask' in sys.modules:
215                 from flask import Flask
216                 if isinstance(app, Flask):
217                     wsgi = WSGIContainer(app)
218                     if slug == '/':
219                         raise ValueError('Flask apps must be served on a subpath.')
220                     if not slug.endswith('/'):
221                         slug += '/'
222                     extra_patterns.append(('^'+slug+'.*', ProxyFallbackHandler,
223                                            dict(fallback=wsgi, proxy=slug)))
224                     continue
225             apps[slug] = partial(_eval_panel, app, server_id, title, location)
226     else:
227         apps = {'/': partial(_eval_panel, panel, server_id, title, location)}
228 
229     opts = dict(kwargs)
230     if loop:
231         loop.make_current()
232         opts['io_loop'] = loop
233     else:
234         opts['io_loop'] = IOLoop.current()
235 
236     if 'index' not in opts:
237         opts['index'] = INDEX_HTML
238 
239     if websocket_origin:
240         if not isinstance(websocket_origin, list):
241             websocket_origin = [websocket_origin]
242         opts['allow_websocket_origin'] = websocket_origin
243 
244     server = Server(apps, port=port, **opts)
245     if verbose:
246         address = server.address or 'localhost'
247         print("Launching server at http://%s:%s" % (address, server.port))
248 
249     state._servers[server_id] = (server, panel, [])
250 
251     if show:
252         def show_callback():
253             server.show('/')
254         server.io_loop.add_callback(show_callback)
255 
256     def sig_exit(*args, **kwargs):
257         server.io_loop.add_callback_from_signal(do_stop)
258 
259     def do_stop(*args, **kwargs):
260         server.io_loop.stop()
261 
262     try:
263         signal.signal(signal.SIGINT, sig_exit)
264     except ValueError:
265         pass # Can't use signal on a thread
266 
267     if start:
268         server.start()
269         try:
270             server.io_loop.start()
271         except RuntimeError:
272             pass
273     return server
274 
275 
276 class StoppableThread(threading.Thread):
277     """Thread class with a stop() method."""
278 
279     def __init__(self, io_loop=None, timeout=1000, **kwargs):
280         from tornado import ioloop
281         super(StoppableThread, self).__init__(**kwargs)
282         self._stop_event = threading.Event()
283         self.io_loop = io_loop
284         self._cb = ioloop.PeriodicCallback(self._check_stopped, timeout)
285         self._cb.start()
286 
287     def _check_stopped(self):
288         if self.stopped:
289             self._cb.stop()
290             self.io_loop.stop()
291 
292     def run(self):
293         if hasattr(self, '_target'):
294             target, args, kwargs = self._target, self._args, self._kwargs
295         else:
296             target, args, kwargs = self._Thread__target, self._Thread__args, self._Thread__kwargs
297         if not target:
298             return
299         bokeh_server = None
300         try:
301             bokeh_server = target(*args, **kwargs)
302         finally:
303             if isinstance(bokeh_server, Server):
304                 bokeh_server.stop()
305             if hasattr(self, '_target'):
306                 del self._target, self._args, self._kwargs
307             else:
308                 del self._Thread__target, self._Thread__args, self._Thread__kwargs
309 
310     def stop(self):
311         self._stop_event.set()
312 
313     @property
314     def stopped(self):
315         return self._stop_event.is_set()
316
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/panel/io/server.py b/panel/io/server.py
--- a/panel/io/server.py
+++ b/panel/io/server.py
@@ -130,8 +130,9 @@
       Whether to open the server in a new browser tab on start
     start : boolean(optional, default=False)
       Whether to start the Server
-    title: str (optional, default=None)
-      An HTML title for the application
+    title: str or {str: str} (optional, default=None)
+      An HTML title for the application or a dictionary mapping
+      from the URL slug to a customized title
     verbose: boolean (optional, default=True)
       Whether to print the address and port
     location : boolean or panel.io.location.Location
@@ -188,8 +189,9 @@
       Whether to open the server in a new browser tab on start
     start : boolean(optional, default=False)
       Whether to start the Server
-    title: str (optional, default=None)
-      An HTML title for the application
+    title: str or {str: str} (optional, default=None)
+      An HTML title for the application or a dictionary mapping
+      from the URL slug to a customized title
     verbose: boolean (optional, default=False)
       Whether to report the address and port
     location : boolean or panel.io.location.Location
@@ -210,6 +212,16 @@
     if isinstance(panel, dict):
         apps = {}
         for slug, app in panel.items():
+            if isinstance(title, dict):
+                try:
+                    title_ = title[slug]
+                except KeyError:
+                    raise KeyError(
+                        "Keys of the title dictionnary and of the apps "
+                        f"dictionary must match. No {slug} key found in the "
+                        "title dictionnary.") 
+            else:
+                title_ = title
             slug = slug if slug.startswith('/') else '/'+slug
             if 'flask' in sys.modules:
                 from flask import Flask
@@ -222,7 +234,7 @@
                     extra_patterns.append(('^'+slug+'.*', ProxyFallbackHandler,
                                            dict(fallback=wsgi, proxy=slug)))
                     continue
-            apps[slug] = partial(_eval_panel, app, server_id, title, location)
+            apps[slug] = partial(_eval_panel, app, server_id, title_, location)
     else:
         apps = {'/': partial(_eval_panel, panel, server_id, title, location)}

{"golden_diff": "diff --git a/panel/io/server.py b/panel/io/server.py\n--- a/panel/io/server.py\n+++ b/panel/io/server.py\n@@ -130,8 +130,9 @@\n       Whether to open the server in a new browser tab on start\n     start : boolean(optional, default=False)\n       Whether to start the Server\n-    title: str (optional, default=None)\n-      An HTML title for the application\n+    title: str or {str: str} (optional, default=None)\n+      An HTML title for the application or a dictionary mapping\n+      from the URL slug to a customized title\n     verbose: boolean (optional, default=True)\n       Whether to print the address and port\n     location : boolean or panel.io.location.Location\n@@ -188,8 +189,9 @@\n       Whether to open the server in a new browser tab on start\n     start : boolean(optional, default=False)\n       Whether to start the Server\n-    title: str (optional, default=None)\n-      An HTML title for the application\n+    title: str or {str: str} (optional, default=None)\n+      An HTML title for the application or a dictionary mapping\n+      from the URL slug to a customized title\n     verbose: boolean (optional, default=False)\n       Whether to report the address and port\n     location : boolean or panel.io.location.Location\n@@ -210,6 +212,16 @@\n     if isinstance(panel, dict):\n         apps = {}\n         for slug, app in panel.items():\n+            if isinstance(title, dict):\n+                try:\n+                    title_ = title[slug]\n+                except KeyError:\n+                    raise KeyError(\n+                        \"Keys of the title dictionnary and of the apps \"\n+                        f\"dictionary must match. No {slug} key found in the \"\n+                        \"title dictionnary.\") \n+            else:\n+                title_ = title\n             slug = slug if slug.startswith('/') else '/'+slug\n             if 'flask' in sys.modules:\n                 from flask import Flask\n@@ -222,7 +234,7 @@\n                     extra_patterns.append(('^'+slug+'.*', ProxyFallbackHandler,\n                                            dict(fallback=wsgi, proxy=slug)))\n                     continue\n-            apps[slug] = partial(_eval_panel, app, server_id, title, location)\n+            apps[slug] = partial(_eval_panel, app, server_id, title_, location)\n     else:\n         apps = {'/': partial(_eval_panel, panel, server_id, title, location)}\n", "issue": "Improve Title functionality of panel.serve to be specific for each app served\nTHE BELOW IS COPIED FROM https://discourse.holoviz.org/t/how-do-i-set-the-app-title-with-panel-serve/550\r\n\r\nIN THAT POST I HAVE TRIED DIFFERENT APPROACHES WITHOUT LUCK.\r\n\r\n#### SOLUTION\r\n\r\nProvide functionality to support use case\r\n\r\n-----------\r\n\r\nI'm using `panel.serve` to serve a list of Panel applications.\r\n\r\nI would like the *index* page to show a custom title in the browser tab like `Corp Analytics`\r\n\r\nI would like each app to show a custom title in the browser tab like `App Name`.\r\n\r\n**How do I achieve that?**\r\n\r\nWhat I can do and would like is shown below\r\n\r\n#### Index Page\r\n\r\n![image](https://user-images.githubusercontent.com/42288570/79939878-80bfc600-8460-11ea-9ef6-0bafdf907667.png)\r\n\r\n#### App 1 Page\r\n\r\n![image](https://user-images.githubusercontent.com/42288570/79939884-85847a00-8460-11ea-8a0f-bcc5f639a792.png)\r\n\r\n#### App 2 Page\r\n\r\n![image](https://user-images.githubusercontent.com/42288570/79939898-8cab8800-8460-11ea-98c6-3f04e77a1a09.png)\r\n\r\n#### Code\r\n\r\n```python\r\nimport panel as pn\r\n\r\ndef create_app():\r\n\r\n    return pn.Spacer(background=\"green\", sizing_mode=\"stretch_both\")\r\n\r\nAPP_ROUTES = {\r\n\r\n    \"app1\": create_app,\r\n\r\n    \"app2\": create_app\r\n\r\n}\r\n\r\npn.serve(APP_ROUTES, port=14033, title=\"My App\")\r\n```\n", "before_files": [{"content": "\"\"\"\nUtilities for creating bokeh Server instances.\n\"\"\"\nfrom __future__ import absolute_import, division, unicode_literals\n\nimport os\nimport signal\nimport sys\nimport threading\nimport uuid\n\nfrom contextlib import contextmanager\nfrom functools import partial\nfrom types import FunctionType\n\nfrom bokeh.document.events import ModelChangedEvent\nfrom bokeh.server.server import Server\nfrom tornado.websocket import WebSocketHandler\nfrom tornado.web import RequestHandler\nfrom tornado.wsgi import WSGIContainer\n\nfrom .state import state\n\n\n#---------------------------------------------------------------------\n# Private API\n#---------------------------------------------------------------------\n\nINDEX_HTML = os.path.join(os.path.dirname(__file__), '..', '_templates', \"index.html\")\n\ndef _origin_url(url):\n    if url.startswith(\"http\"):\n        url = url.split(\"//\")[1]\n    return url\n\n\ndef _server_url(url, port):\n    if url.startswith(\"http\"):\n        return '%s:%d%s' % (url.rsplit(':', 1)[0], port, \"/\")\n    else:\n        return 'http://%s:%d%s' % (url.split(':')[0], port, \"/\")\n\ndef _eval_panel(panel, server_id, title, location, doc):\n    from ..template import BaseTemplate\n    from ..pane import panel as as_panel\n\n    if isinstance(panel, FunctionType):\n        panel = panel()\n    if isinstance(panel, BaseTemplate):\n        return panel._modify_doc(server_id, title, doc, location)\n    return as_panel(panel)._modify_doc(server_id, title, doc, location)\n\n#---------------------------------------------------------------------\n# Public API\n#---------------------------------------------------------------------\n\n\n@contextmanager\ndef unlocked():\n    \"\"\"\n    Context manager which unlocks a Document and dispatches\n    ModelChangedEvents triggered in the context body to all sockets\n    on current sessions.\n    \"\"\"\n    curdoc = state.curdoc\n    if curdoc is None or curdoc.session_context is None:\n        yield\n        return\n    connections = curdoc.session_context.session._subscribed_connections\n\n    hold = curdoc._hold\n    if hold:\n        old_events = list(curdoc._held_events)\n    else:\n        old_events = []\n        curdoc.hold()\n    try:\n        yield\n        events = []\n        for conn in connections:\n            socket = conn._socket\n            if hasattr(socket, 'write_lock') and socket.write_lock._block._value == 0:\n                state._locks.add(socket)\n            locked = socket in state._locks\n            for event in curdoc._held_events:\n                if (isinstance(event, ModelChangedEvent) and event not in old_events\n                    and hasattr(socket, 'write_message') and not locked):\n                    msg = conn.protocol.create('PATCH-DOC', [event])\n                    WebSocketHandler.write_message(socket, msg.header_json)\n                    WebSocketHandler.write_message(socket, msg.metadata_json)\n                    WebSocketHandler.write_message(socket, msg.content_json)\n                    for header, payload in msg._buffers:\n                        WebSocketHandler.write_message(socket, header)\n                        WebSocketHandler.write_message(socket, payload, binary=True)\n                elif event not in events:\n                    events.append(event)\n        curdoc._held_events = events\n    finally:\n        if not hold:\n            curdoc.unhold()\n\n\ndef serve(panels, port=0, websocket_origin=None, loop=None, show=True,\n          start=True, title=None, verbose=True, location=True, **kwargs):\n    \"\"\"\n    Allows serving one or more panel objects on a single server.\n    The panels argument should be either a Panel object or a function\n    returning a Panel object or a dictionary of these two. If a \n    dictionary is supplied the keys represent the slugs at which\n    each app is served, e.g. `serve({'app': panel1, 'app2': panel2})`\n    will serve apps at /app and /app2 on the server.\n\n    Arguments\n    ---------\n    panel: Viewable, function or {str: Viewable}\n      A Panel object, a function returning a Panel object or a\n      dictionary mapping from the URL slug to either.\n    port: int (optional, default=0)\n      Allows specifying a specific port\n    websocket_origin: str or list(str) (optional)\n      A list of hosts that can connect to the websocket.\n\n      This is typically required when embedding a server app in\n      an external web site.\n\n      If None, \"localhost\" is used.\n    loop : tornado.ioloop.IOLoop (optional, default=IOLoop.current())\n      The tornado IOLoop to run the Server on\n    show : boolean (optional, default=False)\n      Whether to open the server in a new browser tab on start\n    start : boolean(optional, default=False)\n      Whether to start the Server\n    title: str (optional, default=None)\n      An HTML title for the application\n    verbose: boolean (optional, default=True)\n      Whether to print the address and port\n    location : boolean or panel.io.location.Location\n      Whether to create a Location component to observe and\n      set the URL location.\n    kwargs: dict\n      Additional keyword arguments to pass to Server instance\n    \"\"\"\n    return get_server(panels, port, websocket_origin, loop, show, start,\n                      title, verbose, location, **kwargs)\n\n\nclass ProxyFallbackHandler(RequestHandler):\n    \"\"\"A `RequestHandler` that wraps another HTTP server callback and\n    proxies the subpath.\n    \"\"\"\n\n    def initialize(self, fallback, proxy=None):\n        self.fallback = fallback\n        self.proxy = proxy\n\n    def prepare(self):\n        if self.proxy:\n            self.request.path = self.request.path.replace(self.proxy, '')\n        self.fallback(self.request)\n        self._finished = True\n        self.on_finish()\n\n\ndef get_server(panel, port=0, websocket_origin=None, loop=None,\n               show=False, start=False, title=None, verbose=False,\n               location=True, **kwargs):\n    \"\"\"\n    Returns a Server instance with this panel attached as the root\n    app.\n\n    Arguments\n    ---------\n    panel: Viewable, function or {str: Viewable}\n      A Panel object, a function returning a Panel object or a\n      dictionary mapping from the URL slug to either.\n    port: int (optional, default=0)\n      Allows specifying a specific port\n    websocket_origin: str or list(str) (optional)\n      A list of hosts that can connect to the websocket.\n\n      This is typically required when embedding a server app in\n      an external web site.\n\n      If None, \"localhost\" is used.\n    loop : tornado.ioloop.IOLoop (optional, default=IOLoop.current())\n      The tornado IOLoop to run the Server on\n    show : boolean (optional, default=False)\n      Whether to open the server in a new browser tab on start\n    start : boolean(optional, default=False)\n      Whether to start the Server\n    title: str (optional, default=None)\n      An HTML title for the application\n    verbose: boolean (optional, default=False)\n      Whether to report the address and port\n    location : boolean or panel.io.location.Location\n      Whether to create a Location component to observe and\n      set the URL location.\n    kwargs: dict\n      Additional keyword arguments to pass to Server instance\n\n    Returns\n    -------\n    server : bokeh.server.server.Server\n      Bokeh Server instance running this panel\n    \"\"\"\n    from tornado.ioloop import IOLoop\n\n    server_id = kwargs.pop('server_id', uuid.uuid4().hex)\n    kwargs['extra_patterns'] = extra_patterns = kwargs.get('extra_patterns', [])\n    if isinstance(panel, dict):\n        apps = {}\n        for slug, app in panel.items():\n            slug = slug if slug.startswith('/') else '/'+slug\n            if 'flask' in sys.modules:\n                from flask import Flask\n                if isinstance(app, Flask):\n                    wsgi = WSGIContainer(app)\n                    if slug == '/':\n                        raise ValueError('Flask apps must be served on a subpath.')\n                    if not slug.endswith('/'):\n                        slug += '/'\n                    extra_patterns.append(('^'+slug+'.*', ProxyFallbackHandler,\n                                           dict(fallback=wsgi, proxy=slug)))\n                    continue\n            apps[slug] = partial(_eval_panel, app, server_id, title, location)\n    else:\n        apps = {'/': partial(_eval_panel, panel, server_id, title, location)}\n\n    opts = dict(kwargs)\n    if loop:\n        loop.make_current()\n        opts['io_loop'] = loop\n    else:\n        opts['io_loop'] = IOLoop.current()\n\n    if 'index' not in opts:\n        opts['index'] = INDEX_HTML\n\n    if websocket_origin:\n        if not isinstance(websocket_origin, list):\n            websocket_origin = [websocket_origin]\n        opts['allow_websocket_origin'] = websocket_origin\n\n    server = Server(apps, port=port, **opts)\n    if verbose:\n        address = server.address or 'localhost'\n        print(\"Launching server at http://%s:%s\" % (address, server.port))\n\n    state._servers[server_id] = (server, panel, [])\n\n    if show:\n        def show_callback():\n            server.show('/')\n        server.io_loop.add_callback(show_callback)\n\n    def sig_exit(*args, **kwargs):\n        server.io_loop.add_callback_from_signal(do_stop)\n\n    def do_stop(*args, **kwargs):\n        server.io_loop.stop()\n\n    try:\n        signal.signal(signal.SIGINT, sig_exit)\n    except ValueError:\n        pass # Can't use signal on a thread\n\n    if start:\n        server.start()\n        try:\n            server.io_loop.start()\n        except RuntimeError:\n            pass\n    return server\n\n\nclass StoppableThread(threading.Thread):\n    \"\"\"Thread class with a stop() method.\"\"\"\n\n    def __init__(self, io_loop=None, timeout=1000, **kwargs):\n        from tornado import ioloop\n        super(StoppableThread, self).__init__(**kwargs)\n        self._stop_event = threading.Event()\n        self.io_loop = io_loop\n        self._cb = ioloop.PeriodicCallback(self._check_stopped, timeout)\n        self._cb.start()\n\n    def _check_stopped(self):\n        if self.stopped:\n            self._cb.stop()\n            self.io_loop.stop()\n\n    def run(self):\n        if hasattr(self, '_target'):\n            target, args, kwargs = self._target, self._args, self._kwargs\n        else:\n            target, args, kwargs = self._Thread__target, self._Thread__args, self._Thread__kwargs\n        if not target:\n            return\n        bokeh_server = None\n        try:\n            bokeh_server = target(*args, **kwargs)\n        finally:\n            if isinstance(bokeh_server, Server):\n                bokeh_server.stop()\n            if hasattr(self, '_target'):\n                del self._target, self._args, self._kwargs\n            else:\n                del self._Thread__target, self._Thread__args, self._Thread__kwargs\n\n    def stop(self):\n        self._stop_event.set()\n\n    @property\n    def stopped(self):\n        return self._stop_event.is_set()\n", "path": "panel/io/server.py"}], "after_files": [{"content": "\"\"\"\nUtilities for creating bokeh Server instances.\n\"\"\"\nfrom __future__ import absolute_import, division, unicode_literals\n\nimport os\nimport signal\nimport sys\nimport threading\nimport uuid\n\nfrom contextlib import contextmanager\nfrom functools import partial\nfrom types import FunctionType\n\nfrom bokeh.document.events import ModelChangedEvent\nfrom bokeh.server.server import Server\nfrom tornado.websocket import WebSocketHandler\nfrom tornado.web import RequestHandler\nfrom tornado.wsgi import WSGIContainer\n\nfrom .state import state\n\n\n#---------------------------------------------------------------------\n# Private API\n#---------------------------------------------------------------------\n\nINDEX_HTML = os.path.join(os.path.dirname(__file__), '..', '_templates', \"index.html\")\n\ndef _origin_url(url):\n    if url.startswith(\"http\"):\n        url = url.split(\"//\")[1]\n    return url\n\n\ndef _server_url(url, port):\n    if url.startswith(\"http\"):\n        return '%s:%d%s' % (url.rsplit(':', 1)[0], port, \"/\")\n    else:\n        return 'http://%s:%d%s' % (url.split(':')[0], port, \"/\")\n\ndef _eval_panel(panel, server_id, title, location, doc):\n    from ..template import BaseTemplate\n    from ..pane import panel as as_panel\n\n    if isinstance(panel, FunctionType):\n        panel = panel()\n    if isinstance(panel, BaseTemplate):\n        return panel._modify_doc(server_id, title, doc, location)\n    return as_panel(panel)._modify_doc(server_id, title, doc, location)\n\n#---------------------------------------------------------------------\n# Public API\n#---------------------------------------------------------------------\n\n\n@contextmanager\ndef unlocked():\n    \"\"\"\n    Context manager which unlocks a Document and dispatches\n    ModelChangedEvents triggered in the context body to all sockets\n    on current sessions.\n    \"\"\"\n    curdoc = state.curdoc\n    if curdoc is None or curdoc.session_context is None:\n        yield\n        return\n    connections = curdoc.session_context.session._subscribed_connections\n\n    hold = curdoc._hold\n    if hold:\n        old_events = list(curdoc._held_events)\n    else:\n        old_events = []\n        curdoc.hold()\n    try:\n        yield\n        events = []\n        for conn in connections:\n            socket = conn._socket\n            if hasattr(socket, 'write_lock') and socket.write_lock._block._value == 0:\n                state._locks.add(socket)\n            locked = socket in state._locks\n            for event in curdoc._held_events:\n                if (isinstance(event, ModelChangedEvent) and event not in old_events\n                    and hasattr(socket, 'write_message') and not locked):\n                    msg = conn.protocol.create('PATCH-DOC', [event])\n                    WebSocketHandler.write_message(socket, msg.header_json)\n                    WebSocketHandler.write_message(socket, msg.metadata_json)\n                    WebSocketHandler.write_message(socket, msg.content_json)\n                    for header, payload in msg._buffers:\n                        WebSocketHandler.write_message(socket, header)\n                        WebSocketHandler.write_message(socket, payload, binary=True)\n                elif event not in events:\n                    events.append(event)\n        curdoc._held_events = events\n    finally:\n        if not hold:\n            curdoc.unhold()\n\n\ndef serve(panels, port=0, websocket_origin=None, loop=None, show=True,\n          start=True, title=None, verbose=True, location=True, **kwargs):\n    \"\"\"\n    Allows serving one or more panel objects on a single server.\n    The panels argument should be either a Panel object or a function\n    returning a Panel object or a dictionary of these two. If a \n    dictionary is supplied the keys represent the slugs at which\n    each app is served, e.g. `serve({'app': panel1, 'app2': panel2})`\n    will serve apps at /app and /app2 on the server.\n\n    Arguments\n    ---------\n    panel: Viewable, function or {str: Viewable}\n      A Panel object, a function returning a Panel object or a\n      dictionary mapping from the URL slug to either.\n    port: int (optional, default=0)\n      Allows specifying a specific port\n    websocket_origin: str or list(str) (optional)\n      A list of hosts that can connect to the websocket.\n\n      This is typically required when embedding a server app in\n      an external web site.\n\n      If None, \"localhost\" is used.\n    loop : tornado.ioloop.IOLoop (optional, default=IOLoop.current())\n      The tornado IOLoop to run the Server on\n    show : boolean (optional, default=False)\n      Whether to open the server in a new browser tab on start\n    start : boolean(optional, default=False)\n      Whether to start the Server\n    title: str or {str: str} (optional, default=None)\n      An HTML title for the application or a dictionary mapping\n      from the URL slug to a customized title\n    verbose: boolean (optional, default=True)\n      Whether to print the address and port\n    location : boolean or panel.io.location.Location\n      Whether to create a Location component to observe and\n      set the URL location.\n    kwargs: dict\n      Additional keyword arguments to pass to Server instance\n    \"\"\"\n    return get_server(panels, port, websocket_origin, loop, show, start,\n                      title, verbose, location, **kwargs)\n\n\nclass ProxyFallbackHandler(RequestHandler):\n    \"\"\"A `RequestHandler` that wraps another HTTP server callback and\n    proxies the subpath.\n    \"\"\"\n\n    def initialize(self, fallback, proxy=None):\n        self.fallback = fallback\n        self.proxy = proxy\n\n    def prepare(self):\n        if self.proxy:\n            self.request.path = self.request.path.replace(self.proxy, '')\n        self.fallback(self.request)\n        self._finished = True\n        self.on_finish()\n\n\ndef get_server(panel, port=0, websocket_origin=None, loop=None,\n               show=False, start=False, title=None, verbose=False,\n               location=True, **kwargs):\n    \"\"\"\n    Returns a Server instance with this panel attached as the root\n    app.\n\n    Arguments\n    ---------\n    panel: Viewable, function or {str: Viewable}\n      A Panel object, a function returning a Panel object or a\n      dictionary mapping from the URL slug to either.\n    port: int (optional, default=0)\n      Allows specifying a specific port\n    websocket_origin: str or list(str) (optional)\n      A list of hosts that can connect to the websocket.\n\n      This is typically required when embedding a server app in\n      an external web site.\n\n      If None, \"localhost\" is used.\n    loop : tornado.ioloop.IOLoop (optional, default=IOLoop.current())\n      The tornado IOLoop to run the Server on\n    show : boolean (optional, default=False)\n      Whether to open the server in a new browser tab on start\n    start : boolean(optional, default=False)\n      Whether to start the Server\n    title: str or {str: str} (optional, default=None)\n      An HTML title for the application or a dictionary mapping\n      from the URL slug to a customized title\n    verbose: boolean (optional, default=False)\n      Whether to report the address and port\n    location : boolean or panel.io.location.Location\n      Whether to create a Location component to observe and\n      set the URL location.\n    kwargs: dict\n      Additional keyword arguments to pass to Server instance\n\n    Returns\n    -------\n    server : bokeh.server.server.Server\n      Bokeh Server instance running this panel\n    \"\"\"\n    from tornado.ioloop import IOLoop\n\n    server_id = kwargs.pop('server_id', uuid.uuid4().hex)\n    kwargs['extra_patterns'] = extra_patterns = kwargs.get('extra_patterns', [])\n    if isinstance(panel, dict):\n        apps = {}\n        for slug, app in panel.items():\n            if isinstance(title, dict):\n                try:\n                    title_ = title[slug]\n                except KeyError:\n                    raise KeyError(\n                        \"Keys of the title dictionnary and of the apps \"\n                        f\"dictionary must match. No {slug} key found in the \"\n                        \"title dictionnary.\") \n            else:\n                title_ = title\n            slug = slug if slug.startswith('/') else '/'+slug\n            if 'flask' in sys.modules:\n                from flask import Flask\n                if isinstance(app, Flask):\n                    wsgi = WSGIContainer(app)\n                    if slug == '/':\n                        raise ValueError('Flask apps must be served on a subpath.')\n                    if not slug.endswith('/'):\n                        slug += '/'\n                    extra_patterns.append(('^'+slug+'.*', ProxyFallbackHandler,\n                                           dict(fallback=wsgi, proxy=slug)))\n                    continue\n            apps[slug] = partial(_eval_panel, app, server_id, title_, location)\n    else:\n        apps = {'/': partial(_eval_panel, panel, server_id, title, location)}\n\n    opts = dict(kwargs)\n    if loop:\n        loop.make_current()\n        opts['io_loop'] = loop\n    else:\n        opts['io_loop'] = IOLoop.current()\n\n    if 'index' not in opts:\n        opts['index'] = INDEX_HTML\n\n    if websocket_origin:\n        if not isinstance(websocket_origin, list):\n            websocket_origin = [websocket_origin]\n        opts['allow_websocket_origin'] = websocket_origin\n\n    server = Server(apps, port=port, **opts)\n    if verbose:\n        address = server.address or 'localhost'\n        print(\"Launching server at http://%s:%s\" % (address, server.port))\n\n    state._servers[server_id] = (server, panel, [])\n\n    if show:\n        def show_callback():\n            server.show('/')\n        server.io_loop.add_callback(show_callback)\n\n    def sig_exit(*args, **kwargs):\n        server.io_loop.add_callback_from_signal(do_stop)\n\n    def do_stop(*args, **kwargs):\n        server.io_loop.stop()\n\n    try:\n        signal.signal(signal.SIGINT, sig_exit)\n    except ValueError:\n        pass # Can't use signal on a thread\n\n    if start:\n        server.start()\n        try:\n            server.io_loop.start()\n        except RuntimeError:\n            pass\n    return server\n\n\nclass StoppableThread(threading.Thread):\n    \"\"\"Thread class with a stop() method.\"\"\"\n\n    def __init__(self, io_loop=None, timeout=1000, **kwargs):\n        from tornado import ioloop\n        super(StoppableThread, self).__init__(**kwargs)\n        self._stop_event = threading.Event()\n        self.io_loop = io_loop\n        self._cb = ioloop.PeriodicCallback(self._check_stopped, timeout)\n        self._cb.start()\n\n    def _check_stopped(self):\n        if self.stopped:\n            self._cb.stop()\n            self.io_loop.stop()\n\n    def run(self):\n        if hasattr(self, '_target'):\n            target, args, kwargs = self._target, self._args, self._kwargs\n        else:\n            target, args, kwargs = self._Thread__target, self._Thread__args, self._Thread__kwargs\n        if not target:\n            return\n        bokeh_server = None\n        try:\n            bokeh_server = target(*args, **kwargs)\n        finally:\n            if isinstance(bokeh_server, Server):\n                bokeh_server.stop()\n            if hasattr(self, '_target'):\n                del self._target, self._args, self._kwargs\n            else:\n                del self._Thread__target, self._Thread__args, self._Thread__kwargs\n\n    def stop(self):\n        self._stop_event.set()\n\n    @property\n    def stopped(self):\n        return self._stop_event.is_set()\n", "path": "panel/io/server.py"}]}