Repository: encode/starlette

Issue:
Serve Simple Page App (angular) on root alongside a backend on starlette
Hello,
I'm developing an angular (7)+ starlette api backend and I would like to make starlette serve the angular app alongside with the api.
I know that I could use ngnix to serve it but I would like to try to deploy just one server. Also I have found the [https://github.com/tiangolo/uvicorn-gunicorn-starlette-docker](uvicorn-gunicorn-starlette-docker) and it would be amazing to be able to just use it with the whole app.
As a normal angular app, it expects (by default) to be located to the "/" endpoint. As my angular prod files are in a folder ./angular relative to the starlette app.py file I have tried the following:
```python
_CURDIR = dirname(abspath(__file__))
app = Starlette()
app.mount('/', StaticFiles(directory=join(_CURDIR, 'angular' )))

@app.exception_handler(404)
async def not_found(request, exc):
    return FileResponse('./angular/index.html')

if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=8090)

```
The index.html is the usual on the angular apps:

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>ArcherytimerMatrix</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.54bd9f72bfb2c07e2a6b.css"></head>
<body>
  <app-root></app-root>
<script type="text/javascript" src="runtime.253d2798c34ad4bc8428.js"></script><script type="text/javascript" src="es2015-polyfills.4a4cfea0ce682043f4e9.js" nomodule>
</script><script type="text/javascript" src="polyfills.407a467dedb63cfdd103.js"></script><script type="text/javascript" src="main.cfa2a2d950b21a173917.js"></script></body>
</html>
```
when I try to reach localhost:8090 the index.html is "shown" but it can't find the styles / runtime / polyfills and main
```
INFO: Uvicorn running on http://0.0.0.0:8090 (Press CTRL+C to quit)
INFO: ('127.0.0.1', 34138) - "GET / HTTP/1.1" 200
INFO: ('127.0.0.1', 34138) - "GET /styles.54bd9f72bfb2c07e2a6b.css HTTP/1.1" 200
INFO: ('127.0.0.1', 34140) - "GET /runtime.253d2798c34ad4bc8428.js HTTP/1.1" 200
INFO: ('127.0.0.1', 34138) - "GET /polyfills.407a467dedb63cfdd103.js HTTP/1.1" 200
INFO: ('127.0.0.1', 34140) - "GET /main.cfa2a2d950b21a173917.js HTTP/1.1" 200
```
(the status is 200 as I'm catching the exception and redirecting...)
If I try to access the static file directy:
```localhost:8090/styles.54bd9f72bfb2c07e2a6b.css```
Is not found either

If I serve the statics files from an endpoint:
```app.mount('/angular', StaticFiles(directory=join(_CURDIR, 'angular' )))```
and I try to access to the "angular"
```localhost:8090/angular/styles.54bd9f72bfb2c07e2a6b.css``` the file is served.
So It seems that the "/" root can't be used for endopoint to static files (or there is a bug if this is not the intended behavior)

Maybe I'm doing it wrong, is there another way to achieve this?

Thank you in advance
Regards






Discussion:
For one thing - you don't really need to mount StaticFiles within a Starlette app if that's all you're using - you can just do...

```python
app = StaticFiles(directory=join(_CURDIR, 'angular' ))
```

Make sure you're using a leading `/` in the hyperlinks. I expect the issue is that you're using *relative* URLs, which are then pointed at the wrong location.

Also, `StaticFiles` doesn't yet support using `/` to serve `index.html`, which would be a really useful thing for it to do. Ideally we'd have a mode like `app = StaticFiles(directory=..., html=True)`, which would:

* Support serving `index.html` for directory URLs.
* Serve any `404.html` in the root, for any 404 cases.
@tomchristie  Thanks for answering.
I'm not only using startlette to serve just the angular app. The main purpouse of Startlette in my project is to provide a backend rest api for such angular app. I wanted to just avoid having to add another server to serve the static app.
I will try adding the slash on the hyperlinks inside the index.html, but it should be pointing to the root.
I'm a bit confused, asthe rest of your comments makes me think that startlette can't really serve the app (at least with the current implementation) Am I right?

> The main purpouse of Startlette in my project is to provide a backend rest api for such angular app. I wanted to just avoid having to add another server to serve the static app.

Oh right, sure - then mount the StaticFiles app. If you're mounting it at `/` then you'll need to make sure it's the last thing in the routing, since it'll match *any* path. Yes you can serve both the app and that static files like that.

Also I've just double checked that there's no issue with serving mounted at the root path...

**example.py**

```python
from starlette.applications import Starlette
from starlette.staticfiles import StaticFiles

app = Starlette()
app.mount('/', StaticFiles(directory='statics'))
```

**statics/a.txt**

```text
123
```

```shell
$ uvicorn example:app
```

Loads `http://127.0.0.1:8000/a.txt` just fine.

I'd suggest checking if you're missing the leading '/'.

Alternatively you can also do...

```python
app.mount('/', StaticFiles(directory='statics'), name='static')
```

Then in the template tags, use `url_for('static', path="...")` for any hyperlinks. (That's kinda overkill for this case, but it'll handle giving you the correct URL, even if you change the mount point)
Hello,
I don't know what is happening. If I tried your example, ```http://127.0.0.1:8000/a.txt```returns a 404
However if I just serve in a diferent endpoint:
```app.mount('/dummy', StaticFiles(directory='statics'))```
Accesing to 
```http://127.0.0.1:8000/dummy/a.txt```
is showing the file...
Why I can't mount the files on the root?

Are you copying example.py exactly?
Do you have the latest version of Starlette installed?
Hello,
Just to be sure I have updated python to python 3.7.2, create a new environment using conda and installing just starlette with ```pip3 install starlette```and ```pip3 install uvicorn``` and it is still not working. I receive a 404 every time I try to access http://127.0.0.1/a.txt
The startlette version is the pypi one.  this is my pip list output:
```
Package           Version
----------------- -------
astroid           2.2.5  
Click             7.0    
h11               0.8.1  
httptools         0.0.13 
isort             4.3.15 
lazy-object-proxy 1.3.1  
mccabe            0.6.1  
pip               18.1   
pylint            2.3.1  
setuptools        40.6.2 
six               1.12.0 
starlette         0.11.3 
typed-ast         1.3.1  
uvicorn           0.6.1  
uvloop            0.12.1 
websockets        7.0    
wrapt             1.11.1 
```
I have copied the example exactly.
I'm running the coe on a MacOs but it doesn't work either on a windows 10

Resolving patch:
diff --git a/starlette/datastructures.py b/starlette/datastructures.py
--- a/starlette/datastructures.py
+++ b/starlette/datastructures.py
@@ -568,7 +568,7 @@ def __delitem__(self, key: str) -> None:
                 pop_indexes.append(idx)
 
         for idx in reversed(pop_indexes):
-            del (self._list[idx])
+            del self._list[idx]
 
     @property
     def raw(self) -> typing.List[typing.Tuple[bytes, bytes]]:
diff --git a/starlette/routing.py b/starlette/routing.py
--- a/starlette/routing.py
+++ b/starlette/routing.py
@@ -294,7 +294,7 @@ def __init__(
             self.app = Router(routes=routes)
         self.name = name
         self.path_regex, self.path_format, self.param_convertors = compile_path(
-            path + "/{path:path}"
+            self.path + "/{path:path}"
         )
 
     @property


Test patch:
diff --git a/tests/test_routing.py b/tests/test_routing.py
--- a/tests/test_routing.py
+++ b/tests/test_routing.py
@@ -271,6 +271,12 @@ def test_reverse_mount_urls():
     )
 
 
+def test_mount_at_root():
+    mounted = Router([Mount("/", ok, name="users")])
+    client = TestClient(mounted)
+    assert client.get("/").status_code == 200
+
+
 def users_api(request):
     return JSONResponse({"users": [{"username": "tom"}]})