为废弃的openssl-1.0.2填坑 (.a转.so)

先吐个槽:话说php7已经大行其道,php8也开测了,某擎微平台却还运行在已经停止支持的php5上,而php5更是依赖了已废弃的openssl-1.0.x

帮小伙伴编译php5环境的时候,发现工具链已经无法很好的编译出openssl的lib库。编译出来的libcrypto.so竟然是0字节,其他文件均正常。调整各项参数,重新编译均无法解决。

最后,只好拿libcrypto.a开刀,尝试编译出正确的.so文件。

ar -x libcrypto.a
gcc -shared *.o -o libcrypto.so
rm *.o

工作原理: .so文件是动态库文件,.a是由一系列.o 文件通过ar程序打包在一起的静态库,要把它转成动态库只需先解开,生成一堆.o文件,再通过编译器(gcc 或 ifort)编成动态库即可。

PHP版百度云加速API/SDK封装

百度云加速API参考文档 https://su.baidu.com/help/index.html#/7_kaifazhinan/2_APIcankao-NEW/2_wangzhanjieru/2.1.1_tianjiayuming.md

<?php

/**
 * Author: anrip <https://www.arnip.com>
 * Update: 2021-04-13
 */

class Yunjiasu
{
    private su;

    privatename = '';
    private zone = [];

    public function __construct(domain, access_key,secret_key)
    {
        this->su = new YunjiasuCore(access_key, secret_key);this->zone = this->su->zones(['name' =>domain]);
        this->name =domain;
    }

    public function __call(name,arguments)
    {
        array_unshift(arguments,this->zone['id']);
        return call_user_func_array(array(this->su,name), arguments);
    }

    public function dns_records(data = [])
    {
        list =this->su->dns_records(this->zone['id']);
        if (empty(list) || empty(data)) {
            returnlist;
        }
        return array_filter(
            list,
            function (item) use (data) {
                isset(data['name']) && data['name'] .= '.' .this->name;
                return data === array_intersect_assoc(item, data);
            }
        );
    }

    public function dns_records_delete(data)
    {
        return array_map(
            function (rs) {
                returnthis->su->dns_records_delete(this->zone['id'],rs['id']);
            },
            this->dns_records(data)
        );
    }
}

class YunjiasuCore
{
    private api_base = 'https://api.su.baidu.com/';

    privateaccess_key = '';
    private secret_key = '';

    public function __construct(access_key, secret_key)
    {this->access_key = access_key;this->secret_key = secret_key;
    }

    ////////////////////////////////////////////////////////////////

    public function zones(data)
    {
        path = 'zones';
        returnthis->api_call('GET', path,data);
    }

    ////////////////////////////////////////////////////////////////

    public function dns_records(zone_id)
    {path = 'zones/' . zone_id . '/dns_records';
        returnthis->api_call('GET', path);
    }

    public function dns_records_insert(zone_id, data)
    {path = 'zones/' . zone_id . '/dns_records';
        returnthis->api_call('POST', path,data);
    }

    public function dns_records_update(zone_id,data)
    {
        path = 'zones/' .zone_id . '/dns_records';
        return this->api_call('PATCH',path, data);
    }

    public function dns_records_delete(zone_id, id)
    {path = 'zones/' . zone_id . '/dns_records/' .id;
        return this->api_call('DELETE',path);
    }

    ////////////////////////////////////////////////////////////////

    public function purge_cache(zone_id,data)
    {
        path = 'zones/' .zone_id . '/purge_cache';
        return this->api_call('DELETE',path, data);
    }

    ////////////////////////////////////////////////////////////////

    private function api_call(method, path,data = NULL)
    {
        path = 'v31/yjs/' .path;

        print_r("\n> " . method .\'/' .path);

        url =this->api_base . path;header = this->api_header(path, data);result = this->http_repuest(method, url,header, data);

        if (!empty(result['errors'])) {
            error = json_encode(result['errors']);
            throw new Exception(error);
        }

        if (!empty(result['result'])) {
            return result['result'];
        }

        if (!empty(result['success'])) {
            return ['success' => true];
        }

        return result;
    }

    private function api_header(path, data = NULL)
    {params = [
            'X-Auth-Access-Key' => this->access_key,
            'X-Auth-Nonce' => uniqid(),
            'X-Auth-Path-Info' =>path,
            'X-Auth-Signature-Method' => 'HMAC-SHA1',
            'X-Auth-Timestamp' => time(),
        ];

        if (is_array(data)) {params = array_merge(params,data);
        }

        ksort(params);header = signls = [];

        foreach (params as k =>v) {
            if (is_bool(v)) {v = v ? 'true' : 'false';
            }
            if (is_array(v)) {
                v = str_replace('","', '", "', json_encode(v, JSON_UNESCAPED_SLASHES));
            }
            if (strpos(k, 'X-Auth') === 0) {header[] = k . ':' .v;
            }
            if (v !== '') {signls[] = k . '=' .v;
            }
        }

        header[] = 'X-Auth-Sign:' . base64_encode(
            hash_hmac('sha1', implode('&',signls), this->secret_key, true)
        );

        returnheader;
    }

    ////////////////////////////////////////////////////////////////

    private function http_repuest(method,url, header = NULL,body = NULL)
    {
        ch = curl_init();

        if (method == 'GET' && body) {url .= '?' . http_build_query(body);body = NULL;
        }

        curl_setopt(ch, CURLOPT_URL,url);
        curl_setopt(ch, CURLOPT_CUSTOMREQUEST,method);
        curl_setopt(ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt(ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt(ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt(ch, CURLOPT_CONNECTTIMEOUT, 30);

        if (header) {
            curl_setopt(ch, CURLOPT_HTTPHEADER, header);
        }

        if (body) {
            if (is_array(body)) {body = json_encode(body);
            }
            curl_setopt(ch, CURLOPT_POSTFIELDS, body);
        }result = curl_exec(ch);errno = curl_errno(ch);error = curl_error(ch);

        curl_close(ch);

        if (errno) {
            return ['error' =>errno, 'message' => error];
        }

        return json_decode(result, true);
    }
}

自动生成百度云加速IP白名单

百度云加速的ip段非常之多,官方给了一个帖子来列出这些ip,获取起来十分不便。
倍感痛苦的我,最终还是决定写个PHP脚本自动更新Nginx的real_ip规则。其他规则也可以参考修改。

最新源码参看:https://github.com/anrip/baidu-yunjiasu-ip

<?php
bdip = read_ip_list('https://ticket-baidu.kf5.com/posts/view/148628');cfip = read_ip_list('https://www.cloudflare.com/ips-v4');
list = array_merge(bdip, cfip);

make_nginx_real_ip_conf(list);

///////////////////////////////////////////////////////////

function make_nginx_real_ip_conf(list) {
    foreach(list as &ip) {ip = "set_real_ip_from {ip};";
    }text = implode("\n", list);
    file_put_contents('nginx_real_ip.conf',text);
}

function read_ip_list(site) {html = file_get_contents(site);
    if(!preg_match_all('/\d+\.\d+\.\d+\.\d+\/\d+/',html, list)) {
        exit("读取远程数据失败: {site}\n");
    }
    return sort_ip_list(list[0]);
}

function sort_ip_list(list) {
    rets = array();
    foreach(array_unique(list) as val) {ip = ip2long(explode('/', val)[0]);ip = sprintf('%u', floatval(ip));rets[ip] =val;
    }
    ksort(rets);
    return array_values(rets);
}

设置CORS实现跨域请求

一、使用php代码实现

#
# CORS config for php
# Code by anrip[wang@rehiy.com]
#

function make_cors(origin = '*') {request_method = _SERVER['REQUEST_METHOD'];

    if (request_method === 'OPTIONS') {

        header('Access-Control-Allow-Origin:'.origin);
        header('Access-Control-Allow-Credentials:true');
        header('Access-Control-Allow-Methods:GET, POST, OPTIONS');

        header('Access-Control-Max-Age:1728000');
        header('Content-Type:text/plain charset=UTF-8');
        header('Content-Length: 0',true);

        header('status: 204');
        header('HTTP/1.0 204 No Content');

    }

    if (request_method === 'POST') {

        header('Access-Control-Allow-Origin:'.origin);
        header('Access-Control-Allow-Credentials:true');
        header('Access-Control-Allow-Methods:GET, POST, OPTIONS');

    }

    if (request_method === 'GET') {

        header('Access-Control-Allow-Origin:'.$origin);
        header('Access-Control-Allow-Credentials:true');
        header('Access-Control-Allow-Methods:GET, POST, OPTIONS');

    }

}

二、使用nginx配置实现

#
# CORS config for nginx
# Code by anrip[wang@rehiy.com]
#

location / {

    set origin '*';

    if (request_method = 'OPTIONS') {

        add_header 'Access-Control-Allow-Origin' origin;

        #
        # Om nom nom cookies
        #
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';

        #
        # Custom headers and headers various browsers *should* be OK with but aren't
        #
        add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';

        #
        # Tell client that this pre-flight info is valid for 20 days
        #
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain charset=UTF-8';
        add_header 'Content-Length' 0;

        return 204;

    }

    if (request_method = 'POST') {

        add_header 'Access-Control-Allow-Origin' origin;
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';

    }

    if (request_method = 'GET') {

        add_header 'Access-Control-Allow-Origin' $origin;
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';

    }

}

Ubuntu修复grub开机引导

安装完Ubuntu忘记写入引导文件,或者使用工具调整分区后,很容易出现grub损坏无法进入系统的情况。
也可能是由于分区调整或分区UUID改变造成grub2不能正常启动,从而进入修复模式了(grub rescue),也称救援模式。

error : unknow filesystem
grub rescue >

在救援模式下只有很少的命令可以用:

(1)set     查看环境变量,这里可以查看启动路径和分区。
(2)ls      查看设备
(3)insmod  加载模块
(4)root    指定用于启动系统的分区,在救援模式下设置grub启动分区
(5)prefix  设定grub启动路径

具体修复步骤如下:

1、查看分区:

grub rescue> ls   //回车,会出现如下字样:
(hd0) (hd0,msdos6) (hd0,msdos5) (hd0,msdos1)

2、寻找ubuntu分区:

若包含/bin以及/sbin等目录,表示为主分区。
若包含/boot/grub或者/grub,即为启动分区。

grub rescue> ls (hd0,msdos1)/

3、修改启动分区:

假如你找到的主分区为(hd0,msdos6),grub所在路径是(hd0,msdos1)/grub

grub rescue > root=hd0,msdos1
grub rescue > prefix=/grub    //grub路径设置
grub rescue > set root=hd0,msdos1
grub rescue > set prefix=(hd0,msdos1)/grub
grub rescue > insmod normal   //启动normal启动
grub rescue > normal

之后你就会看到熟悉的启动菜单栏了

4、进入命令行启动ubuntu

进入系统启动选项界面后还是进不去,按C进入命令行模式

grub > set root=hd0,msdos1
grub > set prefix=(hd0,msdos1)/grub
grub > linux /vmlinuz-xxx-xxx root=/dev/sda6 //按Tab键自动补全,若acpi有问题,在最后添加acpi=off
grub > initrd /initrd.img-xxx-xxx
grub > boot

这样就可以进入了

5、进入ubuntu修复grub

sudo update-grub
sudo grub-install /dev/sda

6、重启,搞定!!

使用php实现mysql转sqlite语句

在写ArkPlus框架过程中一直使用的基于PDO驱动的MySQL,因为项目需求,要转一个SQLite版本,于是写了这个简单的转换函数,实现MySQL建表语句到SQLite的平滑转换,有需要的童鞋们可以拿去。

/**
 * mysql(ctreate_table)转sqlite语句
 * @author anrip[wang@rehiy.com]
 * @version 2.1, 2013-01-18 17:02
 * @link http://www.rehiy.com/?arkplus
 */
function ark_table_mysql2sqlite(sql) {expr = array(
    '/`(\w+)`\s/' => '[1] ',
    '/\s+UNSIGNED/i' => '',
    '/\s+[A-Z]*INT(\([0-9]+\))/i' =>\'INTEGER1',
    '/\s+INTEGER\(\d+\)(.+AUTO_INCREMENT)/i' => ' INTEGER1',
    '/\s+AUTO_INCREMENT(?!=)/i' =>\'PRIMARY KEY AUTOINCREMENT',
    '/\s+ENUM\([^)]+\)/i' =>\'VARCHAR(255)',
    '/\s+ON\s+UPDATE\s+[^,]*/i' =>\'',
    '/\s+COMMENT\s+(["\']).+\1/iU' =>\'',
    '/[\r\n]+\s+PRIMARY\s+KEY\s+[^\r\n]+/i' => '',
    '/[\r\n]+\s+UNIQUE\s+KEY\s+[^\r\n]+/i' => '',
    '/[\r\n]+\s+KEY\s+[^\r\n]+/i' => '',
    '/,([\s\r\n])+\)/i' => '1)',
    '/\s+ENGINE\s*=\s*\w+/i' => ' ',
    '/\s+CHARSET\s*=\s*\w+/i' => ' ',
    '/\s+AUTO_INCREMENT\s*=\s*\d+/i' => ' ',
    '/\s+DEFAULT\s+;/i' => ';',
    '/\)([\s\r\n])+;/i' => ');',
  );
  sql = preg_replace(array_keys(expr), array_values(expr),sql);
  return sql === null ? '{table_mysql2sqlite_error}' :sql;
}

使用php计算一些时间段

在审查项目代码的过程中,发现一处计算时间的地方很是难懂,于是耐着性子看了下去,看完那300多行代码,我终于明悟了它们只是为了获取几个时间段而存在,苦闷!

下面给出我的计算方法(或许还有更简洁的方法,烦请告知);免得有些人再写300行代码去实现~~

//年/月/日/星期/本月天数
list(year,month, day,week, days) = explode('/', date('Y/m/d/w/t'));
//计算今日时间范围
echo '<br/>本日开始 '.date('Y-m-d H:i:s w', strtotime(year.'-'.month.'-'.day));
echo '<br/>本日结束 '.date('Y-m-d H:i:s w', strtotime(year.'-'.month.'-'.day)+86399);
//计算本周时间范围
echo '<br/>本周开始 '.date('Y-m-d H:i:s w', strtotime(year.'-'.month.'-'.day)-86400*week);
echo '<br/>本周结束 '.date('Y-m-d H:i:s w', strtotime(year.'-'.month.'-'.day)+86400*(7-week)-1);
//计算本月时间范围
echo '<br/>本月开始 '.date('Y-m-d H:i:s w', strtotime(year.'-'.month.'-1'));
echo '<br/>本月结束 '.date('Y-m-d H:i:s w', strtotime(year.'-'.month.'-'.days)+86399);

PHP常用header状态代码

PHP的header状态代码,经常用,却总是忘记;今天整理了一下,备忘!

//200 正常状态
header('HTTP/1.1 200 OK');

//301 永久重定向,记得在后面要加重定向地址 Location:url
header('HTTP/1.1 301 Moved Permanently');

//重定向,其实就是302 暂时重定向
header('Location: http://www.maiyoule.com/');

//设置页面304 没有修改
header('HTTP/1.1 304 Not Modified');

//显示登录框,
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="登录信息"');
echo '显示的信息!';

//403 禁止访问
header('HTTP/1.1 403 Forbidden');

//404 错误
header('HTTP/1.1 404 Not Found');

//500 服务器错误
header('HTTP/1.1 500 Internal Server Error');

//3秒后重定向指定地址(也就是刷新到新页面与 <meta http-equiv="refresh" content="10;http://www.maiyoule.com/ /> 相同)
header('Refresh: 3; url=http://www.maiyoule.com/');
echo '10后跳转到http://www.maiyoule.com';

//重写 X-Powered-By 值
header('X-Powered-By: PHP/5.3.0');
header('X-Powered-By: Brain/0.6b');

//设置上下文语言
header('Content-language: en');

//设置页面最后修改时间(多用于防缓存)time = time() - 60; //建议使用filetime函数来设置页面缓存时间
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $time).' GMT');

//设置内容长度
header('Content-Length: 39344');

//设置头文件类型,可以用于流文件或者文件下载
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="example.zip"'); 
header('Content-Transfer-Encoding: binary');
readfile('example.zip');//读取文件到客户端

//禁用页面缓存
header('Cache-Control: no-cache, no-store, max-age=0, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); 
header('Pragma: no-cache');

//设置页面头信息
header('Content-Type: text/html; charset=iso-8859-1');
header('Content-Type: text/html; charset=utf-8');
header('Content-Type: text/plain'); 
header('Content-Type: image/jpeg'); 
header('Content-Type: application/zip'); 
header('Content-Type: application/pdf'); 
header('Content-Type: audio/mpeg');
header('Content-Type: application/x-shockwave-flash'); 

PHP-FPM进程CPU占用100%之探索

服务器不时出现的CPU使用率超高、内存几乎被吃光的现象。top查看之,发现php-fpm进程高挂不下,于是根据Google总结如下方案:

一、检查PHP配置及代码

#找出CPU使用率高的进程PID
top
#查看进程的内存使用情况
pmap PID
#跟踪进程执行的系统操作
strace -p PID
#查看该进程在处理的文件
ll /proc/PID/fd

a.结合系统的内存大小,配置php-fpm的进程数(max_children)
b.将有可疑的PHP代码修改之,如:file_get_contents没有设置超时时间

二、启用php-fpm慢日志
本来不想加此方法,但是@phpsir提出了,我也放上来。
此日志是dump出脚本执行的过程到文本文件,相对比较直观,只是我感觉有时候找不到自己想要东西,可能还是不习惯这么用吧。大家可以尝试下。

三、强制结束高消耗进程

php-fpm 进程占用的内存超过 %5 就把它kill掉

#!/bin/sh
fpms=`ps aux | grep php-fpm | grep -v grep | awk '{if(3>=5)print2}'`
for fpm in fpms; do
  kill -9fpm
  echo `date +'%F %T'` $fpm >>/var/log/php5/kill.log
done

正则表达式 – 模式修饰符(PHP)

i (PCRE_CASELESS)
如果设置了这个修饰符,模式中的字母会进行大小写不敏感匹配。

m (PCRE_MULTILINE)
默认情况下,PCRE 认为目标字符串是由单行字符组成的(然而实际上它可能会包含多行),"行首"元字符 ^ 仅匹配字符串的开始位置, 而"行末"元字符 $ 仅匹配字符串末尾,或者最后的换行符(除非设置了 D 修饰符)。这个行为和 perl 相同。当这个修饰符设置之后,“行首”和“行末”就会匹配目标字符串中任意换行符之前或之后,另外,还分别匹配目标字符串的最开始和最末尾位置。这等同于 perl 的 /m 修饰符。如果目标字符串中没有 \n 字符,或者模式中没有出现 ^$,设置这个修饰符不产生任何影响。

s (PCRE_DOTALL)
如果设置了这个修饰符,模式中的点号元字符匹配所有字符,包含换行符。如果没有这个修饰符,点号不匹配换行符。这个修饰符等同于 perl 中的 /s修饰符。 一个取反字符类比如 [^a] 总是匹配换行符,而不依赖于这个修饰符的设置。

x (PCRE_EXTENDED)
如果设置了这个修饰符,模式中的没有经过转义的或不在字符类中的空白数据字符总会被忽略, 并且位于一个未转义的字符类外部的#字符和下一个换行符之间的字符也被忽略。 这个修饰符等同于 perl 中的 /x 修饰符,使被编译模式中可以包含注释。
Note: 这仅用于数据字符。空白字符还是不能在模式的特殊字符序列中出现。

e (PREG_REPLACE_EVAL)
如果这个修饰符设置了, preg_replace() 在进行了对替换字符串的后向引用替换之后, 将替换后的字符串作为 php 代码评估执行(eval 函数方式),并使用执行结果作为实际参与替换的字符串。单引号、双引号、反斜线(\)和 NULL 字符在后向引用替换时会被用反斜线转义。
Note:preg_replace() 使用此修饰符。PHP5.5之后使用 preg_replace_callback() 代替此修饰符。

A (PCRE_ANCHORED)
如果设置了这个修饰符,模式被强制为"锚定"模式,也就是说约束匹配使其仅从目标字符串的开始位置搜索。这个效果同样可以使用适当的模式构造出来,并且这也是 perl 种实现这种模式的唯一途径。

D (PCRE_DOLLAR_ENDONLY)
如果这个修饰符被设置,模式中的元字符美元符号仅仅匹配目标字符串的末尾。如果这个修饰符没有设置,当字符串以一个换行符结尾时, 美元符号还会匹配该换行符(但不会匹配之前的任何换行符)。如果设置了修饰符m,这个修饰符被忽略. 在 perl 中没有与此修饰符等同的修饰符。

S
当一个模式需要多次使用的时候,为了得到匹配速度的提升,值得花费一些时间对其进行一些额外的分析。如果设置了这个修饰符,这个额外的分析就会执行。当前, 这种对一个模式的分析仅仅适用于非锚定模式的匹配(即没有单独的固定开始字符)。

U (PCRE_UNGREEDY)
这个修饰符逆转了量词的"贪婪"模式。 使量词默认为非贪婪的,通过量词后紧跟? 的方式可以使其成为贪婪的。这和 perl 是不兼容的。 它同样可以使用 模式内修饰符设置 (?U)进行设置, 或者在量词后以问号标记其非贪婪(比如.*?)。
Note: 在非贪婪模式,通常不能匹配超过 pcre.backtrack_limit 的字符。

X (PCRE_EXTRA)
这个修饰符打开了 PCRE 与 perl 不兼容的附件功能。模式中的任意反斜线后就 ingen 一个没有特殊含义的字符都会导致一个错误,以此保留这些字符以保证向后兼容性。 默认情况下,在 perl 中,反斜线紧跟一个没有特殊含义的字符被认为是该字符的原文。当前没有其他特性由这个修饰符控制。

J (PCRE_INFO_JCHANGED)
内部选项设置(?J)修改本地的PCRE_DUPNAMES选项。允许子组重名,(译注:只能通过内部选项设置,外部的 /J 设置会产生错误。)

u (PCRE_UTF8)
此修正符打开一个与 perl 不兼容的附加功能。 模式字符串被认为是utf-8的. 这个修饰符从 unix 版 php 4.1.0 或更高,win32版 php 4.2.3 开始可用。 php 4.3.5 开始检查模式的 utf-8 合法性。