This document describes how Axis2/C supports Android deployment with HTTP/2 and JSON-RPC.
When working with Axis2/C Android support, remember:
-std=gnu99 - Required for json-c header compatibility--whole-archive is required - For weak/strong symbol resolution during linkingAll Axis2/C builds use -std=gnu99 (GNU C99 mode). This is required because:
inline keywordImplementation in all configure.ac files:
if test "$GCC" = "yes"; then dnl Use gnu99 for json-c compatibility CFLAGS="$CFLAGS -std=gnu99 -Wall -Wno-implicit-function-declaration -D_GNU_SOURCE" fi
The following configure.ac files all use this pattern:
configure.ac (root)util/configure.acaxiom/configure.acneethi/configure.acguththila/configure.acsamples/configure.actools/md5/configure.actools/tcpmon/configure.acAndroid builds are detected by the --host parameter:
AC_MSG_CHECKING([for Android target]) case "$host" in *-linux-android*|*-android*) android_build=yes AC_DEFINE([AXIS2_ANDROID_BUILD], [1], [Building for Android]) ;; *) android_build=no ;; esac
When android_build=yes:
AXIS2_ANDROID_BUILD is defined__ANDROID__ is typically already defined by the NDK compilerEnvironment setup:
export ANDROID_NDK_HOME=$HOME/Android/Sdk/ndk/28.0.12916984 export TOOLCHAIN=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64 export CC=$TOOLCHAIN/bin/aarch64-linux-android21-clang export AR=$TOOLCHAIN/bin/llvm-ar export RANLIB=$TOOLCHAIN/bin/llvm-ranlib
Configure command:
./configure \ --host=aarch64-linux-android \ --prefix=$DEPS \ --enable-static \ --disable-shared \ --enable-http2 \ --enable-json \ --with-apr=$DEPS \ --with-openssl=$DEPS
Convenience script: Use build_android.sh in the repository root for a complete build:
./build_android.sh
This script sets up the toolchain, configures with all required flags, and builds. Edit the script to adjust paths for your NDK installation and dependency locations.
Traditional Axis2/C loads services dynamically:
void *lib = dlopen("libmyservice.so", RTLD_NOW); func = dlsym(lib, "service_invoke_json");
Android with static linking has no .so files to load.
Axis2/C uses weak symbols so applications can provide service implementations at link time.
In src/core/receivers/axis2_json_rpc_msg_recv.c:
#ifdef __ANDROID__ typedef json_object* (*json_service_invoke_func_t)( const axutil_env_t *env, json_object *json_request); /* Weak symbol - returns NULL if no implementation linked */ __attribute__((weak)) json_object* camera_control_service_invoke_json( const axutil_env_t *env, json_object *json_request) { (void)env; (void)json_request; return NULL; } /* Lookup function - returns function pointer for known services */ json_service_invoke_func_t android_static_service_lookup(const char *service_name) { if (!service_name) return NULL; if (strcmp(service_name, "CameraControlService") == 0) { return camera_control_service_invoke_json; } /* Add more services here */ return NULL; } #endif
[Axis2/C Framework] [Application]
| |
weak symbol: strong symbol:
camera_control_service_ camera_control_service_
invoke_json() -> NULL invoke_json() -> response
| |
+----------- Link Time ---------+
|
Strong symbol wins
Applications provide two files:
Adapter (bridges Axis2/C to app):
// axis2_static_service_adapter.c #include <axutil_env.h> #include <json-c/json.h> extern int camera_control_service_impl(const char* req, char* resp, size_t size); /* Strong symbol overrides weak in Axis2/C */ json_object* camera_control_service_invoke_json( const axutil_env_t *env, json_object *json_request) { const char *req_str = json_object_to_json_string(json_request); char response[65536] = {0}; camera_control_service_impl(req_str, response, sizeof(response)); return json_tokener_parse(response); }
Implementation (app-specific logic):
// camera_control_service.c int camera_control_service_impl(const char* req, char* resp, size_t size) { /* Parse request, handle action, build response */ snprintf(resp, size, "{\"result\":\"ok\"}"); return 0; }
Registry functions MUST NOT use static:
/* WRONG - may be optimized away */ static json_service_invoke_func_t android_static_service_lookup(...) { } /* CORRECT - global symbol preserved */ json_service_invoke_func_t android_static_service_lookup(...) { }
Static functions have internal linkage and may be excluded from static archives.
_invoke_json SignaturesThe axis2_json_rpc_msg_recv framework dispatches to service functions using different signatures depending on the platform:
json_object*)json_object* camera_control_service_invoke_json( const axutil_env_t *env, json_object *json_request);
android_static_service_lookup)dlopen/dlsym at runtimejson_object* (json-c object pointers)axis2_static_service_adapter.c bridges this to the app’s _impl() functions which use plain char* stringschar*)axis2_char_t* financial_benchmark_service_invoke_json( axis2_svc_t *svc, const axutil_env_t *env, const axis2_char_t *json_request, axis2_msg_ctx_t *msg_ctx);
dlsym(RTLD_DEFAULT, "<serviceclass>_invoke_json") after the service .so is loaded by axutil_class_loader_create_dll()axis2_svc_t and axis2_msg_ctx_t for operation routing, endpoint info, and MCP catalog generationaxis2_char_t* (JSON strings), not json_object*Server-side services need svc and msg_ctx to:
Android services don't need these because:
"action" field in the JSON bodyServices that want to work in both environments can export both:
axis2_char_t* <serviceclass>_invoke_json(svc, env, json_str, msg_ctx) for server-sideaxis2_char_t* <serviceclass>_process_json_only(env, json_str) for simpler contextsLibtool strips --whole-archive flags, so direct linking is necessary:
$CC -fPIC -o httpd modules.o buildmark.o \ -Wl,--export-dynamic \ -L$DEPS/lib \ # Apache modules... \ $DEPS/lib/libmod_axis2.a \ -Wl,--whole-archive $DEPS/lib/libaxis2_engine.a -Wl,--no-whole-archive \ -Wl,--whole-archive libapp_services.a -Wl,--no-whole-archive \ # Other Axis2 libraries... \ -ljson-c -lnghttp2 -laprutil-1 -lapr-1 \ -lm -llog -pthread
Key flags:
| Flag | Purpose |
|---|---|
-Wl,--whole-archive | Include ALL objects from archive |
-Wl,--no-whole-archive | Resume normal archive linking |
-Wl,--export-dynamic | Export all symbols |
-llog | Android logging |
nm httpd | grep -E "android_static|camera_control" # Expected (T = global text): # 00000000007b4998 T android_static_service_lookup # 00000000007b3ae0 T camera_control_service_invoke_json
Use RESTLocation and RESTMethod, NOT httpPath and httpMethod:
<service name="CameraControlService"> <operation name="getStatus"> <messageReceiver class="axis2_json_rpc_msg_recv"/> <parameter name="RESTMethod">POST</parameter> <parameter name="RESTLocation">/getStatus</parameter> <parameter name="contentType">application/json</parameter> </operation> </service>
In src/core/receivers/axis2_json_rpc_msg_recv.c:
#ifdef __ANDROID__ __attribute__((weak)) json_object* my_new_service_invoke_json( const axutil_env_t *env, json_object *json_request) { (void)env; (void)json_request; return NULL; } json_service_invoke_func_t android_static_service_lookup(const char *svc) { /* Existing services... */ if (strcmp(svc, "MyNewService") == 0) { return my_new_service_invoke_json; } return NULL; } #endif
Create adapter and implementation files in your application, then link with --whole-archive.
Deploy to services/MyNewService/services.xml in the Axis2 repository.
In mod_axis2.c, Android builds:
-X flag)#ifdef __ANDROID__ /* Skip shared memory, use pool allocation */ allocator = apr_palloc(pconf, sizeof(axutil_allocator_t)); goto engine_init; #endif
Android logging uses INFO level:
#ifdef __ANDROID__ #include <android/log.h> #define AXIS2_ANDROID_LOG(fmt, ...) \ __android_log_print(ANDROID_LOG_INFO, "Axis2-mod", fmt, ##__VA_ARGS__) #else #define AXIS2_ANDROID_LOG(fmt, ...) ((void)0) #endif
Apache runs with -X (single-process) on Android. For older devices, configure HTTP/2 conservatively:
# In http2-performance.conf H2MaxSessionStreams 1 # Single stream for stability H2StreamTimeout 30 # Prevent hanging streams H2WindowSize 65535 # Reduced memory H2Push off # Not needed for JSON-RPC H2MinWorkers 1 H2MaxWorkers 2
Axis2/C services on Android often need to communicate with Java application components. This is done via Android Intent broadcasts using the am (Activity Manager) command.
INSECURE (vulnerable to command injection):
/* NEVER DO THIS - user input is parsed by shell */ char cmd[1024]; snprintf(cmd, sizeof(cmd), "am broadcast --es pattern '%s'", user_pattern); system(cmd); /* Shell injection if pattern contains: '; rm -rf / ; ' */
SECURE (use fork/execvp):
/* Arguments passed directly to process, no shell parsing */ typedef struct { const char* type; /* "--es" for string, "--ei" for int */ const char* key; const char* value; } intent_extra_t; static int send_intent_broadcast_secure( const char* component, const char* action, const intent_extra_t* extras, int num_extras) { /* Build argument array */ int total_args = 8 + (num_extras * 3) + 1; /* base args + extras + NULL */ char** argv = (char**)malloc(total_args * sizeof(char*)); int i = 0; argv[i++] = "am"; argv[i++] = "broadcast"; argv[i++] = "--user"; argv[i++] = "0"; argv[i++] = "-n"; argv[i++] = (char*)component; argv[i++] = "-a"; argv[i++] = (char*)action; for (int j = 0; j < num_extras; j++) { argv[i++] = (char*)extras[j].type; argv[i++] = (char*)extras[j].key; argv[i++] = (char*)extras[j].value; /* User data - safe, not parsed */ } argv[i] = NULL; pid_t pid = fork(); if (pid == 0) { /* Child process */ execvp("/system/bin/am", argv); _exit(127); /* exec failed */ } /* Parent waits for child */ int status; waitpid(pid, &status, 0); free(argv); return WIFEXITED(status) && WEXITSTATUS(status) == 0 ? 0 : -1; }
| Approach | Shell Invoked | User Data Parsed | Injection Risk |
|---|---|---|---|
system("cmd " + input) | Yes | Yes | HIGH |
popen("cmd " + input, "r") | Yes | Yes | HIGH |
fork() + execvp(argv[]) | No | No | None |
With execvp(), arguments are passed as an array directly to the new process. Shell metacharacters (', ;, |, $(), etc.) are treated as literal characters, not interpreted.
/* Safe: user-provided pattern passed directly to Java */ int camera_device_delete_files_impl(const char* pattern, char* op_id) { intent_extra_t extras[] = { {"--es", "action", "delete_files"}, {"--es", "pattern", pattern}, /* User input - safe */ {"--es", "operation_id", op_id} }; return send_intent_broadcast_secure( "org.kanaha.camera/org.kanaha.camera.CameraControlReceiver", "org.kanaha.CAMERA_CONTROL", extras, 3); }
The Java BroadcastReceiver provides defense-in-depth validation:
// In CameraControlReceiver.java class SecurityValidator { // Length limits static final int MAX_FILENAME_LENGTH = 255; static final int MAX_PATH_LENGTH = 1024; // Blocked patterns private static final String[] PATH_TRAVERSAL = {"..", "%2e%2e", "%00"}; private static final String[] INJECTION = {"<script", "${", "`", "$("}; static String validateFilenameOrPattern(String input, String paramName) { if (input == null || input.isEmpty()) return "empty " + paramName; if (input.length() > MAX_FILENAME_LENGTH) return paramName + " too long"; // Check for path traversal String lower = input.toLowerCase(); for (String pattern : PATH_TRAVERSAL) { if (lower.contains(pattern)) return "path traversal in " + paramName; } // Check for injection attempts for (String pattern : INJECTION) { if (lower.contains(pattern)) return "injection attempt in " + paramName; } // Whitelist allowed characters if (!input.matches("^[a-zA-Z0-9_\\-\\.\\*]+$")) { return "invalid characters in " + paramName; } return null; // Valid } }
See the Kanaha project's docs/SECURITY.md for complete security documentation including:
Note: Kanaha is an independent project (GPLv3 licensed due to OpenCamera dependency) and is not affiliated with Apache Software Foundation. It demonstrates production deployment patterns for the CameraControlService sample in samples/user_guide/camera-control-service/.
| Aspect | Desktop/Server | Android |
|---|---|---|
| C standard | -std=gnu99 | -std=gnu99 |
| Library type | Shared (.so) | Static (.a) |
| Service loading | dlopen() | Weak symbol registry |
| Service location | External .so | Application provides strong symbols |
| Linker | Libtool | Direct clang with --whole-archive |
| Apache mode | Multi-process | Single-process (-X) |
| IPC method | N/A | fork()/execvp() (NOT system()) |
| Input validation | Application | Java SecurityValidator + C checks |
Key files for Android support:
Axis2/C Core:
configure.ac - Android detection, C flagssrc/core/receivers/axis2_json_rpc_msg_recv.c - Static service registrysrc/core/transport/http/server/apache2/mod_axis2.c - Android init, loggingsrc/core/transport/http/server/apache2/apache2_worker.c - Request processingApplication (Kanaha example):
app/src/main/cpp/axis2c/camera_control_service.c - Service impl with secure IPCapp/src/main/java/org/kanaha/camera/CameraControlReceiver.java - Java receiver with SecurityValidatorapp/src/main/assets/apache/httpd.conf - Apache security configapp/src/main/assets/apache/ssl.conf - mTLS and CRL configdocs/SECURITY.md - Comprehensive security documentationLast updated: 2026-01-14