欢迎转载!转载时请注明出处:http://blog.csdn.net/nfer_zhuang/article/details/50316171
前言
WordPress自带的注册页面比较简陋,BuddyPress插件提供了一个比较丰富的注册页面,但是当我打开页面时却显示了“404 Not Found”(已打开WordPress的注册开关并设置了Buddypress的注册页面),本篇文章就是记录如何排查和解决该问题的。
问题截图
注:我是在WordPress4.3.1版本下进行的测试
排查问题
确认BuddyPress页面设置
通过截图,我们确认设置了使用BuddyPress的注册页面来替换原生页面。
确认页面是否可以直接打开
我们注意在每个设置的右边有一个view的按钮,那么直接点击这个按钮是否可以打开页面呢?
通过截图,我们确认直接打开页面是OK的,至少说明设置是有效的,且页面存在。
但是,我们发现了一个奇怪的地方,就是这两个页面的URL中都带有一个index.php
,这是个什么东西呢?看上去完全不符合常理啊。
固定链接
通过查询资料,我们发现这个index.php
是根据固定链接中的设置添加到URL中的。
那为什么WordPress会设置成/index.php/*
这种反人类的方式呢?
源码分析
在wp-admin/includes/upgrade.php文件下有下述代码:
/*
* The Permalink structures to attempt.
*
* The first is designed for mod_rewrite or nginx rewriting.
*
* The second is PATHINFO-based permalinks for web server configurations
* without a true rewrite module enabled.
*/
$permalink_structures = array(
'/%year%/%monthnum%/%day%/%postname%/',
'/index.php/%year%/%monthnum%/%day%/%postname%/'
);
foreach ( (array) $permalink_structures as $permalink_structure ) {
$wp_rewrite->set_permalink_structure( $permalink_structure );
/*
* Flush rules with the hard option to force refresh of the web-server's
* rewrite config file (e.g. .htaccess or web.config).
*/
$wp_rewrite->flush_rules( true );
// Test against a real WordPress Post, or if none were created, a random 404 page.
$test_url = get_permalink( 1 );
if ( ! $test_url ) {
$test_url = home_url( '/wordpress-check-for-rewrites/' );
}
/*
* Send a request to the site, and check whether
* the 'x-pingback' header is returned as expected.
*
* Uses wp_remote_get() instead of wp_remote_head() because web servers
* can block head requests.
*/
$response = wp_remote_get( $test_url, array( 'timeout' => 5 ) );
$x_pingback_header = wp_remote_retrieve_header( $response, 'x-pingback' );
$pretty_permalinks = $x_pingback_header && $x_pingback_header === get_bloginfo( 'pingback_url' );
if ( $pretty_permalinks ) {
return true;
}
}
上述代码是在用户安装WordPress时候运行的代码片段,通过添加打印,我们确认上面的foreach
loop在运行到'/index.php/%year%/%monthnum%/%day%/%postname%/'
的时候才返回true。
回到上面permalink_structures数组的注释上:
/*
* The Permalink structures to attempt.
*
* The first is designed for mod_rewrite or nginx rewriting.
*
* The second is PATHINFO-based permalinks for web server configurations
* without a true rewrite module enabled.
*/
我们了解到第一个固定链接格式是针对于支持mod_rewrite
或nginx rewriting
的,而第二个则是针对不支持rewrite
的HTTP Server环境的。
我这里使用的是LAMP的环境,理论上是支持rewrite
的,那么下一步我们就开始排查rewrite
功能。
LAMP的rewrite功能
检查apache是否已经配置rewrite功能
通过搜索,确认在apache2中已经配置了rewrite功能
$ grep rewrite ./ -nR
./mods-available/rewrite.load:1:LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so
./mods-enabled/rewrite.load:1:LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so
$ ls /usr/lib/apache2/modules/mod_rewrite.so
/usr/lib/apache2/modules/mod_rewrite.so
我们发现在mods-enabled中已经包含rewrite.load,且mod_rewrite.so文件存在。
通过PHP检查rewrite功能是否已配置
stackoverflow中有一个回答中给出了一种方法:
in_array('mod_rewrite', apache_get_modules());
我们添加了一些打印信息:
<?php
if (in_array('mod_rewrite', apache_get_modules()))
print "The apache module mod_rewrite is enabled.<br/>\n";
else
print "The apache module mod_rewrite is NOT enabled.<br/>\n";
但是在cli下运行报错:
$ php apache_rewrite_check
PHP Fatal error: Call to undefined function apache_get_modules() in /home/nfer/bak/apache_rewrite_check on line 2
我们尝试在浏览器中访问该文件,发现可以运行,且输出了The apache module mod_rewrite is enabled.
信息,证明在apache运行环境下通过PHP确认是支持rewrite
功能的。
通过shell命令行检查rewrite功能是否已配置
同样,在该篇回答中给出了一种在Ubuntu命令行下检查是否打开了rewrite
功能的方法:
$ apachectl -M | grep rewrite
rewrite_module (shared)
通过PHP实际测试rewrite功能
按照stackoverflow给出的例子,我们创建了rewrite文件夹,并在该文件夹下创建了.htaccess和index.php文件,内容分别为:
$ cat .htaccess
RewriteEngine On
RewriteRule ^inc/.*$ index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
$ cat index.php
<?php
print "hello world.<br/>\n";
我们测试,通过http://localhost/rewrite/ 或 http://localhost/rewrite/123123 或 http://localhost/rewrite/hello 以及任何http://localhost/rewrite/ 前缀的地址都能正确输出hello world.
,即PHP的rewrite功能是OK的。
WordPress的rewrite判断
我们重点来看一下foreach
的处理:
$wp_rewrite->set_permalink_structure( $permalink_structure );
/*
* Flush rules with the hard option to force refresh of the web-server's
* rewrite config file (e.g. .htaccess or web.config).
*/
$wp_rewrite->flush_rules( true );
// Test against a real WordPress Post, or if none were created, a random 404 page.
$test_url = get_permalink( 1 );
if ( ! $test_url ) {
$test_url = home_url( '/wordpress-check-for-rewrites/' );
}
/*
* Send a request to the site, and check whether
* the 'x-pingback' header is returned as expected.
*
* Uses wp_remote_get() instead of wp_remote_head() because web servers
* can block head requests.
*/
$response = wp_remote_get( $test_url, array( 'timeout' => 5 ) );
$x_pingback_header = wp_remote_retrieve_header( $response, 'x-pingback' );
$pretty_permalinks = $x_pingback_header && $x_pingback_header === get_bloginfo( 'pingback_url' );
if ( $pretty_permalinks ) {
return true;
}
上面的测试主要包括以下几个步骤:
1. 设置固定链接格式
2. 将rewrite规则写入到文件中
3. 使用第一篇博客(id为1)来实际测试rewrite效果
4. 发送wp_remote_get请求并检测返回的header中是否包含x-pingback字段(URL可以访问的话,response中会包含x-pingback字段,否则不包含该字段)
注:更多关于x-pingback的资料请移步WordPress官方文档
flush_rules()
在上面的代码中有一处代码如下:
/*
* Flush rules with the hard option to force refresh of the web-server's
* rewrite config file (e.g. .htaccess or web.config).
*/
$wp_rewrite->flush_rules( true );
继续看flush_rules函数的实现:
public function flush_rules( $hard = true ) {
static $do_hard_later = null;
// Prevent this action from running before everyone has registered their rewrites.
if ( ! did_action( 'wp_loaded' ) ) {
add_action( 'wp_loaded', array( $this, 'flush_rules' ) );
$do_hard_later = ( isset( $do_hard_later ) ) ? $do_hard_later || $hard : $hard;
return;
}
if ( isset( $do_hard_later ) ) {
$hard = $do_hard_later;
unset( $do_hard_later );
}
delete_option('rewrite_rules');
$this->wp_rewrite_rules();
/**
* Filter whether a "hard" rewrite rule flush should be performed when requested.
*
* A "hard" flush updates .htaccess (Apache) or web.config (IIS).
*
* @since 3.7.0
*
* @param bool $hard Whether to flush rewrite rules "hard". Default true.
*/
if ( ! $hard || ! apply_filters( 'flush_rewrite_rules_hard', true ) ) {
return;
}
if ( function_exists( 'save_mod_rewrite_rules' ) )
save_mod_rewrite_rules();
if ( function_exists( 'iis7_save_url_rewrite_rules' ) )
iis7_save_url_rewrite_rules();
}
该函数包括以下流程:
1. 处理在wp_loaded之前的flush_rules请求
2. 更新rewrite_rules配置(到数据库中)
3. 更加环境不同,分别调用save_mod_rewrite_rules和iis7_save_url_rewrite_rules来写入rewrite规则到文件系统中
save_mod_rewrite_rules()
因为我使用的LAMP环境,因此在flush_rules()中实际作用的是save_mod_rewrite_rules()函数,实现如下:
function save_mod_rewrite_rules() {
if ( is_multisite() )
return;
global $wp_rewrite;
$home_path = get_home_path();
$htaccess_file = $home_path.'.htaccess';
/*
* If the file doesn't already exist check for write access to the directory
* and whether we have some rules. Else check for write access to the file.
*/
if ((!file_exists($htaccess_file) && is_writable($home_path) && $wp_rewrite->using_mod_rewrite_permalinks()) || is_writable($htaccess_file)) {
if ( got_mod_rewrite() ) {
$rules = explode( "\n", $wp_rewrite->mod_rewrite_rules() );
return insert_with_markers( $htaccess_file, 'WordPress', $rules );
}
}
return false;
}
在上述代码中,有几处条件检查:
1. 判断htaccess_file文件是否不存在
2. 判断home_path目录是否可写
3. 判断htaccess_file文件是否可写
其实,看到这里我们需要检查是否在安装WordPress的时候WordPress程序不具有home_path目录和htaccess_file文件的写权限?
通过ls -l可以得到下述的列表:
/var/www/tag/WordPress_4_3_1$ ls -l ../
total 4
drwxrwxr-x 6 nfer nfer 4096 12月 15 17:38 WordPress_4_3_1
其中WordPress_4_3_1目录的读写权限是775,即当前用户和当前组均具有读写执行权限,其它用户只有读和执行权限并没有写入权限。我们再看一下apache进行的用户名:
/var/www/tag/WordPress_4_3_1$ ps auxf | grep apache
root 1071 0.0 0.2 38048 9024 ? Ss 04:15 0:08 /usr/sbin/apache2 -k start
www-data 4695 0.0 1.0 73968 44636 ? S 09:04 0:11 \_ /usr/sbin/apache2 -k start
www-data 4723 0.0 1.1 76304 46396 ? S 09:14 0:10 \_ /usr/sbin/apache2 -k start
可以看到apache是通过root用户启动的,然后每个工作进程是使用www-data用户启动的。
修改文件拥有者并重新安装WordPress
通过下述命令,修改WordPress目录的拥有者为www-data:
/var/www/tag$ sudo chown www-data WordPress_4_3_1/ -R
[sudo] password for nfer:
/var/www/tag$ ls -l
total 4
drwxrwxr-x 6 www-data nfer 4096 12月 15 17:38 WordPress_4_3_1
删除并重新创建数据库,删除wp-config.php文件,重新打开WordPress安装程序,整个流程完成后,我们再打开”设置-固定链接”页面,发现固定链接格式为'/%year%/%monthnum%/%day%/%postname%/'
:
总结
该文章描述了我从最开始遇到WordPress+BuddyPress无法打开注册页面问题,到一步步的通过查询资料、阅读相关源码,并最终解决问题的过程。在实际解决问题的过程中,有一些地方并没有详细展开,一些遗留问题已附在下面。
解决问题后,再次打开注册页面,效果如下:
遗留问题
- 为什么在.htaccess无法写入的情况下,使用
'/index.php/%year%/%monthnum%/%day%/%postname%/'
rewrite规则? '/index.php/%year%/%monthnum%/%day%/%postname%/'
是如何在.htaccess无法工作的情况下产生实际效果的?- flush_rules函数中的wp_loaded逻辑是否有必要?