文章目录
- [一、HTTP 简介](#一、HTTP 简介)
- [二、HTTP 协议格式:](#二、HTTP 协议格式:)
-
- [2.1 抓包工具的使用:](#2.1 抓包工具的使用:)
- [2.2 HTTP 请求报文格式:](#2.2 HTTP 请求报文格式:)
- [2.3 HTTP 响应报文格式:](#2.3 HTTP 响应报文格式:)
- [2.4 HTTP 协议格式总结:](#2.4 HTTP 协议格式总结:)
- [三、HTTP 请求详解:](#三、HTTP 请求详解:)
-
- [3.1 刨析 URL:](#3.1 刨析 URL:)
- [3.2 方法(method):](#3.2 方法(method):)
-
- [3.2.1 GET 方法:](#3.2.1 GET 方法:)
- [3.2.2 POST 方法:](#3.2.2 POST 方法:)
- [3.2.3 经典面试题:谈谈 GET 和 POST 的区别:](#3.2.3 经典面试题:谈谈 GET 和 POST 的区别:)
- [3.2.4 其他方法:](#3.2.4 其他方法:)
- [3.3 HTTP 请求的构造方式:](#3.3 HTTP 请求的构造方式:)
- [3.4 请求报头(header):](#3.4 请求报头(header):)
- [3.5 请求正文(body):](#3.5 请求正文(body):)
- [四、HTTP 响应详解:](#四、HTTP 响应详解:)
- 五、HTTPS(重点)
-
- [5.1 核心概念:](#5.1 核心概念:)
- [5.2 HTTPS 的工作流程:](#5.2 HTTPS 的工作流程:)
-
- [5.2.1 对称加密:](#5.2.1 对称加密:)
- [5.2.2 非对称加密:](#5.2.2 非对称加密:)
- [5.2.3 中间人攻击:](#5.2.3 中间人攻击:)
- [5.2.4 引入证书:](#5.2.4 引入证书:)
- [5.2.5 完整流程:](#5.2.5 完整流程:)
一、HTTP 简介
HTTP (全称为"超文本传输协议")是一种应用非常广泛的应用层协议。
HTTP 往往是基于传输层的 TCP 协议实现的。(HTTP1.0,HTTP1.1,HTTP2.0 均为TCP,HTTP3 基于 UDP 实现)。
目前我们主要使用的还是 HTTP1.1 和 HTTP2.0。本文章主要以 HTTP 1.1 版本为主。
https 是在 http 基础之上做了一个加密解密的工作(SSL)。
二、HTTP 协议格式:
HTTP 是一个文本格式的协议。可以通过 Chrome 开发者工具或者 Fiddler 抓包,分析 HTTP 请求/响应的细节。
由于 Fiddler 抓 HTTP 的效果更好,所以我们采用 Fiddler 作为我们的抓包工具。
2.1 抓包工具的使用:
Fiddler 下载地址:Fiddler 官网
下载流程:
- 打开官网,点击下面红色框框里面的内容。
- 填入信息即可安装。
- 安装成功后的界面如下(左边一开始不一定有信息):
-
出现这个页面,一定要选取"是"同意安装证书,否则无法抓取 HTTPS(如果点了否,就要卸掉,重新安装)。
使用教程:
-
左侧窗口显示了所有的 HTTP请求/响应,可以选中某个请求查看详情。
-
右侧上方显示了 HTTP 请求的报文内容。(切换到 Raw 标签页可以看到详细的数据格式)。
-
右侧下方显示了 HTTP 响应的报文内容。(切换到 Raw 标签页可以看到详细的数据格式)。
-
请求和响应的详细数据,可以通过右下角的 View in Notepad 通过记事本打开。
-
可以使用 ctrl + a 全选左侧的抓包结果,delete 键清除所有被选中的结果。
点击左下角 Capturing 即可停止抓包,再次点击开始抓包。
抓包工具的原理:
Fiddler 相当于一个 "代理"。
浏览器访问 csdn 时,就会把 HTTP 请求先发给 Fiddler,Fiddler 再把请求转发给 csdn 的服务器。当 csdn 服务器返回数据时,Fiddler 拿到返回数据,再把数据交给浏览器。因此 Fiddler 对于浏览器和 csdn 服务器之间交互的数据细节,都是非常清楚的。
2.2 HTTP 请求报文格式:
一个 HTTP 请求报文,分成 4 个部分(1)首行(2)请求头(3)空行(4)正文。
- 首行:
首行又包含三个部分。
- 请求的方法(GET,POST之类的)。
- 请求的 URL(请求的对方地址)。
- 版本号(例如 HTTP/1.1)。
这三个部分使用空格进行区分。
- 请求头(header):
请求的属性,冒号分割的键值对。
每组属性之间使用\n
分隔。
遇到空行表示就 Header 部分结束。
- 空行:
最后一个 header 后面,存在一个空行,类似于链表,使用 null 结尾。
- 正文(body):
空行后面的内容都是 Body。Body 允许为空字符串。如果 Body 存在,则在 Header 中会有一个 Content-Length 属性来标识 Body 的长度。
2.3 HTTP 响应报文格式:
一个 HTTP 响应报文也是分成 4 个部分(1)首行(2)响应头(3)空行(4)正文。
基本和 HTTP 的请求报文格式差不多,区别比较大的就是首行,所以接下来主要分析首行。
响应的首行也是由三部分组成(1)版本号(2)状态码(3)状态码描述。
状态码和状态码描述,可以反应此次响应具体情况。
2.4 HTTP 协议格式总结:
这里有个问题:为什么 HTTP 报文要存在 "空行"?
因为 HTTP 协议并没有规定报头部分的键值对有多少个。空行就相当于是 "报头的结束标记",或者是"报头和正文之间的分隔符"。HTTP 在传输层依赖 TCP 协议,TCP 面向字节流,如果没有这个空行,就会出现"粘包问题"。
三、HTTP 请求详解:
3.1 刨析 URL:
平时我们俗称的"网址"其实就是说的 URL (Uniform Resource Locator 统一资源定位符)。
互联网上的每个文件都有一个唯一的 URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。
URL 有的部分可以省略。
- 协议名称: 这个没什么好说的,就是应用层的协议名称。
- 身份认证信息:这个是远古时代弄得,现在基本没有这么做的了(省略)。
- 服务器地址:要访问的服务器的 IP 地址 / 域名。
- 服务器端口号:可以省略,省略之后,由浏览器自动指定一个默认的端口,跟随协议。例如:http 对应 80,https 对应 443。
- 带层次的文件路径:描述了要访问服务器的哪个资源。
- 查询字符串(query string):浏览器给服务器传递的参数(提供一些辅助信息)。内容是键值对,使用 & 分割键值对,使用 = 分割键和值。此处的键和值有啥,都是根据程序员自行设定的。
- 片段标识符:用于页面跳转。
URL 中的可省略部分:
-
协议名:可以省略,省略后默认为 http://。
-
地址/域名:在 HTML 中可以省略(比如 img,link,script,a 标签的 src 或者 href 属性)。省略后表示服务器的 ip/域名 与当前 HTML 所属的 ip/域名 一致。
-
端口号:可以省略。省略后如果是 http 协议,端口号自动设为 80。如果是 https 协议,端口号自动设为 443。
-
带层次的文件路径:可以省略。省略后相当于
/.
。 -
查询字符串:可以省略。
-
片段标识:可以省略。
URL encode:
像/ ? :
等这样的字符,已经被 url 当做特殊意义理解了,因此这些字符不能随意出现。如果某个参数中需要带有这些特殊字符。就必须先对特殊字符进行转义。
一个中文字符由 UTF-8 或者 GBK 这样的编码方式构成,虽然在 URL 中没有特殊含义,但是仍然需要进行转义。否则浏览器可能把 UTF-8/GBK 编码中的某个字节当做 URL 中的特殊符号。
转义规则:
- 将需要编码的字符转换为十六进制表示:对于每个需要编码的字符,先将其转换为 ASCII 码值,然后再转换为十六进制表示。例如,字符 "A" 的 ASCII 码值为 65,十六进制表示为 "41"。
- 在十六进制表示前加上 "%" 符号:将转换后的十六进制表示前面加上 "%" 符号,作为编码后的表示形式。例如,字符 "A" 编码后为 "%41"。
3.2 方法(method):
3.2.1 GET 方法:
GET 是最常用的 HTTP 方法。常用于获取服务器上的某个资源。
GET 请求的特点:
-
首行的第一部分为 GET。
-
搭配 URL 的 query string 使用。
-
body 一般为空。
3.2.2 POST 方法:
POST 方法也是一种常见的方法,多用于提交用户输入的数据给服务器(例如登陆页面)。
POST 请求的特点:
-
首行的第一部分为 POST。
-
URL 中一般不搭配 query string 使用。
-
body 部分一般不为空。
3.2.3 经典面试题:谈谈 GET 和 POST 的区别:
GET 和 POST,从本质上讲,没有什么区别,GET 应用场景,使用 POST 也可以。同理,POST 的应用场景,GET 也可以。
从习惯的角度来说,还是有区别:
-
语义不同:GET 一般用于获取数据,POST 一般用于提交数据。
-
GET 的 body 一般为空,需要传递的数据通过 query string 传递,POST 的 query string 一般为空,需要传递的数据通过 body 传递。
-
GET 请求一般是幂等的,POST 请求一般是不幂等的 (如果多次请求得到的结果一样,就视为请求是幂等的)。
-
GET 可以被缓存,POST 不能被缓存 (这一点也是承接幂等性)。
补充说明:
-
关于语义:GET 完全可以用于提交数据,POST 也完全可以用于获取数据。
-
关于幂等性:标准建议 GET 实现为幂等的。实际开发中 GET 也不必完全遵守这个规则(主流网站都有"猜你喜欢"功能。会根据用户的历史行为实时更新现有的结果)。
-
关于安全性:有些资料上说"POST 比 GET 更安全"。这样的说法是不科学的。是否安全取决于前端在传输密码等敏感信息时是否进行加密,和 GET POST 无关。
-
关于传输数据量:有的资料上说 "GET 传输的数据量小,POST 传输数据量大"。这个也是不科学的,标准没有规定 GET 的 URL 的长度,也没有规定 POST 的 body 的长度。传输数据量多少,完全取决于不同浏览器和不同服务器之间的实现区别。
-
关于传输数据类型:有的资料上说"GET 只能传输文本数据,POST 可以传输二进制数据"。这个也是不科学的。GET 的 query string虽然无法直接传输二进制数据,但是可以针对二进制数据进行 url encode。
3.2.4 其他方法:
- PUT 与 POST 相似,只是具有幂等特性,一般用于更新。
- DELETE 删除服务器指定资源。
- OPTIONS 返回服务器所支持的请求方法。
- HEAD 类似于GET,只不过响应体不返回,只返回响应头。
- TRACE 回显服务器端收到的请求,测试的时候会用到这个。
- CONNECT 预留,暂无使用。
3.3 HTTP 请求的构造方式:
GET:
- 在浏览器地址栏直接输入 url,就是 GET 请求(点击收藏夹也是 GET 请求)。
- 网页 html 中的特殊标签(img/a/link...),这些带有一个 url 作为属性。页面被浏览器加载之后,解析到这些标签, 就会根据 url 构造出新的 http 请求。
- 表单。html 中的 form 标签。
- 通过 js 构造。
POST:
- 表单。
- js。
剩下的其它方法只有通过 js 来构造了。
3.4 请求报头(header):
header 的整体的格式也是键值对结构。每个键值对占一行,键和值之间使用冒号分割。报头的种类有很多,此处仅介绍几个常见的。
- Host:
表示服务器主机的地址和端口。
- Content-Length:
表示 body 中的数据长度。
- Content-Type:
表示请求 body 中的数据格式。
- User-Agent(简称 UA):
表示浏览器 / 操作系统的属性。形如:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...
其中 Windows NT 10.0; Win64; x64 表示操作系统信息,AppleWebKit/537.36...表示浏览器信息。
- Referer:
表示这个页面是从哪个页面跳转过来的。如果直接在浏览器中输入 URL,或者直接通过收藏夹访问页面时是没有 Referer 的。
- Cookie:
存储了一个字符串,这个数据可能是客户端(网页)自行通过 JS 写入,也可能来自于服务器(服务器在 HTTP 响应的 header 中通过 Set-Cookie 字段给浏览器返回数据)。往往可以通过这个字段实现 "身份标识" 的功能。每个不同的域名下都可以有不同的 Cookie,不同网站之间的 Cookie 并不冲突。
在这里就可以观察 Cookie,并选择是否删除。
整个登入过程可以理解为如下图,有了''令牌'',后续打开该网页的其它页面就不需要再次登入了:
3.5 请求正文(body):
正文中的内容格式和 header 中的 Content-Type 密切相关。
常见的有:
- application/x-www-form-urlencoded。
- multipart/form-data。
- application/json。
四、HTTP 响应详解:
由于响应报头的基本格式和请求报头的格式基本一致,类似于 Content-Type ,Content-Length 等属性的含义也和请求中的含义一致。所以这里不再赘述。
关于 HTTP 的响应,这里主要讲解其状态码及其描述。
状态码表示访问一个页面的结果。(是访问成功,还是失败,还是其他的一些情况...)。
以下为常见的状态码。
- 200 OK:
这是一个最常见的状态码,表示访问成功。
- 404 Not Found:
没有找到资源。
如果浏览器输入一个 URL,目的就是为了访问对方服务器上的一个资源。如果这个 URL 标识的资源不存在,那么就会出现 404。
- 403 Forbidden:
表示访问被拒绝。
有的页面通常需要用户具有一定的权限才能访问(登陆后才能访问)。
- 405 Method Not Allowed:
方法不支持。例如有的网页只支持 GET,那么发送除了 GET 之外的请求,就会出现 405。
- 500 Internal Server Error:
服务器出现内部错误。一般是服务器的代码执行过程中遇到了一些特殊情况(服务器异常崩溃)会产生这个状态码。
- 504 Gateway Timeout:
当服务器负载比较大时候,服务器处理单条请求的时候消耗的时间就会很长,就可能会导致出现超时的情况。这种情况在双十一等"秒杀"场景中容易出现。平时不太容易见到。
- 302 Move temporarily:
临时重定向。
理解重定向
就相当于手机号码中的 "呼叫转移" 功能。比如我本来的手机号是 186-1234-5678,后来换了个新号码135-1234-5678,那么不需要让我的朋友知道新号码,只要我去办理一个呼叫转移业务,其他人拨打 186-1234-5678,就会自动转移到 135-1234-5678 上。
在登陆页面中经常会见到 302。用于实现登陆成功后自动跳转到主页。响应报文的 header 部分会包含一个 Location 字段,表示要跳转到哪个页面。
- 301 Moved Permanently:
永久重定向。
当浏览器收到这种响应时,后续的请求都会被自动改成新的地址。301 也是通过 Location 字段来表示要重定向到的新地址。
除了第一次请求需要两次 HTTP 访问,后续只需要一次(直接请求目标)。
- 状态码小结:
五、HTTPS(重点)
HTTPS 也是一个应用层协议。是在 HTTP 协议的基础上引入了一个加密层。
HTTPS = HTTP + SSL。
HTTP 协议内容都是按照文本的方式明文传输的。这就导致在传输过程中出现一些被篡改的情况。
臭名昭著的"运营商劫持",下载一个 天天动听,未被劫持的效果,点击下载按钮,就会弹出天天动听的下载链接。
已被劫持的效果,点击下载按钮,就会弹出 QQ 浏览器的下载链接。
由于我们通过网络传输的任何的数据包都会经过运营商的网络设备(路由器,交换机等)。那么运营商的网络设备就可以解析出你传输的数据内容,并进行篡改。不止运营商可以劫持,其他的黑客也可以用类似的手段进行劫持,来窃取用户隐私信息,或者篡改内容。试想一下,如果黑客在用户登陆支付宝的时候获取到用户账户余额,甚至获取到用户的支付密码,那么后果是非常严重的。
5.1 核心概念:
明文:要传输的真正数据。
密文:加密之后得到的数据。
密钥:用来加密和解密的重要道具。
加密:明文通过密钥变成密文。
解密:密文通过密钥变成明文。
5.2 HTTPS 的工作流程:
既然要保证数据安全,就需要进行 "加密"。网络传输中不再直接传输明文了,而是加密之后的 "密文"。
加密的方式有很多,但是整体可以分成两大类:对称加密 和 非对称加密。
5.2.1 对称加密:
对称加密其实就是通过同一个"密钥",把明文加密成密文,并且也能把密文解密成明文。
引入对称加密之后,即使数据被截获,由于黑客不知道密钥是啥,因此就无法进行解密,也就不知道请求的真实内容是啥了。
但事情没这么简单,服务器同一时刻其实是给很多客户端提供服务的,这么多客户端,每个人用的秘钥都必须是不同的(如果是相同的,密钥就太容易扩散了,黑客就也能拿到了)。因此服务器就需要维护每个客户端和每个密钥之间的关联关系。
比较理想的做法,就是能在客户端和服务器建立连接的时候,双方协商确定这次的密钥是啥。
但是如果直接把密钥明文传输,那么黑客也就能获得密钥了,此时后续的加密操作就形同虚设了。
因此密钥的传输也必须加密传输。
但是要想对密钥进行对称加密,就仍然需要先协商确定一个"密钥的密钥"。这就走进死胡同了,此时密钥的传输再用对称加密就行不通了。
此时,就需要引入非对称加密。
5.2.2 非对称加密:
非对称加密要用到两个密钥,一个叫做 "公钥",一个叫做"私钥",公钥和私钥是配对的。
非对称加密最大的缺点就是运算速度非常慢,比对称加密要慢很多。
通过公钥对明文加密,变成密文,通过私钥对密文解密,变成明文。
也可以反着用。
通过私钥对明文加密,变成密文,通过公钥对密文解密,变成明文。
非对称加密的数学原理比较复杂,涉及到一些数论相关的知识。我们这里感性的理解一下:A 要给 B 一些重要的文件,但是 B 可能不在。于是 A 和 B 提前做出约定,B 说,我桌子上有个盒子,然后我给你一把锁,你把文件放盒子里用锁锁上,然后我回头拿着钥匙来开锁取文件。在这个场景中,这把锁就相当于公钥,钥匙就是私钥。公钥给谁都行(不怕泄露),但是私钥只有 B 自己持有。持有私钥的人才能解密。
整个流程如下:
- 客户端在本地生成对称密钥,通过公钥加密,发送给服务器。
- 由于中间的网络设备没有私钥,即使截获了数据,也无法还原出内部的原文,也就无法获取到对称密钥。
- 服务器通过私钥解密,还原出客户端发送的对称密钥,并且使用这个对称密钥加密给客户端返回的响应数据。
- 后续客户端和服务器的通信都只用对称加密即可,由于该密钥只有客户端和服务器两个主机知道,其他主机 / 设备不知道密钥,即使截获数据也没有意义。
注意:由于对称加密的效率比非对称加密高很多,因此只是在开始阶段协商密钥的时候使用非对称加密,后续的传输仍然使用对称加密。
那么接下来问题又来了:
客户端如何获取到公钥?
客户端如何确定这个公钥不是黑客伪造的?
5.2.3 中间人攻击:
黑客可以使用中间人攻击,获取到对称密钥。
具体步骤如下:
- 服务器具有非对称加密算法的公钥m1,私钥m2。
- 中间人具有非对称加密算法的公钥s1,私钥s2。
- 客户端向服务器发起请求,服务器明文传送公钥 m1 给客户端。
- 中间人劫持数据报文,提取公钥 m1 并保存好,然后将被劫持报文中的公钥 m1 替换成为自己的公钥 s1, 并将伪造报文发给客户端。
- 客户端收到报文,提取公钥 s1(客户端当然不知道公钥被更换过了),客户端形成对称秘钥X,用公钥 s1 加密 X,形成报文发送给服务器。
- 中间人劫持后,直接用自己的私钥 s2 进行解密,得到通信秘钥 X,再用曾经保存的服务端公钥 m1 加密后,将报文推送给服务器
- 服务器拿到报文,用自己的私钥 m2 解密,得到通信秘钥 X。
- 双方开始采用 X 进行对称加密,进行通信。但是一切都在中间人的掌握中(客户端和服务器都没有察觉到),劫持数据,进行窃听甚至修改,都是可以的。
解决方法:使用证书。
5.2.4 引入证书:
服务端在使用 HTTPS 前,需要向 CA 机构申领一份数字证书,数字证书里含有证书申请者信息、公钥信息等。服务器把证书传输给浏览器,浏览器从证书里获取公钥就行了,证书就如身份证,证明服务端公钥的权威性。
基本说明:https://baike.baidu.com/item/CA认证/6471579?fr=aladdin
这个证书可以理解成是一个结构化的字符串,里面包含了以下信息:
-
证书发布机构
-
证书有效期
-
公钥
-
证书所有者
-
签名
-
...
数据签名:
证书机构自己本身有一对公钥和私钥,其中公钥是直接存储在客户端的操作系统(电脑一买来就有的)上面的(证书机构全世界都不多,计算机能轻松存储)。
数据签名是把证书上面的所有数据(除了签名),通过某种 hash 计算成校验和,再通过证书机构的私钥进行加密。这样一份数字证书就可以颁发给服务端了。
客户端接收到证书后,将除了签名外的数据进行校验和计算得出校验和1,另将签名通过证书的公钥进行解密,得到校验和2,通过比较校验和1和校验和2即可知道证书的真假。
通过证书解决中间人攻击:
在客户端和服务器刚一建立连接的时候,服务器给客户端返回一个证书。这个证书包含了刚才的公钥,也包含了网站的身份信息。
中间人能不能篡改证书?
答:不能。如果中间人篡改了证书的明文,由于他没有 CA 机构(证书机构)的私钥,所以无法 hash 之后用私钥加密形成签名,那么也就没法办法对篡改后的证书形成匹配的签名。如果强行篡改,客户端收到该证书后会发现明文 hash 和签名解密后的值不一致,则说明证书已被篡改,证书不可信,从而终止向服务器传输信息,防止信息泄露给中间人。
中间人能不能掉包整个证书?
答:不能。因为中间人没有 CA 私钥,所以中间人只能向 CA 申请真证书,然后用自己申请的证书进行掉包。这个确实能做到证书的整体掉包,但是别忘记,证书明文中包含了域名等服务端认证信息,如果整体掉包,客户端依旧能够识别出来。
永远记住:中间人没有 CA 私钥,所以对任何证书都无法进行合法修改,包括自己的。
5.2.5 完整流程:
左侧都是客户端做的事情,右侧都是服务器做的事情。
总结:
HTTPS 工作过程中涉及到的密钥有三组。
- 第一组(非对称加密):用于校验证书是否被篡改。客户端持有公钥(操作系统包含了可信任的 CA 认证机构有哪些,同时持有对应的公钥)。客户端通过这个公钥解密获取到证书的签名,从而校验证书内容是否是篡改过。
- 第二组(非对称加密):用于协商生成对称加密的密钥。服务器生成私钥-公钥对,然后通过证书把公钥传递给客户端,然后客户端用这个公钥给生成的对称加密的密钥加密,传输给服务器,服务器通过私钥解密获取到对称加密密钥。
- 第三组(对称加密):客户端和服务器后续传输的数据都通过这个对称密钥加密解密。
其实一切的关键都是围绕这个对称加密的密钥,其他的机制都是辅助这个密钥工作的。
第一组非对称加密的密钥是为了让客户端拿到第二组非对称加密的公钥。
第二组非对称加密的密钥是为了让客户端把这个对称密钥传给服务器。
总结:
HTTPS 工作过程中涉及到的密钥有三组。
- 第一组(非对称加密):用于校验证书是否被篡改。客户端持有公钥(操作系统包含了可信任的 CA 认证机构有哪些,同时持有对应的公钥)。客户端通过这个公钥解密获取到证书的签名,从而校验证书内容是否是篡改过。
- 第二组(非对称加密):用于协商生成对称加密的密钥。服务器生成私钥-公钥对,然后通过证书把公钥传递给客户端,然后客户端用这个公钥给生成的对称加密的密钥加密,传输给服务器,服务器通过私钥解密获取到对称加密密钥。
- 第三组(对称加密):客户端和服务器后续传输的数据都通过这个对称密钥加密解密。
其实一切的关键都是围绕这个对称加密的密钥,其他的机制都是辅助这个密钥工作的。
第一组非对称加密的密钥是为了让客户端拿到第二组非对称加密的公钥。
第二组非对称加密的密钥是为了让客户端把这个对称密钥传给服务器。
结语:
其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话,还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。