Repository: jupyterhub/jupyterhub

Issue:
Proxy target missing with Spawner form page
I'm trying out the new Spawner form feature described in https://github.com/jupyter/jupyterhub/pull/355 and I ran into a problem. I'm using Jupyterhub with the SystemUserSpawner. 

When I define a form for the Spawner like this:

``` python
c.SystemUserSpawner.options_form = """  
<label for="args">Extra notebook CLI arguments</label>
<input name="args" placeholder="e.g. --debug"></input>
<label for="env">Environment variables (one per line)</label>
<textarea name="env"></textarea>
"""
```

I start Jupyterhub, I log in as a system user and I see the form correctly displayed. I click on the button to spawn my notebook server and everything works fine. However, if I then follow these steps:
1. I log out
2. I kill the user's container via docker rm
3. I try to log in again with the same user

Then I see the following error in the browser:

```
503: Proxy Target Missing
The upstream service is unavailable
configurable-http-proxy
```

And in the terminal:

```
[D 2016-01-05 09:46:33.457 JupyterHub dockerspawner:310] Getting container 'jupyter-enric'
[I 2016-01-05 09:46:33.459 JupyterHub dockerspawner:318] Container 'jupyter-enric' is gone
[W 2016-01-05 09:46:33.459 JupyterHub dockerspawner:289] container not found
[I 2016-01-05 09:46:33.465 JupyterHub log:100] 302 POST /hub/login?next= (@127.0.0.1) 18.53ms
[I 2016-01-05 09:46:33.465 JupyterHub login:79] User logged in: enric
[D 2016-01-05 09:46:33.468 JupyterHub pages:29] User is running: /user/enric
09:46:33.472 - error: [ConfigProxy] Proxy error:  code=ECONNREFUSED, errno=ECONNREFUSED, syscall=connect
09:46:33.473 - error: [ConfigProxy] 503 GET /user/enric
```

If I follow the same steps without defining the form in the JupyterHub configuration file, everything works fine: JupyterHub realises that the container is no longer there and it creates a new one without throwing the proxy error.

I'm using an Ubuntu 14.04 machine, JupyterHub master and dockerspawner-0.0.1_dev.



Discussion:
It may take a moment, but the Hub should notice that the spawner has died and update the proxy accordingly. The relevant config is `Spawner.poll_interval`, which should default to 30 seconds. If this isn't happening, there's a bug in the Spawner's poll mechanism.

Indeed, after some time the Hub reacts:

```
[D 2016-01-05 11:55:24.543 JupyterHub dockerspawner:310] Getting container 'jupyter-enric'
[I 2016-01-05 11:55:24.549 JupyterHub dockerspawner:318] Container 'jupyter-enric' is gone
[W 2016-01-05 11:55:24.550 JupyterHub dockerspawner:289] container not found
[D 2016-01-05 11:55:24.550 JupyterHub dockerspawner:310] Getting container 'jupyter-enric'
[I 2016-01-05 11:55:24.554 JupyterHub dockerspawner:318] Container 'jupyter-enric' is gone
[W 2016-01-05 11:55:24.554 JupyterHub dockerspawner:289] container not found
[W 2016-01-05 11:55:24.554 JupyterHub base:315] User enric server stopped, with exit code: 
[I 2016-01-05 11:55:24.554 JupyterHub orm:184] Removing user enric from proxy
[D 2016-01-05 11:55:24.561 JupyterHub orm:156] Fetching DELETE http://localhost:444/api/routes/user/enric
[D 2016-01-05 11:55:24.565 JupyterHub dockerspawner:310] Getting container 'jupyter-enric'
[I 2016-01-05 11:55:24.568 JupyterHub dockerspawner:318] Container 'jupyter-enric' is gone
[W 2016-01-05 11:55:24.569 JupyterHub dockerspawner:289] container not found
```

If then I manually refresh my browser, I get redirected to the page with the "Start My Server" button and I can spawn my server. However, I see this exception:

```
[I 2016-01-05 12:00:29.845 JupyterHub log:100] 302 GET /hub/user/enric/api/sessions?_=1451991295235 (enric@127.0.0.1) 15.72ms
[D 2016-01-05 12:00:29.860 JupyterHub log:100] 304 GET /hub/spawn (enric@127.0.0.1) 8.20ms
[I 2016-01-05 12:00:29.866 JupyterHub log:100] 302 GET /hub/user/enric/clusters?_=1451991295236 (enric@127.0.0.1) 32.54ms
[W 2016-01-05 12:00:29.868 JupyterHub dockerspawner:289] container not found
[E 2016-01-05 12:00:29.868 JupyterHub web:1524] Uncaught exception GET /hub/user/enric/api/sessions?_=1451991295235 (127.0.0.1)
    HTTPServerRequest(protocol='https', host='localhost', method='GET', uri='/hub/user/enric/api/sessions?_=1451991295235', version='HTTP/1.1', remote_ip='127.0.0.1', headers={'X-Forwarded-Port': '443', 'Cookie': 'jupyter-hub-token="2|1:0|10:1451991307|17:jupyter-hub-token|44:OGU4N2U0NTAyZmQyNDJlOGFkODc2NGU4NWY1MTNlNGI=|a3cbba300d1601c49180886d8dc08d6f7763589a67d01305415a81efaf8d28a6"', 'Referer': 'https://localhost/user/enric/tree', 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/javascript, */*; q=0.01', 'Host': 'localhost', 'Accept-Language': 'en-US,en;q=0.5', 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:41.0) Gecko/20100101 Firefox/41.0', 'X-Forwarded-For': '127.0.0.1', 'Connection': 'close', 'Accept-Encoding': 'gzip, deflate', 'X-Forwarded-Proto': 'https'})
    Traceback (most recent call last):
      File "/usr/local/lib/python3.4/dist-packages/tornado/web.py", line 1445, in _execute
        result = yield result
      File "/home/enric/jupyterhub/jupyterhub/handlers/base.py", line 463, in get
        self.redirect(target)
      File "/usr/local/lib/python3.4/dist-packages/tornado/web.py", line 659, in redirect
        raise Exception("Cannot redirect after headers have been written")
    Exception: Cannot redirect after headers have been written
```

Please note that all this does not happen if I do **not** specify a spawner form in the JupyterHub configuration file: in that case, a new container is automatically created for me and I see no proxy error. Could it be that the changes in the spawner to support the form have affected the poll mechanism?


Resolving patch:
diff --git a/jupyterhub/handlers/base.py b/jupyterhub/handlers/base.py
--- a/jupyterhub/handlers/base.py
+++ b/jupyterhub/handlers/base.py
@@ -454,6 +454,7 @@ def get(self, name):
                 if status is not None:
                     if current_user.spawner.options_form:
                         self.redirect(url_path_join(self.hub.server.base_url, 'spawn'))
+                        return
                     else:
                         yield self.spawn_single_user(current_user)
             # set login cookie anew


Test patch: