| Line 155... |
Line 155... |
| 155 |
#define METHOD_POST 1
|
155 |
#define METHOD_POST 1
|
| 156 |
#define METHOD_GET 2
|
156 |
#define METHOD_GET 2
|
| 157 |
#define METHOD_HEAD 3
|
157 |
#define METHOD_HEAD 3
|
| 158 |
|
158 |
|
| 159 |
/* attributes of a connection/worker */
|
159 |
/* attributes of a connection/worker */
|
| 160 |
#define CONNECTION_CLOSE 0x01 /* Connection: close response behavior is requested */
|
160 |
#define CONNECTION_CLOSE 0x01 /* Connection: close response behavior is requested */
|
| 161 |
#define HOST_HEADER 0x02 /* headers contained Host: header (required for HTTP/1.1) */
|
161 |
#define HOST_HEADER 0x02 /* headers contained Host: header (required for HTTP/1.1) */
|
| 162 |
#define HTTP_1_0 0x04 /* the client requested HTTP/1.0 */
|
162 |
#define HTTP_1_0 0x04 /* the client requested HTTP/1.0 */
|
| 163 |
#define CONTENT_LENGTH 0x08 /* Content-length: was specified in the headers */
|
163 |
#define CONTENT_LENGTH 0x08 /* Content-length: was specified in the headers */
|
| 164 |
#define THREAD_OWNED 0x10 /* the worker is owned by a thread and cannot removed */
|
164 |
#define THREAD_OWNED 0x10 /* the worker is owned by a thread and cannot removed */
|
| 165 |
#define THREAD_DISPOSE 0x20 /* the thread should dispose of the worker */
|
165 |
#define THREAD_DISPOSE 0x20 /* the thread should dispose of the worker */
|
| - |
|
166 |
#define CONTENT_TYPE 0x40 /* message has a specific content type set */
|
| - |
|
167 |
#define CONTENT_FORM_UENC 0x80 /* message content type is application/x-www-form-urlencoded */
|
| 166 |
|
168 |
|
| 167 |
/* --- connection/worker structure holding all data for an active connection --- */
|
169 |
/* --- connection/worker structure holding all data for an active connection --- */
|
| 168 |
typedef struct httpd_conn {
|
170 |
typedef struct httpd_conn {
|
| 169 |
SOCKET sock; /* client socket */
|
171 |
SOCKET sock; /* client socket */
|
| 170 |
struct in_addr peer; /* IP address of the peer */
|
172 |
struct in_addr peer; /* IP address of the peer */
|
| Line 173... |
Line 175... |
| 173 |
#else
|
175 |
#else
|
| 174 |
InputHandler *ih; /* worker input handler */
|
176 |
InputHandler *ih; /* worker input handler */
|
| 175 |
#endif
|
177 |
#endif
|
| 176 |
char line_buf[LINE_BUF_SIZE]; /* line buffer (used for request and headers) */
|
178 |
char line_buf[LINE_BUF_SIZE]; /* line buffer (used for request and headers) */
|
| 177 |
char *url, *body; /* URL and request body */
|
179 |
char *url, *body; /* URL and request body */
|
| - |
|
180 |
char *content_type; /* content type (if set) */
|
| 178 |
unsigned int line_pos, body_pos, content_length; /* positions in the buffers and desired content length */
|
181 |
unsigned int line_pos, body_pos, content_length; /* positions in the buffers and desired content length */
|
| 179 |
char part, method, attr; /* request part, method and connection attributes */
|
182 |
char part, method, attr; /* request part, method and connection attributes */
|
| 180 |
} httpd_conn_t;
|
183 |
} httpd_conn_t;
|
| 181 |
|
184 |
|
| 182 |
/* returns the HTTP/x.x string for a given connection - we support 1.0 and 1.1 only */
|
- |
|
| 183 |
#define HTTP_SIG(C) ((((C)->attr & HTTP_1_0) == 0) ? "HTTP/1.1" : "HTTP/1.0")
|
- |
|
| 184 |
|
- |
|
| 185 |
#define IS_HTTP_1_1(C) (((C)->attr & HTTP_1_0) == 0)
|
185 |
#define IS_HTTP_1_1(C) (((C)->attr & HTTP_1_0) == 0)
|
| 186 |
|
186 |
|
| - |
|
187 |
/* returns the HTTP/x.x string for a given connection - we support 1.0 and 1.1 only */
|
| - |
|
188 |
#define HTTP_SIG(C) (IS_HTTP_1_1(C) ? "HTTP/1.1" : "HTTP/1.0")
|
| - |
|
189 |
|
| 187 |
/* --- static list of currently activbe workers --- */
|
190 |
/* --- static list of currently active workers --- */
|
| 188 |
static httpd_conn_t *workers[MAX_WORKERS];
|
191 |
static httpd_conn_t *workers[MAX_WORKERS];
|
| 189 |
|
192 |
|
| 190 |
/* --- flag determining whether one-time initialization is yet to be performed --- */
|
193 |
/* --- flag determining whether one-time initialization is yet to be performed --- */
|
| 191 |
static int needs_init = 1;
|
194 |
static int needs_init = 1;
|
| 192 |
|
195 |
|
| Line 234... |
Line 237... |
| 234 |
if (c->body) {
|
237 |
if (c->body) {
|
| 235 |
free(c->body);
|
238 |
free(c->body);
|
| 236 |
c->body = NULL;
|
239 |
c->body = NULL;
|
| 237 |
}
|
240 |
}
|
| 238 |
|
241 |
|
| - |
|
242 |
if (c->content_type) {
|
| - |
|
243 |
free(c->content_type);
|
| - |
|
244 |
c->content_type = NULL;
|
| - |
|
245 |
}
|
| - |
|
246 |
|
| 239 |
if (c->sock != INVALID_SOCKET) {
|
247 |
if (c->sock != INVALID_SOCKET) {
|
| 240 |
closesocket(c->sock);
|
248 |
closesocket(c->sock);
|
| 241 |
c->sock = INVALID_SOCKET;
|
249 |
c->sock = INVALID_SOCKET;
|
| 242 |
}
|
250 |
}
|
| 243 |
}
|
251 |
}
|
| Line 298... |
Line 306... |
| 298 |
return 0;
|
306 |
return 0;
|
| 299 |
}
|
307 |
}
|
| 300 |
|
308 |
|
| 301 |
/* sends HTTP/x.x plus the text (which should be of the form " XXX ...") */
|
309 |
/* sends HTTP/x.x plus the text (which should be of the form " XXX ...") */
|
| 302 |
static int send_http_response(httpd_conn_t *c, const char *text) {
|
310 |
static int send_http_response(httpd_conn_t *c, const char *text) {
|
| - |
|
311 |
char buf[96];
|
| 303 |
const char *s = HTTP_SIG(c);
|
312 |
const char *s = HTTP_SIG(c);
|
| - |
|
313 |
int l = strlen(text);
|
| - |
|
314 |
/* reduce the number of packets by sending the payload en-block from buf */
|
| - |
|
315 |
if (l < sizeof(buf) - 10) {
|
| - |
|
316 |
strcpy(buf, s);
|
| - |
|
317 |
strcpy(buf + 8, text);
|
| - |
|
318 |
return send_response(c->sock, buf, l + 8);
|
| - |
|
319 |
}
|
| 304 |
if (send(c->sock, s, 8, 0) < 8) return -1;
|
320 |
if (send(c->sock, s, 8, 0) < 8) return -1;
|
| 305 |
return send_response(c->sock, text, strlen(text));
|
321 |
return send_response(c->sock, text, strlen(text));
|
| 306 |
}
|
322 |
}
|
| 307 |
|
323 |
|
| 308 |
/* decode URI in place (decoding never expands) */
|
324 |
/* decode URI in place (decoding never expands) */
|
| Line 381... |
Line 397... |
| 381 |
setAttrib(res, R_NamesSymbol, names);
|
397 |
setAttrib(res, R_NamesSymbol, names);
|
| 382 |
UNPROTECT(2);
|
398 |
UNPROTECT(2);
|
| 383 |
return res;
|
399 |
return res;
|
| 384 |
}
|
400 |
}
|
| 385 |
|
401 |
|
| - |
|
402 |
/* create an object representing the request body. It is NULL if the body is empty (or zero length).
|
| - |
|
403 |
* In the case of a URL encoded form it will have the same shape as the query string (named string vector).
|
| - |
|
404 |
* In all other cases it will be a raw vector with a "content-type" attribute (if specified in the headers) */
|
| - |
|
405 |
static SEXP parse_request_body(httpd_conn_t *c) {
|
| - |
|
406 |
if (!c || !c->body) return R_NilValue;
|
| - |
|
407 |
|
| - |
|
408 |
if (c->attr & CONTENT_FORM_UENC) { /* URL encoded form - return parsed form */
|
| - |
|
409 |
c->body[c->content_length] = 0; /* the body is guaranteed to have an extra byte for the termination */
|
| - |
|
410 |
return parse_query(c->body);
|
| - |
|
411 |
} else { /* something else - pass it as a raw vector */
|
| - |
|
412 |
SEXP res = PROTECT(Rf_allocVector(RAWSXP, c->content_length));
|
| - |
|
413 |
if (c->content_length)
|
| - |
|
414 |
memcpy(RAW(res), c->body, c->content_length);
|
| - |
|
415 |
if (c->content_type) /* attach the content type so it can be interpreted */
|
| - |
|
416 |
setAttrib(res, install("content-type"), mkString(c->content_type));
|
| - |
|
417 |
UNPROTECT(1);
|
| - |
|
418 |
return res;
|
| - |
|
419 |
}
|
| - |
|
420 |
}
|
| - |
|
421 |
|
| 386 |
#ifdef WIN32
|
422 |
#ifdef WIN32
|
| 387 |
/* on Windows we have to guarantee that process_request is performed
|
423 |
/* on Windows we have to guarantee that process_request is performed
|
| 388 |
* on the main thread, so we have to dispatch it through a message */
|
424 |
* on the main thread, so we have to dispatch it through a message */
|
| 389 |
static void process_request_main_thread(httpd_conn_t *c);
|
425 |
static void process_request_main_thread(httpd_conn_t *c);
|
| 390 |
|
426 |
|
| Line 420... |
Line 456... |
| 420 |
if (*s) {
|
456 |
if (*s) {
|
| 421 |
*(s++) = 0;
|
457 |
*(s++) = 0;
|
| 422 |
query = s;
|
458 |
query = s;
|
| 423 |
}
|
459 |
}
|
| 424 |
uri_decode(c->url); /* decode the path part */
|
460 |
uri_decode(c->url); /* decode the path part */
|
| 425 |
{ /* construct try(httpd(url, query), silent=TRUE) */
|
461 |
{ /* construct "try(httpd(url, query, body), silent=TRUE)" */
|
| 426 |
SEXP sTrue = PROTECT(ScalarLogical(TRUE));
|
462 |
SEXP sTrue = PROTECT(ScalarLogical(TRUE));
|
| - |
|
463 |
SEXP y, x = PROTECT(lang3(
|
| - |
|
464 |
install("try"),
|
| - |
|
465 |
LCONS(install("httpd"),
|
| 427 |
SEXP y, x = PROTECT(lang3(install("try"), LCONS(install("httpd"), CONS(mkString(c->url), CONS(query ? parse_query(query) : R_NilValue, R_NilValue))), sTrue));
|
466 |
list3(mkString(c->url), query ? parse_query(query) : R_NilValue, parse_request_body(c))),
|
| - |
|
467 |
sTrue));
|
| 428 |
SET_TAG(CDR(CDR(x)), install("silent"));
|
468 |
SET_TAG(CDR(CDR(x)), install("silent"));
|
| 429 |
DBG(Rprintf("eval(try(httpd('%s'),silent=TRUE))\n", c->url));
|
469 |
DBG(Rprintf("eval(try(httpd('%s'),silent=TRUE))\n", c->url));
|
| - |
|
470 |
|
| - |
|
471 |
/* evaluate the above in the tools namespace */
|
| 430 |
x = PROTECT(eval(x, R_FindNamespace(mkString("tools"))));
|
472 |
x = PROTECT(eval(x, R_FindNamespace(mkString("tools"))));
|
| 431 |
|
473 |
|
| 432 |
/* the result is expected to have one of the following forms:
|
474 |
/* the result is expected to have one of the following forms:
|
| 433 |
|
475 |
|
| 434 |
a) character vector of length 1 => error (possibly from try),
|
476 |
a) character vector of length 1 => error (possibly from try),
|
| Line 515... |
Line 557... |
| 515 |
fseek(f, 0, SEEK_SET);
|
557 |
fseek(f, 0, SEEK_SET);
|
| 516 |
sprintf(buf, "\r\nContent-length: %ld\r\n\r\n", fsz);
|
558 |
sprintf(buf, "\r\nContent-length: %ld\r\n\r\n", fsz);
|
| 517 |
send_response(c->sock, buf, strlen(buf));
|
559 |
send_response(c->sock, buf, strlen(buf));
|
| 518 |
if (c->method != METHOD_HEAD) {
|
560 |
if (c->method != METHOD_HEAD) {
|
| 519 |
fbuf = (char*) malloc(32768);
|
561 |
fbuf = (char*) malloc(32768);
|
| - |
|
562 |
if (fbuf) {
|
| 520 |
while (fsz > 0 && !feof(f)) {
|
563 |
while (fsz > 0 && !feof(f)) {
|
| 521 |
int rd = (fsz > 32768) ? 32768 : fsz;
|
564 |
int rd = (fsz > 32768) ? 32768 : fsz;
|
| 522 |
if (fread(fbuf, 1, rd, f) != rd) {
|
565 |
if (fread(fbuf, 1, rd, f) != rd) {
|
| 523 |
free(fbuf);
|
566 |
free(fbuf);
|
| 524 |
UNPROTECT(3);
|
567 |
UNPROTECT(3);
|
| 525 |
c->attr |= CONNECTION_CLOSE;
|
568 |
c->attr |= CONNECTION_CLOSE;
|
| 526 |
return;
|
569 |
return;
|
| - |
|
570 |
}
|
| - |
|
571 |
send_response(c->sock, fbuf, rd);
|
| - |
|
572 |
fsz -= rd;
|
| 527 |
}
|
573 |
}
|
| 528 |
send_response(c->sock, fbuf, rd);
|
574 |
free(fbuf);
|
| - |
|
575 |
} else { /* allocation error - get out */
|
| - |
|
576 |
UNPROTECT(3);
|
| - |
|
577 |
c->attr |= CONNECTION_CLOSE;
|
| 529 |
fsz -= rd;
|
578 |
return;
|
| 530 |
}
|
579 |
}
|
| 531 |
free(fbuf);
|
- |
|
| 532 |
}
|
580 |
}
|
| 533 |
fclose(f);
|
581 |
fclose(f);
|
| 534 |
UNPROTECT(3);
|
582 |
UNPROTECT(3);
|
| 535 |
fin_request(c);
|
583 |
fin_request(c);
|
| 536 |
return;
|
584 |
return;
|
| Line 624... |
Line 672... |
| 624 |
DBG(printf(" end of request, moving to body\n"));
|
672 |
DBG(printf(" end of request, moving to body\n"));
|
| 625 |
if (!(c->attr & HTTP_1_0) && !(c->attr & HOST_HEADER)) { /* HTTP/1.1 mandates Host: header */
|
673 |
if (!(c->attr & HTTP_1_0) && !(c->attr & HOST_HEADER)) { /* HTTP/1.1 mandates Host: header */
|
| 626 |
send_http_response(c, " 400 Bad Request (Host: missing)\r\nConnection: close\r\n\r\n");
|
674 |
send_http_response(c, " 400 Bad Request (Host: missing)\r\nConnection: close\r\n\r\n");
|
| 627 |
remove_worker(c);
|
675 |
remove_worker(c);
|
| 628 |
return;
|
676 |
return;
|
| 629 |
|
- |
|
| 630 |
}
|
677 |
}
|
| 631 |
if (c->attr & CONTENT_LENGTH) {
|
678 |
if (c->attr & CONTENT_LENGTH && c->content_length) {
|
| 632 |
if (c->content_length)
|
679 |
c->body = (char*) malloc(c->content_length + 1); /* allocate an extra termination byte */
|
| 633 |
c->body = (char*) malloc(c->content_length);
|
680 |
if (!c->body) { /* uh oh - out of memory - refuse */
|
| - |
|
681 |
send_http_response(c, " 413 Request Entity Too Large (request body too big)\r\nConnection: close\r\n\r\n");
|
| - |
|
682 |
remove_worker(c);
|
| - |
|
683 |
return;
|
| - |
|
684 |
}
|
| 634 |
}
|
685 |
}
|
| 635 |
c->body_pos = 0;
|
686 |
c->body_pos = 0;
|
| 636 |
c->part = PART_BODY;
|
687 |
c->part = PART_BODY;
|
| 637 |
if (s[0] == '\r') s++;
|
688 |
if (s[0] == '\r') s++;
|
| 638 |
s++;
|
689 |
s++;
|
| Line 651... |
Line 702... |
| 651 |
return;
|
702 |
return;
|
| 652 |
}
|
703 |
}
|
| 653 |
/* keep-alive - reset the worker so it can process a new request */
|
704 |
/* keep-alive - reset the worker so it can process a new request */
|
| 654 |
if (c->url) { free(c->url); c->url = NULL; }
|
705 |
if (c->url) { free(c->url); c->url = NULL; }
|
| 655 |
if (c->body) { free(c->body); c->body = NULL; }
|
706 |
if (c->body) { free(c->body); c->body = NULL; }
|
| - |
|
707 |
if (c->content_type) { free(c->content_type); c->content_type = NULL; }
|
| 656 |
c->body_pos = 0;
|
708 |
c->body_pos = 0;
|
| 657 |
c->method = 0;
|
709 |
c->method = 0;
|
| 658 |
c->part = PART_REQUEST;
|
710 |
c->part = PART_REQUEST;
|
| 659 |
c->attr = 0;
|
711 |
c->attr = 0;
|
| 660 |
c->content_length = 0;
|
712 |
c->content_length = 0;
|
| Line 719... |
Line 771... |
| 719 |
DBG(printf("header '%s' => '%s'\n", bol, k));
|
771 |
DBG(printf("header '%s' => '%s'\n", bol, k));
|
| 720 |
if (!strcmp(bol, "content-length")) {
|
772 |
if (!strcmp(bol, "content-length")) {
|
| 721 |
c->attr |= CONTENT_LENGTH;
|
773 |
c->attr |= CONTENT_LENGTH;
|
| 722 |
c->content_length = atoi(k);
|
774 |
c->content_length = atoi(k);
|
| 723 |
}
|
775 |
}
|
| - |
|
776 |
if (!strcmp(bol, "content-type")) {
|
| - |
|
777 |
char *l = k;
|
| - |
|
778 |
while (*l) { if (*l >= 'A' && *l <= 'Z') *l |= 0x20; l++; }
|
| - |
|
779 |
c->attr |= CONTENT_TYPE;
|
| - |
|
780 |
if (c->content_type) free(c->content_type);
|
| - |
|
781 |
c->content_type = strdup(k);
|
| - |
|
782 |
if (!strncmp(k, "application/x-www-form-urlencoded", 33))
|
| - |
|
783 |
c->attr |= CONTENT_FORM_UENC;
|
| - |
|
784 |
}
|
| 724 |
if (!strcmp(bol, "host"))
|
785 |
if (!strcmp(bol, "host"))
|
| 725 |
c->attr |= HOST_HEADER;
|
786 |
c->attr |= HOST_HEADER;
|
| 726 |
if (!strcmp(bol, "connection")) {
|
787 |
if (!strcmp(bol, "connection")) {
|
| 727 |
char *l = k;
|
788 |
char *l = k;
|
| 728 |
while (*l) { if (*l >= 'A' && *l <= 'Z') *l |= 0x20; l++; }
|
789 |
while (*l) { if (*l >= 'A' && *l <= 'Z') *l |= 0x20; l++; }
|
| Line 758... |
Line 819... |
| 758 |
return;
|
819 |
return;
|
| 759 |
}
|
820 |
}
|
| 760 |
/* keep-alive - reset the worker so it can process a new request */
|
821 |
/* keep-alive - reset the worker so it can process a new request */
|
| 761 |
if (c->url) { free(c->url); c->url = NULL; }
|
822 |
if (c->url) { free(c->url); c->url = NULL; }
|
| 762 |
if (c->body) { free(c->body); c->body = NULL; }
|
823 |
if (c->body) { free(c->body); c->body = NULL; }
|
| - |
|
824 |
if (c->content_type) { free(c->content_type); c->content_type = NULL; }
|
| 763 |
c->line_pos = 0; c->body_pos = 0;
|
825 |
c->line_pos = 0; c->body_pos = 0;
|
| 764 |
c->method = 0;
|
826 |
c->method = 0;
|
| 765 |
c->part = PART_REQUEST;
|
827 |
c->part = PART_REQUEST;
|
| 766 |
c->attr = 0;
|
828 |
c->attr = 0;
|
| 767 |
c->content_length = 0;
|
829 |
c->content_length = 0;
|
| Line 793... |
Line 855... |
| 793 |
c->line_pos -= sh;
|
855 |
c->line_pos -= sh;
|
| 794 |
}
|
856 |
}
|
| 795 |
/* keep-alive - reset the worker so it can process a new request */
|
857 |
/* keep-alive - reset the worker so it can process a new request */
|
| 796 |
if (c->url) { free(c->url); c->url = NULL; }
|
858 |
if (c->url) { free(c->url); c->url = NULL; }
|
| 797 |
if (c->body) { free(c->body); c->body = NULL; }
|
859 |
if (c->body) { free(c->body); c->body = NULL; }
|
| - |
|
860 |
if (c->content_type) { free(c->content_type); c->content_type = NULL; }
|
| 798 |
c->body_pos = 0;
|
861 |
c->body_pos = 0;
|
| 799 |
c->method = 0;
|
862 |
c->method = 0;
|
| 800 |
c->part = PART_REQUEST;
|
863 |
c->part = PART_REQUEST;
|
| 801 |
c->attr = 0;
|
864 |
c->attr = 0;
|
| 802 |
c->content_length = 0;
|
865 |
c->content_length = 0;
|
| 803 |
return;
|
866 |
return;
|
| 804 |
}
|
867 |
}
|
| 805 |
}
|
868 |
}
|
| 806 |
n = recv(c->sock, c->line_buf + c->line_pos, LINE_BUF_SIZE - c->line_pos - 1, 0);
|
869 |
n = recv(c->sock, c->line_buf + c->line_pos, LINE_BUF_SIZE - c->line_pos - 1, 0);
|
| 807 |
if (n < 0) { /* error, scrape this worker */
|
870 |
if (n < 0) { /* error, scrap this worker */
|
| 808 |
remove_worker(c);
|
871 |
remove_worker(c);
|
| 809 |
return;
|
872 |
return;
|
| 810 |
}
|
873 |
}
|
| 811 |
if (n == 0) { /* connection closed -> try to process and then remove */
|
874 |
if (n == 0) { /* connection closed -> try to process and then remove */
|
| 812 |
process_request(c);
|
875 |
process_request(c);
|