go中使用curl實現(xiàn)https請求的示例代碼
之前曾經(jīng)在一個 golang 工程調(diào)用 libcur 實現(xiàn) https的請求,當前自測是通過的。后來遷移到另一個小系統(tǒng)出現(xiàn)段錯誤,于是對該模塊代碼改造,并再次自測。
問題提出
大約2年前,在某golang項目使用libcurl進行https請求(參見容器《Golang實踐錄:go-curl的使用》),由于使用的docker鏡像不支持glibc,又不想重新制作,且該功能不是核心的,因此,就沒有上線?,F(xiàn)在,另一個工程也使用這個模塊,遷移代碼后自測出現(xiàn)問題。
主要出錯信息如下:
fatal error: unexpected signal during runtime execution [signal SIGSEGV: segmentation violation code=0x80 addr=0x0 pc=0x7fa55160bbf4]
經(jīng)定位,在調(diào)用curl_easy_perform函數(shù)時出錯,回顧了curl一般寫法,未發(fā)現(xiàn)問題,只好借助AI工具,一邊提問一邊搜索。
場景描述
本次重提https請求,主要是因為某個測試工程需要用https向另一個服務請求,該服務的證書固定了某個生產(chǎn)環(huán)境的IP,而又需要將該服務部署在測試環(huán)境,但測試環(huán)境無法使用證書,因此無法驗證一些模塊功能。為保證生產(chǎn)環(huán)境版本的正確,需要在測試環(huán)境解決證書請求問題。
在此之前,自己沒有想到解決辦法,問了AI,也沒給出滿意的回答(可能問的方式不恰當)。實際上,借助docker容器,可以很方便解決上述問題。
- 創(chuàng)建docker網(wǎng)段,網(wǎng)段與生產(chǎn)環(huán)境的服務相同。
- 利用容器部署上述測試工程和服務,兩者在同一網(wǎng)段中,并且將部署服務的容器IP設置為生產(chǎn)環(huán)境的IP,這樣使得https證書可用。
- 在測試工程請求時,使用固定IP和固定URL請求。這樣能夠模擬在生產(chǎn)環(huán)境中的請求場景。
重新實現(xiàn)
核心文件代碼如下:
/*
curlApi_linux.go
使用 curl 庫封裝的請求接口
為減少cgo開銷,在 C 中實現(xiàn)完整的初始化、請求過程,使用靜態(tài)變量減少內(nèi)存碎片
編譯、運行的系統(tǒng)必須有l(wèi)ibcurl、libssh2等庫
*/
package mypostservice
/*
#cgo linux LDFLAGS: -lcurl
#cgo darwin LDFLAGS: -lcurl
#cgo windows LDFLAGS: -lcurl
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
static void GetDateTimeStr(char *buf, int len)
{
int Year = 0;
int Month = 0;
int Day = 0;
int Hour = 0;
int Minute = 0;
int Second = 0;
long mSecond = 0;
struct timeval theTime;
gettimeofday(&theTime, NULL);
struct tm * timeinfo = localtime(&(theTime.tv_sec));
Year = 1900 + timeinfo->tm_year;
Month = 1 + timeinfo->tm_mon;
Day = timeinfo->tm_mday;
Hour = timeinfo->tm_hour;
Minute = timeinfo->tm_min;
Second = timeinfo->tm_sec;
mSecond = theTime.tv_usec / 1000;
snprintf(buf, len, "%04d%02d%02d%02d%02d%02d%03ld",
Year, Month, Day, Hour, Minute, Second, mSecond);
}
typedef struct {
char *url;
char *postfile;
char *cafile;
char *clifile;
char *keyfile;
int timeout;
char *jsonStr;
int jsonLen;
} CRequestParams;
typedef struct {
char *data;
size_t len;
} CResponseData;
typedef struct {
char *respBody; // 響應結(jié)果
char *filename; // 響應文件名
int retcode; // 是否成功標志
} CReturnData;
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
CResponseData *mem = (CResponseData *)userp;
char *ptr = realloc(mem->data, mem->len + realsize + 1);
if(!ptr) return 0;
mem->data = ptr;
memcpy(&(mem->data[mem->len]), contents, realsize);
mem->len += realsize;
mem->data[mem->len] = 0;
return realsize;
}
// 頭部回調(diào)函數(shù)用于獲取文件名
static size_t header_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
char *header = strndup(ptr, size * nmemb);
char *filename = (char *)userdata;
// 從Content-Disposition頭部提取文件名
if(strstr(header, "Content-Disposition") != NULL) {
char *start = strstr(header, "filename=");
if(start) {
start += 9; // 跳過"filename="
char *end = strchr(start, ';');
if(!end) end = start + strlen(start);
// 去除可能的引號
if(*start == '"') start++;
if(*(end-1) == '"') end--;
strncpy(filename, start, end - start);
filename[end - start] = '\0';
}
}
free(header);
return size * nmemb;
}
static CReturnData perform_request(CRequestParams *params) {
CURL *curl;
CURLcode res;
CResponseData chunk = {0};
CReturnData ret = {0};
char resp_filename[128] = {0}; // 存儲文件名
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if(!curl) {
ret.respBody = strdup("curl_easy_init failed");
ret.retcode = -1;
goto cleanup;
}
// 設置基本選項
curl_easy_setopt(curl, CURLOPT_URL, params->url); // 服務器URL
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); // // 設置線程安全選項
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, (long)params->timeout); // 超時時間,單位為毫秒
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, (long)params->timeout);
// HTTPS設置
if(strncmp(params->url, "https://", 8) == 0) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L);
curl_easy_setopt(curl, CURLOPT_CAINFO, params->cafile);
curl_easy_setopt(curl, CURLOPT_SSLCERT, params->clifile);
curl_easy_setopt(curl, CURLOPT_SSLCERTPASSWD, "123456");
curl_easy_setopt(curl, CURLOPT_SSLKEY, params->keyfile);
curl_easy_setopt(curl, CURLOPT_SSLKEYPASSWD, "123456");
}
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); // 調(diào)試信息
curl_easy_setopt(curl, CURLOPT_SSLVERSION, 4);
// 設置回調(diào)
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
// 構建表單
struct curl_httppost *formpost = NULL;
struct curl_httppost *lastptr = NULL;
curl_formadd(&formpost, &lastptr,
CURLFORM_COPYNAME, "file",
CURLFORM_BUFFER, params->postfile,
CURLFORM_BUFFERPTR, params->jsonStr,
CURLFORM_BUFFERLENGTH, (long)params->jsonLen,
CURLFORM_CONTENTTYPE, "application/json",
CURLFORM_END);
curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
// 設置頭部回調(diào)以獲取文件名
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, resp_filename);
// 執(zhí)行請求
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
const char *err = curl_easy_strerror(res);
ret.respBody = malloc(strlen(err) + 32);
sprintf(ret.respBody, "curl_easy_perform failed: %s", err);
ret.retcode = -2;
goto cleanup;
}
// printf("debug %s %d resp data len: \n", __func__, __LINE__, chunk.len);
// 成功則復制結(jié)果
if(chunk.data) {
ret.respBody = strdup(chunk.data);
ret.filename = strdup(resp_filename);
ret.retcode = 0;
} else {
ret.respBody = strdup("No data received");
ret.retcode = -3;
}
cleanup:
if(chunk.data) free(chunk.data);
if(formpost) curl_formfree(formpost);
if(curl) curl_easy_cleanup(curl);
curl_global_cleanup();
return ret;
}
*/
import "C"
import (
"unsafe"
)
type CurlResponse struct {
respBody string
filename string
retcode int
}
type MyCURL struct {
url, postfile, cafile, clifile, keyfile string
timeout int
}
func NewCurl() *MyCURL {
return &MyCURL{
timeout: 5000, // 默認超時
}
}
func (c *MyCURL) SetOpt(url, postfile, cafile, clientfile, keyfile string, timeout int) {
c.url = url
c.postfile = postfile
c.cafile = cafile
c.clifile = clientfile
c.keyfile = keyfile
c.timeout = timeout
}
func (c *MyCURL) PostFiledata(jsonStr []byte) CurlResponse {
// 將 Go的json數(shù)據(jù)復制到C的內(nèi)存中
cJsonStr := C.CBytes(jsonStr)
defer C.free(cJsonStr)
params := C.CRequestParams{
url: C.CString(c.url),
postfile: C.CString(c.postfile),
cafile: C.CString(c.cafile),
clifile: C.CString(c.clifile),
keyfile: C.CString(c.keyfile),
timeout: C.int(c.timeout),
jsonStr: (*C.char)(cJsonStr), // 使用C分配的內(nèi)存
jsonLen: C.int(len(jsonStr)),
}
defer func() {
C.free(unsafe.Pointer(params.url))
C.free(unsafe.Pointer(params.cafile))
C.free(unsafe.Pointer(params.clifile))
C.free(unsafe.Pointer(params.keyfile))
}()
// 調(diào)用C函數(shù)并獲取返回結(jié)構體
cRet := C.perform_request(¶ms)
defer func() {
C.free(unsafe.Pointer(cRet.respBody))
C.free(unsafe.Pointer(cRet.filename))
}()
// 轉(zhuǎn)換為Go結(jié)構體
return CurlResponse{
respBody: C.GoString(cRet.respBody),
filename: C.GoString(cRet.filename),
retcode: int(cRet.retcode),
}
}
與上一版本對比,有如下調(diào)整:
- 將
#cgo linux pkg-config: libcurl改為#cgo linux LDFLAGS: -lcurl,對編譯環(huán)境較友好一些。 - 將全局變量改為局域變量,防止多線程情況下出現(xiàn)問題。
- 上版本返回值使用換行符進行解析,現(xiàn)改為返回多個值(go語言本身支持),代碼較友好。
測試
與curl請求有關的輸出信息如下:
* About to connect() to 172.18.18.10 port 86 (#4) * Trying 172.18.18.10... * Connected to 172.18.18.10 (172.18.18.10) port 86 (#4) * Initializing NSS with certpath: sql:/etc/pki/nssdb * CAfile: ../../../cert/all.pem CApath: none * SSL connection using ECDHE-RSA-AES256-GCM-SHA384 * Server certificate: * subject: CN=172.18.18.10 * start date: 2023-02-16 08:19:00 GMT * expire date: 2033-02-16 08:19:00 GMT > POST /mypost/foobar HTTP/1.1 Host: 172.18.18.10:86 Content-Length: 799 Expect: 100-continue Content-Type: multipart/form-data; boundary=----------------------------258acabf1379 < HTTP/1.1 100 Continue < HTTP/1.1 200 OK < Server: nginx/1.16.1 < Date: Sun, 14 May 2025 18:20:48 GMT < Content-Type: application/json < Content-Length: 1083 < Connection: keep-alive < Content-Disposition: form-data;filename=bar.json < * Connection #4 to host 172.18.18.10 left intact
小結(jié)
上述代碼目前只在測試環(huán)境測試,后續(xù)擇機在生產(chǎn)環(huán)境中使用。就測試結(jié)果看,應該是沒有大問題的。
到此這篇關于go中使用curl實現(xiàn)https請求的示例代碼的文章就介紹到這了,更多相關go實現(xiàn)https請求內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

