Repository: faust-streaming/faust (Python)

Issue:
Custom headers are not handled properly when self.error is used in web views
## Checklist

- [x] I have included information about relevant versions
- [x] I have verified that the issue persists when using the `master` branch of Faust.

## Steps to reproduce
If you have a view and you return an error using `self.error`, you cannot send extra custom headers. Any other argument besides `reason` and `status` are merged together in the response, so in the snipped below, the `cors_headers` are actually returned to the user, in the response. Using `self.json` yields the correct behaviour.

```
cors_headers = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': '*',
    'Access-Control-Allow-Headers': '*',
}

@app.page('/test')
async def test(self, request):
    return self.error(reason='test reason', status=400, headers=cors_headers)
```

## Expected behavior
To be able to send custom headers when using `self.error`. This helps in the case of UIs that are making web requests to a faust app running somewhere.

## Actual behavior
If you have a view and you return an error using `self.error`, you cannot send extra custom headers. Any other argument besides `reason` and `status` are merged together in the response, so in the snipped below, the `cors_headers` are actually returned to the user, in the response. Using `self.json` yields the correct behaviour.

A way to "bypass" using `self.error` is to use `self.json` and to build the error message yourself. I think this is caused due to how the method is implemented:
```
    def error(self, status: int, reason: str, **kwargs: Any) -> Response:
        """Create error JSON response."""
        return self.json({"error": reason, **kwargs}, status=status)
```

I'm not entirely sure if `**kwargs` is supposed to be in the message or not. I think the headers should be extracted from the `kwargs` (if existing) and passed down to `self.json`. 

I can make a PR with a solution. Let me know what do you think it would be the best solution and I can try and fix it.

## Full traceback
No traceback.

# Versions
* Python version: 0.7.7
* Faust version: 3.7
* Operating system: Red Had 7




Discussion: Note that a simple solution would be to have
```
    def error(self, status: int, reason: str, **kwargs: Any) -> Response:
        """Create error JSON response."""
        headers = kwargs.pop('headers', {})
        return self.json({"error": reason, **kwargs}, status=status, headers=headers)
```
please open a PR



Fix (patch):
diff --git a/faust/web/views.py b/faust/web/views.py
index 7430f900e..cad55413d 100644
--- a/faust/web/views.py
+++ b/faust/web/views.py
@@ -299,9 +299,11 @@ def notfound(self, reason: str = "Not Found", **kwargs: Any) -> Response:
         """
         return self.error(404, reason, **kwargs)
 
-    def error(self, status: int, reason: str, **kwargs: Any) -> Response:
+    def error(
+        self, status: int, reason: str, headers: MutableMapping = None, **kwargs: Any
+    ) -> Response:
         """Create error JSON response."""
-        return self.json({"error": reason, **kwargs}, status=status)
+        return self.json({"error": reason, **kwargs}, status=status, headers=headers)
 
 
 def takes_model(Model: Type[ModelT]) -> ViewDecorator: