How to retrieve an XHR response?

5down voteaccepted

+100

Missing OnResourceResponse event

In a good old CEF1 you could simply use the OnResourceResponse event. In CEF3, such a seemingly trivial task might become a real challenge, because the Issue 515 linked in the answerto 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 like:

You can use CefResourceHandler via CefRequestHandler::GetResourceHandler and execute the request/return the response contents yourself using CefURLRequest.

So here is what to do in DCEF3 (at this time):

1. Define your own resource handler

As first derive your own TCefResourceHandlerOwn descendant where implement at least the following methods:

  • ProcessRequest - here you will forward (send) request A to the server and receive the server's response B (here's your first chance to work with the response data B), which you should keep stored (ideally in a class field, so that can be flushed to the output buffer of the ReadResponsemethod easily).

  • 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 type parameter members).

  • ReadResponse - this is where you'll flush your response B data for final processing by the browser.

2. Assign your resource handler to the request

The next step is assigning your own resource handler to the request. That is technically as easy as returning a reference to the resource handler interface from the Chromium's OnGetResourceHandlerevent handler. But in this step you should consider that the more you narrow the criteria, the simpler you'll have live in your resource handler. Because if you assign your handler e.g. to any request (for any URL), then you may have to process and filter out responses coming in from servers that are absolutely unrelated to your overall task.

So, I would recommend to narrow the assignment deep down to the requests generated by that web application button, or at least by the request resource type (RT_XHR in this case), so you won't need to process all the requests by yourself.

Code example

There are many ways how to implement the above steps. In this example I've interposed Chromium browser class and added there a new event OnXmlHttpExchange which fires when an XHR request completes (but before it's passed to the browser, which allows you to even modify the response).

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;

And a possible usage of this interposed 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;

shareimprove this answer

edited May 29 '15 at 20:01

answered May 23 '15 at 2:58

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值