# DeviceAtlas NGINX Module # Provides detailed instructions to build, install and use the DeviceAtlas NGINX module. * [Dependencies](#dependencies) * [DeviceAtlas API and NGINX packages](#deviceatlas_ngx) * [Building DeviceAtlas library](#deviceatlas_build) * [Option 1 - static module](#building_static_module) * [Option 2 - dynamic (.so) module](#building_dynamic_module) * [Option 3 - dynamic module for NGINX Plus](#building_NGINXPlus_module) * [NGINX setup with DeviceAtlas](#setup_deviceatlas) * [NGINX setup with Carrier Identification](#setup_carrier_identification) * [DeviceAtlas data file reload](#deviceatlas_data_reload) * [DeviceAtlas data file monitor](#deviceatlas_data_file_monitor) * [DeviceAtlas data file scheduler](#deviceatlas_data_file_scheduler) * [DeviceAtlas data file scheduler/monitor limitations](#deviceatlas_data_file_scheduler_monitor_limitations) * [Example configurations](#example_configs) * [Example application (Python FCGI)](#example_app) * [Debugging](#debugging) * [Common errors](#common_errors) <a name="dependencies"></a> ### Dependencies ### Only the DeviceAtlas C/NGINX API package and NGINX source code (1.10.1 or later) are required to build the module (see below for details). The DeviceAtlas C API requires the PCRE regex library to be available. <a name="deviceatlas_ngx"></a> ### DeviceAtlas API and NGINX packages ### ** Get the DeviceAtlas C/NGINX API package ** - Minimum version: 2.1.1 The API package can be downloaded from the [deviceatlas.com](https://deviceatlas.com/resources/download-enterprise-api) website. A login and appropriate permissions are required to download. Please contact sales@deviceatlas.com if you are not sure about access. Once the package is downloaded, extract the files to a local folder. ```sh unzip deviceatlas-enterprise-c-nginx_{version}.zip ``` ** Get the NGINX source code ** - Minimum NGINX version: 1.10.1 or later If not available in the local environment, download the source code from the [nginx.org](http://nginx.org/en/download.html) website and extract to a local folder. ```sh wget "http://nginx.org/download/nginx-1.10.2.tar.gz" tar -zxvf nginx-1.10.2.tar.gz ``` <a name="deviceatlas_build"></a> ### Building DeviceAtlas API library ### The DeviceAtlas NGINX module requires that the DeviceAtlas API library (libda.so) is built. To build the library and install libda.so, please [read the related section on unix systems](README.DeviceApi.html) for further details. #### Using API with a custom library location #### If `make install` cannot be used then the compiled DeviceAtlas library can be placed in a custom location. The following NGINX configure flag must be used to reference the DeviceAtlas library: ``` --with-ld-opt='-Wl,-rpath,<DeviceAtlas C API folder>/Src' ``` ### Building NGINX module ### The DeviceAtlas module can be built as either a `static` or `dynamic` module. The following sections describes how to build and run the module when built in either of these two forms. The dynamic module provides more flexibility and means the module can be used with pre-built NGINX binaries provided by the System and also with NGINX Plus. <a name="building_static_module"></a> #### Option 1 - static module #### The static option compiles the DeviceAtlas module into an NGINX binary along with the NGINX source code. ** Configure NGINX source and build it with DeviceAtlas ** ```sh cd nginx-{version} ./configure --add-module=<DeviceAtlas C API folder>/Nginx-Mod make sudo make install ``` ** Starting NGINX ** NGINX can be launched by using the binary from the local NGINX installation: ```sh /usr/local/nginx/sbin/nginx ``` With a `nginx.conf` file located at `/usr/local/nginx/conf/nginx.conf`. The file locations may vary based on the installation process. <a name="building_dynamic_module"></a> #### Option 2 - dynamic (.so) module #### The second option is to build as a dynamic module. This creates a .so file separate from the main NGINX build. **Important 1** - The version of the downloaded NGINX source code must match the installed target NGINX binary for the built dynamic module to be compatible. To confirm the version use `nginx -v`. **Important 2** - The same compiler and linker flags as the target NGINX binary should be used, as much as possible, when compiling the module to ensure compatibility, consistency across the components while these flags follow sane security good practices and provide reasonable performances. ** Configure NGINX source and build the DeviceAtlas module ** As mentioned above it is essential to compile with the same flags as the existing NGINX binary. The flags used to compile the NGINX binary can be found as follows: ```sh nginx -V ``` Below is the **sample** output from the above command and is only for illustrative purposes. It should not be used as is. ```sh configure arguments: \ --prefix=/etc/nginx \ --modules-path=/usr/lib/nginx/modules \ --conf-path=/etc/nginx/nginx.conf \ --with-cc-opt='-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wno-sign-compare -Wp,-D_FORTIFY_SOURCE=2' \ --with-ld-opt='-Wl,-z,relro -Wl,--as-needed' ``` To include the DeviceAtlas module it must be added to the list of flags. There is one line to be added: ```sh --add-dynamic-module=<DeviceAtlas C API folder>/Nginx-Mod ``` The above output is modified to include the DeviceAtlas module as follows: (This is an example, the flags used for your NGINX may be different!) ```sh ./configure \ --add-dynamic-module=<DeviceAtlas C API folder>/Nginx-Mod \ --modules-path=/usr/lib/nginx/modules \ --conf-path=/etc/nginx/nginx.conf \ --with-cc-opt='-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2' \ --with-ld-opt='-Wl,-z,relro -Wl,--as-needed' ``` ** Code compilation ** After running the `./configure` command the next step is compilation to get the final dynamic module (.so) file. ```sh make ``` Once built, the `ngx_http_deviceatlas_module.so` dynamic module file should be found in the `nginx-{version}/objs/` folder. Please be aware that it may be necessary to install additional libraries on your system (e.g. openssl, libxml2, GD library, ...) should other NGINX modules require them. <a name="building_NGINXPlus_module"></a> #### Option 3 - dynamic module for NGINX Plus #### Building a module for NGINX Plus is very similar to building the module for NGINX binary. The process is simpler as only a handful of configuration flags are required. Again, it is important to note that the downloaded source code version must be identical to the version of the installed NGINX Plus binaries. To confirm the version use `nginx -v`. ** Configure NGINX source and build the DeviceAtlas module ** To make the module compatible it is necessary to compile it with the `--with-compat` flag and with the same flags which were used for to build the existing NGINX Plus binary. This can be accomplished by using NGINX executable like this: ```sh nginx -V ``` The important options are `--with-cc-opt` and `--with-ld-opt` which need to be copied over (this is only for illustrative purposes, not to be used as is), along with paths to the DeviceAtlas C API source files. ```sh ./configure \ --with-compat \ --add-dynamic-module=<DeviceAtlas C API folder>/Nginx-Mod \ (on unixes systems) --with-cc-opt='-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2' \ --with-ld-opt='-Wl,-z,relro -Wl,--as-needed' (on windows) --with-cl=cl (making sure cl 32/64 same used as for the API build) --with-cc-opt='/GS' (not mandatory but recommended, it possibly decreases a bit the performance but for better safety in return) --with-ld-opt='/FORCE:MULTIPLE' (note: making sure both nginx and API points to the same pcre lib instance is essential. further manual tweaks might be still necessary depending to your own case, especially the nginx linkage) --with-pcre=<path to pcre root dir> ``` ** Code compilation ** As before, the next step is compilation to get the final dynamic module (.so) file. ```sh make ``` Once built, the `ngx_http_deviceatlas_module.so` dynamic module file should be found in the `nginx-{version}/objs/` folder. ** Modules repository ** The module file should be placed in the general NGINX Plus modules folder: ```sh cp objs/ngx_http_deviceatlas_module.so /usr/lib/nginx/modules ``` <a name="setup_deviceatlas"></a> ### NGINX setup with DeviceAtlas ### To use the module, you will need to download the required data file, save it locally and set the `devatlas_db` path in `nginx.conf`. <a name="setup_carrier_identification"></a> ### NGINX setup with Carrier Identification ### To use the Carrier identification capability of the DeviceAtlas module, you will need to download the required data file, save it locally and set the `carrierapi_db` path in `nginx.conf`. ** A snippet from nginx.conf ** ```sh # Uncomment next line and fix the path if using dynamic module. #load_module /path/to/ngx_http_deviceatlas_sch_module.so; (optional) #load_module /path/to/ngx_http_deviceatlas_module.so; http { variables_hash_max_size 2048; ## DeviceAtlas Detection devatlas_db /path/to/DeviceAtlas.json; ## DeviceAtlas variables prefix (optional) # All DeviceAtlas properties are prefixed with da_ and are available # as NGINX's environment variables (all dots are replaced with underscores). # Uncomment `devatlas_property_prefix` line to set a custom prefix for # the DeviceAtlas variables (default is da_). #devatlas_property_prefix http_; # Generate properties as HTTP headers (optional) # By default, the module will generate the data as # NGINX environment variables, however with this # option the data will be available as request HTTP headers #devatlas_http_headers on (default is off); # Additional DeviceAtlas metadata # The module will generate some additional data at start time # related to the data file and service. To potentially save # bucket size settings, it can be switched off. #devatlas_metadata off; # Generate HTTP response headers (optional) devatlas_http_headers off; ## Property filter (optional) # By default the DeviceAtlas module registers all available properties # from the JSON file. If at least one `devatlas_property` definition is # found the module will register only those properties which are defined. # For a list of all properties see https://deviceatlas.com/properties devatlas_property primaryHardwareType; devatlas_property osName; devatlas_property osVersion; devatlas_property browserName; devatlas_property browserVersion; ## Client-side Component (optional) # To use the Client-side Component with the NGINX module, the JavaScript # file located in ExtraTools/ClientSide/ must be included on every webpage. # The JS library will create a cookie with the client properties. The # NGINX module reads this cookie and merges the properties with the server # side detected properties. devatlas_properties_cookie DAPROPS; # If the Client-side properties listed on this page https://deviceatlas.com/resources/client-side-properties # (and not in your list of JSON data file properties) needs to be processed, it warrants a distinct # NGINX configuration directive, the last parameter being the type of the property (bool, string or integer). devatlas_client_property userMedia bool; devatlas_client_property audioRef string; devatlas_client_property orientation integer; # The DeviceAtlas module can cache a definite amount of properties data coupled to a HTTP request. # Note: An increased amount of memory usage is to be expected. It is disabled by default. #devatlas_cache_size 10000; ## Server configuration (optional) # By default the DeviceAtlas module operates in all NGINX server's block set in its configuration. # It is possible to deactivate it, in a server block, with the following directive (off by default). server { ... #devatlas_srv_disable on/off; } ## Carrier Identification carrierapi_db /path/to/CarrierIdentification.dat; # or # carrierapi_db /folder/for/download/DeviceAtlasCarrier.dat; ## Carrier variables prefix (optional) # All Carrier Identification properties are prefixed with ci_ and are available # as NGINX environment variables. # Uncomment the `carrierapi_property_prefix` line to set a custom prefix for # the DeviceAtlas variables (default is da_). #carrierapi_property_prefix geoloc_; ## Property filter (optional) # By default the Carrier module registers all available properties # from the binary file. If at least one `carrierapi_property` definition is # found the module will register only those properties which are defined. # For a list of all properties see https://deviceatlas.com/properties carrierapi_property countryCode; server { listen 8080; server_name localhost; location / { # To leverage Client Hints, allowing to request additional browser informations which increase the properties accuracy. add_header Accept-CH 'Sec-CH-UA-Arch, Sec-CH-UA-Model, Sec-CH-UA-Platform-Version, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Bitness, Sec-CH-UA-Wow64'; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param QUERY_STRING $query_string; # DeviceAtlas properties # nginx environment variables are formed as follows # <prefix><property name> e.g. da_model fastcgi_param DA_PRIMARY_HARDWARE_TYPE $da_primaryHardwareType; fastcgi_param DA_OS_NAME $da_osName; fastcgi_param DA_OS_VERSION $da_osVersion; fastcgi_param DA_BROWSER_NAME $da_browserName; fastcgi_param DA_BROWSER_VERSION $da_browserVersion; # Carrier properties # nginx environment variables are formed as follows # <prefix><property name> e.g. ci_countryCode fastcgi_param CI_COUNTRY_CODE $ci_countryCode; # More NGINX properties here } } } ``` <a name="deviceatlas_data_reload"></a> ### DeviceAtlas data file reload ### The DeviceAtlas data file can be reloaded in place without restarting NGINX. Replace the on-disk file with a new data file downloaded from deviceatlas.com and trigger an NGINX reload. NGINX will continue to serve requests while the new data file is being loaded. An NGINX reload can be triggered by calling `nginx reload` or `service nginx reload`. The reload will ignore corrupt or missing data files and continue using the previous in-memory version. The NGINX error log will contain an error message if this occurs. <a name="deviceatlas_data_file_monitor"></a> ### DeviceAtlas data file monitor ### Alternatively, it is possible to use the JSON file monitor which allow to update the data file in memory without reloading NGINX. ```shell % dadwsch -j <path to the JSON file from the nginx `devatlas_db` directive> [-l <path to the file monitor, log stderr by default>] [-b background mode] ``` Note that in order to use it in conjunction with NGINX, it is important to start it before the latter. To stop it, you can do the following. ```shell % dadwsch -s (useful when it had been launched in background mode) ``` or a `SIGTERM`, `SIGQUIT` or `SIGKILL` signal can be sent to its process. More informations available typing. ```shell % dadwsch -h ``` <a name="deviceatlas_data_file_scheduler"></a> ### DeviceAtlas data file scheduler ### Or, in place of the data file monitor feature, it is also possible to use the JSON file scheduler to automate the data file download at a given time which will update the data file in memory without reloading NGINX. Note that, contrary to the file mode feature, the scheduler does not need a setting related to the `devatlas_db` directive. In order to function properly, the scheduler needs the additional dependencies: * OpenSSL curl backend and libraries (libcurl4-openssl-dev and libssl-dev on debian). * Zlib and Zip libraries (zlib1g-dev and libzip-dev on debian). Note: If another curl backend is already present such as NSS, it is advised to uninstall them first to avoid backend conflicts. ```shell % dadwsch -d <URL of the JSON file download> [-t <hour of the download in the `HH:MM:SS` format> by default the same hour dadwsch had been launched] [-l <path to the file scheduler log, stderr by default>] [-b background mode] ``` Note that in order to use it in conjunction with NGINX, it is important to start it before the latter. To stop it, you can do the following. ```shell % dadwsch -s (useful when it had been launched in background mode) ``` or a `SIGTERM`, `SIGQUIT` or `SIGKILL` signal can be sent to its process. More informations available typing. ```shell % dadwsch -h ``` <a name="deviceatlas_data_file_scheduler_monitor_limitations"></a> #### DeviceAtlas data file scheduler/monitor limitations #### - For the every day JSON update, it is not necessary to reload NGINX manually, however if a change had been done in the `Data File Options` on the deviceatlas.com page before the next data file generation, due to the fact that NGINX pre allocate the right amount of memory for a given amount of properties, NGINX will still function however the changes won't be seen until it is reloaded via the command mentioned in the data file reload section above. - If the data file monitor/scheduler had been stopped, NGINX will no longer acknowledge the JSON updates, even if it is been started again. It is important that the file monitor/scheduler command starts before NGINX does. <a name="example_configs"></a> ### Example configurations ### There are multiple NGINX configuration files and snippets available at `Nginx-Mod/Examples` folder for common use-cases. - Hello World - Non-human traffic detection - Non-human traffic detection (with LUA) - Image optimization - Mobile redirection - Language redirection - Passing variables downstream as headers #### Common settings (for all examples) #### All examples below requires following configuration. ** In main block (i.e. outside of `HTTP` and `server` blocks): ** ``` # Load DeviceAtlas module (Dynamic module option only) load_module /path/to/ngx_http_deviceatlas_module.so; ``` ** In HTTP block: ** ``` variables_hash_max_size 2048; devatlas_db /path/to/DeviceAtlas.json; ``` #### EXAMPLE 1 - Hello World #### This is a very simple configuration to ensure that DeviceAtlas is working (like Hello World, for the module). ``` http { include mime.types; default_type application/octet-stream; variables_hash_max_size 4096; sendfile on; devatlas_db "/etc/deviceatlas/DeviceAtlas.json"; # Assign DeviceAtlas properties as maps. # (It's recommended to always use default values as follows.) map $da_primaryHardwareType $primaryHardwareType { default $da_primaryHardwareType; "" "No primary hardware type available"; } server { listen 8080; server_name localhost; # The path `/DeviceAtlasTest` will return a plain text response # containing the device type e.g. "Mobile Phone" or "Tablet". location /DeviceAtlasTest { add_header Content-Type text/plain; return 200 $primaryHardwareType; } } } ``` #### EXAMPLE 2 - Non-human traffic detection #### This configuration shows how you might treat traffic for known bots. In this case they are redirected to a separate page but alternate content could be served also. ** In server block: ** ``` location / { default_type 'text/plain'; content_by_lua_block { /* General property to detect all types of bots traffic */ local isNonHumanTraffic = ( ngx.var.da_isRobot == "1" ) if isNonHumanTraffic then ngx.say("You are a bot !!") else /* Creation of an environement variable to store the hardware type */ local primaryHardwareType = 'generic' if ngx.var.da_primaryHardwareType then primaryHardwareType = ngx.var.da_primaryHardwareType end set_by_lua $primary_hardware_type primaryHardwareType end } } ``` #### EXAMPLE 3 - Non-human traffic detection (with LUA) #### This configuration shows how you might treat traffic for known bots. In this case they are redirected to a separate page but alternate content could be served also. ** In HTTP block: ** ``` http { include mime.types; default_type application/octet-stream; variables_hash_max_size 4096; sendfile on; # Assign DeviceAtlas properties as maps. # (It's recommended to always use default values as follows.) map $da_isRobot $robot { true 1; false 0; default 0; } server { # Redirect robots to scraping policy page if ($robot) { rewrite ^ http://www.example.com/scrapingpolicy.html; } } } ``` #### EXAMPLE 4 - Image optimization - part #1 #### This example demonstrates how you might resize images to maximum display size for a given device. Desktop devices will see no change, mobile devices should be served images appropriate to their resolutions. Resized images are cached to ensure minimal load on server (one resize operation per target resolution). This is part #1, see part #2 for an actual resizer listening at port 9000. ** In HTTP block: ** ``` # Assign DeviceAtlas properties as maps. # (It's recommended to always use default values as follows.) map $da_usableDisplayWidth $displayWidth { default $da_usableDisplayWidth; "" "-"; } map $da_usableDisplayHeight $displayHeight { default $da_usableDisplayHeight; "" "-"; } ``` ** In server block: ** ``` location ~* "^/images/(.*)" { set $image_path $1; proxy_pass http://127.0.0.1:9000/image_resizer/$image_path?width=$displayWidth&height=$displayHeight; proxy_cache resized; proxy_cache_valid 180m; } ``` #### Image optimization - part #2 #### ** In HTTP block: ** ``` limit_req_zone "1" zone=2persec:32k rate=2r/s; ``` ** In server block: ** ``` listen 9000; allow 127.0.0.1; deny all; limit_req zone=2persec burst=10; location /image_resizer { alias /usr/share/nginx/html/path/to/images; image_filter resize $arg_width $arg_height; } ``` #### EXAMPLE 5 - Mobile redirection #### This example demonstrates how you might rewrite URLS to serve mobile-specific content (e.g. AMP pages) to certain device classes. ** In HTTP block: ** ``` # Assign DeviceAtlas properties as maps. # (It's recommended to always use default values as follows.) map $da_mobileDevice $mobile { true 1; false 0; default 0; } ``` ** In server block: ** ``` # Send mobile device to AMP version of content if ($mobile) { rewrite ^(.*)$ /AMP/$1 break; } ``` #### EXAMPLE 6 - Language redirection #### This example demonstrates how you might rewrite URLS based on a client's detected language. ** In HTTP block: ** ``` # Assign DeviceAtlas properties as maps. # (It's recommended to always use default values as follows.) map $da_language $language { default $da_language; "" "en"; } ``` ** In server block: ** ``` location / { rewrite ^(.*)$ /$language/$1; } ``` #### EXAMPLE 7 - Passing variables downstream as headers #### This example demonstrates how you might pass variables downstream via request headers. ** In server block (LB): ** ``` location /DeviceAtlasTest { proxy_set_header X-DeviceAtlas-isMobilePhone $da_isMobilePhone; proxy_pass http://localhost:8080; } ``` ** In HTTP block (downstream): ** In case of underscore headers, set by LB, an extra directive is needed to be set on the downstream servers. e.g. proxy_set_header DA_IS_MOBILE_PHONE $da_isMobilePhone; This is not needed if the header names are set with dashes as `X-DeviceAtlas-<variable>` (as is in the example above). ``` underscores_in_headers on; ``` #### Data file #### To use the module, you will need to download a data file, save it locally and set the path from **deviceatlas.nginx.conf** using `devatlas_db` directive. To get a data file for Device Detection, please see the [DeviceApi](README.DeviceApi.html) readme file. #### Run NGINX server #### ```sh /usr/local/nginx/sbin/nginx -c <DeviceAtlas C API folder>/Nginx-Mod/Example/deviceatlas.nginx.conf ``` NGINX is now up and running on port 8080. ### Optional ### ** Client-side Component ** To use the Client-side Component with the NGINX module, the client-side library may be included on a webpage by adding the following snippet: ```Javascript <!-- Adding the DeviceAtlas client side component to get client side properties --> <script type="text/javascript" src="https://cs.deviceatlas-cdn.com/dacs.js" async></script> ``` The component will create a cookie with the client properties. The NGINX module automatically reads this cookie and merges the properties with the server side detected properties. For additional information, please see the [Client Side Library](https://docs.deviceatlas.com/apis/clientside/latest/README.ClientSide.html) documentation. <a name="example_app"></a> ### Example application (Python FCGI) ### An example application is packaged with DeviceAtlas nginx-module. Check README in example to view build and run instructions. <a name="debugging"></a> ### Debugging ### ** Is DeviceAtlas module running? ** The simplest way to confirm if the DeviceAtlas module is loaded successfully is by looking in NGINX's error.log (typically in `/var/log/nginx/error.log` or `/usr/local/nginx/logs/error/log`). It will have "[notice]" lines similar to the following in it: ``` [notice] DeviceAtlas cookie name 'DAPROPS' in /etc/nginx/nginx.conf:57 [notice] DeviceAtlas, environment variable 'da_primaryHardwareType' added (property 'primaryHardwareType') in /etc/nginx/nginx.conf:57 [notice] DeviceAtlas, environment variable 'da_osName' added (property 'osName') in /etc/nginx/nginx.conf:57 [notice] DeviceAtlas, environment variable 'da_osVersion' added (property 'osVersion') in /etc/nginx/nginx.conf:57 [notice] Carrier, environment variable 'da_networkOperator' added (property 'networkOperator') in /etc/nginx/nginx.conf:57 [notice] Carrier, environment variable 'da_networkBrand' added (property 'networkBrand') in /etc/nginx/nginx.conf:57 [notice] Carrier, environment variable 'da_mcc' added (property 'mcc') in /etc/nginx/nginx.conf:57 [notice] Carrier, environment variable 'da_countryCode' added (property 'countryCode') in /etc/nginx/nginx.conf:57 ``` ** Checking DeviceAtlas values ** The easiest way to debug issues is to add the DeviceAtlas properties to the NGINX log file so that you can see their value for each request. ``` log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' '"$da_mobileDevice" "$da_vendor $da_model" ' '"$da_networkOperator" "$da_networkBrand" "$da_mcc"'; ``` <a name="common_errors"></a> ### Common errors ### ** Version compatibility ** To prevent the error below ensure that the version of the source NGINX matches the existing built NGINX version (e.g. provided by the OS). ```sh nginx: [emerg] module "/usr/lib/nginx/modules/ngx_http_deviceatlas_module.so" version 1011008 instead of 1011005 ``` ** Binary compatibility ** To prevent the error below make sure the same configuration flags used by the OS's NGINX binary version were used while building the dynamic module. ```sh nginx: [emerg] module "/usr/lib/nginx/modules/ngx_http_deviceatlas_module.so" is not binary compatible in /etc/nginx/nginx.conf ``` Note: if the nginx binary had been compiled with `--with-compat` flag, it is necessary to apply it for the dynamic module. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - _ Copyright (c) DeviceAtlas Limited 2024. All Rights Reserved. _ https://deviceatlas.com <!-- HTML+JS for document formatting when opened in browser --> <div class="btn-group" id="main-menu" style="float:right"><a class="btn dropdown-toggle" data-toggle="dropdown" href="#">Menu<span class="caret"></span></a><ul class="dropdown-menu"><li><a href="README.html">Main</a></li><li><a href="README.DeviceApi.html">Device API Usage C</a></li><li><a href="README.ClientHints.html">Client Hints Support</a></li><li><a href="README.CarrierApi.html">Carrier Identification API</a></li><li><a href="README.Upgrade.html">Device API Upgrade</a></li><li><a href="README.Cpp.html">Device API C++</a></li><li class="disabled"><a href="README.Nginx.html">NGINX Module</a></li><li><a href="README.Apache2.html">Apache2 Module</a></li><li><a href="README.JsonConverter.html">Device Identification API C JSONConverter</a></li><li><a href="README.Scheduler.html">Device Identification API C file monitor and scheduler</a></li><li><a href="README.Go-DeviceApi.html">Device API Usage Go</a></li><li><a href="README.Go-Upgrade.html">Device API Upgrade Go</a></li><li><a href="README.Gpu.html">Device API and GPU/CPU interaction</a></li><li><a href="README.Interfacing.html">Device API Interfacing from other languages</a></li><li class="divider"></li><li><a href="./ApiDocs/index.html">C ApiDocs</a></li><li><a href="./ApiCppDocs/index.html">C ++ interface ApiDocs</a></li><li><a href="./Go-ApiDocs/carrier.html">Go ApiDocs Carrier</a></li><li><a href="./Go-ApiDocs/device.html">Go ApiDocs Device</a></li></ul></div>