PHP技巧:如何将相对URL转换为绝对URL

・19 分钟阅读

技术:PHP 4+

绝对URL是完整的,可用于下载网站文件,但是,网页通常包含不完整的相对URL,缺少部分,如"http "或主机名,或文件路径的第一部分,需要通过从基本绝对URL复制它们来填充这些部分,本文展示了如何和包含代码来实现它。

介绍

一个类似于"http://example.com/index.htm "的绝对URL告诉web浏览器哪些文件获得(index.htm "" ),从哪里获取(来自example.com "" ),如何获取(通过http网络服务器),但是,像logo.png这样的相对URL缺少重要部分,如http,主机名和路径的第一部分,没有这些部分,URL就不能用来获取文件。

使用相对URL,你必须通过从另一个URL复制它们来填充缺失的部分,这个URL或基本URL必须是绝对的,它通常是包含相对URL的网页的URL ,基本URL也可以由页面内的标记设置,通过服务器响应header中的Content-Location字段和(很少)特殊的标记。

URL规范定义了一个"absolutize "算法来组合绝对基URL和相对URL来创建新的绝对URL ,本文介绍了算法的步骤,并在PHP中实现。

下面的代码需要来自一条辅助文章的split_url( )join_url( )函数:

PHP提示:如何解析和生成URLs , 将URL分解成它们的组件部分,然后将这些部分重新组装成一个完整的URL 。

代码

让我们先直接进入代码,后续部分中的解释。

下载url_to_absolute.zip

函数的url_to_absolute( )参数包括绝对基URL和相对URL ,相对URL的缺少部分从基URL复制,以形成函数返回的一个新的绝对URL ,如果两个URL不能解析,或者基URL不是绝对的,则返回FALSE

functionurl_to_absolute($baseUrl,$relativeUrl){// If relative URL has a scheme, clean path and return.$r=split_url($relativeUrl);if($r===FALSE)returnFALSE;if(!empty($r['scheme'])){if(!empty($r['path'])&&$r['path'][0]=='/')$r['path']=url_remove_dot_segments($r['path']);returnjoin_url($r);} 
 // Make sure the base URL is absolute.$b=split_url($baseUrl);if($b===FALSE||empty($b['scheme'])||empty($b['host']))returnFALSE;$r['scheme']=$b['scheme']; 
 // If relative URL has an authority, clean path and return.if(isset($r['host'])){if(!empty($r['path']))$r['path']=url_remove_dot_segments($r['path']);returnjoin_url($r);}unset($r['port']);unset($r['user']);unset($r['pass']); 
 // Copy base authority.$r['host']=$b['host'];if(isset($b['port']))$r['port']=$b['port'];if(isset($b['user']))$r['user']=$b['user'];if(isset($b['pass']))$r['pass']=$b['pass']; 
 // If relative URL has no path, use base pathif(empty($r['path'])){if(!empty($b['path']))$r['path']=$b['path'];if(!isset($r['query'])&&isset($b['query']))$r['query']=$b['query'];returnjoin_url($r);} 
 // If relative URL path doesn't start with /, merge with base pathif($r['path'][0]!='/'){$base=mb_strrchr($b['path'],'/',TRUE,'UTF-8');if($base===FALSE)$base='';$r['path']=$base.'/'.$r['path'];}$r['path']=url_remove_dot_segments($r['path']);returnjoin_url($r);}

url_remove_dot_segments(函数参数的唯一值是筛选的路径,返回筛选的路径。

functionurl_remove_dot_segments($path){// multi-byte character explode$inSegs=preg_split('!/!u',$path);$outSegs=array();foreach($inSegsas$seg){if($seg==''||$seg=='.')continue;if($seg=='..')array_pop($outSegs);elsearray_push($outSegs,$seg);}$outPath=implode('/',$outSegs);if($path[0]=='/')$outPath='/'.$outPath;// compare last multi-byte character against '/'if($outPath!='/'&&(mb_strlen($path)-1)==mb_strrpos($path,'/','UTF-8'))$outPath.='/';return$outPath;}

范例

合并基本URL和相对URL :

$newUrl=url_to_absolute("http://example.com/products/index.htm","./product.png");print("$newUrln");

打印:

http://example.com/products/product.png

从网页中提取网址,并且将每个网址转换为绝对网址(请参阅有关如何从网页中为extract_html_urls提取网址的文章)

// Get the web page text$text=file_get_contents($baseUrl);// Extract URLs and convert to a single list$groupedUrls=extract_html_urls($text);$pageUrls=array();foreach($urlsas$element_entry)foreach($element_entryas$attribute_entry)$pageUrls=array_merge($pageUrls,$attribute_entry);// Convert each URL to absolute$n=count($pageUrls);for($i=0;$i<$n;$i++)$pageUrls[$i]=url_to_absolute($baseUrl,$pageUrls[$i]);

说明

我们熟悉的URL格式由Internet Engineering Task Force在规范 RFC3986中正式定义,就像这个(括号内的部分是可选的):

[scheme ":"] scheme-specific-part ["?"query ]["#"fragment ]

例如在此URL中:

http://example.com/products/index.htm?sku=1234#section42

"http "是方案,"//example.com/products/index.htm "是scheme-specific-part,"sku=1234 "是查询,"section42 "是fragment 。

Internet分配的数字机构(IANA )有60个不同的URL方案,包括"http ","ftp ","file ",","

这个scheme-specific-part只是一个特定于—的方案,不同的方案期望不同的信息,例如,"http "方案的文件路径,"mailto"方案的电子邮件地址。

URL的查询部分通常包含数据库查询的参数,格式没有标准化,因此网站可以根据需要定义查询。

URL末尾的fragment通常是内容部分的名称,对于网页,这是页上锚的名称,它通常用来标记页面的各个部分。

所有方案可分为两种类型: 分层和非分层(也称为不透明),方案的分层scheme-specific-part包含一个通过斜线分隔的一系列单词的路径,对于诸如http http这样的方案,此路径通常选择一个文件,比如,/products/index.htm ",但是,非分层方案的URL包含其他信息,例如,"mailto "方案的电子邮件地址。

对于本文,我们只关心"http ","ftp "和"file "等层次结构,非分层方案不能具有相对URL。

因此,对于分层架构,scheme-specific-part包含一条路径,前面有一个这样的权限:

[ scheme ":"] ["//" authority ] [ path ] ["?" query ] ["#" fragment ]

部分的权限格式为:

[ user [":" pass ] "@"] host [":" port ]

权限包括主机名或IP地址(v4或v6 ),可以选择,主机后面有一个端口号(80用于网络服务器),一些方案支持前面的用户名和密码,但是,这是罕见的(而且在url中包含密码也不是很安全)。

绝对URL总是有一个方案,它通常有一个权限和路径,相对URL没有方案,它可能丢失了一些URL的其他部分或所有其他部分,如果相对URL的路径不以斜线开头,那么它也丢失了路径的第一部分。

当然,本文的重点是如何填充相对URL的缺失部分,使它成为绝对URL 。

绝对化一个相对url

如果缺少相对URL的部分,RFC3986规范将解释如何从绝对基URL复制它们,通常,基本URL是包含相对URL的网页的URL ,有几种其他方法可以获取基本URL :



  • 在下载页面时,在Web服务器的HTTP标头中:
    • Content-Location字段包含基本URL ,
  • 在网页的网页部分:
    • 标记字段的可选href包含基本URL ,
    • 标记属性的可选http-equiv可能包含包含基本URL的Content-Location字段,
  • 在网页的网页正文中:
    • 一个 tag's codebase attribute只能包含该applet的基本URL。

      依据相对URL的URL规范, "绝对化"算法执行以下操作:

      • 将缺少的部分复制到相对URL ,
      • 如果需要,连接基础和相对URL路径,
      • 删除所有的路径段,
      • 折叠所有".. "路径段,

      这相当简单,但是,有一些需要注意的事项:

      • 要复制和合并URL部分,首先需要分割基础和相对URL,PHP标准 parse_url( ) 函数可以这样做,但是,它在复杂和相对URLs方面存在问题,相反在上使用split_url( )函数来解析和构建URLs
      • 分割URLs后,你必须将百分比编码扩展到实际字符,例如将%20转换为空格,这必须在分割URL之后,完成,这样,特殊URL字符的百分比编码就可以,@ : ? # 不要混淆URL分割,如果你使用我的split_url( )函数,解码是为你做的,如果你使用PHP的parse_url( ),就需要使用PHP rawurldecode( )标准解码主机,用户,通道,路径,查询和fragment部件,不使用类似的PHP urldecode( ),,它不遵循URL规范,可以对某些URLs进行纠错,
      • 复制缺少的部分时,永远不会复制URL的基fragment ,只有当相对URL没有路径时,URL部分的基本查询才是副本,
      • 扩展百分比编码后的URL可能包含多字节UTF-8字符(请参见RFC2718 ),URL部分的字符串处理必须使用PHP多字节字符识别函数的,这些都以mb "开头,例如此代码使用 mb_strrchr( ) 查找路径的子字符串到最后一个斜杠,当包含"u "模式修改器时,标准preg_*函数还支持UTF-8,
      • 在你获得所有正确的URL部分之后,你必须对特殊字符进行编码,只有一个URL的部分可以有百分比编码字符,因此你必须在重组URL之前进行此操作,对主机,用户,传递,路径,查询和fragment部件进行编码,但是,不要触摸方案和端口,此外,只有主机部分是名称,而不是IPv4或IPv6地址时,才对它进行编码,一定要使用PHP rawurlencode( ) 但是,不是不正确的urlencode( ),它不遵循URL规范,
      • 最后,将这些部分重新连接到一个完整,如果你在我关于的文章中使用了join_url( )函数来解析和构建URLs,那么还会为你处理百分比编码,

      下面是算法的步骤:

      步骤1:将相对URL拆分为关联数组的关联数组。

      $r=split_url($relativeUrl);if($r==FALSE)returnFALSE;

      步骤2:检查相对URL是否已经是绝对的,如果是,请使用本文后面讨论的url_remove_dot_segments( )函数更新它路径,以删除 "" 和",然后重新构建URL并返回它。

      if(!empty($r['scheme'])){if(!empty($r['path'])&&$r['path'][0]=='/')$r['path']=url_remove_dot_segments($r['path']);returnjoin_url($r);}

      步骤3:将基本URL拆分为它部分,并且确保它是绝对(它必须至少有一个方案和主机),如果是绝对的,将它方案复制到相对URL 。

      $b=split_url($baseUrl);if($b==FALSE ||empty($b['scheme'])||empty($b['host']))returnFALSE;$r['scheme']=$b['scheme'];

      步骤4:检查相对URL是否有主机部分,如果是,那么相对URL的其余部分就完成了,没有什么可以从基本网址复制,更新URL的相对路径以删除". "和".. ",然后重新生成URL,并且返回它。

      if(!empty($r['host'])){if(!empty($r['path']))$r['path']=url_remove_dot_segments($r['path']);returnjoin_url($r);}

      步骤5:将缺少的权限部分从基URL复制到相对URL 。

      $r['host']=$b['host'];if( isset($b['port']))$r['port']=$b['port'];if( isset($b['user']))$r['user']=$b['user'];if( isset($b['pass']))$r['pass']=$b['pass'];

      步骤6:如果相对URL没有路径(稀有),那么使用URL的基本路径和查询,因为URL的基本路径已经是绝对的,所以,应该已经有"."和"..",删除它,因此,重新构建URL并返回它。

      if(empty($r['path'])){if(!empty($b['path']))$r['path']=$b['path'];if( !isset($r['query'])&&isset($b['query']))$r['query']=$b['query'];returnjoin_url($r);}

      步骤7:如果URL的相对路径没有以斜杠开头,那么将URL (到最后一个斜杠)的基本路径的第一部分与相对URLs路径合并,注意mb_strrchr( )用于多字节字符串处理。

      if($r['path'][0]!='/'){ $base=mb_strrchr($b['path'],'/',TRUE,'ISO-8859-1');if($base===FALSE)$base='';$r['path']=$base.'/'.$r['path'];}

      步骤8:更新移除 "" 和 "" "重新生成,并且返回的路径。

      $r['path']=url_remove_dot_segments($r['path']);returnjoin_url($r);

      删除点线段

      路径是由(通常文件夹名称)分隔的一系列段生成的,名为"."和".. "的段具有特殊含义,如果将文件路径视为一系列向下的文件夹,则:

      • a"."指"待在这里",
      • "..""段意味着后退一个文件夹"

      A"."总是多余的,可以安全地删除,路径"/products/./index.htm "与/products/index.htm "相同。

      一个".. "可以在它之前被删除,路径"/products/../logo.png "和/logo.png "是等价的。

      URL规范解释了如何用字符扫描路径字符来删除. "" 和"..",你可以更简单地将路径拆分为斜线段中的段,并且通过段线段扫描路径段,然后,通过在段之间添加斜线来重新组合路径。

      点段删除必须小心处理UTF-8中多字节字符串,例如,要将路径分割成斜线之间的一段线段,就需要使用,explode( ) 但是,对于多字节字符(implode()是)不安全,使用preg_split( )与"u "模式修饰符一起使用。

      步骤1:在每个"/"分解路径以创建路径段的数组。

      $inSegs=preg_split('!/!u',$path);

      步骤2:循环遍历各个段,将非圆点段推送到堆栈上,跳过".."段,并且在".. "段上弹出堆栈以删除上一段。

      $outSegs=array();foreach($inSegsas$seg){ if ($seg==''||$seg=='.')continue;if($seg=='..')array_pop($outSegs);elsearray_push($outSegs,$seg);}

      步骤3:通过在每个段之间添加一个"/"来创建新路径来分解段堆栈。

      $outPath=implode('/',$outSegs);

      步骤4:如果原始路径以"/"开头或结束,将该斜杠放回新路径,要获取多字节字符串的最后一个字符,我们不能安全地使用$path[strlen($path)-1] ,相反,搜索最后一个"/",看看它是否在最后。

      if($path[0]=='/')$outPath='/'.$outPath;if($outPath!='/'&&(mb_strlen($path)-1)==mb_strrpos($path,'/','UTF-8'))$outPath.='/';return$outPath;

      下载

Haojinghui profile image