babelouest / ulfius

Web Framework to build REST APIs, Webservices or any HTTP endpoint in C language. Can stream large amount of data, integrate JSON data with Jansson, and create websocket services
https://babelouest.github.io/ulfius
GNU Lesser General Public License v2.1
1.07k stars 183 forks source link

When ulfius starts in a docker container it exits 0 #273

Open joegasewicz opened 5 days ago

joegasewicz commented 5 days ago

Describe the issue I want to dockerize an app that uses ulfius, it builds and runs successfully but then exists 0 straight away For example:

docker run -p 8005:8005 bandnoticeboard/nottoboard:forestmq-0.1.2
Starting server on http://localhost:8005

Process finished with exit code 0

To Reproduce You can pull down the code from https://github.com/joegasewicz/forestmq

Expected behavior I expect the ulfius server to persist when run inside a docker container

System (please complete the following information):

joegasewicz commented 5 days ago

I think I need to set the host, is this done via the ulfius_init_instance functions's bind_address parameter?

babelouest commented 5 days ago

Hello,

It's hard to say without the server source code, which doesn't seem to be hosted in the repo you mention.

But my first guess is that you don't make your server waiting right after starting the ulfius endpoint. Ulfius webserver is executed in a non blocking thread so your server can do other things.

In the example programs, I usually use getchar() to keep the program running but that's not a good method for a server.

You can search for "daemon programming c" to get some examples.

joegasewicz commented 5 days ago

Here's the current server code:

#include <stdio.h>
#include <stdlib.h>
#include <ulfius.h>
#include <jansson.h>
#include <string.h>
#include <stdbool.h>
#include <netinet/in.h>
#include "tcp.h"
#include "queue.h"
#include "config.h"

static int callback_consumer(const struct _u_request *request,
    struct _u_response *response, void *queue)
{
    JSON_INDENT(4);
    const FMQ_Queue *q = (FMQ_Queue*)queue;
    const FMQ_QNode *node = FMQ_Queue_dequeue((FMQ_Queue*)queue);
    if (node == NULL)
    {
        FMQ_LOGGER(q->log_level ,"Queue is empty\n");
        ulfius_set_json_body_response(response, 204, json_pack("{s:s}", "message", NULL));
        return U_CALLBACK_CONTINUE;
    }
    const FMQ_Data *dataPtr = (FMQ_Data*)node->data;
    FMQ_LOGGER(q->log_level, "Successfully dequeued message for consumer\n");
    ulfius_set_json_body_response(response, 200, json_pack("{s:s}", "message", dataPtr->message));
    free((FMQ_Data*)node->data);
    free((FMQ_QNode*)node);
    return U_CALLBACK_CONTINUE;
}

static int callback_provider(const struct _u_request *request,
    struct _u_response *response, void *queue)
{
    const FMQ_Queue *q = (FMQ_Queue*)queue;
    JSON_INDENT(4);
    json_t *json_body = ulfius_get_json_body_request(request, NULL);
    const char *message = json_string_value(json_object_get(json_body, "message"));
    bool destroy = json_boolean_value(json_object_get(json_body, "destroy"));
    if (destroy) {
        FMQ_QUEUE_destroy((FMQ_Queue*)queue);
        FMQ_LOGGER(q->log_level, "Successfully destroyed queue\n");
        ulfius_set_json_body_response(response, 200, json_pack("{s:s}", "message", message));
        return U_CALLBACK_CONTINUE;
    }
    FMQ_LOGGER(q->log_level, "Received: %s\n", message);
    FMQ_Data *data = (FMQ_Data*)malloc(sizeof(FMQ_Queue));
    data->message = malloc(sizeof(char) * q->msg_size);
    strcpy(data->message, message);
    FMQ_Queue_enqueue((FMQ_Queue*)queue, data);

    ulfius_set_json_body_response(response, 200, json_pack("{s:s}", "message", message));

    json_decref(json_body);
    return U_CALLBACK_CONTINUE;
}

static int start_server(FMQ_TCP *tcp)
{
    struct _u_instance instance;

    if (ulfius_init_instance(&instance, tcp->port, NULL, NULL) != U_OK)
    {
        fprintf(stderr, "Error starting ulfius server\n");
        exit(EXIT_FAILURE);
    }

    ulfius_add_endpoint_by_val(&instance, "POST", "/consumer", NULL, 0, &callback_consumer, tcp->queue);
    ulfius_add_endpoint_by_val(&instance, "POST", "/provider", NULL, 0, &callback_provider, tcp->queue);

    if (ulfius_start_framework(&instance) == U_OK)
    {
        printf("Starting server on http://localhost:%d\n", instance.port);
        getchar();
    }
    else
    {
        fprintf(stderr, "Error starting server...\n");
        printf("Error: \n\t- Tip: Check that port %d is not busy\n", tcp->port);
    }
    FMQ_LOGGER(tcp->log_level, "Stopping server \n");

    ulfius_stop_framework(&instance);
    ulfius_clean_instance(&instance);
    return 0;
}

FMQ_TCP *FMQ_TCP_new(FMQ_Queue *queue, const u_int16_t port, const int8_t log_level)
{
    FMQ_TCP *tcp = (FMQ_TCP*)malloc(sizeof(FMQ_TCP));
    tcp->queue = queue;
    tcp->start = start_server;
    tcp->port = port;
    tcp->log_level = log_level;
    return tcp;
}

This is the Dockerfile

FROM --platform=linux/amd64 ubuntu:22.04

WORKDIR /forestmq

COPY . .

ENV APPLE = 0
ENV UNIX = 1

RUN apt update
RUN apt install -y libulfius-dev uwsc
RUN apt install -y libmicrohttpd-dev
RUN apt install -y libjansson-dev
RUN apt install -y libcurl4-gnutls-dev
RUN apt install -y libgnutls28-dev
RUN apt install -y libgcrypt20-dev
RUN apt install -y libsystemd-dev
RUN apt install -y pkg-config
RUN apt install -y cmake

#RUN mkdir build
RUN cmake -S . -B build
RUN make -C build

EXPOSE 8005

CMD ["./build/forest_mq"]
joegasewicz commented 5 days ago

@babelouest Yes I am blocking the main thread with getchar() as per your example -

 if (ulfius_start_framework(&instance) == U_OK)
    {
        printf("Starting server on http://localhost:%d\n", instance.port);
        getchar();
    }

Is it possible to set the host, for example to 0.0.0.0 ?

babelouest commented 5 days ago

@babelouest Is it possible to set the host, for example to 0.0.0.0 ?

I'm not sure what tout expect if you set the host, but setting the bind_address won't change the server exiting right after starting ulfius webserver.

Your problem doesn't seem to be how to use ulfius but how to program a daemon in C.

My best guess is to look for examples, to help you build a program running in docker.

joegasewicz commented 5 days ago

@babelouest ok thanks for the advice.

I got it working using -it arg to start the container in the interactive mode... might help someone else with this issue. e.g

docker run -it -p 8005:8005 $(IMG_NAME)