Skip to content

Instantly share code, notes, and snippets.

@bacher09
Created December 2, 2015 18:31
Show Gist options
  • Save bacher09/0bf765c9f660cf07750f to your computer and use it in GitHub Desktop.
Save bacher09/0bf765c9f660cf07750f to your computer and use it in GitHub Desktop.
simple http service
#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;
}
#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
#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;
}
#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
#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;
}
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