##// END OF EJS Templates
API fixes...
marcink -
r1489:b951731f beta
parent child Browse files
Show More
@@ -1,228 +1,225
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.api
3 rhodecode.controllers.api
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 JSON RPC controller
6 JSON RPC controller
7
7
8 :created_on: Aug 20, 2011
8 :created_on: Aug 20, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import inspect
28 import inspect
29 import json
29 import json
30 import logging
30 import logging
31 import types
31 import types
32 import urllib
32 import urllib
33
33
34 from paste.response import replace_header
34 from paste.response import replace_header
35
35
36 from pylons.controllers import WSGIController
36 from pylons.controllers import WSGIController
37 from pylons.controllers.util import Response
37 from pylons.controllers.util import Response
38
38
39 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
39 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
40 HTTPBadRequest, HTTPError
40 HTTPBadRequest, HTTPError
41
41
42 from rhodecode.model.user import User
42 from rhodecode.model.db import User
43 from rhodecode.lib.auth import AuthUser
43 from rhodecode.lib.auth import AuthUser
44
44
45 log = logging.getLogger('JSONRPC')
45 log = logging.getLogger('JSONRPC')
46
46
47 class JSONRPCError(BaseException):
47 class JSONRPCError(BaseException):
48
48
49 def __init__(self, message):
49 def __init__(self, message):
50 self.message = message
50 self.message = message
51
51
52 def __str__(self):
52 def __str__(self):
53 return str(self.message)
53 return str(self.message)
54
54
55
55
56 def jsonrpc_error(message, code=None):
56 def jsonrpc_error(message, code=None):
57 """Generate a Response object with a JSON-RPC error body"""
57 """Generate a Response object with a JSON-RPC error body"""
58 return Response(body=json.dumps(dict(result=None,
58 return Response(body=json.dumps(dict(result=None,
59 error=message)))
59 error=message)))
60
60
61
61
62 class JSONRPCController(WSGIController):
62 class JSONRPCController(WSGIController):
63 """
63 """
64 A WSGI-speaking JSON-RPC controller class
64 A WSGI-speaking JSON-RPC controller class
65
65
66 See the specification:
66 See the specification:
67 <http://json-rpc.org/wiki/specification>`.
67 <http://json-rpc.org/wiki/specification>`.
68
68
69 Valid controller return values should be json-serializable objects.
69 Valid controller return values should be json-serializable objects.
70
70
71 Sub-classes should catch their exceptions and raise JSONRPCError
71 Sub-classes should catch their exceptions and raise JSONRPCError
72 if they want to pass meaningful errors to the client.
72 if they want to pass meaningful errors to the client.
73
73
74 """
74 """
75
75
76 def _get_method_args(self):
76 def _get_method_args(self):
77 """
77 """
78 Return `self._rpc_args` to dispatched controller method
78 Return `self._rpc_args` to dispatched controller method
79 chosen by __call__
79 chosen by __call__
80 """
80 """
81 return self._rpc_args
81 return self._rpc_args
82
82
83 def __call__(self, environ, start_response):
83 def __call__(self, environ, start_response):
84 """
84 """
85 Parse the request body as JSON, look up the method on the
85 Parse the request body as JSON, look up the method on the
86 controller and if it exists, dispatch to it.
86 controller and if it exists, dispatch to it.
87 """
87 """
88
89 if 'CONTENT_LENGTH' not in environ:
88 if 'CONTENT_LENGTH' not in environ:
90 log.debug("No Content-Length")
89 log.debug("No Content-Length")
91 return jsonrpc_error(0, "No Content-Length")
90 return jsonrpc_error(message="No Content-Length in request")
92 else:
91 else:
93 length = environ['CONTENT_LENGTH'] or 0
92 length = environ['CONTENT_LENGTH'] or 0
94 length = int(environ['CONTENT_LENGTH'])
93 length = int(environ['CONTENT_LENGTH'])
95 log.debug('Content-Length: %s', length)
94 log.debug('Content-Length: %s', length)
96
95
97 if length == 0:
96 if length == 0:
98 log.debug("Content-Length is 0")
97 log.debug("Content-Length is 0")
99 return jsonrpc_error(0, "Content-Length is 0")
98 return jsonrpc_error(message="Content-Length is 0")
100
99
101 raw_body = environ['wsgi.input'].read(length)
100 raw_body = environ['wsgi.input'].read(length)
102
101
103 try:
102 try:
104 json_body = json.loads(urllib.unquote_plus(raw_body))
103 json_body = json.loads(urllib.unquote_plus(raw_body))
105 except ValueError as e:
104 except ValueError as e:
106 #catch JSON errors Here
105 #catch JSON errors Here
107 return jsonrpc_error("JSON parse error ERR:%s RAW:%r" \
106 return jsonrpc_error(message="JSON parse error ERR:%s RAW:%r" \
108 % (e, urllib.unquote_plus(raw_body)))
107 % (e, urllib.unquote_plus(raw_body)))
109
108
110
111 #check AUTH based on API KEY
109 #check AUTH based on API KEY
112
113 try:
110 try:
114 self._req_api_key = json_body['api_key']
111 self._req_api_key = json_body['api_key']
115 self._req_method = json_body['method']
112 self._req_method = json_body['method']
116 self._req_params = json_body['args']
113 self._req_params = json_body['args']
117 log.debug('method: %s, params: %s',
114 log.debug('method: %s, params: %s',
118 self._req_method,
115 self._req_method,
119 self._req_params)
116 self._req_params)
120 except KeyError as e:
117 except KeyError as e:
121 return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
118 return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
122
119
123 #check if we can find this session using api_key
120 #check if we can find this session using api_key
124 try:
121 try:
125 u = User.get_by_api_key(self._req_api_key)
122 u = User.get_by_api_key(self._req_api_key)
126 auth_u = AuthUser(u.user_id, self._req_api_key)
123 auth_u = AuthUser(u.user_id, self._req_api_key)
127 except Exception as e:
124 except Exception as e:
128 return jsonrpc_error(message='Invalid API KEY')
125 return jsonrpc_error(message='Invalid API KEY')
129
126
130 self._error = None
127 self._error = None
131 try:
128 try:
132 self._func = self._find_method()
129 self._func = self._find_method()
133 except AttributeError, e:
130 except AttributeError, e:
134 return jsonrpc_error(str(e))
131 return jsonrpc_error(message=str(e))
135
132
136 # now that we have a method, add self._req_params to
133 # now that we have a method, add self._req_params to
137 # self.kargs and dispatch control to WGIController
134 # self.kargs and dispatch control to WGIController
138 arglist = inspect.getargspec(self._func)[0][1:]
135 arglist = inspect.getargspec(self._func)[0][1:]
139
136
140 # this is little trick to inject logged in user for
137 # this is little trick to inject logged in user for
141 # perms decorators to work they expect the controller class to have
138 # perms decorators to work they expect the controller class to have
142 # rhodecode_user set
139 # rhodecode_user set
143 self.rhodecode_user = auth_u
140 self.rhodecode_user = auth_u
144
141
145 if 'user' not in arglist:
142 if 'user' not in arglist:
146 return jsonrpc_error('This method [%s] does not support '
143 return jsonrpc_error(message='This method [%s] does not support '
147 'authentication (missing user param)' %
144 'authentication (missing user param)' %
148 self._func.__name__)
145 self._func.__name__)
149
146
150 # get our arglist and check if we provided them as args
147 # get our arglist and check if we provided them as args
151 for arg in arglist:
148 for arg in arglist:
152 if arg == 'user':
149 if arg == 'user':
153 # user is something translated from api key and this is
150 # user is something translated from api key and this is
154 # checked before
151 # checked before
155 continue
152 continue
156
153
157 if not self._req_params or arg not in self._req_params:
154 if not self._req_params or arg not in self._req_params:
158 return jsonrpc_error('Missing %s arg in JSON DATA' % arg)
155 return jsonrpc_error(message='Missing %s arg in JSON DATA' % arg)
159
156
160 self._rpc_args = dict(user=u)
157 self._rpc_args = dict(user=u)
161 self._rpc_args.update(self._req_params)
158 self._rpc_args.update(self._req_params)
162
159
163 self._rpc_args['action'] = self._req_method
160 self._rpc_args['action'] = self._req_method
164 self._rpc_args['environ'] = environ
161 self._rpc_args['environ'] = environ
165 self._rpc_args['start_response'] = start_response
162 self._rpc_args['start_response'] = start_response
166
163
167 status = []
164 status = []
168 headers = []
165 headers = []
169 exc_info = []
166 exc_info = []
170 def change_content(new_status, new_headers, new_exc_info=None):
167 def change_content(new_status, new_headers, new_exc_info=None):
171 status.append(new_status)
168 status.append(new_status)
172 headers.extend(new_headers)
169 headers.extend(new_headers)
173 exc_info.append(new_exc_info)
170 exc_info.append(new_exc_info)
174
171
175 output = WSGIController.__call__(self, environ, change_content)
172 output = WSGIController.__call__(self, environ, change_content)
176 output = list(output)
173 output = list(output)
177 headers.append(('Content-Length', str(len(output[0]))))
174 headers.append(('Content-Length', str(len(output[0]))))
178 replace_header(headers, 'Content-Type', 'application/json')
175 replace_header(headers, 'Content-Type', 'application/json')
179 start_response(status[0], headers, exc_info[0])
176 start_response(status[0], headers, exc_info[0])
180
177
181 return output
178 return output
182
179
183 def _dispatch_call(self):
180 def _dispatch_call(self):
184 """
181 """
185 Implement dispatch interface specified by WSGIController
182 Implement dispatch interface specified by WSGIController
186 """
183 """
187 try:
184 try:
188 raw_response = self._inspect_call(self._func)
185 raw_response = self._inspect_call(self._func)
189 print raw_response
186 print raw_response
190 if isinstance(raw_response, HTTPError):
187 if isinstance(raw_response, HTTPError):
191 self._error = str(raw_response)
188 self._error = str(raw_response)
192 except JSONRPCError as e:
189 except JSONRPCError as e:
193 self._error = str(e)
190 self._error = str(e)
194 except Exception as e:
191 except Exception as e:
195 log.debug('Encountered unhandled exception: %s', repr(e))
192 log.debug('Encountered unhandled exception: %s', repr(e))
196 json_exc = JSONRPCError('Internal server error')
193 json_exc = JSONRPCError('Internal server error')
197 self._error = str(json_exc)
194 self._error = str(json_exc)
198
195
199 if self._error is not None:
196 if self._error is not None:
200 raw_response = None
197 raw_response = None
201
198
202 response = dict(result=raw_response, error=self._error)
199 response = dict(result=raw_response, error=self._error)
203
200
204 try:
201 try:
205 return json.dumps(response)
202 return json.dumps(response)
206 except TypeError, e:
203 except TypeError, e:
207 log.debug('Error encoding response: %s', e)
204 log.debug('Error encoding response: %s', e)
208 return json.dumps(dict(result=None,
205 return json.dumps(dict(result=None,
209 error="Error encoding response"))
206 error="Error encoding response"))
210
207
211 def _find_method(self):
208 def _find_method(self):
212 """
209 """
213 Return method named by `self._req_method` in controller if able
210 Return method named by `self._req_method` in controller if able
214 """
211 """
215 log.debug('Trying to find JSON-RPC method: %s', self._req_method)
212 log.debug('Trying to find JSON-RPC method: %s', self._req_method)
216 if self._req_method.startswith('_'):
213 if self._req_method.startswith('_'):
217 raise AttributeError("Method not allowed")
214 raise AttributeError("Method not allowed")
218
215
219 try:
216 try:
220 func = getattr(self, self._req_method, None)
217 func = getattr(self, self._req_method, None)
221 except UnicodeEncodeError:
218 except UnicodeEncodeError:
222 raise AttributeError("Problem decoding unicode in requested "
219 raise AttributeError("Problem decoding unicode in requested "
223 "method name.")
220 "method name.")
224
221
225 if isinstance(func, types.MethodType):
222 if isinstance(func, types.MethodType):
226 return func
223 return func
227 else:
224 else:
228 raise AttributeError("No such method: %s" % self._req_method)
225 raise AttributeError("No such method: %s" % self._req_method)
General Comments 0
You need to be logged in to leave comments. Login now