Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

1447 lines (1377 sloc) 47.81 kb
/*
* Copyright (c) 2007, OmniTI Computer Consulting, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name OmniTI Computer Consulting, Inc. nor the names
* of its contributors may be used to endorse or promote products
* derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "noit_defines.h"
#include "noit_http.h"
#include "utils/noit_str.h"
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#include <zlib.h>
#include <sys/mman.h>
#include <libxml/tree.h>
#define REQ_PAT "\r\n\r\n"
#define REQ_PATSIZE 4
#define HEADER_CONTENT_LENGTH "content-length"
#define HEADER_EXPECT "expect"
NOIT_HOOK_IMPL(http_request_log,
(noit_http_session_ctx *ctx),
void *, closure,
(void *closure, noit_http_session_ctx *ctx),
(closure,ctx))
struct noit_http_connection {
eventer_t e;
int needs_close;
};
struct noit_http_request {
struct bchain *first_input; /* The start of the input chain */
struct bchain *last_input; /* The end of the input chain */
struct bchain *current_input; /* The point of the input where we */
size_t current_offset; /* analyzing. */
enum { NOIT_HTTP_REQ_HEADERS = 0,
NOIT_HTTP_REQ_EXPECT,
NOIT_HTTP_REQ_PAYLOAD } state;
struct bchain *current_request_chain;
noit_boolean has_payload;
int64_t content_length;
int64_t content_length_read;
char *method_str;
char *uri_str;
char *protocol_str;
noit_hash_table querystring;
u_int32_t opts;
noit_http_method method;
noit_http_protocol protocol;
noit_hash_table headers;
noit_boolean complete;
struct timeval start_time;
char *orig_qs;
};
struct noit_http_response {
noit_http_protocol protocol;
int status_code;
char *status_reason;
noit_hash_table headers;
struct bchain *leader; /* serialization of status line and headers */
u_int32_t output_options;
struct bchain *output; /* data is pushed in here */
struct bchain *output_raw; /* internally transcoded here for output */
size_t output_raw_offset; /* tracks our offset */
noit_boolean output_started; /* locks the options and leader */
/* and possibly output. */
noit_boolean closed; /* set by _end() */
noit_boolean complete; /* complete, drained and disposable */
size_t bytes_written; /* tracks total bytes written */
z_stream *gzip;
};
struct noit_http_session_ctx {
noit_atomic32_t ref_cnt;
int64_t drainage;
int max_write;
noit_http_connection conn;
noit_http_request req;
noit_http_response res;
noit_http_dispatch_func dispatcher;
void *dispatcher_closure;
acceptor_closure_t *ac;
};
static noit_log_stream_t http_debug = NULL;
static noit_log_stream_t http_io = NULL;
static noit_log_stream_t http_access = NULL;
static const char gzip_header[10] =
{ '\037', '\213', Z_DEFLATED, 0, 0, 0, 0, 0, 0, 0x03 };
#define CTX_ADD_HEADER(a,b) \
noit_hash_replace(&ctx->res.headers, \
strdup(a), strlen(a), strdup(b), free, free)
static const char _hexchars[16] =
{'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
static void inplace_urldecode(char *c) {
char *o = c;
while(*c) {
if(*c == '%') {
int i, ord = 0;
for(i = 1; i < 3; i++) {
if(c[i] >= '0' && c[i] <= '9') ord = (ord << 4) | (c[i] - '0');
else if (c[i] >= 'a' && c[i] <= 'f') ord = (ord << 4) | (c[i] - 'a' + 0xa);
else if (c[i] >= 'A' && c[i] <= 'F') ord = (ord << 4) | (c[i] - 'A' + 0xa);
else break;
}
if(i==3) {
*((unsigned char *)o++) = ord;
c+=3;
continue;
}
}
*o++ = *c++;
}
*o = '\0';
}
struct bchain *bchain_alloc(size_t size, int line) {
struct bchain *n;
n = malloc(size + offsetof(struct bchain, _buff));
/*noitL(noit_error, "bchain_alloc(%p) : %d\n", n, line);*/
if(!n) return NULL;
n->type = BCHAIN_INLINE;
n->prev = n->next = NULL;
n->start = n->size = 0;
n->allocd = size;
n->buff = n->_buff;
return n;
}
struct bchain *bchain_mmap(int fd, size_t len, int flags, off_t offset) {
struct bchain *n;
void *buff;
buff = mmap(NULL, len, PROT_READ, flags, fd, offset);
if(buff == MAP_FAILED) return NULL;
n = bchain_alloc(0, 0);
n->type = BCHAIN_MMAP;
n->buff = buff;
n->size = len;
n->allocd = len;
return n;
}
void bchain_free(struct bchain *b, int line) {
/*noitL(noit_error, "bchain_free(%p) : %d\n", b, line);*/
if(b->type == BCHAIN_MMAP) {
munmap(b->buff, b->allocd);
}
free(b);
}
#define ALLOC_BCHAIN(s) bchain_alloc(s, __LINE__)
#define FREE_BCHAIN(a) bchain_free(a, __LINE__)
#define RELEASE_BCHAIN(a) do { \
while(a) { \
struct bchain *__b; \
__b = a; \
a = __b->next; \
bchain_free(__b, __LINE__); \
} \
} while(0)
struct bchain *bchain_from_data(const void *d, size_t size) {
struct bchain *n;
n = ALLOC_BCHAIN(size);
if(!n) return NULL;
memcpy(n->buff, d, size);
n->size = size;
return n;
}
noit_http_request *
noit_http_session_request(noit_http_session_ctx *ctx) {
return &ctx->req;
}
noit_http_response *
noit_http_session_response(noit_http_session_ctx *ctx) {
return &ctx->res;
}
noit_http_connection *
noit_http_session_connection(noit_http_session_ctx *ctx) {
return &ctx->conn;
}
void
noit_http_session_set_dispatcher(noit_http_session_ctx *ctx,
int (*d)(noit_http_session_ctx *), void *dc) {
ctx->dispatcher = d;
ctx->dispatcher_closure = dc;
}
void *noit_http_session_dispatcher_closure(noit_http_session_ctx *ctx) {
return ctx->dispatcher_closure;
}
void noit_http_session_trigger(noit_http_session_ctx *ctx, int state) {
if(ctx->conn.e) eventer_trigger(ctx->conn.e, state);
}
uint32_t noit_http_session_ref_cnt(noit_http_session_ctx *ctx) {
return ctx->ref_cnt;
}
uint32_t noit_http_session_ref_dec(noit_http_session_ctx *ctx) {
return noit_atomic_dec32(&ctx->ref_cnt);
}
uint32_t noit_http_session_ref_inc(noit_http_session_ctx *ctx) {
return noit_atomic_inc32(&ctx->ref_cnt);
}
eventer_t noit_http_connection_event(noit_http_connection *conn) {
return conn->e;
}
void noit_http_request_start_time(noit_http_request *req, struct timeval *t) {
memcpy(t, &req->start_time, sizeof(*t));
}
const char *noit_http_request_uri_str(noit_http_request *req) {
return req->uri_str;
}
const char *noit_http_request_method_str(noit_http_request *req) {
return req->method_str;
}
const char *noit_http_request_protocol_str(noit_http_request *req) {
return req->protocol_str;
}
size_t noit_http_request_content_length(noit_http_request *req) {
return req->content_length;
}
const char *noit_http_request_querystring(noit_http_request *req, const char *k) {
void *vv;
const char *v = NULL;
if(noit_hash_retrieve(&req->querystring, k, strlen(k), &vv))
v = vv;
return v;
}
noit_hash_table *noit_http_request_querystring_table(noit_http_request *req) {
return &req->querystring;
}
noit_hash_table *noit_http_request_headers_table(noit_http_request *req) {
return &req->headers;
}
noit_boolean noit_http_response_closed(noit_http_response *res) {
return res->closed;
}
noit_boolean noit_http_response_complete(noit_http_response *res) {
return res->complete;
}
size_t noit_http_response_bytes_written(noit_http_response *res) {
return res->bytes_written;
}
static noit_http_method
_method_enum(const char *s) {
switch(*s) {
case 'G':
if(!strcasecmp(s, "GET")) return NOIT_HTTP_GET;
break;
case 'H':
if(!strcasecmp(s, "HEAD")) return NOIT_HTTP_HEAD;
break;
case 'P':
if(!strcasecmp(s, "POST")) return NOIT_HTTP_POST;
break;
default:
break;
}
return NOIT_HTTP_OTHER;
}
static noit_http_protocol
_protocol_enum(const char *s) {
if(!strcasecmp(s, "HTTP/1.1")) return NOIT_HTTP11;
if(!strcasecmp(s, "HTTP/1.0")) return NOIT_HTTP10;
return NOIT_HTTP09;
}
static noit_boolean
_fixup_bchain(struct bchain *b) {
/* make sure lines (CRLF terminated) don't cross chain boundaries */
while(b) {
struct bchain *f;
int start_in_b, end_in_f;
size_t new_size;
const char *str_in_f;
start_in_b = b->start;
if(b->size > 2) {
if(memcmp(b->buff + b->start + b->size - 2, "\r\n", 2) == 0) {
b = b->next;
continue;
}
start_in_b = b->start + b->size - 3; /* we already checked -2 */
while(start_in_b >= b->start) {
if(b->buff[start_in_b] == '\r' && b->buff[start_in_b+1] == '\n') {
start_in_b += 2;
break;
}
start_in_b--;
}
}
/* start_in_b points to the beginning of the string we need to build
* into a new buffer.
*/
f = b->next;
if(!f) return noit_false; /* Nothing left, can't complete the line */
str_in_f = strnstrn("\r\n", 2, f->buff + f->start, f->size);
if(!str_in_f) return noit_false; /* nothing in next chain -- too long */
str_in_f += 2;
end_in_f = (str_in_f - f->buff - f->start);
new_size = end_in_f + (b->start + b->size - start_in_b);
if(new_size > DEFAULT_BCHAINSIZE) return noit_false; /* string too long */
f = ALLOC_BCHAIN(new_size);
f->prev = b;
f->next = b->next;
f->start = 0;
f->size = new_size;
memcpy(f->buff, b->buff + start_in_b, b->start + b->size - start_in_b);
memcpy(f->buff + b->start + b->size - start_in_b,
f->buff + f->start, end_in_f);
f->next->prev = f;
f->prev->next = f;
f->prev->size -= start_in_b - b->start;
f->next->size -= end_in_f;
f->next->start += end_in_f;
b = f->next; /* skip f, we know it is right */
}
return noit_true;
}
static noit_boolean
_extract_header(char *l, const char **n, const char **v) {
*n = NULL;
if(*l == ' ' || *l == '\t') {
while(*l == ' ' || *l == '\t') l++;
*v = l;
return noit_true;
}
*n = l;
while(*l != ':' && *l) { *l = tolower(*l); l++; }
if(!*l) return noit_false;
*v = l+1;
/* Right trim the name */
*l-- = '\0';
while(*l == ' ' || *l == '\t') *l-- = '\0';
while(**v == ' ' || **v == '\t') (*v)++;
return noit_true;
}
static void
noit_http_log_request(noit_http_session_ctx *ctx) {
char ip[64], timestr[64];
double time_ms;
struct tm *tm, tbuf;
time_t now;
struct timeval end_time, diff;
if(ctx->req.start_time.tv_sec == 0) return;
if(http_request_log_hook_invoke(ctx) != NOIT_HOOK_CONTINUE) return;
gettimeofday(&end_time, NULL);
now = end_time.tv_sec;
tm = gmtime_r(&now, &tbuf);
strftime(timestr, sizeof(timestr), "%d/%b/%Y:%H:%M:%S -0000", tm);
sub_timeval(end_time, ctx->req.start_time, &diff);
time_ms = diff.tv_sec * 1000 + (double)diff.tv_usec / 1000.0;
noit_convert_sockaddr_to_buff(ip, sizeof(ip), &ctx->ac->remote.remote_addr);
noitL(http_access, "%s - - [%s] \"%s %s%s%s %s\" %d %llu %.3f\n",
ip, timestr,
ctx->req.method_str, ctx->req.uri_str,
ctx->req.orig_qs ? "?" : "", ctx->req.orig_qs ? ctx->req.orig_qs : "",
ctx->req.protocol_str,
ctx->res.status_code,
(long long unsigned)ctx->res.bytes_written,
time_ms);
}
static int
_http_perform_write(noit_http_session_ctx *ctx, int *mask) {
int len, tlen = 0;
size_t attempt_write_len;
struct bchain **head, *b;
choose_bucket:
head = ctx->res.leader ? &ctx->res.leader : &ctx->res.output_raw;
b = *head;
if(!ctx->conn.e) return 0;
#if 0
if(ctx->res.output_started == noit_false) return EVENTER_EXCEPTION;
#endif
if(!b) {
if(ctx->res.closed) ctx->res.complete = noit_true;
*mask = EVENTER_EXCEPTION;
return tlen;
}
if(ctx->res.output_raw_offset >= b->size) {
*head = b->next;
FREE_BCHAIN(b);
b = *head;
if(b) b->prev = NULL;
ctx->res.output_raw_offset = 0;
goto choose_bucket;
}
attempt_write_len = b->size - ctx->res.output_raw_offset;
attempt_write_len = MIN(attempt_write_len, ctx->max_write);
len = ctx->conn.e->opset->
write(ctx->conn.e->fd,
b->buff + b->start + ctx->res.output_raw_offset,
attempt_write_len, mask, ctx->conn.e);
if(len == -1 && errno == EAGAIN) {
*mask |= EVENTER_EXCEPTION;
return tlen;
}
if(len == -1) {
/* socket error */
ctx->res.complete = noit_true;
ctx->conn.needs_close = noit_true;
noit_http_log_request(ctx);
*mask |= EVENTER_EXCEPTION;
return -1;
}
noitL(http_io, " http_write(%d) => %d [\n%.*s\n]\n", ctx->conn.e->fd,
len, len, b->buff + b->start + ctx->res.output_raw_offset);
ctx->res.output_raw_offset += len;
ctx->res.bytes_written += len;
tlen += len;
goto choose_bucket;
}
static noit_boolean
noit_http_request_finalize_headers(noit_http_request *req, noit_boolean *err) {
int start;
void *vval;
const char *mstr, *last_name = NULL;
struct bchain *b;
if(req->state != NOIT_HTTP_REQ_HEADERS) return noit_false;
if(!req->current_input) req->current_input = req->first_input;
if(!req->current_input) return noit_false;
if(req->start_time.tv_sec == 0) gettimeofday(&req->start_time, NULL);
restart:
while(req->current_input->prev &&
(req->current_offset < (req->current_input->start + REQ_PATSIZE - 1))) {
int inset;
/* cross bucket */
if(req->current_input == req->last_input &&
req->current_offset >= (req->last_input->start + req->last_input->size))
return noit_false;
req->current_offset++;
inset = req->current_offset - req->current_input->start;
if(memcmp(req->current_input->buff + req->current_input->start,
REQ_PAT + (REQ_PATSIZE - inset), inset) == 0 &&
memcmp(req->current_input->prev->buff +
req->current_input->prev->start +
req->current_input->prev->size - REQ_PATSIZE + inset,
REQ_PAT + inset,
REQ_PATSIZE - inset) == 0) goto match;
}
start = MAX(req->current_offset - REQ_PATSIZE, req->current_input->start);
mstr = strnstrn(REQ_PAT, REQ_PATSIZE,
req->current_input->buff + start,
req->current_input->size -
(start - req->current_input->start));
if(!mstr && req->current_input->next) {
req->current_input = req->current_input->next;
req->current_offset = req->current_input->start;
goto restart;
}
if(!mstr) return noit_false;
req->current_offset = mstr - req->current_input->buff + REQ_PATSIZE;
match:
req->current_request_chain = req->first_input;
noitL(http_debug, " noit_http_request_finalize : match(%d in %d)\n",
(int)(req->current_offset - req->current_input->start),
(int)req->current_input->size);
if(req->current_offset <
req->current_input->start + req->current_input->size) {
/* There are left-overs */
int lsize = req->current_input->size - req->current_offset;
noitL(http_debug, " noit_http_request_finalize -- leftovers: %d\n", lsize);
req->first_input = ALLOC_BCHAIN(lsize);
req->first_input->prev = NULL;
req->first_input->next = req->current_input->next;
req->first_input->start = 0;
req->first_input->size = lsize;
memcpy(req->first_input->buff,
req->current_input->buff + req->current_offset,
req->first_input->size);
req->current_input->size -= lsize;
if(req->last_input == req->current_input)
req->last_input = req->first_input;
else
FREE_BCHAIN(req->current_input);
}
else {
req->first_input = req->last_input = NULL;
}
req->current_input = NULL;
req->current_offset = 0;
/* Now we need to dissect the current_request_chain into an HTTP request */
/* First step: make sure that no line crosses a chain boundary by
* inserting new chains as necessary.
*/
if(!_fixup_bchain(req->current_request_chain)) {
*err = noit_true;
return noit_false;
}
/* Second step is to parse out the request itself */
for(b = req->current_request_chain; b; b = b->next) {
char *curr_str, *next_str;
b->buff[b->start + b->size - 2] = '\0';
curr_str = b->buff + b->start;
do {
next_str = strstr(curr_str, "\r\n");
if(next_str) {
*((char *)next_str) = '\0';
next_str += 2;
}
if(req->method_str && *curr_str == '\0')
break; /* our CRLFCRLF... end of req */
#define FAIL do { *err = noit_true; return noit_false; } while(0)
if(!req->method_str) { /* request line */
req->method_str = (char *)curr_str;
req->uri_str = strchr(curr_str, ' ');
if(!req->uri_str) FAIL;
*(req->uri_str) = '\0';
req->uri_str++;
req->protocol_str = strchr(req->uri_str, ' ');
if(!req->protocol_str) FAIL;
*(req->protocol_str) = '\0';
req->protocol_str++;
req->method = _method_enum(req->method_str);
req->protocol = _protocol_enum(req->protocol_str);
req->opts |= NOIT_HTTP_CLOSE;
if(req->protocol == NOIT_HTTP11) req->opts |= NOIT_HTTP_CHUNKED;
}
else { /* request headers */
const char *name, *value;
if(_extract_header(curr_str, &name, &value) == noit_false) FAIL;
if(!name && !last_name) FAIL;
if(!strcmp(name ? name : last_name, "accept-encoding")) {
if(strstr(value, "gzip")) req->opts |= NOIT_HTTP_GZIP;
if(strstr(value, "deflate")) req->opts |= NOIT_HTTP_DEFLATE;
}
if(name)
noit_hash_replace(&req->headers, name, strlen(name), (void *)value,
NULL, NULL);
else {
struct bchain *b;
const char *prefix = NULL;
int l1, l2;
noit_hash_retr_str(&req->headers, last_name, strlen(last_name),
&prefix);
if(!prefix) FAIL;
l1 = strlen(prefix);
l2 = strlen(value);
b = ALLOC_BCHAIN(l1 + l2 + 2);
b->next = req->current_request_chain;
b->next->prev = b;
req->current_request_chain = b;
b->size = l1 + l2 + 2;
memcpy(b->buff, prefix, l1);
b->buff[l1] = ' ';
memcpy(b->buff + l1 + 1, value, l2);
b->buff[l1 + 1 + l2] = '\0';
noit_hash_replace(&req->headers, last_name, strlen(last_name),
b->buff, NULL, NULL);
}
if(name) last_name = name;
}
curr_str = next_str;
} while(next_str);
}
/* headers are done... we could need to read a payload */
if(noit_hash_retrieve(&req->headers,
HEADER_CONTENT_LENGTH,
sizeof(HEADER_CONTENT_LENGTH)-1, &vval)) {
const char *val = vval;
req->has_payload = noit_true;
req->content_length = strtoll(val, NULL, 10);
}
if(noit_hash_retrieve(&req->headers, HEADER_EXPECT,
sizeof(HEADER_EXPECT)-1, &vval)) {
const char *val = vval;
if(strncmp(val, "100-", 4) || /* Bad expect header */
req->has_payload == noit_false) /* expect, but no content length */
FAIL;
/* We need to tell the client to "go-ahead" -- HTTP sucks */
req->state = NOIT_HTTP_REQ_EXPECT;
return noit_false;
}
if(req->content_length > 0) {
/* switch modes... let's go read the payload */
req->state = NOIT_HTTP_REQ_PAYLOAD;
return noit_false;
}
req->complete = noit_true;
return noit_true;
}
void
noit_http_process_querystring(noit_http_request *req) {
char *cp, *interest, *brk = NULL;
cp = strchr(req->uri_str, '?');
if(!cp) return;
*cp++ = '\0';
req->orig_qs = strdup(cp);
for (interest = strtok_r(cp, "&", &brk);
interest;
interest = strtok_r(NULL, "&", &brk)) {
char *eq;
eq = strchr(interest, '=');
if(!eq) {
inplace_urldecode(interest);
noit_hash_store(&req->querystring, interest, strlen(interest), NULL);
}
else {
*eq++ = '\0';
inplace_urldecode(interest);
inplace_urldecode(eq);
noit_hash_store(&req->querystring, interest, strlen(interest), eq);
}
}
}
static noit_boolean
noit_http_request_finalize_payload(noit_http_request *req, noit_boolean *err) {
req->complete = noit_true;
return noit_true;
}
static noit_boolean
noit_http_request_finalize(noit_http_request *req, noit_boolean *err) {
if(req->state == NOIT_HTTP_REQ_HEADERS)
if(noit_http_request_finalize_headers(req, err)) return noit_true;
if(req->state == NOIT_HTTP_REQ_EXPECT) return noit_false;
if(req->state == NOIT_HTTP_REQ_PAYLOAD)
if(noit_http_request_finalize_payload(req, err)) return noit_true;
return noit_false;
}
static int
noit_http_complete_request(noit_http_session_ctx *ctx, int mask) {
struct bchain *in;
noit_boolean rv, err = noit_false;
if(mask & EVENTER_EXCEPTION) {
full_error:
ctx->conn.e->opset->close(ctx->conn.e->fd, &mask, ctx->conn.e);
ctx->conn.e = NULL;
return 0;
}
if(ctx->req.complete == noit_true) return EVENTER_EXCEPTION;
/* We could have a complete request in the tail of a previous request */
rv = noit_http_request_finalize(&ctx->req, &err);
if(rv == noit_true) return EVENTER_WRITE | EVENTER_EXCEPTION;
if(err == noit_true) goto full_error;
while(1) {
int len;
in = ctx->req.last_input;
if(!in) {
in = ctx->req.first_input = ctx->req.last_input =
ALLOC_BCHAIN(DEFAULT_BCHAINSIZE);
if(!in) goto full_error;
}
if(in->size > 0 && /* we've read something */
DEFAULT_BCHAINMINREAD > BCHAIN_SPACE(in) && /* we'd like read more */
DEFAULT_BCHAINMINREAD < DEFAULT_BCHAINSIZE) { /* and we can */
in->next = ctx->req.last_input =
ALLOC_BCHAIN(DEFAULT_BCHAINSIZE);
in->next->prev = in;
in = in->next;
if(!in) goto full_error;
}
len = ctx->conn.e->opset->read(ctx->conn.e->fd,
in->buff + in->start + in->size,
in->allocd - in->size - in->start,
&mask, ctx->conn.e);
noitL(http_debug, " noit_http -> read(%d) = %d\n", ctx->conn.e->fd, len);
noitL(http_io, " noit_http:read(%d) => %d [\n%.*s\n]\n", ctx->conn.e->fd, len, len, in->buff + in->start + in->size);
if(len == -1 && errno == EAGAIN) return mask;
if(len <= 0) goto full_error;
if(len > 0) in->size += len;
rv = noit_http_request_finalize(&ctx->req, &err);
if(len == -1 || err == noit_true) goto full_error;
if(ctx->req.state == NOIT_HTTP_REQ_EXPECT) {
const char *expect;
ctx->req.state = NOIT_HTTP_REQ_PAYLOAD;
assert(ctx->res.leader == NULL);
expect = "HTTP/1.1 100 Continue\r\n\r\n";
ctx->res.leader = bchain_from_data(expect, strlen(expect));
_http_perform_write(ctx, &mask);
ctx->req.complete = noit_true;
if(ctx->res.leader != NULL) return mask;
}
if(rv == noit_true) return mask | EVENTER_WRITE | EVENTER_EXCEPTION;
}
/* Not reached:
* return EVENTER_READ | EVENTER_EXCEPTION;
*/
}
noit_boolean
noit_http_session_prime_input(noit_http_session_ctx *ctx,
const void *data, size_t len) {
if(ctx->req.first_input != NULL) return noit_false;
if(len > DEFAULT_BCHAINSIZE) return noit_false;
ctx->req.first_input = ctx->req.last_input =
ALLOC_BCHAIN(DEFAULT_BCHAINSIZE);
memcpy(ctx->req.first_input->buff, data, len);
ctx->req.first_input->size = len;
return noit_true;
}
void
noit_http_request_release(noit_http_session_ctx *ctx) {
noit_hash_destroy(&ctx->req.querystring, NULL, NULL);
noit_hash_destroy(&ctx->req.headers, NULL, NULL);
/* If we expected a payload, we expect a trailing \r\n */
if(ctx->req.has_payload) {
int drained, mask;
ctx->drainage = ctx->req.content_length - ctx->req.content_length_read;
/* best effort, we'll drain it before the next request anyway */
drained = noit_http_session_req_consume(ctx, NULL, ctx->drainage, &mask);
ctx->drainage -= drained;
}
RELEASE_BCHAIN(ctx->req.current_request_chain);
if(ctx->req.orig_qs) free(ctx->req.orig_qs);
memset(&ctx->req.state, 0,
sizeof(ctx->req) - (unsigned long)&(((noit_http_request *)0)->state));
}
void
noit_http_response_release(noit_http_session_ctx *ctx) {
noit_hash_destroy(&ctx->res.headers, free, free);
if(ctx->res.status_reason) free(ctx->res.status_reason);
RELEASE_BCHAIN(ctx->res.leader);
RELEASE_BCHAIN(ctx->res.output);
RELEASE_BCHAIN(ctx->res.output_raw);
if(ctx->res.gzip) {
deflateEnd(ctx->res.gzip);
free(ctx->res.gzip);
}
memset(&ctx->res, 0, sizeof(ctx->res));
}
void
noit_http_ctx_session_release(noit_http_session_ctx *ctx) {
if(noit_atomic_dec32(&ctx->ref_cnt) == 0) {
noit_http_request_release(ctx);
if(ctx->req.first_input) RELEASE_BCHAIN(ctx->req.first_input);
noit_http_response_release(ctx);
free(ctx);
}
}
void
noit_http_ctx_acceptor_free(void *v) {
noit_http_ctx_session_release((noit_http_session_ctx *)v);
}
int
noit_http_session_req_consume(noit_http_session_ctx *ctx,
void *buf, size_t len, int *mask) {
size_t bytes_read = 0;
/* We attempt to consume from the first_input */
struct bchain *in, *tofree;
noitL(http_debug, " ... noit_http_session_req_consume(%d) %d of %d\n",
ctx->conn.e->fd, (int)len,
(int)(ctx->req.content_length - ctx->req.content_length_read));
len = MIN(len, ctx->req.content_length - ctx->req.content_length_read);
while(bytes_read < len) {
int crlen = 0;
in = ctx->req.first_input;
while(in && bytes_read < len) {
int partial_len = MIN(in->size, len - bytes_read);
if(buf) memcpy((char *)buf+bytes_read, in->buff+in->start, partial_len);
bytes_read += partial_len;
ctx->req.content_length_read += partial_len;
noitL(http_debug, " ... filling %d bytes (read through %d/%d)\n",
(int)bytes_read, (int)ctx->req.content_length_read,
(int)ctx->req.content_length);
in->start += partial_len;
in->size -= partial_len;
if(in->size == 0) {
tofree = in;
ctx->req.first_input = in = in->next;
tofree->next = NULL;
RELEASE_BCHAIN(tofree);
if(in == NULL) {
ctx->req.last_input = NULL;
noitL(http_debug, " ... noit_http_session_req_consume = %d\n",
(int)bytes_read);
return bytes_read;
}
}
}
while(bytes_read + crlen < len) {
int rlen;
in = ctx->req.last_input;
if(!in)
in = ctx->req.first_input = ctx->req.last_input =
ALLOC_BCHAIN(DEFAULT_BCHAINSIZE);
else if(in->start + in->size >= in->allocd) {
in->next = ALLOC_BCHAIN(DEFAULT_BCHAINSIZE);
in = ctx->req.last_input = in->next;
}
/* pull next chunk */
if(ctx->conn.e == NULL) return -1;
rlen = ctx->conn.e->opset->read(ctx->conn.e->fd,
in->buff + in->start + in->size,
in->allocd - in->size - in->start,
mask, ctx->conn.e);
noitL(http_debug, " noit_http -> read(%d) = %d\n", ctx->conn.e->fd, rlen);
noitL(http_io, " noit_http:read(%d) => %d [\n%.*s\n]\n", ctx->conn.e->fd, rlen, rlen, in->buff + in->start + in->size);
if(rlen == -1 && errno == EAGAIN) {
/* We'd block to read more, but we have data,
* so do a short read */
if(ctx->req.first_input && ctx->req.first_input->size) break;
/* We've got nothing... */
noitL(http_debug, " ... noit_http_session_req_consume = -1 (EAGAIN)\n");
return -1;
}
if(rlen <= 0) {
noitL(http_debug, " ... noit_http_session_req_consume = -1 (error)\n");
return -1;
}
in->size += rlen;
crlen += rlen;
}
}
/* NOT REACHED */
return bytes_read;
}
int
noit_http_session_drive(eventer_t e, int origmask, void *closure,
struct timeval *now, int *done) {
noit_http_session_ctx *ctx = closure;
int rv = 0;
int mask = origmask;
if(origmask & EVENTER_EXCEPTION)
goto abort_drive;
/* Drainage -- this is as nasty as it sounds
* The last request could have unread upload content, we would have
* noted that in noit_http_request_release.
*/
noitL(http_debug, " -> noit_http_session_drive(%d) [%x]\n", e->fd, origmask);
while(ctx->drainage > 0) {
int len;
noitL(http_debug, " ... draining last request(%d)\n", e->fd);
len = noit_http_session_req_consume(ctx, NULL, ctx->drainage, &mask);
if(len == -1 && errno == EAGAIN) {
noitL(http_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, mask);
return mask;
}
if(len <= 0) goto abort_drive;
ctx->drainage -= len;
}
next_req:
if(ctx->req.complete != noit_true) {
int maybe_write_mask;
noitL(http_debug, " -> noit_http_complete_request(%d)\n", e->fd);
mask = noit_http_complete_request(ctx, origmask);
noitL(http_debug, " <- noit_http_complete_request(%d) = %d\n",
e->fd, mask);
_http_perform_write(ctx, &maybe_write_mask);
if(ctx->conn.e == NULL) goto release;
if(ctx->req.complete != noit_true) {
noitL(http_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd,
mask|maybe_write_mask);
return mask | maybe_write_mask;
}
noitL(http_debug, "HTTP start request (%s)\n", ctx->req.uri_str);
noit_http_process_querystring(&ctx->req);
inplace_urldecode(ctx->req.uri_str);
}
/* only dispatch if the response is not closed */
if(ctx->res.closed == noit_false) {
noitL(http_debug, " -> dispatch(%d)\n", e->fd);
rv = ctx->dispatcher(ctx);
noitL(http_debug, " <- dispatch(%d) = %d\n", e->fd, rv);
}
_http_perform_write(ctx, &mask);
if(ctx->res.complete == noit_true &&
ctx->conn.e &&
ctx->conn.needs_close == noit_true) {
abort_drive:
noit_http_log_request(ctx);
if(ctx->conn.e) {
ctx->conn.e->opset->close(ctx->conn.e->fd, &mask, ctx->conn.e);
ctx->conn.e = NULL;
}
goto release;
}
if(ctx->res.complete == noit_true) {
noit_http_log_request(ctx);
noit_http_request_release(ctx);
noit_http_response_release(ctx);
}
if(ctx->req.complete == noit_false) goto next_req;
if(ctx->conn.e) {
noitL(http_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, mask|rv);
return mask | rv;
}
noitL(http_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, 0);
goto abort_drive;
release:
*done = 1;
/* We're about to release, unhook us from the acceptor_closure so we
* don't get double freed */
if(ctx->ac->service_ctx == ctx) ctx->ac->service_ctx = NULL;
noit_http_ctx_session_release(ctx);
noitL(http_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, 0);
return 0;
}
noit_http_session_ctx *
noit_http_session_ctx_new(noit_http_dispatch_func f, void *c, eventer_t e,
acceptor_closure_t *ac) {
noit_http_session_ctx *ctx;
ctx = calloc(1, sizeof(*ctx));
ctx->ref_cnt = 1;
ctx->req.complete = noit_false;
ctx->conn.e = e;
ctx->max_write = DEFAULT_MAXWRITE;
ctx->dispatcher = f;
ctx->dispatcher_closure = c;
ctx->ac = ac;
return ctx;
}
noit_boolean
noit_http_response_status_set(noit_http_session_ctx *ctx,
int code, const char *reason) {
if(ctx->res.output_started == noit_true) return noit_false;
ctx->res.protocol = ctx->req.protocol;
if(code < 100 || code > 999) return noit_false;
ctx->res.status_code = code;
if(ctx->res.status_reason) free(ctx->res.status_reason);
ctx->res.status_reason = strdup(reason);
return noit_true;
}
noit_boolean
noit_http_response_header_set(noit_http_session_ctx *ctx,
const char *name, const char *value) {
if(ctx->res.output_started == noit_true) return noit_false;
noit_hash_replace(&ctx->res.headers, strdup(name), strlen(name),
strdup(value), free, free);
return noit_true;
}
noit_boolean
noit_http_response_option_set(noit_http_session_ctx *ctx, u_int32_t opt) {
if(ctx->res.output_started == noit_true) return noit_false;
/* transfer and content encodings only allowed in HTTP/1.1 */
if(ctx->res.protocol != NOIT_HTTP11 &&
(opt & NOIT_HTTP_CHUNKED))
return noit_false;
if(ctx->res.protocol != NOIT_HTTP11 &&
(opt & (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)))
return noit_false;
if(((ctx->res.output_options | opt) &
(NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)) ==
(NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE))
return noit_false;
/* Check out "accept" set */
if(!(opt & ctx->req.opts)) return noit_false;
ctx->res.output_options |= opt;
if(ctx->res.output_options & NOIT_HTTP_CHUNKED)
CTX_ADD_HEADER("Transfer-Encoding", "chunked");
if(ctx->res.output_options & (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)) {
CTX_ADD_HEADER("Vary", "Accept-Encoding");
if(ctx->res.output_options & NOIT_HTTP_GZIP)
CTX_ADD_HEADER("Content-Encoding", "gzip");
else if(ctx->res.output_options & NOIT_HTTP_DEFLATE)
CTX_ADD_HEADER("Content-Encoding", "deflate");
}
if(ctx->res.output_options & NOIT_HTTP_CLOSE) {
CTX_ADD_HEADER("Connection", "close");
ctx->conn.needs_close = noit_true;
}
return noit_true;
}
noit_boolean
noit_http_response_append(noit_http_session_ctx *ctx,
const void *b, size_t l) {
struct bchain *o;
int boff = 0;
if(ctx->res.closed == noit_true) return noit_false;
if(ctx->res.output_started == noit_true &&
!(ctx->res.output_options & (NOIT_HTTP_CLOSE | NOIT_HTTP_CHUNKED)))
return noit_false;
if(!ctx->res.output)
assert(ctx->res.output = ALLOC_BCHAIN(DEFAULT_BCHAINSIZE));
o = ctx->res.output;
while(o->next) o = o->next;
while(l > 0) {
if(o->allocd == o->start + o->size) {
/* Filled up, need another */
o->next = ALLOC_BCHAIN(DEFAULT_BCHAINSIZE);
o->next->prev = o->next;
o = o->next;
}
if(o->allocd > o->start + o->size) {
int tocopy = MIN(l, o->allocd - o->start - o->size);
memcpy(o->buff + o->start + o->size, (const char *)b + boff, tocopy);
o->size += tocopy;
boff += tocopy;
l -= tocopy;
}
}
return noit_true;
}
noit_boolean
noit_http_response_append_bchain(noit_http_session_ctx *ctx,
struct bchain *b) {
struct bchain *o;
if(ctx->res.closed == noit_true) return noit_false;
if(ctx->res.output_started == noit_true &&
!(ctx->res.output_options & (NOIT_HTTP_CHUNKED | NOIT_HTTP_CLOSE)))
return noit_false;
if(!ctx->res.output)
ctx->res.output = b;
else {
o = ctx->res.output;
while(o->next) o = o->next;
o->allocd = o->size; /* so we know it is full */
o->next = b;
b->prev = o;
}
return noit_true;
}
noit_boolean
noit_http_response_append_mmap(noit_http_session_ctx *ctx,
int fd, size_t len, int flags, off_t offset) {
struct bchain *n;
n = bchain_mmap(fd, len, flags, offset);
if(n == NULL) return noit_false;
return noit_http_response_append_bchain(ctx, n);
}
static int casesort(const void *a, const void *b) {
return strcasecmp(*((const char **)a), *((const char **)b));
}
static int
_http_construct_leader(noit_http_session_ctx *ctx) {
int len = 0, tlen;
struct bchain *b;
const char *protocol_str;
const char *key, *value;
int klen, i;
const char **keys;
noit_hash_iter iter = NOIT_HASH_ITER_ZERO;
assert(!ctx->res.leader);
ctx->res.leader = b = ALLOC_BCHAIN(DEFAULT_BCHAINSIZE);
protocol_str = ctx->res.protocol == NOIT_HTTP11 ?
"HTTP/1.1" :
(ctx->res.protocol == NOIT_HTTP10 ?
"HTTP/1.0" :
"HTTP/0.9");
tlen = snprintf(b->buff, b->allocd, "%s %03d %s\r\n",
protocol_str, ctx->res.status_code,
ctx->res.status_reason ? ctx->res.status_reason : "unknown");
if(tlen < 0) return -1;
len = b->size = tlen;
#define CTX_LEADER_APPEND(s, slen) do { \
if(b->size + slen > DEFAULT_BCHAINSIZE) { \
b->next = ALLOC_BCHAIN(DEFAULT_BCHAINSIZE); \
assert(b->next); \
b->next->prev = b; \
b = b->next; \
} \
assert(DEFAULT_BCHAINSIZE >= b->size + slen); \
memcpy(b->buff + b->start + b->size, s, slen); \
b->size += slen; \
} while(0)
keys = alloca(sizeof(*keys)*ctx->res.headers.size);
i = 0;
while(noit_hash_next_str(&ctx->res.headers, &iter,
&key, &klen, &value)) {
keys[i++] = key;
}
qsort(keys, i, sizeof(*keys), casesort);
for(i=0;i<ctx->res.headers.size;i++) {
int vlen;
key = keys[i];
klen = strlen(key);
noit_hash_retr_str(&ctx->res.headers, key, klen, &value);
vlen = strlen(value);
CTX_LEADER_APPEND(key, klen);
CTX_LEADER_APPEND(": ", 2);
CTX_LEADER_APPEND(value, vlen);
CTX_LEADER_APPEND("\r\n", 2);
}
CTX_LEADER_APPEND("\r\n", 2);
return len;
}
static int memgzip2(noit_http_response *res, Bytef *dest, uLongf *destLen,
const Bytef *source, uLong sourceLen, int level,
int deflate_option, noit_boolean *done) {
int err, skip=0, expect = Z_OK;
if(!res->gzip) {
res->gzip = calloc(1, sizeof(*res->gzip));
err = deflateInit2(res->gzip, level, Z_DEFLATED, -15, 9,
Z_DEFAULT_STRATEGY);
if (err != Z_OK) {
noitL(noit_error, "memgzip2() -> deflateInit2: %d\n", err);
return err;
}
memcpy(dest, gzip_header, sizeof(gzip_header));
skip = sizeof(gzip_header);
*destLen -= skip;
}
res->gzip->next_in = (Bytef*)source;
res->gzip->avail_in = (uInt)sourceLen;
res->gzip->next_out = dest + skip;
res->gzip->avail_out = (uInt)*destLen;
if ((uLong)res->gzip->avail_out != *destLen) return Z_BUF_ERROR;
err = deflate(res->gzip, deflate_option);
if(deflate_option == Z_FINISH) expect = Z_STREAM_END;
if (err != expect) {
noitL(noit_error, "memgzip2() -> deflate: got %d, need %d\n", err, expect);
deflateEnd(res->gzip);
free(res->gzip);
res->gzip = NULL;
return err == Z_OK ? Z_BUF_ERROR : err;
}
if(done) *done = (err == Z_STREAM_END) ? noit_true : noit_false;
*destLen = (*destLen - res->gzip->avail_out) + skip;
return Z_OK;
}
static noit_boolean
_http_encode_chain(noit_http_response *res,
struct bchain *out, void *inbuff, int inlen,
noit_boolean final, noit_boolean *done) {
int opts = res->output_options;
/* implement gzip and deflate! */
if(done && final) *done = noit_true;
if(opts & NOIT_HTTP_GZIP) {
uLongf olen;
int err;
olen = out->allocd - out->start - 2; /* leave 2 for the \r\n */
err = memgzip2(res, (Bytef *)(out->buff + out->start), &olen,
(Bytef *)(inbuff), (uLong)inlen,
9, final ? Z_FINISH : Z_NO_FLUSH, done);
if(Z_OK != err) {
noitL(noit_error, "zlib compress2 error %d\n", err);
return noit_false;
}
out->size += olen;
}
else if(opts & NOIT_HTTP_DEFLATE) {
uLongf olen;
olen = out->allocd - out->start - 2; /* leave 2 for the \r\n */
if(Z_OK != compress2((Bytef *)(out->buff + out->start), &olen,
(Bytef *)(inbuff), (uLong)inlen,
9)) {
noitL(noit_error, "zlib compress2 error\n");
return noit_false;
}
out->size += olen;
}
else {
/* leave 2 for the \r\n */
if(inlen > out->allocd - out->start - 2) return noit_false;
memcpy(out->buff + out->start, inbuff, inlen);
out->size += inlen;
}
return noit_true;
}
struct bchain *
noit_http_process_output_bchain(noit_http_session_ctx *ctx,
struct bchain *in) {
struct bchain *out;
int ilen, maxlen = in->size, hexlen;
int opts = ctx->res.output_options;
if(in->type == BCHAIN_MMAP &&
0 == (opts & (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE | NOIT_HTTP_CHUNKED))) {
out = ALLOC_BCHAIN(0);
out->buff = in->buff;
out->type = in->type;
out->size = in->size;
out->allocd = in->allocd;
in->type = BCHAIN_INLINE;
return out;
}
/* a chunked header looks like: hex*\r\ndata\r\n */
/* let's assume that content never gets "larger" */
if(opts & NOIT_HTTP_GZIP) maxlen = deflateBound(NULL, in->size);
else if(opts & NOIT_HTTP_DEFLATE) maxlen = compressBound(in->size);
/* So, the link size is the len(data) + 4 + ceil(log(len(data))/log(16)) */
ilen = maxlen;
hexlen = 0;
while(ilen) { ilen >>= 4; hexlen++; }
if(hexlen == 0) hexlen = 1;
out = ALLOC_BCHAIN(hexlen + 4 + maxlen);
/* if we're chunked, let's give outselved hexlen + 2 prefix space */
if(opts & NOIT_HTTP_CHUNKED) out->start = hexlen + 2;
if(_http_encode_chain(&ctx->res, out,in->buff + in->start, in->size,
noit_false, NULL) == noit_false) {
free(out);
return NULL;
}
if(out->size == 0) {
FREE_BCHAIN(out);
out = ALLOC_BCHAIN(0);
}
if((out->size > 0) && (opts & NOIT_HTTP_CHUNKED)) {
ilen = out->size;
assert(out->start+out->size+2 <= out->allocd);
out->buff[out->start + out->size++] = '\r';
out->buff[out->start + out->size++] = '\n';
out->start = 0;
/* terminate */
out->size += 2;
out->buff[hexlen] = '\r';
out->buff[hexlen+1] = '\n';
/* backfill */
out->size += hexlen;
while(hexlen > 0) {
out->buff[hexlen - 1] = _hexchars[ilen & 0xf];
ilen >>= 4;
hexlen--;
}
while(out->buff[out->start] == '0') {
out->start++;
out->size--;
}
}
return out;
}
void
raw_finalize_encoding(noit_http_response *res) {
if(res->output_options & NOIT_HTTP_GZIP) {
noit_boolean finished = noit_false;
struct bchain *r = res->output_raw;
while(r && r->next) r = r->next;
while(finished == noit_false) {
int hexlen, ilen;
struct bchain *out = ALLOC_BCHAIN(DEFAULT_BCHAINSIZE);
/* The link size is the len(data) + 4 + ceil(log(len(data))/log(16)) */
ilen = out->allocd;
hexlen = 0;
while(ilen) { ilen >>= 4; hexlen++; }
if(hexlen == 0) hexlen = 1;
out->start += hexlen + 2;
if(_http_encode_chain(res, out, "", 0, noit_true,
&finished) == noit_false) {
FREE_BCHAIN(out);
break;
}
ilen = out->size;
assert(out->start+out->size+2 <= out->allocd);
out->buff[out->start + out->size++] = '\r';
out->buff[out->start + out->size++] = '\n';
out->start = 0;
/* terminate */
out->size += 2;
out->buff[hexlen] = '\r';
out->buff[hexlen+1] = '\n';
/* backfill */
out->size += hexlen;
while(hexlen > 0) {
out->buff[hexlen - 1] = _hexchars[ilen & 0xf];
ilen >>= 4;
hexlen--;
}
while(out->buff[out->start] == '0') {
out->start++;
out->size--;
}
if(r == NULL)
res->output_raw = out;
else {
r->next = out;
out->prev = r;
}
r = out;
}
deflateEnd(res->gzip);
free(res->gzip);
res->gzip = NULL;
}
}
static noit_boolean
_noit_http_response_flush(noit_http_session_ctx *ctx,
noit_boolean final,
noit_boolean update_eventer) {
struct bchain *o, *r;
int mask, rv;
if(ctx->res.closed == noit_true) return noit_false;
if(ctx->res.output_started == noit_false) {
_http_construct_leader(ctx);
ctx->res.output_started = noit_true;
}
/* encode output to output_raw */
r = ctx->res.output_raw;
while(r && r->next) r = r->next;
/* r is the last raw output link */
o = ctx->res.output;
/* o is the first output link to process */
while(o) {
struct bchain *tofree, *n;
n = noit_http_process_output_bchain(ctx, o);
if(!n) {
/* Bad, response stops here! */
noitL(noit_error, "noit_http_process_output_bchain: NULL\n");
while(o) { tofree = o; o = o->next; free(tofree); }
final = noit_true;
break;
}
if(r) {
r->next = n;
n->prev = r;
r = n;
}
else {
r = ctx->res.output_raw = n;
}
tofree = o; o = o->next; FREE_BCHAIN(tofree); /* advance and free */
}
ctx->res.output = NULL;
if(final) {
struct bchain *n;
ctx->res.closed = noit_true;
raw_finalize_encoding(&ctx->res);
/* We could have just pushed in the only block */
if(!r) r = ctx->res.output_raw;
/* Advance to the end to append out ending */
if(r) while(r->next) r = r->next;
/* Create an ending */
if(ctx->res.output_options & NOIT_HTTP_CHUNKED)
n = bchain_from_data("0\r\n\r\n", 5);
else
n = NULL;
/* Append an ending (chunked) */
if(r) {
r->next = n;
if(n) n->prev = r;
}
else {
ctx->res.output_raw = n;
}
}
rv = _http_perform_write(ctx, &mask);
if(update_eventer && ctx->conn.e) {
eventer_update(ctx->conn.e, mask);
}
if(rv < 0) return noit_false;
/* If the write fails completely, the event will not be closed,
* the following should not trigger the false case.
*/
return ctx->conn.e ? noit_true : noit_false;
}
noit_boolean
noit_http_response_flush(noit_http_session_ctx *ctx,
noit_boolean final) {
return _noit_http_response_flush(ctx, final, noit_true);
}
noit_boolean
noit_http_response_flush_asynch(noit_http_session_ctx *ctx,
noit_boolean final) {
return _noit_http_response_flush(ctx, final, noit_false);
}
noit_boolean
noit_http_response_end(noit_http_session_ctx *ctx) {
if(!noit_http_response_flush(ctx, noit_true)) return noit_false;
return noit_true;
}
/* Helper functions */
static int
noit_http_write_xml(void *vctx, const char *buffer, int len) {
if(noit_http_response_append((noit_http_session_ctx *)vctx, buffer, len))
return len;
return -1;
}
static int
noit_http_close_xml(void *vctx) {
noit_http_response_end((noit_http_session_ctx *)vctx);
return 0;
}
void
noit_http_response_xml(noit_http_session_ctx *ctx, xmlDocPtr doc) {
xmlOutputBufferPtr out;
xmlCharEncodingHandlerPtr enc;
enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
out = xmlOutputBufferCreateIO(noit_http_write_xml,
noit_http_close_xml,
ctx, enc);
xmlSaveFormatFileTo(out, doc, "utf8", 1);
}
void
noit_http_init() {
http_debug = noit_log_stream_find("debug/http");
http_access = noit_log_stream_find("http/access");
http_io = noit_log_stream_find("http/io");
}
Jump to Line
Something went wrong with that request. Please try again.