程序调试 (七) —— 基于iOS的Charles Proxy教程(一)

版本记录

版本号 时间
V1.0 2021.07.13 星期二

前言

程序总会有bug,如果有好的调试技巧和方法,那么就是事半功倍,这个专题专门和大家分享下和调试相关的技巧。希望可以帮助到大家。感兴趣的可以看下面几篇文章。
1. 程序调试 (一) —— App Crash的调试和解决示例(一)
2. 程序调试 (二) —— Xcode Simulator的高级功能(一)
3. 程序调试 (三) —— Xcode Simulator的高级功能(二)
4. 程序调试 (四) —— Xcode内存管理(一)
5. 程序调试 (五) —— 使用Build Configurations 和 .xcconfig 构建你的App(一)
6. 程序调试 (六) —— 使用Build Configurations 和 .xcconfig 构建你的App(二)

开始

首先看下主要内容:

了解如何使用适用于 iOSmacOSCharles 检查您自己的应用程序和第三方应用程序的加密和未加密网络流量。内容来自翻译

接着看下写作环境:

Other, macOS 11, Other

下面就是正文了。

让我们面对现实吧 —— 我们都写过不能正常工作的代码,调试可能很困难。 当您通过网络与其他系统通信时,这会更加困难。

幸运的是,Charles Proxy 可以使网络调试更加容易。

Charles Proxy 位于您的应用程序和互联网之间。 您将模拟器或 iOS 设备配置为通过 Charles Proxy 传递所有网络请求和响应,因此您将能够检查甚至更改中游数据以测试您的应用程序的响应方式。

在本教程中,您将获得相关的实践经验。 在此过程中,您将了解到:

  • 代理及其在 macOSiOS 上的工作方式。
  • 准备您的系统以使用 Charles
  • 窥探应用程序。
  • 模拟和故障排除慢速网络。
  • 对您自己的应用程序进行故障排除。

准备好了吗?

打开入门项目。然后,下载latest version of Charles Proxy(在撰写本文时为 v4.6.1)。 双击 DMG 文件并将 Charles 图标拖到您的应用程序文件夹中进行安装。

Charles Proxy 不是免费的,但有 30 天的免费试用期。 Charles 在试用模式下只会运行 30 分钟,因此您可能需要在整个教程中重新启动它。

注意:Charles 是一个基于 Java 的应用程序,支持 macOS、WindowsLinux。 这个 Charles Proxy 教程是针对 macOS 的,有些东西在其他平台上可能会有所不同。

启动Charles。 它应该请求允许自动配置您的网络设置。 如果没有,请按 Command-Shift-P 手动让 Charles 请求此权限。

如果出现提示,请单击Grant Privileges并输入您的密码。 Charles一启动就开始记录网络事件,因此您应该已经看到事件弹出到左窗格中。

注意:如果您没有看到任何事件,您可能没有授予权限或可能已经设置了另一个代理。 VPN 也会出现问题。 有关故障排除帮助,请参阅 Charles’ FAQ page


Exploring the App

用户界面易于理解,无需太多经验。

许多好东西都隐藏在按钮和菜单后面,工具栏有一些你应该知道的项目:

  • “Broom”清除当前会话和所有记录的活动。
  • “Record/Pause”Charles 记录事件时为红色,停止时为灰色。
  • “Lock”启动/停止 SSL 代理。
  • “Tortoise”“Checkmark”中间的按钮提供对常见操作的访问,包括节流、断点和请求创建。
  • 最后两个按钮提供对常用工具和设置的访问。

现在,单击红色的Record/Pause按钮停止录制。

工具栏下方是 StructureSequence 之间的切换。选择 Sequence 后,顶部窗格包含所有记录的网络请求的摘要,而主窗格包含有关所选请求的详细信息。

选择Structure后,顶部窗格将替换为相同数据的左侧窗格,按站点地址分组。您仍然可以通过展开每个单独的站点来查看单独的请求。

选择Sequence以查看按时间排序的连续列表中的所有事件。在调试自己的应用程序时,您可能会在此屏幕上花费大部分时间。

默认情况下,Charles 将请求和响应合并到一个屏幕中。但是,您可以将它们拆分为单独的事件。

选择 Charles ▸ Preferences并选择Viewers。取消选中Combine request and response,然后按OK。您需要重新启动 Charles 以使更改生效。

尝试浏览用户界面并查看事件。您会注意到一件奇怪的事情:您看不到 HTTPS 事件的大部分详细信息!

SSL/TLS 加密敏感的请求和响应信息。您可能认为这让 Charles 对所有 HTTPS 事件毫无意义,但 Charles 有一种偷偷摸摸的方法来绕过加密。


More About Proxies

您可能想知道:“How does Charles do its magic?”

Charles 是一个代理服务器,这意味着它位于您的应用程序和计算机的网络连接之间。当 Charles 配置您的网络设置时,它会更改您的网络配置以通过它路由所有流量。这允许 Charles 检查进出您的计算机的所有网络事件。

代理服务器处于强大的地位,但这也意味着存在滥用的可能性。这就是 SSL 如此重要的原因:数据加密可防止代理服务器和其他中间件窃听敏感信息。

但是,在这种情况下,您希望 Charles 窥探您的 SSL 消息以让您调试它们。

SSL/TLS 使用由称为证书颁发者的受信任第三方生成的证书对消息进行加密。

Charles 还可以生成自己的自签名证书,您可以将其安装在 MaciOS 设备上以进行 SSL/TLS 加密。由于此证书不是由受信任的证书颁发者颁发的,因此您需要告诉您的设备明确信任它。一旦安装并受信任,Charles 将能够解密 SSL事件!

当黑客使用中间件窥探网络通信时,它被称为“中间人”攻击。通常,您不想信任任何随机证书,否则可能会危及您的网络安全!

在某些情况下,Charles的鬼鬼祟祟的中间人策略行不通。例如,某些应用程序使用 SSL pinning来提高安全性。 SSL pinning意味着应用程序拥有 Web 服务器公钥的副本,并在通信之前使用它来验证网络连接。由于 Charles 的密钥不匹配,应用程序将拒绝通信。

除了记录事件之外,您还可以使用 Charles 动态修改数据,记录下来供以后查看,甚至模拟不良网络连接。

Charles很强大!


Charles Proxy and Your iOS Device

多年来,通过 Charles Proxy 代理来自物理 iOS 设备的流量的唯一方法是告诉您的 iOS 设备将所有网络流量发送到您的计算机。 这仍然是您将介绍的常见做法,但首先,您将查看Charles Proxy for iOS

在您的 iOS 设备上打开 App Store 并搜索 Charles Proxy。 不幸的是,没有免费版本的 iOS 应用程序,因此如果您想遵循本节内容,则必须购买它。

注意:不想购买 iOS 应用程序? 不用担心! 您可以跳过此部分并继续下面的内容,在那里您将了解如何将您的应用程序的网络流量路由到您的 Mac。

在您的设备上安装该应用程序并打开它。 初始屏幕显示代理处于非活动状态。 任何正在运行的会话都有一个开关和一些关键统计数据的概述。 打开Status Switch

一旦被要求获得安装 VPN 配置的权限,点击Allow

状态将更改为Active

点击Current Session详细指示器箭头,应用程序将导航到与桌面应用程序顶部窗格相当的视图。 如果您没有看到任何请求,请切换到 Safari 并加载网页。

点击任何单个请求,您将深入查看该请求的详细视图。 与桌面应用程序一样,任何 SLS/TLS 加密流量仍会被混淆。

是时候解决这个问题了。

1. Installing Charles’ Certificate

仍然在 Charles Proxy 应用程序中,通过点击屏幕左上角的后退箭头两次,导航回初始屏幕。 在代理仍处于活动状态的情况下,点击屏幕左上角的设置齿轮。 选择 SSL Proxying

现在,在此屏幕的底部,您将找到有关安装和信任Charles Proxy CA Certificate的详细说明。 首先,使用应用程序中的按钮安装证书。 您的设备将应用切换到 Safari 并请求安装配置文件的许可。

注意:如果您将 Apple Watch 与您的设备配对,它会询问是在设备上还是在手表上安装配置文件。 选择 iPhone

安装配置文件后,打开Settings应用。 您将看到一个新的Profile Downloaded选项。 点击它并选择右上角的Install选项。

如果您有设备密码,系统会提示您输入设备密码,然后是确认屏幕,警告您此证书未经验证。 再次点击Install。 最后,屏幕底部将出现一个操作屏幕,并带有最终确认。 Apple 真的很想确保您要安装它。

同样,不要只安装任何随机证书,否则您可能会影响您的网络安全!

在本 Charles Proxy 教程结束时,您还将删除此证书。

2. Trusting Charles’ Certificate

您将看到一个确认配置文件已安装的屏幕。 接下来,您需要信任该证书。 仍然在Settings应用程序中,导航到General ▸ About ▸ Certificate Trust Settings。 找到 Charles Proxy 证书并将开关打开。 将出现一个警告对话框。 选择Continue

切换回 Charles Proxy 应用程序,证书状态现在将显示 Trusted。 将屏幕顶部的Enabled开关切换为开启。

Charles 中,导航回主Settings页面并保存您的更改。 打开当前会话并使用屏幕左下角的扫帚图标清除所有流量。 导航到 Safari并重新加载网页。 然后,导航回 Charles Proxy。 点击其中一个请求,然后点击Enable SSL Proxying

返回当前会话并再次清除会话。 重新打开 Safari 并最后一次重新加载页面。 现在,如果您导航回 Charles Proxy,您启用 SSL 代理的 URL 将有一个蓝色网络图标而不是锁图标。

首先,点击 URL 以查看每个请求的完整详细信息。

接下来,点击请求以获取更多详细信息。

然后,点击View body以查看完整的响应正文。

这个例子使用了 Safari,但是当你想要调试你的应用程序的网络时,在你的设备上打开任何应用程序(包括你自己的应用程序)时,以下过程将起作用。

接下来,点击返回请求页面并禁用 SSL 代理。 点击返回初始视图并将 Charles Proxy 状态设置为 Inactive 以停止代理流量。


Proxying iOS Traffic Using Charles Proxy for macOS

如果您想在模拟器上查看流量,或者您没有 Charles Proxy iOS 应用程序,会发生什么情况? 没问题! 将 Charles 设置为代理来自网络上任何计算机或设备(包括 iOS 设备)的流量非常简单。

1. Setting Up Your Device

Mac 上打开 Charles Proxy并通过单击Proxy (drop-down menu) ▸ macOS Proxy取消选中它来关闭 macOS 代理。 这样,您将只能看到来自 iOS 设备的流量。

接下来,单击Proxy ▸ Proxy Settings,单击Proxies选项卡并记下端口号,默认情况下应为 8888

然后,单击Help ▸ Local IP Address并记下您计算机的 IP 地址。 如果有多个 IP 地址,请选择 en0

现在,拿起你的 iOS 设备。 打开Settings,点击 Wi-Fi 并确认您已连接到与计算机相同的网络。 然后,点击 Wi-Fi网络旁边的 ⓘ 按钮。 向下滚动到 HTTP Proxy 部分,选择Configure Proxy,然后点击Manual

Server输入 Mac 的 IP 地址,为Port输入 Charles HTTP 代理端口号。 点击Save

回到 Charles macOS 应用程序。 如果您尚未记录流量,请点击Record/Pause按钮。

您应该会在 Mac 上收到来自 Charles 的弹出警告,要求允许您的 iOS 设备连接。 单击Allow。 如果您没有立即看到这一点,那没关系。 它可能需要一两分钟才能显示出来。

现在您应该开始在 Charles 中查看设备的活动了! 但请注意,您目前还无法查看 SSL 流量。 与 iOS 应用程序一样,您需要安装 Charles 的证书。

2. Installing a Certificate on Your Device

注意:这些说明也适用于在模拟器上安装证书,但有两个不同之处。 首先,您必须在 Charles 的代理菜单中重新启用macOS Proxy 。 其次,您将在Settings ▸ General页面而不是主设置页面上找到下载的配置文件。

仍然在您的 iOS 设备上,打开 Safari 并导航到 http://www.charlesproxy.com/getssl。 在弹出窗口中,点击Allow

注意:同样,如果您将 Apple Watch 与设备配对,iOS 将提示您在设备和手表之间进行选择以安装配置文件。 选择 iPhone

在现在应该是您熟悉的过程中,切换到设置并安装配置文件。 点击安装,输入您的密码(如果已设置)并在警告出现后再次点击安装。 然后,再次点击安装。 最后,点击完成。

和以前一样,打开Settings应用程序并导航到General ▸ About ▸ Certificate Trust Settings。 信任您刚刚安装的证书。

接下来,在 macOS Charles 应用程序中,选择Proxy ▸ SSL Proxying Settings。 确保选中Enable SSL Proxying,并为要检查的流量添加一个值。

注意:如果您不知道要在此处放置什么值,您可以通过辅助(右键)单击在应用程序中选择一个请求,然后从那里选择Enable SSL Proxying

您现在将看到该连接的完整请求和响应正文。


Snooping on Someone Else’s App

如果您像大多数开发人员一样,就会很好奇事情是如何运作的。 Charles 通过为您提供检查任何应用程序通信的工具来实现这种好奇心。

前往您设备上的 App Store,找到并下载Weather Underground。大多数国家/地区都可以使用此免费应用程序。如果它不可用,或者您想尝试其他东西,请随意使用其他应用程序。

当您下载 Weather Underground 时,您会注意到 Charles 的一系列活动。 App Store通信很详细!

安装应用程序后,启动应用程序并单击 Charles 中的扫帚图标以清除最近的活动。

点击Search,输入邮政编码 90210 并选择 Beverly Hills 作为您的位置。然后,点击View。如果您要使用当前位置,则应用程序获取的 URL 可能会在您的位置发生变化时发生变化,这可能会使此Charles Proxy教程中的某些后续步骤更难以遵循。

Structure选项卡中列出了大量站点!这是来自您的 iOS 设备的所有活动的列表。

切换到 Sequence 选项卡并在过滤器框中输入weather以仅显示天气网络数据。

您现在会看到一些对 api.weather.com 的请求。 单击一个。

Overview部分显示了一些请求详细信息,但并不多,因为您尚未为 api.weather.com 启用 SSL 代理。

单击Proxy ▸ SSL Proxying SettingsAdd。 为 Host 输入 api.weather.com,将 Port 留空,然后单击 OK 关闭窗口。

回到 Weather Underground 应用程序,下拉刷新并重新获取数据。 如果应用程序没有刷新,您可能需要从多任务视图中将其终止,然后重试。

Charles 显示未加密的请求!

查找 URL 包含 /v3/wx/observations/current 的请求。 这包含用于填充天气屏幕的有效负载。

1. Modifying the Response

是时候享受一些乐趣并在应用程序获取数据之前更改数据了。 你能让应用程序崩溃或表现得有趣吗?

Charles 中,右键单击 Sequence 列表中的请求,然后单击弹出列表中的 Breakpoints。 然后,单击Proxy ▸ Breakpoint Settings,双击您添加的断点并确保为空Query

这确保您拦截任何包含此路径的请求,而不管查询参数如何,Charles 将暂停并让您编辑请求和响应。

再次在您的设备上,下拉以刷新应用程序。

应弹出一个名为 Breakpoints 的新选项卡,其中包含传出请求。 单击Execute而不修改任何内容。 片刻之后,Breakpoints 选项卡应该再次与响应一起出现。

单击顶部附近的Edit Response选项卡。 在底部,选择JSON text。 向下滚动并找到temperature并将其值更改为不切实际的值,例如 98000。单击Execute

注意:如果您编辑请求或响应的时间过长,应用程序可能会静默超时并且从不显示任何内容。 如果编辑的温度没有出现,请再快一点。

98000°F 非常热!该应用程序似乎不会为超过五位数的温度调整字体大小。这是一个明确的一星评级。

回到 Charles,通过转到Proxy ▸ Breakpoint Settings来删除您设置的断点。

取消选中 api.weather.com 的条目以暂时禁用它,或突出显示该行并单击Remove以将其删除。下拉刷新,温度应该会恢复正常。

2. Simulating Slow Networking

现在,您将模拟慢速网络。单击 Tortoise 图标开始节流。接下来,单击Proxy ▸ Throttle Settings以查看可用选项。默认值为 56 kbps,这非常慢。您还可以在此处调整设置以模拟数据丢失、可靠性问题和高延迟。

尝试刷新应用程序、缩放地图和/或搜索其他位置。痛苦的缓慢,对吧?

在较差的网络条件下测试您自己的应用程序是个好主意。想象一下您的用户在地铁上或进入电梯。您不希望您的应用程序丢失数据,或者更糟的是,在这些情况下崩溃。

AppleNetwork Link Conditioner 提供了类似的节流功能,而 Charles 允许对网络设置进行更精细的控制。例如,您可以仅对特定 URL 应用限制以模拟您的服务器响应缓慢而不是整个连接。

完成后请记住turn off throttling。没有什么比花一个小时调试才发现您从未关闭节流更糟糕的了!


Troubleshooting Your Own Apps

Charles Proxy 特别适合调试和测试您自己的应用程序。 例如,您可以检查服务器响应以确保正确定义了 JSON 键,并且为所有字段返回了预期的数据类型。 您甚至可以使用节流来模拟较差的网络并验证应用程序的超时和错误处理逻辑。

在构建和运行之前,将以下两个host添加到 CharlesSSL 代理设置中,就像您在上面学到的那样:

  • www.countryflags.io
  • restcountries.eu

然后,在您的设备或模拟器上构建并运行示例应用程序。

此应用程序显示所有国家/地区的列表,其中包含每个国家/地区的一些简短信息。 但是图标怎么了? 对来自服务的数据进行解码时似乎出错。 您会看到 Charles 能否帮助您找到问题的根源。

切换到 Charles Proxy(在 Mac 上)并在 Sequence 选项卡中,将过滤器更改为 countryflags.io。 您会看到所有请求都失败并显示 404 错误,因为没有找到任何国家/地区的图像:

正如您在 Charles 中看到的那样,您使用一个三字母代码来获取国旗的图像。 但是根据countryflags.io,您需要使用两个字母的国家/地区代码才能使其正常工作!

现在,将 Sequence 选项卡中的过滤器更改为 restcountries.eu 以监视您从该服务接收到的数据,以查看是否可以获得代码:

[{
    "name": "Afghanistan",
    "topLevelDomain": [".af"],
    "alpha2Code": "AF",
    "alpha3Code": "AFG",
    "callingCodes": ["93"],
    "capital": "Kabul",
    "altSpellings": ["AF", "Afġānistān"],
    "region": "Asia",
    "subregion": "Southern Asia",
    "population": 27657145,
    "latlng": [33.0, 65.0],
    "demonym": "Afghan",
    "area": 652230.0,
    "gini": 27.8,
    "timezones": ["UTC+04:30"],
    "borders": ["IRN", "PAK", "TKM", "UZB", "TJK", "CHN"],
    "nativeName": "افغانستان",
    "numericCode": "004",
    "currencies": [{
        "code": "AFN",
        "name": "Afghan afghani",
        "symbol": "؋"
    }],
...

响应包含两个国家代码,称为 alpha2Codealpha3Code。 在 Xcode 中,打开 Country.swift 并仔细查看 CodingKeys。 确实,代码错了!

替换以下内容:

case code = "alpha3Code"

case code = "alpha2Code"

再次构建并运行应用程序:

成功!这是一个简单但很好的演示,展示了在 Charles Proxy 中查看网络流量如何帮助您发现网络代码中的bug


Removing Charles’ Certificate

过去,Charles 在每个使用它的设备上创建了一个共享证书。幸运的是,Charles 现在创建了唯一的证书。这显着降低了基于此证书的中间人攻击的机会,但在技术上仍然是可能的。因此,您应该始终记住在完成后删除 Charles 的证书。

首先,从 macOS 中删除证书。打开位于Applications ▸ Utilities中的Keychain Access。在搜索框中,键入 Charles Proxydelete搜索找到的所有证书。很可能只有一个要删除。完成后关闭应用程序。

接下来,从您的 iOS 设备中删除证书。打开Settings应用程序并导航到General ▸ Profiles文件。在 Configuration Profiles 下,您将看到 Charles Proxy 的一个或多个条目。点击一个,然后点击Remove Profile。输入您的密码(如果需要)并确认删除。对每个 Charles 代理证书重复此操作。

Profiles & Device Management在 iOS 模拟器中不可用。要删除 Charles 代理证书,请通过单击Hardware菜单然后Erase All Content and Settings…

您还应该通过打开设置并访问 Wi-Fi,点击 ⓘ 按钮,向下滚动到HTTP Proxy部分,选择Configure Proxy,然后点击Off来关闭 iPhone 上 Wi-Fi连接的代理。

现在你应该知道如何使用 Charles Proxy 了。 它具有本教程中未涵盖的更多功能,您可以利用今天学到的知识做更多事情。 查看Charles’ website以获取更多文档。 您使用 Charles 的次数越多,您会发现的功能就越多。

您还可以在read more about SSL/TLS on Wikipedia上阅读有关 SSL/TLS 的更多信息。

后记

本篇主要讲述了iOSCharles Proxy教程,感兴趣的给个赞或者关注~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容