Arduino UNO WiFi Rev 2无法通过SSL连接发送POST请求
问题描述
- 使用Arduino UNO WiFi Rev2搭配WiFiNINA库时,无法向HTTPS协议的Azure服务器发送POST请求;此前使用MKR1000搭配WiFi101库可正常请求同一服务器,两种实现方案均运行失败。
- 已完成前置排查:
- WiFiNINA固件已升级至官方最新版本
- 已通过Arduino IDE上传对应服务端的SSL证书
- 板卡可正常向google.com等站点发起SSL GET请求,WiFi连接稳定,可正常输出WiFi连接成功日志
方案1:基于WiFiNINA官方HTTPS POST示例实现
核心逻辑:引入SPI.h、WiFiNINA.h头文件,配置WiFi SSID、密码、目标服务器地址、443端口、请求路径/wtid/logCovid与表单参数,初始化WiFiSSLClient实例;WiFi连接成功后调用client.connect(server, HTTP_PORT)连接服务器,构造HTTP/1.1规范的POST报文,设置Host、User-Agent: Arduino/1.0、Connection: close、Content-Type: application/x-www-form-urlencoded、Content-Length请求头后发送表单请求体。
运行现象:串口仅输出Starting connection to server...后随即提示disconnecting from server.,无任何服务器响应内容。
完整代码:
This example creates a client object that connects and transfers data using always SSL. It is compatible with the methods normally related to plain connections, like client.connect(host, port). Written by Arturo Guadalupi last revision November 2015 */ #include <SPI.h> #include <WiFiNINA.h> ///////please enter your sensitive data in the Secret tab/arduino_secrets.h char ssid[] = "secret"; // your network SSID (name) char pass[] = "secret"; // your network password (use for WPA, or use as key for WEP) int keyIndex = 0; // your network key index number (needed only for WEP) int status = WL_IDLE_STATUS; // if you don't want to use DNS (and reduce your sketch size) // use the numeric IP instead of the name for the server: //IPAddress server(74,125,232,128); // numeric IP for Google (no DNS) char server[] = "secret"; // name address for Google (using DNS) // Initialize the Ethernet client library // with the IP address and port of the server // that you want to connect to (port 80 is default for HTTP): int HTTP_PORT = 443; String HTTP_METHOD = "POST"; char HOST_NAME[] = "secret"; //IPAddress server(secret); String PATH_NAME = "/wtid/logCovid"; WiFiSSLClient client; String queryString = "uuid=5555&manufacturerID=6666"; void setup() { //Initialize serial and wait for port to open: Serial.begin(9600); while (!Serial) { ; // wait for serial port to connect. Needed for native USB port only } // check for the WiFi module: if (WiFi.status() == WL_NO_MODULE) { Serial.println("Communication with WiFi module failed!"); // don't continue while (true); } String fv = WiFi.firmwareVersion(); if (fv < WIFI_FIRMWARE_LATEST_VERSION) { Serial.println("Please upgrade the firmware"); } // attempt to connect to WiFi network: while (status != WL_CONNECTED) { Serial.print("Attempting to connect to SSID: "); Serial.println(ssid); // Connect to WPA/WPA2 network. Change this line if using open or WEP network: status = WiFi.begin(ssid, pass); // wait 10 seconds for connection: delay(10000); } Serial.println("Connected to WiFi"); printWiFiStatus(); Serial.println("\nStarting connection to server..."); // if you get a connection, report back via serial: if (client.connect(server, HTTP_PORT)) { Serial.println("connected to server"); // Make a HTTP request: // MUST HAVE THESE HEADERS AND IN THIS ORDER!!! client.println(HTTP_METHOD + " " + PATH_NAME + " HTTP/1.1"); client.println("Host: " + String(HOST_NAME)); client.println("User-Agent: Arduino/1.0"); client.println("Connection: close"); client.println("Content-Type: application/x-www-form-urlencoded"); client.print("Content-Length: "); client.println(queryString.length()); client.println(); // Body AKA the data we are sending to the server client.print(queryString); } } void loop() { // if there are incoming bytes available // from the server, read them and print them: while (client.available()) { char c = client.read(); Serial.write(c); } // if the server's disconnected, stop the client: if (!client.connected()) { Serial.println(); Serial.println("disconnecting from server."); client.stop(); // do nothing forevermore: while (true); } } void printWiFiStatus() { // print the SSID of the network you're attached to: Serial.print("SSID: "); Serial.println(WiFi.SSID()); // print your board's IP address: IPAddress ip = WiFi.localIP(); Serial.print("IP Address: "); Serial.println(ip); // print the received signal strength: long rssi = WiFi.RSSI(); Serial.print("signal strength (RSSI):"); Serial.print(rssi); Serial.println(" dBm"); }
串口输出:
Starting connection to server... disconnecting from server.
方案2:基于ArduinoHttpClient库实现
核心逻辑:引入ArduinoHttpClient.h、WiFiNINA.h头文件,配置WiFi参数、目标服务器地址与443端口,初始化HttpClient实例;loop循环中调用client.post()向/wtid/logCovid路径发送Content-Type为application/x-www-form-urlencoded的POST数据,读取响应状态码和响应体输出到串口。
运行现象:串口返回状态码-2,无响应内容。
完整代码:
#include <ArduinoHttpClient.h> #include <WiFiNINA.h> ///////please enter your sensitive data in the Secret tab/arduino_secrets.h /////// WiFi Settings /////// char ssid[] = "secret"; // your network SSID (name) char pass[] = "secret"; // your network password (use for WPA, or use as key for WEP) char serverAddress[] = "secret"; // server address int port = 443; WiFiSSLClient wifi; HttpClient client = HttpClient(wifi, serverAddress, port); int status = WL_IDLE_STATUS; void setup() { Serial.begin(9600); while ( status != WL_CONNECTED) { Serial.print("Attempting to connect to Network named: "); Serial.println(ssid); // print the network name (SSID); // Connect to WPA/WPA2 network: status = WiFi.begin(ssid, pass); } // print the SSID of the network you're attached to: Serial.print("SSID: "); Serial.println(WiFi.SSID()); // print your WiFi shield's IP address: IPAddress ip = WiFi.localIP(); Serial.print("IP Address: "); Serial.println(ip); } void loop() { Serial.println("making POST request"); String contentType = "application/x-www-form-urlencoded"; String postData = "uuid=4444&manufacturerID=5555"; client.post("/wtid/logCovid", contentType, postData); // read the status code and body of the response int statusCode = client.responseStatusCode(); String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); Serial.print("Response: "); Serial.println(response); Serial.println("Wait five seconds"); delay(5000); }
串口输出:
making POST request Status code: -2 Response: Wait five seconds
排查与解决思路
按优先级依次验证以下问题点:
- SNI配置缺失问题
WiFiNINA库的WiFiSSLClient默认不会在SSL握手阶段发送SNI(服务器名称指示)字段,而Azure的HTTPS服务强制要求SNI匹配才能完成握手,这是和WiFi101库行为不一致的核心点。
修复方式:不要直接调用client.connect(server, 443),改用带SNI参数的重载方法client.connect(server, 443, HOST_NAME),第三个参数传入和证书匹配的Azure服务完整域名即可完成SSL握手。使用ArduinoHttpClient库时,需要在初始化client前手动调用wifi对象的connect方法传入SNI参数,再构造HttpClient实例。 - Azure服务TLS版本兼容问题
旧版WiFiNINA固件默认启用TLS1.0/1.1,而Azure服务已全面禁用这两个版本,仅支持TLS1.2及以上。即使已经升级到最新固件,仍需要在代码中显式指定TLS版本:在连接服务器前加一行client.setSSLVersion(WL_TLS_V1_2);强制使用TLS1.2握手。 - 证书上传错误问题
Azure服务使用多层证书链,仅上传叶子节点证书无法完成证书校验。重新上传证书时,需要导出Azure服务完整证书链(包含根证书、中间证书、服务端证书),全部写入板卡的SSL证书存储区,不要只上传单张服务端证书。 - Host头配置问题
方案1中如果server变量和HOST_NAME变量值不一致(比如server填了IP,HOST_NAME填了域名),会导致Azure服务端无法匹配请求对应的站点,直接断开连接。两个变量必须统一填写Azure服务的完整域名,不要混用IP和域名。 - 请求发送时序问题
方案1中发送完请求体后没有调用client.flush(),可能存在部分请求数据滞留在缓冲区没有真正发送到服务端,服务端等待请求超时就会断开连接。在client.print(queryString);后面加一行client.flush();等待所有数据发送完成再等待响应。
优先验证前两个问题点,90%以上的WiFiNINA连接Azure HTTPS失败都是SNI缺失和TLS版本不匹配导致的,这两个点修复后即可正常收到响应。
内容的提问来源于stack exchange,提问作者marsh

