WEB开发网
开发学院软件开发Java 开放源码 CMS 入门,第 5 部分: 为 Jakarta Slide... 阅读

开放源码 CMS 入门,第 5 部分: 为 Jakarta Slide 构建 PHP WebDAV 客户机

 2010-04-16 00:00:00 来源:WEB开发网   
核心提示:预备知识本节内容帮助您为构建 PHP WebDAV 客户机做好准备,关于本系列本教程为系列教程的第 5 部分,开放源码 CMS 入门,第 5 部分: 为 Jakarta Slide 构建 PHP WebDAV 客户机,本系列以 Eclipse、Java™ 技术、Apache Derby 和其他开放源码技术创

预备知识

本节内容帮助您为构建 PHP WebDAV 客户机做好准备。

关于本系列

本教程为系列教程的第 5 部分,本系列以 Eclipse、Java™ 技术、Apache Derby 和其他开放源码技术创建了定制的开放源码内容管理系统(CMS)。在以前的教程中,安装了 Derby、然后在 Derby 中创建 Slide 数据库,并创建了一个适配器,用它为 Derby 实现内容管理存储和检索方法。在这份教程中,将根据这个设置,为基于 Derby 的 PHP 内容管理需求进行构建。

与 Eclipse 配合,还要使用 WebDAV 和其他插件。除 Java 技术之外,还使用了 Jakarta Slide、Apache HTTP Server,当然还有 PHPPHP Eclipse 插件。

关于本教程

本教程面向想创建 PHP WebDAV 客户机的开发人员,用 PHP 访问 Jakarta Slide WebDAV 服务器(或其他 WebDAV 服务器)。通过构建客户端,可了解构建其他应用程序所必需的基本知识,例如由 Jakarta Slide 作为基本支持的 PHP 内容管理系统。

在完成本教程的学习后,您将能够从 PHP 应用程序访问 Slide 或其他任何 WebDAV 服务器(例如,Microsoft® Exchange Server Web 文件夹或 Microsoft Windows® SharePoint® Services Web 文件夹)。通过这种方式,就拥有了一个可以把成百上千用户和文档与文档(内容)和元数据(访问控制列表 [ACL]、资源层次结构等)中央存储捆绑在一起的系统。

本教程覆盖了以下信息和任务:

下载和设置 PHP 环境。

下载和安装 Eclipse 的 PHP 插件。

在 Eclipse 中创建 PHP 客户机项目。

开发和测试针对 Slide 的 PHP 客户机。

回顾和总结。

先决条件

阅读本教程之前,您必须具备基本的 PHP 编程知识和使用 Eclipse 的知识。

要运行本教程中的代码,需要具备以下条件:

PHP 5.1.1 或以上版本

Eclipse 3.0.1 或以上版本

Slide/Tomcat 包 (在第一期教程中已安装)

PHP Eclipse 插件

Apache Tomcat 4.1,如果没有使用上面的 Slide/Tomcat 包,需要安装此程序

请注意: Slide 源不 支持带软件开发包(SDK)1.5 的 Tomcat 5.5。Tomcat 5.0.30 和 SDK 1.4 是代码支持的最新版本。

替代方案 —— 如果没有 Apache、PHP 和 MySql,那么可以使用 XAMPP,这个 Apache 发行版包含 MySQL、PHP 和 Perl。XAMPP 易于安装和使用,只要下载、解压,并按照 XAMPP 安装示例 的说明操作即可。

三个源文件也可从 下载 部分中下载:

class_WebDAVClient.zip

WebDAVClient.zip

Test.zip

系统需求

要运行本教程中的示例,可使用任何版本的 Linux® 或 Windows 操作系统。要求并不严格,所以也可将此客户机与 Slide 放在同一台机器上运行,Slide 是轻量级的,只要能运行 Tomcat,就能运行它。

开始 Eclipse 项目

首先要让 Eclipse 能够创建 PHP WebDAV 客户端。

安装 Eclipse

请注意: 如果在系统上已经运行了 Eclipse 3.0.1 或以上版本,可以跳过这一步。

下载、安装和运行 Eclipse 很容易。只需按以下步骤操作即可:

下载要使用的 Eclipse 版本。本教程基于 Microsoft Windows 上的 Eclipse 3.0.1。

把 .zip 或 .tar 文件释放到选中的目录。

运行 Eclipse.exe (或 UNIX® 上的替代程序)。

如果还没有看过 第 4 份教程 (developerWorks,2006 年 1 月),请现在阅读。需要安装并在 Eclipse 中构建 Slide 2.1 的完整源代码,才能继续学习本教程。

安装 PHP

下载和安装 PHP 也很容易。要安装它,只需按以下步骤进行操作:

下载 PHP 5.1.1 或以上版本。

阅读 PHP 文档,包括 PHP FAQ。

按照 PHP 文档中 Getting Started 部分中的说明进行操作。

下载 Slide 包

在以前的教程中,已经从 Tomcat 包设置了 Slide。可以在本教程中继续使用这个已打包的版本。

设置 PHP eclipse 环境

如果没有学完本系列教程的 第 1 部分,请立刻开始学习,直到按该教程运行了 Slide 2.1。您需要使用这个包,因为本教程不包含 Java 编程的内容。

如果已设置好 PHP eclipse 环境,可以跳到 下一节。 否则,必须正确地创建环境,才能正常使用本教程中的项目。为此,请按照 PHP eclipse Setup PDF 文件或 XAMPP 安装示例 中的说明操作。使用 XAMPP 包可以大大减少开发和测试 PHP 应用程序需要的时间。

由于 XAMPP 包含 Apache、PHP、MySql 和 Perl —— 而且因为 PHP eclipse 拥有 XAMPP 的内置控件,并能把 Eclipse 项目部署到 XAMPP 环境 —— 所以迄今为止,这是最简便的方式。但是,具体选择还是取决于您。

创建 PHP 项目

本节将在 Eclipse 中创建新 PHP 项目。

创建新 PHP 项目

要开始,请单击 File > New > Project > PHP > PHP Project。出现 图 1 所示的 PHP 项目窗口,从这个窗口可以创建 PHP WebDAV 客户机的新项目。

图 1. 创建新 PHP 项目
开放源码 <a target=CMS 入门,第 5 部分: 为 Jakarta Slide 构建 PHP WebDAV 客户机" border="0" onload="return imgzoom(this,550);" style="cursor:pointer;" onclick="javascript:window.open(this.src);"/>

请创建两个新 PHP 文件:一个命名为 WebDAVClient.php,是您的演示页面;另外一个命名为 class_WebDAVClient.php,这是与 WebDAV 服务器(例如 Slide 2.1 服务器)交互的实际的类源文件。

当单击 File > New > PHP File 在 Eclipse 中创建文件时,会得到一个空白的起始文件,如 清单 1 所示。

清单 1. 新 PHP 项目的起始文件

 <?php 
/* 
 * Created on Dec 17, 2005 
 * 
 * To change the template for this generated file go to 
 * Window -> Preferences -> PHPeclipse -> PHP -> 
 * Code Templates 
 */ 
?> 

可以在 Eclipse 的 Preferences 视图中修改这个默认头,这样新文件就可以包含更多信息,例如版权和项目信息。但我们暂时不考虑它。

对于 WebDAVClient.php 文件,请在 ?> 后面输入 Hello World 代码行。这样就可以在 PHP 透视图的 PHP 浏览器视图中查看 Hello World,对文件进行测试。添加的这一行表示现有设置工作正常,可以开始编写代码了。

对于 class_WebDAVClient.php 文件,代码完全放在 ?> 之前,所以 PHP 浏览器视图中无显示。对 class_WebDAVClient.php 作的第一个修改如 清单 2 所示。我们通过这一修改开始编写类代码。

清单 2. 开始编写类代码

<?php 
/* 
 * Created on Dec 17, 2005 
 * 
 * This is the class_WebDAVClient.php source for IBM Tutorial 5 
 * in the open source CMS series. 
 * 
 */ 
class webdavclient { 
 
} 
?> 

所有的类代码都放在 webdavclient 声明的尖括号之间。

关于 WebDAV HTTP 命令的完整列表,请参阅本教程最后的 参考资料 部分。在这份教程中,实现了以下 WebDAV HTTP 命令:

open

ls

mkcol

propfind

get

put

copy

move

delete

options

还实现了两个特定于 Slide 的命令(虽然对于大多数 WebDAV 服务器也适用):

open

ls

其余命令的工作方式或多或少都是类似的,如果学习本教程后您需要编写这些命令,不应存在问题。

第一组方法:open() 和 close()

这一节介绍需在本教程中使用的头两个方法:open() 和 close()。

实现 open() 和 close() 方法

要实现和测试的第一组方法是 open() 方法和它对应的 close() 方法,它们的细节如 清单 3 所示。这些方法直接用 PHP 的套接字方法打开和关闭与 Slide 2.1 WebDAV 服务器的套接字。

函数 open(): 首先发送 open a socket connection 到 _error_log();,然后调用 fsockopen 打开套接字并把 _target 设置到结果。 fsockopen 的参数是局部变量。接下来,把 $this->_target 上的阻塞设置为 true,然后测试 $this->_target 等于 null 还是 error。如果发生了问题,代码把错误记入日志,并返回 false;如果没有问题发生,代码把局部变量 _connection_closed 设置为 false,将成功记入日志,并返回 true。






清单 3. 打开和关闭 WebDAV 服务器套接字的代码

 /** 
 * Opens a socket to a WebDAV server 
 * @return bool true on success. Otherwise false. 
 */ 
function open() { 
  // open a socket 
  $this->_error_log('open a socket connection'); 
  $this->_target = fsockopen ($this->_server, $this- 
 >_port, $this->_error, $this- 
 >_errorstr, $this->_timeout); 
 
  socket_set_blocking($this->_target, true); 
  if (!$this->_target) { 
   $this->_error_log("$this->_errorstr 
 ($this->_error)\n"); 
   return false; 
 } else { 
   $this->_connection_closed = false; 
   $this->_error_log('socket is open: ' . $this->_target); 
   return true; 
 } 
} 
 
/** 
 * Closes the socket. 
 */ 
function close() { 
  $this->_error_log('closing socket ' . $this->_target); 
  $this->_connection_closed = true; 
  fclose($this->_target); 
}         

设置局部变量

在 清单 4 中,设置了局部变量。有些用在 open() 方法中,而有些到后面才会使用。(在后文中使用它们的时候再具体解释)。请把这个代码紧插在 class webdavclient { 代码行之后:

清单 4. 设置局部变量

var $_debug = false; 
var $_target; 
var $_server; 
var $_username = 'root'; 
var $_password; 
var $_protocol = 'HTTP/1.0'; 
var $_port = 8080; 
var $_path ='/files'; 
var $_timeout = 15; 
 
var $_error; 
var $_errorstr; 
var $_agent = 'php class webdavclient $Revision: 1.2 $'; 
var $_crlf = "\r\n"; 
var $_request; 
var $_status; 
var $_parser; 
var $_xmlstr; 
var $_collection; 
var $_list = array(); 
var $_list_element; 
var $_list_element_cdata; 
 
var $_delete = array(); 
var $_delete_element; 
var $_delete_element_cdata; 
 
var $_lock = array(); 
var $_lock_ref; 
var $_lock_rec_cdata; 
 
var $_null = NULL; 
var $_header=''; 
var $_body=''; 
var $_connection_closed = false; 
var $_maxheaderlenth = 1000; 
 

创建局部变量的方法

在 清单 5 中,为这些值创建了一些 set 方法。可能需要在演示页面中做修改。在这个组中,我放了一个构造函数,并把这段代码全都插在局部变量声明之后、open() 方法之前。

清单 5. 为演示页面的值创建 set 方法

/** 
 * Constructor 
 */ 
 function webdavclient() { 
 // noop 
 } 
 
/** 
 * Set the WebDAV server IP address or DNS domain name. 
 * @param string server 
 */ 
function set_server($server) { 
  $this->_server = $server; 
} 
 
/** 
 * Set the TCP port of the Slide WebDAV server. 
 * Default is 8080. 
 * @param int port 
 */ 
function set_port($port) { 
  $this->_port = $port; 
} 
 
/** 
 * Set the user name for authentication 
 * @param string username 
 */ 
function set_username($username) { 
  $this->_username = $username; 
} 
 
/** 
 * Set the password for authentication 
 * @param string pass 
 */ 
function set_password($password) { 
  $this->_password = $password; 
} 
 
/** 
 * Set debug on (1) or off (0). 
 * Produces a lot of debug messages in the Web server's 
 * error log if set to on (1). 
 * The default is off. 
 * @param bool debug 
 */ 
function set_debug($debug) { 
  $this->_debug = $debug; 
} 
 
/** 
 * Set which HTTP protocol will be used. 
 * Value 1 defines that HTTP/1.1 should be used 
 * (Keeps connection to WebDAV server alive.) 
 * Otherwise, HTTP/1.0 will be used. 
 * @param int version 
 */ 
function set_protocol($version) { 
  if ($version == 1) { 
   $this->_protocol = 'HTTP/1.1'; 
  } else { 
   $this->_protocol = 'HTTP/1.0'; 
  } 
  $this->_error_log('HTTP Protocol was set to ' . 
     $this->_protocol); 
} 

测试响应是否来自 WebDAV 服务器

仅仅打开到服务器的套接字是不够的;还需要测试这个服务器是不是 WebDAV 服务器。清单 6 起到了一石二鸟的作用。check_webdav() 方法调用 $this->options() 并看是否有内容返回,如果有内容返回,返回的内容是不是像 WebDAV HTTP 响应。如果是,就返回 true。

清单 6. 判断响应是否来自 WebDAV 服务器

/** 
 * Checks if the server returns a DAV element in the header and when 
 * OPTIONS is supported. 
 * @return bool true if server is webdav server. Otherwise false. 
 */ 
function check_webdav() { 
  $resp = $this->options(); 
  if (!$resp) { 
   return false; 
  } 
  $this->_error_log($resp['header']['DAV']); 
  // check schema 
  if (preg_match('/1,2/', $resp['header']['DAV'])) { 
   return true; 
  } 
  // otherwise return false 
  return false; 
} 
 
/** 
 * Get options from the WebDAV server. 
 * @return false if the server does not return HTTP. 
 */ 
function options() { 
  $this->_unset_header(); 
  $this->_create_request('OPTIONS'); 
  $this->_send(); 
  $this->_get_response(); 
  $response = $this->_process_respond(); 
  // validate the response ... 
  // check http-version 
  if ($response['status']['http-version'] == 'HTTP/1.1' || 
   $response['status']['http-version'] == 'HTTP/1.0') { 
   return $response; 
  } 
  $this->_error_log('Response was not http'); 
  return false; 
} 

显然,WebDAV 需要响应特定于 WebDAV 的请求,例如 options。所以在这里,在 options 中实现了第一个 WebDAV 方法,并用这个方法验证服务器是否为 WebDAV 服务器。

第一个测试:编写演示页面

现在我们已经做好了编写演示页面的准备。通过该页面可以查看效果,执行类并报告类的操作。可用此页面尝试新内容,并测试自己的内容管理应用程序解决方案使用的方法。

连接 WebDAV 服务器

清单 7 显示了使用 webdavclient 类的 PHP 代码,实现了到 WebDAV 服务器的连接,并报告结果。请把这段代码插入到 WebDAVClient.php 源文件的顶部。










清单 7. 头和设置

<?php 
/* 
$Id: WebDAVClient.php,v 1.1 2005/12/10 17:10:18 oliverm Exp $ 
$Author: oliverm $ 
$Date: 2005/12/10 17:10:18 $ 
$Revision: 1.1 $ 
This demonstrates the methods of the webdavclient class. 
 
*/ 
?> 
<html> 
<body> 
<?php 
 
if (!class_exists('webdavclient')) { 
 require('./class_WebDAVClient.php'); 
} 
 
$davclient = new webdavclient(); 
$davclient->set_server('localhost'); 
$davclient->set_port(8080); 
$davclient->set_username('root'); 
$davclient->set_password('MasterP'); 
// use HTTP/1.1 
$davclient->set_protocol(1); 
// enable debugging 
$davclient->set_debug(true); 
 
 
if (!$davclient->open()) { 
 print 'Error: could not open WebDAV connection'; 
 exit; 
} 
 
// check if server supports webdav rfc 2518 
if (!$davclient->check_webdav()) { 
 print 'Error: server does not support webdav or 
 user/password may be wrong'; 
 
 exit; 
} else { 
 print 'Success: server does support webdav 
 and user/password is accepted!'; 
 
 
} 
?> 
</body> 
</html> 
         

现在请确保 Slide 包正在运行。(应当在类似于 http://localhost:8080/slide 这样的 URL 上。)请测试能够通过浏览器、以将在 WebDAVClient.php 中使用的用户名和口令直接连接。了解 Slide 正在运行,而且您能够连接到它之后,请检验在 WebDAVClient.php 中设置了相同的信息并运行它。Eclipse 的 PHP 透视图中的 PHP 浏览器应当工作正常,所以您将看到 Success: server does support WebDAV, and user/password is accepted!。

就象我经常告诉我的程序员的“picollo passo”或“小步走”。在这里,您所做的是连接服务器和运行测试的最少工作。现在,可以在此基础之上构建了。如果这一部分无法正常工作,请查看它哪里出了错。

列出服务器根的内容

下一步是列出 Slide 服务器根的内容。清单 8 显示了类的方法 list (有些人可能喜欢用 ls),这个方法到 Slide 上获取位于 / 的集合的内容清单。

清单 8. ls 方法

/** 
  * Public method ls 
  * 
  * Gets the collection list from the WebDAV server into an 
  * array using PROPFIND 
  * @param string path 
  * @return array list, false on error 
  */ 
  function ls($path) { 
 
   if (trim($path) == '') { 
     $this->_error_log('Missing a path in method ls'); 
     return false; 
   } 
   $this->_path = $path; 
 
   $this->_unset_header(); 
   $this->_create_request('GET'); 
   $this->_send(); 
   $this->_error_log('ls()'); 
   $this->_get_response(); 
   $response = $this->_process_respond(); 
   $this->_error_log('VVVVVVV response[status]\ 
      [http-version]=VVVVVV'); 
   $this->_error_log($response['status']['http-version']); 
   // validate the response ... (only basic validation) 
   // check http-version 
   if ($response['status']['http-version'] == 'HTTP/1.1' || 
     $response['status']['http-version'] == 'HTTP/1.0') { 
     // seems to be http ... proceed 
     $this->_error_log('status::status-code'); 
     $this->_error_log($response['status']['status-code']); 
     // We expect a 200 status code 
     
     if (strcmp($response['status']\ 
      ['status-code'],'200') == 0 ) { 
      $this->_error_log('this->_body='); 
       $this->_error_log($this->_body); 
     
      return $this->_body; 
     } else { 
      $this->_error_log('Missing Content-Type: \ 
          text/xml header in response!!'); 
      return false; 
     } 
   } 
 
   // response was not http 
   $this->_error_log('Error in ls: response was not HTTP'); 
   return false; 
  } 
 

现在,在 WebDAVClient.php 页面中,调用类的 list 方法并显示结果。清单 9 显示了这个页面添加的内容。

清单 9. 列表以测试 ls 方法

$contents = $davclient->ls('/slide/files'); 
?> 
<h1>WebDAVClient Demo:</h1><p> 
Using method webdavclient::ls('/slide/files') 
 to get a listing of dir '/slide/files':<br> 
 
(*Note this snapshot's links are dereferenced and won't work, however 
you can follow this link to the same page... 
 <a href="http://localhost:8080/slide/files/" 
 >/slide/files/</a> 
 
<table summary="ls" border="1"> 
<tr> 
<th>GET /files</th> 
<tr> 
<td> 
<?php 
print $contents 
?> 
</td> 
</tr> 
</table> 

刷新页面。PHP 浏览器现在应当显示如 图 2 所示的内容。

图 2. 新演示页面
开放源码 <a target=CMS 入门,第 5 部分: 为 Jakarta Slide 构建 PHP WebDAV 客户机" border="0" onload="return imgzoom(this,550);" style="cursor:pointer;" onclick="javascript:window.open(this.src);"/>

Slide 用简单的 HTTP 接口处理 GET 命令,Slide servlet 查看普通的旧 GET 并构建带有链接的 HTML 页面。因为要从服务器到服务器,而且这个客户机不是浏览器,所以链接被禁用,并与服务器的上下文相关,所以在从浏览器查看 WebDAVClient.php 时它们不会工作。

清单 10 显示了与其他方法共享的一些工具方法。

清单 10. getInfo 方法

/** 
  * Public method getInfo 
  * 
  * Gets path information from the WebDAV server for one element 
  * @param string path 
  * @return array dirinfo. false on error 
  */ 
  function getInfo($path) { 
 
   // split path by last "/" 
   $path = rtrim($path, "/"); 
   $item = basename($path); 
   $dir = dirname($path); 
 
   $list = $this->list($dir); 
 
   // be sure it is an array 
   if (is_array($list)) { 
     foreach($list as $e) { 
 
      $fullpath = urldecode($e['href']); 
      $filename = basename($fullpath); 
 
      if ($filename == $item && $filename != 
  "" and $fullpath != $dir."/") { 
        return $e; 
      } 
     } 
   } 
   return false; 
  } 
 
 /** 
  * Public method is_file 
  * 
  * Test whether a path points to a file 
  * @param string path 
  * @return bool true or false 
  */ 
  function is_file($path) { 
 
   $item = $this->getInfo($path); 
 
   if ($item === false) { 
     return false; 
   } else { 
     return ($item['resourcetype'] != 'collection'); 
   } 
  } 
 

mkcol、get 和 put 方法

这一节介绍了如何在 WebDAV 客户机中使用 mkcol、get 和 put 方法。

mkcol 方法

接下来添加 mkcol 方法,它在 Slide 仓库中创建集合(即,文件夹或目录)。WebDAV 规范毕竟是 HTTP 的扩展,所以可以预见,HTTP 响应代码是类似的。mkcol HTTP 请求返回的响应码带有一些解释性文本。WebDAV 规范(RFC 2518)把代码分成以下几种:

201(已经创建):集合或结构化资源已经完整创建。

403(禁止):这个错误表示至少出现了以下两种情况之一:1)服务器不允许在其名称空间的指定位置创建集合;2)所请求的统一资源标识符(URI)的父集合存在,但是不接受成员。

405(方法不允许): MKCOL 命令只能在已经删除/不存在的资源上执行。

409(冲突):只在一个或多个中间集合已经创建之后,在请求的 URI 上才能创建某个集合。

415(不支持的媒体类型): 服务器不支持请求的实体类型。

507(存储空间不足): 在这个方法 执行之后,资源没有足够的空间记录资源的状态。

清单 11 显示了 mkcol() 方法,它相当简单。

清单 11. mkcol() 方法

/** 
 * Public method mkcol 
 * 
 * Creates a new collection/folder on a WebDAV server 
 * @param string path 
 * @return int status code received as response 
 */ 
function mkcol($path) { 
  $this->_path = $path; 
  $this->_unset_header(); 
  $this->_create_request('MKCOL'); 
  $this->_send(); 
  $this->_get_response(); 
  $response = $this->_process_respond(); 
  // validate the response ... 
  // check http-version 
  if ($response['status']['http-version'] == 'HTTP/1.1' || 
   $response['status']['http-version'] == 'HTTP/1.0') { 
   /* seems to be http ... to proceed, just return the 
   response status-code */ 
   return $response['status']['status-code']; 
  } 
} 

清单 12 显示了在类的内部使用的一些用来创建部分 WebDAV 请求和响应的专用方法。

清单 12. _header 和其他方法

/** 
  * Private method _header 
  * 
  * extends class var array _request  
  * @param string string 
  * @access private 
  */ 
  function _header($string) { 
   $this->_request[] = $string; 
  } 
 
 /** 
  * Private method _unset_header 
  * 
  * unsets class var array _request  
  * @access private 
  */ 
 
  function _unset_header() { 
   unset($this->_request); 
  } 
 
 /** 
  * Private method _create_request 
  * 
  * creates by using private method _header 
  * an general request header. 
  * @param string method 
  * @access private 
  */ 
  function _create_request($method) { 
   $request = ''; 
   $this->_header(sprintf('%s %s %s', 
 $method, $this->_path, $this->_protocol)); 
 
   $this->_header(sprintf('Host: %s', $this->_server)); 
   $this->_header(sprintf('User-Agent: %s', $this->_agent)); 
   $this->_header(sprintf('Authorization: Basic %s', 
    base64_encode("$this->_username:$this->_password"))); 
 
  } 
 
 /** 
  * Private method _send 
  * 
  * Sends a ready formed HTTP/WebDAV request to 
  * the WebDAV server. 
  * @access private 
  */ 
  function _send() { 
   // check if stream is declared to be open 
   // only logical check we are not sure 
 //if socket is really still open ... 
 
   if ($this->_connection_closed) { 
     // reopen it 
     // be sure to close the open socket. 
     $this->close(); 
     $this->_reopen(); 
   } 
 
   // convert array to string 
   $buffer = implode("\r\n", $this->_request); 
   $buffer .= "\r\n\r\n"; 
   $this->_error_log($buffer); 
   fputs($this->_target, $buffer); 
  } 
 
 /** 
  * Private method _get_response 
  * 
  * Read the response of the WebDAV server. 
  * Stores data into class vars _header for the header data and 
  * _body for the rest of the response. 
  * This routine is the weakest part of this class, because it 
  * depends how PHP handles a socket stream. 
  * If the stream is blocked for some reason, PHP is blocked 
  * as well. 
  * @access private 
  */ 
  function _get_response() { 
   //see the full source for this and other methods 
   //in the downloadable .zip file for this tutorial. 
  } 
 

get 方法

get() 方法如 清单 13 所示,用来返回一个缓冲区,里面添加了内容服务器上的资源 —— 即文件。Slide 检测 get() 得到的是资源而不是集合。

清单 13. get() 方法

/** 
  * Public method get 
  * 
  * Gets a file from a WebDAV collection. 
  * @param string path, string &buffer 
  * @return status code and &$buffer (by reference) with 
  * response data from server on success. False on error. 
  */ 
  function get($path, &$buffer) { 
   $this->_path = $path; 
   $this->_unset_header(); 
   $this->_create_request('GET'); 
   $this->_send(); 
   $this->_get_response(); 
   $response = $this->_process_respond(); 
 
   // validate the response 
   // check http-version 
   if ($response['status']['http-version'] == 'HTTP/1.1' || 
     $response['status']['http-version'] == 'HTTP/1.0') { 
     // seems to be http ... proceed 
     // We expect a 200 code 
     if ($response['status']['status-code'] == 200 ) { 
      $this->_error_log('returning buffer with ' . 
  strlen($response['body']) . ' bytes.'); 
      $buffer = $response['body']; 
     } 
     return $response['status']['status-code']; 
    } 
    // ups: no http status was returned ? 
    return false; 
  } 

清单 14 有两个重要内容。_get_response() 方法处理来自 WebDAV 服务器的响应,检测响应类型(简单头、XML 响应体或字节内容),并把它全部分解成可用的组件。

清单 14. get_response() 方法

/** 
  * Private method _get_response 
  * 
  * Read the response of the WebDAV server. 
  * Stores data into class vars _header for the header data and 
  * _body for the rest of the response. 
  * This routine is not bulletproof, because it depends on how PHP 
  * handles a socket stream. 
  * If the stream is blocked for some reason, 
  * PHP is blocked as well. 
  * @access private 
  */ 
  function _get_response() { 
   $this->_error_log('_get_response()'); 
    
   $buffer = ''; 
   $header = ''; 
   // attention: do not make max_chunk_size too big.... 
   $max_chunk_size = 8192; 
   // be sure we got a open resource 
   if (! $this->_target) { 
     $this->_error_log('socket is not open. 
 Can not process response'); 
 
     return false; 
   } 
 
   // read stream one byte by another until http header ends 
   $i = 0; 
   do { 
     $header.=fread($this->_target,1); 
     $i++; 
   } while (!preg_match('/\\r\\n\\r\\n$/',$header) 
 && $i < $this->_maxheaderlenth); 
 
   $this->_error_log('************ response 
 $header***********'); 
 
   $this->_error_log($header); 
 
   if (preg_match('/Connection: close\\r\\n/', $header)) { 
     // This says that the server will close 
  //connection at the end of this stream. 
     // Therefore we need to reopen the socket 
     //before sending the next request... 
     $this->_error_log('Connection: close found'); 
     $this->_connection_closed = true; 
   } 
   // check how to get the data on socket stream 
   // chunked or content-length (HTTP/1.1) or 
   // one block until feof is received (HTTP/1.0) 
    
 //download the source to see the rest of this method. 
  } 
 
 
 
  // -------------------------------------------------------------------------- 
  // private method _process_respond ... 
  // analyze the response from server 
  //and divide into header and body part 
  // returns an array filled with components 
 /** 
  * Private method _process_respond 
  * 
  * Processes the WebDAV server response, detects its 
  * components (header, body), 
  * and returns data array structure. 
  * @return array ret_struct 
  * @access private 
  */ 
  function _process_respond() { 
   $lines = explode("\r\n", $this->_header); 
   $header_done = false; 
   // $this->_error_log($this->_buffer); 
   // First line should be an http status line 
   //(see http://www.w3.org/Protocols/rfc2616/ 
 // rfc2616-sec6.html#sec6) 
 
   // Format is: HTTP-Version SP Status-Code SP Reason-Phrase CRLF 
   list($ret_struct['status']['http-version'], 
       $ret_struct['status']['status-code'], 
       $ret_struct['status']['reason-phrase']) = 
  explode(' ', $lines[0],3); 
 
 
   // print "HTTP Version: '$http_version' Status-Code: 
 // '$status_code' Reason Phrase: '$reason_phrase'<br>"; 
   // get the response header fields 
   // See http://www.w3.org/Protocols/rfc2616/ 
 // rfc2616-sec6.html#sec6 
 
   for($i=1; $i<count($lines); $i++) { 
     if (rtrim($lines[$i]) == '' && !$header_done) { 
      $header_done = true; 
      // print "--- response header end ---<br>"; 
 
     } 
     if (!$header_done ) { 
      // store all found headers in array ... 
      list($fieldname, $fieldvalue) = explode(':', $lines[$i]); 
      // check if this header was already set 
 //(apache 2.0 webdav module does this....). 
      // If so we add the value to the end of 
 //the fieldvalue, separated by a comma... 
      if (! $ret_struct['header'][$fieldname]) { 
        $ret_struct['header'][$fieldname] = trim($fieldvalue); 
      } else { 
       $ret_struct['header'][$fieldname] .= ',' . 
  trim($fieldvalue); 
 
      } 
     } 
   } 
   // print 'string len of response_body:'. 
 //  strlen($response_body); 
   // print '[' . htmlentities($response_body) . ']'; 
   $ret_struct['body'] = $this->_body; 
   return $ret_struct; 
  } 
         

在 WebDAVClient.php 中尝试所有方法

WebDAVClient.php 页面应当尝试 mkcol、 get 和 put ,以及 copy、move 和 delete 的所有方法。清单 15 显示了执行这些方法的其余代码。在每种情况下,方法都接受一个或两个参数,并返回一个状态码。对于 PHP 程序来说,这段代码并不精妙,但我们目的是尝试练习 Client 类的方法。

清单 15. mkcol() 方法和移动一些文件

Create a new collection (Folder) using method webdavclient::mkcol() 
<?php 
$perm_folder = '/slide/files/permanent/'; 
print '<br>creating collection ' . 
 $perm_folder .' ...<br>'; 
 
$http_status = $davclient->mkcol($perm_folder); 
print 'Slide WebDAV server returns ' . $http_status . '<br>'; 
 
$test_folder = '/slide/files/IBM_Tutorial_5'; 
print '<br>creating collection ' . 
 $test_folder .' ...<br>'; 
 
$http_status = $davclient->mkcol($test_folder); 
print 'Slide WebDAV server returns ' . $http_status . '<br>'; 
 
print 'removing collection just created using 
 method webdavclient::delete ...<br>'; 
 
$http_status_array = $davclient->delete($test_folder); 
print 'Slide WebDAV server returns ' . 
 $http_status_array['status'] . '<br>'; 
 
print 'see what\'s happening when we try to 
 delete the same nonexistent collection again....<br>'; 
 
$http_status_array = $davclient->delete($test_folder); 
print 'WebDAV server returns '. 
 $http_status_array['status'] . '<br>'; 
 
 
print 'see what\'s happening when we try to 
 delete an existing locked collection....<br>'; 
 
$http_status_array = $davclient->delete('/packages.txt'); 
print 'WebDAV server returns ' . 
 $http_status_array['status'] . '<br>'; 
 
print 'create a second collection ...' . 
 $test_folder . '<br>'; 
$http_status = $davclient->mkcol($test_folder); 
print 'WebDAV server returns ' . $http_status . '<br>'; 
 
// put a file to WebDAV collection 
$filename = '/temp/test.txt'; 
print 'put the file ' . $filename . ' using 
 webdavclient::put into collection...<br>'; 
 
$handle = fopen ($filename, 'r'); 
$contents = fread ($handle, filesize ($filename)); 
fclose ($handle); 
$target_path = $test_folder . '/test.txt'; 
$http_status = $davclient->put($target_path,$contents); 
print 'WebDAV server returns ' . $http_status .'<br>'; 
 
$filename = '/temp/test.jpg'; 
print 'Test second put method...<br>'; 
$target_path = $test_folder . '/test.jpg'; 
$http_status = $davclient->putf($target_path, $filename); 
print 'WebDAV server returns ' . $http_status . '<br>'; 
 
print 'get file just put'; 
$http_status = $davclient->get($test_folder . 
 '/test.jpg', $buffer); 
print 'WebDAV server returns ' . $http_status . '. 
 Buffer is filled with ' . strlen($buffer). 
 ' Bytes.<br>'; 
 
 
 
print 'use method webdavclient::copy to create a 
 copy of file ' . $target_path . ' the 
 WebDAV server<br>'; 
 
$new_copy_target = '/test[2].jpg'; 
$http_status = $davclient->copyf( 
 $target_path, $new_copy_target, true); 
 
print 'WebDAV server returns ' . $http_status . '<br>'; 
 
print 'use method webdavclient::copy to create 
 a copy of collection ' . $test_folder . 
 ' the WebDAV server<br>'; 
 
$new_copy_target = '/copy_of_IBM_Tutorial_5'; 
$http_status = $davclient->copyc($test_folder, 
 $new_copy_target, true); 
 
print 'WebDAV server returns ' . $http_status . '<br>'; 
 
print 'move/rename a file in a collection<br>'; 
$new_target_path = $test_folder . '/test_renamed.jpg'; 
$http_status = $davclient->move($target_path, 
 $new_target_path, true); 
 
print 'WebDAV server returns ' . $http_status . '<br>'; 
 
print 'move/rename a collection<br>'; 
$new_target_folder = $test_folder + '_renamed'; 
$http_status = $davclient->move($test_folder, 
 $new_target_folder, true); 
 
print 'Slide WebDAV server returns ' .  
 $http_status . '<br>'; 
 
print 'remove/delete the moved collection ' . 
 $new_target_folder . '<br>'; 
 
$http_status_array = $davclient-> 
delete($new_target_folder); 
print 'Slide WebDAV server returns ' . 
 $http_status_array['status'] . '<br>'; 
 
 
$davclient->close(); 
flush(); 
?> 
</body> 
<html> 
        

上载到 Slide 服务器

现在要在 WebDAVClient.php 中用最后一种主要方法把文件 put 或上载到 Slide 服务器。

使用 PUT 方法上载文件

请注意在完整的源代码中,我创建了两个方法,用不同的方式处理文件和集合。使用 putf() 和 getf() 方法时,我想处理文件名而不是缓冲(字节数组),但是其他都是相同的。我只是用文件名创建读或写的流。

清单 16 中的 put() 方法处理称为 $data 的缓冲区。这个方法相当简单,而且套接字的处理方式也与文件资源非常类似,在 send() 创建了请求并把请求返回 _target 之后,用 fputs() 处理 _target。







清单 16. put() 方法

/** 
  * Public method put 
  * 
  * Puts a file into a collection. 
  *  Data is put as one chunk! 
  * @param string path, string data 
  * @return int status-code read from webdavserver. False on error. 
  */ 
  function put($path, $data ) { 
   $this->_path = $path; 
   $this->_unset_header(); 
   $this->_create_request('PUT'); 
   // add more needed header information ... 
   $this->_header('Content-length: ' . strlen($data)); 
   $this->_header('Content-type: application/octet-stream'); 
   // send header 
   $this->_send(); 
   // send the rest (data) 
   fputs($this->_target, $data); 
   $this->_get_response(); 
   $response = $this->_process_respond(); 
 
   // validate the response 
   // check http-version 
   if ($response['status']['http-version'] == 'HTTP/1.1' || 
     $response['status']['http-version'] == 'HTTP/1.0') { 
     // seems to be http ... proceed 
     // We expect a 200 or 204 status code 
     // see rfc 2068 - 9.6 PUT... 
     // print 'http ok<br>'; 
     return $response['status']['status-code']; 
    } 
    // ups: no http status was returned ? 
    return false; 
  } 
         

用 copy 和 move 方法进行文件传输

清单 17 显示了在 WebDAVClient.php 演示中使用的最后一种主要方法:copy(两种风格)和 move。至此,您应已注意到,所有方法的元语都是类似的:方法接受一组 URI 字符串,完成某些操作,然后返回一个状态码。

清单 17. copy 和 move 方法

/** 
  * Public method copyf 
  * 
  * Copy a file on the WebDAV server 
  * Duplicates a file on the WebDAV server (serverside). 
  * All the work is done on the WebDAV server. If you 
 * set param overwrite as true, 
 * the target will be overwritten. 
 * 
 * @param string src_path, string dest_path, bool overwrite 
 * @return int status code (look at rfc 2518). false on error. 
 */ 
 function copyf($src_path, $dst_path, $overwrite) { 
 $this->_path = $src_path; 
 $this->_unset_header(); 
 $this->_create_request('COPY'); 
 $this->_header(sprintf('Destination: http://%s%s', 
 $this->_server, $dst_path)); 
 
 if ($overwrite) { 
  $this->_header('Overwrite: T'); 
 } else { 
  $this->_header('Overwrite: F'); 
 } 
 $this->_header(''); 
 $this->_send(); 
 $this->_get_response(); 
 $response = $this->_process_respond(); 
 // validate the response ... 
 // check http-version 
 if ($response['status']['http-version'] == 'HTTP/1.1' || 
  $response['status']['http-version'] == 'HTTP/1.0') { 
  /* seems to be http ... proceed */ 
  return $response['status']['status-code']; 
 } 
 return false; 
 } 
 
/** 
 * Public method copyc 
 * 
 * Copy a collection on the WebDAV server 
 * Duplicates a collection on the WebDAV server (serverside). 
 * All work is done on the WebDAV server. If you set param 
 * overwrite as true, 
 * the target will be overwritten. 
 * 
 * @param string src_path, string dest_path, bool overwrite 
 * @return int status code (look at rfc 2518). false on error. 
 */ 
 function copyc($src_path, $dst_path, $overwrite) { 
 $this->_path = $src_path; 
 $this->_unset_header(); 
 $this->_create_request('COPY'); 
 $this->_header(sprintf('Destination: 
 http://%s%s', $this->_server, $dst_path)); 
 
 $this->_header('Depth: Infinity'); 
 
 $xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n"; 
 $xml .= "<d:propertybehavior xmlns:d=\"DAV:\">\r\n"; 
 $xml .= " <d:keepalive>*</d:keepalive>\r\n"; 
 $xml .= "</d:propertybehavior>\r\n"; 
 
 $this->_header('Content-length: ' . strlen($xml)); 
 $this->_header('Content-type: text/xml'); 
 $this->_send(); 
 // send also xml 
 fputs($this->_target, $xml); 
 $this->_get_response(); 
 $response = $this->_process_respond(); 
 // validate the response ... 
 // check http-version 
 if ($response['status']['http-version'] == 'HTTP/1.1' || 
  $response['status']['http-version'] == 'HTTP/1.0') { 
  /* seems to be http ... proceed */ 
  return $response['status']['status-code']; 
 } 
 return false; 
 } 
 
 /** 
 * Public method move 
 * 
 * Move a file or collection on the WebDAV server (serverside) 
 * If you set param overwrite as true, the target 
 * will be overwritten. 
 * 
 * @param string src_path, string dest_path, bool overwrite 
 * @return int status code (look at rfc 2518). false on error. 
 */ 
 // ------------------------------------------------------------- 
 // public method move 
 // move/rename a file/collection on webdav server 
 function move($src_path,$dst_path, $overwrite) { 
 
 $this->_path = $src_path; 
 $this->_unset_header(); 
 
 $this->_create_request('MOVE'); 
 $this->_header(sprintf('Destination: 
  http://%s%s', $this->_server, $dst_path)); 
 
 if ($overwrite) { 
  $this->_header('Overwrite: T'); 
 } else { 
  $this->_header('Overwrite: F'); 
 } 
 $this->_header(''); 
 
 $this->_send(); 
 $this->_get_response(); 
 $response = $this->_process_respond(); 
 // validate the response ... 
 // check http-version 
 if ($response['status']['http-version'] == 'HTTP/1.1' || 
  $response['status']['http-version'] == 'HTTP/1.0') { 
  /* seems to be http ... proceed */ 
  return $response['status']['status-code']; 
 } 
 return false; 
 } 
 

全部状态码如下:

201(已创建):源资源已经成功删除,新资源已经在目标创建。

204(没有内容):源资源已成功移动到已有目标资源上。

403(禁止):源和目标 URI 是相同的。

409(冲突):资源不能在目标上创建 —— 除非创建了一个或多个中间资源。

412(预处理失败):服务器不能维护 propertybehavior XML 元素列出的属性的“活动性”,或 Overwrite 头为 F,而目标源的状态是非 NULL。

423(锁定):源或目标资源被锁定。

502(错误网关):当目标在在另一台服务器上,而目标服务器拒绝接受资源时,可能出现这个错误。

201(已经创建):集合或结构化资源已经完全创建。

403(禁止): 这个错误表示至少以下一种情况:1)服务器不允许在其名称空间的指定位置创建集合;2)请求的 URI 的父集合存在,但是不接受成员。

405(方法不允许): mkcol 方法只能在已经删除或不存在的资源上执行。

409(冲突):在请求的 URI 上不能创建集合 —— 除非创建了一个或多个中间集合。

415(不支持的媒体类型):服务器不支持主体的请求类型。

507(存储空间不足):方法执行之后,资源没有足够的空间记录资源的状态。

未来

在这一节中,您将了解可用刚创建的 PHPWebDAV 客户机实现哪些目的。

WebDAV 客户机的未来

那么接下来该做什么?用这个 PHP WebDAVClient 能做什么?有什么 PHP 应用程序需要保存文件么?该怎么保存不同版本的文件呢?如何保存带有元数据属性的文件呢?想不想扫描到来的发票,用光学字符识别设备(OCR)来扫描它,根据扫描创建发票,并把它们一起保存在 XML 中呢?

我们只是浅显地介绍了 Slide 能实现的目的,这个客户机应当随着时间而成长,满足您的需求。Slide 在保存和检索 XML 文档方面表现出色,可以把元数据属性保存为 XML 并把它们很好地绑定到资源或相等的集合上。

WebDAVClient 类最明显的应用就是用您了解的语言把 PHP 内容管理构建到 Web 站点。可以运行 Slide/Tomcat 包而完全不涉及 Java。利用 PHP 技能就可以构建特性全面的 CMS,而且 还能拥有与 Microsoft Office 或其他 WebDAV 敏感的应用程序集成的 WebDAV 能力。

结束语

PHP 可以通过套接字连接和 HTTP 与任何 WebDAV 服务器对话。了解 WebDAV 规范后,在本教程中编写的 WebDAVClient 类就能简化构建您自己的 PHP 应用程序时要编写的其他代码。可以是需要保存和检索资源资源的任何 PHP 应用程序。这些资源不一定是充满页面和图片的站点意义上的内容 —— 但可以是。我敢确定,您希望以这个客户机类为起点,进一步展开学习,扩展它的一些方法,添加一些自己的方法。

在本系列的下一篇教程中,我们将使用另一种开放源码语言与 Slide 相配合:Python。Python 在某些环境下独具优势,没有理由不让 Python 开发人员与 PHP 开发人员或 Java 开发人员具备相同的能力,以利用 Jakarta Slide 或其他任何 WebDAV 服务器。那么,咱们下回见!

下载

描述名字大小下载方法
WebDAV client class for this tutorialclass_WebDAVClient.zip10KBHTTP
Display page for exercising the class fileWebDAVClient.zip12KBHTTP
Test file for this tutorialTest.zip1KBHTTP

Tags:开放 源码 CMS

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接