NSURLProtocol -- DNS劫持和Web资源本地化

什么是DNS劫持

DNS劫持就是通过劫持了DNS服务器,通过某些手段取得某域名的解析记录控制权,进而修改此域名的解析结果,导致对该域名的访问由原IP地址转入到修改后的指定IP,其结果就是对特定的网址不能访问或访问的是假网址,从而实现窃取资料或者破坏原有正常服务的目的。
 常见的DNS劫持现象网络运营商向网页中注入了Javascript代码,甚至直接将我们的网页请求转发到他们自己的广告页面或者通过自己的DNS服务器将用户请求的域名指向到非法地址

如何解决DNS被劫持

全站使用HTTPS协议,或者采用HttpDNS,通过HTTP向自建的DNS服务器或者安全的DNS服务器发送域名解析请求,然后根据解析结果设置客户端的Host指向,从而绕过网络运营商的DNS解析服务。

本文的解决方案

客户端对WebView的html请求进行DNS解析。优先使用阿里、腾讯、114等公共安全的DNS服务器解析客户端的所有指定域名的http请求。相对来讲我们自己的服务域名变化较少,对此我们做了一个白名单,把凡是访问包含我们公司域名的请求都必须通过白名单的解析和DNS验证。从而杜绝被劫持的情况出现,这时候NSURLProtocol就派上用场了。

NSURLProtocol

这是一个抽象类,所以在oc中只能通过继承来重写父类的方法。

然后在AppDelegate的 application:didFinishLaunchingWithOptions: 方法或者程序首次请求网络数据之前去注册这个NSURLProtocol的子类

注册了自定义的urlProtocol子类后,之后每一个http请求都会先经过该类过滤并且通过+canInitWithRequest:这个方法返回一个布尔值告诉系统该请求是否需要处理,返回Yes才能进行后续处理。

+canonicalRequestForRequest:这个父类的抽象方法子类必须实现。

以下是官方对这个方法的解释。当我们想对某个请求添加请求头或者返回新的请求时,可以在这个方法里自定义然后返回,一般情况下直接返回参数里的NSURLRequest实例即可。

It is up to each concrete protocol implementation to define what “canonical” means. A protocol should guarantee that the same input request always yields the same canonical form.

+requestIsCacheEquivalent:toRquest:这个方法能够判断当拦截URL相同时是否使用缓存数据,以下例子是直接返回父类实现。

-startLoading-stopLoading两个方法分别告诉NSURLProtocol实现开始和取消请求的处理。

由于我们在-startLoading中新建了一个NSURLConnection实例,因此要实现NSURLConnectionDelegate的委托方法。

至此,通过NSURLProtocol和QNDnsManager(七牛DNS解析开源库)可以解决DNS劫持问题。但是NSURLProtocol还有更多的用途,以下是本文第二个内容:webView上web请求的资源本地化。

Web资源本地化

这里只举一个简单的示例,同样是在上述NSURLProtocol的子类的-startLoading方法里

继续阅读NSURLProtocol -- DNS劫持和Web资源本地化

Vue axios 发送 FormData 请求

一、简介
axios 默认是 Payload 格式数据请求,但有时候后端接收参数要求必须是 Form Data 格式的,所以我们就得进行转换。

Payload 和 Form Data 的主要设置是根据请求头的 Content-Type 的值来的:

Payload:

Form Data:

上面三种 Content-Type 值介绍

application/json 和 application/x-www-form-urlencoded 都是表单数据发送时的编码类型。

form 的 enctype 属性为编码方式,常用有两种:application/x-www-form-urlencoded 和multipart/form-data,默认为 application/x-www-form-urlencoded。

当 action 为 get 时候,浏览器用 x-www-form-urlencoded 的编码方式把 form 数据转换成一个字串(name1=value1&name2=value2...),然后把这个字串 append 到 url 后面,用 ?分割,加载这个新的 url。

当 action 为 post 时候,浏览器把 form 数据封装到 http body 中,然后发送到 server。

如果没有 type=file 的控件,用默认的 application/x-www-form-urlencoded 就可以了。

但是如果有 type=file 的话,就要用到 multipart/form-data 了。浏览器会把整个表单以控件为单位分割,并为每个部分加上 Content-Disposition(form-data或者file)、Content-Type(默认为text/plain)、name(控件name) 等信息,并加上分割符 (boundary)。

二、发送 formdata 请求(下面有这几种方式格式化参的数据样本,用于参考比较,看需求选择方式)
方式一,自己封装一个格式化函数:

方式二,使用 qs 组件,但是 qs 格式化会过滤空数组数据:

方式三,数组会被转换成字符串(这种不是特殊情况一般不会使用上)

三、上面方式,参数格式化之后:
方式一 格式化出来的数据:

方式二 格式化出来的数据:

方式三 格式化出来的数据:

参考链接


Vue axios 发送 FormData 请求

sqlite3 database is locked解决方案

注意:

什么时候会返回SQLITE_BUSY错误码?
官方文档给出的解释是:

SQLite只支持库级锁,库级锁意味着什么?——意味着同时只能允许一个写操作,也就是说,即事务T1A表插入一条数据,事务T2B表中插入一条数据,这两个操作不能同时进行,即使你的机器有100CPU,也无法同时进行,而只能顺序进行。表级都不能并行,更别说元组级了——这就是库级锁。但是,SQLite尽量延迟申请X锁,直到数据块真正写盘时才申请X锁,这是非常巧妙而有效的。

简单的办法,全局加锁,单线程执行,复杂一点,则可以启用一个专门的数据库线程异步执行操作。

synchronized全局锁和实例锁的区别

实例锁 -- 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。
               实例锁对应的就是synchronized关键字。
全局锁 -- 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。
               全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。

关于“实例锁”和“全局锁”有一个很形象的例子:

假设,Something有两个实例xy。分析下面4组表达式获取的锁的情况。
(01) x.isSyncA()与x.isSyncB() 
(02) x.isSyncA()与y.isSyncA()
(03) x.cSyncA()与y.cSyncB()
(04) x.isSyncA()与Something.cSyncA()

(01) 不能被同时访问。因为isSyncA()isSyncB()都是访问同一个对象(对象x)的同步锁!

(02) 可以同时被访问。因为访问的不是同一个对象的同步锁,x.isSyncA()访问的是x的同步锁,而y.isSyncA()访问的是y的同步锁。

(03) 不能被同时访问。因为cSyncA()cSyncB()都是static类型,x.cSyncA()相当于Something.isSyncA()y.cSyncB()相当于Something.isSyncB(),因此它们共用一个同步锁,不能被同时反问。

(04) 可以被同时访问。因为isSyncA()是实例方法,x.isSyncA()使用的是对象x的锁;而cSyncA()是静态方法,Something.cSyncA()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的。

参考链接


mybaties连接sqlite,并读取blob类型数据时,报错 java.sql.SQLFeatureNotSupportedException

错误,或者如下错误:

场景:
具体需求,要求像springboot连接mysql或者pgsql数据库一样,在application配置文件中配置sqlite数据库信息,并实现对blob类型数据(我们的库中有该类型的数据)的读写,按照平常一样写好controller、service、dao、mapper.xml后,执行查询操作后报错

原因分析:
sqlite的driver中,JDBC4ResultSet没有实现以下接口:

而是直接抛出了异常。
解决方法:
mapper.xml中该字段的设置:

即blob字段加个typeHandler属性。
然后自定义一个BlobTypeHandler处理类:

注意:实体类中blob字段类型是byte[]。

参考链接


mybaties连接sqlite,并读取blob类型数据时,报 java.sql.SQLException: not implemented by SQLite JDBC driver错误

JsonParser解析json字符串双引号问题

** 今天在项目 中使用到了JsonParser解析json字符串为JsonArray或者JsonObject,解析之后使用 如下代码获取到的字符串多了一层 “”(双引号) **

JSON字符串格式如下

** 如果将这样的字符串直接设值在 yaml 文件中,那么设置完成后的 yaml文件如下: **

** 调试查看之后发现 Jarray.get(k) 获取得到的是 JsonPrimitive对象(会在字符串外面再加一对引号) **

** 弄清楚原因之后我们只要在 Jarray.get(k) 之后再对对象取值就可以了 **

参考链接


JsonParser解析json字符串双引号问题

OS X EI Capitan curl: (22) The requested URL returned error: 403 [[[!!! BREAKING CHANGE !!!]]] Support for clients that do not support Server Name Indication is temporarily disabled and will be permanently deprecated soon.

最近在OS X EI Capitan系统上执行升级工作的时候,发生错误,内容如下:

排查了很久,发现是以前为了修复 解决macOS系统curl报告https证书不正确(curl: (60) SSL certificate problem: Invalid certificate chain)问题而配置了curl忽略安全配置信息导致的,如下:

移除这个配置项目即可解决问题。

参考链接


解决Uncaught (in promise) Error: Navigation cancelled from “/...“ to “/...“ with a new navigation.

解决

这个错误是vue-router内部错误,没有进行catch处理,导致的编程式导航跳转问题,往同一地址跳转,或者在跳转的 mounted/activated 等函数中再次向其他地址跳转会报错。

pushreplace都会导致这个情况的发生

解决方法为在路由中进行如下配置:

参考链接


Tab切换以及缓存页面处理的几种方式

前言

相信tab切换对于大家来说都不算陌生,后台管理系统中多会用到。如果不知道的话,可以看一下浏览器上方的标签页切换,大概效果就是这样。

1.如何切换

  1. 使用动态组件,相信大家都能看懂(部分代码省略)

    注:这个多用于单页下的几个子模块使用,一般切换比较多使用下面的路由

  2. 使用路由(这个就是配置路由的问题了,不作赘述)

2.动态生成tab

一般UI框架给我们的tab切换都像是上面的那种,需要自己写入几个tab页之类的配置。但是我们如果想要通过点击左边的目录来生成一个tab页并且可以随时关闭呢(如下图)?

只需要给路由一个点击事件,把你的路由地址保存到一个列表,渲染成另一个平铺的tab目录即可

假设你的布局是这样,左边的目录,上边的tab,有字的是页面

以上代码并非实际代码,只提供一个大概的思路。至于addToTabListdeleteTab怎么做就是数组方法的简单pushsplice操作了。为了效果好看,我们可能还需要一些tabactive样式,这里不作演示。

3.缓存组件

仅仅是做tab切换,远远是不够的,毕竟大家想要tab页就是要来回切换操作,我们需要保存他在不同tab里操作的进度,比如说填写的表单信息,或者已经查询好的数据列表等。
那么我们要怎么缓存组件呢?
只需要用到vue中的keep-alive组件

3.1 keep-alive

  • <keep-alive>是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。
  • <keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
  • <keep-alive> 与 <transition>相似,只是一个抽象组件,它不会在DOM树中渲染(真实或者虚拟都不会),也不在父组件链中存在,比如:你永远在 this.$parent 中找不到 keep-alive 。

注:不能使用keep-alive来缓存固定组件,会无效

3.2 使用

3.2.1 老版本vue 2.1之前的使用

需要在路由信息里面设置router的元信息meta

3.2.2 比较新而且简单的用法

  • 直接缓存所有组件/路由

  • 使用include来处理需要缓存的组件/路由

include有几种用法,可以是数组,字符串用标点隔开,也可以是正则,使用正则的时候需要使用v-bind来绑定。

  • 使用exclude来排除不需要缓存的路由

include正好相反,在exclude里的组件不会被缓存。用法类似,不作赘述

3.2.3 一种比较奇怪的情况

当页面跳转方式有A->CB->C两种,但是我们从A到C的时候,不需要缓存,从B到C的时候需要缓存。这时候就要用到路由的钩子结合老版本用法来实现了。

3.3 缓存组件的生命周期函数

缓存组件第一次打开的时候,和普通组件一样,也需要执行createdmounted等函数。
但是在被再次激活被停用时,这几个普通组件的生命周期函数都不会执行,会执行两个比较独特的生命周期函数。

  • activated
    这个会在缓存的组件重新激活时调用
  • deactivated
    这个会在缓存的组件停用时调用

参考链接


certbot-auto不再支持所有的操作系统,新的ssl证书方法

最近在一台服务器执行letsencrypt-auto命令出现错误:

系统不再被支持!!!

查看certbot(https://github.com/certbot/certbot/releases)

2021年1月的更新日志:

可知:

certbot-auto不再支持所有的操作系统!根据作者的说法,certbot团队认为维护certbot-auto在几乎所有流行的UNIX系统以及各种环境上的正常运行是一项繁重的工作,加之certbot-auto是基于python 2编写的,而python 2即将寿终正寝,将certbot-auto迁移至python 3需要大量工作,这非常困难,因此团队决定放弃certbot-auto的维护。

既然如此,现在我们还能继续使用certbot吗?certbot团队使用了基于snap的新的分发方法。

1. 环境
操作系统:CentOS 7

Webserver:Nginx

2. 安装letsencrypt
2.1. 安装letsencrypt之前,需要先安装snaps

a. 先安装epel

b. 安装snapd

c. 启用snapd.socket

d. 创建/var/lib/snapd/snap/snap之间的链接。

e. 退出账号并重新登录,或者重启系统,确保snap启用。

f. 将snap更新至最新版本。

2.2. 卸载已安装的certbot

如果之前在系统上已经部署过certbot,则需要先将其进行卸载。

a. 卸载certbot

b. 根据certbot安装位置删除相关文件。

c. 删除certbot附加软件包。

2.3. 安装certbot

a. 通过snap安装certbot

b. 创建/snap/bin/certbot的软链接,方便certbot命令的使用。

3. letsencrypt的使用
3.1. 获取证书。

a. 生成证书。

确保nginx处于运行状态,需要获取证书的站点在80端口,并且可以正常访问。

b. 更新nginx配置并重启nginx

3.2. 更新证书。

ubuntu系统的话,使用如下命令方式安装:

参考链接


certbot-auto不再支持所有的操作系统,新的ssl证书方法