如何解析网页 URL?

19

我需要能够将URL拆分成不同的段落。以这个路径为例:

http://login:password@somehost.somedomain.com:8080/some_path/something_else.html?param1=val&param2=val#nose
\__/   \___/ \______/ \_____________________/ \__/\____________________________/ \___________________/ \__/
 |       |      |               |               |                |                        |              |
Scheme Username Password       Host            Port             Path                    Query         Fragment

这应该分解如下:

Protocol: HTTP
Username: login
Password: password
Host: somehost.somedomain.com
Port: 8080
Path Info: /some_path/something_else.html
Query String: param1=val&param2=val

我怎样才能在Delphi中实现这个功能?是否有现成的工具可以帮我拆分它?如果没有,那么我该如何解析所有可能的格式?这是在假设可能会使用不同的协议,比如HTTPS或RTSP。


我希望出于我们所有人的利益,密码不是明文存储的。 - eggy
@eggy 从技术上讲,有些系统就是这样进行身份验证的。是否要求加密取决于服务器实现。 - Jerry Dodge
补充一下,我注意到这样的Web服务器实际上是硬件API,例如交换机/路由器、IP监控摄像头、VOIP电话等。 - Jerry Dodge
2个回答

39

XE2随带了Indy,其中有一个TIdURI类,用于此目的,例如:

uses
  ..., IdURI;

var
  URI: TIdURI;

URI := TIdURI.Create('http://login:password@somehost.somedomain.com:8080/some_path/something_else.html?param1=val&param2=val');
try
  // Protocol = URI.Protocol
  // Username = URI.Username
  // Password = URI.Password
  // Host = URI.Host
  // Port = URI.Port
  // Path = URI.Path
  // Query = URI.Params
finally
  URI.Free;
end;

+1 更好的是,当某些东西已经封装并在语言中实现时,这通常是一个优势 :) - Jerry Dodge
2
Indy不是Delphi语言的一部分。它只是一个预打包的第三方库。但至少URI解析没有外部依赖,因为它是用纯Delphi代码实现的。 - Remy Lebeau
我所指的是在IDE中可用。当然,Indy不是Delphi的一部分,这就是为什么它是Indy :) 我可能最终会使用这个解决方案,因为它很容易使用。 - Jerry Dodge
等等,所以如果这个Indy方法不依赖于任何东西并且自己解析所有内容,那么我认为这可能是适当的解决方案。我接受了这个解决方案,因为它在技术上只需要Indy的存在,而Indy几乎包含在所有版本的Delphi中。 - Jerry Dodge

23
你可以使用InternetCrackUrl方法。
尝试这个简单的。
{$APPTYPE CONSOLE}

uses
  Windows,
  SysUtils,
  WinInet;

procedure ParseURL(const lpszUrl: string);
var
  lpszScheme      : array[0..INTERNET_MAX_SCHEME_LENGTH - 1] of Char;
  lpszHostName    : array[0..INTERNET_MAX_HOST_NAME_LENGTH - 1] of Char;
  lpszUserName    : array[0..INTERNET_MAX_USER_NAME_LENGTH - 1] of Char;
  lpszPassword    : array[0..INTERNET_MAX_PASSWORD_LENGTH - 1] of Char;
  lpszUrlPath     : array[0..INTERNET_MAX_PATH_LENGTH - 1] of Char;
  lpszExtraInfo   : array[0..1024 - 1] of Char;
  lpUrlComponents : TURLComponents;
begin
  ZeroMemory(@lpszScheme, SizeOf(lpszScheme));
  ZeroMemory(@lpszHostName, SizeOf(lpszHostName));
  ZeroMemory(@lpszUserName, SizeOf(lpszUserName));
  ZeroMemory(@lpszPassword, SizeOf(lpszPassword));
  ZeroMemory(@lpszUrlPath, SizeOf(lpszUrlPath));
  ZeroMemory(@lpszExtraInfo, SizeOf(lpszExtraInfo));
  ZeroMemory(@lpUrlComponents, SizeOf(TURLComponents));

  lpUrlComponents.dwStructSize      := SizeOf(TURLComponents);
  lpUrlComponents.lpszScheme        := lpszScheme;
  lpUrlComponents.dwSchemeLength    := SizeOf(lpszScheme);
  lpUrlComponents.lpszHostName      := lpszHostName;
  lpUrlComponents.dwHostNameLength  := SizeOf(lpszHostName);
  lpUrlComponents.lpszUserName      := lpszUserName;
  lpUrlComponents.dwUserNameLength  := SizeOf(lpszUserName);
  lpUrlComponents.lpszPassword      := lpszPassword;
  lpUrlComponents.dwPasswordLength  := SizeOf(lpszPassword);
  lpUrlComponents.lpszUrlPath       := lpszUrlPath;
  lpUrlComponents.dwUrlPathLength   := SizeOf(lpszUrlPath);
  lpUrlComponents.lpszExtraInfo     := lpszExtraInfo;
  lpUrlComponents.dwExtraInfoLength := SizeOf(lpszExtraInfo);

  InternetCrackUrl(PChar(lpszUrl), Length(lpszUrl), ICU_DECODE or ICU_ESCAPE, lpUrlComponents);

  Writeln(Format('Protocol : %s',[lpszScheme]));
  Writeln(Format('Host     : %s',[lpszHostName]));
  Writeln(Format('User     : %s',[lpszUserName]));
  Writeln(Format('Password : %s',[lpszPassword]));
  Writeln(Format('Path     : %s',[lpszUrlPath]));
  Writeln(Format('ExtraInfo: %s',[lpszExtraInfo]));
end;

begin
  try
   ParseURL('http://login:password@somehost.somedomain.com/some_path/something_else.html?param1=val&param2=val');
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  readln;
end.

这将返回

Protocol : http
Host     : somehost.somedomain.com
User     : login
Password : password
Path     : /some_path/something_else.html
ExtraInfo: ?param1=val&param2=val

3
这个解决方案有依赖性:Windows 2000及以上版本和WinInet。 - Remy Lebeau
1
@RemyLebeau 改写:太多的库依赖 :) 不是针对 Indy,但我猜这可能是 Indy 在幕后使用的东西(虽然我总是更喜欢使用更低级别的方法来完成事情,尽管我并不总是理解它的工作原理,但使用预制库的优点就在于此)。 - Jerry Dodge
5
TIdURI 不使用任何外部API来进行解析。 - Remy Lebeau
3
由于好奇心,我想知道为什么InternetCrackUrl无法处理方案组件中的冒号(例如实际示例jdbc:jtds:sqlserver://localhost/cabinet),因为某些原因(调用成功,但返回的组件不正确)。当然,这与HTTP方案无关,但值得知道WinInet在这种情况下失败了(Indy可以正确处理)。[已投票] - TLama
编写所有代码来解析URL是否永远都是可以接受的? - alcalde
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接