Commit f2dbad19c684012a6ac235a253349ffe049668db
1 parent
45b4b354
another try with a shared memory based ringbuffer...this performs well for keep-…
…alive sessions but is much slower without. actually i am not sure why but most likely the shared memory setup is quite expensive. @TODO: make a profiling.
Showing
35 changed files
with
557 additions
and
223 deletions
| 1 | -2012-02-18 20:12:27 +0100 Georg Hopp | |
| 1 | +2012-02-19 11:35:15 +0100 Georg Hopp | |
| 2 | 2 | |
| 3 | - * lots of changes but primarily change the request parser to use a ringbuffer. The ringbuffer is implemented using the shared memory trick. (HEAD, master) | |
| 3 | + * another try with a shared memory based ringbuffer...this performs well for keep-alive sessions but is much slower without. actually i am not sure why but most likely the shared memory setup is quite expensive. @TODO: make a profiling. (HEAD, ringbuffer) | |
| 4 | 4 | |
| 5 | 5 | 2012-02-15 12:30:33 +0100 Georg Hopp |
| 6 | 6 | |
| 7 | - * some more cleanups in the server code. Removing not needed header includes (origin/master, origin/HEAD) | |
| 7 | + * some more cleanups in the server code. Removing not needed header includes | |
| 8 | 8 | |
| 9 | 9 | 2012-02-15 12:17:39 +0100 Georg Hopp |
| 10 | 10 | ... | ... |
| ... | ... | @@ -4,9 +4,10 @@ |
| 4 | 4 | #include "class.h" |
| 5 | 5 | #include "http/request.h" |
| 6 | 6 | #include "http/message/queue.h" |
| 7 | -#include "ringbuffer.h" | |
| 7 | +#include "cbuf.h" | |
| 8 | + | |
| 9 | +#define HTTP_REQUEST_PARSER_BUFFER_MAX 8192 | |
| 8 | 10 | |
| 9 | -#define HTTP_REQUEST_PARSER_MAX_BUF 131072 | |
| 10 | 11 | |
| 11 | 12 | /** |
| 12 | 13 | * limits to stop invalid requests from killing |
| ... | ... | @@ -31,7 +32,7 @@ typedef enum e_HttpRequestState { |
| 31 | 32 | |
| 32 | 33 | |
| 33 | 34 | CLASS(HttpRequestParser) { |
| 34 | - Ringbuffer buffer; | |
| 35 | + Cbuf buffer; | |
| 35 | 36 | |
| 36 | 37 | HttpMessageQueue request_queue; |
| 37 | 38 | HttpRequest cur_request; |
| ... | ... | @@ -41,6 +42,7 @@ CLASS(HttpRequestParser) { |
| 41 | 42 | |
| 42 | 43 | ssize_t httpRequestParserRead(HttpRequestParser, int); |
| 43 | 44 | ssize_t httpRequestParserParse(HttpRequestParser, int); |
| 45 | +void httpRequestParserGetBody(HttpRequestParser); | |
| 44 | 46 | |
| 45 | 47 | ssize_t httpRequestParserGetRequestLine(HttpRequestParser, char *); |
| 46 | 48 | ssize_t httpRequestParserGetHeader(HttpRequestParser, char *); | ... | ... |
| 1 | 1 | #ifndef __HTTP_RESPONSE_WRITER_H__ |
| 2 | 2 | #define __HTTP_RESPONSE_WRITER_H__ |
| 3 | 3 | |
| 4 | +#include <sys/types.h> | |
| 5 | + | |
| 4 | 6 | #include "class.h" |
| 5 | 7 | #include "http/response.h" |
| 6 | 8 | #include "http/message/queue.h" |
| 9 | +#include "cbuf.h" | |
| 7 | 10 | |
| 8 | 11 | #define RESPONSE_WRITER_MAX_BUF 131072 |
| 9 | 12 | |
| ... | ... | @@ -15,17 +18,15 @@ typedef enum e_HttpResponseState { |
| 15 | 18 | } HttpResponseState; |
| 16 | 19 | |
| 17 | 20 | CLASS(HttpResponseWriter) { |
| 18 | - char * pipe; | |
| 19 | - | |
| 20 | - size_t nheader; | |
| 21 | - size_t nbuffer; | |
| 22 | - size_t written; | |
| 23 | - size_t pstart; | |
| 24 | - size_t pend; | |
| 21 | + Cbuf buffer; | |
| 25 | 22 | |
| 26 | 23 | HttpMessageQueue response_queue; |
| 27 | 24 | HttpResponse cur_response; |
| 28 | 25 | |
| 26 | + size_t nheader; | |
| 27 | + size_t nbody; | |
| 28 | + size_t written; | |
| 29 | + | |
| 29 | 30 | HttpResponseState state; |
| 30 | 31 | }; |
| 31 | 32 | ... | ... |
| ... | ... | @@ -15,7 +15,7 @@ CLASS(Sock) { |
| 15 | 15 | |
| 16 | 16 | void socketConnect(Sock this, const char * addr); |
| 17 | 17 | void socketListen(Sock this, int backlog); |
| 18 | -Sock socketAccept(Sock this, char (*remoteAddr)[]); | |
| 18 | +Sock socketAccept(Sock this, char (*remoteAddr)[16]); | |
| 19 | 19 | |
| 20 | 20 | #endif // __SOCKET_H__ |
| 21 | 21 | ... | ... |
| ... | ... | @@ -9,6 +9,11 @@ RB = ringbuffer.c ringbuffer/rb_read.c |
| 9 | 9 | SOCKET = socket.c socket/accept.c socket/connect.c socket/listen.c |
| 10 | 10 | SERVER = server.c server/run.c server/close_conn.c |
| 11 | 11 | LOGGER = logger.c logger/stderr.c logger/syslog.c |
| 12 | +CB = cbuf.c cbuf/read.c cbuf/write.c \ | |
| 13 | + cbuf/get_line.c cbuf/set_data.c cbuf/get_data.c \ | |
| 14 | + cbuf/addr_index.c cbuf/get_free.c cbuf/get_read.c cbuf/get_write.c \ | |
| 15 | + cbuf/inc_read.c cbuf/inc_write.c cbuf/is_empty.c cbuf/memchr.c \ | |
| 16 | + cbuf/skip_non_alpha.c | |
| 12 | 17 | MSG = http/message.c http/message/queue.c http/message/has_keep_alive.c \ |
| 13 | 18 | http/message/header_size_get.c http/message/header_to_string.c |
| 14 | 19 | REQ = http/request.c |
| ... | ... | @@ -31,7 +36,7 @@ bin_PROGRAMS = testserver |
| 31 | 36 | |
| 32 | 37 | testserver_SOURCES = testserver.c \ |
| 33 | 38 | $(IFACE) $(CLASS) $(SOCKET) $(SERVER) $(LOGGER) $(MSG) $(REQ) \ |
| 34 | - $(WRITER) $(RESP) $(HEADER) $(PARSER) $(WORKER) $(RB) \ | |
| 39 | + $(WRITER) $(RESP) $(HEADER) $(PARSER) $(WORKER) $(CB) \ | |
| 35 | 40 | signalHandling.c daemonize.c |
| 36 | 41 | testserver_CFLAGS = -Wall -I ../include/ |
| 37 | 42 | testserver_LDFLAGS = -lrt | ... | ... |
src/cbuf.c
0 → 100644
| 1 | +#define _POSIX_SOURCE | |
| 2 | +#define _POSIX_C_SOURCE 200112L | |
| 3 | + | |
| 4 | +#include <sys/types.h> | |
| 5 | +#include <sys/stat.h> | |
| 6 | +#include <sys/mman.h> | |
| 7 | +#include <stdarg.h> | |
| 8 | +#include <stdlib.h> | |
| 9 | +#include <stdio.h> | |
| 10 | +#include <unistd.h> | |
| 11 | +#include <fcntl.h> | |
| 12 | + | |
| 13 | +#include "class.h" | |
| 14 | +#include "interface/class.h" | |
| 15 | + | |
| 16 | +#include "cbuf.h" | |
| 17 | + | |
| 18 | + | |
| 19 | +static void dtor(void*); | |
| 20 | + | |
| 21 | +static | |
| 22 | +void | |
| 23 | +ctor(void * _this, va_list * params) | |
| 24 | +{ | |
| 25 | + Cbuf this = _this; | |
| 26 | + char state = 0; | |
| 27 | + char * shm_name = va_arg(*params, char*); | |
| 28 | + long psize = sysconf(_SC_PAGESIZE); | |
| 29 | + size_t size; | |
| 30 | + int shm; | |
| 31 | + | |
| 32 | + this->shm_name = malloc(strlen(shm_name) + 7 + 2); | |
| 33 | + sprintf(this->shm_name, "/%06d_%s", getpid(), shm_name); | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * align size at page boundary. | |
| 37 | + * increase as neccessary | |
| 38 | + */ | |
| 39 | + size = va_arg(*params, size_t); | |
| 40 | + size = (0 >= size)? 1 : (0 != size%psize)? (size/psize)+1 : size/psize; | |
| 41 | + this->bsize = psize * size; | |
| 42 | + | |
| 43 | + while (0 == state) { | |
| 44 | + shm = shm_open(this->shm_name, O_RDWR|O_CREAT|O_EXCL, S_IRWXU); | |
| 45 | + if (-1 == shm) { | |
| 46 | + break; | |
| 47 | + } | |
| 48 | + | |
| 49 | + if (-1 == ftruncate(shm, this->bsize)) { | |
| 50 | + break; | |
| 51 | + } | |
| 52 | + | |
| 53 | + this->data = mmap (0, this->bsize << 1, | |
| 54 | + PROT_READ|PROT_WRITE, MAP_SHARED, shm, 0); | |
| 55 | + if (this->data == MAP_FAILED) { | |
| 56 | + this->data = NULL; | |
| 57 | + break; | |
| 58 | + } | |
| 59 | + | |
| 60 | + munmap(this->data + this->bsize, this->bsize); | |
| 61 | + | |
| 62 | + this->mirror = mmap (this->data + this->bsize, this->bsize, | |
| 63 | + PROT_READ|PROT_WRITE, MAP_SHARED, shm, 0); | |
| 64 | + if (this->mirror != this->data + this->bsize) { | |
| 65 | + if (this->mirror == this->data - this->bsize) { | |
| 66 | + this->data = this->mirror; | |
| 67 | + this->mirror += this->bsize; | |
| 68 | + } | |
| 69 | + else { | |
| 70 | + this->mirror = NULL; | |
| 71 | + break; | |
| 72 | + } | |
| 73 | + } | |
| 74 | + | |
| 75 | + state = 1; | |
| 76 | + } | |
| 77 | + | |
| 78 | + if (-1 != shm) { | |
| 79 | + shm_unlink(this->shm_name); | |
| 80 | + close(shm); | |
| 81 | + } | |
| 82 | + | |
| 83 | + if (1 != state) { | |
| 84 | + dtor(this); | |
| 85 | + } | |
| 86 | +} | |
| 87 | + | |
| 88 | +static | |
| 89 | +void | |
| 90 | +dtor(void * _this) | |
| 91 | +{ | |
| 92 | + Cbuf this = _this; | |
| 93 | + | |
| 94 | + if (NULL != this->shm_name) { | |
| 95 | + free(this->shm_name); | |
| 96 | + this->shm_name = NULL; | |
| 97 | + } | |
| 98 | + | |
| 99 | + if (NULL != this->data) { | |
| 100 | + munmap(this->data, this->bsize); | |
| 101 | + this->data = NULL; | |
| 102 | + } | |
| 103 | + | |
| 104 | + if (NULL != this->mirror) { | |
| 105 | + munmap(this->mirror, this->bsize); | |
| 106 | + this->mirror = NULL; | |
| 107 | + } | |
| 108 | +} | |
| 109 | + | |
| 110 | +INIT_IFACE(Class, ctor, dtor, NULL); | |
| 111 | +CREATE_CLASS(Cbuf, NULL, IFACE(Class)); | |
| 112 | + | |
| 113 | +// vim: set ts=4 sw=4: | ... | ... |
src/cbuf/addr_index.c
0 → 100644
src/cbuf/get_data.c
0 → 100644
| 1 | +#include <sys/types.h> | |
| 2 | +#include <string.h> | |
| 3 | + | |
| 4 | +#include "cbuf.h" | |
| 5 | + | |
| 6 | +char * | |
| 7 | +cbufGetData(Cbuf this, size_t n) | |
| 8 | +{ | |
| 9 | + char * ret = cbufGetRead(this); | |
| 10 | + | |
| 11 | + if (n > this->bused) { | |
| 12 | + return -1; | |
| 13 | + } | |
| 14 | + | |
| 15 | + cbufIncRead(this, n); | |
| 16 | + return ret; | |
| 17 | +} | |
| 18 | + | |
| 19 | +// vim: set ts=4 sw=4: | ... | ... |
src/cbuf/get_free.c
0 → 100644
src/cbuf/get_line.c
0 → 100644
| 1 | +#include <sys/types.h> | |
| 2 | + | |
| 3 | +#include <string.h> | |
| 4 | + | |
| 5 | +#include "cbuf.h" | |
| 6 | + | |
| 7 | +char * | |
| 8 | +cbufGetLine(Cbuf this) | |
| 9 | +{ | |
| 10 | + char * nl = cbufMemchr(this, '\n'); | |
| 11 | + char * ret = NULL; | |
| 12 | + | |
| 13 | + if (NULL != nl) { | |
| 14 | + size_t len = cbufAddrIndex(this, nl) + 1; | |
| 15 | + | |
| 16 | + *nl = 0; | |
| 17 | + *(nl-1) = ('\r' == *(nl-1))? 0 : *(nl-1); | |
| 18 | + | |
| 19 | + ret = cbufGetRead(this); | |
| 20 | + cbufIncRead(this, len); | |
| 21 | + } | |
| 22 | + | |
| 23 | + return ret; | |
| 24 | +} | |
| 25 | + | |
| 26 | +// vim: set ts=4 sw=4: | ... | ... |
src/cbuf/get_read.c
0 → 100644
src/cbuf/get_write.c
0 → 100644
src/cbuf/inc_read.c
0 → 100644
src/cbuf/inc_write.c
0 → 100644
src/cbuf/is_empty.c
0 → 100644
src/cbuf/memchr.c
0 → 100644
src/cbuf/read.c
0 → 100644
| 1 | +#include <sys/types.h> | |
| 2 | +#include <unistd.h> | |
| 3 | +#include <errno.h> | |
| 4 | + | |
| 5 | +#include "cbuf.h" | |
| 6 | + | |
| 7 | + | |
| 8 | +ssize_t | |
| 9 | +cbufRead(Cbuf this, int fd) | |
| 10 | +{ | |
| 11 | + ssize_t rrsize = 0; | |
| 12 | + size_t rsize = cbufGetFree(this); | |
| 13 | + | |
| 14 | + if (0 == rsize) { | |
| 15 | + errno = ECBUFOVFL; | |
| 16 | + return -1; | |
| 17 | + } | |
| 18 | + | |
| 19 | + rrsize = read(fd, cbufGetWrite(this), rsize); | |
| 20 | + | |
| 21 | + switch (rrsize) { | |
| 22 | + case 0: | |
| 23 | + rrsize = -2; | |
| 24 | + // DROP THROUGH | |
| 25 | + | |
| 26 | + case -1: | |
| 27 | + break; | |
| 28 | + | |
| 29 | + default: | |
| 30 | + cbufIncWrite(this, rrsize); | |
| 31 | + break; | |
| 32 | + } | |
| 33 | + | |
| 34 | + return rrsize; | |
| 35 | +} | |
| 36 | + | |
| 37 | +// vim: set ts=4 sw=4: | ... | ... |
src/cbuf/set_data.c
0 → 100644
| 1 | +#include <sys/types.h> | |
| 2 | +#include <string.h> | |
| 3 | +#include <errno.h> | |
| 4 | + | |
| 5 | +#include "cbuf.h" | |
| 6 | + | |
| 7 | +char * | |
| 8 | +cbufSetData(Cbuf this, const void * src, size_t n) | |
| 9 | +{ | |
| 10 | + char * addr; | |
| 11 | + | |
| 12 | + if (n > cbufGetFree(this)) { | |
| 13 | + errno = ECBUFOVFL; | |
| 14 | + return -1; | |
| 15 | + } | |
| 16 | + | |
| 17 | + addr = memcpy(cbufGetWrite(this), src, n); | |
| 18 | + cbufIncWrite(this, n); | |
| 19 | + | |
| 20 | + return addr; | |
| 21 | +} | |
| 22 | + | |
| 23 | +// vim: set ts=4 sw=4: | ... | ... |
src/cbuf/skip_non_alpha.c
0 → 100644
src/cbuf/write.c
0 → 100644
| 1 | +#include <sys/types.h> | |
| 2 | +#include <unistd.h> | |
| 3 | + | |
| 4 | +#include "cbuf.h" | |
| 5 | + | |
| 6 | + | |
| 7 | +ssize_t | |
| 8 | +cbufWrite(Cbuf this, int fd) | |
| 9 | +{ | |
| 10 | + ssize_t wwsize = 0; | |
| 11 | + size_t wsize = this->bused; | |
| 12 | + | |
| 13 | + if (0 == wsize) return 0; | |
| 14 | + | |
| 15 | + wwsize = write(fd, cbufGetRead(this), wsize); | |
| 16 | + | |
| 17 | + switch (wwsize) { | |
| 18 | + case -1: | |
| 19 | + break; | |
| 20 | + | |
| 21 | + default: | |
| 22 | + cbufIncRead(this, wwsize); | |
| 23 | + break; | |
| 24 | + } | |
| 25 | + | |
| 26 | + return wwsize; | |
| 27 | +} | |
| 28 | + | |
| 29 | +// vim: set ts=4 sw=4: | ... | ... |
| 1 | 1 | #include <unistd.h> |
| 2 | -#include <string.h> | |
| 2 | +#include <stdio.h> | |
| 3 | 3 | #include <stdlib.h> |
| 4 | 4 | #include <sys/types.h> |
| 5 | 5 | |
| ... | ... | @@ -10,17 +10,20 @@ |
| 10 | 10 | #include "http/request/parser.h" |
| 11 | 11 | #include "http/message/queue.h" |
| 12 | 12 | #include "http/request.h" |
| 13 | -#include "ringbuffer.h" | |
| 13 | +#include "cbuf.h" | |
| 14 | 14 | |
| 15 | 15 | |
| 16 | 16 | static |
| 17 | 17 | void |
| 18 | 18 | ctor(void * _this, va_list * params) |
| 19 | 19 | { |
| 20 | - HttpRequestParser this = _this; | |
| 21 | - char * shm_name = va_arg(*params, char*); | |
| 20 | + HttpRequestParser this = _this; | |
| 21 | + char * id = va_arg(*params, char*); | |
| 22 | + char cbuf_id[100]; | |
| 23 | + | |
| 24 | + sprintf(cbuf_id, "%s_%s", "parser", id); | |
| 22 | 25 | |
| 23 | - this->buffer = new(Ringbuffer, shm_name, HTTP_REQUEST_LINE_MAX); | |
| 26 | + this->buffer = new(Cbuf, cbuf_id, HTTP_REQUEST_PARSER_BUFFER_MAX); | |
| 24 | 27 | this->request_queue = new(HttpMessageQueue); |
| 25 | 28 | } |
| 26 | 29 | |
| ... | ... | @@ -37,9 +40,8 @@ dtor(void * _this) |
| 37 | 40 | delete(&(this->cur_request)); |
| 38 | 41 | } |
| 39 | 42 | |
| 40 | - | |
| 41 | 43 | INIT_IFACE(Class, ctor, dtor, NULL); |
| 42 | -INIT_IFACE(StreamReader, (fptr_streamReaderRead)httpRequestParserParse); | |
| 44 | +INIT_IFACE(StreamReader, (fptr_streamReaderRead)httpRequestParserRead); | |
| 43 | 45 | CREATE_CLASS(HttpRequestParser, NULL, IFACE(Class), IFACE(StreamReader)); |
| 44 | 46 | |
| 45 | 47 | // vim: set ts=4 sw=4: | ... | ... |
| ... | ... | @@ -3,6 +3,10 @@ |
| 3 | 3 | #include "http/header.h" |
| 4 | 4 | #include "http/message.h" |
| 5 | 5 | #include "http/request/parser.h" |
| 6 | +#include "cbuf.h" | |
| 7 | + | |
| 8 | +#define MAX(a,b) (((a) > (b))? (a) : (b)) | |
| 9 | + | |
| 6 | 10 | |
| 7 | 11 | #define MAX(x,y) ((x) > (y) ? (x) : (y)) |
| 8 | 12 | |
| ... | ... | @@ -14,41 +18,34 @@ void |
| 14 | 18 | httpRequestParserGetBody(HttpRequestParser this) |
| 15 | 19 | { |
| 16 | 20 | HttpMessage message = (HttpMessage)(this->cur_request); |
| 17 | - char * str_nbody; | |
| 18 | - int nbody; | |
| 19 | - int len; | |
| 20 | - | |
| 21 | - str_nbody = httpHeaderGet( | |
| 22 | - &(message->header), | |
| 23 | - "Content-Length"); | |
| 24 | - | |
| 25 | - if (NULL == str_nbody) { | |
| 26 | - this->state = HTTP_REQUEST_DONE; | |
| 27 | - return -1; | |
| 28 | - } | |
| 29 | - | |
| 30 | - nbody = atoi(str_nbody); | |
| 21 | + size_t len; | |
| 31 | 22 | |
| 32 | 23 | if (0 == message->nbody) { |
| 33 | - message->type = HTTP_MESSAGE_BUFFERED; | |
| 34 | - if (0 < nbody) | |
| 35 | - message->body = malloc(nbody); | |
| 24 | + char * nbody = httpHeaderGet( | |
| 25 | + &(message->header), | |
| 26 | + "Content-Length"); | |
| 27 | + | |
| 28 | + if (NULL == nbody) { | |
| 29 | + this->state = HTTP_REQUEST_DONE; | |
| 30 | + return; | |
| 31 | + } | |
| 32 | + else { | |
| 33 | + message->type = HTTP_MESSAGE_BUFFERED; | |
| 34 | + message->nbody = atoi(nbody); | |
| 35 | + message->body = calloc(1, message->nbody); | |
| 36 | + message->dbody = 0; | |
| 37 | + } | |
| 36 | 38 | } |
| 39 | + this->buffer->bused -= len; | |
| 37 | 40 | |
| 38 | - len = MAX(nbody - message->nbody, this->buffer->bused); | |
| 39 | - memcpy(message->body + message->nbody, | |
| 40 | - this->buffer->buffer + this->buffer->bstart, | |
| 41 | - len); | |
| 41 | + if (message->dbody < message->nbody) { | |
| 42 | + len = MAX( | |
| 43 | + message->nbody - message->dbody, | |
| 44 | + this->buffer->bused); | |
| 42 | 45 | |
| 43 | - message->nbody += len; | |
| 44 | - this->buffer->bstart += len; | |
| 45 | - if (this->buffer->bstart >= this->buffer->bsize) { | |
| 46 | - this->buffer->bstart -= this->buffer->bsize; | |
| 47 | - } | |
| 48 | - this->buffer->bused -= len; | |
| 46 | + memcpy(message->body, cbufGetData(this->buffer, len), len); | |
| 49 | 47 | |
| 50 | - if (message->nbody == nbody) { | |
| 51 | - this->state = HTTP_REQUEST_DONE; | |
| 48 | + message->dbody += len; | |
| 52 | 49 | } |
| 53 | 50 | } |
| 54 | 51 | ... | ... |
| ... | ... | @@ -7,81 +7,35 @@ |
| 7 | 7 | #include "http/message.h" |
| 8 | 8 | #include "http/request/parser.h" |
| 9 | 9 | #include "interface/class.h" |
| 10 | - | |
| 11 | -#ifndef TRUE | |
| 12 | -#define TRUE 1 | |
| 13 | -#endif | |
| 14 | - | |
| 15 | -#ifndef FALSE | |
| 16 | -#define FALSE 0 | |
| 17 | -#endif | |
| 18 | - | |
| 19 | -static | |
| 20 | -inline | |
| 21 | -char * | |
| 22 | -getLine(HttpRequestParser this) | |
| 23 | -{ | |
| 24 | - char * cr = memchr( | |
| 25 | - this->buffer->buffer + this->buffer->bstart, | |
| 26 | - '\r', | |
| 27 | - this->buffer->bused); | |
| 28 | - | |
| 29 | - char * nl = (NULL == cr)? NULL : cr + 1; | |
| 30 | - | |
| 31 | - if (NULL != cr && NULL != nl && '\n' == *nl) { | |
| 32 | - *cr = 0; | |
| 33 | - return cr; | |
| 34 | - } | |
| 35 | - | |
| 36 | - return NULL; | |
| 37 | -} | |
| 38 | - | |
| 39 | -static | |
| 40 | -inline | |
| 41 | -char | |
| 42 | -httpRequestSkip(HttpRequestParser this) | |
| 43 | -{ | |
| 44 | - while (this->buffer->bused > 0 && | |
| 45 | - ! isalpha(this->buffer->buffer[this->buffer->bstart])) { | |
| 46 | - this->buffer->bstart = (this->buffer->bstart >= this->buffer->bsize)? | |
| 47 | - 0 : this->buffer->bstart + 1; | |
| 48 | - this->buffer->bused--; | |
| 49 | - } | |
| 50 | - | |
| 51 | - return (isalpha(this->buffer->buffer[this->buffer->bstart]))? TRUE : FALSE; | |
| 52 | -} | |
| 10 | +#include "cbuf.h" | |
| 53 | 11 | |
| 54 | 12 | ssize_t |
| 55 | 13 | httpRequestParserParse(HttpRequestParser this, int fd) |
| 56 | 14 | { |
| 57 | 15 | int cont = 1; |
| 58 | - ssize_t ret; | |
| 16 | + ssize_t read; | |
| 17 | + char * line; | |
| 59 | 18 | |
| 60 | - if (0 > (ret = rbRead(this->buffer, fd))) { | |
| 61 | - cont = 0; | |
| 19 | + if(0 > (read = cbufRead(this->buffer, fd))) { | |
| 20 | + return read; | |
| 62 | 21 | } |
| 63 | 22 | |
| 64 | 23 | while (cont) { |
| 65 | 24 | switch(this->state) { |
| 66 | - char * line_end; | |
| 67 | - size_t len; | |
| 68 | - | |
| 69 | 25 | case HTTP_REQUEST_GARBAGE: |
| 70 | - if (httpRequestSkip(this)) { | |
| 26 | + cbufSkipNonAlpha(this->buffer); | |
| 27 | + if (! cbufIsEmpty(this->buffer)) { | |
| 71 | 28 | this->cur_request = new(HttpRequest); |
| 72 | 29 | this->state = HTTP_REQUEST_START; |
| 73 | 30 | } |
| 74 | - else { | |
| 75 | - cont = 0; | |
| 76 | - } | |
| 77 | 31 | break; |
| 78 | 32 | |
| 79 | 33 | case HTTP_REQUEST_START: |
| 80 | - if (NULL == (line_end = getLine(this))) { | |
| 34 | + if (NULL == (line = cbufGetLine(this->buffer))) { | |
| 81 | 35 | cont = 0; |
| 82 | 36 | break; |
| 83 | 37 | } |
| 84 | - | |
| 38 | + | |
| 85 | 39 | if (0 > httpRequestParserGetRequestLine(this, line_end)) { |
| 86 | 40 | ret = -1; |
| 87 | 41 | cont = 0; |
| ... | ... | @@ -99,7 +53,7 @@ httpRequestParserParse(HttpRequestParser this, int fd) |
| 99 | 53 | break; |
| 100 | 54 | |
| 101 | 55 | case HTTP_REQUEST_REQUEST_LINE_DONE: |
| 102 | - if (NULL == (line_end = getLine(this))) { | |
| 56 | + if (NULL == (line = cbufGetLine(this->buffer))) { | |
| 103 | 57 | cont = 0; |
| 104 | 58 | break; |
| 105 | 59 | } |
| ... | ... | @@ -127,23 +81,33 @@ httpRequestParserParse(HttpRequestParser this, int fd) |
| 127 | 81 | break; |
| 128 | 82 | |
| 129 | 83 | case HTTP_REQUEST_HEADERS_DONE: |
| 130 | - /** | |
| 131 | - * allocate memory according to content-length. | |
| 132 | - * If content length is to large reject request. | |
| 133 | - * | |
| 134 | - * @FUTURE check for multipart mime and handle it | |
| 135 | - * with temporary file. | |
| 136 | - */ | |
| 137 | - httpRequestParserGetBody(this); | |
| 84 | + { | |
| 85 | + HttpMessage message = (HttpMessage)this->cur_request; | |
| 86 | + | |
| 87 | + httpRequestParserGetBody(this); | |
| 88 | + | |
| 89 | + if (message->dbody == message->nbody) { | |
| 90 | + this->state = HTTP_REQUEST_DONE; | |
| 91 | + } | |
| 92 | + } | |
| 138 | 93 | break; |
| 139 | 94 | |
| 140 | 95 | case HTTP_REQUEST_DONE: |
| 141 | 96 | this->request_queue->msgs[(this->request_queue->nmsgs)++] = |
| 142 | 97 | (HttpMessage)this->cur_request; |
| 143 | 98 | |
| 144 | - ret = this->request_queue->nmsgs; | |
| 145 | 99 | this->cur_request = NULL; |
| 146 | 100 | |
| 101 | + /** | |
| 102 | + * dont continue loop if input buffer is empty | |
| 103 | + */ | |
| 104 | + if (cbufIsEmpty(this->buffer)) { | |
| 105 | + cont = 0; | |
| 106 | + } | |
| 107 | + | |
| 108 | + /** | |
| 109 | + * prepare for next request | |
| 110 | + */ | |
| 147 | 111 | this->state = HTTP_REQUEST_GARBAGE; |
| 148 | 112 | |
| 149 | 113 | break; | ... | ... |
| ... | ... | @@ -14,29 +14,46 @@ |
| 14 | 14 | ssize_t |
| 15 | 15 | httpRequestParserRead(HttpRequestParser this, int fd) |
| 16 | 16 | { |
| 17 | - size_t rsize; | |
| 18 | - ssize_t temp; | |
| 19 | - | |
| 20 | - this->bend = (this->bsize == this->bend)? | |
| 21 | - 0 : this->bend; | |
| 22 | - | |
| 23 | - rsize = (this->bstart <= this->bend)? | |
| 24 | - this->bsize - this->bend : | |
| 25 | - this->bstart - 1; | |
| 26 | - | |
| 27 | - if (0 >= (temp = read(fd, &(this->buffer[this->bend]), rsize))) { | |
| 28 | - /** | |
| 29 | - * this means either we had an rsize of 0 what indicates that | |
| 30 | - * the buffer ran full without any processing took place or | |
| 31 | - * the connection was terminated in some way. In both cases | |
| 32 | - * we want to terminate the connection. | |
| 33 | - */ | |
| 34 | - return (0 == temp)? -2 : -1; | |
| 35 | - } | |
| 36 | - | |
| 37 | - this->bend += temp; | |
| 38 | - | |
| 39 | - return temp; | |
| 17 | + /** | |
| 18 | + * @obsolete | |
| 19 | + */ | |
| 20 | + return -1; | |
| 21 | +// size_t remaining, chunks; | |
| 22 | +// char buffer[1024]; | |
| 23 | +// | |
| 24 | +// ssize_t size = read(fd, buffer, 1024); | |
| 25 | +// | |
| 26 | +// if (0 < size) { | |
| 27 | +// remaining = this->buffer_used % HTTP_REQUEST_PARSER_READ_CHUNK; | |
| 28 | +// remaining = HTTP_REQUEST_PARSER_READ_CHUNK - remaining; | |
| 29 | +// chunks = this->buffer_used / HTTP_REQUEST_PARSER_READ_CHUNK; | |
| 30 | +// | |
| 31 | +// /** | |
| 32 | +// * because a division always rounds down | |
| 33 | +// * chunks holds exactly the currently allocated chunks if | |
| 34 | +// * remaining equals 0 but there is no space left. | |
| 35 | +// * Else chunks holds the actually allocated amount of chunks | |
| 36 | +// * minus 1. | |
| 37 | +// * For this reason chunks always has to be increased by 1. | |
| 38 | +// */ | |
| 39 | +// chunks++; | |
| 40 | +// | |
| 41 | +// if (size >= remaining) { | |
| 42 | +// this->buffer = | |
| 43 | +// realloc(this->buffer, chunks * HTTP_REQUEST_PARSER_READ_CHUNK); | |
| 44 | +// } | |
| 45 | +// | |
| 46 | +// memcpy(this->buffer + this->buffer_used, buffer, size); | |
| 47 | +// this->buffer_used += size; | |
| 48 | +// this->buffer[this->buffer_used] = 0; | |
| 49 | +// | |
| 50 | +// size = httpRequestParserParse(this); | |
| 51 | +// } | |
| 52 | +// else { | |
| 53 | +// size = (0 == size)? -2 : size; | |
| 54 | +// } | |
| 55 | +// | |
| 56 | +// return size; | |
| 40 | 57 | } |
| 41 | 58 | |
| 42 | 59 | // vim: set ts=4 sw=4: | ... | ... |
| 1 | 1 | #include <stdlib.h> |
| 2 | +#include <stdio.h> | |
| 2 | 3 | |
| 3 | 4 | #include "class.h" |
| 4 | 5 | #include "interface/class.h" |
| ... | ... | @@ -12,7 +13,12 @@ void |
| 12 | 13 | ctor(void * _this, va_list * params) |
| 13 | 14 | { |
| 14 | 15 | HttpResponseWriter this = _this; |
| 16 | + char * id = va_arg(*params, char*); | |
| 17 | + char cbuf_id[100]; | |
| 15 | 18 | |
| 19 | + sprintf(cbuf_id, "%s_%s", "writer", id); | |
| 20 | + | |
| 21 | + this->buffer = new(Cbuf, cbuf_id, RESPONSE_WRITER_MAX_BUF); | |
| 16 | 22 | this->response_queue = new(HttpMessageQueue); |
| 17 | 23 | } |
| 18 | 24 | |
| ... | ... | @@ -23,6 +29,7 @@ dtor(void * _this) |
| 23 | 29 | HttpResponseWriter this = _this; |
| 24 | 30 | |
| 25 | 31 | delete(&(this->response_queue)); |
| 32 | + delete(&(this->buffer)); | |
| 26 | 33 | |
| 27 | 34 | if (NULL != this->cur_response) |
| 28 | 35 | delete(&(this->cur_response)); | ... | ... |
| ... | ... | @@ -11,7 +11,9 @@ |
| 11 | 11 | #include "http/message.h" |
| 12 | 12 | #include "http/response.h" |
| 13 | 13 | #include "http/response/writer.h" |
| 14 | +#include "cbuf.h" | |
| 14 | 15 | |
| 16 | +#define MIN(x,y) ((x) < (y) ? (x) : (y)) | |
| 15 | 17 | #define MAX(x,y) ((x) > (y) ? (x) : (y)) |
| 16 | 18 | #define _PSIZE(x) (MAX((x),RESPONSE_WRITER_MAX_BUF)) |
| 17 | 19 | #define PSIZE _PSIZE(this->nheader+message->nbody) |
| ... | ... | @@ -21,7 +23,6 @@ httpResponseWriterWrite(HttpResponseWriter this, int fd) |
| 21 | 23 | { |
| 22 | 24 | HttpMessageQueue respq = this->response_queue; |
| 23 | 25 | HttpMessage message = (HttpMessage)this->cur_response; |
| 24 | - ssize_t processed = (message)? 1 : 0; | |
| 25 | 26 | int cont = 1; |
| 26 | 27 | |
| 27 | 28 | while (cont) { |
| ... | ... | @@ -30,19 +31,13 @@ httpResponseWriterWrite(HttpResponseWriter this, int fd) |
| 30 | 31 | if (NULL == this->cur_response && 0 < respq->nmsgs) { |
| 31 | 32 | message = respq->msgs[0]; |
| 32 | 33 | this->cur_response = (HttpResponse)message; |
| 33 | - processed++; | |
| 34 | 34 | |
| 35 | - memmove(respq->msgs, | |
| 36 | - &(respq->msgs[1]), | |
| 37 | - sizeof(void*) * (--respq->nmsgs + 1)); | |
| 38 | - | |
| 39 | - this->nbuffer = 0; | |
| 40 | 35 | this->written = 0; |
| 41 | - this->pstart = 0; | |
| 36 | + this->nbody = 0; | |
| 42 | 37 | this->nheader = httpMessageHeaderSizeGet(message); |
| 43 | - this->pend = this->nheader; | |
| 44 | - this->pipe = malloc(PSIZE); | |
| 45 | - httpMessageHeaderToString(message, this->pipe); | |
| 38 | + | |
| 39 | + httpMessageHeaderToString(message, cbufGetWrite(this->buffer)); | |
| 40 | + cbufIncWrite(this->buffer, this->nheader); | |
| 46 | 41 | |
| 47 | 42 | this->state = HTTP_RESPONSE_WRITE; |
| 48 | 43 | } |
| ... | ... | @@ -55,57 +50,41 @@ httpResponseWriterWrite(HttpResponseWriter this, int fd) |
| 55 | 50 | /** |
| 56 | 51 | * read |
| 57 | 52 | */ |
| 58 | - if (this->nbuffer < message->nbody) { | |
| 59 | - size_t temp = 0; | |
| 60 | - size_t rsize; | |
| 61 | - | |
| 62 | - this->pend = (PSIZE == this->pend)? | |
| 63 | - 0 : this->pend; | |
| 64 | - | |
| 65 | - rsize = (this->pstart <= this->pend)? | |
| 66 | - PSIZE - this->pend : | |
| 67 | - this->pstart - 1; | |
| 53 | + if (this->nbody < message->nbody) { | |
| 54 | + size_t size = MIN( | |
| 55 | + message->nbody - this->nbody, | |
| 56 | + cbufGetFree(this->buffer)); | |
| 68 | 57 | |
| 69 | 58 | switch (message->type) { |
| 70 | 59 | case HTTP_MESSAGE_BUFFERED: |
| 71 | - temp = message->nbody - this->nbuffer; | |
| 72 | - temp = (rsize<temp)? rsize : temp; | |
| 73 | - memcpy( | |
| 74 | - &(this->pipe[this->pend]), | |
| 75 | - &(message->body[this->nbuffer]), | |
| 76 | - temp); | |
| 60 | + cbufSetData(this->buffer, | |
| 61 | + message->body + this->nbody, | |
| 62 | + size); | |
| 77 | 63 | break; |
| 78 | 64 | |
| 79 | 65 | case HTTP_MESSAGE_PIPED: |
| 80 | - temp = read( | |
| 81 | - message->handle, | |
| 82 | - &(this->pipe[this->pend]), | |
| 83 | - rsize); | |
| 66 | + size = cbufRead(this->buffer, message->handle); | |
| 84 | 67 | break; |
| 68 | + | |
| 69 | + default: | |
| 70 | + return -1; | |
| 85 | 71 | } |
| 86 | 72 | |
| 87 | - this->nbuffer += temp; | |
| 88 | - this->pend += temp; | |
| 73 | + this->nbody += size; | |
| 89 | 74 | } |
| 90 | 75 | |
| 91 | 76 | /** |
| 92 | 77 | * write |
| 93 | 78 | */ |
| 94 | 79 | { |
| 95 | - size_t wsize; | |
| 96 | - size_t temp; | |
| 97 | - | |
| 98 | - wsize = (this->pstart <= this->pend)? | |
| 99 | - this->pend - this->pstart : | |
| 100 | - PSIZE - this->pstart; | |
| 80 | + ssize_t written = cbufWrite(this->buffer, fd); | |
| 101 | 81 | |
| 102 | - temp = write(fd, &(this->pipe[this->pstart]), wsize); | |
| 103 | - | |
| 104 | - this->written += temp; | |
| 105 | - this->pstart += temp; | |
| 106 | - | |
| 107 | - this->pstart = (PSIZE == this->pstart)? | |
| 108 | - 0 : this->pstart; | |
| 82 | + if (0 <= written) { | |
| 83 | + this->written += written; | |
| 84 | + } | |
| 85 | + else { | |
| 86 | + return -1; | |
| 87 | + } | |
| 109 | 88 | } |
| 110 | 89 | |
| 111 | 90 | if (this->written == message->nbody + this->nheader) { |
| ... | ... | @@ -121,33 +100,30 @@ httpResponseWriterWrite(HttpResponseWriter this, int fd) |
| 121 | 100 | close(message->handle); |
| 122 | 101 | } |
| 123 | 102 | |
| 124 | - free(this->pipe); | |
| 103 | + this->state = HTTP_RESPONSE_GET; | |
| 125 | 104 | |
| 126 | - this->nheader = 0; | |
| 127 | - this->nbuffer = 0; | |
| 128 | - this->written = 0; | |
| 129 | - this->pstart = 0; | |
| 130 | - this->pend = 0; | |
| 105 | + memmove(respq->msgs, | |
| 106 | + &(respq->msgs[1]), | |
| 107 | + sizeof(void*) * (--respq->nmsgs + 1)); | |
| 131 | 108 | |
| 132 | 109 | if (! httpMessageHasKeepAlive(message)) { |
| 133 | 110 | /** |
| 134 | 111 | * if the message did not have the keep-alive feature |
| 135 | 112 | * we don't care about further pipelined messages and |
| 136 | - * return the to caller with a 0 indicating that the | |
| 113 | + * return to the caller with a 0 indicating that the | |
| 137 | 114 | * underlying connection should be closed. |
| 138 | 115 | */ |
| 139 | - processed = 0; | |
| 140 | - cont = 0; | |
| 116 | + delete(&this->cur_response); | |
| 117 | + return -1; | |
| 141 | 118 | } |
| 142 | 119 | |
| 143 | 120 | delete(&this->cur_response); |
| 144 | 121 | |
| 145 | - this->state = HTTP_RESPONSE_GET; | |
| 146 | 122 | break; |
| 147 | 123 | } |
| 148 | 124 | } |
| 149 | 125 | |
| 150 | - return processed; | |
| 126 | + return respq->nmsgs; | |
| 151 | 127 | } |
| 152 | 128 | |
| 153 | 129 | // vim: set ts=4 sw=4: | ... | ... |
| 1 | 1 | #include <stdlib.h> |
| 2 | 2 | #include <stdarg.h> |
| 3 | +#include <stdlib.h> | |
| 3 | 4 | #include <string.h> |
| 4 | 5 | |
| 5 | 6 | #include "class.h" |
| ... | ... | @@ -17,14 +18,13 @@ void |
| 17 | 18 | ctor(void * _this, va_list * params) |
| 18 | 19 | { |
| 19 | 20 | HttpWorker this = _this; |
| 20 | - char id[sizeof(SHMN) + 15 + 5]; | |
| 21 | + char * id = va_arg(*params, char *); | |
| 21 | 22 | |
| 22 | - this->remoteAddr = va_arg(*params, char *); | |
| 23 | - this->handle = va_arg(*params, int); | |
| 24 | - sprintf(id, SHMN "%s_%05d", this->remoteAddr, this->handle); | |
| 23 | + this->id = malloc(strlen(id) + 1); | |
| 24 | + strcpy(this->id, id); | |
| 25 | 25 | |
| 26 | - this->parser = new(HttpRequestParser, id); | |
| 27 | - this->writer = new(HttpResponseWriter); | |
| 26 | + this->parser = new(HttpRequestParser, this->id); | |
| 27 | + this->writer = new(HttpResponseWriter, this->id); | |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | 30 | static |
| ... | ... | @@ -33,6 +33,8 @@ dtor(void * _this) |
| 33 | 33 | { |
| 34 | 34 | HttpWorker this = _this; |
| 35 | 35 | |
| 36 | + free(this->id); | |
| 37 | + | |
| 36 | 38 | delete(&this->parser); |
| 37 | 39 | delete(&this->writer); |
| 38 | 40 | } | ... | ... |
| 1 | 1 | #include <errno.h> |
| 2 | +#include <stdio.h> | |
| 3 | + | |
| 4 | +#include "http/worker.h" | |
| 5 | + | |
| 2 | 6 | |
| 3 | 7 | #include "http/worker.h" |
| 4 | 8 | |
| ... | ... | @@ -12,11 +16,15 @@ serverHandleAccept(Server this) |
| 12 | 16 | acc = socketAccept(this->sock, &remoteAddr); |
| 13 | 17 | |
| 14 | 18 | if (-1 != acc->handle) { |
| 19 | + char id[21]; | |
| 20 | + | |
| 21 | + sprintf(id, "my_%s_%05d", remoteAddr, acc->handle); | |
| 22 | + | |
| 15 | 23 | //* save the socket handle |
| 16 | 24 | (this->conns)[acc->handle].sock = acc; |
| 17 | 25 | |
| 18 | 26 | //* clone reader |
| 19 | - (this->conns)[acc->handle].worker = new(HttpWorker, remoteAddr, acc->handle); | |
| 27 | + (this->conns)[acc->handle].worker = new(HttpWorker, id); | |
| 20 | 28 | |
| 21 | 29 | (this->fds)[this->nfds].fd = acc->handle; |
| 22 | 30 | (this->fds)[this->nfds].events = POLLIN; | ... | ... |
| ... | ... | @@ -62,12 +62,12 @@ serverRun(Server this) |
| 62 | 62 | nreads--; |
| 63 | 63 | |
| 64 | 64 | switch (serverRead(this, i)) { |
| 65 | - case -2: | |
| 66 | - case -1: | |
| 67 | - serverCloseConn(this, i); | |
| 65 | + case 0: | |
| 68 | 66 | break; |
| 69 | 67 | |
| 70 | - case 0: | |
| 68 | + case -1: | |
| 69 | + case -2: | |
| 70 | + serverCloseConn(this, i); | |
| 71 | 71 | break; |
| 72 | 72 | |
| 73 | 73 | default: |
| ... | ... | @@ -80,14 +80,24 @@ serverRun(Server this) |
| 80 | 80 | * handle writes |
| 81 | 81 | */ |
| 82 | 82 | if (0 != ((this->fds)[i].revents & POLLOUT) && 0 < nwrites) { |
| 83 | + size_t remaining; | |
| 84 | + | |
| 83 | 85 | events--; |
| 84 | 86 | nwrites--; |
| 85 | 87 | |
| 86 | - if (0 >= streamWriterWrite((this->conns)[fd].worker, fd)) { | |
| 87 | - serverCloseConn(this, i); | |
| 88 | - } | |
| 88 | + remaining = streamWriterWrite((this->conns)[fd].worker, fd); | |
| 89 | + switch(remaining) { | |
| 90 | + case -1: | |
| 91 | + serverCloseConn(this, i); | |
| 92 | + break; | |
| 93 | + | |
| 94 | + case 0: | |
| 95 | + (this->fds)[i].events &= ~POLLOUT; | |
| 96 | + break; | |
| 89 | 97 | |
| 90 | - (this->fds)[i].events &= ~POLLOUT; | |
| 98 | + default: | |
| 99 | + break; | |
| 100 | + } | |
| 91 | 101 | } |
| 92 | 102 | } |
| 93 | 103 | } | ... | ... |
| ... | ... | @@ -6,7 +6,7 @@ |
| 6 | 6 | #include "interface/logger.h" |
| 7 | 7 | |
| 8 | 8 | Sock |
| 9 | -socketAccept(Sock this, char (*remoteAddr)[]) | |
| 9 | +socketAccept(Sock this, char (*remoteAddr)[16]) | |
| 10 | 10 | { |
| 11 | 11 | Sock sock; /* Socket for client */ |
| 12 | 12 | unsigned int len; /* Length of client address data structure */ |
| ... | ... | @@ -33,7 +33,8 @@ socketAccept(Sock this, char (*remoteAddr)[]) |
| 33 | 33 | loggerLog(this->log, LOGGER_WARNING, |
| 34 | 34 | "error accepting connection: %s", strerror(errno)); |
| 35 | 35 | } else { |
| 36 | - memcpy(*remoteAddr, inet_ntoa((sock->addr).sin_addr), 15); | |
| 36 | + strcpy(*remoteAddr, inet_ntoa((sock->addr).sin_addr)); | |
| 37 | + | |
| 37 | 38 | loggerLog(this->log, LOGGER_INFO, |
| 38 | 39 | "handling client %s\n", *remoteAddr); |
| 39 | 40 | } | ... | ... |
| ... | ... | @@ -19,8 +19,8 @@ int |
| 19 | 19 | main() |
| 20 | 20 | { |
| 21 | 21 | Logger logger = new(LoggerSyslog, LOGGER_ERR); |
| 22 | - HttpWorker worker = new(HttpWorker, "", 0); | |
| 23 | - Server server = new(Server, logger, worker, 11212, SOMAXCONN); | |
| 22 | + //HttpWorker worker = new(HttpWorker); | |
| 23 | + Server server = new(Server, logger, NULL /*worker*/, 11212, SOMAXCONN); | |
| 24 | 24 | |
| 25 | 25 | struct rlimit limit = {RLIM_INFINITY, RLIM_INFINITY}; |
| 26 | 26 | setrlimit(RLIMIT_CPU, &limit); |
| ... | ... | @@ -30,7 +30,7 @@ main() |
| 30 | 30 | serverRun(server); |
| 31 | 31 | |
| 32 | 32 | delete(&server); |
| 33 | - delete(&worker); | |
| 33 | +// delete(&worker); | |
| 34 | 34 | delete(&logger); |
| 35 | 35 | |
| 36 | 36 | return 0; | ... | ... |
Please
register
or
login
to post a comment