thymeleaf与vue结合使用时,vue如何取模板里的值

<li th:each="grade : ${grades}" th:v-bind:class="|{current: gradeId==${grade.id}}|">

<a th:title="${grade.name}" href="javascript:void(0)"  th:id="${grade.id}"
  th:text="${grade.name}"  th:@click="|getCourses(${grade.id},subjectId,1)|"
  >二年级</a></li>
th:@click="|getCourses(${grade.id},subjectId,1)|"

@clickVUE里绑定的点击事件,此时事件存在于thymeleaf的循环th:each下的元素,getCourses()vue里的方法属于js,但是需要取到模板里产生的值<年级id>

此时可以用th:v-on:"| |" 或者th:@click="| |" 简单来说就是将前端的方法当作字符串拼接起来,前面加th:就能解析${grade.id} 的值

th:v-bind:class="|{current: gradeId==${grade.id}}|"

同理,绑定class用于样式也能如此

参考链接


thymeleaf 与 vue 结合使用时,vue如何取模板里的值

Error: Attribute Signature requires InnerClasses attribute. Check -keepattributes directive

最近在编译Android项目的时候,报告如下错误:

Error: Attribute Signature requires InnerClasses attribute. Check -keepattributes directive

网上查找了很久,才了解到问题出在混淆配置上,具体原因为当混淆配置项中存在

-keepattributes Signature

的时候,需要同步增加

-keepattributes InnerClasses

更详细的解释参考如下:

Signature (Java 8 or higher) works only Java 8 or higher and InnerClasses (Java 5 or higher) so check your Android Studio is using Java SDK version. Please update your Proguard config with below settings

Add this line to your proguard-rules.pro file:

-keepattributes InnerClasses

InnerClasses (Java 5 or higher)

Specifies the relationship between a class and its inner classes and outer classes. Other than this and the naming convention with a '$' separator between the names of inner classes and outer classes, inner classes are just like ordinary classes. Compilers may need this information to find classes referenced in a compiled library. Code may access this information by reflection, for instance to derive the simple name of the class.

Signature (Java 8 or higher)

Specifies the generic signature of the class, field, or method. Compilers may need this information to properly compile classes that use generic types from compiled libraries. Code may access this signature by reflection.

More details about -keepattributes and more settings you can apply, please see below link.

Proguard options

参考链接


Android Build Error: Attribute Signature requires InnerClasses attribute. Check -keepattributes directive

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中只能通过继承来重写父类的方法。

@interface XRKURLProtocol : NSURLProtocol
@end

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

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
     [NSURLProtocol registerClass:[XRKURLProtocol class]];
}

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

+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
        return NO;
    }

    //http和https都会出现dns劫持情况,都需要处理
    NSString *scheme = [[request URL] scheme];
    if (([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame)) {
        // 判断请求是否为白名单
        NSArray *whiteLists = [XRKConfigManager sharedManager].whiteList;
        if (whiteLists && [whiteLists isKindOfClass:[NSArray class]]) {
            for (NSString *url in whiteLists) {
                if (request.URL.host && [request.URL.host hasSuffix:url]) {
                    return YES;
                }
            }
        }
    }

    return NO;
}

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

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return request;
}

以下是官方对这个方法的解释。当我们想对某个请求添加请求头或者返回新的请求时,可以在这个方法里自定义然后返回,一般情况下直接返回参数里的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相同时是否使用缓存数据,以下例子是直接返回父类实现。

+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
    return [super requestIsCacheEquivalent:a toRequest:b];
}

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

- (void)startLoading {
    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];

    //打标签,防止无限循环
    [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];
    // dns解析
    NSMutableURLRequest *request = [self.class replaceHostInRequset:mutableReqeust];
    self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
}

+ (NSMutableURLRequest *)replaceHostInRequset:(NSMutableURLRequest *)request {
    if ([request.URL host].length == 0) {
        return request;
    }

    NSString *originUrlString = [request.URL absoluteString];
    NSString *originHostString = [request.URL host];
    NSRange hostRange = [originUrlString rangeOfString:originHostString];
    if (hostRange.location == NSNotFound) {
        return request;
    }

    //用HappyDNS 替换host
    NSMutableArray *array = [NSMutableArray array];
    /// 第一dns解析为114,第二解析才是系统dns
    [array addObject:[[QNResolver alloc] initWithAddress:@"114.114.115.115"]];
    [array addObject:[QNResolver systemResolver]];
    QNDnsManager *dnsManager = [[QNDnsManager alloc] init:array networkInfo:[QNNetworkInfo normal]];
    NSArray *queryArray = [dnsManager query:originHostString];
    if (queryArray && queryArray.count > 0) {
        NSString *ip = queryArray[0];
        if (ip && ip.length) {
            // 替换host
            NSString *urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:ip];
            NSURL *url = [NSURL URLWithString:urlString];
            request.URL = url;

            [request setValue:originHostString forHTTPHeaderField:@"Host"];
        }
    }

    return request;
}
- (void)stopLoading {
    [self.connection cancel];
}

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

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.client URLProtocol:self didLoadData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [self.client URLProtocolDidFinishLoading:self];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    [self.client URLProtocol:self didFailWithError:error];
}

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

Web资源本地化

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

- (void)startLoading {
    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
    // 处理MIME type
    NSString *mimeType = nil;
    mutableReqeust = [self.class replaceLocalSource:mutableReqeust];
    NSString *pathComponent = mutableReqeust.URL.absoluteString.lastPathComponent;
    if ([pathComponent hasSuffix:@"js"]) {
        mimeType = @"text/javascript";
    } else if ([pathComponent hasSuffix:@"css"]) {
        mimeType = @"text/css";
    }
    
    if (mimeType) {
        NSData *data = [NSData dataWithContentsOfFile:mutableReqeust.URL.absoluteString];
        
        NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[[self request] URL]
                                                            MIMEType:mimeType
                                               expectedContentLength:[data length]
                                                    textEncodingName:@"UTF8"];
        [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
        [[self client] URLProtocol:self didLoadData:data];
        [[self client] URLProtocolDidFinishLoading:self];
    }
}
#pragma mark - 判断是否是本地资源
+ (BOOL)canReplaceLocalSource:(NSURLRequest *)request {
    NSString *absoluteString = request.URL.absoluteString;
    for (NSString *localSourceurl in [self localSourceArray]) {
        if ([absoluteString isEqualToString:localSourceurl]) {
            return YES;
        }
    }
    return NO;
}

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

Vue axios 发送 FormData 请求

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

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

Payload:

Content-Type: 'application/json; charset=utf-8'

Form Data:

Content-Type: 'application/x-www-form-urlencoded'

Content-Type: 'multipart/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 请求(下面有这几种方式格式化参的数据样本,用于参考比较,看需求选择方式)
方式一,自己封装一个格式化函数:

import axios from 'axios'

################################### 请求方式一,全局使用
// 创建 axios 实例
const service = axios.create({
  baseURL: '',
  timeout: 20000,
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
})

// 将请求数据转换成功 formdata 接收格式
service.defaults.transformRequest = (data) => {
  return stringify(data)
}

################################### 请求方式二,局部使用
axios({
  method: 'post',
  url: 'http://localhost:8080/dzm',
  data: {
    username: 'dzm',
    password: 'dzm123456'
  },
  transformRequest: [
    function (data) {
      // 将请求数据转换成功 formdata 接收格式
      return stringify(data)
    }
  ],
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
})

################################### 转换方法封装

// 将参数转换成功 formdata 接收格式
function stringify (data) {
  const formData = new FormData()
  for (const key in data) {
    // eslint-disable-next-line no-prototype-builtins
    if (data.hasOwnProperty(key)) {
      if (data[key]) {
        if (data[key].constructor === Array) {
          if (data[key][0]) {
            if (data[key][0].constructor === Object) {
              formData.append(key, JSON.stringify(data[key]))
            } else {
              data[key].forEach((item, index) => {
                formData.append(key + `[${index}]`, item)
              })
            }
          } else {
            formData.append(key + '[]', '')
          }
        } else if (data[key].constructor === Object) {
          formData.append(key, JSON.stringify(data[key]))
        } else {
          formData.append(key, data[key])
        }
      } else {
        if (data[key] === 0) {
          formData.append(key, 0)
        } else {
          formData.append(key, '')
        }
      }
    }
  }
  return formData
}

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

import axios from 'axios'
// qs 模块是安装 axios 模块的时候就有的,不用另行安装,通过 import 引入即可使用
import qs from 'qs'

################################### 请求方式一,全局使用

// 创建 axios 实例
const service = axios.create({
  baseURL: '',
  timeout: 20000,
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
})

// 将请求数据转换成功 formdata 接收格式,这一段选方式一那种拦截转换也可以。
service.interceptors.request.use(config => {
  const token = Vue.ls.get(ACCESS_TOKEN)
  if (token) {
    // 让每个请求携带自定义 token 请根据实际情况自行修改
    config.headers['X-Token'] = token
  }
  // 将请求数据转换成功 formdata 接收格式
  config.data = qs.stringify(config.data)
  return config
}, err)

################################### 请求方式二,局部使用
axios({
  method: 'post',
  url: 'http://localhost:8080/dzm',
  data: {
    username: 'dzm',
    password: 'dzm123456'
  },
  transformRequest: [
    function (data) {
      // 将请求数据转换成功 formdata 接收格式
      return qs.stringify(data)
    }
  ],
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
})

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

import axios from 'axios'

################################### 请求方式跟上面一样
axios({
  method: 'post',
  url: 'http://localhost:8080/dzm',
  data: {
    username: 'dzm',
    password: 'dzm123456'
  },
  transformRequest: [
    function (data) {
      // 将请求数据转换成功 formdata 接收格式
      return stringify(data)
    }
  ],
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
})

################################### 转换方法封装

// 将参数转换成功 formdata 接收格式
function stringify (data) {
  let ret = ''
  for (const it in data) {
    ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
  }
  ret = ret.substring(0, ret.lastIndexOf('&'))
  return ret
}

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

// 数组无值
id: 2086
intention: 
follower_id[]: 
concat_material[]:

// 数组有值
id: 2086
intention: 
follower_id[0]: 351
follower_id[1]: 66
// 数组 json 为空会被转成正常的数组,有值会被转成字符串,所以服务器需要注意处理
concat_material: [{"fname":"视频订单.xls","key":"local/other/099f4be38fb8e69bb031cbc36ed283a6.xls"}]

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

// 数组无值
id: 2086
intention: 

// 数组有值
id: 2086
intention: 
follower_id[0]: 351
follower_id[1]: 66
concat_material[0][fname]: 视频订单.xls
concat_material[0][key]: local/other/099f4be38fb8e69bb031cbc36ed283a6.xls
concat_material[1][fname]: 视频订单1.xls
concat_material[1][key]: local/other/099f4be38fb8e69bb031cbc36ed283a8.xls

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

// 数组无值
id: 743
intention: 2
follower_id: 
concat_material:

// 数组有值
id: 2086
intention: 
follower_id: 66,351
concat_material: [object Object],[object Object]

参考链接


Vue axios 发送 FormData 请求

sqlite3 database is locked解决方案

注意:

sqlite3只支持一写多读.
读与读可以同时进行
读与写不可同时进行
写与写不可同时进行

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

The SQLITE_BUSY result code indicates that the database file could not be written (or in some cases read) because of concurrent activity by some other database
connection, usually a database connection in a separate process.

For example, if process A is in the middle of a large write transaction and at the same time process B attempts to start a new write transaction, process B will get back an SQLITE_BUSY result because SQLite only supports one writer at a time. Process B will need to wait for process A to finish its transaction before starting a new transaction. The sqlite3_busy_timeout() and sqlite3_busy_handler() interfaces and the busy_timeout pragma are available to process B to help it deal with SQLITE_BUSY errors.

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

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

Many concurrent writers? → choose client/server

If many threads and/or processes need to write the database at the same instant (and they cannot queue up and take turns) then it is best to select a database engine that supports that capability, which always means a client/server database engine.

SQLite only supports one writer at a time per database file. But in most cases, a write transaction only takes milliseconds and so multiple writers can simply take turns. SQLite will handle more write concurrency than many people suspect. Nevertheless, client/server database systems, because they have a long-running server process at hand to coordinate access, can usually handle far more write concurrency than SQLite ever will.

synchronized全局锁和实例锁的区别

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

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

pulbic class Something {
    public synchronized void isSyncA(){}

    public synchronized void isSyncB(){}

    public static synchronized void cSyncA(){}

    public static synchronized void cSyncB(){}
}

假设,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

java.sql.SQLException: not implemented by SQLite JDBC driver

错误,或者如下错误:

2021-06-03 09:25:20.435 ERROR 15643 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'appIcon' from result set.  Cause: java.sql.SQLFeatureNotSupportedException
### The error may exist in xxxx.java (best guess)
### The error may involve xxx.select
### The error occurred while handling results
### SQL: SELECT * FROM xxx WHERE id=?
### Cause: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'appIcon' from result set.  Cause: java.sql.SQLFeatureNotSupportedException] with root cause

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

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

public Blob getBlob(int col)
throws SQLException { throw unused(); }
public Blob getBlob(String col)
throws SQLException { throw unused(); }

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

<select id="queryByList" resultMap="queryBaseResultMap">
    select * from my_blob 
</select>
<resultMap id="queryBaseResultMap" type="com.wx.model.MyBlob" >
    <id column="Id" property="id" jdbcType="INTEGER" />
    <result column="blob" property="blob" typeHandler="com.wx.handler.BlobTypeHandler"/> 
</resultMap>

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

public class BlobTypeHandler extends BaseTypeHandler<byte[]> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, byte[] parameter, JdbcType jdbcType) throws SQLException {
        ps.setBytes(i, parameter);
    }

    @Override
    public byte[] getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getBytes(columnName);
    }

    @Override
    public byte[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return rs.getBytes(columnIndex);
    }

    @Override
    public byte[] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return cs.getBytes(columnIndex);
    }
}

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

参考链接


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

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

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

JSON字符串格式如下

{“key”:"value","key1":"value1"}
JsonObject jarray = jsonParser.prase( json ).getAsJsonObject();
 for (String k : Jarray.keySet()) {
      System.out.println(Jarray.get(k).toString()); //输出的字符串是 “value”   "value1" (是带有 “” 的)
 }

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

---
key: "\"value\""
key1: "\"value1\""

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

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

JsonObject jarray = jsonParser.prase( json ).getAsJsonObject();
 for (String k : Jarray.keySet()) {
      System.out.println(Jarray.get(k).getAsString()); //输出的字符串是  value   value1 (是不带有 “” 的)
 }

参考链接


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系统上执行升级工作的时候,发生错误,内容如下:

$  brew upgrade cmake
==> Upgrading 1 outdated package:
cmake 3.17.3 -> 3.20.3
==> Upgrading cmake 3.17.3 -> 3.20.3 
==> Downloading https://www.bytereef.org/contrib/decimal.diff
Already downloaded: /Users/longsky/Library/Caches/Homebrew/downloads/f60b5004541eb3c87cce87ef3bf94933a2684ab267346afdc45ae1622ffa923a--decimal.diff
==> Downloading https://files.pythonhosted.org/packages/f6/e9/19af16328705915233299f6f1f02db95899fb00c75ac9da4757aa1e5d1de/setuptools-56.0.0.t

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. See https://status.python.org/incidents/hzmjhqsdjqgb and ht
Error: Failed to download resource "python@3.9--setuptools"
Download failed: https://files.pythonhosted.org/packages/f6/e9/19af16328705915233299f6f1f02db95899fb00c75ac9da4757aa1e5d1de/setuptools-56.0.0.tar.gz

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

$ cat .curlrc 
--insecure

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

参考链接