最近在用FreeBasic写跨平台的管理系统,自然少不了REST API模块,但是https请求绕不开加密库,例如curl+openssl+zlib的解决方案,如果是静态编译,空程序就需要2.5M,动态则需要带几个dll体积更大,而实际上我们仅仅只需要https。。
找了一圈,最终发现有个停更七年的开源项目httpclient,项目地址在
httpclient: 纯C语言写的http client,支持 https,支持GET POST, 不依赖其他库
ssl/tls库使用krypton(https://github.com/cesanta/krypton)
解析部分使用http_parser(https://github.com/nodejs/http-parser)
作者把这两个项目文件都拷贝到项目里了,krypton你可能没听说过,但是他的另外一个作品是mongoose。
但是使用MSSY2下面的mingw编译如果krypton没编译通过,提示错误是
krypton.c:697:20: error: invalid suffix "i64" on integer constant
697 | #define COMP_RADIX 4294967296i64
其实问题很简单,i64这个后缀改成LL就行
#define COMP_RADIX 4294967296i64
//修改为
#define COMP_RADIX 4294967296LL
具体原因可以参考C++ Integer Constants | Microsoft Learn
另外krypton库作者估计没用win下面的mingw32/64编译器,只是兼容了msvc的情况,需要做一下调整,不然无法编译测试程序:
#ifdef _MSC_VER
#include <winsock2.h>
#include <windows.h>
#define __unused
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
typedef int int32_t;
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
typedef unsigned char uint8_t;
typedef unsigned long uintptr_t;
typedef long ssize_t;
#define __func__ ""
#define __packed
#ifndef alloca
#define alloca(x) _alloca(x)
#endif
#ifndef EWOULDBLOCK
#define EWOULDBLOCK WSAEWOULDBLOCK
#endif
#define SOCKET_ERRNO WSAGetLastError()
#pragma comment(lib, "ws2_32.lib") // Linking with winsock library
#else /* _MSC_VER */
//需要插入以下代码
#ifdef _WIN32
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib") // Linking with winsock library
#endif
//插入完毕
#include <stdint.h>
#include <unistd.h>
#define __packed __attribute__((packed))
#define SOCKET_ERRNO errno
#endif
这个库很有价值,体积确实很小,执行编译:
gcc -c *.c -lws2_32
ar -rc libhttpclient.a http_parser.o krypton.o http.o
编译完成可以得到libhttpclient.a,体积只有122k,将文件拷贝到Compile\expand\lib\win32文件夹
下面继续祭出fbfrog.exe,拷贝default.h到根目录,执行
# fbfrog.exe http.h -o httpclient.bi -inclib ws2_32 -inclib httpclient
#pragma once
#inclib "ws2_32"
#inclib "httpclient"
extern "C"
#define _HTTP_H_
#define HTTP_API
const FT_SUPPORT_HTTPS = 1
type http_request_method_e as long
enum
M_GET = 0
M_POST
M_HEAD
end enum
type http_error_e as long
enum
ERR_OK = 0
ERR_INVALID_PARAM
ERR_OUT_MEMORY
ERR_OPEN_FILE
ERR_PARSE_REP
ERR_URL_INVALID
ERR_URL_INVALID_PROTO
ERR_URL_INVALID_HOST
ERR_URL_INVALID_IP
ERR_URL_RESOLVED_HOST
ERR_SOCKET_CREATE
ERR_SOCKET_SET_OPT
ERR_SOCKET_NOBLOCKING
ERR_SOCKET_CONNECT
ERR_SOCKET_CONNECT_TIMEOUT
ERR_SOCKET_SELECT
ERR_SOCKET_WRITE
ERR_SOCKET_READ
ERR_SOCKET_TIMEOUT
ERR_SOCKET_CLOSED
ERR_SOCKET_GET_OPT
ERR_SSL_CREATE_CTX
ERR_SSL_CREATE_SSL
ERR_SSL_SET_FD
ERR_SSL_CONNECT
ERR_SSL_WRITE
ERR_SSL_READ
end enum
type data_recv_cb_t as function(byval http as ft_http_client_t ptr, byval data as const zstring ptr, byval size as long, byval total as long, byval user as any ptr) as long
declare function ft_http_init() as long
declare sub ft_http_deinit()
declare function ft_http_new() as ft_http_client_t ptr
declare sub ft_http_destroy(byval http as ft_http_client_t ptr)
declare function ft_http_get_error_code(byval http as ft_http_client_t ptr) as long
declare function ft_http_get_status_code(byval http as ft_http_client_t ptr) as long
declare function ft_http_set_timeout(byval http as ft_http_client_t ptr, byval timeout as long) as long
declare function ft_http_sync_request(byval http as ft_http_client_t ptr, byval url as const zstring ptr, byval m_ as http_request_method_e) as const zstring ptr
declare function ft_http_sync_download_file(byval http as ft_http_client_t ptr, byval url as const zstring ptr, byval filepath as const zstring ptr) as long
declare function ft_http_cancel_request(byval http as ft_http_client_t ptr) as long
declare function ft_http_wait_done(byval http as ft_http_client_t ptr) as long
declare function ft_http_set_data_recv_cb(byval http as ft_http_client_t ptr, byval cb as data_recv_cb_t, byval user as any ptr) as long
declare function ft_http_exit(byval http as ft_http_client_t ptr) as long
declare function ft_http_sync_post_file(byval http as ft_http_client_t ptr, byval url as const zstring ptr, byval filepath as const zstring ptr) as const zstring ptr
end extern
粗略看一下代码,会发现ft_http_client_t结构体并没有翻译出来,这个结构体比较复杂,但是我们可以申明一个type ft_http_client_t as any ptr,然后把ft_http_client_t ptr全部替换为ft_http_client_t
#pragma once
#inclib "ws2_32"
#inclib "httpclient"
extern "C"
#define _HTTP_H_
#define HTTP_API
const FT_SUPPORT_HTTPS = 1
type http_request_method_e as long
enum
M_GET = 0
M_POST
M_HEAD
end enum
type http_error_e as long
enum
ERR_OK = 0
ERR_INVALID_PARAM
ERR_OUT_MEMORY
ERR_OPEN_FILE
ERR_PARSE_REP
ERR_URL_INVALID
ERR_URL_INVALID_PROTO
ERR_URL_INVALID_HOST
ERR_URL_INVALID_IP
ERR_URL_RESOLVED_HOST
ERR_SOCKET_CREATE
ERR_SOCKET_SET_OPT
ERR_SOCKET_NOBLOCKING
ERR_SOCKET_CONNECT
ERR_SOCKET_CONNECT_TIMEOUT
ERR_SOCKET_SELECT
ERR_SOCKET_WRITE
ERR_SOCKET_READ
ERR_SOCKET_TIMEOUT
ERR_SOCKET_CLOSED
ERR_SOCKET_GET_OPT
ERR_SSL_CREATE_CTX
ERR_SSL_CREATE_SSL
ERR_SSL_SET_FD
ERR_SSL_CONNECT
ERR_SSL_WRITE
ERR_SSL_READ
end enum
type ft_http_client_t as any ptr
type data_recv_cb_t as function(byval http as ft_http_client_t, byval data as const zstring ptr, byval size as long, byval total as long, byval user as any ptr) as long
declare function ft_http_init() as long
declare sub ft_http_deinit()
declare function ft_http_new() as ft_http_client_t
declare sub ft_http_destroy(byval http as ft_http_client_t)
declare function ft_http_get_error_code(byval http as ft_http_client_t) as long
declare function ft_http_get_status_code(byval http as ft_http_client_t) as long
declare function ft_http_set_timeout(byval http as ft_http_client_t, byval timeout as long) as long
declare function ft_http_sync_request(byval http as ft_http_client_t, byval url as const zstring ptr, byval m_ as http_request_method_e) as const zstring ptr
declare function ft_http_sync_download_file(byval http as ft_http_client_t, byval url as const zstring ptr, byval filepath as const zstring ptr) as long
declare function ft_http_cancel_request(byval http as ft_http_client_t) as long
declare function ft_http_wait_done(byval http as ft_http_client_t) as long
declare function ft_http_set_data_recv_cb(byval http as ft_http_client_t, byval cb as data_recv_cb_t, byval user as any ptr) as long
declare function ft_http_exit(byval http as ft_http_client_t) as long
declare function ft_http_sync_post_file(byval http as ft_http_client_t, byval url as const zstring ptr, byval filepath as const zstring ptr) as const zstring ptr
end extern
把httpclient.bi拷贝到Compile\expand\inc文件夹
创建一个标准的exe的项目,在起始模块加入#include Once "httpclient.bi"
在Form1上加入
Sub Form1_Shown(hWndForm As hWnd,UserData As Integer)
ft_http_init()
Dim ft As ft_http_client_t = ft_http_new()
ft_http_sync_download_file(ft, "https://www.taobao.com/", "index.html")
End Sub
运行后在release文件夹下面会有index.html文件,大小约为86K
如果不支持https的话,淘宝的返回的将会是
HTTP/1.1 400 Bad Request
Server: Tengine
Date: Mon, 08 May 2023 22:06:32 GMT
Content-Type: text/html
Content-Length: 263
Connection: close
Via: cache2.cn5570[,0]
Timing-Allow-Origin: *
EagleId: 0000000016835835920664266e
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>
<body>
<h1>400 Bad Request</h1>
<p>The plain HTTP request was sent to HTTPS port.<hr/>Powered by Tengine</body>
</html>
证明组件效果很好。