Rework eve_proxy to remove dependancy on eve_api and allow it to handle downtime situations better

This commit is contained in:
2010-11-10 11:36:28 +00:00
parent c6429a4e56
commit 1e52c87a46
5 changed files with 117 additions and 147 deletions

View File

@@ -13,6 +13,7 @@ from django.shortcuts import get_object_or_404
from api.models import AuthAPIKey, AuthAPILog from api.models import AuthAPIKey, AuthAPILog
from eve_proxy.models import CachedDocument from eve_proxy.models import CachedDocument
from eve_proxy.exceptions import *
from eve_api.models import EVEAccount from eve_api.models import EVEAccount
from sso.models import ServiceAccount, Service from sso.models import ServiceAccount, Service
from hr.models import Blacklist from hr.models import Blacklist
@@ -119,8 +120,11 @@ class EveAPIProxyHandler(BaseHandler):
obj = get_object_or_404(EVEAccount, pk=params['userid']) obj = get_object_or_404(EVEAccount, pk=params['userid'])
params['apikey'] = obj.api_key params['apikey'] = obj.api_key
cached_doc = CachedDocument.objects.api_query(url_path, params, exceptions=False) try:
cached_doc = CachedDocument.objects.api_query(url_path, params)
except DocumentRetrievalError:
return HttpResponse(status=500)
else:
return HttpResponse(cached_doc.body) return HttpResponse(cached_doc.body)
@@ -132,28 +136,19 @@ class OpTimerHandler(BaseHandler):
params = {'userID': obj.id, 'apiKey': obj.api_key, 'characterID': FULL_API_CHARACTER_ID} params = {'userID': obj.id, 'apiKey': obj.api_key, 'characterID': FULL_API_CHARACTER_ID}
cached_doc = CachedDocument.objects.api_query('/char/UpcomingCalendarEvents.xml.aspx', params, exceptions=False) error_doc = {'ops': [{'startsIn': -1, 'eventID': 0, 'ownerName': '', 'eventDate': '', 'eventTitle': '<div style="text-align:center">The EVE API calendar is unavailable</div>', 'duration': 0, 'isImportant': 0, 'eventText': 'Fuck CCP tbqh imho srsly', 'endsIn':-1, 'forumLink': ''}]}
if cached_doc: try:
cached_doc = CachedDocument.objects.api_query('/char/UpcomingCalendarEvents.xml.aspx', params)
except DocumentRetrievalError:
return error_doc
dom = minidom.parseString(cached_doc.body.encode('utf-8')) dom = minidom.parseString(cached_doc.body.encode('utf-8'))
enode = dom.getElementsByTagName('error') if dom.getElementsByTagName('error'):
if not cached_doc or enode: error_doc['raw_xml'] = cached_doc.body
return {'ops': [{ return error_doc
'startsIn': -1,
'eventID': 0,
'ownerName': '',
'eventDate': '',
'eventTitle': '<div style="text-align:center">The EVE API is currently down</div>',
'duration': 0,
'isImportant': 0,
'eventText': 'Fuck CCP tbqh imho srsly',
'endsIn':-1,
'forumLink': ''}]}
events = [] events = []
events_node_children = dom.getElementsByTagName('rowset')[0].childNodes for node in dom.getElementsByTagName('rowset')[0].childNodes:
for node in events_node_children:
if node.nodeType == 1: if node.nodeType == 1:
ownerID = node.getAttribute('ownerID') ownerID = node.getAttribute('ownerID')
if ownerID != '1': if ownerID != '1':
@@ -200,7 +195,7 @@ class OpTimerHandler(BaseHandler):
'endsIn':-1, 'endsIn':-1,
'forumLink': ''}]} 'forumLink': ''}]}
else: else:
return {'ops':events} return {'ops': events, 'doc_time': cached_doc.time_retrieved, 'cache_until': cached_doc.cached_until, 'current_time': datetime.utcnow() }
class BlacklistHandler(BaseHandler): class BlacklistHandler(BaseHandler):

View File

@@ -22,27 +22,11 @@ def import_eve_account(api_key, user_id, force_cache=False):
""" """
Imports an account from the API into the EVEAccount model. Imports an account from the API into the EVEAccount model.
""" """
auth_params = {'userID': user_id, 'apiKey': api_key}
auth_params = {'userid': user_id, 'apikey': api_key}
try: try:
account_doc = CachedDocument.objects.api_query('/account/Characters.xml.aspx', account_doc = CachedDocument.objects.api_query('/account/Characters.xml.aspx', params=auth_params, no_cache=force_cache)
params=auth_params, except:
no_cache=force_cache)
except APIAuthException:
try:
account = EVEAccount.objects.get(id=user_id)
except EVEAccount.DoesNotExist:
return
if api_key == account.api_key:
account.api_status = API_STATUS_AUTH_ERROR
account.api_last_updated = datetime.utcnow()
account.save()
return
except APINoUserIDException:
try:
account = EVEAccount.objects.get(id=user_id)
account.delete()
except EVEAccount.DoesNotExist:
return return
if account_doc and account_doc.body: if account_doc and account_doc.body:
@@ -59,11 +43,12 @@ def import_eve_account(api_key, user_id, force_cache=False):
error = enode[0].getAttribute('code') error = enode[0].getAttribute('code')
if int(error) >= 900: if int(error) >= 500:
# API disabled, down or rejecting, return without changes # API disabled, down or rejecting, return without changes
return return
elif error in ['202', '203', '204', '205', '212']:
if error == '211': account.api_status = API_STATUS_AUTH_ERROR
elif error == '211':
account.api_status = API_STATUS_ACC_EXPIRED account.api_status = API_STATUS_ACC_EXPIRED
else: else:
account.api_status = API_STATUS_OTHER_ERROR account.api_status = API_STATUS_OTHER_ERROR

View File

@@ -82,13 +82,11 @@ class EVEPlayerCorporationManager(models.Manager):
# Convert incoming data to UTF-8. # Convert incoming data to UTF-8.
dom = minidom.parseString(corp_doc.body.encode('utf-8')) dom = minidom.parseString(corp_doc.body.encode('utf-8'))
error_node = dom.getElementsByTagName('error') error_node = dom.getElementsByTagName('error')
# If there's an error, see if it's because the corp doesn't exist. # If there's an error, see if it's because the corp doesn't exist.
if error_node: if error_node:
error_code = error_node[0].getAttribute('code') if error_node[0].getAttribute('code') == '523':
if error_code == '523':
raise InvalidCorpID(id) raise InvalidCorpID(id)
return dom return dom

17
eve_proxy/exceptions.py Normal file
View File

@@ -0,0 +1,17 @@
class DocumentRetrievalError(Exception):
"""
Unable to retrieve a document from the EVE API: %s
"""
def __init__(self, value):
self.value = value
def __str__(self):
return self.__doc_ % value_
class InvalidDocument(Exception):
"""
The document retrieved from the EVE API is not a valid XML document
"""
def __str__(self):
return self.__doc__

View File

@@ -1,95 +1,46 @@
import httplib import urllib, urllib2
import urllib
import xml import xml
import hashlib import hashlib
import socket import socket
from datetime import datetime, timedelta from datetime import datetime, timedelta
from xml.dom import minidom from xml.dom import minidom
from django.db import models from django.db import models
from eve_api.api_exceptions import APIAuthException, APINoUserIDException from eve_proxy.exceptions import *
import settings
# You generally never want to change this unless you have a very good reason. # You generally never want to change this unless you have a very good reason.
API_URL = 'api.eve-online.com'
try:
API_URL = getattr(settings, 'EVE_API_URL')
except AttributeError:
API_URL = 'http://api.eve-online.com'
# Errors to rollback if we have a cached version of the document
# Errors 500-999 at the moment, this can be trimmed down as needed
ROLLBACK_ERRORS = range(500, 999)
class CachedDocumentManager(models.Manager): class CachedDocumentManager(models.Manager):
""" """
This manager handles querying or retrieving CachedDocuments. This manager handles querying or retrieving CachedDocuments.
""" """
def get_document_id(self, url_path, params): def construct_url(self, url_path, params):
if params:
p = params.copy() # Valid arguments for EVE API Calls
if 'service' in p: allowed_params = ['userid', 'apikey', 'characterid', 'version', 'names', 'ids', 'corporationid', 'beforerefid', 'accountkey']
del p['service']
paramstr = urllib.urlencode(p) if len(params):
for k, v in params.items():
del params[k]
if k.lower() in allowed_params:
params[k.lower()] = v
url = "%s%s?%s" % (API_URL, url_path, urllib.urlencode(params))
else: else:
paramstr = '' url = "%s%s" % (API_URL, url_path)
return hashlib.sha1('%s?%s' % (url_path, paramstr)).hexdigest() return url
def cache_from_eve_api(self, url_path, params): def api_query(self, url_path, params={}, no_cache=False, exceptions=True):
"""
Connect to the EVE API server, send the request, and cache it to
a CachedDocument. This is typically not something you want to call
directly. Use api_query().
"""
method = 'GET'
paramstr = ''
service = 'auth'
if params:
if 'service' in params:
service = params['service']
del params['service']
paramstr = urllib.urlencode(params)
if len(paramstr.strip()) > 0:
method = 'POST'
headers = {"Content-type": "application/x-www-form-urlencoded"}
conn = httplib.HTTPConnection(API_URL)
try:
conn.request(method, url_path, paramstr, headers)
except socket.error:
return None
response = conn.getresponse()
if response.status == 200:
doc_id = self.get_document_id(url_path, params)
cached_doc, created = self.get_or_create(url_path=doc_id)
cached_doc.body = unicode(response.read(), 'utf-8')
cached_doc.time_retrieved = datetime.utcnow()
try:
# Parse the response via minidom
dom = minidom.parseString(cached_doc.body.encode('utf-8'))
except xml.parsers.expat.ExpatError:
cached_doc.cached_until = datetime.utcnow()
else:
cached_doc.cached_until = dom.getElementsByTagName('cachedUntil')[0].childNodes[0].nodeValue
# If this is user related, write a log instance
if params and 'userID' in params:
try:
v = int(params['userID'])
except ValueError:
pass
else:
log = ApiAccessLog()
log.userid = v
log.service = service
log.time_access = cached_doc.time_retrieved
log.document = url_path
log.save()
# Finish up and return the resulting document just in case.
cached_doc.save()
cached_doc = self.get(id=cached_doc.pk)
return cached_doc
def api_query(self, url_path, params=None, no_cache=False, exceptions=True):
""" """
Transparently handles querying EVE API or retrieving the document from Transparently handles querying EVE API or retrieving the document from
the cache. the cache.
@@ -102,33 +53,57 @@ class CachedDocumentManager(models.Manager):
the query: userID=1&characterID=xxxxxxxx the query: userID=1&characterID=xxxxxxxx
""" """
doc_id = self.get_document_id(url_path, params) url = self.construct_url(url_path, params)
doc = None
if not no_cache: if not no_cache:
try: try:
doc = super(CachedDocumentManager, self).get_query_set().get(url_path=doc_id) doc = super(CachedDocumentManager, self).get_query_set().get(url_path=url)
except self.model.DoesNotExist: except self.model.DoesNotExist:
doc = None
else:
doc = None
if not doc or not doc.cached_until or datetime.utcnow() > doc.cached_until:
req = urllib2.Request(url)
req.add_header('CCP-Contact', 'matalok@pleaseignore.com')
try:
conn = urllib2.urlopen(req)
except urllib2.HTTPError, e:
raise DocumentRetrievalError(e.code)
except urllib2.URLError, e:
raise DocumentRetrievalError(e.reason)
cached_doc, created = self.get_or_create(url_path=url)
cached_doc.body = unicode(conn.read(), 'utf-8')
cached_doc.time_retrieved = datetime.utcnow()
try:
# Parse the response via minidom
dom = minidom.parseString(cached_doc.body.encode('utf-8'))
except xml.parsers.expat.ExpatError:
cached_doc.cached_until = datetime.utcnow()
else:
cached_doc.cached_until = dom.getElementsByTagName('cachedUntil')[0].childNodes[0].nodeValue
enode = dom.getElementsByTagName('error')
if enode:
error = enode[0].getAttribute('code')
else:
error = 0
# If we have a error in the ignored error list use the cached doc, otherwise return the new doc
if not doc or (not error or not error in ROLLBACK_ERRORS):
cached_doc.save()
doc = self.get(id=cached_doc.pk)
# If this is user related, write a log instance
if params and params.get('userid', None):
try:
v = int(params.get('userid', None))
except:
pass pass
else:
# EVE uses UTC. ApiAccessLog(userid=v, service='Unknown', time_access=cached_doc.time_retrieved, document=url).save()
current_eve_time = datetime.utcnow()
if not doc or not doc.cached_until or current_eve_time > doc.cached_until:
doc = self.cache_from_eve_api(url_path, params)
if doc:
dom = minidom.parseString(doc.body.encode('utf-8'))
if dom:
error_node = dom.getElementsByTagName('error')
if error_node and exceptions:
error_code = error_node[0].getAttribute('code')
# User specified an invalid userid and/or auth key.
if error_code == '203':
raise APIAuthException()
elif error_code == '106':
raise APINoUserIDException()
return doc return doc