当前位置: 首页 > java >正文

Golang实践录:在go中使用curl实现https请求

之前曾经在一个 golang 工程调用 libcur 实现 https的请求,当前自测是通过的。后来迁移到另一个小系统出现段错误,于是对该模块代码改造,并再次自测。

问题提出

大约2年前,在某golang项目使用libcurl进行https请求(参见容器《Golang实践录:go-curl的使用》),由于使用的docker镜像不支持glibc,又不想重新制作,且该功能不是核心的,因此,就没有上线。现在,另一个工程也使用这个模块,迁移代码后自测出现问题。

主要出错信息如下:

fatal error: unexpected signal during runtime execution
[signal SIGSEGV: segmentation violation code=0x80 addr=0x0 pc=0x7fa55160bbf4]

经定位,在调用curl_easy_perform函数时出错,回顾了curl一般写法,未发现问题,只好借助AI工具,一边提问一边搜索。

场景描述

本次重提https请求,主要是因为某个测试工程需要用https向另一个服务请求,该服务的证书固定了某个生产环境的IP,而又需要将该服务部署在测试环境,但测试环境无法使用证书,因此无法验证一些模块功能。为保证生产环境版本的正确,需要在测试环境解决证书请求问题。

在此之前,自己没有想到解决办法,问了AI,也没给出满意的回答(可能问的方式不恰当)。实际上,借助docker容器,可以很方便解决上述问题。

  • 创建docker网段,网段与生产环境的服务相同。
  • 利用容器部署上述测试工程和服务,两者在同一网段中,并且将部署服务的容器IP设置为生产环境的IP,这样使得https证书可用。
  • 在测试工程请求时,使用固定IP和固定URL请求。这样能够模拟在生产环境中的请求场景。

重新实现

核心文件代码如下:

/*
curlApi_linux.go
使用 curl 库封装的请求接口
为减少cgo开销,在 C 中实现完整的初始化、请求过程,使用静态变量减少内存碎片
编译、运行的系统必须有libcurl、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;       // 响应结果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;
}// 头部回调函数用于获取文件名
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); // 服务器URLcurl_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); // 调试信息curl_easy_setopt(curl, CURLOPT_SSLVERSION, 4);// 设置回调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);// 设置头部回调以获取文件名curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);curl_easy_setopt(curl, CURLOPT_HEADERDATA, resp_filename);// 执行请求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);// 成功则复制结果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 stringfilename stringretcode  int
}type MyCURL struct {url, postfile, cafile, clifile, keyfile stringtimeout                                 int
}func NewCurl() *MyCURL {return &MyCURL{timeout: 5000, // 默认超时}
}func (c *MyCURL) SetOpt(url, postfile, cafile, clientfile, keyfile string, timeout int) {c.url = urlc.postfile = postfilec.cafile = cafilec.clifile = clientfilec.keyfile = keyfilec.timeout = timeout
}func (c *MyCURL) PostFiledata(jsonStr []byte) CurlResponse {// 将 Go的json数据复制到C的内存中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分配的内存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))}()// 调用C函数并获取返回结构体cRet := C.perform_request(&params)defer func() {C.free(unsafe.Pointer(cRet.respBody))C.free(unsafe.Pointer(cRet.filename))}()// 转换为Go结构体return CurlResponse{respBody: C.GoString(cRet.respBody),filename: C.GoString(cRet.filename),retcode:  int(cRet.retcode),}
}

与上一版本对比,有如下调整:

  • #cgo linux pkg-config: libcurl改为#cgo linux LDFLAGS: -lcurl,对编译环境较友好一些。
  • 将全局变量改为局域变量,防止多线程情况下出现问题。
  • 上版本返回值使用换行符进行解析,现改为返回多个值(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.pemCApath: 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.1Host: 172.18.18.10:86Content-Length: 799Expect: 100-continueContent-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

小结

上述代码目前只在测试环境测试,后续择机在生产环境中使用。就测试结果看,应该是没有大问题的。

http://www.xdnf.cn/news/6033.html

相关文章:

  • 机器学习基础课程-5-课程实验
  • 【Lua】Redis 自增并设置有效期
  • Halcon案例(二):C#联合Halcon回形针以及方向
  • Lighthouse 自定义审计
  • 适用于 iOS 的 开源Ultralytics YOLO:应用程序和 Swift 软件包,用于在您自己的 iOS 应用程序中运行 YOLO
  • AI智能体 | 使用Coze一键制作“假如书籍会说话”视频,18个作品狂吸17.6万粉,读书博主新标杆!(附保姆级教程)
  • LeetCode 820 单词的压缩编码题解
  • Java多线程实现:Thread、Runnable与Callable详解
  • 双向长短期记忆网络-BiLSTM
  • 鸿蒙OSUniApp打造多功能图表展示组件 #三方框架 #Uniapp
  • 行项目违反范围截止值
  • electron结合vue,直接访问静态文件如何跳转访问路径
  • 【IPMV】图像处理与机器视觉:Lec11 Keypoint Features and Corners
  • 以太网供电(PoE)交换机与自愈网络功能:打卡系统的得力助手
  • 基于 Spring Boot 瑞吉外卖系统开发(十四)
  • Vue 和 React 状态管理的性能优化策略对比
  • 数据结构中的高级排序算法
  • Linux内核可配置的参数
  • 单片机-STM32部分:14、SPI
  • 查询公网IP地址的方法:查看自己是不是公网ip,附内网穿透外网域名访问方案
  • 构建优雅对象的艺术:Java 建造者模式的架构解析与工程实践
  • HarmonyOs开发之———使用HTTP访问网络资源
  • Eslint和perrier的作用
  • CSS盒子模型:Padding与Margin的适用场景与注意事项
  • npm 报错 gyp verb `which` failed Error: not found: python2 解决方案
  • 【漫话机器学习系列】259.神经网络参数的初始化(Initialization Of Neural Network Parameters)
  • 【Java面试题】——this 和 super 的区别
  • PHP黑白胶卷底片图转彩图功能 V2025.05.15
  • Stable Diffusion WebUI 插件大全:功能详解与下载地址
  • 【软件测试】:推荐一些接口与自动化测试学习练习网站(API测试与自动化学习全攻略)