Created
December 2, 2015 18:31
-
-
Save bacher09/0bf765c9f660cf07750f to your computer and use it in GitHub Desktop.
simple http service
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "geoip_api.h" | |
#include "GeoIP.h" | |
#include <stdio.h> | |
#include <sys/types.h> | |
#include <sys/socket.h> | |
#include <netdb.h> | |
#include <string.h> | |
static GeoIP *geoip_country; | |
static GeoIP *geoip_country_v6; | |
void Geoip_Init(){ | |
if(!GeoIP_db_avail(GEOIP_COUNTRY_EDITION) || | |
!GeoIP_db_avail(GEOIP_COUNTRY_EDITION_V6)) | |
goto err; | |
geoip_country = GeoIP_open_type(GEOIP_COUNTRY_EDITION, GEOIP_MEMORY_CACHE); | |
if(geoip_country == NULL) | |
goto err; | |
geoip_country_v6 = GeoIP_open_type(GEOIP_COUNTRY_EDITION_V6, GEOIP_INDEX_CACHE); | |
if(geoip_country_v6 == NULL) | |
goto err; | |
return; | |
err: | |
perror("Geoip country is not available"); | |
exit(2); | |
} | |
void Geoip_Cleanup() { | |
GeoIP_delete(geoip_country); | |
GeoIP_delete(geoip_country_v6); | |
} | |
AddrType get_addr_family(const char *address) { | |
struct addrinfo hint, *res = NULL; | |
AddrType result; | |
int err; | |
memset(&hint, 0, sizeof(hint)); | |
hint.ai_family = AF_UNSPEC; | |
hint.ai_flags = AI_NUMERICHOST; | |
err = getaddrinfo(address, NULL, &hint, &res); | |
if(err) | |
return BAD_IP; | |
else if(res->ai_family == AF_INET) | |
result = IP_V4; | |
else if(res->ai_family == AF_INET6) | |
result = IP_V6; | |
else | |
result = BAD_IP; | |
freeaddrinfo(res); | |
return result; | |
} | |
int lookup_country_id(const char *ip_address) { | |
AddrType type = get_addr_family(ip_address); | |
if(type == IP_V4) | |
return GeoIP_id_by_addr(geoip_country, ip_address); | |
else if(type == IP_V6) | |
return GeoIP_id_by_addr_v6(geoip_country_v6, ip_address); | |
else | |
return -1; | |
} | |
const char *country_code_by_id(int id){ | |
return (id > 0) ? GeoIP_code_by_id(id) : NULL; | |
} | |
const char *country_code3_by_id(int id){ | |
return (id > 0) ? GeoIP_code3_by_id(id) : NULL; | |
} | |
const char *country_name_by_id(int id){ | |
return (id > 0) ? GeoIP_country_name_by_id(geoip_country, id) : NULL; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef GEOIP_API_H | |
#define GEOIP_API_H | |
typedef enum { | |
BAD_IP = 0, | |
IP_V4 = 1, | |
IP_V6 = 2, | |
} AddrType; | |
void Geoip_Init(); | |
void Geoip_Cleanup(); | |
AddrType get_addr_family(const char*); | |
int lookup_country_id(const char*); | |
const char *country_code_by_id(int); | |
const char *country_code3_by_id(int); | |
const char *country_name_by_id(int); | |
#endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdbool.h> | |
#include "http_utils.h" | |
static inline signed short hctoi(const char sym){ | |
if (sym >= '0' && sym <= '9') | |
return sym - '0'; | |
else if (sym >= 'a' && sym <= 'f') | |
return sym - 'a' + 10; | |
else if (sym >= 'A' && sym <= 'F') | |
return sym - 'A' + 10; | |
else | |
return -1; | |
} | |
static inline signed short hex2char(const char *hex){ | |
signed short f, s; | |
if(hex == NULL || hex[0] == '\0' || hex[1] == '\0') | |
return -1; | |
f = hctoi(hex[0]); | |
s = hctoi(hex[1]); | |
if(f < 0 || s < 0) | |
return -1; | |
return (f << 4) | s; | |
} | |
static inline int get_bit(const unsigned char bit, const unsigned char bit_set[32]) { | |
return bit_set[bit / 8] >> (bit % 8) & 1; | |
} | |
static inline void set_bit(const unsigned char bit, unsigned char bit_set[32]) { | |
bit_set[bit / 8] |= 1 << (bit % 8); | |
} | |
int read_decode_until(const char *str, const char *seps, char *res, | |
int max_write, int *write_count) { | |
int i, w; | |
signed short dec; | |
#define APPEND(X) if(w < max_write) {res[w++] = (X);} | |
unsigned char char_set[32] = {0}; | |
if(str == NULL || str[0] == '\0') | |
return 0; | |
/* Init bit set */ | |
for(i = 0; seps[i] != '\0'; i++) | |
set_bit(seps[i], char_set); | |
set_bit('\0', char_set); | |
i = w = 0; | |
while (!get_bit(str[i], char_set)){ | |
switch(str[i]){ | |
case '+': | |
APPEND(' '); | |
i++; | |
break; | |
case '%': | |
dec = hex2char(&str[i + 1]); | |
if(dec >= 0) { | |
APPEND((char)dec); | |
i += 3; | |
} else { | |
APPEND('%'); | |
i++; | |
} | |
break; | |
default: | |
APPEND(str[i]); | |
i++; | |
break; | |
} | |
} | |
*write_count = w; | |
return i; | |
#undef APPEND | |
} | |
_Bool iterate_qs(query_param *qp, int *i, const char *query_string) { | |
int readed; | |
qp->key_len = 0; | |
qp->value_len = 0; | |
if (query_string == NULL || *query_string == '\0') | |
return false; | |
/* If there are multiple & symbols then skip them */ | |
while (query_string[*i] == '&') | |
(*i)++; | |
if(query_string[*i] == '\0') | |
return false; | |
readed = read_decode_until(&query_string[*i], "=&", qp->key, | |
qp->max_key, &(qp->key_len)); | |
*i += readed; | |
if(query_string[*i] == '=') | |
(*i)++; | |
readed = read_decode_until(&query_string[*i], "&", qp->value, | |
qp->max_value, &(qp->value_len)); | |
*i += readed; | |
return true; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef HTTP_UTILS_H | |
#define HTTP_UTILS_H | |
#include <stdbool.h> | |
typedef struct { | |
int key_len; | |
int value_len; | |
int max_key; | |
int max_value; | |
char *key; | |
char *value; | |
} query_param; | |
_Bool iterate_qs(query_param*, int*, const char*); | |
#endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "geoip_api.h" | |
#include "http_utils.h" | |
#include "fcgi_config.h" | |
#include "fcgiapp.h" | |
#include <pthread.h> | |
#include <sys/types.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <signal.h> | |
#include <unistd.h> | |
#include <stdbool.h> | |
#define THREAD_COUNT 4 | |
#define BACKLOG 16 | |
#define MAX_KEY 40 | |
#define MAX_VALUE 300 | |
static const char *not_found_str = ( | |
"Status: 404\r\n" | |
"Content-Type: text/html\r\n" | |
"\r\n" | |
"<html><body>Not Found<body></html>\n"); | |
static const char *bad_request_str = ( | |
"Status: 400\r\n" | |
"Content-Type: text/html\r\n" | |
"\r\n" | |
"<html><body>Bad Request</body></html>\n"); | |
struct doit_args { | |
int thread_num; | |
int socket; | |
}; | |
struct handler { | |
const char *path; | |
void (*handler)(); | |
}; | |
void ip_handler(FCGX_Request*); | |
void user_agent_handler(FCGX_Request*); | |
void mycountry_handler(FCGX_Request*); | |
void country_handler(FCGX_Request*); | |
const struct handler url_hanlders[] = { | |
{"/ip", ip_handler}, | |
{"/user-agent", user_agent_handler}, | |
{"/country", country_handler}, | |
{NULL, NULL} | |
}; | |
static void not_found404(FCGX_Request *request){ | |
FCGX_PutS(not_found_str, request->out); | |
} | |
static void bad_request(FCGX_Request *request) { | |
FCGX_PutS(bad_request_str, request->out); | |
} | |
void ip_handler(FCGX_Request *request){ | |
char *ip_address = FCGX_GetParam("REMOTE_ADDR", request->envp); | |
FCGX_PutS("Content-Type: text/plain\r\n\r\n", request->out); | |
FCGX_PutS(ip_address, request->out); | |
} | |
void user_agent_handler(FCGX_Request *request){ | |
char *user_agent = FCGX_GetParam("HTTP_USER_AGENT", request->envp); | |
FCGX_PutS("Content-Type: text/plain\r\n\r\n", request->out); | |
FCGX_PutS(user_agent, request->out); | |
} | |
void country_handler(FCGX_Request *request){ | |
#define MAX_IP 60 | |
#define MAX_TYPE 10 | |
char key[MAX_KEY]; | |
char value[MAX_VALUE]; | |
char ip_address[MAX_IP] = {0}; | |
char type[MAX_TYPE] = {0}; | |
char *ip; | |
query_param qp; | |
int country_id; | |
int i = 0; | |
const char *country_code, *country_code3, *name; | |
char *query_string = FCGX_GetParam("QUERY_STRING", request->envp); | |
qp.key = key; | |
qp.value = value; | |
qp.max_key = MAX_KEY; | |
qp.max_value = MAX_VALUE; | |
while(iterate_qs(&qp, &i, query_string)) { | |
if(qp.key_len >= MAX_KEY || qp.value_len >= MAX_VALUE) | |
continue; | |
key[qp.key_len] = '\0'; | |
value[qp.value_len] = '\0'; | |
if(strcmp(key, "ip") == 0 && strnlen(value, MAX_IP + 1) < MAX_IP) | |
strcpy(ip_address, value); | |
if(strcmp(key, "type") == 0 && strnlen(value, MAX_TYPE + 1) < MAX_TYPE) | |
strcpy(type, value); | |
} | |
if(ip_address[0] == '\0') | |
ip = FCGX_GetParam("REMOTE_ADDR", request->envp); | |
else | |
ip = ip_address; | |
if(ip == NULL) { | |
bad_request(request); | |
return; | |
} | |
country_id = lookup_country_id(ip); | |
FCGX_PutS("Content-Type: text/plain\r\n\r\n", request->out); | |
country_code = country_code_by_id(country_id); | |
if(country_code == NULL) | |
country_code = "Undefined"; | |
country_code3 = country_code3_by_id(country_id); | |
if(country_code3 == NULL) | |
country_code3 = "Undefined"; | |
name = country_name_by_id(country_id); | |
if(name == NULL) | |
name = "Undefined"; | |
if(strcmp(type, "all") == 0) { | |
FCGX_FPrintF(request->out, "%s\n\%s\n%s", country_code, country_code3, name); | |
} else if(strcmp(type, "code3") == 0) { | |
FCGX_PutS(country_code3, request->out); | |
} else if(strcmp(type, "name") == 0) { | |
FCGX_PutS(name, request->out); | |
} else { | |
FCGX_PutS(country_code, request->out); | |
} | |
#undef MAX_IP | |
#undef MAX_TYPE | |
} | |
static void dispatch(FCGX_Request *request, struct doit_args *thread_args) { | |
_Bool not_found = true; | |
char *url = FCGX_GetParam("SCRIPT_NAME", request->envp); | |
for(int i = 0; url_hanlders[i].path != NULL; i++) { | |
if(strcmp(url, url_hanlders[i].path) == 0){ | |
url_hanlders[i].handler(request); | |
not_found = false; | |
break; | |
} | |
} | |
if(not_found) | |
not_found404(request); | |
} | |
static void *doit(struct doit_args *thread_args) { | |
int rc; | |
FCGX_Request request; | |
FCGX_InitRequest(&request, thread_args->socket, 0); | |
for(;;) { | |
rc = FCGX_Accept_r(&request); | |
if(rc < 0) // if error then exit | |
break; | |
dispatch(&request, thread_args); | |
FCGX_Finish_r(&request); | |
} | |
return NULL; | |
} | |
void Cleanup_App(){ | |
puts("Cleanup"); | |
FCGX_ShutdownPending(); | |
Geoip_Cleanup(); | |
} | |
void handle_signal(int signal) { | |
exit(0); | |
} | |
int Init_App(const char *socket_str) { | |
struct sigaction sa; | |
FCGX_Init(); | |
Geoip_Init(); | |
sa.sa_handler = &handle_signal; | |
sigemptyset(&sa.sa_mask); | |
sa.sa_flags = 0; | |
if(sigaction(SIGTERM, &sa, NULL) < 0) | |
goto err; | |
if(sigaction(SIGINT, &sa, NULL) < 0) | |
goto err; | |
atexit(Cleanup_App); | |
return FCGX_OpenSocket(socket_str, BACKLOG); | |
err: | |
perror("Error setting signal handler"); | |
exit(1); | |
} | |
int main(int argc, char *argv[]) { | |
int sock_fd; | |
pthread_t id[THREAD_COUNT]; | |
struct doit_args thread_args[THREAD_COUNT - 1]; | |
if(argc != 2) { | |
perror("Wront argument count\nCall program with ./program socket\n"); | |
return 1; | |
} | |
sock_fd = Init_App(argv[1]); | |
thread_args[0].thread_num = 0; | |
thread_args[0].socket = sock_fd; | |
for(int i = 1; i < THREAD_COUNT; i++) { | |
pthread_create(&id[i - 1], NULL, (void*(*)(void*))doit, (void *)&thread_args[i]); | |
} | |
doit(&thread_args[0]); | |
return 0; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CC=gcc | |
LIBS=-lpthread -lfcgi -lGeoIP | |
CFLAGS=-Wall --std=c11 -O2 -D_POSIX_C_SOURCE=200809L | |
DEPS=geoip_api.h http_utils.h | |
TARGET=ip_api | |
.PHONY: all | |
all: $(TARGET) | |
%.o: %.c $(DEPS) | |
$(CC) -c -o $@ $< $(CFLAGS) | |
ip_api: ip_api.o geoip_api.o http_utils.o | |
$(CC) -o $@ $^ $(CFLAGS) $(LIBS) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment