Skip to content
This repository has been archived by the owner on Jul 13, 2023. It is now read-only.

Commit

Permalink
feat: Add URLs, text to error response messages
Browse files Browse the repository at this point in the history
Added some additional, descriptive logging for duplicate error conditions.

closes #354
  • Loading branch information
jrconlin committed Jul 23, 2016
1 parent eb7ccfe commit da5eca8
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 28 deletions.
32 changes: 21 additions & 11 deletions autopush/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
extract_jwt,
base64url_encode,
)
from autopush.web.base import DEFAULT_ERR_URL


# Our max TTL is 60 days realistically with table rotation, so we hard-code it
MAX_TTL = 60 * 60 * 24 * 60
Expand Down Expand Up @@ -193,26 +195,28 @@ def _init_info(self):
# Error Callbacks
#############################################################
def _write_response(self, status_code, errno, message=None, headers=None,
reason=None):
reason=None, url=None):
"""Writes out a full JSON error and sets the appropriate status"""
self.set_status(status_code, reason)
error_data = dict(
code=status_code,
errno=errno,
error=status_codes.get(status_code, reason or "")
error=status_codes.get(status_code, reason or ""),
)
if message:
error_data["message"] = message
if status_code > 299 and url is None:
error_data["more_info"] = DEFAULT_ERR_URL
self.write(json.dumps(error_data))
self.set_header("Content-Type", "application/json")
if headers:
for header in headers.keys():
self.set_header(header, headers.get(header))
self.finish()

def _write_unauthorized_response(self, message=None):
def _write_unauthorized_response(self, message="Invalid authentication"):
headers = {"www-authenticate": AUTH_SCHEME}
self._write_response(401, 109, message, headers)
self._write_response(401, errno=109, message=message, headers=headers)

def _response_err(self, fail):
"""errBack for all exceptions that should be logged
Expand All @@ -224,14 +228,16 @@ def _response_err(self, fail):
fmt = fail.value.message or 'Exception'
self.log.failure(format=fmt, failure=fail,
status_code=500, errno=999, **self._client_info)
self._write_response(500, 999)
self._write_response(500, errno=999,
message="Unexpected server error occurred")

def _overload_err(self, fail):
"""errBack for throughput provisioned exceptions"""
fail.trap(ProvisionedThroughputExceededException)
self.log.info(format="Throughput Exceeded", status_code=503,
errno=201, **self._client_info)
self._write_response(503, 201)
self._write_response(503, errno=201,
message="Please slow message send rate")

def _router_response(self, response):
for name, val in response.headers.items():
Expand Down Expand Up @@ -276,15 +282,18 @@ def _uaid_not_found_err(self, fail):
self.log.info(format="UAID not found in AWS.",
status_code=410, errno=103,
**self._client_info)
self._write_response(410, 103)
self._write_response(410, errno=103,
message="Endpoint has expired. "
"Do not send messages to this endpoint.")

def _token_err(self, fail):
"""errBack for token decryption fail"""
fail.trap(InvalidToken, InvalidTokenException)
self.log.info(format="Invalid token",
status_code=400, errno=102,
**self._client_info)
self._write_response(400, 102)
self._write_response(400, 102,
message="Invalid endpoint.")

def _auth_err(self, fail):
"""errBack for invalid auth token"""
Expand All @@ -301,7 +310,7 @@ def _chid_not_found_err(self, fail):
self.log.info(format="CHID not found in AWS.",
status_code=410, errno=106,
**self._client_info)
self._write_response(410, 106)
self._write_response(410, 106, message="Invalid endpoint.")

#############################################################
# Utility Methods
Expand Down Expand Up @@ -441,7 +450,7 @@ def put(self, api_ver="v0", token=None):
"web-push-encryption")
self._write_response(
400,
110,
errno=110,
message="You're using outdated encryption; "
"Please update to the format described in " + wpe_url)
return
Expand Down Expand Up @@ -500,7 +509,8 @@ def _uaid_lookup_results(self, result):
for x in req_fields]):
self.log.debug(format="Client error", status_code=400,
errno=101, **self._client_info)
return self._write_response(400, 101)
return self._write_response(
400, errno=101, message="Missing necessary crypto keys.")
if ("encryption-key" in self.request.headers and
"crypto-key" in self.request.headers):
self.log.debug(format="Client error", status_code=400,
Expand Down
6 changes: 6 additions & 0 deletions autopush/router/webpush.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,15 @@ def preflight_check(self, uaid, channel_id):
record = yield deferToThread(self.ap_settings.router.get_uaid, uaid)

if 'current_month' not in record:
self.log.info("Record missing 'current_month' {record}",
record=json.dumps(record))
raise RouterException("No such subscription", status_code=410,
log_exception=False, errno=106)

month_table = record["current_month"]
if month_table not in self.ap_settings.message_tables:
self.log.info("'current_month' out of scope: {record}",
records=json.dumps(record))
yield deferToThread(self.ap_settings.router.drop_user, uaid)
raise RouterException("No such subscription", status_code=410,
log_exception=False, errno=106)
Expand All @@ -80,6 +84,8 @@ def preflight_check(self, uaid, channel_id):

if (not exists or channel_id.lower() not
in map(lambda x: normalize_id(x), chans)):
self.log.info("Unknown subscription: {channel_id}",
channelid=channel_id)
raise RouterException("No such subscription", status_code=410,
log_exception=False, errno=106)
returnValue(month_table)
Expand Down
2 changes: 1 addition & 1 deletion autopush/tests/test_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -1701,7 +1701,7 @@ def mock_encrypt(cleartext):
# sha256(dummy_key).digest()
eq_(cleartext[32:],
('47aedd050b9e19171f0fa7b8b65ca670'
'28f0bc92cd3f2cd3682b1200ec759007').decode('hex'))
'28f0bc92cd3f2cd3682b1200ec759007').decode('hex'))
return 'abcd123'
self.fernet_mock.configure_mock(**{
'encrypt.side_effect': mock_encrypt,
Expand Down
19 changes: 14 additions & 5 deletions autopush/web/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
503: "Service Unavailable",
}

DEFAULT_ERR_URL = ("http://autopush.readthedocs.io/en/latest/http.html"
"#error-codes")


class Notification(namedtuple("Notification",
"version data channel_id headers ttl")):
Expand Down Expand Up @@ -141,13 +144,15 @@ def _init_info(self):
#############################################################
# Error Callbacks
#############################################################
def _write_response(self, status_code, errno, message=None, headers=None):
def _write_response(self, status_code, errno, message=None, headers=None,
url=DEFAULT_ERR_URL):
"""Writes out a full JSON error and sets the appropriate status"""
self.set_status(status_code)
error_data = dict(
code=status_code,
errno=errno,
error=status_codes.get(status_code, "")
error=status_codes.get(status_code, ""),
more_info=url,
)
if message:
error_data["message"] = message
Expand All @@ -165,7 +170,9 @@ def _validation_err(self, fail):
self.log.info(format="Request validation error",
status_code=exc.status_code,
errno=exc.errno)
self._write_response(exc.status_code, exc.errno, headers=exc.headers)
self._write_response(exc.status_code, exc.errno,
message="Request did not validate",
headers=exc.headers)

def _response_err(self, fail):
"""errBack for all exceptions that should be logged
Expand All @@ -177,14 +184,16 @@ def _response_err(self, fail):
fmt = fail.value.message or 'Exception'
self.log.failure(format=fmt, failure=fail,
status_code=500, errno=999, **self._client_info)
self._write_response(500, 999)
self._write_response(500, 999, message="An unexpected server error"
" occurred.")

def _overload_err(self, fail):
"""errBack for throughput provisioned exceptions"""
fail.trap(ProvisionedThroughputExceededException)
self.log.info(format="Throughput Exceeded", status_code=503,
errno=201, **self._client_info)
self._write_response(503, 201)
self._write_response(503, 201,
message="Please slow message send rate")

def _router_response(self, response):
for name, val in response.headers.items():
Expand Down
2 changes: 1 addition & 1 deletion autopush/web/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def validate_uaid(self, d):
except ItemNotFound:
raise InvalidRequest("UAID not found", status_code=410, errno=103)

if result.get("router_type") not in ["webpush", "gcm", "apns"]:
if result.get("router_type") not in ["webpush", "gcm", "apns", "fcm"]:
raise InvalidRequest("Wrong URL for user", errno=108)

# Propagate the looked up user data back out
Expand Down
29 changes: 19 additions & 10 deletions autopush/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@


USER_RECORD_VERSION = 1
DEFAULT_WS_ERR = "http://autopush.readthedocs.io/en/" \
"latest/api/websocket.html#private-http-endpoint"


def extract_code(data):
Expand Down Expand Up @@ -555,12 +557,16 @@ def _notify_node(self, result):
).addCallback(IgnoreBody.ignore)
d.addErrback(self.log_failure, extra="Failed to notify node")

def returnError(self, messageType, reason, statusCode, close=True):
def returnError(self, messageType, reason, statusCode, close=True,
message="", url=DEFAULT_WS_ERR):
"""Return an error to a client, and optionally shut down the connection
safely"""
self.sendJSON({"messageType": messageType,
"reason": reason,
"status": statusCode})
send = {"messageType": messageType,
"reason": reason,
"status": statusCode}
if url:
send["more_info"] = url
self.sendJSON(send)
if close:
self.sendClose()

Expand Down Expand Up @@ -723,7 +729,7 @@ def err_hello(self, failure):
self.log_failure(failure)
self.returnError("hello", "error", 503)

def _check_other_nodes(self, result):
def _check_other_nodes(self, result, url=DEFAULT_WS_ERR):
"""callback to check other nodes for clients and send them a delete as
needed"""
self.transport.resumeProducing()
Expand All @@ -732,7 +738,8 @@ def _check_other_nodes(self, result):
if not registered:
# Registration failed
msg = {"messageType": "hello", "reason": "already_connected",
"status": 500}
"status": 500,
"more_info": url}
self.sendJSON(msg)
return

Expand Down Expand Up @@ -1008,7 +1015,8 @@ def process_register(self, data):
def error_register(self, fail):
"""errBack handler for registering to fail"""
self.transport.resumeProducing()
msg = {"messageType": "register", "status": 500}
msg = {"messageType": "register", "status": 500,
"reason": "An unexpected server error occurred"}
self.sendJSON(msg)

def finish_register(self, endpoint, chid):
Expand Down Expand Up @@ -1223,11 +1231,11 @@ def check_missed_notifications(self, results, resume=False):
if self.ps._check_notifications or self.ps._more_notifications:
self.process_notifications()

def bad_message(self, typ, message=None):
def bad_message(self, typ, message=None, url=DEFAULT_WS_ERR):
"""Error helper for sending a 401 status back"""
msg = {"messageType": typ, "status": 401}
msg = {"messageType": typ, "status": 401, "more_info": url}
if message:
msg["message"] = message
msg["reason"] = message
self.sendJSON(msg)

def _newer_notification_sent(self, channel_id, version):
Expand Down Expand Up @@ -1293,6 +1301,7 @@ def put(self, uaid):

if client.paused:
self.set_status(503)

settings.metrics.increment("updates.router.busy")
return self.write("Client busy.")

Expand Down

0 comments on commit da5eca8

Please sign in to comment.