diff --git a/api/handlers.py b/api/handlers.py
index cc8d3dc..2d81864 100755
--- a/api/handlers.py
+++ b/api/handlers.py
@@ -13,6 +13,7 @@ from django.shortcuts import get_object_or_404
from api.models import AuthAPIKey, AuthAPILog
from eve_proxy.models import CachedDocument
+from eve_proxy.exceptions import *
from eve_api.models import EVEAccount
from sso.models import ServiceAccount, Service
from hr.models import Blacklist
@@ -119,9 +120,12 @@ class EveAPIProxyHandler(BaseHandler):
obj = get_object_or_404(EVEAccount, pk=params['userid'])
params['apikey'] = obj.api_key
- cached_doc = CachedDocument.objects.api_query(url_path, params, exceptions=False)
-
- return HttpResponse(cached_doc.body)
+ try:
+ cached_doc = CachedDocument.objects.api_query(url_path, params)
+ except DocumentRetrievalError:
+ return HttpResponse(status=500)
+ else:
+ return HttpResponse(cached_doc.body)
class OpTimerHandler(BaseHandler):
@@ -132,28 +136,19 @@ class OpTimerHandler(BaseHandler):
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': '
The EVE API calendar is unavailable
', 'duration': 0, 'isImportant': 0, 'eventText': 'Fuck CCP tbqh imho srsly', 'endsIn':-1, 'forumLink': ''}]}
+
+ 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'))
+ if dom.getElementsByTagName('error'):
+ error_doc['raw_xml'] = cached_doc.body
+ return error_doc
- if cached_doc:
- dom = minidom.parseString(cached_doc.body.encode('utf-8'))
- enode = dom.getElementsByTagName('error')
- if not cached_doc or enode:
- return {'ops': [{
- 'startsIn': -1,
- 'eventID': 0,
- 'ownerName': '',
- 'eventDate': '',
- 'eventTitle': 'The EVE API is currently down
',
- 'duration': 0,
- 'isImportant': 0,
- 'eventText': 'Fuck CCP tbqh imho srsly',
- 'endsIn':-1,
- 'forumLink': ''}]}
-
events = []
- events_node_children = dom.getElementsByTagName('rowset')[0].childNodes
-
- for node in events_node_children:
+ for node in dom.getElementsByTagName('rowset')[0].childNodes:
if node.nodeType == 1:
ownerID = node.getAttribute('ownerID')
if ownerID != '1':
@@ -200,7 +195,7 @@ class OpTimerHandler(BaseHandler):
'endsIn':-1,
'forumLink': ''}]}
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):
diff --git a/eve_api/api_puller/accounts.py b/eve_api/api_puller/accounts.py
index 3793265..f285f6b 100755
--- a/eve_api/api_puller/accounts.py
+++ b/eve_api/api_puller/accounts.py
@@ -22,28 +22,12 @@ def import_eve_account(api_key, user_id, force_cache=False):
"""
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:
- account_doc = CachedDocument.objects.api_query('/account/Characters.xml.aspx',
- params=auth_params,
- 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
+ account_doc = CachedDocument.objects.api_query('/account/Characters.xml.aspx', params=auth_params, no_cache=force_cache)
+ except:
+ return
if account_doc and account_doc.body:
dom = minidom.parseString(account_doc.body.encode('utf-8'))
@@ -59,11 +43,12 @@ def import_eve_account(api_key, user_id, force_cache=False):
error = enode[0].getAttribute('code')
- if int(error) >= 900:
+ if int(error) >= 500:
# API disabled, down or rejecting, return without changes
return
-
- if error == '211':
+ elif error in ['202', '203', '204', '205', '212']:
+ account.api_status = API_STATUS_AUTH_ERROR
+ elif error == '211':
account.api_status = API_STATUS_ACC_EXPIRED
else:
account.api_status = API_STATUS_OTHER_ERROR
diff --git a/eve_api/managers.py b/eve_api/managers.py
index 733f4b7..36402fd 100644
--- a/eve_api/managers.py
+++ b/eve_api/managers.py
@@ -82,13 +82,11 @@ class EVEPlayerCorporationManager(models.Manager):
# Convert incoming data to UTF-8.
dom = minidom.parseString(corp_doc.body.encode('utf-8'))
-
error_node = dom.getElementsByTagName('error')
# If there's an error, see if it's because the corp doesn't exist.
if error_node:
- error_code = error_node[0].getAttribute('code')
- if error_code == '523':
+ if error_node[0].getAttribute('code') == '523':
raise InvalidCorpID(id)
return dom
diff --git a/eve_proxy/exceptions.py b/eve_proxy/exceptions.py
new file mode 100644
index 0000000..83ea607
--- /dev/null
+++ b/eve_proxy/exceptions.py
@@ -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__
+
diff --git a/eve_proxy/models.py b/eve_proxy/models.py
index 2d43aa5..67f023b 100755
--- a/eve_proxy/models.py
+++ b/eve_proxy/models.py
@@ -1,95 +1,46 @@
-import httplib
-import urllib
+import urllib, urllib2
import xml
import hashlib
import socket
from datetime import datetime, timedelta
from xml.dom import minidom
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.
-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):
"""
This manager handles querying or retrieving CachedDocuments.
"""
- def get_document_id(self, url_path, params):
- if params:
- p = params.copy()
- if 'service' in p:
- del p['service']
- paramstr = urllib.urlencode(p)
+ def construct_url(self, url_path, params):
+
+ # Valid arguments for EVE API Calls
+ allowed_params = ['userid', 'apikey', 'characterid', 'version', 'names', 'ids', 'corporationid', 'beforerefid', 'accountkey']
+
+ 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:
- 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):
- """
- 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):
+ def api_query(self, url_path, params={}, no_cache=False, exceptions=True):
"""
Transparently handles querying EVE API or retrieving the document from
the cache.
@@ -102,35 +53,59 @@ class CachedDocumentManager(models.Manager):
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:
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:
- pass
-
- # EVE uses UTC.
- current_eve_time = datetime.utcnow()
+ doc = None
+ else:
+ doc = None
- 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 not doc or not doc.cached_until or datetime.utcnow() > doc.cached_until:
- 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()
+ 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)
- return doc
+ 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
+ else:
+ ApiAccessLog(userid=v, service='Unknown', time_access=cached_doc.time_retrieved, document=url).save()
+
+ return doc
class CachedDocument(models.Model):
"""