WURFL Microservice Client API for Rust

Note: This guide assumes that you have access to a running instance of the WURFL Microservice HTTP Server, either through ScientiaMobile's Docker Registry, the AWS Marketplace, the Azure Marketplace, or other distribution channels. The guide also assumes familiarity with Device Detection (WURFL) and WURFL Capabilities.


Pre requisites

To compile and run the wmclient you need Rust 1.48 or above and the Cargo toolchain (see Rust getting started.)

WURFL Capabilities

WURFL Capabilities: WURFL capabilities are device properties represented in WURFL. While users of the WURFL Microservice for Docker have access to the list of licensed capabilities, customers who obtained the product through one of the Marketplace will have access to the WURFL capability list, predefined for the product they have licensed. Please note that there are two types of capabilities supported in WURFL: Static and Virtual.

While the difference between the two is mostly immaterial for you as a user as far as their practical usage goes, you still need to use two separate methods to use one or the other.

WURFL Microservice Client API (wmclient): Given an HTTP Request and a capability, the WURFL Client will return the property value.

Note: While the wmclient is an API, it requires interaction with the WURFL Microservice server to work. This introduces some latency (hugely mitigated by a built-in caching layer). For this reason, ScientiaMobile does not refer to the WURFL Microservice Client (wmclient) as a "WURFL API". That name is reserved for the WURFL OnSite APIs.


Obtaining, Installing and Running the wmclient


In order to use the wmclient please follow these instructions:

  1. Clone the WURFL repository.

  2. Edit example.rs (in the package) by modifying the WmClient::new() call to include the public IP address of your AMI instance, Azure VM instance or Docker container service (i.g. replacing localhost with the IP address of your running HTTP server, be it an AMI instance, Azure VM or Docker container service).

Note: There are multiple ways in which a service deployed through Docker can be exposed to the world around it. Please ask your resident devOp, if you are in doubt about the actual address at which the service is running.*

  1. Run the example with cargo run --color=always --package wmclient --example example.
use std::collections::HashMap;
use wmclient::WmClient;

fn main() {
    // Let's create the WURFL microservice client by setting the connection data of out WURFL Microservice server
    let client_res = WmClient::new("http", "localhost", "8080", "");
    // Client is mutable because because some its internal can be modified depending on its interaction with the user or
    // the server.
    let mut client: WmClient;
    if client_res.is_ok(){
        client = client_res.unwrap();
        println!("-----------------------------------------------------------------------------------");
        println!("WURFL Microservice client created successfully. Rust client API version: {}", client.get_api_version());
        println!("-----------------------------------------------------------------------------------");
    } else {
        println!("Unable to create WURFL Microservice client: {}", client_res.err().unwrap().to_string());
        return;
    }
    // Let's add the caching layer to the client
    client.set_cache_size(10000);
    // Let's gather some server info.
    let info_res = client.get_info();
    if info_res.is_err(){
        println!("Unable to get server info. Exiting.");
        return;
    }
    let info = info_res.unwrap();
    println!("WURFL Microservice information:");
    println!("Server version: {}", info.wm_version);
    println!("WURFL API version: {}", info.wurfl_api_version);
    println!("WURFL file info: {}", info.wurfl_info);

    // set the capabilities we want to receive from WM server
    // Static capabilities
    let static_caps = vec! {"model_name brand_name"};
    client.set_requested_static_capabilities(Some(static_caps));
    // Virtual capabilities
    let virtual_caps = vec! {"is_smartphone form_factor"};
    client.set_requested_virtual_capabilities(Some(virtual_caps));

    // use this headers to perform a device detection.
    let mut headers = HashMap::new();
    headers.insert("Content-Type", "application/json");
    headers.insert("Accept", "text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1");
    headers.insert("Accept-Encoding", "gzip, deflate");
    headers.insert("Accept-Language", "en");
    headers.insert("Device-Stock-Ua", "Mozilla/5.0 (Linux; Android 8.1.0; SM-J610G Build/M1AJQ; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/69.0.3497.100 Mobile Safari/537.36");
    headers.insert("Forwarded", "for=\"110.54.224.195:36350\"");
    headers.insert("Referer", "https://www.cram.com/flashcards/labor-and-delivery-questions-889210");
    headers.insert("User-Agent", "Opera/9.80 (Android; Opera Mini/51.0.2254/184.121; U; en) Presto/2.12.423 Version/12.16");
    headers.insert("X-Clacks-Overhead", "GNU ph");
    headers.insert("X-Forwarded-For", "110.54.224.195, 82.145.210.235");
    headers.insert("X-Operamini-Features", "advanced, camera, download, file_system, folding, httpping, pingback, routing, touch, viewport");
    headers.insert("X-Operamini-Phone", "Android #");
    headers.insert("X-Operamini-Phone-Ua", "Mozilla/5.0 (Linux; Android 8.1.0; SM-J610G Build/M1AJQ; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/69.0.3497.100 Mobile Safari/537.36");

    let device_res = client.lookup_headers(headers);
    if device_res.is_err(){
        println!("Unable to detect device from the given HTTP headers: {}", device_res.err().unwrap().to_string());
        return;
    }
    // No error, let's get the device data
    let device = device_res.unwrap();
    let wurfl_id_opt = device.capabilities.get("wurfl_id");
    if wurfl_id_opt.is_some() {
        println!("-----------------------------------------------------------------------------------");
        println!("Sample device detection using sample headers");
        println!("WURFL device ID : {}", wurfl_id_opt.unwrap());
    }
    // If you are sure the capability you're querying exists and is in your required set, just unwrap the capability option
    println!("This device is a : {} {}", device.capabilities.get("brand_name").unwrap(), device.capabilities.get("model_name").unwrap());

    // check if device is a smartphone (a virtual capability)
    if device.capabilities.get("is_smartphone").unwrap() == "true" {
        println!("This is a smartphone")
    }
    println!("This device form_factor is: {}", device.capabilities.get("form_factor").unwrap());

    // Get all the device manufacturers, and print the first twenty
    let makes_res = client.get_all_device_makes();
    if makes_res.is_err() {
        let err_mk = makes_res.as_ref().err().unwrap();
        println!("Error getting device makes data {}", err_mk.to_string());
    }


    let mut device_makes = makes_res.unwrap();
    device_makes.sort();
    let limit = 20;
    println!("-----------------------------------------------------------------------------------");
    println!("Print the first {} brand_names of {} found", limit, device_makes.len());
    println!("-----------------------------------------------------------------------------------");

    for _i in 0..limit {
        println!("{}", device_makes.get(_i).unwrap());
    }

    // Now call the WM server to get all device model and marketing names produced by Apple
    let model_marketing_names_opt = client.get_all_devices_for_make("Apple".to_string());
    if model_marketing_names_opt.is_err(){
        let err_mmkt = model_marketing_names_opt.as_ref().err().unwrap();
        println!("Error getting device model and marketing data for Apple:  {}", err_mmkt.to_string());
    }

    let mut model_marketing_names = model_marketing_names_opt.unwrap();
    // Sort model_marketing_names structs by their model name, using natural ordering (thus Uppercase names come first, then lowercase)
    model_marketing_names.sort_by(|a,b| a.model_name.cmp(&b.model_name));
    println!("-----------------------------------------------------------------------------------");
    println!("Printing all model and marketing names for Apple brand");
    println!("-----------------------------------------------------------------------------------");
    for name in model_marketing_names{
        println!("- {} {}", name.model_name, name.marketing_name);
    }

    // Now call the WM server to get all operating system names
    println!("-----------------------------------------------------------------------------------");
    println!("Print the list of OSes");
    println!("-----------------------------------------------------------------------------------");
    let os_opt = client.get_all_oses();
    if os_opt.is_err(){
        let os_err = os_opt.as_ref().err().unwrap();
        println!("Unable to get the list of operating systems: {}", os_err.to_string());
    }
    let mut os_list = os_opt.unwrap();
    os_list.sort();
    for os in os_list {
        println!("- {}", os);
    }

    println!("-----------------------------------------------------------------------------------");
    println!("Print all version numbers for Android OS");
    println!("-----------------------------------------------------------------------------------");
    let android_ver_opt = client.get_all_versions_for_os("Android");
    if android_ver_opt.is_err(){
        let ver_err = android_ver_opt.as_ref().err().unwrap();
        println!("Unable to get versions for Android OS: {}", ver_err.to_string());
    }
    let android_versions = android_ver_opt.unwrap();
    for v in android_versions {
        println!("- {}", v);
    }
}

While the wmclient API exposes different lookup methods, if you are using device detection from code running as part of an HTTP server, it is highly recommended that you pass the complete HTTP request to the lookup_headers method as follows:

let device_res = lookup_headers(_req.headers());

where the _req variable is the request used in Hyper web framework, but it can be any struct that implements the IntoIterator<Item=(U, V)>, where U implements ToString trait and V implements AsRef<[u8]> trait.

This will provide optimal detection accuracy (see web_server_example.rs). For a complete reference of all static and virtual capabilities, you can refer to this document. Please note that WURFL Microservice for the AWS, Azure and GCP marketplaces comes with a predefined set of capabilities.

Complete reference of the wmclient API

As of Rust open source project standard, the complete API reference is available on docs.rs

Handle the case of Missing Capabilities

As mentioned above, depending on the configuration of your WURFL Microservice, one of the WURFL capabilities may not be available. In this case, the JSONDeviceData.Capabilities map will not contain an entry for that capability and caller will get the None value as content of the Option returned by the get call.

Presence of a capability can be checked with:

let cap_opt = device.capabilities.get("has_cellular_radio");

cap_opt is of type Option<&String>, and you can check whether the capability exist by using the method:

cap_opt.is_some()

If this method returns true, you can safely unwrap the option and get the capability value.

let cap_value = cap_opt.unwrap();

Additional capabilities can always be obtained by upgrading to greater version of the AMIs in the AWS Marketplace or by contacting ScientiaMobile to license additional capabilities for the Docker Image or other products.

Adding wmclient to your application project

You can add the wmclient APi to your Rust application by adding this line:

wmclient = "^0.1.0"

which will pick any version >= 0.1.0 and < 1.0.0. You can also specify a single version using the exact version number.




© 2021 ScientiaMobile Inc.
All Rights Reserved.

NOTICE: All information contained herein is, and remains the property of ScientiaMobile Incorporated and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to ScientiaMobile Incorporated and its suppliers and may be covered by U.S. and Foreign Patents, patents in process, and are protected by trade secret or copyright law. Dissemination of this information or reproduction of this material is strictly forbidden unless prior written permission is obtained from ScientiaMobile Incorporated.