设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间01 现在时间01 缓存数据 当你走完一段之后回头看,你会发现,那些真正能被记得的事真的是没有多少,真正无法忘记的人屈指可数,真正有趣的日子不过是那么一些,而真正需要害怕的也是寥寥无几。

当你走完一段之后回头看,你会发现,那些真正能被记得的事真的是没有多少,真正无法忘记的人屈指可数,真正有趣的日子不过是那么一些,而真正需要害怕的也是寥寥无几。

查看: 380|回复: 0

go中使用curl实现https请求的示例代码

[复制链接]

  离线 

TA的专栏

  • 打卡等级:即来则安
  • 打卡总天数:15
  • 打卡月天数:0
  • 打卡总奖励:243
  • 最近打卡:2023-08-27 04:33:47
等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
37
主题
29
精华
0
金钱
354
积分
78
注册时间
2023-8-12
最后登录
2025-9-8

发表于 2025-9-2 16:23:50 | 显示全部楼层 |阅读模式
之前曾经在一个 golang 工程调用 libcur 实现 https的请求,当前自测是通过的。后来迁移到另一个小系统出现段错误,于是对该模块代码改造,并再次自测。

问题提出

大约2年前,在某golang项目使用libcurl进行https请求(参见容器《Golang实践录:go-curl的使用》),由于使用的docker镜像不支持glibc,又不想重新制作,且该功能不是核心的,因此,就没有上线。现在,另一个工程也使用这个模块,迁移代码后自测出现问题。
主要出错信息如下:
  1. fatal error: unexpected signal during runtime execution
  2. [signal SIGSEGV: segmentation violation code=0x80 addr=0x0 pc=0x7fa55160bbf4]
复制代码
经定位,在调用
  1. curl_easy_perform
复制代码
函数时出错,回顾了curl一般写法,未发现问题,只好借助AI工具,一边提问一边搜索。

场景描述

本次重提https请求,主要是因为某个测试工程需要用https向另一个服务请求,该服务的证书固定了某个生产环境的IP,而又需要将该服务部署在测试环境,但测试环境无法使用证书,因此无法验证一些模块功能。为保证生产环境版本的正确,需要在测试环境解决证书请求问题。
在此之前,自己没有想到解决办法,问了AI,也没给出满意的回答(可能问的方式不恰当)。实际上,借助docker容器,可以很方便解决上述问题。

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

重新实现

核心文件代码如下:
  1. /*
  2. curlApi_linux.go
  3. 使用 curl 库封装的请求接口
  4. 为减少cgo开销,在 C 中实现完整的初始化、请求过程,使用静态变量减少内存碎片
  5. 编译、运行的系统必须有libcurl、libssh2等库
  6. */

  7. package mypostservice

  8. /*
  9. #cgo linux LDFLAGS: -lcurl
  10. #cgo darwin LDFLAGS: -lcurl
  11. #cgo windows LDFLAGS: -lcurl
  12. #include <stdlib.h>
  13. #include <string.h>
  14. #include <curl/curl.h>

  15. static void GetDateTimeStr(char *buf, int len)
  16. {
  17.         int Year = 0;
  18.         int Month = 0;
  19.         int Day = 0;
  20.         int Hour = 0;
  21.         int Minute = 0;
  22.         int Second = 0;
  23.         long mSecond = 0;

  24.         struct timeval theTime;
  25.         gettimeofday(&theTime, NULL);
  26.         struct tm * timeinfo = localtime(&(theTime.tv_sec));

  27.         Year   = 1900 + timeinfo->tm_year;
  28.         Month  = 1 + timeinfo->tm_mon;
  29.         Day    = timeinfo->tm_mday;

  30.         Hour   = timeinfo->tm_hour;
  31.         Minute = timeinfo->tm_min;
  32.         Second = timeinfo->tm_sec;
  33.         mSecond = theTime.tv_usec / 1000;

  34.         snprintf(buf, len, "%04d%02d%02d%02d%02d%02d%03ld",
  35.                 Year, Month, Day, Hour, Minute, Second, mSecond);
  36. }

  37. typedef struct {
  38.     char *url;
  39.     char *postfile;
  40.         char *cafile;
  41.     char *clifile;
  42.     char *keyfile;
  43.     int timeout;
  44.     char *jsonStr;
  45.     int jsonLen;
  46. } CRequestParams;

  47. typedef struct {
  48.     char *data;
  49.     size_t len;
  50. } CResponseData;

  51. typedef struct {
  52.     char *respBody;       // 响应结果
  53.     char *filename;     // 响应文件名
  54.     int retcode;        // 是否成功标志
  55. } CReturnData;

  56. static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
  57.     size_t realsize = size * nmemb;
  58.     CResponseData *mem = (CResponseData *)userp;

  59.     char *ptr = realloc(mem->data, mem->len + realsize + 1);
  60.     if(!ptr) return 0;

  61.     mem->data = ptr;
  62.     memcpy(&(mem->data[mem->len]), contents, realsize);
  63.     mem->len += realsize;
  64.     mem->data[mem->len] = 0;

  65.     return realsize;
  66. }

  67. // 头部回调函数用于获取文件名
  68. static size_t header_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
  69.     char *header = strndup(ptr, size * nmemb);
  70.     char *filename = (char *)userdata;

  71.     // 从Content-Disposition头部提取文件名
  72.     if(strstr(header, "Content-Disposition") != NULL) {
  73.         char *start = strstr(header, "filename=");
  74.         if(start) {
  75.             start += 9; // 跳过"filename="
  76.             char *end = strchr(start, ';');
  77.             if(!end) end = start + strlen(start);

  78.             // 去除可能的引号
  79.             if(*start == '"') start++;
  80.             if(*(end-1) == '"') end--;

  81.             strncpy(filename, start, end - start);
  82.             filename[end - start] = '\0';
  83.         }
  84.     }

  85.     free(header);
  86.     return size * nmemb;
  87. }

  88. static CReturnData perform_request(CRequestParams *params) {
  89.     CURL *curl;
  90.     CURLcode res;
  91.     CResponseData chunk = {0};
  92.         CReturnData ret = {0};
  93.     char resp_filename[128] = {0};  // 存储文件名

  94.     curl_global_init(CURL_GLOBAL_ALL);
  95.     curl = curl_easy_init();
  96.     if(!curl) {
  97.         ret.respBody = strdup("curl_easy_init failed");
  98.         ret.retcode = -1;
  99.         goto cleanup;
  100.     }

  101.     // 设置基本选项
  102.     curl_easy_setopt(curl, CURLOPT_URL, params->url); // 服务器URL
  103.     curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);  // // 设置线程安全选项
  104.     curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, (long)params->timeout); // 超时时间,单位为毫秒
  105.     curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, (long)params->timeout);

  106.     // HTTPS设置
  107.     if(strncmp(params->url, "https://", 8) == 0) {
  108.         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
  109.         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L);
  110.         curl_easy_setopt(curl, CURLOPT_CAINFO, params->cafile);
  111.         curl_easy_setopt(curl, CURLOPT_SSLCERT, params->clifile);
  112.         curl_easy_setopt(curl, CURLOPT_SSLCERTPASSWD, "123456");
  113.         curl_easy_setopt(curl, CURLOPT_SSLKEY, params->keyfile);
  114.         curl_easy_setopt(curl, CURLOPT_SSLKEYPASSWD, "123456");
  115.     }
  116.         // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); // 调试信息
  117.         curl_easy_setopt(curl, CURLOPT_SSLVERSION, 4);

  118.     // 设置回调
  119.     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
  120.     curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);

  121.     // 构建表单
  122.     struct curl_httppost *formpost = NULL;
  123.     struct curl_httppost *lastptr = NULL;

  124.     curl_formadd(&formpost, &lastptr,
  125.         CURLFORM_COPYNAME, "file",
  126.         CURLFORM_BUFFER, params->postfile,
  127.         CURLFORM_BUFFERPTR, params->jsonStr,
  128.         CURLFORM_BUFFERLENGTH, (long)params->jsonLen,
  129.         CURLFORM_CONTENTTYPE, "application/json",
  130.         CURLFORM_END);

  131.     curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);

  132.         // 设置头部回调以获取文件名
  133.     curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
  134.     curl_easy_setopt(curl, CURLOPT_HEADERDATA, resp_filename);

  135.     // 执行请求
  136.     res = curl_easy_perform(curl);

  137.     if(res != CURLE_OK) {
  138.         const char *err = curl_easy_strerror(res);
  139.         ret.respBody = malloc(strlen(err) + 32);
  140.         sprintf(ret.respBody, "curl_easy_perform failed: %s", err);
  141.         ret.retcode = -2;
  142.         goto cleanup;
  143.     }

  144.         // printf("debug %s %d  resp data len: \n", __func__, __LINE__, chunk.len);
  145.     // 成功则复制结果
  146.     if(chunk.data) {
  147.         ret.respBody = strdup(chunk.data);
  148.         ret.filename = strdup(resp_filename);
  149.         ret.retcode = 0;
  150.     } else {
  151.         ret.respBody = strdup("No data received");
  152.         ret.retcode = -3;
  153.     }

  154. cleanup:
  155.     if(chunk.data) free(chunk.data);
  156.     if(formpost) curl_formfree(formpost);
  157.     if(curl) curl_easy_cleanup(curl);
  158.     curl_global_cleanup();

  159.     return ret;
  160. }
  161. */
  162. import "C"
  163. import (
  164.         "unsafe"
  165. )

  166. type CurlResponse struct {
  167.         respBody string
  168.         filename string
  169.         retcode  int
  170. }

  171. type MyCURL struct {
  172.         url, postfile, cafile, clifile, keyfile string
  173.         timeout                                 int
  174. }

  175. func NewCurl() *MyCURL {
  176.         return &MyCURL{
  177.                 timeout: 5000, // 默认超时
  178.         }
  179. }

  180. func (c *MyCURL) SetOpt(url, postfile, cafile, clientfile, keyfile string, timeout int) {
  181.         c.url = url
  182.         c.postfile = postfile
  183.         c.cafile = cafile
  184.         c.clifile = clientfile
  185.         c.keyfile = keyfile
  186.         c.timeout = timeout
  187. }

  188. func (c *MyCURL) PostFiledata(jsonStr []byte) CurlResponse {
  189.         // 将 Go的json数据复制到C的内存中
  190.         cJsonStr := C.CBytes(jsonStr)
  191.         defer C.free(cJsonStr)

  192.         params := C.CRequestParams{
  193.                 url:      C.CString(c.url),
  194.                 postfile: C.CString(c.postfile),
  195.                 cafile:   C.CString(c.cafile),
  196.                 clifile:  C.CString(c.clifile),
  197.                 keyfile:  C.CString(c.keyfile),
  198.                 timeout:  C.int(c.timeout),
  199.                 jsonStr:  (*C.char)(cJsonStr), // 使用C分配的内存
  200.                 jsonLen:  C.int(len(jsonStr)),
  201.         }

  202.         defer func() {
  203.                 C.free(unsafe.Pointer(params.url))
  204.                 C.free(unsafe.Pointer(params.cafile))
  205.                 C.free(unsafe.Pointer(params.clifile))
  206.                 C.free(unsafe.Pointer(params.keyfile))
  207.         }()

  208.         // 调用C函数并获取返回结构体
  209.         cRet := C.perform_request(&params)
  210.         defer func() {
  211.                 C.free(unsafe.Pointer(cRet.respBody))
  212.                 C.free(unsafe.Pointer(cRet.filename))
  213.         }()

  214.         // 转换为Go结构体
  215.         return CurlResponse{
  216.                 respBody: C.GoString(cRet.respBody),
  217.                 filename: C.GoString(cRet.filename),
  218.                 retcode:  int(cRet.retcode),
  219.         }
  220. }
复制代码
与上一版本对比,有如下调整:

    1. #cgo linux pkg-config: libcurl
    复制代码
    改为
    1. #cgo linux LDFLAGS: -lcurl
    复制代码
    ,对编译环境较友好一些。
  • 将全局变量改为局域变量,防止多线程情况下出现问题。
  • 上版本返回值使用换行符进行解析,现改为返回多个值(go语言本身支持),代码较友好。

测试

与curl请求有关的输出信息如下:
  1. * About to connect() to 172.18.18.10 port 86 (#4)
  2. *   Trying 172.18.18.10...
  3. * Connected to 172.18.18.10 (172.18.18.10) port 86 (#4)
  4. * Initializing NSS with certpath: sql:/etc/pki/nssdb
  5. *   CAfile: ../../../cert/all.pem
  6.    CApath: none
  7. * SSL connection using ECDHE-RSA-AES256-GCM-SHA384
  8. * Server certificate:
  9. *        subject: CN=172.18.18.10
  10. *        start date: 2023-02-16 08:19:00 GMT
  11. *        expire date: 2033-02-16 08:19:00 GMT
  12. > POST /mypost/foobar HTTP/1.1
  13. Host: 172.18.18.10:86
  14. Content-Length: 799
  15. Expect: 100-continue
  16. Content-Type: multipart/form-data; boundary=----------------------------258acabf1379

  17. < HTTP/1.1 100 Continue
  18. < HTTP/1.1 200 OK
  19. < Server: nginx/1.16.1
  20. < Date: Sun, 14 May 2025 18:20:48 GMT
  21. < Content-Type: application/json
  22. < Content-Length: 1083
  23. < Connection: keep-alive
  24. < Content-Disposition: form-data;filename=bar.json
  25. <
  26. * Connection #4 to host 172.18.18.10 left intact
复制代码
小结

上述代码目前只在测试环境测试,后续择机在生产环境中使用。就测试结果看,应该是没有大问题的。
到此这篇关于go中使用curl实现https请求的示例代码的文章就介绍到这了,更多相关go实现https请求内容请搜索晓枫资讯以前的文章或继续浏览下面的相关文章希望大家以后多多支持晓枫资讯!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表