Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Fork of PubNub by
PubNub.cpp@0:9858347c382d, 2014-03-02 (annotated)
- Committer:
- pasky
- Date:
- Sun Mar 02 01:32:54 2014 +0000
- Revision:
- 0:9858347c382d
- Child:
- 6:09d0a383fdde
- Child:
- 7:a7bbafe53f2d
Initial version: Basic working API + short docs with examples. Please refer to PubNubDemo program for a reference usage code.
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
pasky | 0:9858347c382d | 1 | #include <cstring> |
pasky | 0:9858347c382d | 2 | |
pasky | 0:9858347c382d | 3 | #include "mbed.h" |
pasky | 0:9858347c382d | 4 | #include "EthernetInterface.h" |
pasky | 0:9858347c382d | 5 | |
pasky | 0:9858347c382d | 6 | #include "PubNub.h" |
pasky | 0:9858347c382d | 7 | |
pasky | 0:9858347c382d | 8 | |
pasky | 0:9858347c382d | 9 | #define DBG if (0) // change to if (1) to enable debug prints on USB serial |
pasky | 0:9858347c382d | 10 | |
pasky | 0:9858347c382d | 11 | |
pasky | 0:9858347c382d | 12 | /* We roll our own HTTP communication stack as donatien's HTTPClient has |
pasky | 0:9858347c382d | 13 | * some hard limits on URL length and this will allow a more frugal RAM |
pasky | 0:9858347c382d | 14 | * usage for us. */ |
pasky | 0:9858347c382d | 15 | |
pasky | 0:9858347c382d | 16 | /* Also, we pass messages as serialized JSON strings instead of parsed |
pasky | 0:9858347c382d | 17 | * structures. The available JSON parsers (picojson in particular) are |
pasky | 0:9858347c382d | 18 | * heavy on many tiny memory allocations, which can get very troublesome |
pasky | 0:9858347c382d | 19 | * with mbed's malloc() - we get very high memory fragmentation. In many |
pasky | 0:9858347c382d | 20 | * cases, working with raw JSON is just fine, and in our demo example, |
pasky | 0:9858347c382d | 21 | * we still show picojson usage for end-user parsing of complex incoming |
pasky | 0:9858347c382d | 22 | * messages. */ |
pasky | 0:9858347c382d | 23 | |
pasky | 0:9858347c382d | 24 | |
pasky | 0:9858347c382d | 25 | /** Custom no-frills HTTP stream. */ |
pasky | 0:9858347c382d | 26 | |
pasky | 0:9858347c382d | 27 | class PubNubHTTP: public TCPSocketConnection { |
pasky | 0:9858347c382d | 28 | public: |
pasky | 0:9858347c382d | 29 | /* Connect http socket to origin. */ |
pasky | 0:9858347c382d | 30 | PubNubRes http_connect(const char *hostname); |
pasky | 0:9858347c382d | 31 | |
pasky | 0:9858347c382d | 32 | /* These methods are used to send the request type and URL. */ |
pasky | 0:9858347c382d | 33 | /* Send a given NUL-terminated string over the http socket. */ |
pasky | 0:9858347c382d | 34 | void sendstr(const char *string); |
pasky | 0:9858347c382d | 35 | /* Send a given NUL-terminated string over the http socket, URL encoded. */ |
pasky | 0:9858347c382d | 36 | void sendstr_urlenc(const char *string); |
pasky | 0:9858347c382d | 37 | |
pasky | 0:9858347c382d | 38 | /* A common HTTP request "bottom half" - send the rest of headers |
pasky | 0:9858347c382d | 39 | * and wait for reply + receive. */ |
pasky | 0:9858347c382d | 40 | PubNubRes http_request_bh(const char *host, char **reply, int *replylen); |
pasky | 0:9858347c382d | 41 | |
pasky | 0:9858347c382d | 42 | protected: |
pasky | 0:9858347c382d | 43 | /* These methods represent sub-stages of http_request_bh(). |
pasky | 0:9858347c382d | 44 | * They share the state of m_replybuf and m_replylen. */ |
pasky | 0:9858347c382d | 45 | PubNubRes http_send_headers(const char *host); |
pasky | 0:9858347c382d | 46 | PubNubRes http_recv_headers(); |
pasky | 0:9858347c382d | 47 | PubNubRes http_recv_content(); |
pasky | 0:9858347c382d | 48 | |
pasky | 0:9858347c382d | 49 | char *m_replybuf; |
pasky | 0:9858347c382d | 50 | int m_replylen; |
pasky | 0:9858347c382d | 51 | int m_content_length; |
pasky | 0:9858347c382d | 52 | }; |
pasky | 0:9858347c382d | 53 | |
pasky | 0:9858347c382d | 54 | PubNubRes |
pasky | 0:9858347c382d | 55 | PubNubHTTP::http_connect(const char *hostname) |
pasky | 0:9858347c382d | 56 | { |
pasky | 0:9858347c382d | 57 | int ret = connect(hostname, 80); |
pasky | 0:9858347c382d | 58 | if (ret < 0) { |
pasky | 0:9858347c382d | 59 | close(); |
pasky | 0:9858347c382d | 60 | return PNR_IO_ERROR; |
pasky | 0:9858347c382d | 61 | } |
pasky | 0:9858347c382d | 62 | return PNR_OK; |
pasky | 0:9858347c382d | 63 | } |
pasky | 0:9858347c382d | 64 | |
pasky | 0:9858347c382d | 65 | void |
pasky | 0:9858347c382d | 66 | PubNubHTTP::sendstr(const char *s) |
pasky | 0:9858347c382d | 67 | { |
pasky | 0:9858347c382d | 68 | send_all((char *) s, strlen(s)); |
pasky | 0:9858347c382d | 69 | } |
pasky | 0:9858347c382d | 70 | |
pasky | 0:9858347c382d | 71 | void |
pasky | 0:9858347c382d | 72 | PubNubHTTP::sendstr_urlenc(const char *s) |
pasky | 0:9858347c382d | 73 | { |
pasky | 0:9858347c382d | 74 | while (s[0]) { |
pasky | 0:9858347c382d | 75 | /* RFC 3986 Unreserved characters plus few |
pasky | 0:9858347c382d | 76 | * safe reserved ones. */ |
pasky | 0:9858347c382d | 77 | size_t okspan = strspn(s, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~" ",=:;@[]"); |
pasky | 0:9858347c382d | 78 | if (okspan > 0) { |
pasky | 0:9858347c382d | 79 | send_all((char *) s, okspan); |
pasky | 0:9858347c382d | 80 | s += okspan; |
pasky | 0:9858347c382d | 81 | } |
pasky | 0:9858347c382d | 82 | if (s[0]) { |
pasky | 0:9858347c382d | 83 | /* %-encode a non-ok character. */ |
pasky | 0:9858347c382d | 84 | char enc[3] = {'%'}; |
pasky | 0:9858347c382d | 85 | enc[1] = "0123456789ABCDEF"[s[0] / 16]; |
pasky | 0:9858347c382d | 86 | enc[2] = "0123456789ABCDEF"[s[0] % 16]; |
pasky | 0:9858347c382d | 87 | send_all((char *) enc, 3); |
pasky | 0:9858347c382d | 88 | s++; |
pasky | 0:9858347c382d | 89 | } |
pasky | 0:9858347c382d | 90 | } |
pasky | 0:9858347c382d | 91 | } |
pasky | 0:9858347c382d | 92 | |
pasky | 0:9858347c382d | 93 | PubNubRes |
pasky | 0:9858347c382d | 94 | PubNubHTTP::http_request_bh(const char *host, char **reply, int *replylen) |
pasky | 0:9858347c382d | 95 | { |
pasky | 0:9858347c382d | 96 | m_replybuf = NULL; |
pasky | 0:9858347c382d | 97 | m_replylen = 0; |
pasky | 0:9858347c382d | 98 | |
pasky | 0:9858347c382d | 99 | PubNubRes res; |
pasky | 0:9858347c382d | 100 | res = http_send_headers(host); |
pasky | 0:9858347c382d | 101 | if (res != PNR_OK) { |
pasky | 0:9858347c382d | 102 | close(); |
pasky | 0:9858347c382d | 103 | return res; |
pasky | 0:9858347c382d | 104 | } |
pasky | 0:9858347c382d | 105 | |
pasky | 0:9858347c382d | 106 | res = http_recv_headers(); |
pasky | 0:9858347c382d | 107 | if (res != PNR_OK) { |
pasky | 0:9858347c382d | 108 | if (m_replybuf) free(m_replybuf); |
pasky | 0:9858347c382d | 109 | close(); |
pasky | 0:9858347c382d | 110 | return res; |
pasky | 0:9858347c382d | 111 | } |
pasky | 0:9858347c382d | 112 | |
pasky | 0:9858347c382d | 113 | res = http_recv_content(); |
pasky | 0:9858347c382d | 114 | if (res != PNR_OK) { |
pasky | 0:9858347c382d | 115 | if (m_replybuf) free(m_replybuf); |
pasky | 0:9858347c382d | 116 | close(); |
pasky | 0:9858347c382d | 117 | return res; |
pasky | 0:9858347c382d | 118 | } |
pasky | 0:9858347c382d | 119 | |
pasky | 0:9858347c382d | 120 | close(); |
pasky | 0:9858347c382d | 121 | *reply = (char *) realloc(m_replybuf, m_replylen); |
pasky | 0:9858347c382d | 122 | *replylen = m_replylen; |
pasky | 0:9858347c382d | 123 | return PNR_OK; |
pasky | 0:9858347c382d | 124 | } |
pasky | 0:9858347c382d | 125 | |
pasky | 0:9858347c382d | 126 | PubNubRes |
pasky | 0:9858347c382d | 127 | PubNubHTTP::http_send_headers(const char *host) |
pasky | 0:9858347c382d | 128 | { |
pasky | 0:9858347c382d | 129 | /* Finish the first line of the request. */ |
pasky | 0:9858347c382d | 130 | sendstr(" HTTP/1.1\r\n"); |
pasky | 0:9858347c382d | 131 | /* Finish HTTP request. */ |
pasky | 0:9858347c382d | 132 | sendstr("Host: "); |
pasky | 0:9858347c382d | 133 | sendstr(host); |
pasky | 0:9858347c382d | 134 | sendstr("\r\nUser-Agent: PubNub-mbed/0.1\r\nConnection: close\r\n\r\n"); |
pasky | 0:9858347c382d | 135 | if (!is_connected()) |
pasky | 0:9858347c382d | 136 | return PNR_IO_ERROR; |
pasky | 0:9858347c382d | 137 | return PNR_OK; |
pasky | 0:9858347c382d | 138 | } |
pasky | 0:9858347c382d | 139 | |
pasky | 0:9858347c382d | 140 | PubNubRes |
pasky | 0:9858347c382d | 141 | PubNubHTTP::http_recv_headers() |
pasky | 0:9858347c382d | 142 | { |
pasky | 0:9858347c382d | 143 | /* Now, read HTTP reply. */ |
pasky | 0:9858347c382d | 144 | m_replybuf = (char *) malloc(PUBNUB_REPLY_MAXLEN+1); |
pasky | 0:9858347c382d | 145 | |
pasky | 0:9858347c382d | 146 | /* First, receive headers. */ |
pasky | 0:9858347c382d | 147 | /* XXX: For now, we assume complete headers will fit in m_replybuf. */ |
pasky | 0:9858347c382d | 148 | m_replylen = receive_all(m_replybuf, PUBNUB_REPLY_MAXLEN); |
pasky | 0:9858347c382d | 149 | if (m_replylen < 0) |
pasky | 0:9858347c382d | 150 | return PNR_IO_ERROR; |
pasky | 0:9858347c382d | 151 | m_replybuf[m_replylen] = 0; |
pasky | 0:9858347c382d | 152 | |
pasky | 0:9858347c382d | 153 | char *bufptr = m_replybuf; |
pasky | 0:9858347c382d | 154 | |
pasky | 0:9858347c382d | 155 | /* Parse the first line. */ |
pasky | 0:9858347c382d | 156 | if (strncmp(bufptr, "HTTP/1.", 7) || !bufptr[7] || !bufptr[8]) |
pasky | 0:9858347c382d | 157 | return PNR_IO_ERROR; |
pasky | 0:9858347c382d | 158 | int http_code = atoi(bufptr+9); |
pasky | 0:9858347c382d | 159 | if (http_code / 100 != 2) |
pasky | 0:9858347c382d | 160 | return PNR_HTTP_ERROR; |
pasky | 0:9858347c382d | 161 | bufptr = strstr(bufptr, "\r\n"); |
pasky | 0:9858347c382d | 162 | if (!bufptr) |
pasky | 0:9858347c382d | 163 | return PNR_IO_ERROR; |
pasky | 0:9858347c382d | 164 | bufptr += 2; |
pasky | 0:9858347c382d | 165 | |
pasky | 0:9858347c382d | 166 | /* Parse the rest of the lines. */ |
pasky | 0:9858347c382d | 167 | m_content_length = 0; |
pasky | 0:9858347c382d | 168 | bool is_chunked = false; |
pasky | 0:9858347c382d | 169 | char *newline; |
pasky | 0:9858347c382d | 170 | for (;; bufptr = newline+2) { |
pasky | 0:9858347c382d | 171 | newline = strstr(bufptr, "\r\n"); |
pasky | 0:9858347c382d | 172 | if (!newline) |
pasky | 0:9858347c382d | 173 | return PNR_IO_ERROR; |
pasky | 0:9858347c382d | 174 | *newline = 0; |
pasky | 0:9858347c382d | 175 | |
pasky | 0:9858347c382d | 176 | char h_chunked[] = "Transfer-Encoding: chunked"; |
pasky | 0:9858347c382d | 177 | char h_length[] = "Content-Length: "; |
pasky | 0:9858347c382d | 178 | if (*bufptr == 0) { |
pasky | 0:9858347c382d | 179 | /* Empty line. End of headers. */ |
pasky | 0:9858347c382d | 180 | bufptr += 2; |
pasky | 0:9858347c382d | 181 | break; |
pasky | 0:9858347c382d | 182 | } else if (!strncmp(bufptr, h_chunked, sizeof(h_chunked)-1)) { |
pasky | 0:9858347c382d | 183 | /* Transfer-Encoding: chunked */ |
pasky | 0:9858347c382d | 184 | is_chunked = true; |
pasky | 0:9858347c382d | 185 | } else if (!strncmp(bufptr, h_length, sizeof(h_length)-1)) { |
pasky | 0:9858347c382d | 186 | /* Content-Length: ... */ |
pasky | 0:9858347c382d | 187 | m_content_length = atoi(bufptr + sizeof(h_length)-1); |
pasky | 0:9858347c382d | 188 | } |
pasky | 0:9858347c382d | 189 | } |
pasky | 0:9858347c382d | 190 | |
pasky | 0:9858347c382d | 191 | /* Possibly process a chunk header. */ |
pasky | 0:9858347c382d | 192 | if (is_chunked) { |
pasky | 0:9858347c382d | 193 | m_content_length = atoi(bufptr); |
pasky | 0:9858347c382d | 194 | bufptr = strstr(bufptr, "\r\n"); |
pasky | 0:9858347c382d | 195 | if (!bufptr) |
pasky | 0:9858347c382d | 196 | return PNR_IO_ERROR; |
pasky | 0:9858347c382d | 197 | bufptr += 2; |
pasky | 0:9858347c382d | 198 | } |
pasky | 0:9858347c382d | 199 | |
pasky | 0:9858347c382d | 200 | /* Consolidate the buffer. */ |
pasky | 0:9858347c382d | 201 | m_replylen -= bufptr - m_replybuf; |
pasky | 0:9858347c382d | 202 | memmove(m_replybuf, bufptr, m_replylen); |
pasky | 0:9858347c382d | 203 | |
pasky | 0:9858347c382d | 204 | return PNR_OK; |
pasky | 0:9858347c382d | 205 | } |
pasky | 0:9858347c382d | 206 | |
pasky | 0:9858347c382d | 207 | PubNubRes |
pasky | 0:9858347c382d | 208 | PubNubHTTP::http_recv_content() |
pasky | 0:9858347c382d | 209 | { |
pasky | 0:9858347c382d | 210 | /* Now, we are ready to process data! */ |
pasky | 0:9858347c382d | 211 | |
pasky | 0:9858347c382d | 212 | if (m_content_length > PUBNUB_REPLY_MAXLEN) { |
pasky | 0:9858347c382d | 213 | /* XXX: Actually, too much data, sorry. */ |
pasky | 0:9858347c382d | 214 | DBG printf("too much data\r\n"); |
pasky | 0:9858347c382d | 215 | return PNR_FORMAT_ERROR; |
pasky | 0:9858347c382d | 216 | } |
pasky | 0:9858347c382d | 217 | |
pasky | 0:9858347c382d | 218 | if (m_replylen < m_content_length) { |
pasky | 0:9858347c382d | 219 | /* More data coming. */ |
pasky | 0:9858347c382d | 220 | int recv2len = receive_all(m_replybuf + m_replylen, PUBNUB_REPLY_MAXLEN - m_replylen); |
pasky | 0:9858347c382d | 221 | if (recv2len < 0) |
pasky | 0:9858347c382d | 222 | return PNR_IO_ERROR; |
pasky | 0:9858347c382d | 223 | m_replylen += recv2len; |
pasky | 0:9858347c382d | 224 | } |
pasky | 0:9858347c382d | 225 | |
pasky | 0:9858347c382d | 226 | if (m_replylen < m_content_length) |
pasky | 0:9858347c382d | 227 | return PNR_IO_ERROR; /* Incomplete data. */ |
pasky | 0:9858347c382d | 228 | return PNR_OK; |
pasky | 0:9858347c382d | 229 | } |
pasky | 0:9858347c382d | 230 | |
pasky | 0:9858347c382d | 231 | |
pasky | 0:9858347c382d | 232 | /** Some utility routines. */ |
pasky | 0:9858347c382d | 233 | |
pasky | 0:9858347c382d | 234 | /* Split a JSON array (with arbitrary contents) to multiple NUL-terminated |
pasky | 0:9858347c382d | 235 | * C strings. */ |
pasky | 0:9858347c382d | 236 | static PubNubRes |
pasky | 0:9858347c382d | 237 | split_array(char *buf, int len) |
pasky | 0:9858347c382d | 238 | { |
pasky | 0:9858347c382d | 239 | bool escaped = false, in_string = false; |
pasky | 0:9858347c382d | 240 | int bracket_level = 0; |
pasky | 0:9858347c382d | 241 | for (int i = 0; i < len; i++) { |
pasky | 0:9858347c382d | 242 | if (escaped) { |
pasky | 0:9858347c382d | 243 | escaped = false; |
pasky | 0:9858347c382d | 244 | } else if (in_string) { |
pasky | 0:9858347c382d | 245 | switch (buf[i]) { |
pasky | 0:9858347c382d | 246 | case '\\': escaped = true; break; |
pasky | 0:9858347c382d | 247 | case '"': in_string = false; break; |
pasky | 0:9858347c382d | 248 | default: break; |
pasky | 0:9858347c382d | 249 | } |
pasky | 0:9858347c382d | 250 | } else { |
pasky | 0:9858347c382d | 251 | switch (buf[i]) { |
pasky | 0:9858347c382d | 252 | case '"': in_string = true; break; |
pasky | 0:9858347c382d | 253 | case '[': case '{': bracket_level++; break; |
pasky | 0:9858347c382d | 254 | case ']': case '}': bracket_level--; break; |
pasky | 0:9858347c382d | 255 | /* if at root, split! */ |
pasky | 0:9858347c382d | 256 | case ',': if (bracket_level == 0) buf[i] = 0; break; |
pasky | 0:9858347c382d | 257 | default: break; |
pasky | 0:9858347c382d | 258 | } |
pasky | 0:9858347c382d | 259 | } |
pasky | 0:9858347c382d | 260 | } |
pasky | 0:9858347c382d | 261 | DBG printf("parse %d %d %d\r\n", escaped, in_string, bracket_level); |
pasky | 0:9858347c382d | 262 | if (escaped || in_string || bracket_level > 0) |
pasky | 0:9858347c382d | 263 | return PNR_FORMAT_ERROR; |
pasky | 0:9858347c382d | 264 | return PNR_OK; |
pasky | 0:9858347c382d | 265 | } |
pasky | 0:9858347c382d | 266 | |
pasky | 0:9858347c382d | 267 | |
pasky | 0:9858347c382d | 268 | /** PubNub API. */ |
pasky | 0:9858347c382d | 269 | |
pasky | 0:9858347c382d | 270 | PubNub::PubNub(const char *publish_key_, const char *subscribe_key_, const char *origin_) : |
pasky | 0:9858347c382d | 271 | m_publish_key(publish_key_), m_subscribe_key(subscribe_key_), m_origin(origin_), |
pasky | 0:9858347c382d | 272 | m_replybuf(NULL), m_replylen(0) |
pasky | 0:9858347c382d | 273 | { |
pasky | 0:9858347c382d | 274 | strcpy(m_timetoken, "0"); |
pasky | 0:9858347c382d | 275 | } |
pasky | 0:9858347c382d | 276 | |
pasky | 0:9858347c382d | 277 | PubNub::~PubNub() |
pasky | 0:9858347c382d | 278 | { |
pasky | 0:9858347c382d | 279 | if (m_replybuf) |
pasky | 0:9858347c382d | 280 | free(m_replybuf); |
pasky | 0:9858347c382d | 281 | } |
pasky | 0:9858347c382d | 282 | |
pasky | 0:9858347c382d | 283 | const char * |
pasky | 0:9858347c382d | 284 | PubNub::origin_hostname() |
pasky | 0:9858347c382d | 285 | { |
pasky | 0:9858347c382d | 286 | /* TODO: More generic URL handling */ |
pasky | 0:9858347c382d | 287 | return m_origin + strlen("http://"); |
pasky | 0:9858347c382d | 288 | } |
pasky | 0:9858347c382d | 289 | |
pasky | 0:9858347c382d | 290 | |
pasky | 0:9858347c382d | 291 | PubNubRes |
pasky | 0:9858347c382d | 292 | PubNub::publish(const char *channel, const char *message, char **reply) |
pasky | 0:9858347c382d | 293 | { |
pasky | 0:9858347c382d | 294 | PubNubHTTP http; |
pasky | 0:9858347c382d | 295 | PubNubRes res = http.http_connect(origin_hostname()); |
pasky | 0:9858347c382d | 296 | if (res != PNR_OK) |
pasky | 0:9858347c382d | 297 | return res; |
pasky | 0:9858347c382d | 298 | |
pasky | 0:9858347c382d | 299 | http.sendstr("GET /publish/"); |
pasky | 0:9858347c382d | 300 | http.sendstr(m_publish_key); |
pasky | 0:9858347c382d | 301 | http.sendstr("/"); |
pasky | 0:9858347c382d | 302 | http.sendstr(m_subscribe_key); |
pasky | 0:9858347c382d | 303 | http.sendstr("/0/"); |
pasky | 0:9858347c382d | 304 | http.sendstr(channel); |
pasky | 0:9858347c382d | 305 | http.sendstr("/0/"); |
pasky | 0:9858347c382d | 306 | http.sendstr_urlenc(message); |
pasky | 0:9858347c382d | 307 | |
pasky | 0:9858347c382d | 308 | char *locreply; |
pasky | 0:9858347c382d | 309 | if (!reply) |
pasky | 0:9858347c382d | 310 | reply = &locreply; |
pasky | 0:9858347c382d | 311 | int replylen; |
pasky | 0:9858347c382d | 312 | |
pasky | 0:9858347c382d | 313 | res = http.http_request_bh(origin_hostname(), reply, &replylen); |
pasky | 0:9858347c382d | 314 | if (res != PNR_OK) |
pasky | 0:9858347c382d | 315 | return res; |
pasky | 0:9858347c382d | 316 | bool success = (*reply)[1] == '1' && (*reply)[2] == ','; |
pasky | 0:9858347c382d | 317 | if (reply == &locreply) |
pasky | 0:9858347c382d | 318 | free(locreply); |
pasky | 0:9858347c382d | 319 | return success ? PNR_OK : PNR_PUBNUB_ERROR; |
pasky | 0:9858347c382d | 320 | } |
pasky | 0:9858347c382d | 321 | |
pasky | 0:9858347c382d | 322 | |
pasky | 0:9858347c382d | 323 | PubNubRes |
pasky | 0:9858347c382d | 324 | PubNub::subscribe(const char *channel, char **reply) |
pasky | 0:9858347c382d | 325 | { |
pasky | 0:9858347c382d | 326 | if (m_replybuf) { |
pasky | 0:9858347c382d | 327 | int prevlen = strlen(m_replybuf); |
pasky | 0:9858347c382d | 328 | //DBG printf("reply (%s) %d > %d: %d\r\n", m_replybuf, prevlen, m_replylen); |
pasky | 0:9858347c382d | 329 | if (prevlen < m_replylen) { |
pasky | 0:9858347c382d | 330 | /* Next message from stash-away buffer. */ |
pasky | 0:9858347c382d | 331 | /* XXX: We can be either memory-frugal or CPU-frugal |
pasky | 0:9858347c382d | 332 | * here. We choose to be memory-frugal by copying |
pasky | 0:9858347c382d | 333 | * over messages many times, but we may want to make |
pasky | 0:9858347c382d | 334 | * this configurable. */ |
pasky | 0:9858347c382d | 335 | m_replylen -= prevlen + 1; |
pasky | 0:9858347c382d | 336 | memmove(m_replybuf, m_replybuf + prevlen + 1, m_replylen); |
pasky | 0:9858347c382d | 337 | m_replybuf = (char *) realloc(m_replybuf, m_replylen); |
pasky | 0:9858347c382d | 338 | *reply = m_replybuf; |
pasky | 0:9858347c382d | 339 | return PNR_OK; |
pasky | 0:9858347c382d | 340 | |
pasky | 0:9858347c382d | 341 | } else { |
pasky | 0:9858347c382d | 342 | /* That's all. free() and fetch new messages. */ |
pasky | 0:9858347c382d | 343 | free(m_replybuf); |
pasky | 0:9858347c382d | 344 | m_replybuf = NULL; |
pasky | 0:9858347c382d | 345 | m_replylen = 0; |
pasky | 0:9858347c382d | 346 | } |
pasky | 0:9858347c382d | 347 | } |
pasky | 0:9858347c382d | 348 | |
pasky | 0:9858347c382d | 349 | PubNubHTTP http; |
pasky | 0:9858347c382d | 350 | PubNubRes res; |
pasky | 0:9858347c382d | 351 | res = http.http_connect(origin_hostname()); |
pasky | 0:9858347c382d | 352 | if (res != PNR_OK) |
pasky | 0:9858347c382d | 353 | return res; |
pasky | 0:9858347c382d | 354 | |
pasky | 0:9858347c382d | 355 | http.sendstr("GET /subscribe/"); |
pasky | 0:9858347c382d | 356 | http.sendstr(m_subscribe_key); |
pasky | 0:9858347c382d | 357 | http.sendstr("/"); |
pasky | 0:9858347c382d | 358 | http.sendstr(channel); |
pasky | 0:9858347c382d | 359 | http.sendstr("/0/"); |
pasky | 0:9858347c382d | 360 | http.sendstr(m_timetoken); |
pasky | 0:9858347c382d | 361 | |
pasky | 0:9858347c382d | 362 | char *replybuf = NULL; |
pasky | 0:9858347c382d | 363 | int replylen; |
pasky | 0:9858347c382d | 364 | res = http.http_request_bh(origin_hostname(), &replybuf, &replylen); |
pasky | 0:9858347c382d | 365 | if (res != PNR_OK) |
pasky | 0:9858347c382d | 366 | goto error; |
pasky | 0:9858347c382d | 367 | |
pasky | 0:9858347c382d | 368 | /* Process the reply, sets timetoken and m_replybuf, m_replylen. */ |
pasky | 0:9858347c382d | 369 | res = subscribe_processjson(replybuf, replylen); |
pasky | 0:9858347c382d | 370 | if (res != PNR_OK) |
pasky | 0:9858347c382d | 371 | goto error; |
pasky | 0:9858347c382d | 372 | replybuf = NULL; // freed by processjosn |
pasky | 0:9858347c382d | 373 | |
pasky | 0:9858347c382d | 374 | /* Split JSON array to messages. */ |
pasky | 0:9858347c382d | 375 | res = split_array(m_replybuf, m_replylen); |
pasky | 0:9858347c382d | 376 | if (res != PNR_OK) { |
pasky | 0:9858347c382d | 377 | free(m_replybuf); |
pasky | 0:9858347c382d | 378 | goto error; |
pasky | 0:9858347c382d | 379 | } |
pasky | 0:9858347c382d | 380 | |
pasky | 0:9858347c382d | 381 | *reply = m_replybuf; |
pasky | 0:9858347c382d | 382 | return res; |
pasky | 0:9858347c382d | 383 | |
pasky | 0:9858347c382d | 384 | error: |
pasky | 0:9858347c382d | 385 | if (res == PNR_FORMAT_ERROR) { |
pasky | 0:9858347c382d | 386 | /* In case of PubNub protocol error, abort an ongoing |
pasky | 0:9858347c382d | 387 | * subscribe and start over. This means some messages |
pasky | 0:9858347c382d | 388 | * were lost, but allows us to recover from bad |
pasky | 0:9858347c382d | 389 | * situations, e.g. too many messages queued or |
pasky | 0:9858347c382d | 390 | * unexpected problem caused by a particular message. */ |
pasky | 0:9858347c382d | 391 | strcpy(m_timetoken, "0"); |
pasky | 0:9858347c382d | 392 | } |
pasky | 0:9858347c382d | 393 | if (reply) |
pasky | 0:9858347c382d | 394 | free(reply); |
pasky | 0:9858347c382d | 395 | m_replybuf = NULL; |
pasky | 0:9858347c382d | 396 | m_replylen = 0; |
pasky | 0:9858347c382d | 397 | return res; |
pasky | 0:9858347c382d | 398 | } |
pasky | 0:9858347c382d | 399 | |
pasky | 0:9858347c382d | 400 | PubNubRes |
pasky | 0:9858347c382d | 401 | PubNub::subscribe_processjson(char *reply, int replylen) |
pasky | 0:9858347c382d | 402 | { |
pasky | 0:9858347c382d | 403 | if (reply[0] != '[' || reply[replylen-1] != ']' |
pasky | 0:9858347c382d | 404 | || reply[replylen-2] != '"') { |
pasky | 0:9858347c382d | 405 | DBG printf("bad reply '%s'\r\n", reply); |
pasky | 0:9858347c382d | 406 | return PNR_FORMAT_ERROR; |
pasky | 0:9858347c382d | 407 | } |
pasky | 0:9858347c382d | 408 | |
pasky | 0:9858347c382d | 409 | /* Extract timetoken. */ |
pasky | 0:9858347c382d | 410 | reply[replylen-2] = 0; |
pasky | 0:9858347c382d | 411 | int i; |
pasky | 0:9858347c382d | 412 | for (i = replylen-3; i > 0 && i > int(replylen-3 - (sizeof(m_timetoken)-1)); i--) |
pasky | 0:9858347c382d | 413 | if (reply[i] == '"') |
pasky | 0:9858347c382d | 414 | break; |
pasky | 0:9858347c382d | 415 | if (!i || reply[i-1] != ',' || replylen-2 - (i+1) >= 64) { |
pasky | 0:9858347c382d | 416 | DBG printf("bad reply '%s'\r\n", reply); |
pasky | 0:9858347c382d | 417 | return PNR_FORMAT_ERROR; |
pasky | 0:9858347c382d | 418 | } |
pasky | 0:9858347c382d | 419 | strcpy(m_timetoken, &reply[i+1]); |
pasky | 0:9858347c382d | 420 | reply[i-1] = 0; // terminate the [] message array |
pasky | 0:9858347c382d | 421 | |
pasky | 0:9858347c382d | 422 | /* Empty reply? */ |
pasky | 0:9858347c382d | 423 | if (i == 4) { /* "[[]" */ |
pasky | 0:9858347c382d | 424 | free(reply); |
pasky | 0:9858347c382d | 425 | m_replybuf = NULL; |
pasky | 0:9858347c382d | 426 | m_replylen = 0; |
pasky | 0:9858347c382d | 427 | return PNR_OK; |
pasky | 0:9858347c382d | 428 | } |
pasky | 0:9858347c382d | 429 | |
pasky | 0:9858347c382d | 430 | /* Extract the messages array. */ |
pasky | 0:9858347c382d | 431 | if (reply[1] != '[' || reply[i-2] != ']') { |
pasky | 0:9858347c382d | 432 | DBG printf("bad reply end '%s'\r\n", reply); |
pasky | 0:9858347c382d | 433 | return PNR_FORMAT_ERROR; |
pasky | 0:9858347c382d | 434 | } |
pasky | 0:9858347c382d | 435 | reply[i-2] = 0; |
pasky | 0:9858347c382d | 436 | |
pasky | 0:9858347c382d | 437 | /* Shrink memory buffer to bare minimum. */ |
pasky | 0:9858347c382d | 438 | memmove(reply, reply + 2, i-2-2); |
pasky | 0:9858347c382d | 439 | m_replylen = i-2-2; |
pasky | 0:9858347c382d | 440 | m_replybuf = (char *) realloc(reply, m_replylen); |
pasky | 0:9858347c382d | 441 | |
pasky | 0:9858347c382d | 442 | return PNR_OK; |
pasky | 0:9858347c382d | 443 | } |
pasky | 0:9858347c382d | 444 | |
pasky | 0:9858347c382d | 445 | |
pasky | 0:9858347c382d | 446 | PubNubRes |
pasky | 0:9858347c382d | 447 | PubNub::history(const char *channel, char **reply, int *replysize, int limit) |
pasky | 0:9858347c382d | 448 | { |
pasky | 0:9858347c382d | 449 | PubNubHTTP http; |
pasky | 0:9858347c382d | 450 | PubNubRes res; |
pasky | 0:9858347c382d | 451 | res = http.http_connect(origin_hostname()); |
pasky | 0:9858347c382d | 452 | if (res != PNR_OK) |
pasky | 0:9858347c382d | 453 | return res; |
pasky | 0:9858347c382d | 454 | |
pasky | 0:9858347c382d | 455 | http.sendstr("GET /history/"); |
pasky | 0:9858347c382d | 456 | http.sendstr(m_subscribe_key); |
pasky | 0:9858347c382d | 457 | http.sendstr("/"); |
pasky | 0:9858347c382d | 458 | http.sendstr(channel); |
pasky | 0:9858347c382d | 459 | http.sendstr("/0/"); |
pasky | 0:9858347c382d | 460 | char limitbuf[8]; snprintf(limitbuf, sizeof(limitbuf), "%d", limit); |
pasky | 0:9858347c382d | 461 | http.sendstr(limitbuf); |
pasky | 0:9858347c382d | 462 | |
pasky | 0:9858347c382d | 463 | char *replybuf = NULL; |
pasky | 0:9858347c382d | 464 | int replylen; |
pasky | 0:9858347c382d | 465 | res = http.http_request_bh(origin_hostname(), &replybuf, &replylen); |
pasky | 0:9858347c382d | 466 | if (res != PNR_OK) |
pasky | 0:9858347c382d | 467 | goto error; |
pasky | 0:9858347c382d | 468 | |
pasky | 0:9858347c382d | 469 | /* Extract from the array and split it. */ |
pasky | 0:9858347c382d | 470 | |
pasky | 0:9858347c382d | 471 | if (replybuf[0] != '[' || replybuf[replylen-1] != ']') { |
pasky | 0:9858347c382d | 472 | res = PNR_FORMAT_ERROR; |
pasky | 0:9858347c382d | 473 | goto error; |
pasky | 0:9858347c382d | 474 | } |
pasky | 0:9858347c382d | 475 | |
pasky | 0:9858347c382d | 476 | replylen -= 2; |
pasky | 0:9858347c382d | 477 | if (replylen == 0) { /* The reply was [] */ |
pasky | 0:9858347c382d | 478 | free(replybuf); |
pasky | 0:9858347c382d | 479 | *reply = NULL; |
pasky | 0:9858347c382d | 480 | *replysize = 0; |
pasky | 0:9858347c382d | 481 | return PNR_OK; |
pasky | 0:9858347c382d | 482 | } |
pasky | 0:9858347c382d | 483 | |
pasky | 0:9858347c382d | 484 | memmove(replybuf, replybuf + 1, replylen); |
pasky | 0:9858347c382d | 485 | replybuf[replylen] = 0; |
pasky | 0:9858347c382d | 486 | |
pasky | 0:9858347c382d | 487 | res = split_array(replybuf, replylen); |
pasky | 0:9858347c382d | 488 | if (res != PNR_OK) |
pasky | 0:9858347c382d | 489 | goto error; |
pasky | 0:9858347c382d | 490 | |
pasky | 0:9858347c382d | 491 | *reply = replybuf; |
pasky | 0:9858347c382d | 492 | *replysize = replylen; |
pasky | 0:9858347c382d | 493 | return PNR_OK; |
pasky | 0:9858347c382d | 494 | |
pasky | 0:9858347c382d | 495 | error: |
pasky | 0:9858347c382d | 496 | free(replybuf); |
pasky | 0:9858347c382d | 497 | return res; |
pasky | 0:9858347c382d | 498 | } |
pasky | 0:9858347c382d | 499 | |
pasky | 0:9858347c382d | 500 | |
pasky | 0:9858347c382d | 501 | PubNubRes |
pasky | 0:9858347c382d | 502 | PubNub::time(char *ts) |
pasky | 0:9858347c382d | 503 | { |
pasky | 0:9858347c382d | 504 | PubNubHTTP http; |
pasky | 0:9858347c382d | 505 | PubNubRes res = http.http_connect(origin_hostname()); |
pasky | 0:9858347c382d | 506 | if (res != PNR_OK) |
pasky | 0:9858347c382d | 507 | return res; |
pasky | 0:9858347c382d | 508 | |
pasky | 0:9858347c382d | 509 | http.sendstr("GET /time/0"); |
pasky | 0:9858347c382d | 510 | |
pasky | 0:9858347c382d | 511 | char *reply; |
pasky | 0:9858347c382d | 512 | int replylen; |
pasky | 0:9858347c382d | 513 | res = http.http_request_bh(origin_hostname(), &reply, &replylen); |
pasky | 0:9858347c382d | 514 | if (res != PNR_OK) |
pasky | 0:9858347c382d | 515 | return res; |
pasky | 0:9858347c382d | 516 | |
pasky | 0:9858347c382d | 517 | if (replylen < 3 || replylen > 32 || reply[0] != '[' || reply[replylen-1] != ']') { |
pasky | 0:9858347c382d | 518 | free(reply); |
pasky | 0:9858347c382d | 519 | return PNR_FORMAT_ERROR; |
pasky | 0:9858347c382d | 520 | } |
pasky | 0:9858347c382d | 521 | |
pasky | 0:9858347c382d | 522 | replylen -= 2; |
pasky | 0:9858347c382d | 523 | memcpy(ts, reply + 1, replylen); |
pasky | 0:9858347c382d | 524 | ts[replylen] = 0; |
pasky | 0:9858347c382d | 525 | |
pasky | 0:9858347c382d | 526 | free(reply); |
pasky | 0:9858347c382d | 527 | return PNR_OK; |
pasky | 0:9858347c382d | 528 | } |