Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # Embedded file name: modules\serverapi\main.py
- import urllib
- import base64
- import urllib2
- import json
- import time
- import logging
- import re
- import socket
- from mbroker import m_interface
- from functools import wraps
- log = logging.getLogger(__name__)
- API_CONFIG = {'base_url': 'https://api.nexon.net/'}
- NSS_CONFIG = {'base_url': 'https://nss.nexon.net/log/'}
- API_CACHE_DATA = {}
- SERVER_ENV = 'live'
- API_TIMEOUT = 30
- _running = False
- _listener = []
- nss_cookie = None
- def m_run():
- global _listener
- global _running
- _listener.append(m_interface.listen_async('serverapi', serverapi_message_handler))
- _listener.append(m_interface.listen_async('profile', profile_message_handler))
- _running = True
- def m_shutdown():
- global _running
- global API_CACHE_DATA
- API_CACHE_DATA = {}
- for t in _listener:
- t.stop()
- t.join()
- _running = False
- def serverapi_message_handler(message):
- for key in message.keys():
- try:
- msg = message[key]
- if 'error' in msg:
- return
- if key.startswith('callApi'):
- g = key.split('_')
- l = len(g)
- if l < 3:
- resp = {'error': {'code': 20397,
- 'message': 'Invalid API Endpoint'}}
- else:
- method = g[l - 1].upper()
- path = '/'.join(g[1:l - 1]).lower()
- req = {'method': method,
- 'path': path}
- if msg is not None:
- if 'params' in msg:
- req['params'] = msg['params']
- if 'token' in msg['params']:
- req['token'] = msg['params']['token']
- if 'use_cache' in msg and method == 'GET':
- req['use_cache'] = msg['use_cache']
- if 'token' in msg:
- req['token'] = msg['token']
- if 'retry_cnt' in msg:
- req['retry_cnt'] = msg['retry_cnt']
- resp = call_api(req)
- elif key == 'getApiUrl':
- resp = get_api_url(msg)
- elif key == 'getApiConfig':
- resp = get_api_config()
- else:
- continue
- if not isinstance(resp, dict) or 'error' not in resp and 'success' not in resp:
- resp = {'success': resp}
- except Exception as e:
- log.exception(str(e))
- resp = {'error': {'code': 20399,
- 'type': 'Internal Server Error'}}
- m_interface.publish('serverapi-events', {key: resp})
- return
- def profile_message_handler(message):
- for key in message.keys():
- try:
- resp = None
- msg = message[key]
- if msg is None:
- msg = {}
- log.debug('profile message handler - key: %s, message: %r ', key, msg)
- if key == 'getProfile':
- req = {'method': 'GET',
- 'resource': 'profile'}
- if 'uid' in msg:
- req['uid'] = msg['uid']
- if 'use_cache' in msg:
- req['use_cache'] = msg['use_cache']
- elif key == 'addFriend':
- if 'uid' not in msg:
- resp = {'error': {'code': '5',
- 'message': 'Parameter is invalid.'}}
- else:
- params = {'user_no': msg['uid']}
- req = {'method': 'POST',
- 'resource': 'friends',
- 'params': params}
- elif key == 'removeFriend':
- if 'uid' not in msg:
- resp = {'error': {'code': '5',
- 'message': 'Parameter is invalid.'}}
- else:
- params = {'user_no': msg['uid']}
- req = {'method': 'DELETE',
- 'resource': 'friends',
- 'params': params}
- elif key == 'getProfilename':
- req = {'method': 'GET',
- 'resource': 'profilename',
- 'token': 'null'}
- if 'uid' in msg:
- req['uid'] = msg['uid']
- elif key == 'getProfilenameHistory':
- params = {'type': 'history'}
- if 'limit' in msg:
- params['limit'] = msg['limit']
- req = {'method': 'GET',
- 'resource': 'profilename',
- 'params': params,
- 'use_cache': False}
- if 'uid' in msg:
- req['uid'] = msg['uid']
- elif key == 'getMyProfilename':
- req = {'method': 'GET',
- 'resource': 'profilename'}
- elif key == 'getAvatarList':
- req = {'method': 'GET',
- 'resource': 'avatar',
- 'params': {'type': 'list'}}
- elif key == 'updateProfile':
- req = {'method': 'POST',
- 'resource': 'profile',
- 'params': msg}
- elif key == 'searchUser':
- if 'query' not in msg:
- resp = {'error': {'code': '6',
- 'message': 'Parameter is missing.'}}
- else:
- path = 'search/' + msg['query']
- params = {'type': 'user'}
- req = {'method': 'GET',
- 'path': path,
- 'params': params,
- 'token': 'null',
- 'use_cache': True}
- resp = call_api(req)
- else:
- continue
- if resp is None:
- resp = call_users_api(req)
- except Exception as e:
- err_msg = str(e)
- log.exception(err_msg)
- resp = {'error': {'code': 20399,
- 'type': 'Internal Server Error',
- 'message': err_msg}}
- if 'error' in resp:
- m_interface.publish('profile-events', {key + 'Failed': resp})
- else:
- m_interface.publish('profile-events', {key + 'Success': resp})
- return
- def get_session_token():
- from auth import main as auth
- try:
- token = auth.user.session.auth_token
- except Exception as e:
- log.debug(e)
- token = None
- return token
- def get_api_config():
- return API_CONFIG
- def get_api_url(path):
- if path is None:
- return
- else:
- m = re.search('[http://|https://]?api(\\-dev|\\-test)?.nexon.net/(.*)', path)
- if m is not None:
- path = m.group(2)
- elif path.startswith('/'):
- path = path[1:]
- api_url = API_CONFIG['base_url'] + path
- return api_url
- def get_nss_url(endpoint):
- if endpoint is None:
- return
- else:
- m = re.search('[http://|https://]?nss(\\-dev|\\-test)?.nexon.net/(.*)', endpoint)
- if m is not None:
- endpoint = m.group(2)
- elif endpoint.startswith('/'):
- endpoint = endpoint[1:]
- nss_url = NSS_CONFIG['base_url'] + endpoint
- return nss_url
- def call_users_api(input):
- uid = None
- if 'uid' in input:
- uid = input['uid']
- if uid is None or len(uid) == 0:
- uid = 'me'
- path = 'users/' + uid
- if 'resource' in input:
- path = path + '/' + input['resource']
- input['path'] = path
- return call_api(input)
- def retry(exception_to_check, tries = 5, delay = 1, backoff = 2):
- """Retry calling the decorated function using an exponential backoff.
- http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
- original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry
- :param exception_to_check: the exception to check. may be a tuple of
- exceptions to check
- :type exception_to_check: Exception or tuple
- :param tries: number of times to try (not retry) before giving up
- :type tries: int
- :param delay: initial delay between retries in seconds
- :type delay: int
- :param backoff: backoff multiplier e.g. value of 2 will double the delay
- each retry
- :type backoff: int
- """
- def deco_retry(f):
- @wraps(f)
- def f_retry(*args, **kwargs):
- mtries, mdelay = tries, delay
- err = None
- start = time.time()
- while mtries > 1:
- try:
- return f(*args, **kwargs)
- except urllib2.HTTPError as e:
- err = e
- if e.code < 500:
- raise
- except exception_to_check as e:
- err = e
- log.debug('%s, Retrying in %d seconds...' % (str(e), mdelay))
- time.sleep(mdelay)
- mtries -= 1
- mdelay *= backoff
- if mtries <= 1 or time.time() - start > API_TIMEOUT:
- raise err
- return f(*args, **kwargs)
- return f_retry
- return deco_retry
- @retry((urllib2.URLError, socket.timeout), tries=5, delay=1)
- def urlopen_with_retry(req, timeout = 10):
- return urllib2.urlopen(req, timeout=timeout)
- pending_requests = {}
- class RequestTracker:
- def __init__(self, path):
- self.path = path
- def __enter__(self):
- global pending_requests
- pending_requests[self.path] = True
- return True
- def __exit__(self, exc_type, exc_value, traceback):
- pending_requests[self.path] = False
- if exc_type is not None:
- log.debug('RequestTracker:exit - %s %s %s', exc_type, exc_value, traceback)
- raise
- return True
- def call_api(input, no_retry = False, timeout = 10):
- if input is None or 'path' not in input:
- return {'error': {'code': 2,
- 'type': 'Bad Request',
- 'message': 'This API End Point is invalid.'}}
- else:
- if 'method' in input:
- method = input['method'].upper()
- if method not in ('GET', 'POST', 'DELETE'):
- method = 'GET'
- else:
- method = 'GET'
- params = {}
- if 'params' in input and isinstance(input['params'], dict):
- params = input['params']
- if method == 'GET':
- use_cache = True
- if 'use_cache' in input:
- use_cache = input['use_cache']
- else:
- use_cache = True
- token = ''
- if 'token' in input and input['token'] is not None:
- token = input['token']
- if token == 'null':
- token = ''
- elif len(token) == 0:
- token = get_session_token()
- if token is None:
- return {'error': {'code': 20398,
- 'type': 'Internal Server Error',
- 'message': 'Invalid Token'}}
- path = input['path']
- api_endpoint = get_api_url(path)
- url_params = {}
- old_api = is_old_api(path)
- if old_api:
- header_data = {'Content-Type': 'application/x-www-form-urlencoded'}
- if len(token) > 0:
- url_params['access_token'] = token
- else:
- header_data = {'Content-Type': 'application/json'}
- if len(token) > 0:
- header_data['Cookie'] = 'nxtk=' + token + ';domain=.nexon.net;path=/;'
- header_data['Authorization'] = 'bearer ' + base64.b64encode(token)
- from apps import launcher
- header_data['User-Agent'] = 'NexonLauncher.' + launcher.get_version()
- request_data = {'method': method,
- 'url': api_endpoint}
- try:
- log.debug('API Request init ( %s ): %r', api_endpoint, input)
- if method == 'GET' and len(params) > 0:
- url_params.update(params)
- api_url = api_endpoint
- if len(url_params) > 0:
- api_url = api_url + '?' + urllib.urlencode(url_params)
- result = None
- if method == 'GET':
- cache_key = get_cache_key(url_params)
- if use_cache:
- result = get_cache('session', api_endpoint, cache_key)
- if result is None:
- req = urllib2.Request(api_url, headers=header_data)
- start_time = time.time()
- log.debug('API Request: %r', req.__dict__)
- if no_retry:
- conn = urllib2.urlopen(req, timeout=timeout)
- else:
- conn = urlopen_with_retry(req, timeout=timeout)
- resp = conn.read()
- response_time = time.time() - start_time
- log.debug('API Response: GET %s ==> %r', api_url, resp)
- if response_time > 3.0:
- log.error('Slow API response-time: %s - %d ms', api_url, response_time * 1000)
- else:
- log.debug('API response-time: %s - %d ms', api_url, response_time * 1000)
- conn.close()
- result = json.loads(resp)
- put_cache('session', api_endpoint, cache_key, result)
- elif method == 'POST' or method == 'DELETE':
- data = ''
- if len(params) > 0:
- if old_api:
- data = urllib.urlencode(params)
- else:
- data = json.dumps(params)
- header_data['Content-Length'] = len(data)
- req = urllib2.Request(api_url, data, header_data)
- if method == 'DELETE':
- req.get_method = lambda : 'DELETE'
- log.debug('API Request: %r', req.__dict__)
- start_time = time.time()
- if no_retry:
- conn = urllib2.urlopen(req, timeout=timeout)
- else:
- conn = urlopen_with_retry(req, timeout=timeout)
- resp = conn.read()
- response_time = time.time() - start_time
- log.debug('API Response: (%d ms) %s %s => %r', response_time * 1000, method, api_url, resp)
- conn.close()
- result = json.loads(resp)
- if 'error' not in result:
- del_cache('session', api_endpoint)
- if 'productcode' in api_endpoint:
- clear_cache()
- except urllib2.HTTPError as e:
- resp = e.read()
- result = json.loads(resp)
- result['statuscode'] = e.code
- if e.code == 401 and not no_retry:
- from auth import main as auth
- resp = auth.user.session.update_token()
- if 'error' not in resp:
- return call_api(input)
- result = resp
- else:
- api_exception_log(e, api_endpoint, 20397)
- except socket.timeout as e:
- result = {'error': {'code': 20010,
- 'type': 'Internal Server Error'}}
- api_exception_log(e, request_data, 20010)
- except Exception as e:
- if hasattr(e, 'reason') and isinstance(e.reason, socket.timeout):
- result = {'error': {'code': 20010,
- 'type': 'Internal Server Error'}}
- api_exception_log(e, api_endpoint, 20010)
- else:
- result = {'error': {'code': 20399,
- 'type': 'Internal Server Error'}}
- api_exception_log(e, api_endpoint, 20399)
- return result
- def api_exception_log(e, request_data, code):
- try:
- log.error(str(e), exc_info=True, extra={'data': {'type': type(e).__name__,
- 'code': code,
- 'extra': request_data}})
- except Exception as e:
- log.exception(str(e))
- def update_api_cache(input, data):
- token = ''
- if 'token' in input and input['token'] is not None:
- token = input['token']
- if token == 'null':
- token = ''
- elif len(token) == 0:
- token = get_session_token()
- if token is None:
- return {'error': {'code': 20398,
- 'type': 'Internal Server Error',
- 'message': 'Invalid Token'}}
- else:
- path = input['path']
- api_endpoint = get_api_url(path)
- params = {}
- if 'params' in input and isinstance(input['params'], dict):
- params = input['params']
- cache_key = get_cache_key(params)
- put_cache('session', api_endpoint, cache_key, data)
- return
- def call_nss(input):
- global nss_cookie
- if input is None or 'endpoint' not in input:
- return {'error': {'code': 2,
- 'type': 'Bad Request',
- 'message': 'This NSS End Point is invalid.'}}
- else:
- params = {}
- if 'params' in input and isinstance(input['params'], dict):
- params = input['params']
- endpoint = get_nss_url(input['endpoint'])
- header_data = {'Content-Type': 'application/json'}
- if nss_cookie is not None:
- header_data['Cookie'] = nss_cookie
- try:
- log.debug('NSS Request init ( %s ): %r', endpoint, input)
- api_url = endpoint
- data = ''
- if len(params) > 0:
- data = json.dumps(params)
- header_data['Content-Length'] = len(data)
- from apps import launcher
- header_data['User-Agent'] = 'NexonLauncher.' + launcher.get_version()
- req = urllib2.Request(api_url, data, header_data)
- log.debug('NSS Request: ' + str(req.__dict__))
- start_time = time.time()
- conn = urlopen_with_retry(req)
- resp = conn.read()
- new_cookie = conn.info().getheader('Set-Cookie')
- if new_cookie is not None:
- nss_cookie = new_cookie
- response_time = time.time() - start_time
- log.debug('NSS Response: (%d ms) %s %s => %r', response_time * 1000, input['endpoint'], api_url, resp)
- conn.close()
- except Exception as e:
- log.exception(str(e))
- return
- def post_event(product_id, event_type, comments = None):
- try:
- from auth import main as auth
- path = 'log/' + event_type
- params = {'access_token': get_session_token(),
- 'product_id': product_id,
- 'device_id': auth.device_id,
- 'channel_id': 'nxa'}
- if comments:
- params['comments'] = json.dumps(comments)
- call_nss({'endpoint': event_type,
- 'params': params})
- except Exception as e:
- log.exception('failed to post_event: ' + str(e))
- def is_old_api(path):
- ex = '^/?(login$|logout$|me$|session/renew$|library$|library/access$|store/games$|activate$|profile$|log/)'
- m = re.search(ex, path)
- return m is not None
- def get_cache_key(url_params):
- if url_params is None or len(url_params) == 0:
- return '-'
- else:
- arr = sorted(url_params.items())
- return urllib.urlencode(arr)
- def get_cache(bucket_name, cache_root, cache_key):
- if bucket_name is None or len(bucket_name) == 0:
- bucket_name = '-'
- if bucket_name not in API_CACHE_DATA or cache_root not in API_CACHE_DATA[bucket_name] or cache_key not in API_CACHE_DATA[bucket_name][cache_root]:
- return
- else:
- cache_data = API_CACHE_DATA[bucket_name][cache_root][cache_key]
- exp_ts = cache_data['exp_ts']
- if exp_ts != 0 and exp_ts < time.time():
- API_CACHE_DATA[bucket_name][cache_root].pop(cache_key, None)
- return
- data = cache_data['data']
- return data
- def put_cache(bucket_name, cache_root, cache_key, data, duration = 3600):
- if data is None:
- return
- else:
- if bucket_name is None or len(bucket_name) == 0:
- bucket_name = '-'
- if bucket_name not in API_CACHE_DATA:
- API_CACHE_DATA[bucket_name] = {}
- if cache_root not in API_CACHE_DATA[bucket_name]:
- API_CACHE_DATA[bucket_name][cache_root] = {}
- if duration == 0:
- exp_ts = 0
- else:
- exp_ts = time.time() + duration
- API_CACHE_DATA[bucket_name][cache_root][cache_key] = {'exp_ts': exp_ts,
- 'data': data}
- return
- def del_cache(bucket_name, cache_root = None):
- if bucket_name is None or len(bucket_name) == 0:
- bucket_name = '-'
- if bucket_name not in API_CACHE_DATA:
- return
- elif cache_root is None:
- API_CACHE_DATA.pop(bucket_name, None)
- return
- else:
- API_CACHE_DATA[bucket_name].pop(cache_root, None)
- return
- def clear_cache():
- global API_CACHE_DATA
- API_CACHE_DATA = {}
Add Comment
Please, Sign In to add comment