Delphi WebService 使用 HTTP Header 管理 Session

背景知识

使用 Delphi 的 WebBroker 框架可以开发标准的 Web Server 程序。

使用 Delphi 的 SOAP 框架,可以开发支持 SOAP 标准的 WebService 服务端程序。此框架建立的 WebBroker 框架之上。

使用 Delphi 可以开发客户端程序,去调用 WebService 服务器提供的 SOAP 函数。同时也可以去调用 WebBroker 开发的 Web Server 提供的 RESTfull 的 URL 接口。

问题

WebBroker 的架构下,客户端和服务器端没有长连接,是无状态的。也就是服务器端并不能记住客户端的上一次访问。因此,需要一个 Session 机制。

因为是 Web 访问,可以采用 HTTP Cookie 来让客户端和服务器端之间维护一个 Session。这个方式的好处是客户端基本上不需要写代码。

但是,

如果应用程序的代码框架比较复杂,比如同一个客户端,可能需要访问多个不同的 WebService 服务器端,甚至同时还要访问多个 WebBroker 的 WebServer 服务器端。此时,Cookie 因为不能跨域,从一个 WebService 服务器端程序获得的 HTTP Cookie 无法让另外一个服务器端程序读到。

如果要在多个 WebService 服务器端程序之间保持同一个 Session,则需要考虑使用另外两种方式:

  1. 在 URL 里面,加上 Session 有关的参数;

  2. 在 HTTP Header 里面加上 Session 有关的参数;

使用 HTTP Header 来维护 Session 的代码实现

客户端

客户端如果使用 HTTRIO 去调用服务器端的函数,设计期选中 HTTPRIO1,属性面板里面,事件标签页,HTTPWebNode 下拉开,找到 OnBeforePost 事件,双击产生事件方法代码:

Delphi 复制代码
uses Soap.SOAPHTTPTrans;

procedure TForm2.HTTPRIO1HTTPWebNode1BeforePost(const HTTPReqResp:
    THTTPReqResp; Client: THTTPClient);
begin
  Client.CustomHeaders['ABC'] := 'abcdexyz';
end;

则每次使用这个 httpRIO1 来调用服务器端方法时,都会为 HTTP 加上一个名字为 abc 的 Header;

SoapConnection 的问题是,它内部的 RIO 不是一直存在的。为它设置了 URL 以后,需要设置它的 Connected 属性为 True,这个 RIO 才会被创建出来。

因此,代码可能需要显示地执行 SoapConnection1.Connected := True; 然后再判断是否有 RIO,如果有,则执行以下代码绑定事件:

Delphi 复制代码
SoapConnection1.RIO.HTTPWebNode.OnBeforePost := TForm2.HTTPRIO1HTTPWebNode1BeforePost;

这样绑定后,SoapConnection 和 HTTPRIO 应该就能共享同一个 HTTP Header 的代码了。

以下代码测试通过:

Delphi 复制代码
procedure TForm2.Button2Click(Sender: TObject);
begin
  SoapConnection1.Connected := True;
  if Assigned(SoapConnection1.RIO) then
  begin
    SoapConnection1.RIO.HTTPWebNode.OnBeforePost := HTTPRIO1HTTPWebNode1BeforePost;
  end;

  ClientDataSet1.Close;
  ClientDataSet1.Open;
end;

服务器端

服务器端在接口方法的实现里面,直接读取 HTTP Header: 的代码:

Delphi 复制代码
uses  SOAP.WebBrokerSOAP, web.HttpApp;


{ TtestHeader }

function TtestHeader.testHeader: string;
begin
  Result := GetSOAPWebModule.Request.GetFieldByName('abc');
end;

服务器端的 Soap Data Module:对应客户端的 SoapConnection 当客户端的 ClientDataSet.Open 的时候,服务器端的 DataSetProvider.BeforeGetRecords 事件里面写代码,读 Header 是否正确,代码如下:

Delphi 复制代码
uses  SOAP.WebBrokerSOAP, web.HttpApp;

{$R *.DFM}

procedure TMyDataCreateInstance(out obj: TObject);
begin
 obj := TMyData.Create(nil);
end;

function TMyData.CheckHeader: Boolean;
begin
  Result := 'abcdexyz' = GetSOAPWebModule.Request.GetFieldByName('abc');
end;

procedure TMyData.DataSetProvider1BeforeGetRecords(Sender: TObject; var
    OwnerData: OLEVariant);
begin
  if not CheckHeader then raise Exception.Create('身份验证没通过');
end;

总之 服务器端不管是接口方法的实现部分,还是 SoapDataModule 里面,都是调用:GetSOAPWebModule.Request.GetFieldByName('abc'); 去读取 Header 来获得客户端提交的 Session ID 或者说: Token

客户端使用 IdHTTP

Delphi 复制代码
procedure TForm2.Button3Click(Sender: TObject);
var
  S: string;
begin
  IdHTTP1.Request.CustomHeaders.Values['abc'] := 'abcde';
  S := IdHTTP1.Get('http://localhost:8080/MyHeader');
  Log(S);
end;

上述代码,使用 TIdHTTP 作为 HTTP 客户端去请求一个 WebBroker 服务器端的 MyHeader 路径。请求之前添加了一个自定义的 CustomHeader

然后,WebBroker 服务器端对应的代码,通过以下方法读客户端传过来的 HTTP Header:

Delphi 复制代码
procedure TWebModule1.WebModule1MyHeaderAction(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
  S: string;
begin
  S := Request.GetFieldByName('abc');
  Response.Content := S;
end;
相关推荐
XUEYUAN52123 天前
ASN 自治系统号风控:平台如何通过 IP 所属自治域批量识别代理流量
python·网络协议·http·网络安全·socks5
captain3763 天前
HTTP(2)
java·网络·http·java-ee
比兔代理4 天前
正向代理与反向代理技术辨析,为什么代理 IP 属于正向代理
服务器·网络·http·ip
碳基修炼5 天前
排查记:本地 devServer 是 http,浏览器却把 302 重定向升级成了 https
前端·http
那年窗外下的雪.5 天前
第 02 天:Linux 权限、inode、目录项与链接
学习·http
那年窗外下的雪.5 天前
AIDC 学习日志|第 27 天|EVPN 多归属收敛时间线与 Leaf2 接管验证
网络协议·学习·http·tcpdump
j7~5 天前
【Linux】三十八.C++ 手写 HTTP 服务器:裸 socket + fork 多进程,从 HTTP 报文解析到浏览器打开网页
linux·网络·网络协议·学习·http·网络编程
布莱克6057 天前
HTTP 状态码详解:从分类到实战
计算机网络·http·状态码
StevenSurpass7 天前
打印业务彻底解耦:FastPrintAgent 统一 JSON 模型 + FastReport 引擎的设计与落地
mqtt·http·中间件·打印·fastreport
小此方8 天前
Linux网络(十二):HTTP短连接与长连接:从Connection机制到Cookie、Session,彻底理解HTTP的无状态
服务器·网络·http