首先,一个 HTTP 的 URL 有下面几个部分组成:
- 协议类型。是 HTTP 还是 HTTPS 协议,分别对应于 http:// 和 https://;
- 用户名和密码。这个位于协议类型的后面,用户名和密码之间采用 “:” 进行分隔,跟主机的域名或IP部分用 ‘@’ 符号进行分隔;
- 域名或IP地址;
- 要访问的资源地址(URI),以 “/” 开始;
- 附加的额外参数,与 URI 之间以 “?” 进行分隔,多个参数之前用 “&” 进行分隔。每个参数如果分成名称和参数部分,则名称和参数部分采用 “=” 分隔
一个典型的 URL 如下所示 :
http://www.baidu.com/s?wd=abcd&tag=qdac
分别对应下:协议为 HTTP ,用户名和密码没有,域名为 www.baidu.com,URI 为 /s ,参数有两个,分别为 wd 和 tag,它们的值分别为 abcd 和 qdac。
根据上面的规则,我们定义了一个下面的结构:
THttpParam = record Name, Value: String; end; THttpUrl = record Host, URI, UserName, Password: QStringW; Params: array of THttpParam; Port: Word; Https: Boolean; private public function Encode(AUseUtf8: Boolean): QStringW; function Decode(const AUrl: QStringW): Boolean; class function Escape(const AURI: QStringW; AUseUtf8: Boolean) : QStringW; static; class function Unescape(const S: QStringW): QStringW; static; end;
只所以用记录(结构体)而不是类,是因为它可以在栈上运行,可以简化编码。
上面的定义实现了几个函数:
- Encode 函数用于将各个部分组合成一个有效的 URL ;
- Decode 函数用于解析一个 URL ,并将其各部分的值,保存到相应的成员中;
- Escape 函数用于将一个字符串按照 URL 编码规则进行转义,主要用于中文和特殊字符的处理;
- Unescape 函数用于将一个使用 URL 编码规则的字符串反转为正常的字符串,注意这个字符串应该是转义过的字符串,否则可能出现乱码
好了,下面看具体的实现:
{ THttpUrl } function THttpUrl.Decode(const AUrl: QStringW): Boolean; var p, ps: PWideChar; i: Integer; begin Result := true; p := PWideChar(AUrl); if StartWithW(p, 'https://', true) then begin Https := true; Inc(p, 8); Port := 443; end else begin Https := False; if StartWithW(p, 'http://', true) then Inc(p, 7); Port := 80; end; if StrStrW(p, '@') <> nil then // http://username:password@host begin UserName := DecodeTokenW(p, ':@', #0, False, False); if p^ = ':' then Inc(p); Password := DecodeTokenW(p, '@', #0, False, true); end; Host := DecodeTokenW(p, ':/', #0, False, False); if p^ = ':' then begin Inc(p); ps := p; SkipUntilW(p, '/'); if TryStrToInt(StrDupX(ps, p - ps), i) then Port := i else Result := False; end; URI := DecodeTokenW(p, '?', #0, False, False); if Length(URI) = 0 then URI := '/'; if p^ = '?' then begin Inc(p); SetLength(Params, 16); i := 0; repeat if i = Length(Params) then SetLength(Params, i + 16); Params[i].Name := Unescape(DecodeTokenW(p, '=', #0, False, False)); if p^ = '=' then Inc(p); Params[i].Value := Unescape(DecodeTokenW(p, '&', #0, False, False)); Inc(i); until p^ = #0; SetLength(Params, i); end else SetLength(Params, 0); end; function THttpUrl.Encode(AUseUtf8: Boolean): QStringW; var i: Integer; ADefPort: Boolean; begin if Https then begin Result := 'https://'; ADefPort := Port = 443; end else begin Result := 'http://'; ADefPort := Port = 80; end; if Length(UserName) > 0 then begin Result := Result + Escape(UserName, AUseUtf8); if Length(Password) > 0 then Result := Result + ':' + Escape(Password, AUseUtf8); Result := Result + '@'; end; Result := Result + Host; if not ADefPort then Result := Result + ':' + IntToStr(Port); if Length(URI) > 0 then begin if PWideChar(URI)^ <> '/' then URI := '/' + URI; end else URI := '/'; Result := Result + URI; if Length(Params) > 0 then begin Result := Result + '?'; for i := 0 to High(Params) do begin if i <> 0 then Result := Result + '&'; Result := Result + Escape(Params[i].Name, AUseUtf8); if Length(Params[i].Value) > 0 then Result := Result + '=' + Escape(Params[i].Value, AUseUtf8); end; end; end; class function THttpUrl.Escape(const AURI: QStringW; AUseUtf8: Boolean) : QStringW; var S: QStringA; p: PQCharA; i, L: Integer; pd: PWideChar; begin if AUseUtf8 then S := qstring.Utf8Encode(AURI) else S := qstring.AnsiEncode(AURI); p := PQCharA(S); SetLength(Result, S.Length * 3); pd := PWideChar(Result); while p^ <> 0 do begin if p^ in [$30 .. $39, $41 .. $5A, $61 .. $7A] then pd^ := WideChar(p^) else begin pd^ := '%'; Inc(pd); pd^ := HexChar(p^ shr 4); Inc(pd); pd^ := HexChar(p^ and $F); end; Inc(pd); Inc(p); end; SetLength(Result, pd - PWideChar(Result)); end; class function THttpUrl.Unescape(const S: QStringW): QStringW; var ATemp: TBytes; p: PWideChar; i: Integer; ABom: Boolean; begin if Pos(S, '%') = 0 then Result := S else begin SetLength(ATemp, Length(S) shl 1); p := PWideChar(S); i := 0; while p^ <> #0 do begin if p^ = '%' then begin Inc(p); ATemp[i] := HexValue(p^) shl 4; Inc(p); ATemp[i] := ATemp[i] + HexValue(p^); end else if Ord(p^) <= $FF then ATemp[i] := Ord(p^) else begin ATemp[i] := Ord(p^) shr 8; Inc(i); ATemp[i] := Ord(p^) and $FF; end; Inc(i); Inc(p); end; case DetectTextEncoding(@ATemp[0], i, ABom) of teUtf8: Result := qstring.Utf8Decode(@ATemp[0], i) else Result := qstring.AnsiDecode(@ATemp[0], i); end; end; end;
当然了,这个代码引用了 QString.pas 中的几个函数,所以你要用的话,一样需要引入 QString 单元。
至于疗效嘛,暂略吧。套用一句话,谁用谁知道!