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

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

小结

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

相关文章:

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

之前曾经在一个 golang 工程调用 libcur 实现 https的请求&#xff0c;当前自测是通过的。后来迁移到另一个小系统出现段错误&#xff0c;于是对该模块代码改造&#xff0c;并再次自测。 问题提出 大约2年前&#xff0c;在某golang项目使用libcurl进行https请求&#xff08;参…...

自然语言处理入门级项目——文本分类

文章目录 前言1.数据预处理1.1数据集介绍1.2数据集抽取1.3划分数据集1.4数据清洗1.5数据保存 2.样本的向量化表征2.1词汇表2.2向量化2.3自定义数据集2.4备注 结语 前言 本篇博客主要介绍自然语言处理领域中一个项目案例——文本分类&#xff0c;具体而言就是判断评价属于积极还…...

如何利用大模型对文章进行分段,提高向量搜索的准确性?

利用大模型对文章进行分段以提高向量搜索准确性,需结合文本语义理解、分块策略优化以及向量表示技术。以下是系统性的解决方案: 一、分块策略的核心原则 语义完整性优先 分块需确保每个文本单元在语义上独立且完整。研究表明,当分块内容保持单一主题时,向量嵌入的语义表征能…...

一发入魂:极简解决 SwiftUI 复杂视图未能正确刷新的问题(上)

概述 各位似秃非秃小码农们都知道&#xff0c;在 SwiftUI 中视图是状态的函数&#xff0c;这意味着状态的改变会导致界面被刷新。 但是&#xff0c;对于有些复杂布局的 SwiftUI 视图来说&#xff0c;它们的界面并不能直接映射到对应的状态上去。这就会造成一个问题&#xff1…...

深入理解 Dijkstra 算法:原理、实现与优化

算法核心思想 Dijkstra算法采用贪心策略&#xff0c;其核心思想可以概括为&#xff1a; 初始化&#xff1a;设置起点到自身的距离为0&#xff0c;到其他所有点的距离为无穷大 迭代处理&#xff1a; 从未处理的顶点中选择当前距离起点最近的顶点 标记该顶点为已处理 通过该顶…...

Postman中https和http的区别是什么?

作为每天与API打交道的测试工程师,理解HTTP与HTTPS的区别不仅关乎协议本身,更直接影响测试方案设计。本文将用测试视角揭示二者在Postman中的关键差异,并分享实战排查技巧。 一、协议层本质差异(测试工程师需要知道的底层原理) 1. 安全传输机制对比 特性HTTPHTTPS加密方…...

软件设计师-下午题-试题4(15分)

目录 1 回溯法 1.1 N皇后问题 1.1.1 非递归求解N皇后问题 1.1.2 递归求解N皇后问题 1.2 真题 2 分治法 2.1 真题 3 动态规划法 3.1 0-1背包问题 3.2 真题 1 回溯法 1.1 N皇后问题 上图Q4与Q2在同一列且与Q1在同一斜线&#xff0c;先回溯到上一个皇后改变Q3皇后的位置…...

《隐私计算:数据安全与隐私保护的新希望》

一、引言 在数字化时代&#xff0c;数据已成为企业和组织的核心资产。然而&#xff0c;数据的收集、存储和使用过程中面临着诸多隐私和安全挑战。隐私计算作为一种新兴技术&#xff0c;旨在解决数据隐私保护和数据共享之间的矛盾。本文将深入探讨隐私计算的基本概念、技术原理、…...

leetcode二叉树相关题目复习(C语言版)

目录 1.单值二叉树 2.相同的树 3.对称二叉树 4.二叉树的前序遍历 5.另一颗树的子树 1.单值二叉树 思路1&#xff1a; 判断根节点、左节点与右节点的值是否相等&#xff0c;因为正向判断&#xff08;即判断三值相等返回true&#xff09;比较麻烦&#xff08;不能根节点满足…...

第十九次博客打卡

今天学习的内容是Java中的常见循环。 在 Java 中&#xff0c;常见的循环结构主要有以下几种&#xff1a;for 循环、while 循环、do-while 循环以及增强型 for 循环&#xff08;也称为 for-each 循环&#xff09;。 1. for 循环 for 循环是一种非常灵活的循环结构&#xff0c…...

【Pandas】pandas DataFrame describe

Pandas2.2 DataFrame Computations descriptive stats 方法描述DataFrame.abs()用于返回 DataFrame 中每个元素的绝对值DataFrame.all([axis, bool_only, skipna])用于判断 DataFrame 中是否所有元素在指定轴上都为 TrueDataFrame.any(*[, axis, bool_only, skipna])用于判断…...

机器人示教操作

机器人基础操作 **ES机器人试教操作知识** **1. 视角移动** **1.1 基础模式** - 关节轴控制&#xff1a;通过关节1至关节6实现单轴正反转移动 - 直线移动&#xff1a;通过X/Y/Z坐标轴沿指定方向直线移动 - 旋转移动&#xff1a;通过RX/RY/RZ坐标轴绕指定轴旋转 **1.2 步进模式…...

浅聊一下数据库的索引优化

背景 这里的索引说的是关系数据库&#xff08;MSSQL&#xff09;中的索引。 本篇不是纯技术性的内容&#xff0c;只是聊一次性能调优的经历&#xff0c;包含到一些粗浅的实现和验证手段&#xff0c;所以&#xff0c;大神忽略即可。 额…对了&#xff0c;笔者对数据库的优化手段…...

山东大学软件学院软件工程计算机图形学复习笔记(2025)

写在前面&#xff1a; 现在是考完试的第二天&#xff0c;考试的内容还是有一部分没有复习到的…… 根据三角形的3个顶点坐标和内部某点坐标D&#xff0c;写出点D的基于面积的权重坐标Bresenham的算法描述与改进策略&#xff08;这里ppt上很不清晰&#xff09;以及直线反走样的…...

【Docker】Docker Compose方式搭建分布式内存数据库(Redis)集群

文章目录 开发环境开发流程运行效果Docker Desktop桌面中的Redis结点启动图Redis结点1的打印日志情况图 配置代码命令行启动配置文件: README.md删除集群信息新建数据目录本地Redis的结点的域名,并添加到/etc/hosts文件的末尾域名映射启动集群结点创建集群关闭集群结点 redis-c…...

如何在 Bash 中使用 =~ 操作符 ?

在 Bash 脚本世界中&#xff0c;有各种操作符可供我们使用&#xff0c;使我们能够操作、比较和测试数据。其中一个操作符是 ~ 操作符。这个操作符经常被忽视&#xff0c;但功能非常强大&#xff0c;它为我们提供了一种使用正则表达式匹配字符串模式的方法。 ~ 操作符语法 语法…...

科学养生指南:打造健康生活

在快节奏的现代生活中&#xff0c;健康养生成为人们关注的焦点。科学养生无需复杂理论&#xff0c;掌握以下几个关键要素&#xff0c;就能为身体构筑坚实的健康防线。​ 合理饮食是健康的基础。世界卫生组织建议&#xff0c;每天应摄入至少 5 份蔬菜和水果&#xff0c;保证维生…...

华为OD机试真题——单词接龙(首字母接龙)(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳实现

2025 A卷 100分 题型 本专栏内全部题目均提供Java、python、JavaScript、C、C++、GO六种语言的最佳实现方式; 并且每种语言均涵盖详细的问题分析、解题思路、代码实现、代码详解、3个测试用例以及综合分析; 本文收录于专栏:《2025华为OD真题目录+全流程解析+备考攻略+经验分…...

React构建组件

React构建组件 React 组件构建方式详解 React 组件的构建方式随着版本迭代不断演进&#xff0c;目前主要有 函数组件 和 类组件 两种核心模式&#xff0c;并衍生出多种高级组件设计模式。以下是完整的构建方式指南&#xff1a; 文章目录 React构建组件React 组件构建方式详解…...

计算机网络-MPLS VPN基础概念

前面几篇文章我们学习了MPLS的标签转发原理&#xff0c;有静态标签分发和LDP动态标签协议&#xff0c;可以实现LSR设备基于标签实现数据高效转发。现在开始学习MPLS在企业实际应用的场景-MPLS VPN。 一、MPLS VPN概念 MPLS&#xff08;多协议标签交换&#xff09;位于TCP/IP协…...

基于TouchSocket实现WebSocket自定义OpCode扩展协议

基于TouchSocket实现WebSocket自定义OpCode扩展协议 前言一、WebSocket OpCode规范速览二、实现示例&#xff1a;协同编辑光标同步1. 客户端发送实现2. 服务端接收处理 三、应用场景分析1. 实时协作系统2. 物联网控制协议3. 游戏实时交互 四、协议设计建议1. 帧结构优化2. 性能…...

【Linux系列】bash_profile 与 zshrc 的编辑与加载

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

Spring Boot中的拦截器!

每次用户请求到达Spring Boot服务端&#xff0c;你是否需要重复写日志、权限检查或请求格式化代码&#xff1f;这些繁琐的“前置后置”工作让人头疼&#xff01;好在&#xff0c;Spring Boot拦截器如同一道智能关卡&#xff0c;统一处理请求的横切逻辑&#xff0c;让代码优雅又…...

基于 Spring Boot 瑞吉外卖系统开发(十五)

基于 Spring Boot 瑞吉外卖系统开发&#xff08;十五&#xff09; 前台用户登录 在登录页面输入验证码&#xff0c;单击“登录”按钮&#xff0c;页面会携带输入的手机号和验证码向“/user/login”发起请求。 定义UserMapper接口 Mapper public interface UserMapper exte…...

计算机网络笔记(二十三)——4.5IPv6

4.5.1IPv6的基本首部 IPv6 的基本首部相对于 IPv4 进行了重大简化和优化&#xff0c;固定长度为 40 字节&#xff0c;大幅提升了路由器的处理效率。以下是各字段的详细说明&#xff1a; IPv6 基本首部字段组成 字段名位数作用描述版本 (Version)4 bits固定值为 6&#xff0c…...

推荐一个Winform开源的UI工具包

从零学习构建一个完整的系统 推荐一个开源、免费的适合.NET WinForms 控件的套件。 项目简介 Krypton是一套开源的.Net组件&#xff0c;用于快速构建具有丰富UI交互的WinForms应用程序。 丰富的UI控件&#xff0c;提供了48个基础控件&#xff0c;如按钮、文本框、标签、下拉…...

位与运算

只有当除数是 2 的幂次方&#xff08;如 2、4、8、16...&#xff09;时&#xff0c;取模运算才可以转换为位运算。 int b 19;int a1 b % 16; // 传统取模运算int a2 b & 15; // 位运算替代取模printf("b %d\n", b);printf("b %% 8 %d\n",…...

算法备案如何判断自己的产品是否具备舆论属性

判断互联网产品是否具备舆论属性或社会动员能力&#xff0c;需要结合《具备舆论属性或社会动员能力的互联网信息服务安全评估规定》法规及实际功能、用户规模、信息传播方式等综合因素判定。 一、舆论属性判断标准 &#xff08;1&#xff09;服务功能与形式 信息交互功能&am…...

AR禁毒:科技赋能,筑牢防毒新防线

过去&#xff0c;传统禁毒宣传教育方式对普及禁毒知识、提高禁毒意识意义重大。但随着时代和社会环境变化&#xff0c;其困境逐渐显现。传统宣传方式单一&#xff0c;主要依靠讲座、发传单、办展览。讲座形式枯燥&#xff0c;对青少年吸引力不足&#xff1b;发传单易被丢弃&…...

趣味编程:四叶草

概述&#xff1a;在万千三叶草中寻觅&#xff0c;只为那一抹独特的四叶草之绿&#xff0c;它象征着幸运与希望。本篇博客主要介绍四叶草的绘制。 1. 效果展示 绘制四叶草的过程是一个动态的过程&#xff0c;因此博客中所展示的为绘制完成的四叶草。 2. 源码展示 #define _CR…...