aboutsummaryrefslogtreecommitdiffstats
path: root/toys/asynhttp/http_evented.py
blob: b5dbf348fbf95560b7ca017242c368339a347f36 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
"""Event based HTTP/1.1 client library

This module is an attempt to create a true asynchronous event based 
(javascript like) HTTP request-response interface. It is built up on
the asynchttp client interface.

contact:
Dhruv Matani <dhruvbird@gmail.com>
"""
__author__="""
Dhruv Matani
"""
__copyright__="""
Copyright (c) 2010 Dhruv Matani.

Distributed and Licensed under the provisions of the Python Open Source License
Agreement which is included by reference. (See 'Front Matter' in the latest
Python documentation)

WARRANTIES
YOU UNDERSTAND AND AGREE THAT:

a. YOUR USE OF THE PACKAGE IS AT YOUR SOLE RISK.  THE PACKAGE IS PROVIDED ON
AN 'AS IS' AND 'AS AVAILABLE' BASIS.  DOWNRIGHT EXPRESSLY DISCLAIMS ALL
WARRANTIES OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED
TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
AND NON-INFRINGEMENT.

b. DOWNRIGHT MAKES NO WARRANTY THAT (1) THE PACKAGE WILL MEET YOUR
REQUIREMENTS, (2) THE PACKAGE WILL BE UNINTERRUPTED, TIMELY, SECURE, OR
ERROR-FREE, (3) THE RESULTS THAT MAY BE OBTAINED FROM THE USE OF THE PACKAGE
WILL BE ACCURATE OR RELIABLE, (4) THE OTHER MATERIAL PURCHASED OR OBTAINED BY
YOU THROUGH THE PACKAGE WILL MEET YOUR EXPECTATIONS,, AND (5) ANY ERRORS IN
THE PACKAGE WILL BE CORRECTED.

c. ANY MATERIALS DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE
PACKAGE IS DONE AT YOUR OWN DISCRETION AND RISK AND THAT YOU WILL BE SOLELY
RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR LOSS OF DATA THAT
RESULTS FROM THE DOWNLOAD OF ANY SUCH MATERIAL.

d. NO ADVICE OR INFORMATION, WHETHER ORAL OR WRITTEN, OBTAINED BY YOU FROM
DOWNRIGHT OR THROUGH OR FROM THE PACKAGE SHALL CREATE ANY WARRANTY NOT
EXPRESSLY STATED IN THE TOS.

LIMITATION OF LIABILITY
YOU EXPRESSLY UNDERSTAND AND AGREE THAT DOWNRIGHT SHALL NOT BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES,
INCLUDING BUT NOT LIMITED TO, DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE,
DATA OR OTHER INTANGIBLE LOSSES (EVEN IF DOWNRIGHT HAS BEEN ADVISED OF SUCH
DAMAGES), RESULTING FROM:
(1) THE USE OR THE INABILITY TO USE THE PACKAGE;
(2) THE COST OF PROCUREMENT OF SUBSTITUTE GOODS AND SERVICES RESULTING FROM
ANY GOODS, DATA, INFORMATION OR SERVICES PURCHASED OR OBTAINED OR MESSAGES
RECEIVED OR TRANSACTIONS ENTERED INTO THROUGH OR FROM THE PACKAGE;
(3) UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA;
(4) STATEMENTS OF CONDUCT OF ANY THIRD PARTY ON THE PACKAGE; OR
(5) ANY OTHER MATTER RELATING TO THE PACKAGE.
"""

from asynchttp import AsyncHTTPConnection
import collections

STATE_CONNECTING   = 1
STATE_CONNECTED    = 2
STATE_DISCONNECTED = 3

def call_if_not_none_and_callable(o, **kwargs):
    if o is not None and callable(o):
        o(**kwargs)

class http_evented(AsyncHTTPConnection, object):
    """
    This is an event based async HTTP client. It lets you register
    events which will be called whenever an HTTP request is completed.

    It sort of mimics the javascript way of making ajax calls, which
    I have started to like for various reasons -- the last of which
    is efficiency though ;)

    Use function parameter binding while using this module to get the
    maximum bang for your buck :D

    DO NOT use request pipelining with unordered responses along with
    this class if you are expecting your handlers to be called. You may
    however use standard HTTP request pipelining in which responses are
    guaranteed to be returned in the same order as the requests are made

    However, if you are handling re-ordering of responses at a higher
    layer, then you may use it as you feel free. Make sure that that you
    are prepared to handle the calling of the event handlers in ANY order
    when the response is received from the server. You can typically
    handle this by always registering the same function for every request
    that you make
    """
    def __init__(self, host_and_port, onConnected=None,
                 onClose=None, onException=None):
        self._connection_state = STATE_DISCONNECTED
        self._eventHandlers    = collections.deque()
        self._onClose          = onClose
        self._onException      = onException
        self._onConnected      = None
        self.reconnect(host_and_port, onConnected)

    def reconnect(self, host_and_port, onReconnect=None):
        """
        [Re]connect to the HTTP end point
        """
        if self._connection_state != STATE_CONNECTING and self._connection_state == STATE_DISCONNECTED:
            host, port = host_and_port
            self._connection_state = STATE_CONNECTING
            self._onConnected = onReconnect
            AsyncHTTPConnection.__init__(self, host, port)
            self.connect()

    def handle_response(self):
        """
        Called when a response from the server is received
        """
        call_if_not_none_and_callable(self._eventHandlers.popleft(),
                                      response=self.response)

    def handle_connect(self):
        """
        Called when the connection to the HTTP end point succeeds
        """
        print "http_evented::handle_connect"
        self._connection_state = STATE_CONNECTED
        super(http_evented, self).handle_connect()
        call_if_not_none_and_callable(self._onConnected)

    def handle_close(self):
        """
        Called when the connection is closed from the server end
        """
        self._connection_state = STATE_DISCONNECTED
        super(http_evented, self).handle_close()
        self._fail_all_pending_event_handlers()
        call_if_not_none_and_callable(self._onClose)

    def handle_error(self):
        super(http_evented, self).handle_error()
        self._perform_on_error_handling()

    def handle_expt(self):
        """
        Called in case an exception is thrown while executing code.
        This can also happen due to disconnection if the remote
        HTTP end point goes down
        """
        self._perform_on_error_handling()

    def _perform_on_error_handling(self):
        self._connection_state = STATE_DISCONNECTED
        self._fail_all_pending_event_handlers()
        call_if_not_none_and_callable(self._onException)

    def push_HTTP_request(self, method, url, body, headers, callback=None):
        """
        Just PUSH the request on to the request queue. It will be sent
        ONLY when pop_response() is called
        """
        self.request(method, url, body, headers)
        self.push_request()
        self._eventHandlers.append(callback)

    def make_HTTP_request(self, method, url, body, headers, callback=None):
        """
        Make an HTTP request to the other HTTP end point. The callable
        'callback' will be called with either no parameter OR a single
        parameter named 'response' which will hold the HTTP response
        object. If there is an error or the response could NOT be
        processed for some reason, then response=None is passed to the
        callback function
        """
        self.push_HTTP_request(method, url, body, headers, callback)
        self.pop_response()

    def _fail_all_pending_event_handlers(self):
        for eh in self._eventHandlers:
            call_if_not_none_and_callable(eh, response=None)
        self._eventHandlers.clear()