rustaws-lambdaopensslcargo-lambda

Cargo lambda build does not find OpenSSL development headers


I'm trying to write a lambda function for AWS in Rust using cargo-lambda.

The example function generated by cargo lambda new builds fine when I call cargo lambda build --release --arm64.

However, when I try to build code I write by following the instructions and examples on https://github.com/awslabs/aws-lambda-rust-runtime & https://www.cargo-lambda.info/guide/getting-started.html, the build fails to find the headers for OpenSSL.

I already installed OpenSSL with pacman -S openssl and ran pacman -S linux-headers for good measure, and tried running export OPENSSL_LIB_DIR=/usr/lib/ and export OPENSSL_INCLUDE_DIR=/usr/include/openssl/ as well as setting them in my shell config file, but neither has worked.

I can't get past:

cargo:warning=build/expando.c:1:10: fatal error: 'openssl/opensslv.h' file not found
cargo:warning=#include <openssl/opensslv.h>

I have visually verified the file exists in the directory but for some reason openssl-sys-0.9.87 fails to find it and panics. Has anyone else encountered this, or does anyone have any ideas I can try? I'm not sure if the problem is with my cargo-lambda setup, my OpenSSL setup, my code, or something else entirely.

This code was generated with cargo lambda new project_name and builds fine:

use lambda_runtime::{run, service_fn, Error, LambdaEvent};
use serde::{Deserialize, Serialize};

/// Main function generated by cargo-lambda
#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        // disable printing the name of the module in every log line.
        .with_target(false)
        // disabling time is handy because CloudWatch will add the ingestion time.
        .without_time()
        .init();

    lambda_runtime::run(service_fn(function_handler)).await
}

/// This is a made-up example.
#[derive(Deserialize)]
struct Request {
    command: String,
}

/// This is a made-up example of what a response structure may look like.
#[derive(Serialize)]
struct Response {
    req_id: String,
    msg: String,
}

/// This is the main body for the function.
/// Write your code inside it.
async fn function_handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
    // Extract some useful info from the request
    let command = event.payload.command;

    // Prepare the response
    let resp = Response {
        req_id: event.context.request_id,
        msg: format!("Command {}.", command),
    };

    // Return `Response` (it will be serialized to JSON automatically by the runtime)
    Ok(resp)
}

This is the code I'm trying to build but getting an error from openssl-sys-0.9.87 even though it was written in the same project and follows the examples from the online documentation:

use lambda_runtime::{run, service_fn, Error, LambdaEvent};
use reqwest::blocking::Client;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};

#[tokio::main]
async fn main() -> Result<(), lambda_runtime::Error> {
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        // disable printing the name of the module in every log line.
        .with_target(false)
        // disabling time is handy because CloudWatch will add the ingestion time.
        .without_time()
        .init();

    let query = service_fn(query_handler);
    run(query).await
}

async fn query_handler(event: LambdaEvent<Value>) -> Result<Value, Error> {
    let (event, _context) = event.into_parts();
    let symbol = event["symbol"].to_string();
    let message = query_price(symbol)?;
    Ok(json!({ "message": format!("{}", message) }))
}

#[derive(Deserialize, Debug)]
#[allow(non_snake_case)]
struct PriceQuote {
    pub s: String,
    pub symbol: Vec<String>,
    pub ask: Vec<f32>,
    pub askSize: Vec<u32>,
    pub bid: Vec<f32>,
    pub bidSize: Vec<u32>,
    pub mid: Vec<f32>,
    pub last: Vec<f32>,
    pub volume: Vec<u32>,
    pub updated: Vec<u32>,
}

fn query_price(symbol: String) -> Result<String, reqwest::Error> {
    //let symbol = "AAPL";

    let url = format!("https://api.marketdata.app/v1/stocks/quotes/{}", symbol);
    let client = Client::new();
    let response = client.get(url).send()?;
    let price_quote: PriceQuote = response.json()?;

    let symbol: &String = &price_quote.symbol[0];
    let last_price: &f32 = &price_quote.last[0];

    Ok(format!("Last price for {} is {}", symbol, last_price).to_string())
}

Why would one build but not the other? Are the dependencies I added messing with it? Do I have the wrong OpenSSL version? Did I make a mistake? What am I missing? Sorry if these are dumb questions, I'm new to AWS and my head is spinning.


Solution

  • Unsure of exactly what went wrong, but it seems the reqwest crate was the root of the trouble. I've never had this problem except when building via cargo-lambda so I imagine it has something to do with Zig?

    Either way, it can be solved by adding openssl directly to Cargo.toml:

    openssl = { version = "0.10.35", features = ["vendored"] }
    

    This seems to provide the correct linkage for reqwest and allows cargo lambda to build. Haven't tested if reqwest still works but I see no reason it shouldn't.

    Update: The function deploys and reqwest works fine