ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

造https client轮子的记录

2022-01-24 21:02:02  阅读:190  来源: 互联网

标签:缓存 HTTP read chunk CTX SSL client https 轮子


最近我们的服务器需要嵌入HTTP服务,需要支持httpclient和httpserver,httpclient要同时支持https。
我们现在服务器进程之间的网络通信使用的是自有实现,它使用io多路复用技术。因为http只是在tcp之上进行明文传输而已,所以实现也包括了一个简陋的httpclient和httpserver,还基于openssl实现玩具版的https,但基本是不能在生产中使用的。
本来我想着用开源库,但看了下好像与epoll结合也挺麻烦,就先自己手撸轮子试试。虽然过程痛苦,但加深了对相关知识的理解,比如之前写的理解https的验证过程。以下是造轮子过程中遇到的一些问题。

SSL_METHOD

通过SSL_CTX *SSL_CTX_new(const SSL_METHOD *method);创建SSL_CTX对象,method又通过一系列的函数来创建,这些函数的参数都是void,返回值都是const SSL_METHOD *,其中SSLv23_method是通用版本,实际使用的SSL/TLS版本通过与对方协商确定,支持SSLv2,SSLv3,TLSv1,TLSv1.1,TLSv1.2,其他的比如TLSv1_2_method则只支持TLSv1.2协议。
更多细节可通过man SSL_CTX_new了解。

信任server端证书

测试环境webserver使用的是自签名证书,所以httpclient需要跳过证书验证步骤,直接信任webserver发来的证书。
查了一下网上说,需要自己注册一个验证证书的回调函数,我实际使用中发现,不对SSL_CTX对象调用SSL_CTX_load_verify_locations和SSL_CTX_use_certificate_file就不会去验证证书。

HTTP POST body常见数据格式

参考HTTP POST body常见的四种数据格式,我们使用的是application/x-www-form-urlencoded格式,这是浏览器的原生form表单格式,上文中说“如果不设置Content-Type属性,则默认以application/x-www-form-urlencoded方式传输数据”,我猜这个默认是浏览器给加的吧,反正我如果不手动添加“Content-Type: application/x-www-form-urlencoded”的header,那边的webserver不认我的请求。

URL编码

上面的x-www-form-urlencoded格式数据需要进行URL编码,这种编码很简单,需要把英文字母、阿拉伯数字、以及“-_.~”之外的字符都进行编码,空格编码成+,其他字符编码成%后面加字符十六进制数字的格式。
比如上面的application/x-www-form-urlencoded格式的body可能是“title=test&sub=%E5%A4%A7%E5%AE%B6”。

chunk编码

有些webserver返回的http报文中,没有Content-Length头部,取而代之的是Transfer-Encoding: chunked,表示包体长度未知或者把包体拆成几个小块传输。每个 chunk 的格式如下所示:

${chunk-length}\r\n${chunk-data}\r\n
其中,${chunk-length} 表示 chunk 的字节长度,使用 16 进制表示,${chunk-data} 为 chunk 的内容
当 chunk 都传输完,需要额外传输 0\r\n\r\n 表示结束

下面是一个例子(来自HTTP 进阶之 chunked 编码):

HTTP/1.1 200 OK
Date: Wed, 01 Jan 2020 08:46:31 GMT
Connection: keep-alive
Transfer-Encoding: chunked

1\r\n
a\r\n
3\r\n
+-=\r\n
14\r\n
ghijklmnopqrstuvwxyz\r\n
0\r\n\r\n

SSL_connect

客户端调用SSL_connect进行握手,如果socket是非阻塞的,SSL_connect会立即返回-1,此时需要调用SSL_get_error获得具体错误,如果是SSL_ERROR_WANT_READ表示还在握手中,等待下次触发读时再调用SSL_connect,如此往复,直到SSL_connect返回1完成握手。

边沿触发下数据的读取

SSL/TLS是介于TCP与HTTP之间的一层,它本身有buffer,epoll触发读调用SSL_read时,先把数据从socket缓存读入SSL缓存,再从SSL缓存读入用户缓存。
没有SSL的时候,epoll触发读了,调用recv或read从socket缓存读到用户缓存,边沿触发模式下一次没读完的话,主动EPOLL_CTL_MOD EPOLLIN事件,此时只要该socket的缓存还有数据可以读,epoll_wait会返回读就绪。
而改为SSL_read后,如果一下把socket缓存都读到了SSL缓存中,而又没有全读入用户缓存,要是没有新数据来到,epoll_wait就不会返回读就绪,SSL缓存中的数据就只能等对端关闭socket的时候触发读了。
应该判断SSL_read的返回值。正数表示读入字节数目,判断返回值如果比SSL_read的第三个参数(最多读取的字节数)小,表示已读取完毕,否则应继续尝试调用SSL_read,而不能像没有SSL的时候直接返回,因为那样就可能会没机会再触发读了。0或负数需要再调用SSL_get_error返回具体错误,如果是SSL_ERROR_WANT_READ表示SSL缓存中已无数据。

标签:缓存,HTTP,read,chunk,CTX,SSL,client,https,轮子
来源: https://blog.csdn.net/liuyuan185442111/article/details/122673637

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有