Modified PubNub library to better utilize low bandwidth connections like a GSM modem. Instead of 10 socket connections sending 3 bytes only one is made and it sends all 30 bytes at the same time.
Fork of PubNub by
Revision 7:55eb53c78b47, committed 2014-10-01
- Comitter:
- embeddedartists
- Date:
- Wed Oct 01 11:36:11 2014 +0000
- Parent:
- 6:09d0a383fdde
- Commit message:
- Modified PubNub library to better utilize low bandwidth connections like a GSM modem. Instead of 10 sockets connections sending 3 bytes only one is made and it sends all 30 bytes at the same time.
Changed in this revision
PubNub.cpp | Show annotated file Show diff for this revision Revisions of this file |
PubNub.h | Show annotated file Show diff for this revision Revisions of this file |
--- a/PubNub.cpp Tue May 20 11:43:18 2014 +0000 +++ b/PubNub.cpp Wed Oct 01 11:36:11 2014 +0000 @@ -8,6 +8,7 @@ #define DBG if (0) // change to if (1) to enable debug prints on USB serial +//#define USE_ORIGINAL_CODE /* We roll our own HTTP communication stack as donatien's HTTPClient has * some hard limits on URL length and this will allow a more frugal RAM @@ -21,6 +22,24 @@ * we still show picojson usage for end-user parsing of complex incoming * messages. */ +#include "rtos.h" + +void pnub_err_handler(PubNubRes code, int line); +extern Serial pc; +extern Mutex stdio_mutex; +#define safe_printf(...) do { \ + stdio_mutex.lock(); \ + pc.printf(__VA_ARGS__); \ + stdio_mutex.unlock(); \ + } while(0) + +#define PNUB_RETURN_STAT(__stat) do { \ + if ((__stat) != PNR_OK) { \ + pnub_err_handler((__stat), __LINE__); \ + } \ + return (__stat); \ + } while(0) + /** Custom no-frills HTTP stream. */ @@ -34,6 +53,8 @@ void sendstr(const char *string); /* Send a given NUL-terminated string over the http socket, URL encoded. */ void sendstr_urlenc(const char *string); + /* URL encode a given NUL-terminated string into the given buffer. */ + void prepare_urlenc(const char *string, char* buff); /* A common HTTP request "bottom half" - send the rest of headers * and wait for reply + receive. */ @@ -57,7 +78,9 @@ int ret = connect(hostname, 80); if (ret < 0) { close(); - return PNR_IO_ERROR; + //return PNR_IO_ERROR; + safe_printf("http_connect(%s): ret = %d\n", hostname, ret); + PNUB_RETURN_STAT(PNR_IO_ERROR); } return PNR_OK; } @@ -90,6 +113,32 @@ } } +void +PubNubHTTP::prepare_urlenc(const char *s, char* buff) +{ + char* b = buff; + while (s[0]) { + /* RFC 3986 Unreserved characters plus few + * safe reserved ones. */ + size_t okspan = strspn(s, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~" ",=:;@[]"); + if (okspan > 0) { + strncpy(b, s, okspan); + b += okspan; + //send_all((char *) s, okspan); + s += okspan; + } + if (s[0]) { + /* %-encode a non-ok character. */ + *b++ = '%'; + *b++ = "0123456789ABCDEF"[s[0] / 16]; + *b++ = "0123456789ABCDEF"[s[0] % 16]; + //send_all((char *) enc, 3); + s++; + } + } + *b = '\0'; +} + PubNubRes PubNubHTTP::http_request_bh(const char *host, char **reply, int *replylen) { @@ -100,21 +149,24 @@ res = http_send_headers(host); if (res != PNR_OK) { close(); - return res; + //return res; + PNUB_RETURN_STAT(res); } res = http_recv_headers(); if (res != PNR_OK) { if (m_replybuf) free(m_replybuf); close(); - return res; + //return res; + PNUB_RETURN_STAT(res); } res = http_recv_content(); if (res != PNR_OK) { if (m_replybuf) free(m_replybuf); close(); - return res; + //return res; + PNUB_RETURN_STAT(res); } close(); @@ -126,6 +178,7 @@ PubNubRes PubNubHTTP::http_send_headers(const char *host) { +#ifdef USE_ORIGINAL_CODE /* Finish the first line of the request. */ sendstr(" HTTP/1.1\r\n"); /* Finish HTTP request. */ @@ -133,8 +186,18 @@ sendstr(host); sendstr("\r\nUser-Agent: PubNub-mbed/0.1\r\nConnection: close\r\n\r\n"); if (!is_connected()) - return PNR_IO_ERROR; + //return PNR_IO_ERROR; + PNUB_RETURN_STAT(PNR_IO_ERROR); return PNR_OK; +#else + char buff[200]; + snprintf(buff, 200, " HTTP/1.1\r\nHost: %s\r\nUser-Agent: PubNub-mbed/0.1\r\nConnection: close\r\n\r\n", host); + sendstr(buff); + if (!is_connected()) + //return PNR_IO_ERROR; + PNUB_RETURN_STAT(PNR_IO_ERROR); + return PNR_OK; +#endif } PubNubRes @@ -147,20 +210,24 @@ /* XXX: For now, we assume complete headers will fit in m_replybuf. */ m_replylen = receive_all(m_replybuf, PUBNUB_REPLY_MAXLEN); if (m_replylen < 0) - return PNR_IO_ERROR; + //return PNR_IO_ERROR; + PNUB_RETURN_STAT(PNR_IO_ERROR); m_replybuf[m_replylen] = 0; char *bufptr = m_replybuf; /* Parse the first line. */ if (strncmp(bufptr, "HTTP/1.", 7) || !bufptr[7] || !bufptr[8]) - return PNR_IO_ERROR; + //return PNR_IO_ERROR; + PNUB_RETURN_STAT(PNR_IO_ERROR); int http_code = atoi(bufptr+9); if (http_code / 100 != 2) - return PNR_HTTP_ERROR; + //return PNR_HTTP_ERROR; + PNUB_RETURN_STAT(PNR_HTTP_ERROR); bufptr = strstr(bufptr, "\r\n"); if (!bufptr) - return PNR_IO_ERROR; + //return PNR_IO_ERROR; + PNUB_RETURN_STAT(PNR_IO_ERROR); bufptr += 2; /* Parse the rest of the lines. */ @@ -170,7 +237,8 @@ for (;; bufptr = newline+2) { newline = strstr(bufptr, "\r\n"); if (!newline) - return PNR_IO_ERROR; + //return PNR_IO_ERROR; + PNUB_RETURN_STAT(PNR_IO_ERROR); *newline = 0; char h_chunked[] = "Transfer-Encoding: chunked"; @@ -193,7 +261,8 @@ m_content_length = atoi(bufptr); bufptr = strstr(bufptr, "\r\n"); if (!bufptr) - return PNR_IO_ERROR; + //return PNR_IO_ERROR; + PNUB_RETURN_STAT(PNR_IO_ERROR); bufptr += 2; } @@ -212,19 +281,22 @@ if (m_content_length > PUBNUB_REPLY_MAXLEN) { /* XXX: Actually, too much data, sorry. */ DBG printf("too much data\r\n"); - return PNR_FORMAT_ERROR; + //return PNR_FORMAT_ERROR; + PNUB_RETURN_STAT(PNR_FORMAT_ERROR); } if (m_replylen < m_content_length) { /* More data coming. */ int recv2len = receive_all(m_replybuf + m_replylen, PUBNUB_REPLY_MAXLEN - m_replylen); if (recv2len < 0) - return PNR_IO_ERROR; + //return PNR_IO_ERROR; + PNUB_RETURN_STAT(PNR_IO_ERROR); m_replylen += recv2len; } if (m_replylen < m_content_length) - return PNR_IO_ERROR; /* Incomplete data. */ + //return PNR_IO_ERROR; /* Incomplete data. */ + PNUB_RETURN_STAT(PNR_IO_ERROR); return PNR_OK; } @@ -260,7 +332,8 @@ } DBG printf("parse %d %d %d\r\n", escaped, in_string, bracket_level); if (escaped || in_string || bracket_level > 0) - return PNR_FORMAT_ERROR; + //return PNR_FORMAT_ERROR; + PNUB_RETURN_STAT(PNR_FORMAT_ERROR); return PNR_OK; } @@ -294,8 +367,10 @@ PubNubHTTP http; PubNubRes res = http.http_connect(origin_hostname()); if (res != PNR_OK) - return res; + //return res; + PNUB_RETURN_STAT(res); +#ifdef USE_ORIGINAL_CODE http.sendstr("GET /publish/"); http.sendstr(m_publish_key); http.sendstr("/"); @@ -304,6 +379,13 @@ http.sendstr(channel); http.sendstr("/0/"); http.sendstr_urlenc(message); +#else + char* buff = (char*)malloc(2000); + int len = sprintf(buff, "GET /publish/%s/%s/0/%s/0/",m_publish_key,m_subscribe_key,channel); + http.prepare_urlenc(message, buff+len); + http.sendstr(buff); + free(buff); +#endif char *locreply; if (!reply) @@ -312,13 +394,56 @@ res = http.http_request_bh(origin_hostname(), reply, &replylen); if (res != PNR_OK) - return res; + //return res; + PNUB_RETURN_STAT(res); bool success = (*reply)[1] == '1' && (*reply)[2] == ','; if (reply == &locreply) free(locreply); - return success ? PNR_OK : PNR_PUBNUB_ERROR; + //return success ? PNR_OK : PNR_PUBNUB_ERROR; + PNUB_RETURN_STAT(success ? PNR_OK : PNR_PUBNUB_ERROR); } +PubNubRes +PubNub::publish_urlenc(const char *channel, const char *message, char **reply) +{ + PubNubHTTP http; + PubNubRes res = http.http_connect(origin_hostname()); + if (res != PNR_OK) + //return res; + PNUB_RETURN_STAT(res); + +#ifdef USE_ORIGINAL_CODE + http.sendstr("GET /publish/"); + http.sendstr(m_publish_key); + http.sendstr("/"); + http.sendstr(m_subscribe_key); + http.sendstr("/0/"); + http.sendstr(channel); + http.sendstr("/0/"); + http.sendstr_urlenc(message); +#else + char* buff = (char*)malloc(2000); + /*int len = */sprintf(buff, "GET /publish/%s/%s/0/%s/0/%s",m_publish_key,m_subscribe_key,channel,message); + //http.prepare_urlenc(message, buff+len); + http.sendstr(buff); + free(buff); +#endif + + char *locreply; + if (!reply) + reply = &locreply; + int replylen; + + res = http.http_request_bh(origin_hostname(), reply, &replylen); + if (res != PNR_OK) + //return res; + PNUB_RETURN_STAT(res); + bool success = (*reply)[1] == '1' && (*reply)[2] == ','; + if (reply == &locreply) + free(locreply); + //return success ? PNR_OK : PNR_PUBNUB_ERROR; + PNUB_RETURN_STAT(success ? PNR_OK : PNR_PUBNUB_ERROR); +} PubNubRes PubNub::subscribe(const char *channel, char **reply) @@ -350,14 +475,21 @@ PubNubRes res; res = http.http_connect(origin_hostname()); if (res != PNR_OK) - return res; + //return res; + PNUB_RETURN_STAT(res); +#ifdef USE_ORIGINAL_CODE http.sendstr("GET /subscribe/"); http.sendstr(m_subscribe_key); http.sendstr("/"); http.sendstr(channel); http.sendstr("/0/"); http.sendstr(m_timetoken); +#else + char buff[200]; + snprintf(buff, 200, "GET /subscribe/%s/%s/0/%s", m_subscribe_key, channel, m_timetoken); + http.sendstr(buff); +#endif char *replybuf = NULL; int replylen; @@ -394,7 +526,8 @@ free(reply); m_replybuf = NULL; m_replylen = 0; - return res; + //return res; + PNUB_RETURN_STAT(res); } PubNubRes @@ -403,7 +536,8 @@ if (reply[0] != '[' || reply[replylen-1] != ']' || reply[replylen-2] != '"') { DBG printf("bad reply '%s'\r\n", reply); - return PNR_FORMAT_ERROR; + //return PNR_FORMAT_ERROR; + PNUB_RETURN_STAT(PNR_FORMAT_ERROR); } /* Extract timetoken. */ @@ -414,7 +548,8 @@ break; if (!i || reply[i-1] != ',' || replylen-2 - (i+1) >= 64) { DBG printf("bad reply '%s'\r\n", reply); - return PNR_FORMAT_ERROR; + //return PNR_FORMAT_ERROR; + PNUB_RETURN_STAT(PNR_FORMAT_ERROR); } strcpy(m_timetoken, &reply[i+1]); reply[i-1] = 0; // terminate the [] message array @@ -430,7 +565,8 @@ /* Extract the messages array. */ if (reply[1] != '[' || reply[i-2] != ']') { DBG printf("bad reply end '%s'\r\n", reply); - return PNR_FORMAT_ERROR; + //return PNR_FORMAT_ERROR; + PNUB_RETURN_STAT(PNR_FORMAT_ERROR); } reply[i-2] = 0; @@ -450,8 +586,10 @@ PubNubRes res; res = http.http_connect(origin_hostname()); if (res != PNR_OK) - return res; + //return res; + PNUB_RETURN_STAT(res); +#ifdef USE_ORIGINAL_CODE http.sendstr("GET /history/"); http.sendstr(m_subscribe_key); http.sendstr("/"); @@ -459,6 +597,11 @@ http.sendstr("/0/"); char limitbuf[8]; snprintf(limitbuf, sizeof(limitbuf), "%d", limit); http.sendstr(limitbuf); +#else + char buff[200]; + snprintf(buff, 200, "GET /history/%s/%s/0/%d", m_subscribe_key, channel, limit); + http.sendstr(buff); +#endif char *replybuf = NULL; int replylen; @@ -494,7 +637,8 @@ error: free(replybuf); - return res; + //return res; + PNUB_RETURN_STAT(res); } @@ -504,7 +648,8 @@ PubNubHTTP http; PubNubRes res = http.http_connect(origin_hostname()); if (res != PNR_OK) - return res; + //return res; + PNUB_RETURN_STAT(res); http.sendstr("GET /time/0"); @@ -512,11 +657,13 @@ int replylen; res = http.http_request_bh(origin_hostname(), &reply, &replylen); if (res != PNR_OK) - return res; + //return res; + PNUB_RETURN_STAT(res); if (replylen < 3 || replylen > 32 || reply[0] != '[' || reply[replylen-1] != ']') { free(reply); - return PNR_FORMAT_ERROR; + //return PNR_FORMAT_ERROR; + PNUB_RETURN_STAT(PNR_FORMAT_ERROR); } replylen -= 2; @@ -525,4 +672,4 @@ free(reply); return PNR_OK; -} \ No newline at end of file +}
--- a/PubNub.h Tue May 20 11:43:18 2014 +0000 +++ b/PubNub.h Wed Oct 01 11:36:11 2014 +0000 @@ -110,6 +110,10 @@ * @param reply optional pointer for passing the returned reply (free() after use). * @return PNR_OK on success. */ PubNubRes publish(const char *channel, const char *message, char **reply = NULL); + + /* Same as publish() but the user guarantees that the message is URL encoded already. + * This saves time compared to having to parse and convert the string every call. */ + PubNubRes publish_urlenc(const char *channel, const char *message, char **reply = NULL); /** Subscribe API call * @@ -197,10 +201,10 @@ char m_timetoken[32]; char *m_replybuf; int m_replylen; - + /* Back-end post-processing of subscribe(). free()s *reply * in the process. */ PubNubRes subscribe_processjson(char *reply, int replylen); }; -#endif \ No newline at end of file +#endif