| @ -0,0 +1,273 @@ | |||||
| #include <curl/curl.h> | |||||
| #include <kclangc.h> | |||||
| #include <netinet/in.h> | |||||
| #include <pthread.h> | |||||
| #include <signal.h> | |||||
| #include <sys/socket.h> | |||||
| static const char* VERSION = "0.0.1"; | |||||
| static sig_atomic_t misses = 0; | |||||
| static sig_atomic_t hits = 0; | |||||
| static KCDB* db; | |||||
| static KCLIST* queue; | |||||
| static int sd; | |||||
| const char* STATS_FORMAT = | |||||
| "STAT hits %d\r\n" | |||||
| "STAT misses %d\r\n" | |||||
| "STAT hit_ratio %d\r\n" | |||||
| "STAT records %d\r\n" | |||||
| "END\r\n"; | |||||
| typedef struct { | |||||
| KCLIST* list; | |||||
| pthread_mutex_t mutex; | |||||
| pthread_cond_t cond; | |||||
| int size; | |||||
| } ProxyQueue; | |||||
| static ProxyQueue requests; | |||||
| void list_shift(KCLIST* list, char* next){ | |||||
| if(kclistcount(list)){ | |||||
| size_t size; | |||||
| strcpy(next, kclistget(list, 0, &size)); | |||||
| kclistshift(list); | |||||
| } | |||||
| } | |||||
| void queue_init(ProxyQueue q){ | |||||
| q.list = kclistnew(); | |||||
| q.size = 0; | |||||
| pthread_mutex_init(&q.mutex, NULL); | |||||
| pthread_cond_init(&q.cond, NULL); | |||||
| } | |||||
| void queue_add(ProxyQueue* q, char* value){ | |||||
| pthread_mutex_lock(&q->mutex); | |||||
| //kclistpush(*(&q->list), value, strlen(value)); | |||||
| *(&q->size) += 1; | |||||
| pthread_mutex_unlock(&q->mutex); | |||||
| pthread_cond_signal(&q->cond); | |||||
| } | |||||
| void queue_get(ProxyQueue* q, char* value){ | |||||
| pthread_mutex_lock(&q->mutex); | |||||
| while(*(&q->size) == 0){ | |||||
| pthread_cond_wait(&q->cond, &q->mutex); | |||||
| } | |||||
| //list_shift(*(&q->list), value); | |||||
| *(&q->size) -= 1; | |||||
| pthread_mutex_unlock(&q->mutex); | |||||
| } | |||||
| void lower(char* word){ | |||||
| int length = strlen(word); | |||||
| int i; | |||||
| for(i = 0; i < length; ++i){ | |||||
| word[i] = tolower(word[i]); | |||||
| } | |||||
| } | |||||
| void tokenize(KCLIST* tokens, char* base){ | |||||
| char* remainder; | |||||
| char* token; | |||||
| char* ptr = base; | |||||
| while(token = strtok_r(ptr, " ", &remainder)){ | |||||
| int last; | |||||
| int len = strlen(token); | |||||
| for(last = len - 1; last >= 0; --last){ | |||||
| if(token[last] == '\n' || token[last] == '\r'){ | |||||
| token[last] = 0; | |||||
| break; | |||||
| } | |||||
| } | |||||
| lower(token); | |||||
| kclistpush(tokens, (const char*)token, strlen(token)); | |||||
| ptr = remainder; | |||||
| } | |||||
| free(token); | |||||
| } | |||||
| void* call_proxy(void* arg){ | |||||
| char next[1024]; | |||||
| printf("Waiting\r\n"); | |||||
| queue_get(&requests, next); | |||||
| printf("Calling Proxy For %s\r\n", next); | |||||
| } | |||||
| void handle_stats(KCLIST* tokens, FILE* client){ | |||||
| char out[1024]; | |||||
| float hit_ratio = (float)hits / (float)(hits + misses); | |||||
| int records = kcdbcount(db); | |||||
| sprintf(out, STATS_FORMAT, hits, misses, hit_ratio, records); | |||||
| fputs(out, client); | |||||
| } | |||||
| void handle_version(KCLIST* tokens, FILE* client){ | |||||
| char out[1024]; | |||||
| sprintf(out, "VERSION %s\r\n", VERSION); | |||||
| fputs(out, client); | |||||
| } | |||||
| void handle_get(KCLIST* tokens, FILE* client){ | |||||
| char key[128]; | |||||
| list_shift(tokens, key); | |||||
| if(key){ | |||||
| char out[1024]; | |||||
| char* result_buffer; | |||||
| size_t result_size; | |||||
| result_buffer = kcdbget(db, key, strlen(key), &result_size); | |||||
| if(result_buffer){ | |||||
| if(strcmp(result_buffer, "0") == 0){ | |||||
| ++misses; | |||||
| sprintf(out, "VALUE %s 0 2\r\n{}\r\nEND\r\n", key); | |||||
| kcfree(result_buffer); | |||||
| } else{ | |||||
| ++hits; | |||||
| sprintf(out, "VALUE %s 0 %d\r\n%s\r\nEND\r\n", key, (int)strlen(result_buffer), result_buffer); | |||||
| } | |||||
| kcfree(result_buffer); | |||||
| } else{ | |||||
| ++misses; | |||||
| sprintf(out, "VALUE %s 0 2\r\n{}\r\nEND\r\n", key); | |||||
| kcdbset(db, key, strlen(key), "0", 1); | |||||
| queue_add(&requests, key); | |||||
| } | |||||
| fputs(out, client); | |||||
| } else{ | |||||
| fputs("INVALID GET COMMAND: GET <KEY>\r\n", client); | |||||
| return; | |||||
| } | |||||
| } | |||||
| int handle_command(KCLIST* tokens, FILE* client){ | |||||
| int status = 0; | |||||
| char command[128]; | |||||
| list_shift(tokens, command); | |||||
| if(command){ | |||||
| if(strcmp(command, "get") == 0){ | |||||
| handle_get(tokens, client); | |||||
| } else if(strcmp(command, "stats") == 0){ | |||||
| handle_stats(tokens, client); | |||||
| } else if(strcmp(command, "version") == 0){ | |||||
| handle_version(tokens, client); | |||||
| } else if(strcmp(command, "quit") == 0){ | |||||
| status = -1; | |||||
| } else{ | |||||
| printf("Unknown Command: %s\r\n", command); | |||||
| char out[1024]; | |||||
| sprintf(out, "UNKNOWN COMMAND: %s\r\n", command); | |||||
| fputs(out, client); | |||||
| } | |||||
| } else{ | |||||
| printf("No Command\r\n"); | |||||
| } | |||||
| return status; | |||||
| } | |||||
| void* worker(void* arg){ | |||||
| FILE* fp = (FILE*) arg; | |||||
| char buffer[100]; | |||||
| KCLIST* tokens = kclistnew(); | |||||
| int status; | |||||
| while(fgets(buffer, sizeof(buffer), fp)){ | |||||
| int last = strlen(buffer) - 1; | |||||
| for(; last > 0; --last){ | |||||
| if(buffer[last] == '\r' || buffer[last] == '\n'){ | |||||
| buffer[last] = 0; | |||||
| } | |||||
| } | |||||
| if(strlen(buffer)){ | |||||
| tokenize(tokens, buffer); | |||||
| status = handle_command(tokens, fp); | |||||
| if(status == -1){ | |||||
| break; | |||||
| } | |||||
| kclistclear(tokens); | |||||
| } | |||||
| } | |||||
| kclistdel(tokens); | |||||
| fclose(fp); | |||||
| return 0; | |||||
| } | |||||
| void on_signal(){ | |||||
| if(sd){ | |||||
| printf("Closing socket\r\n"); | |||||
| close(sd); | |||||
| } | |||||
| if(queue){ | |||||
| kclistdel(queue); | |||||
| } | |||||
| if(db){ | |||||
| printf("Closing Cache\r\n"); | |||||
| kcdbclose(db); | |||||
| } | |||||
| exit(0); | |||||
| } | |||||
| int main(){ | |||||
| signal(SIGINT, on_signal); | |||||
| int pool_size = 1; | |||||
| pthread_t pool[pool_size]; | |||||
| queue_init(requests); | |||||
| queue = kclistnew(); | |||||
| db = kcdbnew(); | |||||
| if(!kcdbopen(db, "*#bnum=1000000#capsiz=1g", KCOWRITER | KCOCREATE)){ | |||||
| fprintf(stderr, "Error opening cache: %s\n", kcecodename(kcdbecode(db))); | |||||
| return -1; | |||||
| } | |||||
| int i; | |||||
| printf("Starting %d worker threads\r\n", pool_size); | |||||
| for(i = 0; i < pool_size; ++i){ | |||||
| pthread_create(&(pool[i]), NULL, call_proxy, NULL); | |||||
| } | |||||
| struct sockaddr_in addr, client_addr; | |||||
| int addr_len; | |||||
| sd = socket(PF_INET, SOCK_STREAM, 0); | |||||
| if(!sd){ | |||||
| fprintf(stderr, "Could not create socket\r\n"); | |||||
| return -1; | |||||
| } | |||||
| addr.sin_family = AF_INET; | |||||
| addr.sin_port = htons(7000); | |||||
| addr.sin_addr.s_addr = htonl(INADDR_ANY); | |||||
| if(bind(sd, (struct sockaddr*)&addr, sizeof(addr)) < 0){ | |||||
| fprintf(stderr, "Address already in use\r\n"); | |||||
| return -1; | |||||
| } | |||||
| if(listen(sd, 24) < 0){ | |||||
| fprintf(stderr, "Address already in use\r\n"); | |||||
| return -1; | |||||
| } else{ | |||||
| printf("Listening on 0.0.0.0:7000\r\n"); | |||||
| int client; | |||||
| pthread_t child; | |||||
| FILE *fp; | |||||
| while(1){ | |||||
| addr_len = sizeof(client_addr); | |||||
| client = accept(sd, 0, 0); | |||||
| fp = fdopen(client, "r+"); | |||||
| pthread_create(&child, 0, worker, fp); | |||||
| pthread_detach(child); | |||||
| } | |||||
| } | |||||
| return 0; | |||||
| } | |||||