如何检索 XHR 响应?
How to retrieve an XHR response?
基本上,我使用 Chromium 来显示我无法控制的网站。当用户点击特定按钮时,发送 XHR 请求;我希望能够检索到答案。因此,假设请求发送 A,服务器回复 B;我想读B。我该怎么做?
- https://groups.google.com/forum/#!topic/delphichromiumembedded/SKHuVjutSFE 似乎有答案,但它仅适用于 DCEF。
- 我的印象是制作 TScheme 的后代就可以完成这项工作,但我不明白它是如何工作的...
- 就我而言,我无法强制 DCEF 发送请求。我确实需要在用户单击特定按钮时检索值。
缺少 OnResourceResponse 事件
在旧的 CEF1 中,您可以简单地使用 OnResourceResponse
事件。在 CEF3 中,这样一个看似微不足道的任务可能会成为一个真正的挑战,因为 Issue 515
linked in the answer
to the fundamentally same question as you're asking here is still opened and it seems the only way (at this time) is implementing your own CefResourceHandler
handler to make a proxy between the browser and the world outside. The implementation principle is described in a similar topic
比如:
You can use CefResourceHandler via
CefRequestHandler::GetResourceHandler and execute the request/return
the response contents yourself using CefURLRequest.
所以这是在 DCEF3 中要做的(此时):
1。定义您自己的资源处理程序
作为第一个导出你自己的 TCefResourceHandlerOwn
后代,至少要实现以下方法:
ProcessRequest
- 这里你将转发(发送)请求A到服务器并接收服务器的响应B(这是你第一次机会使用响应数据 B),您应该将其存储(最好在 class 字段中,以便可以轻松地将其刷新到 ReadResponse
方法的输出缓冲区)。
GetResponseHeaders
- in this method you'll need to fill the output parameters about the response B data length and (some of) the header fields (this might be the place where you will need to have your response B parsed for headers to fill the CefResponse
类型参数成员).
ReadResponse
- 这是您刷新响应 B 数据以供浏览器进行最终处理的地方。
2。将您的资源处理程序分配给请求
下一步是为请求分配您自己的资源处理程序。这在技术上就像从 Chromium 的 OnGetResourceHandler
事件处理程序返回对资源处理程序接口的引用一样简单。但是在这一步中,您应该考虑的是,您越缩小标准,您在资源处理程序中的生活就会越简单。因为如果你分配你的处理程序,例如对于任何请求(对于任何 URL),那么您可能必须处理和过滤掉来自与您的总体任务完全无关的服务器的响应。
因此,我建议将分配范围缩小到由该 Web 应用程序按钮生成的请求,或者至少按请求资源类型(在本例中为 RT_XHR
),这样您就不会需要自己处理所有的请求。
代码示例
实现上述步骤的方法有很多种。在这个例子中,我插入了 Chromium 浏览器 class 并在那里添加了一个新事件 OnXmlHttpExchange
当 XHR 请求完成时触发(但在它被传递到浏览器之前,它允许您甚至修改响应) .
uses
CefLib, CefVCL;
type
TXmlHttpExchangeEvent = procedure(Sender: TObject; const Request: ICefRequest; const Response: ICefResponse; DataStream: TMemoryStream) of object;
TChromium = class(CefVCL.TChromium)
private
FOnXmlHttpExchange: TXmlHttpExchangeEvent;
protected
procedure DoXmlHttpExchange(const Request: ICefRequest; const Response: ICefResponse; DataStream: TMemoryStream); virtual;
function doOnGetResourceHandler(const Browser: ICefBrowser; const Frame: ICefFrame; const Request: ICefRequest): ICefResourceHandler; override;
public
property OnXmlHttpExchange: TXmlHttpExchangeEvent read FOnXmlHttpExchange write FOnXmlHttpExchange;
end;
TXmlHttpHandler = class(TCefResourceHandlerOwn)
private
FOwner: TChromium;
FOffset: NativeUInt;
FStream: TMemoryStream;
FCallback: ICefCallback;
FResponse: ICefResponse;
protected
function ProcessRequest(const Request: ICefRequest; const Callback: ICefCallback): Boolean; override;
procedure GetResponseHeaders(const Response: ICefResponse; out ResponseLength: Int64; out RedirectUrl: ustring); override;
function ReadResponse(const DataOut: Pointer; BytesToRead: Integer; var BytesRead: Integer; const Callback: ICefCallback): Boolean; override;
public
constructor Create(Owner: TChromium; const Browser: ICefBrowser; const Frame: ICefFrame; const SchemeName: ustring; const Request: ICefRequest); reintroduce;
destructor Destroy; override;
procedure WriteResponse(const Request: ICefUrlRequest; Data: Pointer; Size: NativeUInt); virtual;
procedure CompleteRequest(const Request: ICefUrlRequest); virtual;
end;
TXmlHttpRequestClient = class(TCefUrlrequestClientOwn)
private
FHandler: TXmlHttpHandler;
protected
procedure OnDownloadData(const Request: ICefUrlRequest; Data: Pointer; DataLength: NativeUInt); override;
procedure OnRequestComplete(const Request: ICefUrlRequest); override;
public
constructor Create(Handler: TXmlHttpHandler); reintroduce;
end;
implementation
{ TChromium }
procedure TChromium.DoXmlHttpExchange(const Request: ICefRequest; const Response: ICefResponse; DataStream: TMemoryStream);
begin
// fire the OnXmlHttpExchange event
if Assigned(FOnXmlHttpExchange) then
FOnXmlHttpExchange(Self, Request, Response, DataStream);
end;
function TChromium.doOnGetResourceHandler(const Browser: ICefBrowser; const Frame: ICefFrame; const Request: ICefRequest): ICefResourceHandler;
begin
// first trigger the browser's OnGetResourceHandler event
Result := inherited;
// if no handler was assigned and request is of type XHR, create our custom one
if not Assigned(Result) and (Request.ResourceType = RT_XHR) then
Result := TXmlHttpHandler.Create(Self, Browser, Frame, 'XhrIntercept', Request);
end;
{ TXmlHttpHandler }
constructor TXmlHttpHandler.Create(Owner: TChromium; const Browser: ICefBrowser; const Frame: ICefFrame; const SchemeName: ustring; const Request: ICefRequest);
begin
inherited Create(Browser, Frame, SchemeName, Request);
FOwner := Owner;
FStream := TMemoryStream.Create;
end;
destructor TXmlHttpHandler.Destroy;
begin
FStream.Free;
inherited;
end;
function TXmlHttpHandler.ProcessRequest(const Request: ICefRequest; const Callback: ICefCallback): Boolean;
begin
Result := True;
// reset the offset value
FOffset := 0;
// store the callback reference
FCallback := Callback;
// create the URL request that will perform actual data exchange (you can replace
// it with any other; e.g. with MSXML, or an Indy client)
TCefUrlRequestRef.New(Request, TXmlHttpRequestClient.Create(Self));
end;
procedure TXmlHttpHandler.GetResponseHeaders(const Response: ICefResponse; out ResponseLength: Int64; out RedirectUrl: ustring);
var
HeaderMap: ICefStringMultimap;
begin
// return the size of the data we have in the response stream
ResponseLength := FStream.Size;
// fill the header fields from the response returned by the URL request
Response.Status := FResponse.Status;
Response.StatusText := FResponse.StatusText;
Response.MimeType := FResponse.MimeType;
// copy the header map from the response returned by the URL request
HeaderMap := TCefStringMultimapOwn.Create;
FResponse.GetHeaderMap(HeaderMap);
if HeaderMap.Size <> 0 then
FResponse.SetHeaderMap(HeaderMap);
end;
function TXmlHttpHandler.ReadResponse(const DataOut: Pointer; BytesToRead: Integer; var BytesRead: Integer; const Callback: ICefCallback): Boolean;
begin
// since this method can be called multiple times (reading in chunks), check if we
// have still something to transfer
if FOffset < FStream.Size then
begin
Result := True;
BytesRead := BytesToRead;
// copy the data from the response stream to the browser buffer
Move(Pointer(NativeUInt(FStream.Memory) + FOffset)^, DataOut^, BytesRead);
// increment the offset by the amount of data we just copied
Inc(FOffset, BytesRead);
end
else
Result := False;
end;
procedure TXmlHttpHandler.WriteResponse(const Request: ICefUrlRequest; Data: Pointer; Size: NativeUInt);
begin
// write the just downloaded data to the intermediate response stream
FStream.Write(Data^, Size);
end;
procedure TXmlHttpHandler.CompleteRequest(const Request: ICefUrlRequest);
begin
FStream.Position := 0;
// store the response reference for the GetResponseHeaders method
FResponse := Request.GetResponse;
// this method is executed when the URL request completes, so we have everything we
// need to trigger the OnXmlHttpExchange event
FOwner.DoXmlHttpExchange(Request.GetRequest, FResponse, FStream);
// this signals the handler that the request has completed and that it can process
// the response headers and pass the content to the browser
if Assigned(FCallback) then
FCallback.Cont;
end;
{ TXmlHttpRequestClient }
constructor TXmlHttpRequestClient.Create(Handler: TXmlHttpHandler);
begin
inherited Create;
FHandler := Handler;
end;
procedure TXmlHttpRequestClient.OnDownloadData(const Request: ICefUrlRequest; Data: Pointer; DataLength: NativeUInt);
begin
FHandler.WriteResponse(Request, Data, DataLength);
end;
procedure TXmlHttpRequestClient.OnRequestComplete(const Request: ICefUrlRequest);
begin
FHandler.CompleteRequest(Request);
end;
和这个插入的可能用法 class:
type
TForm1 = class(TForm)
Chromium: TChromium;
procedure FormCreate(Sender: TObject);
private
procedure XmlHttpExchange(Sender: TObject; const Request: ICefRequest; const Response: ICefResponse; DataStream: TMemoryStream);
end;
implementation
procedure TForm1.FormCreate(Sender: TObject);
begin
Chromium.OnXmlHttpExchange := XmlHttpExchange;
end;
procedure TForm1.XmlHttpExchange(Sender: TObject; const Request: ICefRequest; const Response: ICefResponse; DataStream: TMemoryStream);
begin
// here you can find the request for which you want to read the response (explore the
// Request parameter members to see by what you can do this)
if Request.Url = 'http://example.com' then
begin
// the DataStream stream contains the response, so process it here as you wish; you
// can even modify it here if you want
DataStream.SaveToFile(...);
end;
end;
基本上,我使用 Chromium 来显示我无法控制的网站。当用户点击特定按钮时,发送 XHR 请求;我希望能够检索到答案。因此,假设请求发送 A,服务器回复 B;我想读B。我该怎么做?
- https://groups.google.com/forum/#!topic/delphichromiumembedded/SKHuVjutSFE 似乎有答案,但它仅适用于 DCEF。
- 我的印象是制作 TScheme 的后代就可以完成这项工作,但我不明白它是如何工作的...
- 就我而言,我无法强制 DCEF 发送请求。我确实需要在用户单击特定按钮时检索值。
缺少 OnResourceResponse 事件
在旧的 CEF1 中,您可以简单地使用 OnResourceResponse
事件。在 CEF3 中,这样一个看似微不足道的任务可能会成为一个真正的挑战,因为 Issue 515
linked in the answer
to the fundamentally same question as you're asking here is still opened and it seems the only way (at this time) is implementing your own CefResourceHandler
handler to make a proxy between the browser and the world outside. The implementation principle is described in a similar topic
比如:
You can use CefResourceHandler via CefRequestHandler::GetResourceHandler and execute the request/return the response contents yourself using CefURLRequest.
所以这是在 DCEF3 中要做的(此时):
1。定义您自己的资源处理程序
作为第一个导出你自己的 TCefResourceHandlerOwn
后代,至少要实现以下方法:
ProcessRequest
- 这里你将转发(发送)请求A到服务器并接收服务器的响应B(这是你第一次机会使用响应数据 B),您应该将其存储(最好在 class 字段中,以便可以轻松地将其刷新到ReadResponse
方法的输出缓冲区)。GetResponseHeaders
- in this method you'll need to fill the output parameters about the response B data length and (some of) the header fields (this might be the place where you will need to have your response B parsed for headers to fill theCefResponse
类型参数成员).ReadResponse
- 这是您刷新响应 B 数据以供浏览器进行最终处理的地方。
2。将您的资源处理程序分配给请求
下一步是为请求分配您自己的资源处理程序。这在技术上就像从 Chromium 的 OnGetResourceHandler
事件处理程序返回对资源处理程序接口的引用一样简单。但是在这一步中,您应该考虑的是,您越缩小标准,您在资源处理程序中的生活就会越简单。因为如果你分配你的处理程序,例如对于任何请求(对于任何 URL),那么您可能必须处理和过滤掉来自与您的总体任务完全无关的服务器的响应。
因此,我建议将分配范围缩小到由该 Web 应用程序按钮生成的请求,或者至少按请求资源类型(在本例中为 RT_XHR
),这样您就不会需要自己处理所有的请求。
代码示例
实现上述步骤的方法有很多种。在这个例子中,我插入了 Chromium 浏览器 class 并在那里添加了一个新事件 OnXmlHttpExchange
当 XHR 请求完成时触发(但在它被传递到浏览器之前,它允许您甚至修改响应) .
uses
CefLib, CefVCL;
type
TXmlHttpExchangeEvent = procedure(Sender: TObject; const Request: ICefRequest; const Response: ICefResponse; DataStream: TMemoryStream) of object;
TChromium = class(CefVCL.TChromium)
private
FOnXmlHttpExchange: TXmlHttpExchangeEvent;
protected
procedure DoXmlHttpExchange(const Request: ICefRequest; const Response: ICefResponse; DataStream: TMemoryStream); virtual;
function doOnGetResourceHandler(const Browser: ICefBrowser; const Frame: ICefFrame; const Request: ICefRequest): ICefResourceHandler; override;
public
property OnXmlHttpExchange: TXmlHttpExchangeEvent read FOnXmlHttpExchange write FOnXmlHttpExchange;
end;
TXmlHttpHandler = class(TCefResourceHandlerOwn)
private
FOwner: TChromium;
FOffset: NativeUInt;
FStream: TMemoryStream;
FCallback: ICefCallback;
FResponse: ICefResponse;
protected
function ProcessRequest(const Request: ICefRequest; const Callback: ICefCallback): Boolean; override;
procedure GetResponseHeaders(const Response: ICefResponse; out ResponseLength: Int64; out RedirectUrl: ustring); override;
function ReadResponse(const DataOut: Pointer; BytesToRead: Integer; var BytesRead: Integer; const Callback: ICefCallback): Boolean; override;
public
constructor Create(Owner: TChromium; const Browser: ICefBrowser; const Frame: ICefFrame; const SchemeName: ustring; const Request: ICefRequest); reintroduce;
destructor Destroy; override;
procedure WriteResponse(const Request: ICefUrlRequest; Data: Pointer; Size: NativeUInt); virtual;
procedure CompleteRequest(const Request: ICefUrlRequest); virtual;
end;
TXmlHttpRequestClient = class(TCefUrlrequestClientOwn)
private
FHandler: TXmlHttpHandler;
protected
procedure OnDownloadData(const Request: ICefUrlRequest; Data: Pointer; DataLength: NativeUInt); override;
procedure OnRequestComplete(const Request: ICefUrlRequest); override;
public
constructor Create(Handler: TXmlHttpHandler); reintroduce;
end;
implementation
{ TChromium }
procedure TChromium.DoXmlHttpExchange(const Request: ICefRequest; const Response: ICefResponse; DataStream: TMemoryStream);
begin
// fire the OnXmlHttpExchange event
if Assigned(FOnXmlHttpExchange) then
FOnXmlHttpExchange(Self, Request, Response, DataStream);
end;
function TChromium.doOnGetResourceHandler(const Browser: ICefBrowser; const Frame: ICefFrame; const Request: ICefRequest): ICefResourceHandler;
begin
// first trigger the browser's OnGetResourceHandler event
Result := inherited;
// if no handler was assigned and request is of type XHR, create our custom one
if not Assigned(Result) and (Request.ResourceType = RT_XHR) then
Result := TXmlHttpHandler.Create(Self, Browser, Frame, 'XhrIntercept', Request);
end;
{ TXmlHttpHandler }
constructor TXmlHttpHandler.Create(Owner: TChromium; const Browser: ICefBrowser; const Frame: ICefFrame; const SchemeName: ustring; const Request: ICefRequest);
begin
inherited Create(Browser, Frame, SchemeName, Request);
FOwner := Owner;
FStream := TMemoryStream.Create;
end;
destructor TXmlHttpHandler.Destroy;
begin
FStream.Free;
inherited;
end;
function TXmlHttpHandler.ProcessRequest(const Request: ICefRequest; const Callback: ICefCallback): Boolean;
begin
Result := True;
// reset the offset value
FOffset := 0;
// store the callback reference
FCallback := Callback;
// create the URL request that will perform actual data exchange (you can replace
// it with any other; e.g. with MSXML, or an Indy client)
TCefUrlRequestRef.New(Request, TXmlHttpRequestClient.Create(Self));
end;
procedure TXmlHttpHandler.GetResponseHeaders(const Response: ICefResponse; out ResponseLength: Int64; out RedirectUrl: ustring);
var
HeaderMap: ICefStringMultimap;
begin
// return the size of the data we have in the response stream
ResponseLength := FStream.Size;
// fill the header fields from the response returned by the URL request
Response.Status := FResponse.Status;
Response.StatusText := FResponse.StatusText;
Response.MimeType := FResponse.MimeType;
// copy the header map from the response returned by the URL request
HeaderMap := TCefStringMultimapOwn.Create;
FResponse.GetHeaderMap(HeaderMap);
if HeaderMap.Size <> 0 then
FResponse.SetHeaderMap(HeaderMap);
end;
function TXmlHttpHandler.ReadResponse(const DataOut: Pointer; BytesToRead: Integer; var BytesRead: Integer; const Callback: ICefCallback): Boolean;
begin
// since this method can be called multiple times (reading in chunks), check if we
// have still something to transfer
if FOffset < FStream.Size then
begin
Result := True;
BytesRead := BytesToRead;
// copy the data from the response stream to the browser buffer
Move(Pointer(NativeUInt(FStream.Memory) + FOffset)^, DataOut^, BytesRead);
// increment the offset by the amount of data we just copied
Inc(FOffset, BytesRead);
end
else
Result := False;
end;
procedure TXmlHttpHandler.WriteResponse(const Request: ICefUrlRequest; Data: Pointer; Size: NativeUInt);
begin
// write the just downloaded data to the intermediate response stream
FStream.Write(Data^, Size);
end;
procedure TXmlHttpHandler.CompleteRequest(const Request: ICefUrlRequest);
begin
FStream.Position := 0;
// store the response reference for the GetResponseHeaders method
FResponse := Request.GetResponse;
// this method is executed when the URL request completes, so we have everything we
// need to trigger the OnXmlHttpExchange event
FOwner.DoXmlHttpExchange(Request.GetRequest, FResponse, FStream);
// this signals the handler that the request has completed and that it can process
// the response headers and pass the content to the browser
if Assigned(FCallback) then
FCallback.Cont;
end;
{ TXmlHttpRequestClient }
constructor TXmlHttpRequestClient.Create(Handler: TXmlHttpHandler);
begin
inherited Create;
FHandler := Handler;
end;
procedure TXmlHttpRequestClient.OnDownloadData(const Request: ICefUrlRequest; Data: Pointer; DataLength: NativeUInt);
begin
FHandler.WriteResponse(Request, Data, DataLength);
end;
procedure TXmlHttpRequestClient.OnRequestComplete(const Request: ICefUrlRequest);
begin
FHandler.CompleteRequest(Request);
end;
和这个插入的可能用法 class:
type
TForm1 = class(TForm)
Chromium: TChromium;
procedure FormCreate(Sender: TObject);
private
procedure XmlHttpExchange(Sender: TObject; const Request: ICefRequest; const Response: ICefResponse; DataStream: TMemoryStream);
end;
implementation
procedure TForm1.FormCreate(Sender: TObject);
begin
Chromium.OnXmlHttpExchange := XmlHttpExchange;
end;
procedure TForm1.XmlHttpExchange(Sender: TObject; const Request: ICefRequest; const Response: ICefResponse; DataStream: TMemoryStream);
begin
// here you can find the request for which you want to read the response (explore the
// Request parameter members to see by what you can do this)
if Request.Url = 'http://example.com' then
begin
// the DataStream stream contains the response, so process it here as you wish; you
// can even modify it here if you want
DataStream.SaveToFile(...);
end;
end;