diff --git a/Makefile b/Makefile index 1c51ac1..b01ce07 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CC = gcc -CFLAGS = -Wall -g -LIBS = -lmicrohttpd +CFLAGS = -Wall -g -I/usr/include/postgresql +LIBS = -lmicrohttpd -L/usr/lib/x86_64-linux-gnu -lpq SRCDIR = src BINDIR = bin diff --git a/README.md b/README.md index 715a76e..54efd62 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ I've deliberately chosen the C programming language for this project to challeng My primary goal was to gain a deeper understanding of how APIs function under the hood and how they communicate with the underlying operating system. The C language is exceptionally suited for this purpose due to its low-level nature and direct interaction with hardware and system resources. +## Postgres Database +In this project, I'm using a Postgres database to store user information. The database is hosted on cloud in the [supabase](https://supabase.com/), and the server communicates with it using the [libpq](https://www.postgresql.org/docs/9.1/libpq.html) library. + ## How to Run the Server ### Prerequisites: @@ -29,7 +32,11 @@ My primary goal was to gain a deeper understanding of how APIs function under th cd rest-api-C ``` -3. Running the Server: +3. Update the Database Credentials + - Open the `pg.h` file in the `src` directory. + - Update the variables with your database credentials. + +4. Running the Server: - Using Docker: ```bash docker build -t rest-api-c . @@ -45,15 +52,14 @@ My primary goal was to gain a deeper understanding of how APIs function under th ./bin/main ``` -4. Access the API at http://localhost:8080 +5. Access the API at http://localhost:8080 ## Endpoints (Work in Progress) This RESTful web server provides the following endpoints (please note that this section is a work in progress): -- **GET /:** Welcome message and basic information about the API (not yet implemented). -- **GET /users:** Retrieve a list of users (not yet implemented). -- **GET /users/{id}:** Retrieve detailed information about a specific user (not yet implemented). +- **GET /users:** Retrieve a list of users. +- **GET /users/{id}:** Retrieve detailed information about a specific user. - **POST /users:** Create a new user (not yet implemented). - **PUT /users/{id}:** Update information for a specific user (not yet implemented). - **DELETE /users/{id}:** Delete a user (not yet implemented). diff --git a/bin/handler.o b/bin/handler.o deleted file mode 100644 index e776d6e..0000000 Binary files a/bin/handler.o and /dev/null differ diff --git a/bin/main b/bin/main index 3583c4d..8fcfcb4 100755 Binary files a/bin/main and b/bin/main differ diff --git a/bin/main.o b/bin/main.o deleted file mode 100644 index ce29ffe..0000000 Binary files a/bin/main.o and /dev/null differ diff --git a/build.sh b/build.sh index f51dc43..f6b5876 100755 --- a/build.sh +++ b/build.sh @@ -4,4 +4,4 @@ set -e mkdir -p bin -gcc -o bin/main src/main.c -lmicrohttpd +gcc -o bin/main src/main.c -lmicrohttpd -I/usr/include/postgresql -L/usr/lib/x86_64-linux-gnu -lpq diff --git a/dockerfile b/dockerfile index adde841..35ae91f 100644 --- a/dockerfile +++ b/dockerfile @@ -3,7 +3,7 @@ FROM gcc:latest # Instale as dependências necessárias RUN apt-get update && \ - apt-get install -y libmicrohttpd-dev make nginx + apt-get install -y libmicrohttpd-dev make nginx libpq-dev RUN mkdir -p /app diff --git a/src/handler.h b/src/handler.h index cfb43ae..c13e3b1 100644 --- a/src/handler.h +++ b/src/handler.h @@ -1,4 +1,11 @@ #include "response_builder.h" +#include "user_handler.h" +#include + +jmp_buf exceptionBuffer; + +#define TRY if (setjmp(exceptionBuffer) == 0) +#define CATCH else void log_api(const char *url, const char *method) { printf("[%s] %s\n", method, url); @@ -12,29 +19,43 @@ enum MHD_Result default_handler(void *cls, struct MHD_Connection *connection, co int ret; struct MHD_Response *response; - HTTP_status status; - char *response_body = NULL; + HTTP_response response_api; log_api(url_str, method_str); - if (strcmp(url_str, "/") == 0) { - response_body = simple_message("Hello, world!"); - status = OK; - } else if (strcmp(url_str, "/users") == 0) { - // mandar para modulo de usuarios - response_body = simple_message("Sorry, this endpoint is not implemented yet"); - status = OK; - } else { - response_body = simple_message("Not found"); - status = NOT_FOUND; + TRY { + if (strcmp(url_str, "/") == 0) { + response_api = (HTTP_response){ + .body = simple_message("Hello world!"), + .status = OK + }; + } + + else if (is_valid_route(url_str, "/users")) { + response_api = user_router(url_str, method_str, upload_data); + } + + else { + response_api = (HTTP_response){ + .body = simple_message("Not found"), + .status = NOT_FOUND + }; + } + } CATCH { + response_api = (HTTP_response){ + .body = simple_message("Internal server error"), + .status = INTERNAL_SERVER_ERROR + }; + + printf("Internal server error"); } - response = HTTP_build_response_JSON(response_body, status); + response = HTTP_build_response_JSON(response_api.body); if (!response) return MHD_NO; - ret = MHD_queue_response(connection, status, response); + ret = MHD_queue_response(connection, response_api.status, response); MHD_destroy_response(response); return ret; diff --git a/src/main.c b/src/main.c index aecb336..b283293 100644 --- a/src/main.c +++ b/src/main.c @@ -6,18 +6,18 @@ #define PORT 8080 int main() { - printf("Starting server on port %d\n", PORT); + printf("Starting server on port %d\n", PORT); - struct MHD_Daemon *daemon; + struct MHD_Daemon *daemon; - daemon = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, PORT, NULL, NULL, - &default_handler, NULL, MHD_OPTION_END); - if (!daemon) - return 1; + daemon = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, PORT, NULL, NULL, + &default_handler, NULL, MHD_OPTION_END); + if (!daemon) + return 1; - getchar(); + getchar(); - MHD_stop_daemon(daemon); + MHD_stop_daemon(daemon); - return 0; + return 0; } diff --git a/src/pg.h b/src/pg.h new file mode 100644 index 0000000..d0a9606 --- /dev/null +++ b/src/pg.h @@ -0,0 +1,85 @@ +#include +#include +#include +#include + +#define database "*****" +#define user "*****" +#define password "*****" +#define host "*****" +#define port "*****" + +char *executeQueryToJson(const char *query); + +char *formatResultAsJson(PGresult *result); + +char *executeQueryToJson(const char *query) { + PGconn *conn = PQconnectdb( + "user=" user + " password=" password + " dbname=" database + " host=" host + " port=" port + ); + + if (PQstatus(conn) != CONNECTION_OK) { + fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn)); + PQfinish(conn); + return NULL; + } + + PGresult *result = PQexec(conn, query); + + if (PQresultStatus(result) != PGRES_TUPLES_OK) { + fprintf(stderr, "Query execution failed: %s", PQerrorMessage(conn)); + PQclear(result); + PQfinish(conn); + return NULL; + } + + char *jsonResult = formatResultAsJson(result); + + PQclear(result); + PQfinish(conn); + + return jsonResult; +} + +char *formatResultAsJson(PGresult *result) { + int numFields = PQnfields(result); + int numRows = PQntuples(result); + + // Calcula o tamanho total necessário para a string JSON -> para não estourar o buffer + int totalSize = numRows * (2 + numFields * 256) + numRows - 1 + 3; + // Cada campo contribui com pelo menos 256 caracteres, 2 para aspas e 1 para vírgula + // numRows - 1 é para as vírgulas entre objetos JSON + // 3 é para as chaves finais e terminador de string + + char *json = (char *)malloc(totalSize); + json[0] = '\0'; + + if (numRows == 0) { + strcat(json, "[]"); + return json; + } + + for (int i = 0; i < numRows; ++i) { + strcat(json, "{"); + for (int j = 0; j < numFields; ++j) { + if (j > 0) strcat(json, ","); + char *fieldName = PQfname(result, j); + char *fieldValue = PQgetvalue(result, i, j); + + strcat(json, "\""); + strncat(json, fieldName, totalSize - strlen(json) - 5); + strcat(json, "\":\""); + strncat(json, fieldValue, totalSize - strlen(json) - 5); + strcat(json, "\""); + } + strcat(json, "}"); + if (i < numRows - 1) strcat(json, ","); + } + + return json; +} + diff --git a/src/response_builder.h b/src/response_builder.h index 225078d..a9c55de 100644 --- a/src/response_builder.h +++ b/src/response_builder.h @@ -1,7 +1,7 @@ #include -#include "types.h" +#include "utils.h" -struct MHD_Response *HTTP_build_response_JSON(const char *message, HTTP_status status) { +struct MHD_Response *HTTP_build_response_JSON(const char *message) { struct MHD_Response *response; response = MHD_create_response_from_buffer(strlen(message), (void *)message, MHD_RESPMEM_PERSISTENT); diff --git a/src/types.h b/src/types.h deleted file mode 100644 index 23a89ba..0000000 --- a/src/types.h +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include -#include - -typedef enum { - OK = 200, - BAD_REQUEST = 400, - NOT_FOUND = 404, - INTERNAL_SERVER_ERROR = 500, - NOT_IMPLEMENTED = 501 -} HTTP_status; - -char *simple_message(const char *message_str) { - char *formatted_message = NULL; - - size_t formatted_message_size = strlen(message_str) + 20; - formatted_message = (char *)malloc(formatted_message_size); - - if (formatted_message) { - snprintf(formatted_message, formatted_message_size, "{\"message\": \"%s\"}", message_str); - } - - return formatted_message; -} \ No newline at end of file diff --git a/src/user_handler.h b/src/user_handler.h new file mode 100644 index 0000000..2abdd49 --- /dev/null +++ b/src/user_handler.h @@ -0,0 +1,93 @@ +#include "pg.h" +#include +#include + +HTTP_response get_all(const char *url) { + const char *query = "SELECT * FROM users"; + + char *result = executeQueryToJson(query); + + if (result == NULL) { + return (HTTP_response){ + .body = simple_message("Internal server error"), + .status = INTERNAL_SERVER_ERROR + }; + } + + return (HTTP_response){ + .body = result, + .status = OK + }; +} + +HTTP_response get_one(const char *user_id) { + char query[64]; + snprintf(query, sizeof(query), "SELECT * FROM users WHERE id = %s", user_id); + + char *result = executeQueryToJson(query); + + if (result == NULL) { + return (HTTP_response){ + .body = simple_message("Internal server error"), + .status = INTERNAL_SERVER_ERROR + }; + } + + return (HTTP_response){ + .body = result, + .status = OK + }; +} + +HTTP_response create(const char *body) { + return (HTTP_response){ + .body = simple_message("create"), + .status = OK + }; +} + +HTTP_response update(const char *user_id, const char *body) { + return (HTTP_response){ + .body = simple_message("update"), + .status = OK + }; +} + +HTTP_response drop(const char *user_id) { + return (HTTP_response){ + .body = simple_message("drop"), + .status = OK + }; +} + +HTTP_response user_router(const char *url, const char *method, const char *body){ + char *user_id = strstr(url, "/users/"); + if (user_id != NULL) { + user_id += strlen("/users/"); + } + + if (is_valid_method(method, "GET")) { + if(user_id == NULL){ + return get_all(url); + } else { + return get_one(user_id); + } + } + + if (is_valid_method(method, "POST")) { + return create(body); + } + + if (is_valid_method(method, "PUT")) { + return update(user_id, body); + } + + if (is_valid_method(method, "DELETE")) { + return drop(user_id); + } + + return (HTTP_response){ + .body = simple_message("Not implemented"), + .status = NOT_IMPLEMENTED + }; +} \ No newline at end of file diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..f92a703 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,44 @@ +#include +#include +#include +#include + +typedef enum { + OK = 200, + BAD_REQUEST = 400, + NOT_FOUND = 404, + INTERNAL_SERVER_ERROR = 500, + NOT_IMPLEMENTED = 501 +} HTTP_status; + +typedef struct { + char *body; + HTTP_status status; +} HTTP_response; + +typedef struct { + int id; + char *name; + char *email; +} USER; + +char *simple_message(const char *message_str) { + char *formatted_message = NULL; + + size_t formatted_message_size = strlen(message_str) + 20; + formatted_message = (char *)malloc(formatted_message_size); + + if (formatted_message) { + snprintf(formatted_message, formatted_message_size, "{\"message\": \"%s\"}", message_str); + } + + return formatted_message; +} + +bool is_valid_route(const char *url, char *route) { + return strstr(url, route) != NULL; +} + +bool is_valid_method(const char *method, char *valid_method) { + return strcmp(method, valid_method) == 0; +} \ No newline at end of file