]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/python/osrf/gateway.py
8796ce223fcfe2a0721c0da2ea9f5bfd8d88dc13
[OpenSRF.git] / src / python / osrf / gateway.py
1 from xml.dom import minidom
2 from xml.sax import handler, make_parser, saxutils
3 from osrf.json import to_object
4 from osrf.net_obj import NetworkObject, new_object_from_hint
5 import osrf.log
6 import urllib, urllib2, sys, re
7
8 defaultHost = None
9
10 class GatewayRequest:
11     def __init__(self, service, method, params=[]):
12         self.service = service
13         self.method = method
14         self.params = params
15         self.path = 'gateway'
16         self.bytes_read = 0 # for now this, this is really characters read
17
18     def setPath(self, path):
19         self.path = path
20
21     def send(self):
22         params = self.buildPOSTParams()
23         request = urllib2.Request(self.buildURL(), data=params)
24         response = None
25         try:
26             response =urllib2.urlopen(request)
27         except urllib2.HTTPError, e:
28             # log this?
29             sys.stderr.write('%s => %s?%s\n' % (unicode(e), self.buildURL(), params))
30             raise e
31             
32         return self.handleResponse(response)
33
34     def buildPOSTParams(self):
35
36         params = urllib.urlencode({   
37             'service': self.service,
38             'method': self.method,
39             'format': self.getFormat(),
40             'input_format': self.getInputFormat()
41         })
42
43         for p in self.params:
44             params += '&param=%s' % urllib.quote(self.encodeParam(p), "'/")
45         return params
46
47     def setDefaultHost(host):
48         global defaultHost
49         defaultHost = host
50     setDefaultHost = staticmethod(setDefaultHost)
51
52     def buildURL(self):
53         """
54         Builds the URL for the OpenSRF gateway based on the host and path
55
56         Previous versions of the code assumed that the host would be a bare
57         hostname or IP address, and prepended the http:// protocol. However,
58         to enable more secure communications, now we check for the existence
59         of the HTTP or HTTPS prefix and use that if it has been supplied.
60         """
61
62         if defaultHost.lower().startswith(('http://', 'https://')):
63             return '%s/%s' % (defaultHost, self.path)
64
65         return 'http://%s/%s' % (defaultHost, self.path)
66
67 class JSONGatewayRequest(GatewayRequest):
68     def __init__(self, service, method, *params):
69         GatewayRequest.__init__(self, service, method, list(params))
70
71     def getFormat(self):
72         return 'json'
73
74     def getInputFormat(self):
75         return self.getFormat()
76
77     def handleResponse(self, response):
78
79         data = response.read()
80         self.bytes_read = len(str(response.headers)) + len(data)
81         obj = to_object(data)
82
83         if obj['status'] != 200:
84             sys.stderr.write('JSON gateway returned status %d:\n' % (obj['status']))
85             return None
86
87         # the gateway wraps responses in an array to handle streaming data
88         # if there is only one item in the array, it (probably) wasn't a streaming request
89         p = obj['payload']
90         if len(p) > 1: return p
91         if len(p): return p[0]
92         return None
93
94     def encodeParam(self, param):
95         return osrf.json.to_json(param)
96
97 class XMLGatewayRequest(GatewayRequest):
98
99     def __init__(self, service, method, *params):
100         GatewayRequest.__init__(self, service, method, list(params))
101
102     def getFormat(self):
103         return 'xml'
104
105     def getInputFormat(self):
106         return self.getFormat()
107
108     def handleResponse(self, response):
109         handler = XMLGatewayParser()
110         parser = make_parser()
111         parser.setContentHandler(handler)
112         try:
113             parser.parse(response)
114         except Exception, e:
115             osrf.log.log_error('Error parsing gateway XML: %s' % unicode(e))
116             return None
117
118         return handler.getResult()
119
120     def encodeParam(self, param):
121         return osrf.net_obj.to_xml(param);
122
123 class XMLGatewayParser(handler.ContentHandler):
124
125     def __init__(self):
126         self.result = None
127         self.objStack = []
128         self.keyStack = []
129         self.posStack = [] # for tracking array-based hinted object indices
130
131         # true if we are parsing an element that may have character data
132         self.charsPending = 0 
133
134     def getResult(self):
135         return self.result
136
137     def __getAttr(self, attrs, name):
138         for (k, v) in attrs.items():
139             if k == name:
140                 return v
141         return None
142
143     def startElement(self, name, attrs):
144         
145         if self.charsPending:
146             # we just read a 'string' or 'number' element that resulted
147             # in no text data.  Appaned a None object
148             self.appendChild(None)
149
150         if name == 'null':
151             self.appendChild(None)
152             return
153
154         if name == 'string' or name == 'number':
155             self.charsPending = True
156             return
157
158         if name == 'element': # this is an object item wrapper
159             self.keyStack.append(self.__getAttr(attrs, 'key'))
160             return
161
162         hint = self.__getAttr(attrs, 'class_hint')
163         if hint:
164             obj = new_object_from_hint(hint)
165             self.appendChild(obj)
166             self.objStack.append(obj)
167             if name == 'array':
168                 self.posStack.append(0)
169             return
170
171         if name == 'array':
172             obj = []
173             self.appendChild(obj)
174             self.objStack.append(obj)
175             return
176
177         if name == 'object':
178             obj = {}
179             self.appendChild(obj)
180             self.objStack.append(obj)
181             return
182
183         if name == 'boolean':
184             self.appendChild((self.__getAttr(attrs, 'value') == 'true'))
185             return
186
187
188     def appendChild(self, child):
189
190         if self.result == None:
191             self.result = child
192
193         if not self.objStack: return;
194
195         parent = self.objStack[len(self.objStack)-1]
196
197         if isinstance(parent, list):
198             parent.append(child)
199         else:
200             if isinstance(parent, dict):
201                 parent[self.keyStack.pop()] = child
202             else:
203                 if isinstance(parent, NetworkObject):
204                     key = None
205                     if parent.get_registry().protocol == 'array':
206                         keys = parent.get_registry().keys
207                         i = self.posStack.pop()
208                         key = keys[i]
209                         if i+1 < len(keys):
210                             self.posStack.append(i+1)
211                     else:
212                         key = self.keyStack.pop()
213
214                     parent.set_field(key, child)
215
216     def endElement(self, name):
217         if name == 'array' or name == 'object':
218             self.objStack.pop()
219
220     def characters(self, chars):
221         self.charsPending = False
222         self.appendChild(urllib.unquote_plus(chars))
223
224
225
226     
227