lua 异步HTTPS并发请求库

项目使用skynet框架,这个框架主要用lua写逻辑,但缺乏对HTTPS支持,所以我利用一点时间写了lua模块,支持异步HTTPS请求,文章这里讲述HTTPS相关知识,如何接入openssl请求HTTPS数据,同时也分享了lua模块给大家参考。



HTTPS说明

HTTPS可以理解成 HTTP协议的安全版,协议还是HTTP协议,只是对传输过程的数据进行了加密处理,保证数据传输的安全。(默认端口是443)
HTTPS验证数据的过程如下:

skynet HTTPS支持

skynet只封装了HTTP的接口,没有对HTTPS做支持,所以要外接lua 库使用。

skynet支持HTTPS请求有两种方法:
  优点 缺点
CURL 支持http/https/ftp等,接入较简单 并发支持差
OpenSSL 更底层,效率较高 接入复杂

其实,CURL是利用OpenSSL实现的,有网友封装了非阻塞版本的lua CURL库,可用于skynet处理HTTPS请求。链接 猛击这里

前面提到了CURL的缺点,CURL本身可做并发请求(libcurl multi),但做法却是将所有URL请求合到一起处理,需等全部URL数据处理完毕才返回数据。假设其中一个URL出现超时,那一起的其他URL都会受影响。

所以,文章推荐使用OpenSSL,这里先介绍C/C++如何处理HTTPS请求,然后再封装一个lua库,给大家演示下 skynet 如何请求HTTPS数据。

C/C++处理HTTPS请求

这里以一个例子,说明 C/C++如何处理HTTPS请求。
  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <errno.h>  
  4. #include <sys/socket.h>  
  5. #include <resolv.h>  
  6. #include <stdlib.h>  
  7. #include <netinet/in.h>  
  8. #include <arpa/inet.h>  
  9. #include <unistd.h>  
  10. #include <sys/types.h>  
  11. #include <sys/stat.h>  
  12. #include <fcntl.h>  
  13. #include "openssl/ssl.h"  
  14. #include "openssl/err.h"  
  15.   
  16. #define MAXBUF 1024  
  17.   
  18. int main(int argc, char **argv)  
  19. {  
  20.     int sockfd, len;  
  21.     char sendFN[1024];  
  22.     struct sockaddr_in dest;  
  23.     char buffer[MAXBUF + 1];  
  24.     SSL_CTX *ctx;  
  25.     SSL *ssl;  
  26.     char host_file[] = "";  
  27.     char  host_addr[] = "www.jd.com";  
  28.     char ip[] = "112.91.125.129";  
  29.     int port = "443";  
  30.   
  31.     /* SSL 库初始化 */  
  32.     SSL_library_init();  
  33.     OpenSSL_add_all_algorithms();  
  34.     SSL_load_error_strings();  
  35.     ctx = SSL_CTX_new(SSLv23_client_method());  
  36.     if (ctx == NULL)  
  37.     {  
  38.         ERR_print_errors_fp(stdout);  
  39.         exit(1);  
  40.     }  
  41.     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)  
  42.     {  
  43.         perror("Socket Create Fail!");  
  44.         exit(errno);  
  45.     }  
  46.     /* 建立 TCP 连接 */  
  47.     bzero(&dest, sizeof(dest));  
  48.     dest.sin_family = AF_INET;  
  49.     dest.sin_port = htons(atoi(port));  
  50.     if (inet_aton(ip, (struct in_addr *) &dest.sin_addr.s_addr) == 0)  
  51.     {  
  52.         perror("Socket Init Fail!");  
  53.         exit(errno);  
  54.     }  
  55.     printf("Socket Created\n");  
  56.   
  57.     if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0)  
  58.     {  
  59.         perror("Socket Connect Fail!");  
  60.         exit(errno);  
  61.     }  
  62.     printf("Socket Connected\n");  
  63.   
  64.     /* 绑定 Socket 与 SSL */  
  65.     ssl = SSL_new(ctx);  
  66.     SSL_set_fd(ssl, sockfd);  
  67.     /* 建立 SSL 连接 */  
  68.     if (SSL_connect(ssl) == -1)  
  69.         ERR_print_errors_fp(stderr);  
  70.     else  
  71.         printf("SSL Connected with %s encryption\n", SSL_get_cipher(ssl));  
  72.   
  73.   
  74.     sprintf(sendFN, "GET /%s HTTP/1.1\r\nHost: %s\r\nConnection: Close\r\n\r\n", host_file, host_addr);  
  75.   
  76.     /* SSL 发数据 */  
  77.     len = SSL_write(ssl, sendFN, strlen(sendFN));  
  78.     if (len < 0)  
  79.         printf("SSL Send failure! errno = %d, err_msg = %s\n", errno, strerror(errno));  
  80.       
  81.     printf("SSL Send Done !\n\n");  
  82.   
  83.     bzero(buffer, MAXBUF + 1);  
  84.     int nbytes;  
  85.     /* SSL 收数据 */  
  86.     while ((nbytes = SSL_read(ssl, buffer, 1)) == 1) {  
  87.         /* 打印收到的数据 */  
  88.         printf("%s", buffer);  
  89.     }  
  90.       
  91.     printf("\n\nSSL Read Done !\n");  
  92.   
  93.     /* 关闭连接 */  
  94.     SSL_shutdown(ssl);  
  95.     SSL_free(ssl);  
  96.     close(sockfd);  
  97.     SSL_CTX_free(ctx);  
  98.     return 0;  
  99. }  
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "openssl/ssl.h"
#include "openssl/err.h"

#define MAXBUF 1024

int main(int argc, char **argv)
{
	int sockfd, len;
	char sendFN[1024];
	struct sockaddr_in dest;
	char buffer[MAXBUF + 1];
	SSL_CTX *ctx;
	SSL *ssl;
	char host_file[] = "";
	char  host_addr[] = "www.jd.com";
	char ip[] = "112.91.125.129";
	int port = "443";

	/* SSL 库初始化 */
	SSL_library_init();
	OpenSSL_add_all_algorithms();
	SSL_load_error_strings();
	ctx = SSL_CTX_new(SSLv23_client_method());
	if (ctx == NULL)
	{
		ERR_print_errors_fp(stdout);
		exit(1);
	}
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("Socket Create Fail!");
		exit(errno);
	}
	/* 建立 TCP 连接 */
	bzero(&dest, sizeof(dest));
	dest.sin_family = AF_INET;
	dest.sin_port = htons(atoi(port));
	if (inet_aton(ip, (struct in_addr *) &dest.sin_addr.s_addr) == 0)
	{
		perror("Socket Init Fail!");
		exit(errno);
	}
	printf("Socket Created\n");

	if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0)
	{
		perror("Socket Connect Fail!");
		exit(errno);
	}
	printf("Socket Connected\n");

	/* 绑定 Socket 与 SSL */
	ssl = SSL_new(ctx);
	SSL_set_fd(ssl, sockfd);
	/* 建立 SSL 连接 */
	if (SSL_connect(ssl) == -1)
		ERR_print_errors_fp(stderr);
	else
		printf("SSL Connected with %s encryption\n", SSL_get_cipher(ssl));


	sprintf(sendFN, "GET /%s HTTP/1.1\r\nHost: %s\r\nConnection: Close\r\n\r\n", host_file, host_addr);

	/* SSL 发数据 */
	len = SSL_write(ssl, sendFN, strlen(sendFN));
	if (len < 0)
		printf("SSL Send failure! errno = %d, err_msg = %s\n", errno, strerror(errno));
	
	printf("SSL Send Done !\n\n");

	bzero(buffer, MAXBUF + 1);
	int nbytes;
	/* SSL 收数据 */
	while ((nbytes = SSL_read(ssl, buffer, 1)) == 1) {
		/* 打印收到的数据 */
		printf("%s", buffer);
	}
	
	printf("\n\nSSL Read Done !\n");

	/* 关闭连接 */
	SSL_shutdown(ssl);
	SSL_free(ssl);
	close(sockfd);
	SSL_CTX_free(ctx);
	return 0;
}
原理很简单,底层还是走了socket,只是TCP连接建立后,把连接交给SSL,让SSL收发数据。

Lua处理HTTPS请求

这里提供lua版本处理HTTPS请求,除了skynet,其他lua项目也可以使用。
以下是lua C模块的代码,保存为lua_httpsc.c
  1. #include <stdlib.h>  
  2. #include <string.h>  
  3. #include <lua.h>  
  4. #include <lauxlib.h>  
  5. #include <time.h>  
  6.   
  7. #include <netinet/in.h>  
  8. #include <sys/types.h>  
  9. #include <sys/socket.h>  
  10. #include <arpa/inet.h>  
  11. #include <unistd.h>  
  12. #include <errno.h>  
  13. #include <fcntl.h>  
  14. #include "openssl/ssl.h"  
  15. #include "openssl/err.h"  
  16. #include <poll.h>  
  17.   
  18.   
  19. #define CACHE_SIZE 0x1000  
  20. #define ERROR_FD -1  
  21. #define SEND_RETRY 10  
  22.   
  23. static SSL_CTX *ctx = NULL;  
  24.   
  25. typedef struct {  
  26.     int is_init;  
  27. } cutil_conf_t;  
  28.   
  29. enum conn_st  
  30. {  
  31.     CONNECT_INIT = 1,  
  32.     CONNECT_PORT = 2,  
  33.     CONNECT_SSL = 3,  
  34.     CONNECT_DONE = 4  
  35. };  
  36.   
  37. typedef struct {  
  38.     int fd;  
  39.     SSL* ssl;  
  40.     enum conn_st status;  
  41. } cutil_fd_t;  
  42.   
  43. static cutil_conf_t* fetch_config(lua_State *L) {  
  44.     cutil_conf_t* cfg;  
  45.     cfg = lua_touserdata(L, lua_upvalueindex(1));  
  46.     if (!cfg)  
  47.         luaL_error(L, "httpsc: Unable to fetch cfg");  
  48.   
  49.     return cfg;  
  50. }  
  51.   
  52. static void close_fd_t(cutil_fd_t* fd_t) {  
  53.     if ( fd_t == NULL )  
  54.         return;  
  55.   
  56.     SSL* ssl = fd_t->ssl;  
  57.     if ( ssl != NULL ) {  
  58.         SSL_shutdown(ssl);  
  59.         SSL_free(ssl);  
  60.     }  
  61.       
  62.     int fd = fd_t->fd;  
  63.     if (fd != ERROR_FD)  
  64.         close(fd);  
  65.   
  66.     free(fd_t);  
  67. }  
  68.   
  69. static int try_connect_ssl(SSL* ssl) {  
  70.     int ret,err;  
  71.     ret = SSL_connect(ssl);  
  72.     err = SSL_get_error(ssl, ret);  
  73.     if (ret == 1)   
  74.         return 0;  
  75.   
  76.     if (err != SSL_ERROR_WANT_WRITE && err != SSL_ERROR_WANT_READ ) {  
  77.         return -1;  
  78.     }  
  79.     return 1;  
  80. }  
  81.   
  82. static int lconnect(lua_State *L) {  
  83.     cutil_conf_t* cfg = fetch_config(L);  
  84.     if(!cfg->is_init)  
  85.     {  
  86.         luaL_error(L, "httpsc: Not inited");  
  87.         return 0;  
  88.     }  
  89.       
  90.     const char * addr = luaL_checkstring(L, 1);  
  91.     int port = luaL_checkinteger(L, 2);  
  92.   
  93.     cutil_fd_t* fd_t = (cutil_fd_t *)malloc(sizeof(cutil_fd_t));  
  94.     if ( fd_t == NULL )  
  95.         return luaL_error(L, "httpsc: Create fd %s %d failed", addr, port);  
  96.     fd_t->fd = ERROR_FD;  
  97.     fd_t->ssl = NULL;  
  98.     fd_t->status = CONNECT_INIT;  
  99.   
  100.       
  101.     int fd = socket(AF_INET, SOCK_STREAM, 0);  
  102.     struct sockaddr_in my_addr;  
  103.     fd_t->fd = fd;  
  104.   
  105.     bzero(&my_addr, sizeof(my_addr));  
  106.     my_addr.sin_addr.s_addr = inet_addr(addr);  
  107.     my_addr.sin_family = AF_INET;  
  108.     my_addr.sin_port = htons(port);  
  109.   
  110.     int ret;  
  111.     struct timeval timeo = {3, 0};  
  112.     socklen_t len = sizeof(timeo);  
  113.     ret = setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, len);  
  114.     if (ret) {  
  115.         close_fd_t(fd_t);  
  116.         return luaL_error(L, "httpsc: Setsockopt %s %d failed", addr, port);  
  117.     }  
  118.   
  119.     int flag = fcntl(fd, F_GETFL, 0);  
  120.     fcntl(fd, F_SETFL, flag | O_NONBLOCK);  
  121.   
  122.     ret = connect(fd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_in));  
  123.     if (ret != 0) {  
  124.         if (errno == EINPROGRESS) {  
  125.             fd_t->status = CONNECT_PORT;  
  126.         } else {  
  127.             close_fd_t(fd_t);  
  128.             return luaL_error(L, "httpsc: Connect %s %d failed", addr, port);  
  129.         }  
  130.   
  131.     } else {  
  132.         fd_t->status = CONNECT_SSL;  
  133.         SSL *ssl = SSL_new(ctx);  
  134.         fd_t->ssl = ssl;  
  135.         SSL_set_fd(ssl, fd);  
  136.         ret = try_connect_ssl(ssl);  
  137.         if (ret == 0) {  
  138.             fd_t->status = CONNECT_DONE;  
  139.         } else if (ret == -1) {  
  140.             close_fd_t(fd_t);  
  141.             return luaL_error(L, "httpsc ssl_connect error, errno = %d", errno);  
  142.         }  
  143.     }  
  144.       
  145.     lua_pushlightuserdata(L, fd_t);  
  146.   
  147.     return 1;  
  148. }  
  149.   
  150. static int lcheck_connect(lua_State *L) {  
  151.     cutil_fd_t* fd_t = (cutil_fd_t* ) lua_touserdata(L, 1);  
  152.     if ( fd_t == NULL )  
  153.         return luaL_error(L, "httpsc fd error");  
  154.   
  155.     switch (fd_t->status) {  
  156.         case CONNECT_DONE:  
  157.             lua_pushboolean(L, 1);  
  158.             return 1;  
  159.         case CONNECT_PORT:   
  160.             {  
  161.                 struct pollfd fds;  
  162.                 int ret, err;  
  163.                 fds.fd = fd_t->fd;  
  164.                 fds.events = POLLIN | POLLOUT;  
  165.                 /* get status immediately */  
  166.                 ret = poll(&fds, 1, 0);  
  167.   
  168.                 if (ret != -1) {  
  169.                     socklen_t len = sizeof(int);  
  170.                     ret = getsockopt(fd_t->fd, SOL_SOCKET, SO_ERROR, &err, &len);  
  171.                     if (ret < 0) {  
  172.                         close_fd_t(fd_t);  
  173.                         return luaL_error(L, "httpsc getsockopt error, ret = %d", ret);  
  174.                     }  
  175.                     if (err == 0) {  
  176.                         fd_t->status = CONNECT_SSL;  
  177.                         SSL *ssl = SSL_new(ctx);  
  178.                         fd_t->ssl = ssl;  
  179.                         SSL_set_fd(ssl, fd_t->fd);  
  180.                         ret = try_connect_ssl(ssl);  
  181.                         if (ret == 0) {  
  182.                             fd_t->status = CONNECT_DONE;  
  183.                             lua_pushboolean(L, 1);  
  184.                             return 1;  
  185.                         } else if (ret == -1) {  
  186.                             close_fd_t(fd_t);  
  187.                             return luaL_error(L, "httpsc connect ssl error, errno = %d", errno);  
  188.                         }  
  189.                     } else {  
  190.                         if (errno == EAGAIN || errno == EINTR || errno == EINPROGRESS ) {  
  191.                             return 0;  
  192.                         } else {  
  193.                             close_fd_t(fd_t);  
  194.                             return luaL_error(L, "httpsc connect sockopt error, errno = %d", errno);  
  195.                         }  
  196.                     }  
  197.                 } else {  
  198.                     close_fd_t(fd_t);  
  199.                     return luaL_error(L, "httpsc connect poll error, ret = %d", ret);  
  200.                 }  
  201.                 return 0;  
  202.             }  
  203.         case CONNECT_SSL:  
  204.             {  
  205.                 int ret = try_connect_ssl(fd_t->ssl);  
  206.                 if (ret == 0) {  
  207.                     fd_t->status = CONNECT_DONE;  
  208.                     lua_pushboolean(L, 1);  
  209.                     return 1;  
  210.                 } else if (ret == -1) {  
  211.                     close_fd_t(fd_t);  
  212.                     return luaL_error(L, "httpsc connect ssl 2 error, errno = %d", errno);  
  213.                 }  
  214.                 return 0;  
  215.             }  
  216.         default:  
  217.             ;  
  218.     }  
  219.   
  220.     close_fd_t(fd_t);  
  221.     return luaL_error(L, "httpsc connect fator error");  
  222. }  
  223.   
  224. static int lclose(lua_State *L) {  
  225.     cutil_fd_t* fd_t = (cutil_fd_t* ) lua_touserdata(L, 1);  
  226.     if ( fd_t == NULL )  
  227.         return luaL_error(L, "httpsc fd error");  
  228.       
  229.     close_fd_t(fd_t);  
  230.   
  231.     return 0;  
  232. }  
  233.   
  234. static void block_send(lua_State *L, SSL *ssl, const char * buffer, int sz) {  
  235.     int retry = SEND_RETRY;  
  236.     while(sz > 0) {  
  237.         if (retry <= 0) {  
  238.             luaL_error(L, "httpsc: socket error: retry timeout");  
  239.             return;  
  240.         }  
  241.         retry -= 1;  
  242.         int r = SSL_write(ssl, buffer, sz);  
  243.         if (r < 0) {  
  244.             if (errno == EAGAIN || errno == EINTR)  
  245.                 continue;  
  246.             luaL_error(L, "httpsc: socket error: %s", strerror(errno));  
  247.             return;  
  248.         }  
  249.         buffer += r;  
  250.         sz -= r;  
  251.     }  
  252. }  
  253.   
  254. static int lsend(lua_State *L) {  
  255.     cutil_conf_t* cfg = fetch_config(L);  
  256.     if(!cfg->is_init)  
  257.     {  
  258.         luaL_error(L, "httpsc: Not inited");  
  259.         return 0;  
  260.     }  
  261.       
  262.     size_t sz = 0;  
  263.     cutil_fd_t* fd_t = (cutil_fd_t* ) lua_touserdata(L, 1);  
  264.     if ( fd_t == NULL )  
  265.         return luaL_error(L, "httpsc fd error");  
  266.     SSL* ssl = fd_t->ssl;  
  267.     const char * msg = luaL_checklstring(L, 2, &sz);  
  268.   
  269.     block_send(L, ssl, msg, (int)sz);  
  270.   
  271.     return 0;  
  272. }  
  273.   
  274.   
  275. static int lrecv(lua_State *L) {  
  276.     cutil_conf_t* cfg = fetch_config(L);  
  277.     if(!cfg->is_init)  
  278.     {  
  279.         luaL_error(L, "httpsc: Not inited");  
  280.         return 0;  
  281.     }  
  282.     cutil_fd_t* fd_t = (cutil_fd_t* ) lua_touserdata(L, 1);  
  283.     if ( fd_t == NULL )  
  284.         return luaL_error(L, "httpsc fd error");  
  285.     SSL* ssl = fd_t->ssl;  
  286.     int top = lua_gettop(L);  
  287.   
  288.     char buffer[CACHE_SIZE];  
  289.     int size = CACHE_SIZE;  
  290.     if ( top > 1 && lua_isnumber(L, 2)) {  
  291.         int _size = lua_tointeger(L, 2);  
  292.         size = _size > size ? size : _size;  
  293.     }  
  294.   
  295.     int r = SSL_read(ssl, buffer, size);  
  296.     // if (r == 0) {  
  297.     //  lua_pushliteral(L, "");  
  298.     //  /* close */  
  299.     //  return 1;  
  300.     // }  
  301.     if (r <= 0) {  
  302.         if (errno == EAGAIN || errno == EINTR) {  
  303.             return 0;  
  304.         }  
  305.         return luaL_error(L, "httpsc: socket error: %s", strerror(errno));  
  306.     }  
  307.     lua_pushlstring(L, buffer, r);  
  308.     return 1;  
  309. }  
  310.   
  311.   
  312. static int lusleep(lua_State *L) {  
  313.     int n = luaL_checknumber(L, 1);  
  314.     usleep(n);  
  315.     return 0;  
  316. }  
  317.   
  318.   
  319. /* GC, clean up the buf */  
  320. static int _gc(lua_State *L)  
  321. {  
  322.     cutil_conf_t *cfg;  
  323.     cfg = lua_touserdata(L, 1);  
  324.   
  325.     if (ctx != NULL){  
  326.         SSL_CTX_free(ctx);  
  327.         ctx = NULL;  
  328.     }  
  329.       
  330.     /* todo: auto gc */  
  331.   
  332.     cfg = NULL;  
  333.     return 0;  
  334. }  
  335.   
  336. static void _create_config(lua_State *L)  
  337. {  
  338.     cutil_conf_t *cfg;  
  339.     cfg = lua_newuserdata(L, sizeof(*cfg));  
  340.     cfg->is_init = !!NULL;  
  341.     /* Create GC method to clean up buf */  
  342.     lua_newtable(L);  
  343.     lua_pushcfunction(L, _gc);  
  344.     lua_setfield(L, -2, "__gc");  
  345.     lua_setmetatable(L, -2);  
  346.   
  347.     /* openssl init */  
  348.     if ( ctx == NULL) {  
  349.         SSL_library_init();  
  350.         OpenSSL_add_all_algorithms();  
  351.         SSL_load_error_strings();  
  352.         ctx = SSL_CTX_new(SSLv23_client_method());  
  353.         if (ctx == NULL)  
  354.         {  
  355.             ERR_print_errors_fp(stdout);  
  356.             luaL_error(L, "httpsc: Unable to init openssl");  
  357.             return;  
  358.         }  
  359.     }  
  360.   
  361.     cfg->is_init = !NULL;  
  362. }  
  363.   
  364.   
  365. int luaopen_httpsc(lua_State *L)  
  366. {  
  367.     static const luaL_Reg funcs[] = {  
  368.         { "connect", lconnect },  
  369.         { "check_connect", lcheck_connect },  
  370.         { "recv", lrecv },  
  371.         { "send", lsend },  
  372.         { "close", lclose },  
  373.         { "usleep", lusleep },  
  374.         {NULL, NULL}  
  375.     };  
  376.   
  377.     lua_newtable(L);  
  378.     _create_config(L);  
  379.     luaL_setfuncs(L, funcs, 1);  
  380.   
  381.     return 1;  
  382. }  
#include <stdlib.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <time.h>

#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include "openssl/ssl.h"
#include "openssl/err.h"
#include <poll.h>


#define CACHE_SIZE 0x1000
#define ERROR_FD -1
#define SEND_RETRY 10

static SSL_CTX *ctx = NULL;

typedef struct {
	int is_init;
} cutil_conf_t;

enum conn_st
{
	CONNECT_INIT = 1,
	CONNECT_PORT = 2,
	CONNECT_SSL = 3,
	CONNECT_DONE = 4
};

typedef struct {
	int fd;
	SSL* ssl;
	enum conn_st status;
} cutil_fd_t;

static cutil_conf_t* fetch_config(lua_State *L) {
	cutil_conf_t* cfg;
	cfg = lua_touserdata(L, lua_upvalueindex(1));
	if (!cfg)
		luaL_error(L, "httpsc: Unable to fetch cfg");

	return cfg;
}

static void close_fd_t(cutil_fd_t* fd_t) {
	if ( fd_t == NULL )
		return;

	SSL* ssl = fd_t->ssl;
	if ( ssl != NULL ) {
		SSL_shutdown(ssl);
		SSL_free(ssl);
	}
	
	int fd = fd_t->fd;
	if (fd != ERROR_FD)
		close(fd);

	free(fd_t);
}

static int try_connect_ssl(SSL* ssl) {
	int ret,err;
	ret = SSL_connect(ssl);
	err = SSL_get_error(ssl, ret);
	if (ret == 1) 
		return 0;

	if (err != SSL_ERROR_WANT_WRITE && err != SSL_ERROR_WANT_READ ) {
		return -1;
	}
	return 1;
}

static int lconnect(lua_State *L) {
	cutil_conf_t* cfg = fetch_config(L);
	if(!cfg->is_init)
	{
		luaL_error(L, "httpsc: Not inited");
		return 0;
	}
	
	const char * addr = luaL_checkstring(L, 1);
	int port = luaL_checkinteger(L, 2);

	cutil_fd_t* fd_t = (cutil_fd_t *)malloc(sizeof(cutil_fd_t));
	if ( fd_t == NULL )
		return luaL_error(L, "httpsc: Create fd %s %d failed", addr, port);
	fd_t->fd = ERROR_FD;
	fd_t->ssl = NULL;
	fd_t->status = CONNECT_INIT;

	
	int fd = socket(AF_INET, SOCK_STREAM, 0);
	struct sockaddr_in my_addr;
	fd_t->fd = fd;

	bzero(&my_addr, sizeof(my_addr));
	my_addr.sin_addr.s_addr = inet_addr(addr);
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(port);

	int ret;
	struct timeval timeo = {3, 0};
	socklen_t len = sizeof(timeo);
	ret = setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, len);
	if (ret) {
		close_fd_t(fd_t);
		return luaL_error(L, "httpsc: Setsockopt %s %d failed", addr, port);
	}

	int flag = fcntl(fd, F_GETFL, 0);
	fcntl(fd, F_SETFL, flag | O_NONBLOCK);

	ret = connect(fd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_in));
	if (ret != 0) {
		if (errno == EINPROGRESS) {
			fd_t->status = CONNECT_PORT;
		} else {
			close_fd_t(fd_t);
			return luaL_error(L, "httpsc: Connect %s %d failed", addr, port);
		}

	} else {
		fd_t->status = CONNECT_SSL;
		SSL *ssl = SSL_new(ctx);
		fd_t->ssl = ssl;
		SSL_set_fd(ssl, fd);
		ret = try_connect_ssl(ssl);
		if (ret == 0) {
			fd_t->status = CONNECT_DONE;
		} else if (ret == -1) {
			close_fd_t(fd_t);
			return luaL_error(L, "httpsc ssl_connect error, errno = %d", errno);
		}
	}
	
	lua_pushlightuserdata(L, fd_t);

	return 1;
}

static int lcheck_connect(lua_State *L) {
	cutil_fd_t* fd_t = (cutil_fd_t* ) lua_touserdata(L, 1);
	if ( fd_t == NULL )
		return luaL_error(L, "httpsc fd error");

	switch (fd_t->status) {
		case CONNECT_DONE:
			lua_pushboolean(L, 1);
			return 1;
		case CONNECT_PORT: 
			{
				struct pollfd fds;
				int ret, err;
				fds.fd = fd_t->fd;
				fds.events = POLLIN | POLLOUT;
				/* get status immediately */
				ret = poll(&fds, 1, 0);

				if (ret != -1) {
					socklen_t len = sizeof(int);
					ret = getsockopt(fd_t->fd, SOL_SOCKET, SO_ERROR, &err, &len);
					if (ret < 0) {
						close_fd_t(fd_t);
						return luaL_error(L, "httpsc getsockopt error, ret = %d", ret);
					}
					if (err == 0) {
						fd_t->status = CONNECT_SSL;
						SSL *ssl = SSL_new(ctx);
						fd_t->ssl = ssl;
						SSL_set_fd(ssl, fd_t->fd);
						ret = try_connect_ssl(ssl);
						if (ret == 0) {
							fd_t->status = CONNECT_DONE;
							lua_pushboolean(L, 1);
							return 1;
						} else if (ret == -1) {
							close_fd_t(fd_t);
							return luaL_error(L, "httpsc connect ssl error, errno = %d", errno);
						}
					} else {
						if (errno == EAGAIN || errno == EINTR || errno == EINPROGRESS ) {
							return 0;
						} else {
							close_fd_t(fd_t);
							return luaL_error(L, "httpsc connect sockopt error, errno = %d", errno);
						}
					}
				} else {
					close_fd_t(fd_t);
					return luaL_error(L, "httpsc connect poll error, ret = %d", ret);
				}
				return 0;
			}
		case CONNECT_SSL:
			{
				int ret = try_connect_ssl(fd_t->ssl);
				if (ret == 0) {
					fd_t->status = CONNECT_DONE;
					lua_pushboolean(L, 1);
					return 1;
				} else if (ret == -1) {
					close_fd_t(fd_t);
					return luaL_error(L, "httpsc connect ssl 2 error, errno = %d", errno);
				}
				return 0;
			}
		default:
			;
	}

	close_fd_t(fd_t);
	return luaL_error(L, "httpsc connect fator error");
}

static int lclose(lua_State *L) {
	cutil_fd_t* fd_t = (cutil_fd_t* ) lua_touserdata(L, 1);
	if ( fd_t == NULL )
		return luaL_error(L, "httpsc fd error");
	
	close_fd_t(fd_t);

	return 0;
}

static void block_send(lua_State *L, SSL *ssl, const char * buffer, int sz) {
	int retry = SEND_RETRY;
	while(sz > 0) {
		if (retry <= 0) {
			luaL_error(L, "httpsc: socket error: retry timeout");
			return;
		}
		retry -= 1;
		int r = SSL_write(ssl, buffer, sz);
		if (r < 0) {
			if (errno == EAGAIN || errno == EINTR)
				continue;
			luaL_error(L, "httpsc: socket error: %s", strerror(errno));
			return;
		}
		buffer += r;
		sz -= r;
	}
}

static int lsend(lua_State *L) {
	cutil_conf_t* cfg = fetch_config(L);
	if(!cfg->is_init)
	{
		luaL_error(L, "httpsc: Not inited");
		return 0;
	}
	
	size_t sz = 0;
	cutil_fd_t* fd_t = (cutil_fd_t* ) lua_touserdata(L, 1);
	if ( fd_t == NULL )
		return luaL_error(L, "httpsc fd error");
	SSL* ssl = fd_t->ssl;
	const char * msg = luaL_checklstring(L, 2, &sz);

	block_send(L, ssl, msg, (int)sz);

	return 0;
}


static int lrecv(lua_State *L) {
	cutil_conf_t* cfg = fetch_config(L);
	if(!cfg->is_init)
	{
		luaL_error(L, "httpsc: Not inited");
		return 0;
	}
	cutil_fd_t* fd_t = (cutil_fd_t* ) lua_touserdata(L, 1);
	if ( fd_t == NULL )
		return luaL_error(L, "httpsc fd error");
	SSL* ssl = fd_t->ssl;
	int top = lua_gettop(L);

	char buffer[CACHE_SIZE];
	int size = CACHE_SIZE;
	if ( top > 1 && lua_isnumber(L, 2)) {
		int _size = lua_tointeger(L, 2);
		size = _size > size ? size : _size;
	}

	int r = SSL_read(ssl, buffer, size);
	// if (r == 0) {
	// 	lua_pushliteral(L, "");
	// 	/* close */
	// 	return 1;
	// }
	if (r <= 0) {
		if (errno == EAGAIN || errno == EINTR) {
			return 0;
		}
		return luaL_error(L, "httpsc: socket error: %s", strerror(errno));
	}
	lua_pushlstring(L, buffer, r);
	return 1;
}


static int lusleep(lua_State *L) {
	int n = luaL_checknumber(L, 1);
	usleep(n);
	return 0;
}


/* GC, clean up the buf */
static int _gc(lua_State *L)
{
	cutil_conf_t *cfg;
	cfg = lua_touserdata(L, 1);

	if (ctx != NULL){
		SSL_CTX_free(ctx);
		ctx = NULL;
	}
	
	/* todo: auto gc */

	cfg = NULL;
	return 0;
}

static void _create_config(lua_State *L)
{
	cutil_conf_t *cfg;
	cfg = lua_newuserdata(L, sizeof(*cfg));
	cfg->is_init = !!NULL;
	/* Create GC method to clean up buf */
	lua_newtable(L);
	lua_pushcfunction(L, _gc);
	lua_setfield(L, -2, "__gc");
	lua_setmetatable(L, -2);

	/* openssl init */
	if ( ctx == NULL) {
		SSL_library_init();
		OpenSSL_add_all_algorithms();
		SSL_load_error_strings();
		ctx = SSL_CTX_new(SSLv23_client_method());
		if (ctx == NULL)
		{
			ERR_print_errors_fp(stdout);
			luaL_error(L, "httpsc: Unable to init openssl");
			return;
		}
	}

	cfg->is_init = !NULL;
}


int luaopen_httpsc(lua_State *L)
{
	static const luaL_Reg funcs[] = {
		{ "connect", lconnect },
		{ "check_connect", lcheck_connect },
		{ "recv", lrecv },
		{ "send", lsend },
		{ "close", lclose },
		{ "usleep", lusleep },
		{NULL, NULL}
	};

	lua_newtable(L);
	_create_config(L);
	luaL_setfuncs(L, funcs, 1);

	return 1;
}

编译到项目执行后,lua调用的方法如下:
  1. local httpsc = require "httpsc"  
  2. for k,v in pairs(httpsc ) do  
  3.     print(k,v)  
  4. end  
  5.   
  6. local fd = httpsc.connect("163.177.151.109", 443)  
  7. print(fd)  
  8.   
  9. while true do  
  10.     local ok = httpsc.check_connect(fd)  
  11.     if ok then break end  
  12.     httpsc.usleep(10000)  
  13. end  
  14.   
  15. -- httpsc.usleep(10000)  
  16. httpsc.send(fd, "GET / HTTP/1.1\r\nAccept: */*\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n")  
  17.   
  18. httpsc.usleep(1000000)  
  19.   
  20. local body = ""  
  21. while true do  
  22.     local r = httpsc.recv(fd)  
  23.     print(r)  
  24.     if r and #r>0 then  
  25.         body = body .. r  
  26.     else  
  27.         break  
  28.     end  
  29. end  
  30.   
  31. print(body)  
  32.   
  33. httpsc.close(fd)  
  34.   
  35. print("ok!")  
local httpsc = require "httpsc"
for k,v in pairs(httpsc ) do
	print(k,v)
end

local fd = httpsc.connect("163.177.151.109", 443)
print(fd)

while true do
	local ok = httpsc.check_connect(fd)
	if ok then break end
	httpsc.usleep(10000)
end

-- httpsc.usleep(10000)
httpsc.send(fd, "GET / HTTP/1.1\r\nAccept: */*\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n")

httpsc.usleep(1000000)

local body = ""
while true do
	local r = httpsc.recv(fd)
	print(r)
	if r and #r>0 then
		body = body .. r
	else
		break
	end
end

print(body)

httpsc.close(fd)

print("ok!")
以上只是一个简单的例子,我同时也参考skynet clientsocket写了一个对HTTP协议支持友好的版本,代码托管在Git : https://github.com/chenweiqi/lua_httpsc ,欢迎交流。
这个lua代码目前有个不足,没有做openssl多线程支持,不支持同时多个线程加载和调用模块,这个以后的版本会做优化

OpenSSL动态库的编译

既然基于OpenSSL开发,就需要编译OpenSSL库来使用。
这里提供最近版本几个依赖库的编译方法:
openssl  -  libssl.a / libcrypto.a
  1. wget https://www.openssl.org/source/openssl-1.0.2k.tar.gz  
  2. tar -zxf openssl-1.0.2k.tar.gz  
  3. cd  openssl-1.0.2k  
  4. ./config -fPIC enable-shared  
  5. make depend  
  6. make  
  7. make install  
  8. find -name "*.a"  
wget https://www.openssl.org/source/openssl-1.0.2k.tar.gz
tar -zxf openssl-1.0.2k.tar.gz
cd  openssl-1.0.2k
./config -fPIC enable-shared
make depend
make
make install
find -name "*.a"

zlib - zlib.a
  1. wget http://zlib.net/zlib-1.2.11.tar.xz  
  2. tar -zxf zlib-1.2.11.tar.xz  
  3. cd zlib-1.2.11  
  4. export CFLAGS=" -fPIC"  
  5. ./configure  
  6. make  
  7. find -name "*.a"  
wget http://zlib.net/zlib-1.2.11.tar.xz
tar -zxf zlib-1.2.11.tar.xz
cd zlib-1.2.11
export CFLAGS=" -fPIC"
./configure
make
find -name "*.a"

idn - libidn.a
  1. wget http://ftp.gnu.org/gnu/libidn/libidn-1.33.tar.gz  
  2. tar -zxf libidn-1.33.tar.gz  
  3. cd libidn-1.33  
  4. export CFLAGS=" -fPIC"  
  5. ./configure  
  6. make  
  7. find -name "*.a"  
wget http://ftp.gnu.org/gnu/libidn/libidn-1.33.tar.gz
tar -zxf libidn-1.33.tar.gz
cd libidn-1.33
export CFLAGS=" -fPIC"
./configure
make
find -name "*.a"

更新说明:
2017/4/15 优化socket创建连接过程,由阻塞改成非阻塞方式
2017/6/9 标题由“skynet lua 支持HTTPS请求”改成“lua 异步HTTPS并发请求库”

参考:
http://blog.csdn.net/mycwq/article/details/64440253

给 skynet 增加 http 服务器模块: https://blog.codingnow.com/2014/07/skynet_http.html
转自:https://blog.csdn.net/mycwq/article/details/64440253

猜你喜欢

转载自blog.csdn.net/u010144805/article/details/80417146
今日推荐