首先,承認本人對于http協議認知確實不夠,從來沒有仔細研究這一塊。
其次,這回確實要把自己十幾年的理解更新一下了,主要還是自己過去沒有認真研究過http協議。
這一次是這么回事,碰到一個情況,要在一次消息中傳輸文本和照片,這個按說很常見,過去寫網頁都是表單中就直接用了,也沒有碰到過自己封裝協議的情況。
這回呢,因為客戶端是單片機,單片機這塊之前也是單獨提交要么文本,要么圖片,沒有一次性傳過多種數據。
然后呢,我讓kimi給我代碼,結果給我的是流指針的方式
kimi錯誤方案一:
// 獲取底層 WiFiClient 對象WiFiClient *stream = http.getStreamPtr();if (stream) {// 發送文本部分stream->print(body);// 發送圖片數據stream->write(current_fb->buf, current_fb->len);// 發送結束邊界stream->print(endBoundary);
WiFiClient 流指針在esp32單片機實測,壓根沒法正確初始化,至于原因,就不想找了,怎么測都是不行的,初始化后都是false。既然庫中有這個功能,按說是能用的,也可能是我單片機中性芯片的問題。
下來事情呢,我就想自己拼接字符串給服務器。
kimi又給我錯誤方案二:
// 構建請求體String body;body += "--" + boundary + "\r\n";body += "Content-Disposition: form-data; name=\"message\"\r\n\r\n";body += photo_text + "\r\n";body += "--" + boundary + "\r\n";body += "Content-Disposition: form-data; name=\"image\"; filename=\"photo.jpg\"\r\n";body += "Content-Type: image/jpeg\r\n\r\n";String endBoundary = "\r\n--" + boundary + "--\r\n";// 計算總請求體長度size_t totalLen = body.length() + current_fb->len + endBoundary.length();// 設置請求體長度http.addHeader("Content-Length", String(totalLen));// 構建完整的請求體String fullBody = body;fullBody += String((char*)current_fb->buf, current_fb->len); // 直接將圖片的二進制數據追加到字符串中
因為? fullBody += String((char*)current_fb->buf, current_fb->len); 其實實際加入協議的是字符串化的二進制,php服務器端?$_FILES['image'];讀取的是二進制,所以肯定對不上,就會報錯誤3.
這個事情說明,不要完全依賴于AI,他會反復給一個錯誤的方向,你怎么都調不通,最終沒有辦法,只好自己親自下場把內容研究透徹。
經過我和kimi的唇槍舌戰,kimi終于開竅了,給我了第三個方案。【這里我要罵AI了】
// 發送HTTP POST請求Serial.println("Sending HTTP POST request");HTTPClient http;http.begin(serverURL);String boundary = "----ESP32_CAMERA_BOUNDARY";http.addHeader("Content-Type", "multipart/form-data; boundary=" + boundary);http.addHeader("User-Agent", "ESP32-Camera");http.setTimeout(60000);// 構建請求體的文本部分String body;body += "--" + boundary + "\r\n";body += "Content-Disposition: form-data; name=\"message\"\r\n\r\n";body += photo_text + "\r\n";body += "--" + boundary + "\r\n";body += "Content-Disposition: form-data; name=\"image\"; filename=\"photo.jpg\"\r\n";body += "Content-Type: image/jpeg\r\n\r\n";String endBoundary = "\r\n--" + boundary + "--\r\n";// 計算總請求體長度size_t totalLen = body.length() + current_fb->len + endBoundary.length();// 設置請求體長度http.addHeader("Content-Length", String(totalLen));// 構建完整的請求體http.addHeader("Content-Type", "multipart/form-data; boundary=" + boundary);// 發送請求http.begin(serverURL);int httpCode = http.sendRequest("POST", body.c_str(), body.length());if (httpCode == 200) {// 文本部分發送成功,現在發送圖片數據http.write(current_fb->buf, current_fb->len);http.write(endBoundary.c_str(), endBoundary.length());Serial.println("Image uploaded successfully");} else {Serial.println("Failed to send text part of the request");}http.end();Serial.println("arduino HTTP request completed");
這是一個通過兩次發送數據,達到目的的一個方案,這個我是可以接受的,畢竟在同一個函數任務中,但是這么一個方案明明很簡單為啥AI給我搞了兩天,我和他激辯很久,才告訴我。。。好吧,果然人還是要靠自己。
其實吧,這個事情還有很多解決方案,比如base64,這是經常用的,再比如使用ArduinoHttpClient庫,只是這次就想死磕多類型數據協議封裝,才搞了好幾天。
=============================
說一下最終決定寫本文的目的吧。之所以被AI的這幾套方案套了三天了,根源在于自己對于http的理解有問題。也是因為沒有完全了解它。
現在更正一下我的認知,我一直以為http就是一去一回的,就是送過去,返回結果。
因為用慣了api什么的,寫慣了網頁,之前都是這么用的,最慚愧的是以前給小弟也是這么講的(此處臉部溫度180度)。盡管我很早,在20年前就知道長連接這個事情,但是默認的訪問我一直認知上認為是一去一回,然后通訊中斷。
直到今天(再次臉紅),我和AI溝通了原因,http1.0的協議確實默認不支持長連接,都是一去一回就完事了。本人最早用http差不多在2003年,而http1.1是1997年就出來了,所以最早的對瀏覽器的認知確實是有問題的,十幾年來一直認為默認就是一去一回,而現在的協議肯定都是http1.1的,問了ai默認都是http1.1,也就是早就支持默認長連接,斷開的話需要代碼控制斷開。
這次去底層拼接,才搞明白這個事,真的是愚鈍了。(臉紅+1)