资讯专栏INFORMATION COLUMN

【大量干货】史上最完整的Tengine HTTPS原理解析、实践与调试

snowell / 1784人阅读

摘要:内容主要有四个方面趋势基础实践调试。一趋势这一章节主要介绍近几年和未来的趋势,包括两大浏览器和对的态度,以及淘宝天猫和阿里云的实践情况。完整性是指为了避免网络中传输的数据被非法篡改,使用算法来保证消息的完整性。

摘要: 本文邀请阿里云CDN HTTPS技术专家金九,分享Tengine的一些HTTPS实践经验。内容主要有四个方面:HTTPS趋势、HTTPS基础、HTTPS实践、HTTPS调试。 一、HTTPS趋势 这一章节主要介绍近几年和未来HTTPS的趋势,包括两大浏览器chrome和firefix对HTTPS的态度,以及淘宝天猫和阿里云CDN的HTTPS实践情况。

本文邀请阿里云CDN HTTPS技术专家金九,分享Tengine的一些HTTPS实践经验。内容主要有四个方面:HTTPS趋势、HTTPS基础、HTTPS实践、HTTPS调试。

一、HTTPS趋势
这一章节主要介绍近几年和未来HTTPS的趋势,包括两大浏览器chrome和firefix对HTTPS的态度,以及淘宝天猫和阿里云CDN的HTTPS实践情况。

上图是 chrome 统计的HTTPS网页占比的趋势,2015年的时候,大多数国家的HTTPS网页加载次数只占了不到 50%,2016年美国这个占比到了将近60%,去年就已经超过 70%,目前已经超过 80%。最下面是日本,目前是60%左右,这里面没有统计到中国的数据,我估计中国的占比会更少,空间还有很大,但未来HTTPS趋势是明显的。

同样,Firefox 浏览器加载HTTPS网页的统计跟 chrome 差不多,全球 HTTPS网页加载占比在 70% 左右,上升趋势也很明显。

更值得关注的是,Google 在今年年初的时候在其安全博客上表明,在今年7月份左右发布的 chrome68 浏览器会将所有HTTP网站标记为不安全,现在是5月份底了,离这个时间也就一个多月的时间了。右边的截图可以看出本来是绿色的小锁变成了不安全,这种网页在输入密码时就很不安全了。

早期天猫淘宝只是在关键的登录和交易的环节上了HTTPS,但随着互联网的发展,劫持、篡改等等问题也越来越严重,试想在天猫淘宝上看的商品图片被恶意人替换了或者价格被篡改了会怎么样?这样用户、商家和平台都受到伤害,只有上了 HTTPS才能从根本上解决这些问题。所以,天猫和淘宝在2015年7月份的时候已经完成了全站HTTPS。

二、HTTPS基础
本章节主要介绍HTTPS为什么安全,包括对称加密、非对称加密、签名、证书&证书链、SSL是怎么握手的、以及私钥密钥在HTTPS中发挥什么样的作用、keyless又是解决什么样的问题等等。

HTTPS的定义
简单来讲,HTTPS就是安全的HTTP,我们知道HTTP是运行在TCP层之上的,HTTPS在HTTP层和TCP层之间加了一个SSL层,SSL向上提供加密和解密的服务,对HTTP比较透明,这样也便于服务器和客户端的实现以及升级。

HTTPS为什么安全?
HTTPS安全是由一套安全机制来保证的,主要包含这4个特性:机密性、完整性、真实性和不可否认性。

机密性是指传输的数据是采用Session Key(会话密钥)加密的,在网络上是看不到明文的。
完整性是指为了避免网络中传输的数据被非法篡改,使用MAC算法来保证消息的完整性。
真实性是指通信的对方是可信的,利用了PKI(Public Key Infrastructure 即『公钥基础设施』)来保证公钥的真实性。
不可否认性是这个消息就是你给我发的,无法伪装和否认,是因为使用了签名的技术来保证的。
对称加密和非对称加密
HTTPS有对称加密和非对称加密两种算法,目的都是把明文加密成密文,区别是密钥的个数不一样,对称加密是一把密钥,这把密钥可以加密明文,也可以解密加密后的密文,常见的对称加密算法有AES、DES、RC4,目前最常用的是AES。

非对称加密是两把密钥,分别是公钥和私钥,公钥加密的密文只有相对应的私钥才能解密,私钥加密的内容也只有相对应的公钥才能解密,其中公钥是公开的,私钥是自己保存,不能公开,常见的非对称加密算法有RSA和ECC(椭圆曲线算法)。

SSL结合了这两种加密算法的优点,通过非对称加密来协商对称加密的密钥,握手成功之后便可使用对称加密来做加密通信,对于RSA来说,客户端是用RSA的公钥把预主密钥加密后传给服务器,服务器再用私钥来解密,双方再通过相同的算法来生成会话密钥,之后的应用层数据就可以通过会话密钥来加密通信。

签名
SSL中还有一个使用非对称加密的地方就是签名,签名的目的是让对方相信这个数据是我发送的,而不是其他人发送的。

密钥安全强度与性能对比
加密之后的数据破解难度就体现在密钥的长度上,密钥越长,破解难度也越大,但是运算的时间也越长,性能也就越差。相同安全强度下,对称密钥长度在最短,ECC次之,RSA密钥长度则最长。

目前比较常用的密钥长度是:对称密钥128位、ECC 256位、RSA 2048位。

RSA和ECC在SSL中更多的是用来签名,通过测试发现,ECC 的签名性能比RSA好很多,但是RSA的验签性能比ECC更好,所以RSA更适合于验证签名频繁而签名频度较低的场景,ECC更适合于签名频繁的场景,在SSL场景中,ECC算法性能更好。

公钥基础设施(PKI)
简单的说,PKI就是浏览器和CA,CA是整个安全机制的重要保障,我们平时用的证书就是由CA机构颁发,其实就是用CA的私钥给用户的证书签名,然后在证书的签名字段中填充这个签名值,浏览器在验证这个证书的时候就是使用CA的公钥进行验签。

证书
那CA在PKI中又是怎样发挥作用的呢,首先,CA的作用就是颁发证书,颁发证书其实就是使用CA的私钥对证书请求签名文件进行签名,其次,CA颁发的证书浏览器要信任,浏览器只需要用CA的公钥进行验签成功就表示这个证书是合法可信的,这就需要浏览器内置CA的公钥,也就是内置CA的证书。一般来说,操作系统都会内置权威CA的证书,有的浏览器会使用操作系统内置的CA证书列表,有的浏览器则自己维护的CA证书列表,比如Firefox。

CA分为根CA,二级CA,三级CA,三级CA证书由二级CA的私钥签名,二级CA证书由根CA的私钥签名,根CA是自签名的,不会给用户证书签名,我们平时用的证书都是由二级CA或者三级CA签名的,这样就形成了一个证书链,浏览器在验签的时侯一层层往上验证,直到用内置的根CA证书的公钥来验签成功就可以表示用户证书是合法的证书。
根证书已经内置在浏览器或者操作系统里了,在SSL握手时就不需要发根CA证书了,只需要提供中间二级三级CA证书和用户证书就可以。

交叉证书
交叉证书的应用场景是这样的:假如现在阿里云成为一个新的根CA机构,那阿里云签发的证书想要浏览器信任的话,阿里云的根证书就需要内置在各大操作系统和浏览器中,这需要较长时间的部署,那在没有完全部署完成之前,阿里云签发的证书怎么才能让浏览器信任呢,这就需要用到交叉证书了。

上图中,根证书A是一个所有浏览器都内置了的根证书,B是一个新的根证书,用户证书D是由根证书 B的二级CA B1来签发的,但是根证书B并没有在浏览器中内置,所以浏览器不会信任用户证书D,这是因为浏览器在验签时只验证到B1这一层然后找不到根证书B就无法验证下去了。
如果二级CA B1证书是由根证书A来签名,那这个问题就解了,所以新的根证书机构B会将二级CA证书(准确地讲是二级CA的证书签名请求文件)给老的根证书A来签名,然后得到一个新的中间证书就是交叉证书。
浏览器在验证的时侯用了新的信任链,这样就可以解决用户证书的信任问题。

当B的根证书全部部署完成后,再替换成自己的二级CA就可以了。

证书分类

证书分有三类:DV、OV、EV

DV证书只校验域名的所有权,所以我们在申请DV证书的时候CA会提供几种验证域名所有权的方法:比如文件校验、DNS校验、邮件校验,DV证书支持单域名、多域名和泛域名,但不支持多个泛域名,只支持一个泛域名,一般价格比较便宜,具体价格跟域名的数量有关,域名越多价格越高。

OV证书要验证组织机构,在申请证书时CA会打电话确认这个域名是否真的属于相应的公司或者机构,OV证书是单域名、多域名、泛域名和多个泛域名都支持,价格一般比DV证书要贵。

EV证书的校验就更细了,比如组织机构的地址之类的,EV证书会在地址栏上显示组织机构的名称,EV证书只支持单域名或者多个域名,不支持泛域名,而且更贵,所以普通用户一般不会用EV证书。

证书吊销
证书是有生命周期的,如果证书的私钥泄漏了那这个证书就得吊销,一般有两种吊销方式:CRL和OCSP。

CRL是CA机构维护的一个已经被吊销的证书序列号列表,浏览器需要定时更新这个列表,浏览器在验证证书合法性的时候也会在证书吊销列表中查询是否已经被吊销,如果被吊销了那这个证书也是不可信的。可以看出,这个列表随着被吊销证书的增加而增加,列表会越来越大,浏览器还需要定时更新,实时性也比较差。

所以,后来就有了 OCSP 在线证书状态协议,这个协议就是解决了 CRL 列表越来越大和实时性差的问题而生的。有了这个协议,浏览器就可以不用定期更新CRL了,在验证证书的时候直接去CA服务器实时校验一下证书有没有被吊销就可以,是解决了CRL的问题,但是每次都要去CA服务器上校验也会很慢,在网络环境较差的时候或者跨国访问的时候,体验就非常差了,OCSP虽然解决了CRL的问题但是性能却很差。所以后来就有了OCSP stapling。

OCSP stapling主要解决浏览器OCSP查询性能差的问题,本来由浏览器去CA的OCSP服务器查询证书状态的,现在改由域名的服务器去查询,将OCSP的结果告诉浏览器即可,一般服务器的网络性能要好于用户电脑的网络,所以OCSP stapling的性能是最好的。但是并不是所有的服务器都支持OCSP stapling,所以目前浏览器还是比较依赖CRL,证书吊销的实时性要求也不是特别高,所以定期更新问题也不大。

HTTPS工作模式
对于客户端和服务器来讲,应用层协议还是HTTP,只是加了一个SSL层来做加密解密的逻辑,对应用层来说是透明的,在网络上传输的都是加密的请求和加密的响应,从而达到安全传输的目的。

这是SSL层在网络模型的位置,SSL属于应用层协议。接管应用层的数据加解密,并通过网络层发送给对方。

更细地分,SSL协议分握手协议和记录协议,握手协议用来协商会话参数(比如会话密钥、应用层协议等等),记录协议主要用来传输应用层数据和握手协议消息数据,以及做加解密处理。我们应用层的的消息数据在SSL记录协议会给分成很多段,然后再对这个片段进行加密,最后在加上记录头后就发送出去。

TLS握手协议
SSL/TLS 握手协议又细分为四个子协议,分别是握手协议、密码规格变更协议、警告协议和应用数据协议。

完整的握手流程

首先是TCP握手,TCP三次完成之后才进入SSL握手,SSL握手总是以ClientHello消息开始,就跟TCP握手总是以SYN包开始一样;

ClientHello主要包含客户端支持的协议、密钥套件、session id、客户端随机数、sni、应用层协议列表(http/1.1、h2)、签名算法等等;
服务器收到ClientHello之后会从中选取本次通信的协议版本、密钥套件、应用层协议、签名算法,以及服务器随机数,然后通过ServerHello消息响应,如果没有session复用,那将服务器的证书通过Certificate消息响应,如果是选用了ECC算法来做密钥交换的话,那还会将椭圆曲线参数以及签名值通过ServerKeyExchange消息发送给对方,最后通过ServerHelloDone消息来结束本次协商过程;

客户端收到这些消息之后,会生成预主密钥、主密钥和会话密钥,然后将椭圆曲线参数通过ClientKeyExchange发送给服务器,服务器拿到客户端的椭圆曲线参数也会生成预主密钥,如果是RSA的话ClientKeyExchange用来发送公钥加密的预主密钥,服务器用私钥来解密一下就可以得到预主密钥,再由预主密钥生成主密钥和会话密钥。最后双方都以ChangeCipherSpce和Finished消息来告知对方,自己已经准备好了可以进行加密通信了,然后握手完成。

可以看出,完整的SSL握手需要2个RTT,而且每次握手都用到了非对称加密算法签名或者解密的操作,比较耗CPU,所以后来就有了session复用的优化手段,后面会做介绍。

SSL的握手消息虽然比较多,但很多消息都是放在一个TCP包中发送的,从抓包可以看出完整的SSL握手需要2个RTT。

刚才通过完整的SSL握手可以看出几个缺点:
1、2个RTT,首包时间较长
2、每次都要做非对称加密,比较耗CPU,影响性能
3、每次都要传证书,证书一般都比较大,浪费带宽

SSL握手的目的是协商会话密钥以及参数,如果能把这些会话参数缓存那就可以没有必要每次都传证书和做非对称加密计算,这样就可以提高握手性能,而且可以将RTT减到1个,提高用户体验。

所以就有了session id的复用方式,每次完整握手之后都将协商好的会话参数缓存在服务器中,客户端下次握手时会将上次握手的session id带上,服务器通过session id查询是否有会话缓存,有的话就直接复用,没有的话就重新走完整握手流程。但是session id这种方式也有缺点,比较难支持分布式缓存以及耗费服务器的内存。

而session ticket 方案,其原理可以理解为跟 http cookie 一样,服务器将协商好的会话参数加密成 session ticket,然后发送给客户端,客户端下次握手时会将这个session ticket带上,服务器解密成功就复用上次的会话参数,否则就重新走完整握手流程。可以看出session ticket这种方式不需要服务器缓存什么,支持分布式环境,有很大的优势。这种方式有一个缺点是并不是所有客户端都支持,支持率比较低,但随着客户端版本的更新迭代,以后各种客户端会都支持。因为session id客户端支持率比较高,所以目前这两种方式都在使用。

这是 session ticket 的复用,跟 session id 的区别是 client hello 会带上 Session ticket ,将近200个字节的加密数据,服务器会用session ticket 密钥来解密,解密成功则直接复用,也简化了握手流程,提升效率和性能。

这是我们上了分布式 Session id 复用的效果,session id复用的比例在没有开启分布式缓存时只占了3%左右,复用率很低,上了分布式session缓存之后,这个比例提升到20%多。握手时间也从75ms左右降低到65ms左右,性能提升效果还是很明显的。

SSL/TLS握手时的私钥用途(RSA、ECDHE)
我们知道私钥是整个SSL中最重要的东西,那私钥在SSL握手里面又是怎么使用的呢?两种使用方式分别是:使用RSA来做密钥交换和使用ECDHE来做密钥交换。对于RSA来说,客户端生成预主密钥,然后用公钥加密再发给服务器,服务器用私钥来解密得到预主密钥,然后由预主密钥生成主密钥,再由主密钥生会话密钥,最后用会话密钥来通信。

对于ECDHE来说,客户端和服务器双方是交换椭圆曲线参数,私钥只是用来签名,这是为了保证这个消息是持有私钥的人给我发的,而不是冒充的。双方交换完参数之后生成预主密钥,再生成主密钥和会话密钥。这就跟刚才RSA后面的流程一样了。

可以看出RSA和椭圆曲线密钥交换算法的私钥用途是不一样的,RSA密钥交换时是用来做加解密的,椭圆曲线密钥交换时是用来做签名的。

SSL/TLS中的密钥
预主密钥、主密钥和会话密钥,这几个密钥都是有联系的。

对于RSA来说,预主密钥是客户端生成,加密之后发给服务器,服务器用私钥来解密。对于ECDHE来说,预主密钥是双方通过椭圆曲线算法来生成。

主密钥是由预主密钥、客户端随机数和服务器随机数通过PRF函数来生成;会话密钥是由主密钥、客户端随机数和服务器随机数通过PRF函数来生成,会话密钥里面包含对称加密密钥、消息认证和CBC模式的初始化向量,但对于非CBC模式的加密算法来说,就没有用到这个初始化向量。

session 缓存和session ticket里面保存的是主密钥,而不是会话密钥,这是为了保证每次会话都是独立的,这样才安全,即使一个主密钥泄漏了也不影响其他会话。

Keyless
刚才提到RSA和ECDHE的私钥用途,对于服务器来说,密钥交换算法是RSA时,私钥是用来做解密的,ECDHE时,私钥是用来签名的。

Keyless就是将私钥参与运算的部分分离远程服务器,这样可以解决两个问题:
1,公私钥分离,用户不需要将私钥给第三方,减少私钥泄露的风险。
2,将非对称加密运算这种cpu密集型运算剥离到keyserver,可以在keyserver中安装ssl加速卡做硬件加速,提高性能。

HTTP/2
HTTP/2主要有这几个特性:
一、二进制协议,可以做更多的优化;
二、多路复用,一定程度上解决了队头阻塞的问题,提高并发和总的加载时间
三、头部压缩,很多请求头或者响应头都没有必要重复传输浪费带宽,比如user-agent、cookie还有其他没有必要每次都传的头部在http2中都做了优化
四、安全,虽然RFC规定HTTP/2可以运行在明文之上,但目前所有浏览器都只支持https之上的http/2,所以是安全的。

开启HTTP/2会有这几个收益:

一、加载时间的提升,我们做了这么一个测试,一张大图片分割成几百个小图片,然后用http/1.1和http/2打开,http/1.1加载完所有小图片用了五六秒,但http/2加载完所有小图片只需要不到1s的时间,有5倍左右的提升,效果还是很明显的,具体提升百分之多少这得具体看具体的业务类型。

二、头部压缩有95%左右的提升,http/1.1的平均响应头大小有500个字节左右,而http/2的平均响应头大小只有20多个字节,提升非常大,所以http/2非常适合小图片小文件的业务类型,因为小文件的http头部比重较大,而http/2对头部的压缩做了非常好的优化。

三、服务器QPS的性能有60%多的提升,这是因为http/2的连接复用和多路复用机制,可以处理更多的并发请求。

三、HTTPS实践
本章节会重点给大家讲讲 Tengine 如何配置 https、HTTP/2、TLSv1.3,以及如何实现动态证书。

Tengine如何开启HTTPS

http {
        ssl_session_tickets         on;
        ssl_session_ticket_key     ticket.key;

        server {
            listen                443 ssl;
            ssl_protocols           TLSv1 TLSv1.1 TLSv1.2;
            ssl_ciphers                  EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;
            ssl_prefer_server_ciphers     on;
            ssl_certificate             www.alicdn.com.crt;
            ssl_certificate_key     www.alicdn.com.key;     
            ssl_session_cache              shared:SSL:256M;
            ssl_session_timeout      15m;
            #……
        }
}

在Tengine中开启http2,只需要在listen的后面加上http2参数就可以了。

server {
    listen           443 ssl http2;
    http2               on;
    server_name     a.com;
    #……
}
server {
    listen           443 ssl;
    http2               off;
    server_name     b.com;
    #……
}

但是有一个场景需要注意,因为有些域名并不想开启http2,比如上面这个配置的b.com并不想开http2,但是因为a.com开启了http2,所以b.com也被自动开启了,这是因为http2这个参数作用在ip和端口上,在ssl握手时用了a.com的配置参数,所以tengine针对这种情况做了一个域名级别的开关来做控制。

TLSv1.3——更快、更安全
1、1-RTT & 0-RTT
2、只支持完全前向安全性的密钥交换算法
3、ServerHello 之后的所有消息都是加密的
4、淘汰 Session ID 和 Session Ticket,用 PSK 代替
5、Chrome 63+, Firefox 58+

Tengine也支持了TLSv1.3,开启方式:

server {
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers           TLS13-AES-128-GCM-SHA256:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-256-GCM-SHA384:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;
    #……
}

Tengine 实现与配置动态证书
动态证书主要解决的问题是接入域名太多,server块过多导致tengine reload慢的问题,lua-nginx模块提供了一个证书的lua阶段,可以在这个阶段来做证书的热加载,不需要reload tengine,这样可以提高效率和性能。

配置也比较简单,在ssl_cert.lua里面做证书的管理,在ssl握手时拿到sni,去拿这个域名的证书和私钥,再调用lua ffi接口就可以完成证书和私钥的切换。

server {
    ssl_certificate             www.alicdn.com.crt;
    ssl_certificate_key         www.alicdn.com.key;
    ssl_certificate_by_lua_file         ssl_cert.lua; 
    #……
}

ssl_cert.lua:
调用 lua ffi 接口设置证书和私钥

四、HTTPS调试
我们知道HTTP是明文传输,调试就很简单,抓包就可以看得清清楚楚,但HTTPS是加密的,抓包看到的是密文,这一节我告诉大家怎么去解密HTTPS抓包文件。

抓包解密
方法一:
配置Wireshark:

Wireshark  preferences…  Protocols  SSL  (Pre)-Master-Secret log filename => /tmp/sslkey.txt
(一):

export  SSLKEYLOGFILE=/tmp/sslkey.txt

(二):

openssl s_client -connect 127.0.0.1:443 -servername www.alicdn.com -keylogfile /tmp/sslkey.txt

(三):

echo “CLIENT_RANDOM 7EC0498BCF09E8300A1E9F8BA6C81E2A4383D7CDCFB10907B4074520FA8DF680 FA2457782F6FAECE47CF8E01BF9E0A441CEA8DCC91664F42F45F1EF5AB18ED902E35825713FF2D4D9651CE51ED885BB4
”>> /tmp/sslkey.txt

方法二:
强制使用RSA密钥交换算法,Wireshark配置私钥。

抓包解密-wireshark设置

常用命令及参数

curl

写在最后
证书的购买和申请是非常复杂耗时的,为了缩短开通周期,为开发者提供最大化便利,简化HTTPS加速设置环节,目前阿里云CDN已经支持控制台可实现一键开通HTTPS,后台将完成代理免费证书购买、证书节点部署以及证书到期之前自动续签,帮助开发者更便捷的完成全站HTTPS访问加速。

了解阿里云CDN 快速开启HTTPS加速

最后,金九老师所在的团队最近正在招聘系统研发专家 & HTTPS 研发专家,需要熟悉C/C++/Lua/Golang或tengine/nginx/openresty/openssl的伙伴加入,联系邮箱:zuxi.wzx@alibaba-inc.com,欢迎志同道合的技术专家们联系我们哦~

原文链接

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/11372.html

相关文章

  • 深度解析Tengine调试资源监控方法论

    摘要:是由淘宝网发起的服务器项目。回源监控是内容分发网络的简称,其分发的内容来自用户源站,负责回源的模块是最重要组成部分之一,使跨越单机的限制,完成网络数据的接收处理和转发。这部分主要介绍的一些调试技巧和回源资源监控的内容,以及相应的实例分享。 摘要: Tengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,提供更强大的流量负载均衡能力、全站HTTPS...

    everfight 评论0 收藏0
  • 架构~微服务

    摘要:接下来继续介绍三种架构模式,分别是查询分离模式微服务模式多级缓存模式。分布式应用程序可以基于实现诸如数据发布订阅负载均衡命名服务分布式协调通知集群管理选举分布式锁和分布式队列等功能。 SpringCloud 分布式配置 SpringCloud 分布式配置 史上最简单的 SpringCloud 教程 | 第九篇: 服务链路追踪 (Spring Cloud Sleuth) 史上最简单的 S...

    xinhaip 评论0 收藏0
  • 「码个蛋」2017年200篇精选干货集合

    摘要:让你收获满满码个蛋从年月日推送第篇文章一年过去了已累积推文近篇文章,本文为年度精选,共计篇,按照类别整理便于读者主题阅读。本篇文章是今年的最后一篇技术文章,为了让大家在家也能好好学习,特此花了几个小时整理了这些文章。 showImg(https://segmentfault.com/img/remote/1460000013241596); 让你收获满满! 码个蛋从2017年02月20...

    wangtdgoodluck 评论0 收藏0

发表评论

0条评论

snowell

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<