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

cJSON support #200

Closed elockman closed 2 years ago

elockman commented 2 years ago

I am running into issues installing jansson on my embedded system.

Would it be possible to provide cjson support to create a simple json REST API on embedded systems?

cjson is just comprised of a single c and a single h file.

babelouest commented 2 years ago

Hello @elockman ,

In fact, Ulfius' Jansson integration is just with those 4 functions, and in the ulfius_set_{request,response}_properties functions with the U_OPT_JSON_BODY.

If you check ulfius_get_json_body_request and ulfius_set_json_body_response (the most used in my case), you'll see the code is pretty simple: converting a char * from and to a json_t *:

https://github.com/babelouest/ulfius/blob/master/src/u_request.c#L843 https://github.com/babelouest/ulfius/blob/master/src/u_response.c#L676

At first sight I'm not very willing to support another JSON library in ulfius, but I can definitely help you write the functions like ulfius_get_cjson_body_request and ulfius_set_cjson_body_response that you can keep in your own code base. How about that?

elockman commented 2 years ago

Sounds good. I've done a little digging and found those 2 files as the majority of the work to move forward. There would potentially be significant work with integrating cJSON build arg flags. Your help would be greatly appreciated to understand the required changes.

babelouest commented 2 years ago

Your help would be greatly appreciated to understand the required changes.

I invite you to send a link to your code or a PR to review then

elockman commented 2 years ago

@babelouest I have an example function I am using here:

int callback_write_config_param (const struct _u_request * request, struct _u_response * response, void * user_data) {
  char group[24];
  char param[24];
  char value[24];

  sprintf(group, "%s", u_map_get(request->map_url, "group"));
  sprintf(param, "%s", u_map_get(request->map_url, "param"));
  sprintf(value, "%s", u_map_get(request->map_url, "value"));
  write_config_param(group, param, value);

  json_t * json_body = NULL;
  json_body = json_object();
  json_object_set_new(json_body, param, json_string(value));
  ulfius_set_json_body_response(response, 200, json_body);
  json_decref(json_body);

  return U_CALLBACK_CONTINUE;
}

As you can see, this uses jansson functions in the application code as well. Could I simply replace the the json block with a manually (or cjson) assembled string to pass to a ulfius_set_cjson_body_response() function?

  char * resp_body = NULL;
  sprintf(resp_body, "{\"%s\":\"%s\"}", param, value);  
  ulfius_set_cjson_body_response(response, 200, resp_body);

The ulfius_set_cjson_body_response() function would then just assign the binary_body string, binary_body_length, and status. Then it would u_map_put the json header with ULFIUS_HTTP_ENCODING_JSON.

Does that make sense?

elockman commented 2 years ago

This would be a proposed ulfiussetcjson_body_response() function with some of the safeguards removed for the time being.

int ulfius_set_cjson_body_response(struct _u_response * response, const unsigned int status, const char * j_body) {
  if (response != NULL && j_body != NULL) {
    response->binary_body = j_body;
    response->binary_body_length = o_strlen((char*)response->binary_body);
    response->status = status;

    u_map_put(response->map_header, ULFIUS_HTTP_HEADER_CONTENT, ULFIUS_HTTP_ENCODING_JSON);
    return U_OK;
  } else {
    return U_ERROR_PARAMS;
  }
}
elockman commented 2 years ago

I'll fork and make proposed changes for you to review.

babelouest commented 2 years ago

According to cJSON's documentation, you should use cJSON_Parse and cJSON_Print, something like this:

int ulfius_set_cjson_body_response(struct _u_response * response, const unsigned int status, cJSON *json) {
  if (response != NULL && json != NULL) {
    o_free(response->binary_body);
    response->binary_body = cJSON_Print(json);
    response->binary_body_length = o_strlen((char*)response->binary_body);
    response->status = status;

    u_map_put(response->map_header, ULFIUS_HTTP_HEADER_CONTENT, ULFIUS_HTTP_ENCODING_JSON);
    return U_OK;
  } else {
    return U_ERROR_PARAMS;
  }
}
elockman commented 2 years ago

Thanks. You can view my code here: https://github.com/elockman/ulfius/tree/dev_cjson It contains a rough webui_example project. It breaks on the /info page where it grabs data from a JSON file.

[user@pc webui_example]$ ./webui_example 8080
2021-09-24T23:50:54Z - webui_example INFO: Starting WebUI code 
Start WebUI at http://localhost:8080/info
read_config_param: config/wifi/name = None
./webui_example: symbol lookup error: ./webui_example: undefined symbol: ulfius_set_cjson_body_response
[user@pc webui_example]$

I'm not sure where the undefined symbol error is coming from.

babelouest commented 2 years ago

Like I said above, I suggest to write the cjson functions in your own code base, rather than forking ulfius. With a fork, it will be more difficult to keep your fork up-to-date with my repo, since your code changes core functions.

Instead, I suggest you create a single project webui that has ulfius and cJSON as dependency. In that project you can implement the functions ulfius_set_cjson_body_response and others.

elockman commented 2 years ago

Thanks, that makes sense. It was also much easier.