Repository: encode/starlette

Issue:
Raising Exceptions in sub-applications routes
### Checklist

- [X] The bug is reproducible against the latest release or `master`.
- [X] There are no similar issues or pull requests to fix it yet.

### Describe the bug

Let's start with this PR: #1262

It's about preventing raise `anyio.ExceptionGroup` in views under a `BaseHTTPMiddleware`. PR resolve that problem with nonlocal variable that stores our exception. But in the case of sub-applications, it does not work. 

As I can see (fyi I am not good at asyncio), in the case below, we reach and read a response before we raise an exception and store it to our nonlocal variable:

fragment of `BaseHTTPMiddleware.__call__`
```python
async def call_next(request: Request) -> Response:
    app_exc: typing.Optional[Exception] = None
    send_stream, recv_stream = anyio.create_memory_object_stream()

    async def coro() -> None:
        nonlocal app_exc

        async with send_stream:
            try:
                task = await self.app(scope, request.receive, send_stream.send)
            except Exception as exc:
                app_exc = exc

    task_group.start_soon(coro)

    try:
        message = await recv_stream.receive()
    except anyio.EndOfStream:
        if app_exc is not None:
            raise app_exc
        raise RuntimeError("No response returned.")
    
    ...
    response = StreamingResponse(
        status_code=message["status"], content=body_stream()
    )
    response.raw_headers = message["headers"]
    return response
```

in this moment: `except anyio.EndOfStream:` exception still no raised.

### Steps to reproduce the bug

```python
import httpx
import pytest
from fastapi import FastAPI, APIRouter
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.responses import Response
from starlette.routing import Route


class SomeError(Exception):
    pass


class SomeMiddleware(BaseHTTPMiddleware):
    async def dispatch(
        self, request: Request, call_next: RequestResponseEndpoint
    ) -> Response:
        return await call_next(request)

# Drop (or use not BaseHTTPMiddleware based) middleware and test works fine
app = FastAPI(middleware=[Middleware(SomeMiddleware), ])


async def simple_route(request: Request):
    raise SomeError


another_router = APIRouter(
    routes=[Route('/simple-route/', simple_route, methods=['GET'])]
)
sub_app = FastAPI()
sub_app.include_router(another_router)
app.router.mount(f'/api', sub_app)


@pytest.mark.asyncio
async def test_simple_route():
    async with httpx.AsyncClient(app=app) as client:
        with pytest.raises(SomeError):
            await client.get("http://testserver/api/simple-route/")

```

### Expected behavior

An exception was raised and caught by pytest exception

### Actual behavior

An exception wasn't raised

### Debugging material

_No response_

### Environment

macOS Monterey 12.0.1, starlette 0.17.1, Python 3.9.9


### Additional context

_No response_


Discussion:

Resolving patch:
diff --git a/starlette/middleware/base.py b/starlette/middleware/base.py
--- a/starlette/middleware/base.py
+++ b/starlette/middleware/base.py
@@ -52,6 +52,9 @@ async def body_stream() -> typing.AsyncGenerator[bytes, None]:
                         assert message["type"] == "http.response.body"
                         yield message.get("body", b"")
 
+                if app_exc is not None:
+                    raise app_exc
+
             response = StreamingResponse(
                 status_code=message["status"], content=body_stream()
             )


Test patch:
diff --git a/tests/middleware/test_base.py b/tests/middleware/test_base.py
--- a/tests/middleware/test_base.py
+++ b/tests/middleware/test_base.py
@@ -3,7 +3,7 @@
 from starlette.applications import Starlette
 from starlette.middleware import Middleware
 from starlette.middleware.base import BaseHTTPMiddleware
-from starlette.responses import PlainTextResponse
+from starlette.responses import PlainTextResponse, StreamingResponse
 from starlette.routing import Route
 
 
@@ -28,6 +28,16 @@ def exc(request):
     raise Exception("Exc")
 
 
+@app.route("/exc-stream")
+def exc_stream(request):
+    return StreamingResponse(_generate_faulty_stream())
+
+
+def _generate_faulty_stream():
+    yield b"Ok"
+    raise Exception("Faulty Stream")
+
+
 @app.route("/no-response")
 class NoResponse:
     def __init__(self, scope, receive, send):
@@ -56,6 +66,10 @@ def test_custom_middleware(test_client_factory):
         response = client.get("/exc")
     assert str(ctx.value) == "Exc"
 
+    with pytest.raises(Exception) as ctx:
+        response = client.get("/exc-stream")
+    assert str(ctx.value) == "Faulty Stream"
+
     with pytest.raises(RuntimeError):
         response = client.get("/no-response")
 
@@ -158,3 +172,13 @@ async def dispatch(self, request, call_next):
     client = test_client_factory(app)
     response = client.get("/does_not_exist")
     assert response.text == "Custom"
+
+
+def test_exception_on_mounted_apps(test_client_factory):
+    sub_app = Starlette(routes=[Route("/", exc)])
+    app.mount("/sub", sub_app)
+
+    client = test_client_factory(app)
+    with pytest.raises(Exception) as ctx:
+        client.get("/sub/")
+    assert str(ctx.value) == "Exc"