Repository: sanic-org/sanic

Issue:
Sanic exceptions
How does sanic exceptions are supposed to work? The docs states that 

> Exceptions can be thrown from within request handlers and will automatically be handled by Sanic. Exceptions take a message as their first argument, and can also take a status code to be passed back in the HTTP response.

This is my route
```python
@app.route("/")
async def test(request):
    abort(401)
```

When I make a request on the path I get a response of : 

>Internal Server Error
The server encountered an internal error and cannot complete your request.

`2017-08-24 10:18:43 - (sanic)[ERROR]: Traceback (most recent call last):
  File "/home/nikos/.virtualenvs/3.6/lib/python3.6/site-packages/sanic/app.py", line 503, in handle_request
    response = await response
  File "/home/nikos/Desktop/Side Projects/micro/test2.py", line 15, in test
    abort(401)
  File "/home/nikos/.virtualenvs/3.6/lib/python3.6/site-packages/sanic/exceptions.py", line 262, in abort
    raise sanic_exception(message=message, status_code=status_code)
TypeError: __init__() missing 1 required positional argument: 'scheme'`

Also after a bit the connection times out and the log trace is 

`2017-08-24 10:18:43 - (network)[INFO][127.0.0.1:34734]: GET http://0.0.0.0:8001/  500 144
2017-08-24 10:19:43 - (sanic)[ERROR]: Traceback (most recent call last):
  File "/home/nikos/.virtualenvs/3.6/lib/python3.6/site-packages/sanic/server.py", line 143, in connection_timeout
    raise RequestTimeout('Request Timeout')
sanic.exceptions.RequestTimeout: Request Timeout`


Discussion:
There might be a bug with `abort(401)`.

`abort` function raises an exception based on SanicException.

`abort(401)` will raise `Unauthorized` exception.

And `Unauthorized.__init__` requires an argument `scheme`, but `SanicException` doesn't.

Maybe you can try:
```
@app.route("/")
async def test(request):
    raise Unauthorized("Unauthorized", "Basic")
```

or 

```
@app.route("/")
async def test(request):
    abort(400)
```
Actually, I was just testing it. Yes, the Unauthorized exception needs a scheme argument .From source code: 

```python 
    def __init__(self, message, scheme, **kwargs):
        super().__init__(message)

        values = ["{!s}={!r}".format(k, v) for k, v in kwargs.items()]
        challenge = ', '.join(values)

        self.headers = {
            "WWW-Authenticate": "{} {}".format(scheme, challenge).rstrip()
        }
```
This might be a solution?

```python 
    def __init__(self, message, scheme=None, **kwargs):
```



Resolving patch:
diff --git a/sanic/exceptions.py b/sanic/exceptions.py
--- a/sanic/exceptions.py
+++ b/sanic/exceptions.py
@@ -209,6 +209,7 @@ class Unauthorized(SanicException):
     Unauthorized exception (401 HTTP status code).
 
     :param message: Message describing the exception.
+    :param status_code: HTTP Status code.
     :param scheme: Name of the authentication scheme to be used.
 
     When present, kwargs is used to complete the WWW-Authentication header.
@@ -216,11 +217,13 @@ class Unauthorized(SanicException):
     Examples::
 
         # With a Basic auth-scheme, realm MUST be present:
-        raise Unauthorized("Auth required.", "Basic", realm="Restricted Area")
+        raise Unauthorized("Auth required.",
+                           scheme="Basic",
+                           realm="Restricted Area")
 
         # With a Digest auth-scheme, things are a bit more complicated:
         raise Unauthorized("Auth required.",
-                           "Digest",
+                           scheme="Digest",
                            realm="Restricted Area",
                            qop="auth, auth-int",
                            algorithm="MD5",
@@ -228,20 +231,24 @@ class Unauthorized(SanicException):
                            opaque="zyxwvu")
 
         # With a Bearer auth-scheme, realm is optional so you can write:
-        raise Unauthorized("Auth required.", "Bearer")
+        raise Unauthorized("Auth required.", scheme="Bearer")
 
         # or, if you want to specify the realm:
-        raise Unauthorized("Auth required.", "Bearer", realm="Restricted Area")
+        raise Unauthorized("Auth required.",
+                           scheme="Bearer",
+                           realm="Restricted Area")
     """
-    def __init__(self, message, scheme, **kwargs):
-        super().__init__(message)
+    def __init__(self, message, status_code=None, scheme=None, **kwargs):
+        super().__init__(message, status_code)
 
-        values = ["{!s}={!r}".format(k, v) for k, v in kwargs.items()]
-        challenge = ', '.join(values)
+        # if auth-scheme is specified, set "WWW-Authenticate" header
+        if scheme is not None:
+            values = ["{!s}={!r}".format(k, v) for k, v in kwargs.items()]
+            challenge = ', '.join(values)
 
-        self.headers = {
-            "WWW-Authenticate": "{} {}".format(scheme, challenge).rstrip()
-        }
+            self.headers = {
+                "WWW-Authenticate": "{} {}".format(scheme, challenge).rstrip()
+            }
 
 
 def abort(status_code, message=None):


Test patch:
diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py
--- a/tests/test_exceptions.py
+++ b/tests/test_exceptions.py
@@ -31,14 +31,18 @@ def handler_404(request):
     def handler_403(request):
         raise Forbidden("Forbidden")
 
+    @app.route('/401')
+    def handler_401(request):
+        raise Unauthorized("Unauthorized")
+
     @app.route('/401/basic')
     def handler_401_basic(request):
-        raise Unauthorized("Unauthorized", "Basic", realm="Sanic")
+        raise Unauthorized("Unauthorized", scheme="Basic", realm="Sanic")
 
     @app.route('/401/digest')
     def handler_401_digest(request):
         raise Unauthorized("Unauthorized",
-                           "Digest",
+                           scheme="Digest",
                            realm="Sanic",
                            qop="auth, auth-int",
                            algorithm="MD5",
@@ -47,12 +51,16 @@ def handler_401_digest(request):
 
     @app.route('/401/bearer')
     def handler_401_bearer(request):
-        raise Unauthorized("Unauthorized", "Bearer")
+        raise Unauthorized("Unauthorized", scheme="Bearer")
 
     @app.route('/invalid')
     def handler_invalid(request):
         raise InvalidUsage("OK")
 
+    @app.route('/abort/401')
+    def handler_invalid(request):
+        abort(401)
+
     @app.route('/abort')
     def handler_invalid(request):
         abort(500)
@@ -124,6 +132,9 @@ def test_forbidden_exception(exception_app):
 
 def test_unauthorized_exception(exception_app):
     """Test the built-in Unauthorized exception"""
+    request, response = exception_app.test_client.get('/401')
+    assert response.status == 401
+
     request, response = exception_app.test_client.get('/401/basic')
     assert response.status == 401
     assert response.headers.get('WWW-Authenticate') is not None
@@ -186,5 +197,8 @@ def test_exception_in_exception_handler_debug_off(exception_app):
 
 def test_abort(exception_app):
     """Test the abort function"""
+    request, response = exception_app.test_client.get('/abort/401')
+    assert response.status == 401
+
     request, response = exception_app.test_client.get('/abort')
     assert response.status == 500