Repository: streamlit/streamlit

Issue:
EXCEPTION! 'NoneType' object has no attribute 'strip'
# Summary
Fail to run streamlit on Python3.6.3 with anaconda and docker

# Steps to reproduce
Just Type `streamlit hello`

## Expected behavior:
Try to run streamlit according to the document

## Actual behavior:
Make an exception as **EXCEPTION! 'NoneType' object has no attribute 'strip'**



Discussion:
Encounter the same error when there is no internet connectivity e.g. when on an air gapped host.

```
Did not auto detect external IP.
Please go to https://streamlit.io/docs/ for debugging hints.
EXCEPTION! 'NoneType' object has no attribute 'strip'
  File "/home/smartexpert/miniconda3/bin/streamlit", line 10, in <module>
    sys.exit(main())
  File "/home/smartexpert/miniconda3/lib/python3.7/site-packages/click/core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "/home/smartexpert/miniconda3/lib/python3.7/site-packages/click/core.py", line 717, in main
    rv = self.invoke(ctx)
  File "/home/smartexpert/miniconda3/lib/python3.7/site-packages/click/core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/smartexpert/miniconda3/lib/python3.7/site-packages/click/core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/smartexpert/miniconda3/lib/python3.7/site-packages/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/home/smartexpert/miniconda3/lib/python3.7/site-packages/streamlit/cli.py", line 226, in main_run
    _main_run(target, args)
  File "/home/smartexpert/miniconda3/lib/python3.7/site-packages/streamlit/cli.py", line 251, in _main_run
    bootstrap.run(file, command_line, args)
  File "/home/smartexpert/miniconda3/lib/python3.7/site-packages/streamlit/bootstrap.py", line 229, in run
    ioloop.start()
  File "/home/smartexpert/miniconda3/lib/python3.7/site-packages/tornado/platform/asyncio.py", line 132, in start
    self.asyncio_loop.run_forever()
  File "/home/smartexpert/miniconda3/lib/python3.7/asyncio/base_events.py", line 539, in run_forever
    self._run_once()
  File "/home/smartexpert/miniconda3/lib/python3.7/asyncio/base_events.py", line 1775, in _run_once
    handle._run()
  File "/home/smartexpert/miniconda3/lib/python3.7/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
  File "/home/smartexpert/miniconda3/lib/python3.7/site-packages/tornado/ioloop.py", line 758, in _run_callback
    ret = callback()
  File "/home/smartexpert/miniconda3/lib/python3.7/site-packages/tornado/stack_context.py", line 300, in null_wrapper
    return fn(*args, **kwargs)
  File "/home/smartexpert/miniconda3/lib/python3.7/site-packages/tornado/gen.py", line 326, in wrapper
    yielded = next(result)
  File "/home/smartexpert/miniconda3/lib/python3.7/site-packages/streamlit/server/Server.py", line 342, in _loop_coroutine
    traceback.print_stack(file=sys.stdout)

Please report this bug at https://github.com/streamlit/streamlit/issues.
```
I get this error when running `streamlit hello` in a docker container.
I was able to go a workaround using https://discuss.streamlit.io/t/how-do-i-set-the-server-to-0-0-0-0-for-deployment-using-docker/216/2?u=shuaib

Essentially using a config.toml witht the following config:

```
[server]
enableCORS = false
headless = true

[browser]
serverAddress = "0.0.0.0"
gatherUsageStats = false
serverPort = 9000
```

It seems like the issue occurs because streamlit attempts to make tornado listen on an external ip address, when external up cannot be fetched/read. I might be wrong, but haven't used tornado so someone with more knowledge maybe able to troubleshoot easily.

check the bootstrap.py, Server.py and get_external_ip() in net_util.py along with the stack trace my my comment above. Happy to look into it and post a fix if someone can give me some pointers.
For me this error is raised when I start the streamlit script with `--server.headless True`
But sometimes it runs and gives a very strange message:
```
  You can now view your Streamlit app in your browser.

  Network URL: http://10.204.150.61:8511
  External URL: http://<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>
<head>
	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="Expires" content="-1"/>
<meta http-equiv="Pragma" content="no-cache"/>
<meta http-equiv="Cache-Control" content="no-cache"/>
	<meta name="id" content="polLoginRedirect" >
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<title>Policy Login Redirect</title>
	<script type="text/JavaScript" src="jquery_1_8_3-6.2.5-1589068415(eng).js"></script>
	<script type="text/javascript">
	<!--
	var strPolicyRequir = "The policy set up by your network administrator requires that ";
	var strAuthYourself = "you authenticate yourself with this firewall before you can have access.";
	var strAuthYourselfInDomain = "only authenticated users are allowed access through this firewall. You do not appear to be logged into the domain, and so must authenticate yourself before you can have access.";
	var strAuthIntro = "To authenticate yourself click on the following link and enter your user name and password to log in to the firewall.";
	var strClickLogin = "Click here to log in";
	// -->
	</script>
	<script type="text/JavaScript" src="polLoginRedirect.js"></script>
	<link rel=stylesheet href="swl_login-6.2.5-328142343(eng).css" TYPE="text/css">
</head>
<body class="login_bg">
	<div class="login_outer">
		<div class="login_inner">
			<div class="vgap48"></div>
			<div class="login_logo">
				<img src="logo_sw.png">
			</div>
			<div class="login_prodname">
				Network Security Appliance
			</div>
			<div class="vgap48"></div>
			<div class="contentWithIconBox">
				<div class="containter_left">
					<div class="icon_warning"></div>
				</div>
				<div class="container_right">
					<div class="login_msg_header">
						Authentication Required
					</div>
					<div id="popup_box_text" class="login_msg_content">
						<a href="https://10.7.0.14:444/auth.html?cid=3"></a>
					</div>
				</div>
			</div>
		</div>
	</div>
</body>
</html>:8511
```
Note that I run it as a service so it has never run with a headless=False, so there are no credentials created.

I found that if I add the option `--browser.serverAddress "0.0.0.0"`, it seems to work

Note that I am with
- windows 10, running as a service
- python 3.7.7
- streamlit 0.56
I deploy app in heroku and before this I try run app locally.

$ heroku local 

My app starts as expected but I include shell script which must run in server.
This script create file 
~/.streamlit/config.toml 
[server]
headless = true
enableCORS=false
port = 5000

After that a had this exception.
I had same EXCEPTION! 'NoneType' object has no attribute 'strip'

Yes. I can run app like suggest @hcoohb  

$ streamlit hello --browser.serverAddress "0.0.0.0"

But I just remove config.toml :-)

Resolving patch:
diff --git a/lib/streamlit/bootstrap.py b/lib/streamlit/bootstrap.py
--- a/lib/streamlit/bootstrap.py
+++ b/lib/streamlit/bootstrap.py
@@ -181,17 +181,23 @@ def _print_url():
         ]
 
     elif config.get_option("server.headless"):
-        named_urls = [
-            ("Network URL", Report.get_url(net_util.get_internal_ip())),
-            ("External URL", Report.get_url(net_util.get_external_ip())),
-        ]
+        internal_ip = net_util.get_internal_ip()
+        if internal_ip:
+            named_urls.append(("Network URL", Report.get_url(internal_ip)))
+
+        external_ip = net_util.get_external_ip()
+        if external_ip:
+            named_urls.append(("External URL", Report.get_url(external_ip)))
 
     else:
         named_urls = [
             ("Local URL", Report.get_url("localhost")),
-            ("Network URL", Report.get_url(net_util.get_internal_ip())),
         ]
 
+        internal_ip = net_util.get_internal_ip()
+        if internal_ip:
+            named_urls.append(("Network URL", Report.get_url(internal_ip)))
+
     click.secho("")
     click.secho("  %s" % title_message, fg="blue", bold=True)
     click.secho("")
diff --git a/lib/streamlit/net_util.py b/lib/streamlit/net_util.py
--- a/lib/streamlit/net_util.py
+++ b/lib/streamlit/net_util.py
@@ -45,7 +45,9 @@ def get_external_ip():
 
     response = _make_blocking_http_get(_AWS_CHECK_IP, timeout=5)
 
-    if response is None:
+    if _looks_like_an_ip_adress(response):
+        _external_ip = response
+    else:
         LOGGER.warning(
             # fmt: off
             "Did not auto detect external IP.\n"
@@ -53,8 +55,7 @@ def get_external_ip():
             # fmt: on
             util.HELP_DOC
         )
-    else:
-        _external_ip = response.strip()
+        _external_ip = None
 
     return _external_ip
 
@@ -93,6 +94,29 @@ def get_internal_ip():
 
 def _make_blocking_http_get(url, timeout=5):
     try:
-        return requests.get(url, timeout=timeout).text
+        text = requests.get(url, timeout=timeout).text
+        if isinstance(text, str):
+            text = text.strip()
+        return text
     except Exception as e:
         return None
+
+
+def _looks_like_an_ip_adress(address):
+    if address is None:
+        return False
+
+    try:
+        socket.inet_pton(socket.AF_INET, address)
+        return True  # Yup, this is an IPv4 address!
+    except (AttributeError, OSError):
+        pass
+
+    try:
+        socket.inet_pton(socket.AF_INET6, address)
+        return True  # Yup, this is an IPv6 address!
+    except (AttributeError, OSError):
+        pass
+
+    # Nope, this is not an IP address.
+    return False
diff --git a/lib/streamlit/server/Server.py b/lib/streamlit/server/Server.py
--- a/lib/streamlit/server/Server.py
+++ b/lib/streamlit/server/Server.py
@@ -113,7 +113,7 @@ def server_port_is_manually_set():
 
 
 def start_listening(app):
-    """Takes the server start listening at the configured port.
+    """Makes the server start listening at the configured port.
 
     In case the port is already taken it tries listening to the next available
     port.  It will error after MAX_PORT_SEARCH_RETRIES attempts.


Test patch:
diff --git a/lib/tests/streamlit/bootstrap_test.py b/lib/tests/streamlit/bootstrap_test.py
--- a/lib/tests/streamlit/bootstrap_test.py
+++ b/lib/tests/streamlit/bootstrap_test.py
@@ -115,6 +115,56 @@ def test_print_urls_remote(self, mock_get_internal_ip, mock_get_external_ip):
         self.assertTrue("Network URL: http://internal-ip" in out)
         self.assertTrue("External URL: http://external-ip" in out)
 
+    @patch("streamlit.net_util.get_external_ip")
+    @patch("streamlit.net_util.get_internal_ip")
+    def test_print_urls_remote_no_external(
+        self, mock_get_internal_ip, mock_get_external_ip
+    ):
+
+        mock_is_manually_set = testutil.build_mock_config_is_manually_set(
+            {"browser.serverAddress": False}
+        )
+        mock_get_option = testutil.build_mock_config_get_option(
+            {"server.headless": True}
+        )
+
+        mock_get_internal_ip.return_value = "internal-ip"
+        mock_get_external_ip.return_value = None
+
+        with patch.object(config, "get_option", new=mock_get_option), patch.object(
+            config, "is_manually_set", new=mock_is_manually_set
+        ):
+            bootstrap._print_url()
+
+        out = sys.stdout.getvalue()
+        self.assertTrue("Network URL: http://internal-ip" in out)
+        self.assertTrue("External URL: http://external-ip" not in out)
+
+    @patch("streamlit.net_util.get_external_ip")
+    @patch("streamlit.net_util.get_internal_ip")
+    def test_print_urls_remote_no_internal(
+        self, mock_get_internal_ip, mock_get_external_ip
+    ):
+
+        mock_is_manually_set = testutil.build_mock_config_is_manually_set(
+            {"browser.serverAddress": False}
+        )
+        mock_get_option = testutil.build_mock_config_get_option(
+            {"server.headless": True}
+        )
+
+        mock_get_internal_ip.return_value = None
+        mock_get_external_ip.return_value = "external-ip"
+
+        with patch.object(config, "get_option", new=mock_get_option), patch.object(
+            config, "is_manually_set", new=mock_is_manually_set
+        ):
+            bootstrap._print_url()
+
+        out = sys.stdout.getvalue()
+        self.assertTrue("Network URL: http://internal-ip" not in out)
+        self.assertTrue("External URL: http://external-ip" in out)
+
     @patch("streamlit.net_util.get_internal_ip")
     def test_print_urls_local(self, mock_get_internal_ip):
         mock_is_manually_set = testutil.build_mock_config_is_manually_set(
@@ -179,3 +229,28 @@ def test_print_urls_base(self, mock_get_internal_ip):
         out = sys.stdout.getvalue()
         self.assertTrue("Local URL: http://localhost:8501/foo" in out)
         self.assertTrue("Network URL: http://internal-ip:8501/foo" in out)
+
+    @patch("streamlit.net_util.get_internal_ip")
+    def test_print_urls_base_no_internal(self, mock_get_internal_ip):
+        mock_is_manually_set = testutil.build_mock_config_is_manually_set(
+            {"browser.serverAddress": False}
+        )
+        mock_get_option = testutil.build_mock_config_get_option(
+            {
+                "server.headless": False,
+                "server.baseUrlPath": "foo",
+                "server.port": 8501,
+                "global.useNode": False,
+            }
+        )
+
+        mock_get_internal_ip.return_value = None
+
+        with patch.object(config, "get_option", new=mock_get_option), patch.object(
+            config, "is_manually_set", new=mock_is_manually_set
+        ):
+            bootstrap._print_url()
+
+        out = sys.stdout.getvalue()
+        self.assertTrue("Local URL: http://localhost:8501/foo" in out)
+        self.assertTrue("Network URL: http://internal-ip:8501/foo" not in out)
diff --git a/lib/tests/streamlit/net_util_test.py b/lib/tests/streamlit/net_util_test.py
--- a/lib/tests/streamlit/net_util_test.py
+++ b/lib/tests/streamlit/net_util_test.py
@@ -36,3 +36,19 @@ def test_get_external_ip(self):
         with requests_mock.mock() as m:
             m.get(net_util._AWS_CHECK_IP, exc=requests.exceptions.ConnectTimeout)
             self.assertEqual(None, net_util.get_external_ip())
+
+    def test_get_external_ip_html(self):
+        # This tests the case where the external URL returns a web page.
+        # https://github.com/streamlit/streamlit/issues/554#issuecomment-604847244
+
+        response_text = """
+        <html>
+            ... stuff
+        </html>
+        """
+
+        with requests_mock.mock() as m:
+            m.get(net_util._AWS_CHECK_IP, text=response_text)
+            self.assertEqual(None, net_util.get_external_ip())
+
+        net_util._external_ip = None