如何在IdhttpServer上实现长时间运行的查询?

时间:2014-09-26 07:15:32

标签: delphi delphi-2010 indy10

在IdHttpServer上实现长时间运行查询的好方法是什么。我已经写了这么简单的逻辑,请建议更好的方法来实现我的努力与其表现的挣扎。 我正在使用D2010和Indy 10.5.8来实现目标,还建议如果我们经常从会话中检索值会是资源密集型的吗?

procedure TForm1.ServerCommandGet(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
   SessionObj : TSessionData;
begin
 if ARequestInfo.Document = 'EXECUTEQUERY' then
 begin
   if not Assigned(ARequestInfo.Session.Content) then
   begin
      SessionObj := TSessionData.Create;
      ARequestInfo.Session.Content.AddObject('U_SESSION', SessionObj);
      SessionObj.RunLongQuery;
   end;
 end;

 if ARequestInfo.Document = 'GETDATA' then
 begin
   SessionObj := TSessionData(ARequestInfo.Session.Content.Objects[ ARequestInfo.Session.Content.IndexOf('U_SESSION')]);
   if SessionObj.GetQueryStat = Done  then
    begin
       AResponseInfo.ContentStream.CopyFrom(SessionObj.GetMemStream, SessionObj.GetMemStream.Size);
       SessionObj.GetMemStream.Clear;
       AResponseInfo.ResponseNo := 200;
    end else if SessionObj.GetQueryStat = Error then
        AResponseInfo.ResponseNo := 500
    else AResponseInfo.ResponseNo := 102;
 end;

end;

procedure TForm1.ServerSessionEnd(Sender: TIdHTTPSession);
begin
  TSessionData(Sender.Content.Objects[ Sender.Content.IndexOf('U_SESSION')]).Free;
end;

{ TProcessQuery }

constructor TProcessQuery.Create;
begin
  myConn := TMyConnection.Create(nil);
  myConn.LoginPrompt := False;
  myConn.UserName := 'UserName';
  myConn.Password := 'Password';
  myConn.Server := 'Host';
  myConn.Database := 'DBName';
  myConn.Connected := True;
  myQuery := TMyQuery.Create(nil);
  myQuery.Unidirectional := True;
  myQuery.Options.CreateConnection := False;
  myQuery.Connection := myConn;
  Fstat := None;
  Fstream := TMemoryStream.Create;
end;

destructor TProcessQuery.Destroy;
begin
  if Assigned(myConn) then begin
    myConn.Close;
    myConn.Disconnect;
    FreeAndNil(myConn);
  end;
end;

procedure TProcessQuery.ExecuteQuery;
begin
  Status := Started;
  myQuery.SQL.Text := '<Some Query>';
  myQuery.Open;
  try
    try
    while not myQuery.Eof do
    begin
         Status := Inprogress;
         //Add to FStream which would be returned to user.
    end;
    except
        on Exception do
        Status := Error;
    end;
  finally
    myQuery.Close;
  end;
end;

{ TSessionData }

constructor TSessionData.Create;
begin
  FProcessQuery :=  TProcessQuery.Create;
end;

function TSessionData.GetMemStream: TMemoryStream;
begin
  result := FProcessQuery.Fstream;
end;

function TSessionData.GetQueryStat: TStatus;
begin
  result := FProcessQuery.Status;
end;

procedure TSessionData.RunLongQuery;
begin
  FProcessQuery.ExecuteQuery
end;

1 个答案:

答案 0 :(得分:3)

您正在ServerCommandGet()的上下文中运行实际查询,因此在查询完成之前,客户端将不会收到回复。对于您正在尝试的内容,您需要将查询移动到其自己的线程并让ServerCommandGet()退出,以便客户端获得回复并继续,从而释放它以发送后续GETDATA个请求。在ServerSessionEnd()中,如果查询线程仍在运行,则必须终止查询线程,并释放TSessionData对象。

您的代码还存在其他一些问题。

ServerCommandGet()正在检查not Assigned(ARequestInfo.Session.Content),然后在ARequestInfo.Session.Content.AddObject()为零时调用ARequestInfo.Session.Content。我没有看到任何创建ARequestInfo.Session.Content对象的代码。

如果客户发出多个EXECUTEQUERY请求,您将使用相同的名称将这些请求全部存储在AResponseInfo.Session.Content中,并使用&#39; U_SESSION&#39;。 GETDATA将仅返回它找到的第一个查询的结果,ServerSessionEnd()仅释放它找到的第一个查询。因此,要么为每个查询指定一个唯一的名称,然后将其发送回客户端,以便将其包含在GETDATA中,并使ServerSessionEnd()遍历整个Sender.Content,否则不允许多个查询在同一个HTTP会话中。如果客户端在前一个查询仍处于活动状态时发出新的EXECUTEQUERY,请在启动新查询之前终止上一个查询。

当客户端发出GETDATA时,代码需要考虑所请求的查询可能不存在,例如它先前已过期且被ServerSessionEnd()释放。此外,如果查询确实存在且已完成,则在调用AResponseInfo.ContentStream.CopyFrom()时,您正在调用AResponseInfo.ContentStream,但ServerCommandGet()为nil。您有责任提供自己的ContentStream对象。因此,要么取得TSessionData内存流的所有权并将其指定为AResponseInfo.ContentStream对象,要么创建一个新的TMemoryStream进行复制,然后将其指定为{{ 1}}对象。无论哪种方式,AResponseInfo.ContentStream都会在将TIdHTTPServer发送给客户后将其释放。