meaningful API error messages

This commit is contained in:
Jack 2016-11-11 13:40:19 -05:00
parent c407d32f5d
commit ec4f9011b9
2 changed files with 57 additions and 49 deletions

View file

@ -1,15 +1,17 @@
import sys import sys
import json
import argparse import argparse
import json
from lbrynet.conf import settings from lbrynet.conf import settings
from lbrynet.lbrynet_daemon.auth.client import LBRYAPIClient from lbrynet.lbrynet_daemon.auth.client import LBRYAPIClient
from jsonrpc.common import RPCError
help_msg = "Usage: lbrynet-cli method json-args\n" \
help_msg = "Usage: lbrynet-cli method kwargs\n" \
+ "Examples: " \ + "Examples: " \
+ "lbrynet-cli resolve_name '{\"name\": \"what\"}'\n" \ + "lbrynet-cli resolve_name name=what\n" \
+ "lbrynet-cli get_balance\n" \ + "lbrynet-cli get_balance\n" \
+ "lbrynet-cli help '{\"function\": \"resolve_name\"}'\n" \ + "lbrynet-cli help function=resolve_name\n" \
+ "\n******lbrynet-cli functions******\n" + "\n******lbrynet-cli functions******\n"
@ -59,14 +61,13 @@ def main():
meth = args.method[0] meth = args.method[0]
params = {} params = {}
if args.params: if len(args.params) > 1:
if len(args.params) > 1: params = get_params_from_kwargs(args.params)
elif len(args.params) == 1:
try:
params = json.loads(args.params[0])
except ValueError:
params = get_params_from_kwargs(args.params) params = get_params_from_kwargs(args.params)
elif len(args.params) == 1:
try:
params = json.loads(args.params[0])
except ValueError:
params = get_params_from_kwargs(args.params)
msg = help_msg msg = help_msg
for f in api.help(): for f in api.help():
@ -83,14 +84,16 @@ def main():
else: else:
result = LBRYAPIClient.config(service=meth, params=params) result = LBRYAPIClient.config(service=meth, params=params)
print json.dumps(result, sort_keys=True) print json.dumps(result, sort_keys=True)
except: except RPCError as err:
# TODO: The api should return proper error codes # TODO: The api should return proper error codes
# and messages so that they can be passed along to the user # and messages so that they can be passed along to the user
# instead of this generic message. # instead of this generic message.
# https://app.asana.com/0/158602294500137/200173944358192 # https://app.asana.com/0/158602294500137/200173944358192
print "Something went wrong, here's the usage for %s:" % meth print "Something went wrong, here's the usage for %s:" % meth
print api.help({'function': meth}) print api.help({'function': meth})
print "Here's the traceback for the error you encountered:"
print err.msg
else: else:
print "Unknown function" print "Unknown function"
print msg print msg

View file

@ -1,9 +1,10 @@
import logging import logging
from decimal import Decimal from decimal import Decimal
from zope.interface import implements from zope.interface import implements
from twisted.web import server, resource from twisted.web import server, resource
from twisted.internet import defer from twisted.internet import defer
from twisted.python.failure import Failure
from txjsonrpc import jsonrpclib from txjsonrpc import jsonrpclib
from lbrynet.core.Error import InvalidAuthenticationToken, InvalidHeaderError, SubhandlerError from lbrynet.core.Error import InvalidAuthenticationToken, InvalidHeaderError, SubhandlerError
@ -19,6 +20,12 @@ def default_decimal(obj):
return float(obj) return float(obj)
class JSONRPCException(Exception):
def __init__(self, err, code):
self.faultCode = code
self.faultString = err.getTraceback()
class AuthorizedBase(object): class AuthorizedBase(object):
def __init__(self): def __init__(self):
self.authorized_functions = [] self.authorized_functions = []
@ -90,7 +97,16 @@ class AuthJSONRPCServer(AuthorizedBase):
def setup(self): def setup(self):
return NotImplementedError() return NotImplementedError()
def _render_error(self, request, failure, version=jsonrpclib.VERSION_1, response_code=FAILURE):
fault = jsonrpclib.dumps(JSONRPCException(Failure(failure), response_code), version=version)
self._set_headers(request, fault)
if response_code != AuthJSONRPCServer.FAILURE:
request.setResponseCode(response_code)
request.write(fault)
request.finish()
def render(self, request): def render(self, request):
notify_finish = request.notifyFinish()
assert self._check_headers(request), InvalidHeaderError assert self._check_headers(request), InvalidHeaderError
session = request.getSession() session = request.getSession()
@ -114,8 +130,10 @@ class AuthJSONRPCServer(AuthorizedBase):
content = request.content.read() content = request.content.read()
try: try:
parsed = jsonrpclib.loads(content) parsed = jsonrpclib.loads(content)
except ValueError: except ValueError as err:
return server.failure log.error("Unable to decode request json")
self._render_error(request, err)
return server.NOT_DONE_YET
function_name = parsed.get('method') function_name = parsed.get('method')
args = parsed.get('params') args = parsed.get('params')
@ -125,36 +143,35 @@ class AuthJSONRPCServer(AuthorizedBase):
try: try:
self._run_subhandlers(request) self._run_subhandlers(request)
except SubhandlerError: except SubhandlerError as err:
return server.failure self._render_error(request, err, version)
return server.NOT_DONE_YET
reply_with_next_secret = False reply_with_next_secret = False
if self._use_authentication: if self._use_authentication:
if function_name in self.authorized_functions: if function_name in self.authorized_functions:
try: try:
self._verify_token(session_id, parsed, token) self._verify_token(session_id, parsed, token)
except InvalidAuthenticationToken: except InvalidAuthenticationToken as err:
log.warning("API validation failed") log.error("API validation failed")
request.setResponseCode(self.UNAUTHORIZED) self._render_error(request, err, version, response_code=AuthJSONRPCServer.UNAUTHORIZED)
request.finish()
return server.NOT_DONE_YET return server.NOT_DONE_YET
self._update_session_secret(session_id) self._update_session_secret(session_id)
reply_with_next_secret = True reply_with_next_secret = True
try: try:
function = self._get_jsonrpc_method(function_name) function = self._get_jsonrpc_method(function_name)
except Exception: except AttributeError as err:
log.warning("Unknown method: %s", function_name) log.error("Unknown method: %s", function_name)
return server.failure self._render_error(request, err, version)
return server.NOT_DONE_YET
d = defer.maybeDeferred(function) if args == [{}] else defer.maybeDeferred(function, *args) d = defer.maybeDeferred(function) if args == [{}] else defer.maybeDeferred(function, *args)
# cancel the response if the connection is broken
notify_finish = request.notifyFinish()
notify_finish.addErrback(self._response_failed, d)
d.addErrback(self._errback_render, id)
d.addCallback(self._callback_render, request, id, version, reply_with_next_secret)
d.addErrback(notify_finish.errback)
# cancel the response if the connection is broken
notify_finish.addErrback(self._response_failed, d)
d.addCallback(self._callback_render, request, version, reply_with_next_secret)
d.addErrback(lambda err: self._render_error(request, err, version))
return server.NOT_DONE_YET return server.NOT_DONE_YET
def _register_user_session(self, session_id): def _register_user_session(self, session_id):
@ -209,7 +226,7 @@ class AuthJSONRPCServer(AuthorizedBase):
return True return True
def _get_jsonrpc_method(self, function_path): def _get_jsonrpc_method(self, function_path):
assert self._check_function_path(function_path) assert self._check_function_path(function_path), AttributeError(function_path)
return self.callable_methods.get(function_path) return self.callable_methods.get(function_path)
def _initialize_session(self, session_id): def _initialize_session(self, session_id):
@ -242,9 +259,9 @@ class AuthJSONRPCServer(AuthorizedBase):
assert handler(request) assert handler(request)
except Exception as err: except Exception as err:
log.error(err.message) log.error(err.message)
raise SubhandlerError raise SubhandlerError(err.message)
def _callback_render(self, result, request, id, version, auth_required=False): def _callback_render(self, result, request, version, auth_required=False):
result_for_return = result if not isinstance(result, dict) else result['result'] result_for_return = result if not isinstance(result, dict) else result['result']
if version == jsonrpclib.VERSION_PRE1: if version == jsonrpclib.VERSION_PRE1:
@ -255,20 +272,8 @@ class AuthJSONRPCServer(AuthorizedBase):
encoded_message = jsonrpclib.dumps(result_for_return, version=version, default=default_decimal) encoded_message = jsonrpclib.dumps(result_for_return, version=version, default=default_decimal)
self._set_headers(request, encoded_message, auth_required) self._set_headers(request, encoded_message, auth_required)
self._render_message(request, encoded_message) self._render_message(request, encoded_message)
except: except Exception as err:
fault = jsonrpclib.Fault(self.FAILURE, "can't serialize output") self._render_error(request, err, response_code=self.FAILURE, version=version)
encoded_message = jsonrpclib.dumps(fault, version=version)
self._set_headers(request, encoded_message)
self._render_message(request, encoded_message)
def _errback_render(self, failure, id):
log.error("Request failed:")
log.error(failure)
log.error(failure.value)
log.error(id)
if isinstance(failure.value, jsonrpclib.Fault):
return failure.value
return server.failure
def _render_response(self, result, code): def _render_response(self, result, code):
return defer.succeed({'result': result, 'code': code}) return defer.succeed({'result': result, 'code': code})