comparison idavoll/gateway.py @ 183:c21b986cff30

Implement HTTP client to gateway and implement functional tests with it.
author Ralph Meijer <ralphm@ik.nu>
date Fri, 11 Apr 2008 14:41:16 +0000
parents faf1c9bc2612
children 9038908dc2f5
comparison
equal deleted inserted replaced
182:4aa29b1a8c67 183:c21b986cff30
1 # -*- test-case-name: idavoll.test.test_gateway -*-
2 #
1 # Copyright (c) 2003-2008 Ralph Meijer 3 # Copyright (c) 2003-2008 Ralph Meijer
2 # See LICENSE for details. 4 # See LICENSE for details.
3 5
6 """
7 Web resources and client for interacting with pubsub services.
8 """
9
4 import cgi 10 import cgi
5 from time import gmtime, strftime 11 from time import gmtime, strftime
12 import urllib
13 import urlparse
6 14
7 import simplejson 15 import simplejson
8 16
9 from twisted.application import service 17 from twisted.application import service
10 from twisted.internet import defer, reactor 18 from twisted.internet import defer, reactor
19 from twisted.python import log
11 from twisted.web import client 20 from twisted.web import client
12 from twisted.web2 import http, http_headers, resource, responsecode 21 from twisted.web2 import http, http_headers, resource, responsecode
22 from twisted.web2 import channel, server
13 from twisted.web2.stream import readStream 23 from twisted.web2.stream import readStream
14 from twisted.words.protocols.jabber.jid import JID 24 from twisted.words.protocols.jabber.jid import JID
15 from twisted.words.protocols.jabber.error import StanzaError 25 from twisted.words.protocols.jabber.error import StanzaError
16 from twisted.words.xish import domish 26 from twisted.words.xish import domish
17 27
19 from wokkel.pubsub import PubSubClient 29 from wokkel.pubsub import PubSubClient
20 30
21 from idavoll import error 31 from idavoll import error
22 32
23 NS_ATOM = 'http://www.w3.org/2005/Atom' 33 NS_ATOM = 'http://www.w3.org/2005/Atom'
24 34 MIME_ATOM_ENTRY = 'application/atom+xml;type=entry'
35 MIME_JSON = 'application/json'
25 36
26 class RemoteSubscriptionService(service.Service, PubSubClient): 37 class RemoteSubscriptionService(service.Service, PubSubClient):
27 38
28 def __init__(self, jid): 39 def __init__(self, jid):
29 self.jid = jid 40 self.jid = jid
140 151
141 if eventType: 152 if eventType:
142 headers['Event'] = eventType 153 headers['Event'] = eventType
143 154
144 def postNotification(callbackURI): 155 def postNotification(callbackURI):
145 return client.getPage(str(callbackURI), 156 d = client.getPage(str(callbackURI),
146 method='POST', 157 method='POST',
147 postdata=postdata, 158 postdata=postdata,
148 headers=headers) 159 headers=headers)
160 d.addErrback(log.err)
149 161
150 for callbackURI in callbacks: 162 for callbackURI in callbacks:
151 reactor.callLater(0, postNotification, callbackURI) 163 reactor.callLater(0, postNotification, callbackURI)
152 164
153 165
326 responsecode.UNSUPPORTED_MEDIA_TYPE, 338 responsecode.UNSUPPORTED_MEDIA_TYPE,
327 "Unsupported Media Type: %s" % 339 "Unsupported Media Type: %s" %
328 http_headers.generateContentType(ctype))) 340 http_headers.generateContentType(ctype)))
329 341
330 def parseXMLPayload(self, stream): 342 def parseXMLPayload(self, stream):
331 if not stream:
332 print "Stream is empty", repr(stream)
333 elif not stream.length:
334 print "Stream length is", repr(stream.length)
335 p = WebStreamParser() 343 p = WebStreamParser()
336 return p.parse(stream) 344 return p.parse(stream)
337 345
338 def http_POST(self, request): 346 def http_POST(self, request):
339 """ 347 """
464 def __init__(self, service): 472 def __init__(self, service):
465 self.service = service 473 self.service = service
466 474
467 def render(self, request): 475 def render(self, request):
468 def responseFromNodes(nodeIdentifiers): 476 def responseFromNodes(nodeIdentifiers):
469 import pprint 477 return http.Response(responsecode.OK,
470 return http.Response(responsecode.OK, stream=pprint.pformat(nodeIdentifiers)) 478 stream=simplejson.dumps(nodeIdentifiers))
471 479
472 d = self.service.get_nodes() 480 d = self.service.get_nodes()
473 d.addCallback(responseFromNodes) 481 d.addCallback(responseFromNodes)
474 return d 482 return d
483
484
485 def getPageWithFactory(url, contextFactory=None, *args, **kwargs):
486 """Download a web page.
487
488 Download a page. Return the factory that holds a deferred, which will
489 callback with a page (as a string) or errback with a description of the
490 error.
491
492 See HTTPClientFactory to see what extra args can be passed.
493 """
494
495 scheme, host, port, path = client._parse(url)
496 factory = client.HTTPClientFactory(url, *args, **kwargs)
497 factory.protocol.handleStatus_204 = lambda self: self.handleStatus_200()
498
499 if scheme == 'https':
500 from twisted.internet import ssl
501 if contextFactory is None:
502 contextFactory = ssl.ClientContextFactory()
503 reactor.connectSSL(host, port, factory, contextFactory)
504 else:
505 reactor.connectTCP(host, port, factory)
506 return factory
507
508
509 class CallbackResource(resource.Resource):
510 """
511 Web resource for retrieving gateway notifications.
512 """
513
514 def __init__(self, callback):
515 self.callback = callback
516
517 http_GET = None
518
519 def http_POST(self, request):
520 p = WebStreamParser()
521 d = p.parse(request.stream)
522 d.addCallback(self.callback, request.headers)
523 d.addCallback(lambda _: http.Response(responsecode.NO_CONTENT))
524 return d
525
526 class GatewayClient(service.Service):
527 """
528 Service that provides client access to the HTTP Gateway into Idavoll.
529 """
530
531 agent = "Idavoll HTTP Gateway Client"
532
533 def __init__(self, baseURI, callbackHost=None, callbackPort=None):
534 self.baseURI = baseURI
535 self.callbackHost = callbackHost or 'localhost'
536 self.callbackPort = callbackPort or 8087
537 root = resource.Resource()
538 root.child_callback = CallbackResource(lambda *args, **kwargs: self.callback(*args, **kwargs))
539 self.site = server.Site(root)
540
541 def startService(self):
542 self.port = reactor.listenTCP(self.callbackPort,
543 channel.HTTPFactory(self.site))
544
545 def stopService(self):
546 return self.port.stopListening()
547
548 def _makeURI(self, verb, query=None):
549 uriComponents = urlparse.urlparse(self.baseURI)
550 uri = urlparse.urlunparse((uriComponents[0],
551 uriComponents[1],
552 uriComponents[2] + verb,
553 '',
554 query and urllib.urlencode(query) or '',
555 ''))
556 return uri
557
558 def callback(self, data, headers):
559 pass
560
561 def create(self):
562 f = getPageWithFactory(self._makeURI('create'),
563 method='POST',
564 agent=self.agent)
565 return f.deferred.addCallback(simplejson.loads)
566
567 def publish(self, entry, xmppURI=None):
568 query = xmppURI and {'uri': xmppURI}
569
570 f = getPageWithFactory(self._makeURI('publish', query),
571 method='POST',
572 postdata=entry.toXml().encode('utf-8'),
573 headers={'Content-Type': MIME_ATOM_ENTRY},
574 agent=self.agent)
575 return f.deferred.addCallback(simplejson.loads)
576
577 def listNodes(self):
578 f = getPageWithFactory(self._makeURI('list'),
579 method='GET',
580 agent=self.agent)
581 return f.deferred.addCallback(simplejson.loads)
582
583 def subscribe(self, xmppURI):
584 params = {'uri': xmppURI,
585 'callback': 'http://%s:%s/callback' % (self.callbackHost,
586 self.callbackPort)}
587 f = getPageWithFactory(self._makeURI('subscribe'),
588 method='POST',
589 postdata=simplejson.dumps(params),
590 headers={'Content-Type': MIME_JSON},
591 agent=self.agent)
592 return f.deferred