
离线 TA的专栏
- 打卡等级:即来则安
- 打卡总天数:15
- 打卡月天数:0
- 打卡总奖励:243
- 最近打卡:2023-08-27 04:33:47
|
之前曾经在一个 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一般写法,未发现问题,只好借助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); // 服务器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); // 调试信息
- 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 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数据复制到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(¶ms)
- 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.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
复制代码 小结
上述代码目前只在测试环境测试,后续择机在生产环境中使用。就测试结果看,应该是没有大问题的。
到此这篇关于go中使用curl实现https请求的示例代码的文章就介绍到这了,更多相关go实现https请求内容请搜索晓枫资讯以前的文章或继续浏览下面的相关文章希望大家以后多多支持晓枫资讯!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:  进行删除处理。
4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
|