// ---------------------------------------------------------------------------- // the callback used to format data (into text, json, html, etc.) for clients // ---------------------------------------------------------------------------- // HTTP/1.1 200 OK // Content-type:text/plain // Transfer-Encoding: chunked // // 3c; // KEY:time VAL:Mon, 12 Mar 2012 13:11:49 GMT|KEY:00 VAL:14.03 // 3c; // KEY:time VAL:Mon, 12 Mar 2012 13:11:49 GMT|KEY:00 VAL:40.75 // 3c; // KEY:time VAL:Mon, 12 Mar 2012 13:11:49 GMT|KEY:00 VAL:45.02 // 0 // static int push_fn(char *argv[], xbuf_t *reply) { reply->len = 0; // empty buffer xbuf_xcat(reply, " \r\n" // room for chunck size: "1a; " "KEY:time VAL:%s|", (char*)get_env(argv, SERVER_DATE)); // fill the 2D HTML <table> cells with our 1D array // (we randomly skip some cells to avoid updating all of them) { int i = 0, r = 0, c = 0; while(i < CELLS) { if(!(sw_rand(&rnd) & 3)) // skip some cells xbuf_xcat(reply, "KEY:%c%c VAL:%.02f|", '0' + r, '0' + c, s_data[i++]); if(c + 1 < COLS) c++; else { c = 0; if(r + 1 < ROWS) r++; else r = 0; } } } reply->ptr[--reply->len] = 0; // remove the ending '|' // now we know it, setup the chunk size char *p = reply->ptr; int i = s_snprintf(p, 8, "%x;", reply->len - (sizeof(" \r\n") - 2)); p[i] = ' '; // useful for tracing, when we puts(reply->ptr); later xbuf_ncat(reply, "\r\n", sizeof("\r\n") - 1); // close this chunk return 1; }
int main(int argc, char *argv[]) { xbuf_t data; xbuf_init(&data); xbuf_cat(&data, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sit amet " "quam purus, vitae fermentum turpis. Nam in augue mi. Donec suscipit moles" "tie felis, eget rhoncus risus pharetra in. Sed id diam id felis fringilla" " adipiscing non vitae odio. Etiam vulputate tristique elit, nec eleifend" " mauris scelerisque eu. Vestibulum luctus, enim a luctus posuere, mauris" " mauris rutrum mi, eget fermentum massa tortor id enim. Nulla feugiat " "porta urna quis laoreet. Morbi metus ante, commodo quis dictum vitae, " "rhoncus a libero. Cras viverra feugiat orci id interdum. Duis pulvinar " "neque id erat adipiscing facilisis. Maecenas vitae urna risus, euismod" " sollicitudin risus." ); char gzipped[4096]; u32 len = zlib_cmp(data.ptr, 0, data.len, gzipped, sizeof(gzipped), 1); printf("Original length: %i\n", data.len); printf("Gzipped length: %i\n\n", len); xbuf_t *reply = get_reply(argv); xbuf_xcat(reply, "HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" "Content-Length: %i\r\n" "Content-Encoding: gzip\r\n\r\n", len ); xbuf_ncat(reply, gzipped, len); xbuf_free(&data); return -1; //custom headers }
int main(int argc, char *argv[]) { // get the server 'reply' buffer where to write our answer to the client xbuf_t *reply = get_reply(argv); // ------------------------------------------------------------------------- // step 1: setup a per-request context // ------------------------------------------------------------------------- void **data = (void**)get_env(argv, US_REQUEST_DATA); if(!data[0]) // we did not setup our per-request counter yet { data[0] = (void*)1; // use the persistent pointer as a simple counter char head[] = "HTTP/1.1 200 OK\r\n" "Connection: close\r\n" "Content-length: 60\r\n" // 3 * 20 characters "Content-type: text/plain; charset=utf-8\r\n" "\r\n\r\n"; xbuf_ncat(reply, head, sizeof(head) - 1); } // ------------------------------------------------------------------------- // step 2: repeatedly send to client an incremental reply // ------------------------------------------------------------------------- xbuf_xcat(reply, "partial reply %llu<br>\n", data[0]++); // ------------------------------------------------------------------------- // 3: decide when to stop streaming // ------------------------------------------------------------------------- if(data[0] > (void*)NBR_CHUNKS) { data[0] = 0; return RC_NOHEADERS; // RC_NOHEADERS: do not generate HTTP headers } // RC_NOHEADERS: do not generate HTTP headers // RC_STREAMING: call me again after send() is done return RC_NOHEADERS + RC_STREAMING; }
// ============================================================================ // main() is receiving the query parameters ("csp?arg1&arg2&arg3...") in argv[] // ---------------------------------------------------------------------------- int main(int argc, char *argv[]) { // create a dynamic buffer and get a pointer on the server response buffer xbuf_t *reply = get_reply(argv); // ------------------------------------------------------------------------- // filter input data to avoid all the useless/nasty cases // ------------------------------------------------------------------------- char *Feed = ""; get_arg("feed=", &Feed, argc, argv); char *Delay = ""; get_arg("delay=", &Delay, argc, argv); // ------------------------------------------------------------------------- // no valid parameters provided, redirect client to "csp_comet.html" // ------------------------------------------------------------------------- if(argc < 1 || !Delay[0] || !Feed[0] || memcmp(Feed, FEED_NAME, sizeof(FEED_NAME))) { static char redir[] = "HTTP/1.1 302 Found\r\n" "Content-type:text/plain\r\n" "Location: /csp_comet.html\r\n\r\n" "<html><head><title>Redirect</title></head><body>" "Click <a href=\"/csp_comet.html\">here</a>.</body></html>"; xbuf_ncat(reply, redir, sizeof(redir) - 1); return 302; // return an HTTP code (302:'Found') } int delay = atoi(Delay); if(delay < 1) delay = 1; else if(delay > 10) delay = 10; // arbitrary choice for this demo // ------------------------------------------------------------------------- // make this client subscribe to the requested feed // ------------------------------------------------------------------------- // this also creates the feed if it does not exist already { // internally, make_freq is never > push_freq; here we just define a // by-default value aimed at not loading the server pointlessly u32 make_freq = 1, push_freq = delay; if(!push_list_add(argv, FEED_NAME, make_fn, make_freq, // 1 second here (easy to see) push_fn, push_freq, // user-defined free_fn)) return 503; // 503: Service Unavailable (cannot create feed) } // ------------------------------------------------------------------------- // send the "Transfer-Encoding: chunked" HTTP header // ------------------------------------------------------------------------- // anatomy of a chunked response: // // HTTP/1.1 200 OK [CRLF] <-+ // Content-Type: text/html [CRLF] | HTTP headers // Transfer-Encoding: chunked [CRLF] <-+ // [CRLF] // 1a; optional-stuff-here [CRLF] // hexadecimal length // abcdefghijklmnopqrstuvwxyz [CRLF] // data (ASCII/binary) // 10 [CRLF] // hexadecimal length // 1234567890abcdef [CRLF] // data (ASCII/binary) // 0 [CRLF] // 0: end of chunks // optional-footer: some-value [CRLF] // can be HTTP headers // optional-another-footer: another-value [CRLF] // can be HTTP headers // [CRLF] { char head[] = "HTTP/1.1 200 OK\r\n" //"Connection: close\r\n" //"Content-type: application/json; charset=utf-8\r\n" "Content-type: text/plain; charset=utf-8\r\n" "Transfer-Encoding: chunked\r\n\r\n"; xbuf_ncat(reply, head, sizeof(head) - 1); } // ------------------------------------------------------------------------- /* send first frame // ------------------------------------------------------------------------- xbuf_t buf; xbuf_init(&buf); make_fn(argv); push_fn(argv, &buf); xbuf_ncat(reply, buf.ptr, buf.len); xbuf_free(&buf); */ //puts(reply->ptr); return 200; }
int main(int argc, char *argv[]) { // get the server 'reply' buffer where to write our answer to the client xbuf_t *reply = get_reply(argv); data_t **Data = (data_t **)get_env(argv, US_SERVER_DATA), *data; data = *Data; // ------------------------------------------------------------------------- // step 1: setup a per-request context // ------------------------------------------------------------------------- gw_req_async_t **R_D = (gw_req_async_t **)get_env(argv, US_REQUEST_DATA) , *r_d; if(!*R_D) // we did not setup our per-request structure yet { // create a per-request memory pool if(!gc_init(argv, 4070)) // can now use gc_alloc() for up to 4070 bytes return 503; // could not allocate memory! *R_D = gc_malloc(argv, sizeof(gw_req_async_t)); // allocate a request context if(!*R_D) return 500; // not possible here, but a good habit anyway r_d = *R_D; r_d->reply = reply; r_d->lock.val = 1; // ---------------------------------------------------------------------- // step 2: schedule asynchronous jobs // ---------------------------------------------------------------------- // ev_async_send is not required, only example. ev_async_send(data->loop, &data->async); redisAsyncCommand(data->c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redisAsyncCommand(data->c, getCallback, r_d, "GET key"); // ---------------------------------------------------------------------- // tell G-WAN to run the script when reply received from redis // ---------------------------------------------------------------------- // WK_MS:milliseconds, WK_FD:file_descriptor wake_up(argv, data->c->c.fd, WK_FD, NULL); // ---------------------------------------------------------------------- // send chunked encoding HTTP header and HTTP status code // ---------------------------------------------------------------------- static const char head[] = "HTTP/1.1 200 OK\r\n" "Connection: close\r\n" // this will limit the connection rate "Content-type: text/html; charset=utf-8\r\n" "Transfer-Encoding: chunked\r\n\r\n" "5\r\n<pre>\r\n"; // format is: "[length]\r\n[data]\n\n" xbuf_ncat(reply, head, sizeof(head) - 1); light_lock(&r_d->lock); // ------------------------------------------------------------------------- // return code // ------------------------------------------------------------------------- // RC_NOHEADERS: do not generate HTTP headers // RC_STREAMING: call me again after send() is done return RC_NOHEADERS + RC_STREAMING; } else { r_d = *R_D; // We already initialized request wide data pointer // now wait for redis response light_lock(&r_d->lock); // end chunked encoding; format is: "[length]\r\n[data]\n\n" static const char end[] = "6\r\n</pre>\r\n0\r\n\r\n"; xbuf_ncat(reply, end, sizeof(end) - 1); wake_up(argv, 0, WK_FD, NULL); // 0:disable any further wake-up return RC_NOHEADERS; // RC_NOHEADERS: do not generate HTTP headers } return 200; // never get here }
// ---------------------------------------------------------------------------- // imported functions: // get_reply(): get a pointer on the 'reply' dynamic buffer from the server // getus(): get current time in microseconds (1 millisecond = 1,000 us) // get_env(): get connection's 'environment' variables from the server // xbuf_cat(): like strcat(), but it works in the specified dynamic buffer // gif_build(): build an in-memory GIF image from a bitmap and palette // ---------------------------------------------------------------------------- int main(int argc, char *argv[]) { // ------------------------------------------------------------------------- // get a pointer on the server reply // ------------------------------------------------------------------------- xbuf_t *reply = get_reply(argv); // ------------------------------------------------------------------------- // allocate memory for a raw bitmap // ------------------------------------------------------------------------- int w = 800, h = 800, nbcolors = 256, wXh = w * h; u8 *bmp = (u8*)malloc(wXh); if(!bmp) return 503; // service unavailable // ------------------------------------------------------------------------- // render the Mandelbrot set in our bitmap // ------------------------------------------------------------------------- fractals(bmp, w, h, nbcolors); // ------------------------------------------------------------------------- // display the palette (useful when playing with 'tabcol[]' values) // ------------------------------------------------------------------------- { #define ROUND(a) ((a) > 0 ? (int)((a)+0.5) : -(int)(0.5-(a))) u8 *p = bmp; int i = h, wd20 = w / 20; float color = 0, col = (float)nbcolors / (float)h; while(i--) { color += col; memset(p, ROUND(color) & 255, wd20); p += w; } } // ------------------------------------------------------------------------- // build a smooth multi-gradient color palette from the fixed values below // ------------------------------------------------------------------------- static rgb_t tabcol[]={ { 0, 0, 128}, // Med. Blue { 0, 100, 200}, // Light Blue {100, 160, 160}, // Cyan { 0, 220, 100}, // Green {255, 255, 0}, // Yellow {255, 128, 0}, // Orange {128, 0, 0}, // Med. Red { 64, 0, 0}, // Dark Red {128, 0, 0}, // Med. Red {255, 128, 0}, // Orange {255, 255, 0}, // Yellow { 0, 220, 100}, // Green {100, 160, 160}, // Cyan { 0, 100, 200}, // Light Blue { 0, 0, 128}, // Med. Blue { 64, 0, 0}, // Dark Red {128, 0, 0}, // Med. Red {255, 128, 0}, // Orange {255, 255, 0}, // Yellow { 0, 220, 100}, // Green {100, 160, 160}, // Cyan { 0, 100, 200}, // Light Blue { 0, 0, 128}, // Med. Blue { 0, 0, 64}, // Dark Blue }, *tab = tabcol; // ------------------------------------------------------------------------- // just for fun, select different colors for each call // ------------------------------------------------------------------------- static u32 call = 0; u32 ncols = sizeof(tabcol) / sizeof(rgb_t); switch(call) { case 0: tab = tabcol; ncols = 10; break; // blue case 1: tab = &tabcol[ 4]; ncols = 10; break; // yellow case 2: tab = &tabcol[ 7]; ncols = 7; break; // dark red case 3: tab = &tabcol[ 1]; ncols = 16; break; // rainbow - } call = (call + 1) & 3; // generate the palette with our defined gradient steps u8 pal[768]; dr_gradient(pal, nbcolors, tab, ncols); // nice palete but we want a black body to delimit the mandelbrot set memset(pal + ((nbcolors - (nbcolors / 16)) * 3), 0, (nbcolors / 16) * 3); // ------------------------------------------------------------------------- // create custom HTTP response headers to send a GIF file // ------------------------------------------------------------------------- // (G-WAN automatically generates headers if none are provided but it can't // guess all MIME types so this automatic feature is for 'text/html' only // ...unless you explicitly specify the reply buffer MIME type) #ifdef BUILD_CUSTOM_HEADERS // old way of doing things // get the current HTTP date (like "Wed, 02 Jun 2010 06:49:37 GMT") u8 *date = (u8*)get_env(argv, SERVER_DATE); xbuf_xcat(reply, "HTTP/1.1 200 OK\r\n" "Date: %s\r\n" "Last-Modified: %s\r\n" "Content-type: image/gif\r\n" "Content-Length: \r\n" // make room for the for GIF length "Connection: close\r\n\r\n", date, date); // ------------------------------------------------------------------------- // make sure that we have enough space in the 'reply' buffer // (we are going to fill it directly from gif_build(), not via xbuf_xxx) // ------------------------------------------------------------------------- // (if we have not enough memory, we will get a 'graceful' crash) if(reply->allocated < (wXh / 10)) // very gross approximation { if(!xbuf_growto(reply, wXh / 10)) // resize reply { xbuf_init(reply); xbuf_ncat(reply, " ", 1); reply->len = 0; // discart pointless data, keep allocated memory return 503; // error: we could not allocate enough memory } } // ------------------------------------------------------------------------- // save the place where to patch the void 'Content-Length' HTTP Header // ------------------------------------------------------------------------- char *p = reply->ptr + reply->len - (sizeof("\r\nConnection: close\r\n\r\n") - 1); // ------------------------------------------------------------------------- // append a GIF image (-1:no transparency, 0: no comment) to 'reply' // ------------------------------------------------------------------------- int len = gif_build((u8*)(reply->ptr + reply->len), bmp, w, h, pal, nbcolors, -1, 0); if(len < 0) len = 0; // (len == -1) if gif_build() failed reply->len += len; // add the GIF size to the 'reply' buffer length free(bmp); // ------------------------------------------------------------------------- // store the GIF size in the empty space of the 'Content-Length' header // ------------------------------------------------------------------------- u32toa(p, len); #else // #ifdef BUILD_CUSTOM_HEADERS // works with any supported MIME type // ------------------------------------------------------------------------- // specify a MIME type so we don't have to build custom HTTP headers // ------------------------------------------------------------------------- char *mime = (char*)get_env(argv, REPLY_MIME_TYPE); // note that we setup the FILE EXTENTION, not the MIME type: mime[0] = '.'; mime[1] = 'g'; mime[2] = 'i'; mime[3] = 'f'; mime[4] = 0; // ------------------------------------------------------------------------- // make sure that we have enough space in the 'reply' buffer // (we are going to fill it directly from gif_build(), not via xbuf_xxx) // ------------------------------------------------------------------------- // (if we have not enough memory, we will get a 'graceful' crash) if(reply->allocated < (wXh / 10)) // very gross approximation { if(!xbuf_growto(reply, wXh / 10)) // resize reply { xbuf_init(reply); xbuf_ncat(reply, " ", 1); reply->len = 0; // discart pointless data, keep allocated memory return 503; // error: we could not allocate enough memory } } // ------------------------------------------------------------------------- // append a GIF image (-1:no transparency, 0: no comment) to 'reply' // ------------------------------------------------------------------------- int len = gif_build((u8*)(reply->ptr + reply->len), bmp, w, h, pal, nbcolors, -1, 0); if(len < 0) len = 0; // (len == -1) if gif_build() failed reply->len += len; // add the GIF size to the 'reply' buffer length free(bmp); #endif // #else #ifdef BUILD_CUSTOM_HEADERS return 200; // return an HTTP code (200:'OK') }
// ---------------------------------------------------------------------------- // imported functions: // get_reply(): get a pointer on the 'reply' dynamic buffer from the server // get_env(): get connection's 'environment' variables from the server // xbuf_cat(): like strcat(), but it works in the specified dynamic buffer // gif_build(): build an in-memory GIF image from a bitmap and palette // ---------------------------------------------------------------------------- int main(int argc, char *argv[]) { // ------------------------------------------------------------------------- // build the top of our HTML page // ------------------------------------------------------------------------- static char top[]= "<!DOCTYPE HTML>" "<html lang=\"en\"><head><title>Captcha</title><meta http-equiv" "=\"Content-Type\" content=\"text/html; charset=utf-8\">" "<link href=\"/imgs/style.css\" rel=\"stylesheet\" type=\"text/css\">" "</head><body style=\"margin:0 16px;\"><br><h2>Captcha for Humans</h2>" "<p>Please enter the SUM of all the GREEN FIGURES (not letters) below " "(that's twice the same image, just with a different HTML background " "- the Data-URI-inlined GIF background is transparent):</p><br>\r\n"; xbuf_t *reply = get_reply(argv); xbuf_ncat(reply, top, sizeof(top) - 1); // ------------------------------------------------------------------------- // allocate memory for a raw bitmap // ------------------------------------------------------------------------- const int w = BMP_WIDTH, h = BMP_HEIGHT, wXh = w * h; u8 *bmp = (u8*)calloc(CHAR_WIDTH * w, h); if(!bmp) return 503; // service unavailable // ------------------------------------------------------------------------- // render the captcha in our bitmap // ------------------------------------------------------------------------- u32 seed = (u32)getns(); prnd_t rnd; // pseudo-random generator (period: 1 << 158) sw_init(&rnd, seed); // EPOCH time in nano-seconds // structure needed by G-WAN's frame buffer routines like dr_text() bmp_t img ={ .bmp = bmp, .p = bmp, .bbp = 8, .pen = 1, .bgd = 0, .rect = {0,0, w,h}, .flags = 0, .w = w, .h = h, .x = 0, .y = 0 }; u32 sum = captcha(&img, &rnd); // ------------------------------------------------------------------------- // build the GIF image, gif_build(0:transparent color index, 0: no comment) // ------------------------------------------------------------------------- u8 pal[] = { 255, 255, 255, 223, 255, 191, 132, 164, 100, 0, 0, 0 }; const int nbcolors = (sizeof(pal) / sizeof(u8)) / 3; // RGB values u8 *gif = (u8*)malloc(CHAR_WIDTH * wXh); if(!gif) { free(bmp); return 503; } // service unavailable int gln = gif_build(gif, bmp, w, h, pal, nbcolors, 0, 0); // ------------------------------------------------------------------------- // store the base64 encoded GIF in the 'reply' buffer // ------------------------------------------------------------------------- if(gln > 0) // (gln == -1) if gif_build() failed { // a real captcha test would only display the first of those two views: // (they are shown side-by-side to visualize the background trick) xbuf_cat(reply, "<table><tr>\r\n" "<td style=\"background:#dfffbf;\">\r\n"); u32 img_pos = reply->len; xbuf_xcat(reply, "<img src=\"data:image/gif;base64,%*B\" alt=\"A tree\" " "width=\"%d\" height=\"%d\" /></td>\r\n", gln, gif, w + w, h + h); // scale picture xbuf_xcat(reply, "<td style=\"background:#84a464;\">%.*s</tr>\r\n</table>\n\r", reply->len - img_pos, reply->ptr + img_pos); } free(gif); free(bmp); // ------------------------------------------------------------------------- // close our HTML page // ------------------------------------------------------------------------- xbuf_xcat(reply, "<br>The two sums are: <b>%u</b> and <b>%u</b>... " "for the same Captcha image!<br><br>" "By just changing the <b>HTML background color</b> (mouse cursor " "hovering, previous state or input or shared secret) used for " "the transparent GIF Captcha image we can make something simple " "for humans become difficult or even completely impossible " "for robots.<br><br>" "HTML and GIF are served with one single request: the picture" " is generated on-the-fly and embedded into the HTML code by " " using the base64 encoding (look at the HTML source code)." "<br></body></html>", (sum & 0xffff0000) >> 16, sum & 0x0000ffff); return 200; // return an HTTP code (200:'OK') }